Чтобы действительно понять, насколько полезным может быть вычислительное выражение, вам нужно реализовать решение как с ним, так и без него.
Без выражения вычисления (CE)
При работе с монадами вам всегда нужноbind
и функция возврата, которую здесь я буду называть rtn
, потому что return
- это ключевое слово:
let bind f reader = fun env -> f (reader env) env
let rtn x = fun _ -> x
Они являются лишь копией тех, что в упражнении.
чтобы реализовать выражение Sum
с помощью bind
и rtn
, вы можете сделать это следующим образом
let rec eval e : Map<string,int> -> int =
match e with
...
| Sum(e1, e2) ->
eval e1 |> bind (fun i1 ->
eval e2 |> bind (fun i2 ->
rtn (i1 + i2) ))
...
Этот код работает, но его трудно прочитать.
С операторами
Вы можете упростить монадический код, используя некоторые операторы для bind
и map
:
let (>>=) reader f = bind f reader
let (|>>) reader f = bind (f >> rtn) reader // map
, и тогда eval
может выглядеть следующим образом:
let rec eval e : Map<string,int> -> int = reader {
match e with
...
| Sum(e1, e2) ->
eval e1 >>= fun i1 ->
eval e2 |>> fun i2 ->
i1 + i2
...
, что является улучшением, но все же немного странно, если вы не привыкли к такому типу кода.
С CE
Вы можете сравнить его с выражением вычисленияв ответе @ kvb:
let rec eval e : Map<string,int> -> int = reader {
match e with
...
| Sum(e1, e2) ->
let! i1 = eval e1
let! i2 = eval e2
return i1 + i2
...
Все элементы одинаковы bно СЕ немного проще и понятнее.Это выглядит как обычный код, а не как монадический код.
Без монады Reader
В качестве упражнения давайте посмотрим, как будет выглядеть eval, если бы мы не использовали монаду Reader и вместо этого должны были пройтиenv
каждый раз:
let rec eval e (env: Map<string,int>) : int =
match e with
...
| Sum(e1, e2) ->
let i1 = eval e1 env
let i2 = eval e2 env
i1 + i2
...
Эй!Это выглядит почти так же, как код CE, за исключением взрыва !
, return
и env
, которые в монаде читателя неявны.