Kann mir jemand sagen, warum das Haskell-Präludium zwei separate Funktionen für die Potenzierung definiert (dh ^
und **
)? Ich dachte, das Typensystem sollte diese Art der Vervielfältigung beseitigen.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
Kann mir jemand sagen, warum das Haskell-Präludium zwei separate Funktionen für die Potenzierung definiert (dh ^
und **
)? Ich dachte, das Typensystem sollte diese Art der Vervielfältigung beseitigen.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
Antworten:
Es gibt eigentlich drei Potenzierung Betreiber: (^)
, (^^)
und (**)
. ^
ist eine nicht negative integrale Exponentiation, ^^
eine ganzzahlige Exponentiation und **
eine Gleitkomma-Exponentiation:
(^) :: (Num a, Integral b) => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a
Der Grund ist die Typensicherheit: Ergebnisse numerischer Operationen haben im Allgemeinen den gleichen Typ wie die Eingabeargumente. Sie können jedoch keine Int
auf eine Gleitkomma-Potenz erhöhen und ein typisches Ergebnis erhalten Int
. Das Typsystem verhindert dies und verhindert (1::Int) ** 0.5
einen Typfehler. Das gilt auch für (1::Int) ^^ (-1)
.
Eine andere Möglichkeit, dies auszudrücken: Num
Typen werden unter geschlossen ^
(sie müssen keine multiplikative Inverse haben), Fractional
Typen werden unter geschlossen ^^
, Floating
Typen werden unter geschlossen **
. Da es keine Fractional
Instanz für gibt Int
, können Sie sie nicht auf eine negative Potenz erhöhen.
Idealerweise wäre das zweite Argument von ^
statisch darauf beschränkt, nicht negativ zu sein ( 1 ^ (-2)
löst derzeit eine Laufzeitausnahme aus). Aber es gibt keinen Typ für natürliche Zahlen in der Prelude
.
Das Typsystem von Haskell ist nicht leistungsfähig genug, um die drei Potenzierungsoperatoren als einen auszudrücken. Was Sie wirklich wollen, ist ungefähr so:
class Exp a b where (^) :: a -> b -> a
instance (Num a, Integral b) => Exp a b where ... -- current ^
instance (Fractional a, Integral b) => Exp a b where ... -- current ^^
instance (Floating a, Floating b) => Exp a b where ... -- current **
Dies funktioniert nicht wirklich, selbst wenn Sie die Klassenerweiterung mit mehreren Parametern aktivieren, da die Instanzauswahl klüger sein muss, als Haskell derzeit zulässt.
Int
als als auch ist Integer
. Um diese drei Instanzdeklarationen zu erhalten, muss die Instanzauflösung Backtracking verwenden, und kein Haskell-Compiler implementiert dies.
Es definiert nicht zwei Operatoren - es definiert drei! Aus dem Bericht:
Es gibt drei Exponentiationsoperationen mit zwei Argumenten: (
^
) erhöht eine beliebige Zahl auf eine nichtnegative ganzzahlige Potenz, (^^
) erhöht eine gebrochene Zahl auf eine ganzzahlige Potenz und (**
) verwendet zwei Gleitkommaargumente. Der Wert vonx^0
oderx^^0
ist 1 für allex
, einschließlich Null;0**y
ist nicht definiert.
Dies bedeutet, dass es drei verschiedene Algorithmen gibt, von denen zwei genaue Ergebnisse ( ^
und ^^
) liefern , während sie **
ungefähre Ergebnisse liefern. Durch Auswahl des zu verwendenden Operators wählen Sie den aufzurufenden Algorithmus aus.
^
erfordert, dass sein zweites Argument ein ist Integral
. Wenn ich mich nicht irre, kann die Implementierung effizienter sein, wenn Sie wissen, dass Sie mit einem integralen Exponenten arbeiten. Wenn Sie so etwas wie möchten 2 ^ (1.234)
, obwohl Ihre Basis ein Integral 2 ist, ist Ihr Ergebnis offensichtlich ein Bruchteil. Sie haben mehr Optionen, damit Sie genauer steuern können, welche Typen in Ihre Exponentiationsfunktion ein- und ausgehen.
Das Typsystem von Haskell hat nicht das gleiche Ziel wie andere Typsysteme wie C, Python oder Lisp. Das Tippen von Enten ist (fast) das Gegenteil der Haskell-Denkweise.
class Duck a where quack :: a -> Quack
definiert, was wir von einer Ente erwarten, und dann gibt jede Instanz etwas an, das sich wie eine Ente verhalten kann.
Duck
.