8

モナドの戻り値の型を持つ可変引数関数を作成しようとしていますが、そのパラメーターにもモナドのコンテキストが必要です。(その 2 番目の点をどのように説明すればよいかわかりません。たとえば、printfを返すことができますが、最終的にまたはIO ()であるかどうかに関係なく、そのパラメーターが同じように扱われるという点で異なります。)IO ()String

基本的に、たとえば 2 つのパラメーターを取るデータ コンストラクターがありCharます。代わりに、型クラスのインスタンスを介してID Char囲んでいるモナドから自動的にデコードできる2 つのポインター スタイルの引数を提供したいと考えています。Stateだから、する代わりに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

ここで、次のモナドで実行される可変個引数関数が必要です。

> type MyState = State Env

そして、基本的に、どのように進めればよいかわかりません。variadicM :: r1 -> r2型クラスをさまざまな方法 (および)で表現しようとしましvariadicM :: 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))

(ここでは注釈の必要性を取り除きたいのですが、この定式化には 2 つのインスタンスが必要であるという事実がFoo問題になります。)

Bool -> Char -> Foo私は苦情を理解しています: 私は からへ行くインスタンスしか持っていませんID Bool -> MyState (ID Char -> Foo). しかし、 をに変換MyStateできるようにどこかに必要なため、必要なインスタンスを作成できません。ID BoolBool

私が完全に軌道から外れているのか、それとも何なのかわかりません。のような型を使用して、さまざまな数の ID パラメーターに対して /スタイルの関数をidGet s作成するなど、さまざまな方法で基本的な問題 (コードをあらゆる場所で同等のもので汚染したくない) を解決できることを知っていますが、私はこれについて考えるのに多くの時間を費やしました。:-) この可変個引数のソリューションがどのように見えるべきか知りたいです。liftAliftM(a -> b -> ... -> z -> ret) -> ID a -> ID b -> ... -> ID z -> MyState ret

4

1 に答える 1

2

警告

この種の作業には可変個引数関数を使用しないことをお勧めします。コンストラクターの数は限られているため、スマート コンストラクターは大したことではないようです。必要な ~10 ~ 20 行は、可変長のソリューションよりもはるかにシンプルで保守しやすいものです。また、適用可能なソリューションははるかに少ない作業です。

警告

可変関数と組み合わせたモナド/アプリカティブが問題です。「問題」は、可変個引数クラスに使用される引数追加ステップです。基本クラスは次のようになります

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

フォームのインスタンスを持つことによって可変個にする場所

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

モナドを使い始めると壊れます。クラス定義にモナドを追加すると、引数の展開が妨げられます (一部のモナド M に対して :: M (arg -> f) が得られます)。それを基本ケースに追加すると、展開インスタンスにモナド制約を追加することは (私が知る限り) 不可能であるため、展開でモナドを使用できなくなります。複雑な解決策のヒントについては、PS を参照してください。

結果が得られる関数を使用するソリューションの方向性は、(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

型ファミリ拡張は、マッチングをより厳密に/より良くするために使用されます。今度は可変個引数クラスです。

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'

PS モナド可変長関数を(ある程度)機能させることはできますが、非常に奇妙な方法で関数(および心)を曲げる必要があります。私がそのようなことを機能させる方法は、すべての可変引数を異種リストに「折りたたむ」ことです。その後、ラップ解除はモナド的に行うことができます。私はそのようなことをいくつか行ってきましたが、実際の (使用された) コードでそのようなものを使用することは強くお勧めしません。

于 2012-08-30T11:44:25.457 に答える