type signature で ExceptT モナド値を作る

前回は StateT モナド変換子による複合モナドのモナド値を type signature で作ってみたが、今回は ExceptT モナド変換子による複合モナドのモナド値を作ってみる。

まずは、type signature ではなく、データコンストラクタによる Except 複合モナドのモナド値を作ってみる。

Prelude> import Control.Monad.Except

Prelude Control.Monad.Except> :t ExceptT
ExceptT :: m (Either e a) -> ExceptT e m a

Prelude Control.Monad.Except> foo = ExceptT (Just (Right (2::Int)))
Prelude Control.Monad.Except> runExceptT foo
Just (Right 2)

次に、return 関数を使って ExceptT の複合モナドの値を作る。これには type signature が必要になる。

Prelude Control.Monad.Except> bar = (return 2) :: ExceptT String Maybe Int
Prelude Control.Monad.Except> runExceptT bar
Just (Right 2)

さらに、lift 関数を使って EcxeptT の複合モナドの値を作る。

Prelude Control.Monad.Except> baz = (lift $ Just 2) :: ExceptT String Maybe Int
Prelude Control.Monad.Except> runExceptT baz
Just (Right 2)

最後に、throwError 関数を使って ExceptT の複合モナドを作る。

Prelude Control.Monad.Except> qux = (throwError "hello") :: ExceptT String Maybe Int
Prelude Control.Monad.Except> runExceptT qux
Just (Left "hello")

これを見ると ExceptT モナド変換子の複合モナドのモナド値を作るには、直接的なデータを使うときは ExceptT データコンストラクタで作り、return, lift, throwError などのモナド関数を利用するときは Except e m a タイプシグネチャーを利用すればいいことがわかる。

以上のことがわかると、次のような do 記法で作成された、複数の式による複合モナドのプログラムの動作の意味がわかってくる。

Prelude Control.Monad.Except> runExceptT (do x <- lift $ Just 2; return x)
Just (Right 2)
Prelude Control.Monad.Except> runExceptT (do x <- lift Nothing; return x)
Nothing


[PR]
# by tnomura9 | 2018-12-15 07:34 | Haskell | Comments(0)

return 関数と複合モナド

モナドの return 関数は抽象的な関数で、「データをモナドでラッピングする」という意味がある。(return 2) という値は type signatuer で指定しなければどのモナド値をとるのかわからない。たとえば Maybe モナドを指定すると次のように Maybe 型の値を返す。

Prelude> (return 2) :: Maybe Int
Just 2

それでは複合モナドについても、同様に return 2 の戻り値を type signature で指定できるのだろうか。それは、簡単にできる。

return 2 の戻り値を type signature で StateT s Maybe Int 型のデータに指定するには次のようにする。

Prelude> import Control.Monad.State
Prelude Control.Monad.State> foo = (return 2) :: StateT s Maybe Int
Prelude Control.Monad.State> :t foo
foo :: StateT s Maybe Int
Prelude Control.Monad.State> runStateT foo 3
Just (2,3)

StateT s [] Int 型にするには次のようにする。

Prelude Control.Monad.State> bar = (return 2) :: StateT s [] Int
Prelude Control.Monad.State> :t bar
bar :: StateT s [] Int
Prelude Control.Monad.State> runStateT bar 3
[(2,3)]

StateT s (Either String) Int 型にするには次のようにする。

Prelude Control.Monad.State> buz = (return 2) :: StateT s (Either String) Int
Prelude Control.Monad.State> :t buz
buz :: StateT s (Either String) Int
Prelude Control.Monad.State> runStateT buz 3
Right (2,3)

lift 関数についても同様のことができる。

Prelude Control.Monad.State> qux = (lift $ Left "hello") :: StateT s (Either String) Int
Prelude Control.Monad.State> :t qux
qux :: StateT s (Either String) Int
Prelude Control.Monad.State> runStateT qux 3
Left "hello"

このように、(return 2) は StateT s Maybe Int のような複雑な複合モナドの型にも type signature で指定できることがわかる。


[PR]
# by tnomura9 | 2018-12-14 22:43 | Haskell | Comments(0)

StateT変換子の RunStateT 関数

ghci をいじってみて StateT モナド変換子が実は、データコンストラクタだということが分かった。

Prelude> import Control.Monad.State
Prelude Control.Monad.State> foo = StateT (\s -> Just (2,s))
Prelude Control.Monad.State> :t foo
foo :: Num a => StateT b Maybe a

