Pergunta
Eu quero criar minha própria mônada. Isso é o que escrevi:
data LeafConType a = LeafCon (a,Int,Int)
instance Monad (LeafConType ) where
return = LeafCon
lc@(LeafCon (t,i,n)) >>= f = if i>=n
then lc
else f (t,i,n)
Mas isso não funciona. GHC diz:
leafcon.hs:26:1:
Occurs check: cannot construct the infinite type: a = (a, Int, Int)
When generalising the type(s) for `return'
In the instance declaration for `Monad LeafConType'
leafcon.hs:27:1:
Occurs check: cannot construct the infinite type: a = (a, Int, Int)
When generalising the type(s) for `>>='
In the instance declaration for `Monad LeafConType'
O que há de errado nisso?
Eu quero fazer cálculos enquanto estiver menor que n. n deve ser constantes por ainda não sei como fazer isso correto. Deve ser uma mistura de estado e talvez. Se você tem alguns conselhos, sinta -se à vontade para compartilhar comigo: P
Solução
Sobre return
:
Prelude> :t return
return :: (Monad m) => a -> m a
Então return
leva um argumento do tipo a
, e retorna algo do tipo m a
. Nesse caso m
é LeafConType
, assim LeafConType a
é devolvido.
Agora suponha que passamos True
. Então a = Bool
, então o tipo de retorno deve ser LeafConType Bool
. No entanto, você define:
return = LeafCon
Então, return True
torna-se LeafCon True
. Mas isso não é permitido, porque a definição de tipo de LeafConType
afirma isso
data LeafConType a = LeafCon (a, Int, Int)
Então para LeafConType Bool
o argumento para LeafCon
deve ter tipo (Bool, Int, Int)
, não apenas Bool
. E é isso que o erro de compilação significa: a
não pode ser o mesmo que (a, Int, Int)
. Você afirma:
Eu quero fazer cálculos enquanto
i
é menor quen
.
Isso significa que você precisará de alguns valores padrão para i
e n
, por caso contrário, será impossível definir return
. Se os dois forem zero por padrão, você poderá definir:
return a = LeafCon (a, 0, 0)
Sobre (>>=)
:
Prelude> :t (>>=)
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b
Agora veja sua implementação (notação ligeiramente diferente, a mesma ideia):
lc@(LeafCon (t, i, n)) >>= f | i >= n = lc
| otherwise = f t
O que vemos aqui é que lc
é devolvido quando i >= n
. Mas lc
é do tipo LeafConType a
, enquanto f
é uma função que pode retornar um valor do tipo LeafConType b
, por algum b
. Como resultado, pode ser isso b
não é igual a a
E, portanto, esses tipos não correspondem. Em conclusão, você precisa seriamente fazer uma pergunta:
Esse tipo de computação pode ser expresso como uma mônada?
Outras dicas
As funções que você especificou para >>=
e return
Não satisfaça os tipos necessários por Monad
:
return :: a -> LeafConType a
Dada a declaração
return = LeafCon
você dá à função o tipo incompatível
return :: (a, Int, Int) -> LeafConType a
Uma declaração como return 42
Portanto, seria impossível em sua mônada.
Não entendo o que sua mônada deve fazer. Primeiro dê uma olhada em Mônadas simples e trabalhando!
instance Monad [] where
(>>=) = concatMap
return a = [a]
instance Monad Maybe where
return = Just
(Just x) >>= f = f x
Nothing >>= f = Nothing
A julgar pela sua descrição do que você deseja que sua mônada faça, acho que você quer algo um pouco assim:
data LeafConType a = LeafCon { runLeafCon' :: Int -> Int -> (Maybe a, Int, Int) }
runLeafCon :: Int -> Int -> LeafConType a -> Maybe a
runLeafCon i n lc = let (t, _, _) = runLeafCon' lc i n in t
getI :: LeafConType Int
getI = LeafCon $ \i n -> (Just i, i, n)
getN :: LeafConType Int
getN = LeafCon $ \i n -> (Just n, i, n)
setI :: Int -> LeafConType ()
setI i = LeafCon $ \_ n -> (Just (), i, n)
setN :: Int -> LeafConType ()
setN n = LeafCon $ \i _ -> (Just (), i, n)
instance Monad LeafConType where
return t = LeafCon $ \i n -> if (i < n)
then (Just t, i, n)
else (Nothing, i, n)
(LeafCon k) >>= f =
LeafCon $ \i n ->
let (t, i', n') = k i n
in case t of
Nothing -> (Nothing, i', n')
(Just t') -> if (i' < n')
then runLeafCon' (f t') i' n'
else (Nothing, i, n)
example :: Int -> LeafConType ((), Int)
example x = do
i <- getI
m <- setI (i + x)
return (m, i + x)
Alguns exemplos:
*Main> runLeafCon 2 10 $ example 4
Just ((),6)
*Main> runLeafCon 2 10 $ example 8
Nothing
*Main> runLeafCon 2 10 $ example 7
Just ((),9)
Juntei isso rapidamente, é bastante feio e não verifiquei se isso obedece a alguma das leis da Mônada, então use por sua conta e risco! :)