Les types Haskell frustrent 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 caractères de Haskell interdit à la moyenne de travailler sur un type numérique générique.

Je veux:

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

Mais je ne peux que recevoir:

 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> :tx x :: [Integer] *Main> averageFrac x :1:0: No instance for (Fractional Integer) arising from a use of `averageFrac ' at :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 deux être [Num]

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

Y a-t-il moyen de forcer Integrals dans Fractionnaires qui ne s’étouffe pas quand il reçoit une entrée fractionnaire?

Y a-t-il un moyen d’utiliser Either et either de créer une sorte de fonction moyenne polymorphe qui fonctionnerait sur n’importe quel type de tableau numérique?

Le système de caractères de Haskell interdit-il carrément cette fonction d’exister?

Apprendre le 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 que je ne sais même pas assez pour formuler correctement la question, de sorte que toute idée sera chaleureusement acceptée.

(En outre, note de bas de page: ceci est basé sur un problème de devoirs. Tout le monde est d’accord pour dire que averageFrac, ci-dessus, a des points, mais je pense qu’il y a un moyen de le faire fonctionner sur les tableaux Integral AND Fractional)

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

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

BTW, vous voulez aussi 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 réelle (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 qui se font attraper par les littéraux numériques polymorphes dans Haskell. 1 n’est pas un entier, c’est n’importe quel nombre.

La classe Real fournit une seule méthode: la possibilité de transformer une valeur de la classe Num en rationnelle. Ce qui 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 

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

En calculant la moyenne de cette façon:

average xs = realToFrac (sum xs) / genericLength xs

Ce que votre code fera, c’est parcourir la liste deux fois, une fois pour calculer la sum de ses éléments et une fois pour obtenir sa longueur. Pour autant que je sache, GHC n’est pas encore en mesure de l’optimiser et de calculer la sum et la longueur en un seul passage.

En tant que débutant, cela ne fait pas de mal d’y penser et de solutions possibles. Par exemple, la fonction moyenne peut être écrite en utilisant un pli qui calcule à la fois la sum 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 ne semble 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/

Puisque le don a fait un si bon travail pour répondre à votre question, je vais travailler sur la remise en question de votre question ….

Par exemple, dans votre question, où vous lancez d’abord une moyenne sur une liste donnée, vous obtenez une bonne réponse. Ensuite, vous prenez ce qui ressemble à la même liste , l’assignez à une variable, puis utilisez la fonction la variable … qui explose ensuite.

Ce que vous avez rencontré ici, c’est une configuration dans le compilateur, appelée DMR: la ressortingction syntaxique M onomorphique à lecture D. Lorsque vous avez passé la liste directement dans la fonction, le compilateur n’a fait aucune supposition sur le type des nombres, il a simplement déduit quels types il pourrait être basé sur l’utilisation, puis en a choisi un dès qu’il ne pouvait plus réduire le champ. C’est un peu comme l’opposé direct de la dactylographie, là.

Quoi qu’il en soit, lorsque vous avez assigné la liste à une variable, le DMR a été lancé. Puisque vous avez mis la liste dans une variable, mais sans donner de conseils sur son utilisation, le compilateur a choisi un type, cas, il en a choisi un qui correspondait à la forme et semblait correspondre: Integer . Comme votre fonction ne peut pas utiliser un entier dans son opération / (il a besoin d’un type dans la classe Fractional ), cela rend très difficile: 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 un seul formulaire (“mono-morphique”, obtenez-le?) Jusqu’à ce qu’il en ait besoin, mais cela rend les messages d’erreur un peu 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 les propriétés flottantes NOT héritées de Real, et j’ai ensuite supposé qu’elles ne partageraient aucun type en commun.

Haskell fait les types différemment de ce que vous avez l’habitude de faire. Real et Floating sont des classes de type, qui fonctionnent plus comme des interfaces que des classes d’objects. Ils vous disent ce que vous pouvez faire avec un type qui se trouve dans cette classe, mais cela ne signifie pas que certains types ne peuvent pas faire autre chose, pas plus qu’une interface ne signifie qu’une classe (de style OO) ne peut pas avoir d’autres.

Apprendre le 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 ressemblent et fonctionnent de la même manière, mais il y a aussi des mots qui veulent dire quelque chose autre. Mais une fois que vous maîsortingsez cela, vos amis réguliers seront étonnés de la façon dont vous pouvez lancer ce truc bizarre qui fait des trucs incroyables pour les plus belles. 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 métaphore…

 :m Data.List let list = [1..10] let average = div (sum list) (genericLength list) average 

Ouais, le système de caractères de Haskell est très difficile. Le problème ici est le type de fromIntegral:

 Prelude> :t fromIntegral fromIntegral :: (Integral a, Num b) => a -> b 

fromIntegral n’acceptera un Integral que comme un autre type de Num. (/), d’autre part n’accepte que les fractions. Comment allez-vous faire fonctionner les deux ensemble?

Eh bien, la fonction sum est un bon début:

 Prelude> :t sum sum :: (Num a) => [a] -> a 

Sum prend une liste de Num et retourne un Num.

Votre prochain problème est la longueur de la liste. La longueur est un Int:

 Prelude> :t length length :: [a] -> Int 

Vous devez également convertir cette Int en Num. C’est ce que fait Intégral.

Alors maintenant, vous avez une fonction qui retourne un Num et une autre fonction qui renvoie un Num. Il y a quelques règles pour la promotion de type des nombres que vous pouvez consulter , mais en gros, à ce stade, vous pouvez aller:

 Prelude> let average xs = (sum xs) / (fromIntegral (length xs)) Prelude> :t average average :: (Fractional a) => [a] -> a 

Faisons un essai:

 Prelude> average [1,2,3,4,5] 3.0 Prelude> average [1.2,3.4,5.6,7.8,9.0] 5.4 Prelude> average [1.2,3,4.5,6,7.8,9] 5.25