つまり、StateT はコンテナに s -> m (a, s) 型の関数を一つだけ入れることのできるデータコンストラクタなのだ。つまり、StateT b Maybe a という複合モナドは、実態は、StateTデータコンストラクタに (\s -> Just (2,s)) という関数を入れた代数的データ型のデータなのだ。

それでは runStateT とはどんな関数なのだろうか。上の文脈でいうと、それは StateT データコンストラクタのコンテナの中にある関数を取り出すアクセサ関数だ。別の言い方をすると、StateT データコンストラクタのフィールドラベルが runStateT であるということだ。つまり、

StateT {runStateT :: (\s -> Just (2,s))}

こういう風に考えると、StateT b Maybe a という複合モナドのデータ型も、普通のデータ型と同じに見えてくる。実際にそれは次のように確かめることができる。

Prelude Control.Monad.State> :t runStateT foo
runStateT foo :: Num a => s -> Maybe (a, s)

runStateT foo の方は s -> Maybe (a,s) 型の関数だ。したがってその引数 s に値を与えてやると Maybe (a, s) 型のデータが得られるはずだ。

Prelude Control.Monad.State> runStateT foo 3
Just (2,3)

この辺りは、以前のバージョンの State モナドの仕組みと全く同じだ。それだけでなく、StateT データコンストラクタの場合は、任意のモナドを含めることができる、State モナドの拡張版になっている。

Prelude Control.Monad.State> bar = StateT (\s -> Right (2, s))
Prelude Control.Monad.State> runStateT bar 3
Right (2,3)

したがって、新しい State モナドの定義はStateTモナドを使って作るように変更されている。StateTデータコンストラクタに Identity モナドを含めるのだ。

Prelude Control.Monad.State> import Control.Monad.Identity
Prelude Control.Monad.State Control.Monad.Identity> baz = StateT (\s -> Identity (2,s))
Prelude Control.Monad.State Control.Monad.Identity> runStateT baz 3
Identity (2,3)

ペア (2,3) が Identity データコンストラクタのコンテナに入っているが、runIdentity アクセサを利用すれば、オールドバージョンの State モナドのように生のペアを取り出すことができる。

Prelude Control.Monad.State Control.Monad.Identity> runIdentity $ runStateT baz 3
(2,3)

したがって、新しい定義では State データコンストラクタはなく、state 関数がその役割を果たしている。

Prelude Control.Monad.State Control.Monad.Identity> moo = state (\s -> (2,s))
Prelude Control.Monad.State Control.Monad.Identity> :t moo
moo :: (MonadState s m, Num a) => m a
Prelude Control.Monad.State Control.Monad.Identity> runState moo 3
(2,3)

このようにモナド変換子 StateT は State モナドの改良版であったはずだが、今では State モナドは StateT モナドの特殊な場合になっている。また、古いバージョンの State モナドが理解できていれば、同じような発想で StateT モナドが理解できることが分かる。

[PR]
# by tnomura9 | 2018-12-12 23:47 | Haskell | Comments(0)

モナド変換子はデータ型だ

モナド変換しの実態がもう一つ分からないので StateT についていろいろテストしてみた。

まずは、StateT の型を調べてみた。

Prelude> import Control.Monad.State
Prelude Control.Monad.State> :t StateT
StateT :: (s -> m (a, s)) -> StateT s m a

これは StateT が (s -> m (a,s)) 型の関数を引数にとるデータ構築子であることを示している。そこで、実際に (\s -> Just (2,s)) という関数を StateT に渡してモナド値を作ってみた。

Prelude Control.Monad.State> foo = StateT (\s -> Just (2,s))
Prelude Control.Monad.State> :t foo
foo :: Num a => StateT b Maybe a

すると、foo は StateT b Maybe a 型のモナド値になった。概念的には (StateT b Maybe) というモナド型のデータコンストラクタのコンテナに値 a が入っているとイメージできる。モナド値がイメージできたのでこれを do 記法の中で使ってみた。

Prelude Control.Monad.State> runStateT foo 3
Just (2,3)
Prelude Control.Monad.State> runStateT (do x <- foo; y <- get; return (x*y)) 3
Just (6,3)
Prelude Control.Monad.State> runStateT (do x <- lift $ Nothing; y <- get; return (x*y)) 3
Nothing

確かに (StateT b Maybe) 型のデータコンストラクタは State モナドも Maybe モナドも同時に使うことができるのが分かる。StateT b Maybe a 型のデータがモナド値であることが分かったので、StateT モナド変換子のなかに StateT b Mayb a のデータを入れ子にして内包させてみた。

