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
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
Odpowiedzi:
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.
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.
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.
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^0lubx^^0wynosi 1 dla dowolnegox, 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.
^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.
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.
Duck.