2011-08-03 14 views
9

Napisałem funkcję podobną do Data.Enumerator.List.map, która sprawia, że ​​Iteratee jest kompatybilna z Enumerator, która podaje inny typ Stream.Wpisz podpis w miejscu gdzie klauzula

import Data.Enumerator

test :: Monad m => (ao -> ai) -> Iteratee ai m b -> Iteratee ao m b 
test f iter = go $$ iter 
    where go (Continue k) = continue $ 
      \stream -> go $$ k (fmap f stream) 
     go (Yield res _) = yield res EOF 

Jeśli pominąć podpis typu dla go, to będzie działać dobrze. Chciałbym jednak dołączyć to, ale nie jestem w stanie określić, jaki powinien być poprawny podpis. Oto co myślę, że powinno być:

go :: Monad m => Step ai m b -> Iteratee ao m b

ale to nie działa.
Potrzebuję porady dotyczącej znalezienia poprawnego podpisu typu dla go.

Odpowiedz

12

Prawdopodobnie nie można podać go podpisu typu tak jak jest.

Powodem tego jest użycie polimorficznych argumentów związanych z test. Oznacza to, że wewnątrz go, identyfikator f ma typ (ao -> ai) dla niektórych specyficznych, ale nieznanych typów ao i ai.

zmiennych typu są na ogół tylko w zakresie do pojedynczego podpisu typu, gdzie są one wprowadzone, więc gdy dasz go swój podpis typu, ao i ai są nowe, typy polimorficzne, co oczywiście powoduje błąd typu przy próbie połączenia ich z podobnie nazwanymi, ale naprawionymi (i niepoznawalnymi) typami z podpisu test.

Wynik końcowy jest taki, że nie można jednoznacznie napisać typu go, co nie jest zbyt satysfakcjonujące. Aby rozwiązać ten problem, GHC oferuje między innymi the ScopedTypeVariables extension, która pozwala na wprowadzanie zmiennych wprowadzonych w podpisie typu w zakresie wewnątrz klauzuli where funkcji.

Uwaga: jeśli użyjesz klauzuli where do utworzenia wewnętrznego zakresu definicji i nie użyjesz identyfikatorów związanych z argumentami do funkcji zewnętrznej, możesz pisać podpisy typu w klauzuli where, tak jak możesz dla powiązań najwyższego poziomu. Jeśli nie chcesz używać rozszerzeń GHC, możesz po prostu przekazać parametry w sposób redundantny. Coś takiego powinno zadziałać w takim przypadku:

test :: Monad m => (ao -> ai) -> Iteratee ai m b -> Iteratee ao m b 
test f iter = go f $$ iter 
    where go :: Monad m => (ao -> ai) -> Step ai m b -> Iteratee ao m b 
     go f (Continue k) = continue $ 
      \stream -> go f $$ k (fmap f stream) 
     go _ (Yield res _) = yield res EOF 
+0

@hammar: Dzięki. Przysięgam, że robię głupią literówkę za każdym razem, gdy umieszczam więcej niż cztery linie kodu w odpowiedzi, bez wcześniejszego ładowania ich w GHCi. Westchnienie... –

6

Prawdopodobnie chcesz tego typu, ale z rozszerzeniem ScopedTypeVariables włączoną i ze zmiennymi od typu podpisu test jest w zakresie:

{-# LANGUAGE ScopedTypeVariables #-} 
import Data.Enumerator 
test :: forall m ai ao b. Monad m => (ao -> ai) -> Iteratee ai m b -> Iteratee ao m b 
test f iter = go $$ iter 
    where go :: Step ai m b -> Iteratee ao m b 
     go (Continue k) = continue $ 
      \stream -> go $$ k (fmap f stream) 
     go (Yield res _) = yield res EOF 

Zobacz GHC documentation aby uzyskać więcej informacji.

+1

N.B. - Zauważ, że nie ma tutaj żadnego ograniczenia "Monad m" w typie "go", w przeciwieństwie do proponowanego typu w pytaniu. Jest to ważne, ponieważ nie tylko jest to ten sam zakres 'm' w zakresie, ale ograniczenie * klasy * również jest nadal w zakresie. –

+0

... chociaż prawdopodobnie działałoby dobrze, gdyby dodać ograniczenie. GHC byłoby szczególnie łatwo znaleźć instancję. –

+0

To robi.Można również dodać "Monadę", "m ~ m" lub dowolne inne banalne ograniczenie kontekstu, a GHC zaakceptuje je bez zarzutu. Ostrzega jednak przed '(Monad m, Monad m, Monad m) =>'. –