Prelude Control.Monad.State> :t runStateT (lift foo) 5
runStateT (lift foo) 5 :: (Num a, Num s) => StateT b Maybe (a, s)
Prelude Control.Monad.State> runStateT (runStateT (lift foo) 5) 3
Just ((2,5),3)

最後の例はわかりにくいかもしれないが、最初に StateT と Maybe モナドの複合モナド StateT b Maybe a を作って、さらに StateT と StateT b Maybe b モナドの複合モナドを作るというイメージが操作的にできれば、複数のモナドを利用する複合モナドが使えるようになる。最後の runStateT を入れ子に使っているところが StateT c (StateT b Maybe) a モナドのコンテナの値を取り出す方法だ。


[PR]
# by tnomura9 | 2018-12-12 05:55 | Haskell | Comments(0)

モナドの return 関数

モナドの return 関数は一種の抽象関数だ。どんなモナドのプログラムにも出現して、値をモナドに包んで返すという抽象的な働きをしている。それは、リターン関数をいろいろなモナドに使用した次の実行例からもわかる。

Prelude> return 5 :: Maybe Int
Just 5
Prelude> return 5 :: [Int]
[5]
Prelude> return 5 :: Either () Int
Right 5

なぜこのようなことができるかと言うと、モナドの関数は全て M a 型の値を返すからだ。モナドとは概念的には、唯一つのコンテナを持つデータコンストラクタ M a のモナド値と a -> M b 型の引数を一つ取りモナド値を返すモナド関数を、バインド演算子で結合したものと思うとわかりやすい。Ma >>= foo は M b 型の値を返す。M b 型もやはりモナド値なので、>>= は次のように次々に連結していくことができる。

Ma >>= foo >>= bar >>= ... >>= buz

どのように連結していってもモナドのプログラムが返すのは M a というモナド値であることには変わりない。したがって、return 関数はどのようなモナドについても生のデータをモナドでラッピングすることができる。

ようするにモナドとは、モナド値 M a、モナド関数 a -> M b、return 関数、バインド演算子 >>= からのみ構成されているプログラムだ。これが端的に現れるのは Identity モナドのプログラムだ。Maybe モナドなどには失敗値である Nothing の処理ができるなど固有の機能があるが、Identity モナドにはそのようなものは全く無い単に最も基本的なモナドとして働くだけだ。したがって、Identityモナドで動くブログラムは原則どのようなモナドでも動く。

たとえば、次のプログラム foo は do 記法と <- とreturn からしかできていない。これは Identity モナドのプログラムとして働く。

Prelude> import Control.Monad.Identity
Prelude Control.Monad.Identity> foo x = do y <- return 10; return (x*y)
Prelude Control.Monad.Identity> foo 5 :: Identity Int
Identity 50

しかし、これは同時にこのプログラムが Maybe モナドや Either モナドでも動くことを意味している。

Prelude Control.Monad.Identity> foo 5 :: Maybe Int
Just 50

Prelude Control.Monad.Identity> foo 5 :: Either () Int
Right 50

これらのことは、Identity モナドで設計されたプログラムに Maybe 型や Either 型を使ったエラー処理などを簡単に導入できることを示している。

いろいろなモナドのプログラムは、モナドとしての共通の性質に注目すれば、プログラムの本体にほとんど手を入れずに他のモナドのプログラムとして利用できる。これが実際のプログラムにどのような利点をもたらしてくれるのかはわからないが、面白い性質だ。

Haskell のいろいろなプログラムテクニックは発展途中のような気がする。これからも色々な工夫が試され、発展したり、廃れたりしていくのだろう。結構理解するのが大変な割に実際にはあまり有用ではなかったりするものもあるのではないだろうか。しかし、Haskell のそのような様々なトリックを楽しめるところは、いろいろと楽しい。


[PR]
# by tnomura9 | 2018-12-09 23:00 | Haskell | Comments(0)

monad transformers -- モナド変換子とエラー処理(2)

前回、monad transformers step by step 日本語版)の 2 章 3 説で ExceptT モナド変換子を用いたエラー処理を導入した。前回のソースは、複合モナドを導入することでフレームワークを作ったが、エラー処理そのもののコーディングはまだだった。しかし、フレームワークができているので、個別のエラー処理を記述するのは簡単だった。詳しくは原著を参考にしてほしいが、次の実行例のように、構文エラーのときに Left のコンテナにエラーメッセージを入れて表示できる。

