Potenzierung in Haskell

90

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
Skytreebird
quelle

Antworten:

130

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 Intauf eine Gleitkomma-Potenz erhöhen und ein typisches Ergebnis erhalten Int. Das Typsystem verhindert dies und verhindert (1::Int) ** 0.5einen Typfehler. Das gilt auch für (1::Int) ^^ (-1).

Eine andere Möglichkeit, dies auszudrücken: NumTypen werden unter geschlossen ^(sie müssen keine multiplikative Inverse haben), FractionalTypen werden unter geschlossen ^^, FloatingTypen werden unter geschlossen **. Da es keine FractionalInstanz 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.

Mikhail Glushenkov
quelle
31

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.

August
quelle
4
Ist die Aussage, dass dies nicht umsetzbar ist, noch wahr? IIRC, haskell hat eine Option für den zweiten Parameter einer Typklasse mit mehreren Parametern, die streng durch den ersten Parameter bestimmt wird. Gibt es darüber hinaus ein anderes Problem, das nicht gelöst werden kann?
RussellStewart
2
@singular Es ist immer noch wahr. Das erste Argument bestimmt nicht das zweite. Sie möchten beispielsweise, dass der Exponent sowohl Intals als auch ist Integer. Um diese drei Instanzdeklarationen zu erhalten, muss die Instanzauflösung Backtracking verwenden, und kein Haskell-Compiler implementiert dies.
August
7
Gilt das Argument "Typsystem ist nicht leistungsfähig genug" noch ab März 2015?
Erik Kaplun
3
Sie können es sicherlich nicht so schreiben, wie ich es vorschlage, aber es könnte eine Möglichkeit geben, es zu codieren.
August
2
@ErikAllik wahrscheinlich für Standard-Haskell, da seit 2010 kein neuer Haskell-Bericht veröffentlicht wurde.
Martin Capodici
10

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 von x^0oder x^^0ist 1 für alle x, einschließlich Null; 0**yist 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.

Gabe
quelle
4

^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.

Dan Burton
quelle
4
Ich stimme nicht ganz zu, dass die Denkweise des Haskell-Typs das Gegenteil von Enten-Typisierung ist. Haskell-Typklassen ähneln stark der Ententypisierung. class Duck a where quack :: a -> Quackdefiniert, was wir von einer Ente erwarten, und dann gibt jede Instanz etwas an, das sich wie eine Ente verhalten kann.
August
9
@augustss Ich sehe, woher du kommst. Aber das informelle Motto hinter dem Tippen von Enten lautet: "Wenn es wie eine Ente aussieht, sich wie eine Ente verhält und wie eine Ente quakt, dann ist es eine Ente." In Haskell ist es keine Ente, es sei denn, es wird als Instanz von deklariert Duck.
Dan Burton
1
Das stimmt, aber das würde ich von Haskell erwarten. Sie können alles machen, was Sie wollen, eine Ente, aber Sie müssen explizit darüber sein. Wir wollen nichts verwechseln, wonach wir nicht gefragt haben, ob wir eine Ente sein wollen.
August
Es gibt einen spezifischeren Unterschied zwischen der Art und Weise, wie Haskell Dinge tut, und der Eingabe von Enten. Ja, Sie können jedem Typ die Entenklasse geben, aber es ist keine Ente. Es ist in der Lage zu quaken, sicher, aber es ist immer noch konkret, welcher Typ es auch war. Sie können immer noch keine Liste von Enten haben. Eine Funktion, die eine Liste von Enten akzeptiert und verschiedene Arten von Klassenenten mischt und zusammenbringt, funktioniert nicht. In dieser Hinsicht erlaubt Haskell nicht, einfach zu sagen: "Wenn es wie eine Ente quakt, dann ist es eine Ente." In Haskell müssen alle Ihre Enten Quäker des gleichen Typs sein. Dies ist in der Tat ganz anders als das Tippen von Enten.
mmachenry
Sie können eine Liste gemischter Enten haben, benötigen jedoch die Erweiterung Existential Quantification.
Bolpat