Упрощение вызова функций, хранящихся в среде ReaderT - PullRequest
2 голосов
/ 06 мая 2020

Предположим, у меня есть такая запись среды:

import Control.Monad.IO.Class
import Control.Monad.Trans.Reader

type RIO env a = ReaderT env IO a

data Env = Env
  { foo :: Int -> String -> RIO Env (),
    bar :: Int -> RIO Env Int
  }

env :: Env
env =
  Env
    { foo = \_ _ -> do
        liftIO $ putStrLn "foo",
      bar = \_ -> do
        liftIO $ putStrLn "bar"
        return 5
    }

Функции, хранящиеся в среде, могут иметь разное количество аргументов, но они всегда будут выдавать значения в монаде RIO Env, то есть , в ReaderT поверх IO, параметризованного самой средой.

Я хотел бы иметь краткий способ вызова этих функций внутри монады RIO Env.

Я мог бы напишите что-то вроде этой call функции:

import Control.Monad.Reader 

call :: MonadReader env m => (env -> f) -> (f -> m r) -> m r
call getter execute = do
  f <- asks getter
  execute f

И используйте это так (возможно, в сочетании с -XBlockArguments):

 example1 :: RIO Env ()
 example1 = call foo $ \f -> f 0 "fooarg"

Но, В идеале я хотел бы иметь версию call, которая допускала бы следующий более прямой синтаксис и при этом работала бы для функций с другим количеством параметров:

 example2 :: RIO Env ()
 example2 = call foo 0 "fooarg"

 example3 :: RIO Env Int
 example3 = call bar 3

Возможно ли это?

Ответы [ 2 ]

3 голосов
/ 06 мая 2020

Из двух примеров мы можем предположить, что call будет иметь тип (Env -> r) -> r.

example2 :: RIO Env ()
example2 = call foo 0 "fooarg"

example3 :: RIO Env Int
example3 = call bar 3

Поместите это в класс типа и рассмотрите два случая, r - стрелка a -> r' или r - это RIO Env r'. Реализация вариативности с классами типов обычно не одобряется из-за того, насколько они fr agile, но здесь она работает хорошо, потому что тип RIO обеспечивает естественный базовый случай, и все определяется типами средств доступа (так что вывод типа не мешает).

class Call r where
  call :: (Env -> r) -> r

instance Call r => Call (a -> r) where
  call f x = call (\env -> f env x)

instance Call (RIO Env r') where
  call f = ask >>= f
1 голос
/ 07 мая 2020

Вот несколько небольших улучшений в ответе Ли-яо. Эта версия не указывает c на IO в качестве базовой монады или Env в качестве типа среды. Использование ограничения равенства в экземпляре базового случая должно немного улучшить вывод типа, хотя, поскольку call предназначен для использования, которое, вероятно, повлияет только на типизированные дыры.

{-# language MultiParamTypeClasses, TypeFamilies, FlexibleInstances #-}

class e ~ TheEnv r => Call e r where
  type TheEnv r
  call :: (e -> r) -> r

instance Call e r => Call e (a -> r) where
  type TheEnv (a -> r) = TheEnv r
  call f x = call (\env -> f env x)

instance (Monad m, e ~ e') => Call e (ReaderT e' m r) where
  type TheEnv (ReaderT e' m r) = e'
  call f = ask >>= f

Связанный тип, вероятно, избыточен. Также можно было бы использовать функциональную зависимость:

{-# language FunctionalDependencies, TypeFamilies, FlexibleInstances, UndecidableInstances #-}

class Call e r | r -> e where
  call :: (e -> r) -> r

instance Call e r => Call e (a -> r) where
  call f x = call (\env -> f env x)

instance (Monad m, e ~ e') => Call e (ReaderT e' m r) where
  call f = ask >>= f
...