*Transformers> runEval2 (eval2 Map.empty (Var "x"))
Identity (Left "unbound variable: x")
*Transformers> runEval2 (eval2 Map.empty (Plus (Lit 1) (Var "x")))
Identity (Left "unbound variable: x")
*Transformers> runEval2 (eval2 Map.empty (App (Lit 1) (Lit 2)))
Identity (Left "type error in application")

モナド変換子と複合モナドの説明がわかりにくかったので今まで挑戦しなかったのが悔やまれる。これまでで、2つのモナドを同時に利用する方法についてなんとなくわかって来たような気がするが、原著にはさらに3つ以上のモナドを含む複合モナドについて解説してある。少々疲れてきがが、すこし休憩してから挑戦してみようと思う。

この記事のソースは次のようになる。

module Transformers where
import Control.Monad.Identity
import Control.Monad.Except
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map

type Name = String
data Exp =
    Lit Integer
  | Var Name
  | Plus Exp Exp
  | Abs Name Exp
  | App Exp Exp
  deriving (Show)
data Value =
    IntVal Integer
  | FunVal Env Name Exp
  deriving (Show)
type Env = Map.Map Name Value

exampleExp = Lit 12 `Plus` (App (Abs "x" (Var "x")) (Lit 4 `Plus` Lit 2))

type Eval2 a = ExceptT String Identity a
runEval2 :: Eval2 a -> Identity (Either String a)
runEval2 ev = runExceptT ev

eval2 :: Env -> Exp -> Eval2 Value
eval2 env (Lit i) = return $ IntVal i
eval2 env (Var n) = case Map.lookup n env of
  Nothing -> throwError ("unbound variable: " ++ n)
  Just val -> return val
eval2 env (Plus e1 e2) = do
  e1' <- eval2 env e1
  e2' <- eval2 env e2
  case (e1', e2') of
    (IntVal i1, IntVal i2) -> return $ IntVal (i1 + i2)
    _ -> throwError "type error in addition"
eval2 env (Abs n e) = return $ FunVal env n e
eval2 env (App e1 e2) = do
  val1 <- eval2 env e1
  val2 <- eval2 env e2
  case val1 of
    FunVal env' n body -> eval2 (Map.insert n val2 env') body
    _ -> throwError "type error in application"

[PR]
# by tnomura9 | 2018-12-08 22:59 | Haskell | Comments(0)

monad transformeers -- 複合モナドでエラー処理

今回は Monad Transformers Step by Step (日本語版)の第2章3節に従ってエラー処理を導入する。前回までの Identity モナドのプログラムで Identity モナドの代わりに複合モナド ExceptT String Identity a に変更することで評価プログラムにエラー処理を導入する。おどろいたことに、これは前回の数式の評価をする関数 eval1 には全く変更を行わない。Eval1 a モナドの型とrunEval 関数の定義を変更するだけだ。前回のプログラムでは Eval a モナドの方と runEval1 関数の定義は次のようになっていた。

type Eval1 a = Identity a
runEval1 :: Eval1 a -> a
runEval1 ev = runIdentity ev

これを次のように変えるだけだ。

type Eval2 a = ExceptT String Identity a
runEval2 :: Eval a -> Identity (Either String a)
runEval2 ev = runExceptT ev

元々の Eval1 a は 単に Identity a モナドの別名だった。今回定義した複合モナド Env2 a は、これを ExceptT String Identity a モナドに変更している。複雑だがイメージ的には Except というデータコンストラクタに対し String 型と、Identity モナドと、値 a がコンテナに入っていると思うと分かりやすい(公式の見解ではないあくまでもイメージ)。

runEval2 関数は Eval2 a モナドを引数にとり、Identity (Either String a) 型の値を返す。これはモナド変換子の仕様で、runExcept 関数の戻り値の型が Identity モナドでコンテナの値が Either String a 型の値になることを示している。

イメージ的には、runIdentity 関数は戻り値に Identity a 型の値を返すところが、runEval2 関数では Identity 関数のコンテナの値を Either String a モナドにくるんで返すと考えると分かりやすい。

eval のプログラムはモナドのプログラムなので、Identity モナドであろうと Except String Identity a モナドであろうと一律に使うことができる。使い分けはコンパイラが面倒を見てくれる。これは Identity モナドの関数がそのまま IO モナドで動いてしまうことを見た前回の記事と同じ発想だ。これは、第1章の eval0 関数を第2章のモナドプログラムの eval1 プログラムに編集したときからの目的だ。

