我正在尝试使用一元返回类型制作一个可变参数函数,其参数也需要一元上下文。(我不确定如何描述第二点:例如printf
可以返回IO ()
,但不同之处在于它的参数被视为相同,无论它最终是IO ()
还是String
。)
基本上,我有一个数据构造函数,它接受两个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 -> r2
和variadicM :: r1 -> MyState r2
)表达类型类,但我没有成功编写实例。我也尝试过调整上面的非单子解决方案,以便我以某种方式“结束”,Env -> Foo
然后我可以轻松地变成 a 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 -> Foo
到的实例ID Bool -> MyState (ID Char -> Foo)
。但我无法制作它想要的实例,因为我需要MyState
在某个地方,以便我可以ID Bool
将Bool
.
我不知道我是完全偏离轨道还是什么。我知道我可以用不同的方式解决我的基本问题(我不想idGet s
到处都用等价物污染我的代码),例如为不同数量的 ID 参数 创建liftA
/ -style 函数,类型如, 但是我花了太多时间思考这个问题。:-) 我想知道这个可变参数解决方案应该是什么样子。liftM
(a -> b -> ... -> z -> ret) -> ID a -> ID b -> ... -> ID z -> MyState ret