Haskell:如何编写monadic可变参数函数,使用monadic上下文的参数

时间:2012-08-29 10:30:11

标签: haskell types monads typeclass variadic-functions

我正在尝试使用monadic返回类型创建一个可变参数函数,其参数也需要monadic上下文。 (我不确定如何描述第二点:例如printf可以返回IO (),但它的不同之处在于它的参数是否相同,无论它最终是IO ()还是{{} 1}}。)

基本上,我有一个数据构造函数,比如两个String参数。我想提供两个指针样式Char参数,可以通过类型类实例从封闭的ID Char monad自动解码。因此,我想做State

,而不是get >>= \s -> foo1adic (Constructor (idGet s id1) (idGet s id2))

以下是我到目前为止所获得的内容,如果有人想复制它并且弄乱它,那就是Literate Haskell风格。

首先,基本环境:

fooVariadic Constructor id1 id2

为方便起见,一些测试数据:

> {-# 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

我有这个非monadic版本,它只是将环境作为第一个参数。这很好,但它不是我想要的。例子:

> 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

现在,我想要一个在以下monad中运行的可变函数。

> 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

基本上,我不知道该怎么办。我尝试以不同的方式表达类型类(> type MyState = State Env variadicM :: r1 -> r2),但我没有成功编写实例。我也试过调整上面的非monadic解决方案,以便我以某种方式“结束”variadicM :: r1 -> MyState r2然后我可以很容易地变成Env -> Foo,但也没有运气。

以下是我迄今为止的最佳尝试。

MyState Foo

适用于Foo0和Foo1,但不能超越:

> 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

(这里我想摆脱注释的需要,但是这个公式需要$ 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)) 的两个实例这一事实会使问题变得复杂。)

我了解投诉:我的实例只有FooBool -> Char -> Foo。但我无法做到 它需要的实例,因为我需要ID Bool -> MyState (ID Char -> Foo)在某处,以便我可以 将MyState转换为ID Bool

我不知道我是完全偏离轨道还是什么。我知道我可以用不同的方式解决我的基本问题(我不想用Bool等价物污染我的代码),例如创建idGet s / liftA不同数量的ID参数的样式函数,类型为liftM,但我花了太多时间考虑这个问题。 :-)我想知道这个变量解决方案应该是什么样的。

1 个答案:

答案 0 :(得分:2)

警告

最好不要为这类工作使用可变函数。你只有有限数量的构造函数,所以聪明的构造函数似乎不是什么大问题。与变参数解决方案相比,您需要的~10-20行更简单,更易于维护。另外,一个应用解决方案的工作要少得多。

警告

monad / applicative与可变函数相结合是问题所在。 '问题'是用于可变参数类的参数添加步骤。基本类看起来像

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

通过使用表单

的实例使其变量可变
instance Variadic BaseType where ...
instance Variadic f =>  Variadic (arg -> f) where ...

当你开始使用monads时会破坏。在类定义中添加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 monad,防止了monad和variadic函数之间的问题(对于State monad,resolve函数应该返回一个新的环境)。使用app Env Foo1 ID :: Foo进行测试会产生预期的Foo1 'a'

P.S。 您可以获得monadic可变参数函数(在某种程度上),但它需要以一些非常奇怪的方式弯曲您的函数(和思想)。我有这样的工作方式是将所有可变参数“折叠”到异构列表中。然后可以通过monadic-ally进行展开。虽然我做过类似的事情,但我强烈反对在实际(使用过的)代码中使用这些东西,因为它很快变得难以理解且无法维护(更不用说你会得到的类型错误)。