Ich spiele mit Anfänger Haskell herum und wollte eine durchschnittliche Funktion schreiben. Es schien das Einfachste auf der Welt zu sein, oder?
Falsch.
Es scheint, als ob das Typensystem von Haskell es dem Durchschnitt verbietet, an einem generischen numerischen Typ zu arbeiten - ich kann ihn dazu bringen, an einer Liste von Integralen oder einer Liste von Brüchen zu arbeiten, aber nicht an beiden.
Ich will:
average :: (Num a, Fractional b) => [a] -> b
average xs = ...
Aber ich kann nur bekommen:
averageInt :: (Integral a, Fractional b) => [a] -> b
averageInt xs = fromIntegral (sum xs) / fromIntegral (length xs)
oder
averageFrac :: (Fractional a) => [a] -> a
averageFrac xs = sum xs / fromIntegral (length xs)
und der zweite scheint zu funktionieren. Bis ich versuche, eine Variable zu übergeben.
*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
Anscheinend ist Haskell sehr wählerisch in Bezug auf seine Typen. Das macht Sinn. Aber nicht, wenn sie beide [Num] sein könnten
Vermisse ich eine offensichtliche Anwendung von RealFrac?
Gibt es eine Möglichkeit, Integrale in Brüche zu zwingen, die nicht ersticken, wenn sie eine Bruchteileingabe erhalten?
Gibt es eine Möglichkeit, eine polymorphe Durchschnittsfunktion zu verwenden Either
und either
zu erstellen, die für jede Art von numerischem Array funktioniert?
Verbietet das Typensystem von Haskell, dass diese Funktion jemals existiert?
Haskell zu lernen ist wie Kalkül zu lernen. Es ist wirklich kompliziert und basiert auf Bergen der Theorie, und manchmal ist das Problem so verblüffend komplex, dass ich nicht einmal genug weiß, um die Frage richtig zu formulieren, sodass jede Einsicht sehr akzeptiert wird.
(Auch Fußnote: Dies basiert auf einem Hausaufgabenproblem. Alle sind sich einig, dass durchschnittliches Frac oben volle Punkte erhält, aber ich habe den schleichenden Verdacht, dass es eine Möglichkeit gibt, es sowohl auf Integral- als auch auf Fractional-Arrays zum Laufen zu bringen.)
Antworten:
Grundsätzlich sind Sie also durch die Art von (/) eingeschränkt:
(/) :: (Fractional a) => a -> a -> a
Übrigens möchten Sie auch Data.List.genericLength
genericLength :: (Num i) => [b] -> i
Wie wäre es also mit dem Entfernen von Integral für etwas Allgemeineres:
import Data.List average xs = realToFrac (sum xs) / genericLength xs
das hat nur eine echte Einschränkung (Int, Integer, Float, Double) ...
average :: (Real a, Fractional b) => [a] -> b
Das wird also jeden Real in einen Bruchteil bringen.
Und beachten Sie alle Poster, die von den polymorphen numerischen Literalen in Haskell erfasst werden. 1 ist keine ganze Zahl, es ist eine beliebige Zahl.
Die Real-Klasse bietet nur eine Methode: die Fähigkeit, einen Wert in der Klasse Num in einen rationalen Wert umzuwandeln. Welches ist genau das, was wir hier brauchen.
Und somit,
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
quelle
Die Frage wurde von Dons sehr gut beantwortet, ich dachte, ich könnte etwas hinzufügen.
Wenn Sie den Durchschnitt folgendermaßen berechnen:
average xs = realToFrac (sum xs) / genericLength xs
Ihr Code durchläuft die Liste zweimal, einmal, um die Summe seiner Elemente zu berechnen, und einmal, um ihre Länge zu ermitteln. Soweit ich weiß, ist GHC noch nicht in der Lage, dies zu optimieren und sowohl die Summe als auch die Länge in einem einzigen Durchgang zu berechnen.
Es tut selbst Anfängern nicht weh, darüber und über mögliche Lösungen nachzudenken. Beispielsweise könnte die Durchschnittsfunktion mit einer Falte geschrieben werden, die sowohl die Summe als auch die Länge berechnet. auf 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
Die Funktion sieht nicht so elegant aus, aber die Leistung ist besser.
Weitere Informationen auf Dons Blog:
http://donsbot.wordpress.com/2008/06/04/haskell-as-fast-as-c-working-at-a-high-altitude-for-low-level-performance/
quelle
foldl' (\(!b,!c) a -> ...
eines strengen Paartypsdata P a = P !a !a
ist in diesem Fall der Schlüssel für eine gute Leistung.Da Dons bei der Beantwortung Ihrer Frage so gute Arbeit geleistet hat, werde ich daran arbeiten, Ihre Frage zu stellen ...
Zum Beispiel in Ihrer Frage, in der Sie zuerst einen Durchschnitt für eine bestimmte Liste ermitteln und eine gute Antwort erhalten. Dann nehmen Sie genau die gleiche Liste , weisen sie einer Variablen zu und verwenden dann die Funktion Variable ..., die dann explodiert.
Was haben Sie in hier laufen ist ein Set-up in den Compiler, der DMR genannt: die D readed M onomorphic R estriction. Als Sie die Liste direkt an die Funktion übergeben haben, hat der Compiler keine Annahme darüber getroffen, um welchen Typ es sich bei den Zahlen handelt. Er hat lediglich abgeleitet, welche Typen auf der Verwendung basieren könnten, und dann einen ausgewählt, sobald das Feld nicht mehr eingegrenzt werden konnte. Es ist so etwas wie das direkte Gegenteil von Entenschreiben.
Wenn Sie die Liste einer Variablen zugewiesen haben, wurde der DMR aktiviert. Da Sie die Liste in eine Variable eingefügt haben, aber keine Hinweise darauf gegeben haben, wie Sie sie verwenden möchten, hat der DMR den Compiler dazu gebracht, einen Typ auszuwählen In diesem Fall wurde eine ausgewählt, die der Form entsprach und zu passen schien :
Integer
. Da Ihre Funktion in ihrer/
Operation keine Ganzzahl verwenden konnte (sie benötigt einen Typ in derFractional
Klasse), macht sie genau diese Beschwerde: Es gibt keine InstanzInteger
in derFractional
Klasse. Es gibt Optionen, die Sie in GHC festlegen können, damit Ihre Werte nicht in eine einzige Form ("monomorph", verstanden?) Erzwungen werden, bis dies erforderlich ist, aber es macht Fehlermeldungen etwas schwieriger, dies herauszufinden.In einem weiteren Punkt hatten Sie eine Antwort auf die Antwort von Dons, die mir aufgefallen ist:
Haskell tippt anders als Sie es gewohnt sind.
Real
undFloating
sind Typklassen, die eher wie Schnittstellen als wie Objektklassen funktionieren. Sie sagen Ihnen, was Sie mit einem Typ tun können, der zu dieser Klasse gehört, aber das bedeutet nicht, dass ein Typ keine anderen Dinge tun kann. Eine einzige Schnittstelle bedeutet nicht, dass eine Klasse (n OO-Stil) dies nicht kann habe andere.Ich würde sagen, Haskell zu lernen ist wie Schwedisch zu lernen - es gibt viele kleine, einfache Dinge (Buchstaben, Zahlen), die gleich aussehen und funktionieren, aber es gibt auch Wörter, die so aussehen, als sollten sie eine Sache bedeuten, wenn sie tatsächlich etwas bedeuten sonst. Aber sobald Sie es fließend beherrschen, werden Ihre regelmäßigen Freunde erstaunt sein, wie Sie dieses seltsame Zeug ausstoßen können, das wunderschöne Schönheiten dazu bringt, erstaunliche Tricks zu machen. Seltsamerweise sind in Haskell von Anfang an viele Leute involviert, die auch Schwedisch sprechen. Vielleicht ist diese Metapher mehr als nur eine Metapher ...
quelle
:m Data.List let list = [1..10] let average = div (sum list) (genericLength list) average
quelle
Ja, Haskells Typensystem ist sehr wählerisch. Das Problem hier ist die Art von fromIntegral:
Prelude> :t fromIntegral fromIntegral :: (Integral a, Num b) => a -> b
fromIntegral akzeptiert nur ein Integral als eine andere Art von Num. (/) akzeptiert dagegen nur Bruchteile. Wie bringen Sie die beiden zusammen?
Nun, die Summenfunktion ist ein guter Anfang:
Prelude> :t sum sum :: (Num a) => [a] -> a
Sum nimmt eine Liste aller Num und gibt eine Num zurück.
Ihr nächstes Problem ist die Länge der Liste. Die Länge ist ein Int:
Prelude> :t length length :: [a] -> Int
Sie müssen dieses Int auch in eine Num konvertieren. Das macht fromIntegral.
Jetzt haben Sie eine Funktion, die eine Num zurückgibt, und eine andere Funktion, die eine Num zurückgibt. Es gibt einige Regeln für die Typwerbung von Nummern, die Sie nachschlagen können , aber im Grunde können Sie jetzt loslegen:
Prelude> let average xs = (sum xs) / (fromIntegral (length xs)) Prelude> :t average average :: (Fractional a) => [a] -> a
Probieren wir es aus:
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
quelle