Wnioskowanie typu w przypadku typów wyższego rzędu jest nierozstrzygalne. Algorytm wnioskowania typu GHC tworzy pewne uproszczone założenia, które powodują, że jest on niepełny. Należą do nich:
GHC przyjmie zmienna związana lambda jest monotypia (? Nie zawiera (prowadząca) forall
s)
Podczas korzystania polimorficzny funkcji GHC zakłada, że wolne zmienne typu w swoim rodzaju będzie instancja w monotypie
Oba te założenia mogą być nadpisane jeśli zapytać gHC grzecznie do korzystania z konkretnego polytype zamiast.
Teraz, jak można się spodziewać, że program będzie sprawdzać?
- Aby
run fs
na typ kontroli, mieliśmy lepiej mieć fs :: forall s. [ST.STRef s Int -> ST.ST s Int]
- Tak więc, zgodnie z pierwszym punktem powyżej musimy napisać ten podpis typu na lambda, która wiąże go:
\(fs :: forall s. [ST.STRef s Int -> ST.ST s Int]) -> ...
(używając ScopedTypeVariables
)
Teraz rozważ użycie >>=
. Jego typ to Monad m => m a -> (a -> m b) -> m b
. Ale potrzebujemy a = forall s. [ST.STRef s Int -> ST.ST s Int]
. Tak więc, według drugiego punktu powyżej musimy dać ten >>=
podpis typu, jak w
... `op` (\(fs :: forall s. [ST.STRef s Int -> ST.ST s Int]) -> ...)
where op :: Monad m
=> m (forall s. [ST.STRef s Int -> ST.ST s Int])
-> ((forall s. [ST.STRef s Int -> ST.ST s Int]) -> m b)
-> m b
op = (>>=)
- Teraz napotkasz nowego rodzaju problemu. W tej aplikacji
op
pierwszy argument ma typ Either String (forall s. [ST.STRef s Int -> ST.ST s Int])
. Stosowanie konstruktora typu (innego niż (->)
) do typu poliseksu jest niedozwolone, chyba że włączysz (zepsute) ImpredicativeTypes. Jednak możemy go włączyć i kontynuować ...
- Zaniżając pierwszy argument, widzimy, że będziemy potrzebować
return :: Monad m => a -> m a
z instancją a = forall s. ST.STRef s Int -> ST.ST s Int
. Tak więc, musimy dodać kolejny podpis typu do return
- Podobnie musimy
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
instancja w b = forall s. ST.STRef s Int -> ST.ST s Int
Jeśli uważnie można zauważyć jeszcze jeden problem: wynik mapM
ma typ
Either String [forall s. ST.STRef s Int -> ST.ST s Int]
ale argument (>>=)
musi być typu
Either String (forall s. [ST.STRef s Int -> ST.ST s Int])
i będzie musiał przekonwertować między nimi.W rzeczywistości jest to nie-op, ale GHC nie jest wystarczająco silny, aby to wiedzieć, więc trzeba będzie zrobić konwersję liniowy w czasie, coś
liftM (\x -> map (\(y :: forall s. ST.STRef s Int -> ST.ST s Int) -> y) x)
(z wyjątkiem liftM
będą musiały kolejny podpis typ)
Morał z opowieści: Możesz to zrobić, ale nie powinieneś.
Będziesz generalnie mają łatwiej, jeśli ukryć forall
s wewnątrz newtypes jak
newtype S s = S { unS :: forall s. ST.STRef s Int -> ST.ST s Int }
co sprawia, że punkty, w których polimorfizm jest wprowadzone bardziej wyraźne w programie (poprzez wystąpień S
i unS
).
[** 'sequence' **] (https://www.haskell.org/hoogle/?hoogle=sequence)? –
@ recursion.ninja to doskonała propozycja, ale niestety nie wydaje się to mieć znaczenia. Czy spodziewałbyś się, że wystąpią sekwencje mające inne wyniki niż 'mapM'? – ryachza
Teraz formułuję odpowiedź –