Grałem z pakietem Haskell reflection i napotkałem błąd typu, którego nie rozumiem w pełni. Najpierw próbowałem napisać następującą funkcję, która szczęśliwie typechecks:Jaka jest przyczyna tego tajemniczego błędu typu Haskell polegającego na odbiciu?
{-# LANGUAGE TypeApplications #-}
reifyGood :: (forall s. Reifies s a => Proxy a) ->()
reifyGood p = reify undefined (\(_ :: Proxy t) -> p @t `seq`())
Co ciekawe, to jednak nieco inny program robi nie typecheck:
reifyBad :: (forall s. Reifies s s => Proxy s) ->()
reifyBad p = reify undefined (\(_ :: Proxy t) -> p @t `seq`())
• Could not deduce (Reifies s s) arising from a use of ‘p’
from the context: Reifies s a0
bound by a type expected by the context:
Reifies s a0 => Proxy s ->()
• In the first argument of ‘seq’, namely ‘p @t’
In the expression: p @t `seq`()
In the second argument of ‘reify’, namely
‘(\ (_ :: Proxy t) -> p @t `seq`())’
wyrażenia są identyczne, ale zauważ różnicę między sygnaturami typu:
reifyGood :: (forall s. Reifies s a => Proxy a) ->()
reifyBad :: (forall s. Reifies s s => Proxy s) ->()
Uważam to za ciekawe. Na pierwszy rzut oka jest to nieważne, ponieważ w drugim przykładzie skolem s
wymknie się spod jego zakresu. Jednakże, nie jest to faktycznie prawda, komunikat o błędzie nie wspomina Skolem ucieczki, w przeciwieństwie do tego nieco inny program:
reifyBad' :: (forall s. Reifies s s => Proxy s) ->()
reifyBad' p = reify undefined (\(_ :: Proxy t) -> p @t) `seq`()
• Couldn't match expected type ‘t0’ with actual type ‘Proxy s’
because type variable ‘s’ would escape its scope
This (rigid, skolem) type variable is bound by
a type expected by the context:
Reifies s a0 => Proxy s -> t0
• In the expression: p @t
In the second argument of ‘reify’, namely
‘(\ (_ :: Proxy t) -> p @t)’
In the first argument of ‘seq’, namely
‘reify undefined (\ (_ :: Proxy t) -> p @t)’
Więc może coś innego jest tutaj w grę.
Badania typu reify
, staje się mało jasne, że coś jest nie w porządku:
reify :: forall a r. a -> (forall s. Reifies s a => Proxy s -> r) -> r
Zakresy a
i s
są wyraźnie różne, więc wydaje się mieć sens, że GHC nie pozwoli im ujednolicić. Jednak wydaje się, że coś z lokalnego ograniczenia równości wprowadzonego przez funkcjonalną zależność od Reifies
powoduje dziwne zachowanie. Co ciekawe, ta para funkcji typechecks:
foo :: forall a r. Proxy a -> (forall s. (s ~ a) => Proxy s -> r) -> r
foo _ f = f Proxy
bar :: (forall a. Proxy a) ->()
bar p = let p' = p in foo p' (\(_ :: Proxy s) -> (p' :: Proxy s) `seq`())
... ale usunięcie ograniczenia równości w podpisie typu foo
czyni je nie typecheck, produkując błąd Skolem ucieczki:
• Couldn't match type ‘a0’ with ‘s’
because type variable ‘s’ would escape its scope
This (rigid, skolem) type variable is bound by
a type expected by the context:
Proxy s ->()
Expected type: Proxy s
Actual type: Proxy a0
• In the first argument of ‘seq’, namely ‘(p' :: Proxy s)’
In the expression: (p' :: Proxy s) `seq`()
In the second argument of ‘foo’, namely
‘(\ (_ :: Proxy s) -> (p' :: Proxy s) `seq`())’
W tym momencie, Jestem zakłopotany. Mam kilka (bardzo powiązanych) pytań.
Dlaczego
reifyBad
nie sprawdza się najpierw przy typie?Dokładniej, dlaczego powoduje wystąpienie błędu braku instancji ?
Co więcej, czy to zachowanie jest oczekiwane i dobrze zdefiniowane, czy też jest to tylko dziwny przypadek, gdy typechecker ma taki wynik?