web-dev-qa-db-fra.com

Types de Haskell frustrant une simple fonction «moyenne»

Je joue avec le débutant Haskell et je voulais écrire une fonction moyenne. Cela semblait être la chose la plus simple au monde, non?

Faux.

Il semble que le système de types de Haskell interdit à la moyenne de travailler sur un type numérique générique - je peux le faire fonctionner sur une liste d'intégrales ou une liste de fractionnaires, mais pas les deux.

Je voudrais:

average :: (Num a, Fractional b) => [a] -> b
average xs = ...

Mais je ne peux qu'obtenir:

averageInt :: (Integral a, Fractional b) => [a] -> b
averageInt xs = fromIntegral (sum xs) / fromIntegral (length xs)

ou

averageFrac :: (Fractional a) => [a] -> a
averageFrac xs = sum xs / fromIntegral (length xs)

et le second semble fonctionner. Jusqu'à ce que j'essaie de passer une variable.

*Main> averageFrac [1,2,3]
2.0
*Main> let x = [1,2,3]
*Main> :t x
x :: [Integer]
*Main> averageFrac x

<interactive>:1:0:
    No instance for (Fractional Integer)
      arising from a use of `averageFrac ' at <interactive>:1:0-8
    Possible fix: add an instance declaration for (Fractional Integer)
    In the expression: average x
    In the definition of `it': it = averageFrac x

Apparemment, Haskell est vraiment pointilleux sur ses types. Ça a du sens. Mais pas quand ils pourraient tous les deux être [Num]

Est-ce que je manque une application évidente de RealFrac?

Existe-t-il un moyen de contraindre les intégrales en fractionnaires qui ne s'étouffent pas lorsqu'elles reçoivent une entrée fractionnelle?

Existe-t-il un moyen d'utiliser Either et either pour créer une sorte de fonction moyenne polymorphe qui fonctionnerait sur n'importe quelle sorte de tableau numérique?

Le système de type de Haskell interdit-il purement et simplement cette fonction d'exister?

Apprendre Haskell, c'est comme apprendre le calcul. C'est vraiment compliqué et basé sur des montagnes de théorie, et parfois le problème est tellement complexe d'esprit que je n'en sais même pas assez pour formuler correctement la question, donc tout aperçu sera chaleureusement accepté.

