70 votes

Les types de Haskell frustrant une simple fonction 'moyenne'

Je suis en train de jouer autour avec débutant Haskell, et j'ai voulu écrire une fonction moyenne. Cela semblait être la chose la plus simple du monde, non?

Mal.

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

Je veux:

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

Mais je ne peux faire:

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 à propos de ses types. Qui fait sens. Mais pas quand ils pourraient à la fois être [Num]

Suis-je en manque d'une application évidente de RealFrac?

Est-il moyen de forcer les Intégrales en Fractionals qui ne veut pas s'étouffer quand il obtient une fraction d'entrée?

Est-il un moyen d'utiliser Either et either de faire une sorte de polymorphe fonction moyenne qui serait à l'œuvre sur tout type de tableau numérique?

Ne Haskell type de système de carrément interdire cette fonction déjà existante?

Apprendre Haskell, c'est comme apprendre le Calcul. C'est vraiment compliqué et basé sur les montagnes de la théorie, et parfois, le problème est donc mindbogglingly complexe que je ne sais même pas assez pour une phrase correctement à la question, de sorte que toute la perspicacité seront chaleureusement accueillis.

(Aussi, note de bas de page: c'est un problème. Tout le monde est d'accord que averageFrac, ci-dessus, obtient la totalité des points, mais j'ai l'intuition qu'il existe un moyen de le faire fonctionner sur les deux Intégrales ET de la fraction de tableaux)

97voto

Don Stewart Points 94361

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

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

BTW, vous aussi vous voulez de Données.Liste.genericLength

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

Alors, comment sur la suppression de la 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

Alors, que vais prendre tout Réel dans les Fractions.

Et de noter toutes les affiches de se faire attraper par le polymorphe littéraux numériques en Haskell. 1 n'est pas un entier, il est un certain nombre.

La Vraie classe ne fournit qu'une seule méthode: la capacité de transformer une valeur dans la classe Num de façon rationnelle. Ce qui est exactement ce dont nous avons besoin ici.

Et donc,

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

25voto

David V. Points 3065

La question a été très bien répondu par des Dons, j'ai pensé que je pourrais ajouter quelque chose.

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

average xs = realToFrac (sum xs) / genericLength xs

Ce que votre code est le parcours de la liste, une fois pour calculer la somme de ses éléments, et une fois pour obtenir sa longueur. Autant que je sache, GHC n'est pas encore en mesure d'optimiser ce et de calculer à la fois la somme et de la longueur en un seul passage.

Il ne fait pas de mal, même en tant que débutant à penser à ce sujet et les solutions possibles, par exemple la moyenne de la fonction peut être écrite à l'aide d'un pli qui calcule à la fois la somme et de 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'a pas l'air aussi élégante, mais la performance est meilleure.

Plus d'informations sur les Dons blog:

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

7voto

BMeph Points 907

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

Par exemple, dans votre question, lorsque vous exécutez une moyenne sur une liste donnée, l'obtention d'une bonne réponse. Ensuite, vous prenez ce qui ressemble exactement la même liste, l'affecter à une variable, puis d'utiliser la fonction de la variable...qui ensuite exploser.

Ce que vous avez ici est un set-up dans le compilateur, appelé le DMR: le D lus M onomorphic R estriction. Lorsque vous avez franchi la liste directement dans la fonction, le compilateur fait aucune hypothèse sur le type les chiffres étaient, il vient de déduire quels types il pourrait être basé sur l'utilisation, puis repris, une fois qu'il ne pouvait pas réduire le champ de plus. C'est un peu comme le contraire de canard-typage, là.

Toute façon, une fois que vous avez attribué à la liste à une variable, le DMR des coups de pied. Depuis que vous avez mis la liste dans une variable, mais étant donné aucune indication sur la façon dont vous voulez l'utiliser, le DMR fait le compilateur de choisir un type, dans ce cas, il en pris un et qui correspondent à la forme et semble s'adapter: Integer. Depuis votre fonction ne pouvais pas utiliser un nombre Entier dans ses / de l'opération (il a besoin d'un type dans l' Fractional de la classe), il le fait très plainte: il n'y a pas d'instance de Integer dans la Fractional classe. Il y a des options que vous pouvez définir dans GHC, de sorte qu'il n'a pas la force de vos valeurs dans un formulaire unique ("mono-morphiques", l'obtenir?) jusqu'à ce qu'il doit, mais il en fait des messages d'erreur légèrement plus difficile à comprendre.

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

J'ai été induit en erreur par le tableau de la dernière page de cs.ut.ee/~varmo/MFP2004/PreludeTour.pdf qui montre Flottant de ne PAS hériter des propriétés de Vrai, et j'ai alors supposé que ils n'aient pas de types en commun.

Haskell n'types différemment de ce que vous êtes habitué. Real et Floating sont des classes de type, qui fonctionnent plus comme des interfaces de classes d'objets. Ils vous disent ce que vous pouvez faire avec un type qui est dans la classe, mais cela ne veut pas dire qu'un certain type ne peut pas faire d'autres choses, plus que d'avoir une interface signifie que a(n OO-style) de classe ne peut pas avoir toute les autres.

Apprendre Haskell est comme l'apprentissage du Calcul

Je dirais apprendre Haskell, c'est comme apprendre le suédois - il y a beaucoup de petites choses simples (lettres, chiffres) qui fonctionnent de la même, mais il y a aussi les mots qui se ressemblent, ils devraient signifier qu'une chose, quand ils signifient réellement quelque chose d'autre. Mais une fois que vous obtenez connaissant bien, vos amis seront surpris à la façon dont vous pouvez le bec hors de cette excentrique choses qui les rend magnifiques beautés faire des trucs incroyables. Curieusement, il ya beaucoup de gens impliqués dans Haskell, dès les commencements, qui savent aussi suédois. Peut-être que la métaphore est plus qu'une métaphore...

3voto

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

-6voto

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

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

fromIntegral sera seulement accepter Intégrante comme un, pas un autre genre de Num. (/), d'autre part accepte uniquement les fractions. Comment allez-vous faire les deux fonctionnent ensemble?

Ainsi, la fonction somme est un bon début:

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

Somme prend une liste de Num et renvoie un Nombre.

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

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

Vous avez besoin de les convertir Int dans un Num. C'est ce que fromIntegral.

Alors maintenant, vous avez une fonction qui renvoie un nombre et une autre fonction qui renvoie un Nombre. Il y a quelques règles pour la promotion du type de numéros , vous pouvez regarder, mais, fondamentalement, à ce stade, vous êtes bon pour aller:

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

Nous allons lui donner 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X