LoginSignup
0
0

More than 5 years have passed since last update.

Effモナドの効果のうち1つだけをrunする

Last updated at Posted at 2017-07-09

 例えばこのようなpartialContext関数とtotalContext関数があるとき

partialContext :: forall s. (Member (Reader Logs) s, SetMember Lift (Lift IO) s) => Eff s ()

partialContext :: Eff (Reader Logs :> Lift IO :> Void) ()
totalContext :: Eff (Writer Logs :> Reader Logs :> Lift IO :> Void) ()

partialContext内でtotalContextを走らせることができる。
(今回の試みではpartialContext

partialContext :: (Member (Reader Logs) s, SetMember Lift (Lift IO) s) => Eff s ()

のように多相化することには失敗していて、ただしtotalContextは以下のように多相化してもよい

totalContext :: (Member (Writer Logs) r, Member (Reader Logs) r, SetMember Lift (Lift IO) r) => Eff r ()

コード(実例)

output

["nozomi","eli"] was added
(["nozomi","eli","nico","maki"],())
()

実例

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeOperators #-}

import Control.Eff (Eff, Member, run, (:>), SetMember)
import Control.Eff.Lift (Lift, lift, runLift)
import Control.Eff.Reader.Lazy (Reader, ask, runReader)
import Control.Eff.State.Lazy (State, get, put, runState)
import Control.Eff.Writer.Lazy (Writer, tell, runWriter, runMonoidWriter)
import Data.Void (Void)

type Logs = [String]

-- Writer Logs, Reader Logs, Lift IOを使う文脈があるじゃろ
totalContext :: (Member (Writer Logs) r, Member (Reader Logs) r, SetMember Lift (Lift IO) r) => Eff r ()
totalContext = do
  logs <- ask
  tell (logs :: Logs)
  lift . putStrLn $ show logs ++ " was added"

-- そのうちReader Logs, Lift IOのみを使う文脈がある
--partialContext :: forall s. (Member (Reader Logs) s, SetMember Lift (Lift IO) s) => Eff s ()
partialContext :: Eff (Reader Logs :> Lift IO :> Void) ()
partialContext = do
  x <- runWriter' totalContext
  lift $ print x
  where
    -- そうすると、なんとWriter Logs効果だけを消費することができるのじゃ!
    --TODO: totalContextを単相化する必要があるので、それを含むpartialContextを多相化できない。どうすればいい?
    runWriter' :: Eff (Writer Logs :> s) () -> Eff s (Logs, ())
    runWriter' = runWriter (++) ["nico", "maki"]

main :: IO ()
main = do
  x <- runLift $ runReader partialContext ["nozomi", "eli"]
  print x

解説

 まずここではEff型コンストラクタの第一引数に指定される型を効果と呼んでいる:dog2:

(Monad……と呼称するのが一番しっくりくると思うけど、残念ながら現時点でextensible-effectsのWriterReaderはMonadインスタンスになってないので!)

で、partialContextは効果の数が少ないという意味でtotalContextよりも小さい。

なのでtotalContextの効果のうちpartialContextの持っていないWrite Logs効果を引いてしまえば、
partialContextの効果とtotalContextの持つ効果が等しくなるので、同じ文脈として(同じEff aモナドとして)使えるのではないかと思った。
そして、やったらできた。

 件の、効果の引き算になっているのはこれ

runWriter' :: Eff (Writer Logs :> s) () -> Eff s (Logs, ())
runWriter' = runWriter (++) ["nico", "maki"]

引き算の性質は型に表れていて、Writer Logs :> sからWriter Logsを引いている。
なのでrunWriter' totalContext

runWriter' totalContext :: (Member (Reader Logs) s', SetMember Lift (Lift IO) s') => Eff s' (Logs, ())

という型になって、単相化するとこれはpartialContextの文脈(Eff aモナドの形)と等しいので、

runWriter' totalContext :: Eff (Reader Logs :> Lift IO :> Void) (Logs, ())
--                         ^==================================v
partialContext          :: Eff (Reader Logs :> Lift IO :> Void) ()

runWriter' totalContextpartialContextの中で<-できる!
partialContextが単相的なので、runWriter' totalContextは型推論により単相化される)

何に使えるの?

base :: (Member (State Foo) r, SetMember Lift (Lift IO) r) => Eff r ()

という文脈がある中で、局所的に失敗する文脈を扱える。
(以下は、局所的に失敗する文脈child

child :: (Member (Exc String) r, Member (State Foo) r, SetMember Lift (Lift IO) r) => Eff r ()

(と言いたいんだけど実際は、実用するにはbaseを単相化する必要があって……誰かbase……
実例におけるpartialContext……を多相的なままにしておける方法知らない?)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0