Как я могу написать функцию `run`, которая вызывает` runStateT` или `runReaderT`? - PullRequest
2 голосов
/ 29 октября 2019

Как мне написать обобщенную функцию run, которая принимает объект какого-либо монадного преобразователя и вызывает соответствующую функцию?

Дано run s,

  • Если s является StateT, run = runStateT
  • Если s является ReaderT, run = runReaderT
  • Если s является MaybeT, run = runMaybeT

Я пытался создать класс типов Runnable:

:set -XMultiParamTypeClasses
:set -XFlexibleInstances

class Runnable a b where
  run :: a -> b
  (//) :: a -> b
  (//) = run

instance Runnable (StateT s m a) (s -> m (a, s)) where
 run = runStateT

instance Runnable (ReaderT r m a) (r -> m a) where
 run = runReaderT

Но когда я пытаюсь использовать run, это не работает. Например, давайте определим simpleReader, который просто возвращает 10 при чтении:

simpleReader = ReaderT $ \env -> Just 10

runReaderT simpleReader ()

Это выводит Just 10, как и ожидалось.

Однако, когда я пытаюсь использовать run, выдает ошибку:

run simpleReader ()
<interactive>:1:1: error:
    • Non type-variable argument in the constraint: Runnable (ReaderT r Maybe a) (() -> t)
      (Use FlexibleContexts to permit this)
    • When checking the inferred type
        it :: forall r a t. (Runnable (ReaderT r Maybe a) (() -> t), Num a) => t

Если я включаю FlexibleContexts, как это предлагается, я получаю другую ошибку:

<interactive>:1:1: error:
    • Could not deduce (Runnable (ReaderT r0 Maybe a0) (() -> t))
        (maybe you haven't applied a function to enough arguments?)
      from the context: (Runnable (ReaderT r Maybe a) (() -> t), Num a)
        bound by the inferred type for ‘it’:
                   forall r a t. (Runnable (ReaderT r Maybe a) (() -> t), Num a) => t
        at <interactive>:1:1-19
      The type variables ‘r0’, ‘a0’ are ambiguous
    • In the ambiguity check for the inferred type for ‘it’
      To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
      When checking the inferred type
        it :: forall r a t. (Runnable (ReaderT r Maybe a) (() -> t), Num a) => t

Ответы [ 2 ]

6 голосов
/ 29 октября 2019

Краткий ответ: вам нужна функциональная зависимость от вашего класса.

Длинный ответ :

Когда компилятор видит run,ему нужно найти соответствующий экземпляр Runnable, чтобы определить, какую реализацию run использовать. И чтобы найти этот экземпляр, ему нужно знать, что такое a и b. Он знает, что a - это ReaderT, так что он покрыт. Но что такое b?

Компилятор видит, что вы используете b в качестве функции, передавая () в качестве аргумента. Поэтому, думает, что компилятор b должен иметь форму () -> t, где t еще не известно.

И вот где он как бы останавливается: компилятору некуда получить t отпоэтому он не знает b, поэтому он не может найти подходящий экземпляр, так что kaboom!

Но в этой ситуации есть выход. Если мы посмотрим ближе на то, что на самом деле означает ваш класс Runnable, то легко увидеть, что b должен быть строго определен как a. То есть, если мы знаем, что такое монада, мы знаем, каким будет возвращаемое значение. Следовательно, компилятор должен иметь возможность определять b, зная a. Но, увы, компилятор этого не знает!

Но есть способ объяснить это компилятору. Она называется «функциональная зависимость» и написана так:

class Runnable a b | a -> b where

Эта запись a -> b сообщает компилятору, что b должно быть однозначно определено a. Это будет означать, что, с одной стороны, компилятор не позволит вам определять экземпляры, нарушающие это правило, а с другой стороны, он сможет найти соответствующий экземпляр Runnable, просто зная a, изатем определите b из этого экземпляра.

5 голосов
/ 29 октября 2019

Тип вывода run полностью определяется типом ввода для него. Представьте это как функциональную зависимость (-XFunctionalDependencies):

class Runnable a b | a -> b where
  run :: a -> b
  -- side note: (//) does not belong here
(//) :: Runnable a b => a -> b
(//) = run
-- instances as given

Теперь это работает. Причина, по которой ваша версия не работает, заключается в том, что нет способа узнать, каким должен быть тип вывода. Если у вас есть, например,

act :: ReaderT Env Identity Ret

Затем

run act :: Runnable (ReaderT Env Identity Ret) b => b

И тогда мы застряли, нет никакого способа выяснить, каким должен быть b. Например, можно добавить еще один экземпляр

instance Runnable (ReaderT r m a) (ReaderT r m a) where
  run = id

И теперь run act может быть ReaderT Env Identity Ret или Env -> Identity Ret. Зависимость a -> b а) позволяет нам выводить b из a и б) ограничивает объявления instance, чтобы сделать это возможным. Конфликтующие экземпляры, подобные тому, который я дал, отклоняются, и run act имеет тип b, выведенный на Env -> Identity Ret, если посмотреть на instance, который вы дали, как вам нужно.

...