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 Int
do potęgi zmiennoprzecinkowej i otrzymać wyniku typu Int
. Dlatego system typów zapobiega temu: (1::Int) ** 0.5
generuje błąd typu. To samo dotyczy (1::Int) ^^ (-1)
.
Inaczej rzecz ujmując: Num
typy są zamknięte pod ^
(nie wymaga się, aby mieć odwrotność multiplikatywną), Fractional
typy są zamknięte pod ^^
, Floating
typy są zamknięte pod **
. Ponieważ nie ma Fractional
instancji 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.
Int
i 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^0
lubx^^0
wynosi 1 dla dowolnegox
, w tym zera;0**y
jest 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 -> Quack
określa, czego oczekujemy od kaczki, a następnie każda instancja określa coś, co może zachowywać się jak kaczka.
Duck
.