2015-07-11 177 views
3

屈服/等待功能延續單子我想創建一個類型像這樣的自動機類型:在哈斯克爾

newtype Auto i o = Auto {runAuto :: i -> (o, Auto i o)} 

我知道這是Automata arrow的類型,但我沒有找一個箭頭。我想使這個單子,所以推測其將有一種像

newtype Auto i o a = ???? What goes here? 

具有這樣的功能:

yield :: o -> Auto i o i 

所以,當我打電話從內自動單子的「產量」 「runAuto」函數返回一個由「yield」參數和continuation函數組成的對。當應用程序調用continuation函數時,參數將作爲「yield」的結果在monad中返回。

我知道這將需要一些延續單子的味道,但儘管過去我已經與延續爭執,我看不出如何編碼這一個。

我也知道這與Michael Snoyman的Conduit monad非常相似,只不過他分裂了「收益」和「等待」。這個monad每個輸入都必須有一個輸出。

背景:我正在編寫一些以複雜的方式響應GUI事件的代碼。我希望能夠編寫接受一系列輸入的代碼,以便隨着用戶交互的進行產生更新屏幕,而不是將其轉換爲手動編碼的狀態機。

編輯

這一切都被證明是錯誤的巧妙。我寫了PetrPudlák在他的回覆中提出的代碼,似乎可行,但「收益率」操作始終產生了先前的收益率。哪一個很奇怪。

經過多次盯着屏幕後,我終於明白,我需要粘貼在這裏的代碼。關鍵的區別在於AutoF類型。比較下面的和Petr提出的那個。

import Control.Applicative 
import Control.Monad 
import Control.Monad.IO.Class 
import Control.Monad.State.Class 
import Control.Monad.Trans.Class 
import Control.Monad.Trans.Free 
import Data.Void 

class (Monad m) => AutoClass i o m | m -> i, m -> o where 
    yield :: o -> m i 

data AutoF i o a = AutoF o (i -> a) 

instance Functor (AutoF i o) where 
    fmap f (AutoF o nxt) = AutoF o $ \i -> f $ nxt i 

newtype AutoT i o m a = AutoT (FreeT (AutoF i o) m a) 
    deriving (Functor, Applicative, Monad, MonadIO, MonadTrans, MonadState s) 

instance (Monad m) => AutoClass i o (AutoT i o m) where 
    yield v = AutoT $ liftF $ AutoF v id 

runAutoT :: (Monad m) => AutoT i o m Void -> m (o, i -> AutoT i o m Void) 
runAutoT (AutoT step) = do 
    f <- runFreeT step 
    case f of 
     Pure v -> absurd v 
     Free (AutoF o nxt) -> return (o, AutoT . nxt) 


-- Quick test 
-- 
-- > runTest testStart 
testStart :: Int -> AutoT Int Int IO Void 
testStart x = do 
    liftIO $ putStrLn $ "My state is " ++ show x 
    y <- liftIO $ do 
     putStrLn "Give me a number: " 
     read <$> getLine 
    v1 <- yield $ x + y 
    liftIO $ putStrLn $ "I say " ++ show v1 
    v2 <- yield $ 2 * v1 
    testStart v2 

runTest auto = do 
    putStrLn "Next input:" 
    v1 <- read <$> getLine 
    (v2, nxt) <- runAutoT $ auto v1 
    putStrLn $ "Output = " ++ show v2 
    runTest nxt 

回答

2

你可以擴展你的自動機在Conduit的精神,也就是讓它退出和有限多輸入返回值:

data Auto i o a 
    = Step (i -> (o, Auto i o a)) 
    | End a 

然後你可以定義使用連接兩個自動單子實例>>=:第一個完成時,第二個繼續。

好消息是你不需要自己實現它。使用函數返回值或嵌套正是free monad所做的(請參見its haddock docs)。所以,讓我們定義

{-# LANGUAGE DeriveFunctor #-} 
import Control.Monad.Free 
import Data.Void 

-- | A functor describing one step of the automaton 
newtype AutoF i o t = AutoF (i -> (o, t)) 
    deriving (Functor) 

那麼原來Auto類型可以只爲一個別名來定義:

type Auto i o = Free (AutoF i o) 

這會自動給你的Free所有的情況下,你也可以定義你原有的功能:

-- | If @[email protected] is 'Void', the machine runs forever: 
runAuto :: Auto i o Void -> i -> (o, Auto i o Void) 
runAuto (Pure v) _   = absurd v 
runAuto (Free (AutoF f)) i = f i 

yield :: o -> Auto i o() 
yield x = liftF (AutoF $ \_ -> (x,())) 

請注意,使用與FreeT相同的函子,您將得到相應的monad變換器:

import Control.Monad.Trans.Free 

type AutoT i o = FreeT (AutoF i o) 

yieldT :: (Monad m) => o -> AutoT i o m() 
yieldT x = liftF (AutoF $ \_ -> (x,())) 

... 
+0

幾乎正是我想要的,除了「yield」還需要在下一個狀態轉換的新輸入,所以它應該是「yield :: o - > Auto i o i」。所以推測是「yield x = liftF(AutoF $ \ v - >(x,v))」。不過,感謝大家深入瞭解免費monads是我所需要的。過去我已經閱讀過關於它們的內容,但沒有將它們與這個特定的問題聯繫起來。當然,現在它非常明顯。 –

+0

確實,這將是你想要的語義的「yield」的正確定義。 –

+1

你也可以考慮使用['Codensity'](https://hackage.haskell.org/package/kan-extensions-4.2.2/docs/Control-Monad-Codensity.html)來獲得更好的漸進性能。 –