以上に述べたたったこれだけの変更で eval1 関数が ExceptT 変換子によるエラー処理付きの評価関数に変貌してしまう。

*Transformers> runEval2 (eval2 Map.empty exampleExp)
Identity (Right (IntVal 18))

動作確認をしたソースは次のようになる。

module Transformers where
import Control.Monad.Identity
import Control.Monad.Except
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map

type Name = String
data Exp =
    Lit Integer
  | Var Name
  | Plus Exp Exp
  | Abs Name Exp
  | App Exp Exp
  deriving (Show)
data Value =
    IntVal Integer
  | FunVal Env Name Exp
  deriving (Show)
type Env = Map.Map Name Value

exampleExp = Lit 12 `Plus` (App (Abs "x" (Var "x")) (Lit 4 `Plus` Lit 2))

type Eval2 a = ExceptT String Identity a
runEval2 :: Eval2 a -> Identity (Either String a)
runEval2 ev = runExceptT ev

eval2 :: Env -> Exp -> Eval2 Value
eval2 env (Lit i) = return $ IntVal i
eval2 env (Var n) = return $ fromJust $ Map.lookup n env
eval2 env (Plus e1 e2) = do
  IntVal i1 <- eval2 env e1
  IntVal i2 <- eval2 env e2
  return $ IntVal (i1 + i2)
eval2 env (Abs n e) = return $ FunVal env n e
eval2 env (App e1 e2) = do
  val1 <- eval2 env e1
  val2 <- eval2 env e2
  case val1 of
    FunVal env' n body -> eval2 (Map.insert n val2 env') body

[PR]
# by tnomura9 | 2018-12-08 21:16 | Haskell | Comments(0)

monad transformers -- Identity モナドと IO モナド

前回の記事で式を評価する関数 eval0 をモナド化して eval1 に改装した。eval1 の型は eval1 :: Env -> Exp -> Identity Value で eval1 は Identity モナドの関数だった。しかし、Iidentity モナドで記述された関数はモナド特有の関数として return と >>= しか使っていない。ということは、eval1 関数の記述を全く変更せずに eval1 :: Env -> Exp -> IO Value と eval1 の関数の型を IO モナドのものに変更するだけで eval1 関数は IO モナドの関数として使うことができる。Identity モナドのモナドとしての性質以外全く何もないという性格が、関数をそのまま IO モナドで使うという離れ業を実現させる。

ソースコードは最後に表示するが、以下の実行例のように eval1 の具体的な記述には一切手を入れずに、eval1 を IO モナドの関数として取り扱うことができることがわかる。

コードの main 関数

main = do
  x <- eval1 Map.empty exampleExp
  putStrLn $ show exampleExp
  putStrLn $ show x

実行例

~/programming/Haskell$ runghc evalio.hs
Plus (Lit 12) (App (Abs "x" (Var "x")) (Plus (Lit 4) (Lit 2)))
IntVal 18

ソース

module Transformers where
import Control.Monad.Identity
import Control.Monad.Except
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map

type Name = String
data Exp =
    Lit Integer
  | Var Name
  | Plus Exp Exp
  | Abs Name Exp
  | App Exp Exp
  deriving (Show)
data Value =
    IntVal Integer
  | FunVal Env Name Exp
  deriving (Show)
type Env = Map.Map Name Value

exampleExp = Lit 12 `Plus` (App (Abs "x" (Var "x")) (Lit 4 `Plus` Lit 2))

eval1 :: Env -> Exp -> IO Value
eval1 env (Lit i) = return $ IntVal i
eval1 env (Var n) = return $ fromJust $ Map.lookup n env
eval1 env (Plus e1 e2) = do
  IntVal i1 <- eval1 env e1
  IntVal i2 <- eval1 env e2
  return $ IntVal (i1 + i2)
eval1 env (Abs n e) = return $ FunVal env n e
eval1 env (App e1 e2) = do
  val1 <- eval1 env e1
  val2 <- eval1 env e2
  case val1 of
    FunVal env' n body -> eval1 (Map.insert n val2 env') body

main = do
  x <- eval1 Map.empty exampleExp
  putStrLn $ show exampleExp
  putStrLn $ show x

それ自身ではあまり意味がないように思われる Identity モナドだが、Identity モナドで動く関数は、全てのモナドで動作させることができる。自前の関数のモナド化には Identity モナドでの実験が必須とも言える。次回の記事では、これを ExceptT モナドで動くようにする。

