2013-01-11 50 views
5

所以我最近這個不錯的主意想出了所有的情況下,在共享代碼的嚴格和懶惰State變壓器模塊之間的希望:覆蓋可提升數據類型

{-# LANGUAGE FlexibleInstances, DataKinds, KindSignatures #-} 
module State where 

data Strictness = Strict | Lazy 
newtype State (t :: Strictness) s a = State (s -> (s, a)) 

returnState :: a -> State t s a 
returnState x = State $ \s -> (s, x) 

instance Monad (State Lazy s) where 
    return = returnState 
    State ma >>= amb = State $ \s -> case ma s of 
    ~(s', x) -> runState (amb x) s' 

instance Monad (State Strict s) where 
    return = returnState 
    State ma >>= amb = State $ \s -> case ma s of 
    (s', x) -> runState (amb x) s' 

get :: State t s s 
get = State $ \s -> (s, s) 

put :: s -> State t s() 
put s = State $ \_ -> (s,()) 

你可以看到,getput兩者都在沒有任何重複的情況下工作 - 對於嚴格類型和惰性類型都沒有類型實例,也沒有任何重複類型。然而,即使我涵蓋Strictness兩種可能的情況下,我不一般都有一個單子實例State t s a

-- from http://blog.melding-monads.com/2009/12/30/fun-with-the-lazy-state-monad/ 
pro :: State t [Bool]() 
pro = do 
    pro 
    s <- get 
    put (True : s) 

-- No instance for (Monad (State t [Bool])) arising from a do statement 

下工作得很好,儘管需要FlexibleContexts

pro :: (Monad (State t [Bool])) => State t [Bool]() 
-- otherwise as before 

然後我可以實例化tLazyStrict並運行結果並得到我所期望的結果。但爲什麼我必須提供這樣的背景?這是一個概念上的限制,還是一個實際的限制?有沒有什麼理由我錯過了爲什麼Monad (State t s a)實際上不成立,還是沒有辦法說服GHC呢?

(旁白:使用上下文Monad (State t s)工作:

Could not deduce (Monad (State t [Bool])) arising from a do statement from the context (Monad (State t s))

這只是混淆我更加肯定前者是後者的抵扣?)

+0

這確實是'DataKinds'的一個限制。我看到一些相關的事情發生,GHC無法弄清楚'DataKinds'的GADT模式是否詳盡,並且產生了不會進行類型檢查的建議。 – 2013-01-11 00:31:27

回答

5

這是一個限制,但有一個很好的理由:如果它不這樣工作會怎麼樣的

runState :: State t s a -> s -> (s,a) 
runState (State f) s = f s 

example :: s -> a 
example = snd $ runState ((State undefined) >> return 1)() 

以及預期的語義,也可能是

example = snd $ runState ((State undefined) >>= \_ -> return 1)() 
     = snd $ runState (State $ \s -> case undefined s of (s',_) -> (s',1))() 
     = snd $ case undefined() of (s',_) -> (s',1) 
     = snd $ case undefined of (s',_) -> (s',1) 
     = snd undefined 
     = undefined 

,也可能是

example = snd $ runState ((State undefined) >>= \_ -> return 1)() 
     = snd $ runState (State $ \s -> case undefined s of ~(s',_) -> (s',1))() 
     = snd $ case undefined() of ~(s',_) -> (s',1) 
     = snd $ (undefined,1) 
     = 1 

這些都是不一樣的。一種選擇是定義一個函數一個額外的類,像

class IsStrictness t where 
    bindState :: State t s a -> (a -> State t s b) -> State t s b 

,然後再定義

instance IsStrictness t => Monad (State t s) where 
    return = returnState 
    (>>=) = bindState 

和方法而不是bindStateIsStrictness一部分,你可以使用一個單

data SingStrictness (t :: Strictness) where 
    SingStrict :: SingStrictness Strict 
    SingLazy :: SingStrictness Lazy 

class IsStrictness t where 
    singStrictness :: SingStrictness t 

bindState :: IsStrictness t => State t s a -> (a -> State t s b) -> State t s b 
bindState ma' amb' = go singStrictness ma' amb' where 
    go :: SingStrictness t -> State t s a -> (a -> State t s b) -> State t s b 
    go SingStrict ma amb = ... 
    go SingLazy ma amb = ... 

它可以使用GHC 7.6的singelton基礎結構而不是自定義類和單例類型進一步增強。你會以

instance SingI t => Monad (State t s) 

這真的不是那麼可怕。習慣於在你的約束集中有大量的SingI _。這是至少在一段時間內工作的方式,並不那麼難看。

至於爲什麼State t [Bool]不是推斷出從State t s:問題是,State t s是在你的頂級背景下,這意味着s是在最外層量化。你正在定義一個函數,說「對於任何t和s,這樣Monad(州t s)我會給你......」。但是,這並不是說「對於Monad(State t [Bool])我會給你......」。可悲的是,這些普遍量化的約束在Haskell中並不那麼容易。

+0

我很滿意'example'像現在一樣對它做出反應,並且出現了一個模糊的類型錯誤 - 但是認識到,不管模糊性是否解決,都會產生一個Monad實例。 –

+0

它看起來像一個類型變量't :: Strictness'可以保存'Strict','Lazy'和兩者都不排序的值。不過,我明白你的意思,謝謝。似乎應該有辦法表達普遍的約束。 –