2012-08-29 25 views
8

我正在嘗試使用monadic返回類型來做可變參數函數,其參數也需要monadic上下文。 (我不知道如何來形容第二點:如printf可以返回IO()但它的不同之處在於它是否最終被IO()String其參數將被視爲相同)Haskell:如何使用monadic上下文編寫單變量可變參數函數

基本上,我有一個數據構造函數需要兩個參數Char。我想提供兩個指針樣式ID Char參數,它們可以通過一個類型實例從一個封閉的State monad自動解碼。所以,而不是做get >>= \s -> foo1adic (Constructor (idGet s id1) (idGet s id2)),我想做fooVariadic Constructor id1 id2

接下來是我到目前爲止所知道的,Literate Haskell風格,以防有人想要複製它並混淆它。

一,基本環境:

> {-# LANGUAGE FlexibleContexts #-} 
> {-# LANGUAGE FlexibleInstances #-} 
> {-# LANGUAGE MultiParamTypeClasses #-} 

> import Control.Monad.Trans.State 

> data Foo = Foo0 
>   | Foo1 Char 
>   | Foo2 Bool Char 
>   | Foo3 Char Bool Char 
> deriving Show 

> type Env = (String,[Bool]) 
> newtype ID a = ID {unID :: Int} 
> deriving Show 

> class InEnv a where envGet :: Env -> ID a -> a 
> instance InEnv Char where envGet (s,_) i = s !! unID i 
> instance InEnv Bool where envGet (_,b) i = b !! unID i 

爲了方便一些測試數據:

> cid :: ID Char 
> cid = ID 1 
> bid :: ID Bool 
> bid = ID 2 
> env :: Env 
> env = ("xy", map (==1) [0,0,1]) 

我有了這個非一元的版本,它只是需要將環境作爲第一參數。這工作正常,但它不是我所追求的。例子:

$ mkFoo env Foo0 :: Foo 
Foo0 
$ mkFoo env Foo3 cid bid cid :: Foo 
Foo3 'y' True 'y' 

(我可以用函數依賴或類型的家庭擺脫需要對:: Foo類型註釋現在我不大驚小怪了,因爲這不是我的興趣。無論如何。)

> mkFoo :: VarC a b => Env -> a -> b 
> mkFoo = variadic 
> 
> class VarC r1 r2 where 
> variadic :: Env -> r1 -> r2 
> 
> -- Take the partially applied constructor, turn it into one that takes an ID 
> -- by using the given state. 
> instance (InEnv a, VarC r1 r2) => VarC (a -> r1) (ID a -> r2) where 
> variadic e f = \aid -> variadic e (f (envGet e aid)) 
> 
> instance VarC Foo Foo where 
> variadic _ = id 

現在,我想要一個可變函數在下面的monad中運行。

> type MyState = State Env 

基本上,我不知道該怎麼做。我試過用不同的方式表達類型類(variadicM :: r1 -> r2variadicM :: r1 -> MyState r2),但我沒有成功編寫實例。我也嘗試過適應上面的非單調解決方案,以便我以某種方式「結束」Env -> Foo,然後我可以很容易地變成MyState Foo,但是沒有運氣。

以下是我迄今爲止的最佳嘗試。

> mkFooM :: VarMC r1 r2 => r1 -> r2 
> mkFooM = variadicM 
> 
> class VarMC r1 r2 where 
> variadicM :: r1 -> r2 
> 
> -- I don't like this instance because it requires doing a "get" at each 
> -- stage. I'd like to do it only once, at the start of the whole computation 
> -- chain (ideally in mkFooM), but I don't know how to tie it all together. 
> instance (InEnv a, VarMC r1 r2) => VarMC (a -> r1) (ID a -> MyState r2) where 
> variadicM f = \aid -> get >>= \e -> return$ variadicM (f (envGet e aid)) 
> 
> instance VarMC Foo Foo where 
> variadicM = id 
> 
> instance VarMC Foo (MyState Foo) where 
> variadicM = return 

它適用於Foo0和Foo1,但從未被超越的是:

$ flip evalState env (variadicM Foo1 cid :: MyState Foo) 
Foo1 'y' 
$ flip evalState env (variadicM Foo2 cid bid :: MyState Foo) 

No instance for (VarMC (Bool -> Char -> Foo) 
         (ID Bool -> ID Char -> MyState Foo)) 

(在這裏我想擺脫的需要進行標註,但事實證明,這一提法需要兩個實例對於Foo使這個問題。)

我瞭解投訴:我只有一個實例,從Bool -> Char -> FooID Bool -> MyState (ID Char -> Foo)。但我不能讓它的 實例,因爲我需要MyState在那裏,以便我可以 將ID Bool變成Bool

我不知道我是完全偏離軌道還是什麼。我知道我可以用不同的方式解決我的基本問題(我不想污染我的代碼,其代碼爲idGet s),例如爲不同數量的ID參數創建liftA/liftM樣式函數,像(a -> b -> ... -> z -> ret) -> ID a -> ID b -> ... -> ID z -> MyState ret,但我花了太多時間思考這個問題。 :-)我想知道這個可變參數解決方案應該是什麼樣子。

+0

由於您明確不想查找「Applicative」解決方案,因此我將其添加到評論中:https://gist.github.com/f8e5d1ecf20ea09a8b36 –

回答

2

警告

優選地不使用參數可變型函數用於這種類型的工作。你只有有限數量的構造函數,所以聰明的構造函數似乎沒有什麼大不了的。您需要的〜10-20行比可變解決方案更簡單,更易維護。另外一個適用的解決方案是少得多的工作。

警告

與可變參數的功能組合的單子/應用性的問題。 '問題'是用於可變參數類的參數添加步驟。基本類會是什麼樣子

class Variadic f where 
    func :: f 
    -- possibly with extra stuff 

,你讓它變參具有形式

instance Variadic BaseType where ... 
instance Variadic f => Variadic (arg -> f) where ... 

這將打破當你開始使用單子的實例。在類定義中添加monad會阻止參數擴展(對於某些monad M,您會得到:: M(arg - > f))。將它添加到基本情況將阻止在擴展中使用monad,因爲不可能(據我所知)將monadic約束添加到擴展實例。有關複雜解決方案的提示,請參閱P.S ..

使用導致(Env -> Foo)的函數的解決方案更有希望。下面的代碼仍然需要:: Foo類型約束,並且爲簡潔起見使用簡化版本的Env/ID。

{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE MultiParamTypeClasses, TypeFamilies #-} 

module Test where 

data Env = Env 
data ID a = ID 
data Foo 
    = Foo0 
    | Foo1 Char 
    | Foo2 Char Bool 
    | Foo3 Char Bool Char 
    deriving (Eq, Ord, Show) 

class InEnv a where 
    resolve :: Env -> ID a -> a 
instance InEnv Char where 
    resolve _ _ = 'a' 
instance InEnv Bool where 
    resolve _ _ = True 

Type家族擴展用於使匹配更嚴格/更好。現在是可變的函數類。

class MApp f r where 
    app :: Env -> f -> r 

instance MApp Foo Foo where 
    app _ = id 
instance (MApp r' r, InEnv a, a ~ b) => MApp (a -> r') (ID b -> r) where 
    app env f i = app env . f $ resolve env i 
    -- using a ~ b makes this instance to match more easily and 
    -- then forces a and b to be the same. This prevents ambiguous 
    -- ID instances when not specifying there type. When using type 
    -- signatures on all the ID's you can use 
    -- (MApp r' r, InEnv a) => MApp (a -> r') (ID a -> r) 
    -- as constraint. 

環境Env明確過去了,在本質上是Reader單子拆包防止單子和可變參數的功能(用於State單子決心函數返回一個新的環境)之間的問題。使用app Env Foo1 ID :: Foo進行測試的結果爲預期的Foo1 'a'

P.S. 你可以得到monadic可變參數函數(在一定程度上),但它需要以一些非常奇怪的方式來彎​​曲你的函數(和介意)。我有這樣的工作方式是將所有可變參數「摺疊」成一個異構列表。然後可以單方面完成解包。儘管我已經做了一些這樣的事情,但我強烈建議您不要在實際(使用過的)代碼中使用這些東西,因爲它很快變得難以理解和不可維護(不會說出您將得到的類型錯誤)。

+0

感謝您的洞察力。雖然,你的'MApp'類和它的實例是否與我的非單變量'VarC'基本相同? – Deewiant

+0

@Dewwiant的確是。我認爲這是最好的,在這種情況下可以完成。但我懷疑,如果不做一些令人討厭的技巧,我可以發佈(嘗試)一個答案,如果你想要的話,可以做一個monadic variadic函數。 – Laar

+0

請做!我沒有計劃使用它,但我對它需要什麼樣的欺騙感興趣。 – Deewiant

相關問題