Ich bin verwirrt darüber, wie der Haskell-Compiler manchmal Typen ableitet, die weniger polymorph sind als erwartet, beispielsweise wenn punktfreie Definitionen verwendet werden.
Es scheint, dass das Problem die "Monomorphismus-Einschränkung" ist, die bei älteren Versionen des Compilers standardmäßig aktiviert ist.
Betrachten Sie das folgende Haskell-Programm:
{-# LANGUAGE MonomorphismRestriction #-}
import Data.List(sortBy)
plus = (+)
plus' x = (+ x)
sort = sortBy compare
main = do
print $ plus' 1.0 2.0
print $ plus 1.0 2.0
print $ sort [3, 1, 2]
Wenn ich dies mit kompiliere ghc
, erhalte ich keine Fehler und die Ausgabe der ausführbaren Datei ist:
3.0
3.0
[1,2,3]
Wenn ich den main
Körper ändere zu:
main = do
print $ plus' 1.0 2.0
print $ plus (1 :: Int) 2
print $ sort [3, 1, 2]
Ich erhalte keine Fehler bei der Kompilierung und die Ausgabe wird:
3.0
3
[1,2,3]
wie erwartet. Wenn ich jedoch versuche, es zu ändern in:
main = do
print $ plus' 1.0 2.0
print $ plus (1 :: Int) 2
print $ plus 1.0 2.0
print $ sort [3, 1, 2]
Ich erhalte einen Tippfehler:
test.hs:13:16:
No instance for (Fractional Int) arising from the literal ‘1.0’
In the first argument of ‘plus’, namely ‘1.0’
In the second argument of ‘($)’, namely ‘plus 1.0 2.0’
In a stmt of a 'do' block: print $ plus 1.0 2.0
Das gleiche passiert, wenn Sie versuchen, sort
zweimal mit verschiedenen Typen anzurufen :
main = do
print $ plus' 1.0 2.0
print $ plus 1.0 2.0
print $ sort [3, 1, 2]
print $ sort "cba"
erzeugt den folgenden Fehler:
test.hs:14:17:
No instance for (Num Char) arising from the literal ‘3’
In the expression: 3
In the first argument of ‘sort’, namely ‘[3, 1, 2]’
In the second argument of ‘($)’, namely ‘sort [3, 1, 2]’
- Warum denkt man
ghc
plötzlich, dassplus
das nicht polymorph ist und einInt
Argument erfordert ? Der einzige HinweisInt
ist in einer Anwendung vonplus
, wie kann das wichtig sein, wenn die Definition eindeutig polymorph ist? - Warum denkt
ghc
plötzlich, dass diessort
eineNum Char
Instanz erfordert ?
Außerdem, wenn ich versuche, die Funktionsdefinitionen in einem eigenen Modul zu platzieren, wie in:
{-# LANGUAGE MonomorphismRestriction #-}
module TestMono where
import Data.List(sortBy)
plus = (+)
plus' x = (+ x)
sort = sortBy compare
Beim Kompilieren wird folgende Fehlermeldung angezeigt:
TestMono.hs:10:15:
No instance for (Ord a0) arising from a use of ‘compare’
The type variable ‘a0’ is ambiguous
Relevant bindings include
sort :: [a0] -> [a0] (bound at TestMono.hs:10:1)
Note: there are several potential instances:
instance Integral a => Ord (GHC.Real.Ratio a)
-- Defined in ‘GHC.Real’
instance Ord () -- Defined in ‘GHC.Classes’
instance (Ord a, Ord b) => Ord (a, b) -- Defined in ‘GHC.Classes’
...plus 23 others
In the first argument of ‘sortBy’, namely ‘compare’
In the expression: sortBy compare
In an equation for ‘sort’: sort = sortBy compare
- Warum ist nicht der
ghc
Lage , den polymorphen Typ verwendenOrd a => [a] -> [a]
fürsort
? - Und warum
ghc
behandeltplus
undplus'
anders?plus
sollte den polymorphen Typ habenNum a => a -> a -> a
und ich sehe nicht wirklich, wie sich dieser vom Typ unterscheidetsort
und dennoch nursort
einen Fehler auslöst.
Letzte Sache: Wenn ich die Definition sort
der Datei kommentiere, wird sie kompiliert. Wenn ich jedoch versuche, es zu laden ghci
und die Typen zu überprüfen, die ich erhalte:
*TestMono> :t plus
plus :: Integer -> Integer -> Integer
*TestMono> :t plus'
plus' :: Num a => a -> a -> a
Warum ist der Typ nicht plus
polymorph?
Dies ist die kanonische Frage zur Monomorphismusbeschränkung in Haskell, wie in der Meta-Frage erörtert .
Antworten:
Was ist die Monomorphismusbeschränkung?
Die im Haskell-Wiki angegebene Monomorphismus-Einschränkung lautet:
Dies bedeutet, dass der Compiler unter bestimmten Umständen , wenn Ihr Typ mehrdeutig (dh polymorph) ist , diesen Typ auf etwas nicht mehrdeutiges instanziiert .
Wie behebe ich das?
Zunächst können Sie immer explizit eine Typensignatur angeben, um das Auslösen der Einschränkung zu vermeiden:
plus :: Num a => a -> a -> a plus = (+) -- Okay! -- Runs as: Prelude> plus 1.0 1 2.0
Wenn Sie eine Funktion definieren, können Sie alternativ einen punktfreien Stil vermeiden und beispielsweise schreiben:
plus x y = x + y
Ausschalten
Sie können die Einschränkung einfach deaktivieren, damit Sie nichts an Ihrem Code ändern müssen, um sie zu beheben. Das Verhalten wird durch zwei Erweiterungen gesteuert:
MonomorphismRestriction
Aktiviert es (dies ist die Standardeinstellung) undNoMonomorphismRestriction
deaktiviert es.Sie können die folgende Zeile ganz oben in Ihre Datei einfügen:
{-# LANGUAGE NoMonomorphismRestriction #-}
Wenn Sie GHCi verwenden, können Sie die Erweiterung mit dem folgenden
:set
Befehl aktivieren :Prelude> :set -XNoMonomorphismRestriction
Sie können
ghc
die Erweiterung auch über die Befehlszeile aktivieren:ghc ... -XNoMonomorphismRestriction
Hinweis: Sie sollten die erste Option der Auswahl der Erweiterung über Befehlszeilenoptionen vorziehen.
Auf der Seite von GHC finden Sie eine Erläuterung dieser und anderer Erweiterungen.
Eine vollständige Erklärung
Ich werde versuchen, im Folgenden alles zusammenzufassen, was Sie wissen müssen, um zu verstehen, was die Monomorphismusbeschränkung ist, warum sie eingeführt wurde und wie sie sich verhält.
Ein Beispiel
Nehmen Sie die folgende triviale Definition:
plus = (+)
Sie würden denken, in der Lage zu sein, jedes Vorkommen von
+
durch zu ersetzenplus
. Insbesondere , da(+) :: Num a => a -> a -> a
würde man erwarten , dass auch habenplus :: Num a => a -> a -> a
.Dies ist leider nicht der Fall. Zum Beispiel versuchen wir in GHCi Folgendes:
Prelude> let plus = (+) Prelude> plus 1.0 1
Wir erhalten folgende Ausgabe:
Möglicherweise müssen Sie
:set -XMonomorphismRestriction
in neueren GHCi-Versionen.Und tatsächlich können wir sehen, dass die Art von
plus
nicht das ist, was wir erwarten würden:Prelude> :t plus plus :: Integer -> Integer -> Integer
Was passiert ist, ist, dass der Compiler sah, dass
plus
TypNum a => a -> a -> a
, ein polymorpher Typ. Darüber hinaus fällt die obige Definition unter die Regeln, die ich später erläutern werde, und so hat er beschlossen, den Typ monomorph zu machen, indem er die Typvariable standardmäßig verwendeta
. Die Standardeinstellung ist,Integer
wie wir sehen können.Beachten Sie, dass beim Versuch , den obigen Code mit zu kompilieren
ghc
, keine Fehler auftreten. Dies liegt daran, wie die interaktiven Definitionenghci
behandelt werden (und behandelt werden müssen ). Grundsätzlich kann jede Anweisung eingegeben inghci
müssen vollständig geprüft Typ vor Folgendes berücksichtigt wird; Mit anderen Worten, es ist, als ob jede Anweisung in einem separaten Modul wäre . Später werde ich erklären, warum das so ist.Ein anderes Beispiel
Betrachten Sie die folgenden Definitionen:
f1 x = show x f2 = \x -> show x f3 :: (Show a) => a -> String f3 = \x -> show x f4 = show f5 :: (Show a) => a -> String f5 = show
Wir würden erwarten, dass sich alle diese Funktionen gleich verhalten und den gleichen Typ haben, dh den Typ
show
:Show a => a -> String
.Beim Zusammenstellen der obigen Definitionen erhalten wir jedoch die folgenden Fehler:
Also
f2
undf4
nicht kompilieren. Darüber hinaus , wenn sie versuchen , diese Funktion in GHCi wir bekommen definieren keine Fehler , aber der Typ fürf2
undf4
ist() -> String
!Monomorphie Einschränkung ist , was macht
f2
undf4
einen monomorphen Typ erfordern, und das unterschiedliche Verhalten bewteenghc
undghci
ist aufgrund unterschiedlicher säumige Regeln .Wann passiert es?
In Haskell gibt es, wie im Bericht definiert , zwei verschiedene Arten von Bindungen . Funktionsbindungen und Musterbindungen. Eine Funktionsbindung ist nichts anderes als eine Definition einer Funktion:
f x = x + 1
Beachten Sie, dass ihre Syntax lautet:
Modulo Wachen und
where
Erklärungen. Aber sie spielen keine Rolle.wo es mindestens ein Argument geben muss .
Eine Musterbindung ist eine Deklaration des Formulars:
Wieder Modulo Wachen.
Beachten Sie, dass Variablen Muster sind , also die Bindung:
plus = (+)
ist eine Musterbindung . Es bindet das Muster
plus
(eine Variable) an den Ausdruck(+)
.Wenn eine Musterbindung nur aus einem Variablennamen besteht, spricht man von einer einfachen Musterbindung.
Die Monomorphismus-Einschränkung gilt für einfache Musterbindungen!
Nun, formal sollten wir das sagen:
Abschnitt 4.5.1 des Berichts .
Und dann (Abschnitt 4.5.5 des Berichts ):
Beispiele von mir hinzugefügt.
Eine eingeschränkte Deklarationsgruppe ist also eine Gruppe, in der entweder nicht einfache Musterbindungen (z. B.
(x:xs) = f something
oder(f, g) = ((+), (-))
) oder eine einfache Musterbindung ohne Typensignatur (wie inplus = (+)
) vorhanden sind.Die Monomorphismusbeschränkung betrifft eingeschränkte Deklarationsgruppen.
Meistens definieren Sie keine gegenseitigen rekursiven Funktionen und daher wird eine Deklarationsgruppe nur zu einer Bindung.
Was tut es?
Die Monomorphismusbeschränkung wird in Abschnitt 4.5.5 des Berichts durch zwei Regeln beschrieben .
Erste Regel
Der hervorgehobene Teil ist das, was die Monomorphismusbeschränkung einführt. Es heißt, wenn der Typ polymorph ist (dh eine Typvariable enthält) und diese Typvariable eingeschränkt ist (dh eine Klassenbeschränkung hat: z. B. ist der Typ
Num a => a -> a -> a
polymorph, weil er enthält,a
und auch eingeschränkt, weil der Typa
die EinschränkungNum
darüber hat .) dann kann es nicht verallgemeinert werden.In einfachen Worten bedeutet nicht verallgemeinern , dass die Verwendung der Funktion
plus
ihren Typ ändern kann.Wenn Sie die Definitionen hätten:
plus = (+) x :: Integer x = plus 1 2 y :: Double y = plus 1.0 2
dann würden Sie einen Tippfehler bekommen. Denn wenn der Compiler sieht, dass
plus
über einInteger
in der Deklaration von aufgerufenx
wird, wird die Typvariablea
mit vereinheitlichtInteger
und daher wird der Typ vonplus
:Integer -> Integer -> Integer
Wenn dann die Definition von typ überprüft
y
wird,plus
wird angezeigt, dass sie auf einDouble
Argument angewendet wird und die Typen nicht übereinstimmen.Beachten Sie, dass Sie weiterhin verwenden können,
plus
ohne einen Fehler zu erhalten:plus = (+) x = plus 1.0 2
In diesem Fall
plus
wird zuerst auf den Typ von geschlossen ,Num a => a -> a -> a
aber dann wird seine Verwendung in der Definition vonx
, wo1.0
eineFractional
Einschränkung erforderlich ist , in geändertFractional a => a -> a -> a
.Begründung
Der Bericht sagt:
Für diesen Punkt ist das Beispiel aus dem Wiki meines Erachtens klarer. Betrachten Sie die Funktion:
f xs = (len, len) where len = genericLength xs
Wenn
len
polymorphf
wäre, wäre die Art von :f :: Num a, Num b => [c] -> (a, b)
Die beiden Elemente des Tupels
(len, len)
können also tatsächlich unterschiedliche Werte sein! Dies bedeutet jedoch, dass die Berechnung von wiederholt werdengenericLength
muss , um die beiden unterschiedlichen Werte zu erhalten.Das Grundprinzip hier ist: Der Code enthält einen Funktionsaufruf, aber wenn diese Regel nicht eingeführt wird, können zwei versteckte Funktionsaufrufe erzeugt werden, was nicht intuitiv ist.
Mit der Monomorphismus-Beschränkung wird die Art von
f
:f :: Num a => [b] -> (a, a)
Auf diese Weise muss die Berechnung nicht mehrmals durchgeführt werden.
Ich glaube, dieses Beispiel ist selbsterklärend. Es gibt Situationen, in denen die Nichtanwendung der Regel zu einer Mehrdeutigkeit des Typs führt.
Wenn Sie die Erweiterung wie oben vorgeschlagen deaktivieren, wird beim Kompilieren der obigen Deklaration ein Typfehler angezeigt. Dies ist jedoch kein wirkliches Problem: Sie wissen bereits, dass
read
Sie dem Compiler bei der Verwendung irgendwie mitteilen müssen, welchen Typ er zu analysieren versuchen soll ...Zweite Regel
Das bedeutet, dass. Wenn Sie Ihre übliche Definition haben:
plus = (+)
Dies hat einen Typ,
Num a => a -> a -> a
bei dema
es sich aufgrund der oben beschriebenen Regel 1 um eine monomorphe Typvariable handelt. Sobald das gesamte Modul abgeleitet ist, wählt der Compiler einfach einen Typ aus, der diesena
gemäß den Standardregeln ersetzt.Das Endergebnis ist :
plus :: Integer -> Integer -> Integer
.Beachten Sie, dass dies erfolgt, nachdem das gesamte Modul abgeleitet wurde.
Dies bedeutet, wenn Sie die folgenden Erklärungen haben:
plus = (+) x = plus 1.0 2.0
innerhalb eines Moduls, bevor geben Sie den Typ des säumigen
plus
wird:Fractional a => a -> a -> a
(siehe Regel 1 , warum dies geschieht). An diesem Punkt wird das Befolgen der Standardregelna
durch ersetztDouble
und wir habenplus :: Double -> Double -> Double
undx :: Double
.Standardmäßig
Wie bereits erwähnt , bevor einige gibt es säumige Regeln, beschrieben in Abschnitt 4.3.4 des Berichts , dass die Rückschließer annehmen können , und das wird einen polymorphen Typen mit einem monomorphic ersetzen. Dies geschieht immer dann, wenn ein Typ nicht eindeutig ist .
Zum Beispiel im Ausdruck:
let x = read "<something>" in show x
hier ist der Ausdruck mehrdeutig, weil die Typen für
show
undread
sind:show :: Show a => a -> String read :: Read a => String -> a
Das
x
hat also TypRead a => a
. Aber diese Einschränkung wird durch eine Menge von Arten erfüllt ist :Int
,Double
oder()
zum Beispiel. Welches soll ich wählen? Nichts kann uns sagen.In diesem Fall können wir die Mehrdeutigkeit beheben, indem wir dem Compiler mitteilen, welchen Typ wir möchten, und eine Typensignatur hinzufügen:
let x = read "<something>" :: Int in show x
Das Problem ist nun: Da Haskell die
Num
Typklasse verwendet, um Zahlen zu verarbeiten, gibt es viele Fälle, in denen numerische Ausdrücke Mehrdeutigkeiten enthalten.Erwägen:
show 1
Was soll das Ergebnis sein?
Wie zuvor
1
hat TypNum a => a
und es gibt viele Arten von Zahlen, die verwendet werden könnten. Welches soll ich wählen?Fast jedes Mal, wenn wir eine Zahl verwenden, einen Compilerfehler zu haben, ist keine gute Sache, und daher wurden die Standardregeln eingeführt. Die Regeln können mithilfe einer
default
Deklaration gesteuert werden . Durch Angabe könnendefault (T1, T2, T3)
wir ändern, wie der Inferencer die verschiedenen Typen standardmäßig verwendet.Eine mehrdeutige Typvariable
v
ist standardmäßig verfügbar, wenn:v
erscheint nur in Beschränkungen der Art, in derC v
es sichC
um eine Klasse handelt (dh wenn es wieMonad (m v)
folgt aussieht : dann ist es nicht standardmäßig).Num
oder eine Unterklasse vonNum
.Eine voreingestellte Typvariable wird durch den ersten Typ in der
default
Liste ersetzt, der eine Instanz aller Klassen der mehrdeutigen Variablen ist.Die Standarddeklaration
default
lautetdefault (Integer, Double)
.Zum Beispiel:
plus = (+) minus = (-) x = plus 1.0 1 y = minus 2 1
Die abgeleiteten Typen wären:
plus :: Fractional a => a -> a -> a minus :: Num a => a -> a -> a
die standardmäßig zu folgenden Regeln werden:
plus :: Double -> Double -> Double minus :: Integer -> Integer -> Integer
Beachten Sie, dass dies erklärt, warum im Beispiel in der Frage nur die
sort
Definition einen Fehler auslöst. Der TypOrd a => [a] -> [a]
kann nicht als Standard festgelegt werden, daOrd
es sich nicht um eine numerische Klasse handelt.Erweiterte Standardeinstellung
Beachten Sie, dass GHCi kommt mit erweiterten säumigen Regeln (oder hier für GHC8 ), die in Dateien aktiviert werden können , als auch die Verwendung von
ExtendedDefaultRules
Erweiterungen.Der ausfall Typ Variablen muß nicht nur in contraints erscheinen , wo alle Klassen Standard und es muss mindestens eine Klasse sein , die unter ist
Eq
,Ord
,Show
oderNum
und ihre Unterklassen.Darüber hinaus
default
lautet die Standarddeklarationdefault ((), Integer, Double)
.Dies kann zu merkwürdigen Ergebnissen führen. Nehmen Sie das Beispiel aus der Frage:
In ghci erhalten wir keinen Typfehler, aber die
Ord a
Einschränkungen führen zu einer Standardeinstellung,()
die so gut wie nutzlos ist.Nützliche Links
Es gibt viele Ressourcen und Diskussionen über die Monomorphismusbeschränkung.
Hier sind einige Links, die ich nützlich finde und die Ihnen helfen können, das Thema zu verstehen oder weiter zu vertiefen:
quelle