Potęgowanie w Haskellu

91

Czy ktoś może mi powiedzieć, dlaczego Haskell Prelude definiuje dwie oddzielne funkcje potęgowania (tj. ^I **)? Myślałem, że system typów miał wyeliminować tego rodzaju powielanie.

Prelude> 2^2
4
Prelude> 4**0.5
2.0
Skytreebird
źródło

Odpowiedzi:

130

Są właściwie trzy operatorów potęgowania: (^), (^^)i (**). ^jest nieujemnym potęgowaniem ^^całkowitym , jest potęgowaniem liczb całkowitych i **jest potęgowaniem zmiennoprzecinkowym:

(^) :: (Num a, Integral b) => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a

Powodem jest bezpieczeństwo typów: wyniki operacji numerycznych mają zazwyczaj ten sam typ, co argumenty wejściowe. Ale nie możesz podnieść a Intdo potęgi zmiennoprzecinkowej i otrzymać wyniku typu Int. Dlatego system typów zapobiega temu: (1::Int) ** 0.5generuje błąd typu. To samo dotyczy (1::Int) ^^ (-1).

Inaczej rzecz ujmując: Numtypy są zamknięte pod ^(nie wymaga się, aby mieć odwrotność multiplikatywną), Fractionaltypy są zamknięte pod ^^, Floatingtypy są zamknięte pod **. Ponieważ nie ma Fractionalinstancji dla Int, nie możesz podnieść jej do ujemnej mocy.

W idealnym przypadku drugi argument ^byłby statycznie ograniczony, aby był nieujemny (obecnie 1 ^ (-2)zgłasza wyjątek w czasie wykonywania). Ale nie ma typu dla liczb naturalnych w Prelude.

Michaił Gluszenkow
źródło
31

System typów Haskella nie jest wystarczająco silny, aby wyrazić trzy operatory potęgowania jako jeden. To, czego naprawdę chcesz, to coś takiego:

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 **

To tak naprawdę nie działa, nawet jeśli włączysz rozszerzenie klasy typu wieloparametrowego, ponieważ wybór instancji musi być sprytniejszy, niż obecnie zezwala Haskell.

augustss
źródło
4
Czy stwierdzenie, że nie można tego zrealizować, jest nadal prawdziwe? IIRC, haskell ma opcję, aby drugi parametr klasy typu wieloparametrowego był określany ściśle przez pierwszy parametr. Czy jest jeszcze inny problem, którego nie można rozwiązać?
RussellStewart
2
@singular To wciąż prawda. Pierwszy argument nie określa drugiego, na przykład chcesz, aby wykładnik był jednocześnie Inti Integer. Aby móc mieć te trzy deklaracje instancji, rozwiązanie instancji musi korzystać z funkcji śledzenia wstecznego, a żaden kompilator Haskell tego nie implementuje.
sierpień
7
Czy argument „system typów nie jest wystarczająco silny” jest nadal aktualny w marcu 2015 r.?
Erik Kaplun
3
Z pewnością nie możesz tego napisać tak, jak sugeruję, ale może istnieć sposób, aby to zakodować.
sierpnia
2
@ErikAllik prawdopodobnie robi dla standardowego Haskell, jak żadna nowa Haskell Zgłoś wyszła od 2010 roku
Martin Capodici
10

Nie definiuje dwóch operatorów - definiuje trzy! Z raportu:

Istnieją trzy dwuargumentowe operacje potęgowania: ( ^) podnosi dowolną liczbę do nieujemnej potęgi całkowitej, ( ^^) podnosi liczbę ułamkową do dowolnej potęgi całkowitej, a ( **) przyjmuje dwa argumenty zmiennoprzecinkowe. Wartość x^0lub x^^0wynosi 1 dla dowolnego x, w tym zera; 0**yjest niezdefiniowana.

Oznacza to, że istnieją trzy różne algorytmy, z których dwa dają dokładne wyniki ( ^i ^^), a **dają przybliżone wyniki. Wybierając operator, którego chcesz użyć, wybierasz algorytm do wywołania.

Gabe
źródło
4

^wymaga, aby drugi argument był rozszerzeniem Integral. Jeśli się nie mylę, implementacja może być bardziej wydajna, jeśli wiesz, że pracujesz z wykładnikiem całkowym. Ponadto, jeśli chcesz czegoś takiego 2 ^ (1.234), nawet jeśli twoja podstawa jest całką 2, twój wynik będzie oczywiście ułamkowy. Masz więcej opcji, dzięki czemu możesz mieć ściślejszą kontrolę nad typami wchodzącymi i wychodzącymi z funkcji potęgowania.

System typów Haskella nie ma tego samego celu, co inne systemy typów, takie jak C, Python czy Lisp. Pisanie kaczkami jest (prawie) przeciwieństwem sposobu myślenia Haskella.

Dan Burton
źródło
4
Nie do końca zgadzam się, że sposób myślenia typu Haskell jest przeciwieństwem pisania kaczego. Klasy typu Haskell są bardzo podobne do pisania kaczego. class Duck a where quack :: a -> Quackokreśla, czego oczekujemy od kaczki, a następnie każda instancja określa coś, co może zachowywać się jak kaczka.
sierpień
9
@augustss Widzę, skąd pochodzisz. Ale nieformalne motto za pisaniem kaczki brzmi: „jeśli wygląda jak kaczka, zachowuje się jak kaczka i kwacze jak kaczka, to jest to kaczka”. W Haskell nie jest to kaczka, chyba że zostanie zadeklarowana jako instancja Duck.
Dan Burton
1
To prawda, ale tego właśnie oczekiwałbym od Haskella. Możesz zrobić kaczkę, co chcesz, ale musisz to wyraźnie powiedzieć. Nie chcemy, aby coś, o co nie prosiliśmy, było kaczką.
sierpień
Istnieje bardziej szczegółowa różnica między sposobem robienia rzeczy w Haskellu a pisaniem typu kaczego. Tak, możesz nadać dowolnemu typowi klasę Duck, ale nie jest to Duck. Jest zdolny do kwakania, jasne, ale to wciąż, konkretnie, niezależnie od tego, jaki to był typ. Nadal nie możesz mieć listy kaczek. Funkcja akceptująca listę Ducks oraz mieszanie i dopasowywanie różnych typów klas Duck nie będzie działać. Pod tym względem Haskell nie pozwala ci po prostu powiedzieć: „Jeśli kwacze jak kaczka, to jest kaczką”. W Haskell wszystkie twoje kaczki muszą być szarlatanami tego samego typu. Rzeczywiście różni się to od pisania kaczkami.
mmachenry
Możesz mieć listę mieszanych kaczek, ale potrzebujesz rozszerzenia Existential Quantification.
Bolpat