Ограничение эффектов, как у `Freer`, с использованием стиля MTL

Мотивация: Чтобы иметь возможность управлять эффектами в MTL, как мы можем в стиле Free/Freer.

Пример может быть немного надуманным — представьте себе программу с некоторыми базовыми операциями (GHC 8.2 с использованием freer-simple),

#!/usr/bin/env stack
-- stack --resolver lts-10.2 --install-ghc runghc --package freer-simple
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
import Control.Monad.Freer

data Effect next where
  ReadFilename :: String -> Effect String
  WriteOutput :: Show a => a -> Effect ()
  Computation :: Int -> Int -> Effect Int

readFilename :: Member Effect effs => String -> Eff effs String
readFilename = send . ReadFilename

writeOutput :: (Member Effect effs, Show a) => a -> Eff effs ()
writeOutput = send . WriteOutput

computation :: Member Effect effs => Int -> Int -> Eff effs Int
computation i1 i2 = send $ Computation i1 i2

Таким образом, мы можем смоделировать программу, которая выполняет несколько простых операций, читает файл и выводит его, выполняет вычисления и выводит его,

program :: Eff '[Effect, IO] ()
program = do
 contents <- readFilename "test.txt"
 writeOutput contents
 result <- computation 12 22
 writeOutput result

Затем мы можем реализовать наши интерпретаторы, которые будут решать, как запускать код. Наш первый интерпретатор будет размещен на стороне клиента и будет запускать IO инструкций локально, а чистые инструкции он будет отправлять на сервер для расчета.

runClientEffect :: Eff '[Effect, IO] a -> IO a
runClientEffect = runM . interpretM (\case
  ReadFilename filename -> readFile filename
  WriteOutput s -> print s
  Computation i1 i2 -> do
    print "Imagine the networking happening here"
    pure $ i1 + i2)

Серверную часть мы пока можем пропустить.

Мы надеемся, что это демонстрирует интерфейс, который опирается на то, что некоторые аспекты являются чистыми и, следовательно, могут быть отправлены на сервер, в то время как другие являются нечистыми и выполняются локально.

С чем я борюсь, так это с тем, как сделать это в стиле MTL, а именно, как ограничить количество IO, которое можно выполнить в монадных операциях.

Дайте мне знать, если вопрос слишком расплывчатый!


person Tehnix    schedule 23.01.2018    source источник
comment
Вы можете найти этот пример стиля mtl информативным.   -  person Alexis King    schedule 23.01.2018
comment
Ах, я на самом деле клонировал это, но совершенно забыл об этом! Спасибо, проверю (в этот раз обязательно) :)   -  person Tehnix    schedule 23.01.2018


Ответы (1)


Это довольно просто.

import Control.Monad.IO.Class

Вместо типа данных Effect, представляющего синтаксис нашего DSL, мы определяем класс типов EffectMonad, который абстрагирует саму монаду.

class Monad m => EffectMonad m where
  readFilename :: String -> m String
  writeOutput :: Show a => a -> m ()
  computation :: Int -> Int -> m Int

Программа та же (вплоть до подписи типа).

program :: EffectMonad m => m ()
program = do
  contents <- readFilename "test.txt"
  writeOutput contents
  result <- computation 12 22
  writeOutput result

И интерпретатор дается как экземпляр (если вы накладываете эффекты с трансформерами, вот где мы сталкиваемся с проблемой O(n*m) экземпляра).

instance EffectMonad IO where
  readFilename filename = readFile filename
  writeOutput s = print s
  computation i1 i2 = do
    print "Imagine the networking happening here"
    pure $ i1 + i2

Затем запуск программы — это просто создание ее экземпляра с правильным типом.

main :: IO ()
main = program
person Li-yao Xia    schedule 23.01.2018
comment
Ха, это довольно просто! :) Я не знаю, почему я пытался быть таким необычным и определять свои собственные преобразователи монад и прочее :/ Спасибо! - person Tehnix; 23.01.2018