[PR]
# by tnomura9 | 2018-12-06 21:25 | Haskell | Comments(0)

Haskell の例外処理

Haskellの例外処理についての分かりやすい記事を見つけた。syocy's diary というサイトの "Haskell の最近の例外ハンドリング" という記事だ。次の記事の引用のように Control.Monad.Except を利用するようだ。

import Control.Monad.Trans(lift)
import Control.Monad.Except(runExceptT, throwError, catchError)

main :: IO ()
main = do
    runExceptT $ do
        (do
                lift $ print "start"
                throwError "exception"
                lift $ print "finish"
            ) `catchError` (\e -> do
                lift $ print "caught"
                lift $ print e
            )
    return ()

これだと、普通の手続き型言語の例外処理の記述法とあまり変わりない。IO モナドの中に ExceptT モナド変換子を導入しているが、モナド変換子と lift の威力がよくわかる。Haskell の情報はまだまだ少ないが、情報が増えてくれば、Haskell も普通のプログラム言語の仲間入りをするかもしれない。

[PR]
# by tnomura9 | 2018-12-06 07:57 | Haskell | Comments(0)

monad transformers step by step モナド化

Monad Transformers Step by Step日本語版)の第1章では簡易言語の代数的データ型の定義とその評価関数 eval0 の定義を行ったが、2章ではこれをモナド変換子化する。

2章1節 Converting Monad Style ではまず評価関数 eval0 をモナド化した eval1 を定義する。

モナド化すると言ってもどうすればいいのだという話になるが、この文書では Eval1 Valu1 型のモナド型を作る。Eval1 を Monad クラスのインスタンスにするのは結構手間がかかるが、ここでは、Eval1 を Identity モナドの別名にすることでその手間を省いている。つまり次の3行を定義するだけで、Eval1 a をモナドとして扱うことができる。

type Eval1 a = Identity a
runEval1 :: Eval1 a -> a
runEval1 ev = runIdentity ev

何をやっているのかと言うと、type 宣言で Eval a を Identitiy a の別名にして、runEval ev を runIdentity ev として定義するだけだ。これで、Eval1 a はモナドとして扱うことができる。

つぎにやることは eval0 関数を Eval Value モナド型の関数に変更するだけだ。これは eval0 関数の定義を do 記法で関数を書き直すことによって eval1 関数をモナド型の関数に変更する。ソースコード全体は最後に表示するが、次のように runEval1 関数によって Eval1 モナドが評価され、eval0 関数がモナド化された eval1 になったことがわかる。

*Transformers> runEval1 (eval1 Map.empty exampleExp)
IntVal 18

モナド化の利点は何かと言うと、次のように Eval1 モナドをモナド変換子の中に組み込むことができるようになったということだ。データコンストラクタに Identityを使っているが、type 宣言では型の別名は定義できるが、データコンストラクタは導入されないからだ。

*Transformers> runStateT (do x <- lift (Identity (IntVal 2)); return x) ()
Identity (IntVal 2,())

ソースコードは次のようになる。

module Transformers where
import Control.Monad.Identity
import Control.Monad.Except
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map

type Name = String
data Exp =
    Lit Integer
  | Var Name
  | Plus Exp Exp
  | Abs Name Exp
  | App Exp Exp
  deriving (Show)
data Value =
    IntVal Integer
  | FunVal Env Name Exp
  deriving (Show)
type Env = Map.Map Name Value

exampleExp = Lit 12 `Plus` (App (Abs "x" (Var "x")) (Lit 4 `Plus` Lit 2))

type Eval1 a = Identity a
runEval1 :: Eval1 a -> a
runEval1 ev = runIdentity ev

eval1 :: Env -> Exp -> Eval1 Value
eval1 env (Lit i) = return $ IntVal i
eval1 env (Var n) = return $ fromJust $ Map.lookup n env
eval1 env (Plus e1 e2) = do
  IntVal i1 <- eval1 env e1
  IntVal i2 <- eval1 env e2
  return $ IntVal (i1 + i2)
eval1 env (Abs n e) = return $ FunVal env n e
eval1 env (App e1 e2) = do
  val1 <- eval1 env e1
  val2 <- eval1 env e2
  case val1 of
    FunVal env' n body -> eval1 (Map.insert n val2 env') body

[PR]
# by tnomura9 | 2018-12-04 22:46 | Haskell | Comments(0)