(En outre, note de bas de page: cela est basé sur un problème de devoirs. Tout le monde convient que la moyenne de Frac, ci-dessus, obtient des points complets, mais je soupçonne qu'il existe un moyen de le faire fonctionner à la fois sur les tableaux intégrés ET fractionnaires)

67
jakebman

Donc, fondamentalement, vous êtes contraint par le type de (/):

(/) :: (Fractional a) => a -> a -> a

BTW, vous voulez également Data.List.genericLength

genericLength :: (Num i) => [b] -> i

Alors, que diriez-vous de supprimer fromIntegral pour quelque chose de plus général:

import Data.List

average xs = realToFrac (sum xs) / genericLength xs

qui n'a qu'une contrainte Real (Int, Integer, Float, Double) ...

average :: (Real a, Fractional b) => [a] -> b

Donc, cela va prendre n'importe quel réel dans n'importe quel fractionnaire.

Et notez toutes les affiches se faire attraper par les littéraux numériques polymorphes de Haskell. 1 n'est pas un entier, c'est n'importe quel nombre.

La classe Real ne propose qu'une seule méthode: la possibilité de transformer une valeur de la classe Num en rationnelle. C'est exactement ce dont nous avons besoin ici.

Et ainsi,

Prelude> average ([1 .. 10] :: [Double])
5.5
Prelude> average ([1 .. 10] :: [Int])
5.5
Prelude> average ([1 .. 10] :: [Float])
5.5
Prelude> average ([1 .. 10] :: [Data.Word.Word8])
5.5
94
Don Stewart

La question a été très bien répondu par Dons, je pensais que je pourrais ajouter quelque chose.

Lors du calcul de la moyenne de cette façon:

average xs = realToFrac (sum xs) / genericLength xs

Votre code va parcourir deux fois la liste, une fois pour calculer la somme de ses éléments et une fois pour obtenir sa longueur. Pour autant que je sache, GHC n'est pas encore en mesure d'optimiser cela et de calculer la somme et la longueur en une seule passe.

Cela ne fait pas de mal même en tant que débutant d'y penser et des solutions possibles, par exemple la fonction moyenne peut être écrite en utilisant un pli qui calcule à la fois la somme et la longueur; sur ghci:

:set -XBangPatterns

import Data.List

let avg l=let (t,n) = foldl' (\(!b,!c) a -> (a+b,c+1)) (0,0) l in realToFrac(t)/realToFrac(n)

avg ([1,2,3,4]::[Int])
2.5
avg ([1,2,3,4]::[Double])
2.5

La fonction n'est pas aussi élégante, mais les performances sont meilleures.

Plus d'informations sur le blog Dons:

http://donsbot.wordpress.com/2008/06/04/haskell-as-fast-as-c-working-at-a-high-altitude-for-low-level-performance/ =

25
David V.

Puisque dons a fait un si bon travail pour répondre à votre question, je vais travailler sur le questionnement de votre question ....

Par exemple, dans votre question, où vous exécutez d'abord une moyenne sur une liste donnée, obtenez une bonne réponse. Ensuite, vous prenez ce qui ressemble exactement à la même liste, affectez-le à une variable, puis utilisez la fonction la variable ... qui explose ensuite.

Ce que vous avez rencontré ici est une configuration dans le compilateur, appelée DMR: le D lu M onomorphe R restriction. Lorsque vous avez passé la liste directement dans la fonction, le compilateur n'a fait aucune hypothèse sur le type des nombres, il a simplement déduit de quels types il pouvait être basé sur l'utilisation, puis en a choisi un une fois qu'il ne pouvait plus réduire le champ. C'est un peu comme l'opposé direct de la frappe de canard, là.

Quoi qu'il en soit, lorsque vous avez affecté la liste à une variable, le DMR est entré en jeu. Puisque vous avez placé la liste dans une variable, mais sans donner d'indications sur la façon dont vous voulez l'utiliser, le DMR a demandé au compilateur de choisir un type, dans ce cas, il en a choisi un qui correspondait à la forme et semblait correspondre: Integer. Étant donné que votre fonction ne peut pas utiliser un entier dans son / opération (il a besoin d'un type dans la classe Fractional), elle se plaint: il n'y a pas d'instance de Integer dans la classe Fractional. Il y a des options que vous pouvez définir dans GHC pour qu'il ne force pas vos valeurs dans une seule forme ("mono-morphique", vous obtenez?) Jusqu'à ce qu'il en ait besoin, mais cela rend les messages d'erreur légèrement plus difficiles à comprendre.

Maintenant, sur une autre note, vous avez eu une réponse à la réponse de dons qui a attiré mon attention:

J'ai été induit en erreur par le graphique de la dernière page de cs.ut.ee/~varmo/MFP2004/PreludeTour.pdf qui montre Floating NOT héritant des propriétés de Real, et j'ai ensuite supposé qu'ils ne partageraient aucun type en commun.

Haskell fait des types différents de ce à quoi vous êtes habitué. Real et Floating sont des classes de type, qui fonctionnent plus comme des interfaces que des classes d'objets. Ils vous disent ce que vous pouvez faire avec un type qui est dans cette classe, mais cela ne signifie pas qu'un type ne peut pas faire autre chose, pas plus qu'une interface ne signifie qu'une classe (n style OO) ne peut pas en avoir d'autres.

Apprendre Haskell, c'est comme apprendre le calcul

Je dirais qu'apprendre Haskell, c'est comme apprendre le suédois - il y a beaucoup de petites choses simples (lettres, chiffres) qui se ressemblent et fonctionnent de la même façon, mais il y a aussi des mots qui semblent vouloir signifier une chose, alors qu'ils signifient réellement quelque chose autre. Mais une fois que vous en parlerez couramment, vos amis habitués seront étonnés de la façon dont vous pouvez faire jaillir ces trucs bizarres qui font que les beautés magnifiques font des tours incroyables. Curieusement, il y a beaucoup de gens impliqués dans Haskell depuis les débuts, qui connaissent aussi le suédois. Peut-être que cette métaphore est plus qu'une simple métaphore ...

7
BMeph
:m Data.List
let list = [1..10]
let average = div (sum list) (genericLength list)
average
3
had same problem