#haskell #monads #monad-transformers
#haskell #монады #монады-трансформеры
Вопрос:
Я новичок в Haskell и разбираюсь с несколькими примерами из книги Стивена Дила «Что бы я хотел знать при изучении Haskell».
Я застрял на этом примере monad transformer : исходный код.
Даже после упрощения кода до минимума я не смог определить причину ошибки. Я также рассмотрел здесь другие вопросы о SO, которые кажутся похожими, но причина проблемы, похоже, другая.
Вот упрощенный код:
import Control.Monad.Reader
type Env = [(String, Int)]
type Eval a = ReaderT Env Maybe a
data Expr
= Val Int
| Var String
deriving (Show)
eval :: Expr -> Eval Int
eval ex = case ex of
Val n -> return n
Код компилируется правильно, но как только я запускаю eval (Val 5)
GHCi, выдает следующую ошибку.
Prelude> eval (Val 5)
<interactive>:135:1: error:
• No instance for (Show (Eval Int)) arising from a use of ‘print’
• In a stmt of an interactive GHCi command: print it
Спасибо.
Комментарии:
1. Вы не говорите, что вы хотели / ожидали, что ghci ответит. Как можно написать код для этого отсутствующего экземпляра Show ?
2. О, да, это верно. Я имею дело с
ReaderT
этим. Я ожидал, что он выведет тот же результат, чтоreturn
и inmain :: IO String
. Я не знаю, почему я подумал, что, возможно, мой мозг дал сбой 😉
Ответ №1:
Вы не можете напечатать значение типа Eval Int
, поскольку оно определено как ReaderT Env Maybe Int
. Печать значения в этом типе по сути равносильна печати функции Env -> Maybe Int
, а функции не могут быть напечатаны.
Рассмотрите возможность вызова функции с помощью
runReaderT (eval (Val 5)) []
где выше []
обозначает среду оценки.
Комментарии:
1. Спасибо. Теперь я понимаю, почему я ошибался, предполагая, что
eval (Val 5)
это выведет значение. Я не понимаю вашего второго утверждения, в котором вы говорите: «печать значения в этом типе по сути равносильна печати функцииEnv -> Maybe Int
….» . Как вы получили эту функцию?2. Когда я проверяю тип
runReaderT (eval (Val 5))
в GHCi, он показывает, какой тип должен бытьEnv -> Maybe Int
. Именно поэтому вы сказали, что «Печать значения …» ? Я получаю все, что вы упомянули, кроме этого … как печать в этом типе по существу сводится к упомянутой вами функции?3. @shekhar
ReaderT
Значение представляет собой оболочку вокруг значения функции. Это можно увидеть, в частности, вnewtype
определенииReaderT
(например, это определение приведено вReaderT
разделе предоставленной вами ссылки), хотя синтаксис записи может сделать его немного более запутанным.4. @David Ohh понял это сейчас. Да, вы правы, сначала это сбивает с толку, возможно, это из-за того, что не используются типы продуктов и слишком много работают с типами сумм. Строка — «Значение `ReaderT» является оболочкой вокруг значения функции «. щелкнул для меня. Спасибо!
Ответ №2:
Это не лишено смысла, на самом деле это помогло мне не путать перенос / разворачивание нового типа с фактическими данными.
Если мы определяем ReaderT
как синоним типа, а не как newtype, это функция, которую мы можем применять напрямую
type MyReaderT :: Type -> (Type -> Type) -> Type -> Type
type MyReaderT a m b = a -> m b
mypure :: Applicative f => b -> MyReaderT a f b
mypure b _a = pure b
ваш eval
становится
type Eval :: Type -> Type
type Eval b = MyReaderT Env Maybe b
разворачивание MyReaderT
:
type Eval :: Type -> Type
type Eval b = Env -> Maybe b
eval :: Expr -> Eval Int
eval (Val n) = mypure n
вы можете передать среду напрямую
myeval (Val 3)
:: Env -> Maybe Int
myeval (Val 3) [("x", 4)]
= Just 3
:: Maybe Int
Мое предложение, особенно для монадных трансформаторов, заключается в реализации my*
версий всего. newtype
существуют s, позволяющие нам писать экземпляры, мы не можем написать ни один из обычных экземпляров MyReaderT ..
, но это может приблизить вас к реальной проблеме.
Для меня в школе мне было трудно понять что-то вроде
type Time :: Type
type Time = Double
type Signal :: Type -> Type
newtype Signal a = Sig { unSig :: Time -> a }
Мне нужно было сказать, что нам нужны newtype
экземпляры for, и это Signal ..
просто переносится Time -> ..
.
Комментарии:
1. Джек, рад видеть тебя здесь 🙂 Часть упаковки / разворачивания действительно сбивает с толку. Я проработал ваш код, он все прояснил.
type MyReaderT a m b = a -> m b
это также ключевая и последняя строка вашего ответа. Спасибо!2. @shekhar Есть тонкая разница между вашей
type MyReaderT a m b = a -> m b
и библиотекойnewtype ReaderT a m b = ...
. Ваше определение делает два типа совершенно одинаковыми. Определение библиотеки создает толькоReaderT a m b
отдельный тип, который, однако, изоморфен вашему типу (вам нужно будет применять / удалять оболочки по мере необходимости).3. Я видел ваш пост, шекхар! Проверьте это DerivingVia :
newtype Eval a = Eval (Env -> Maybe a) deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadFix, MonadZip, MonadFail, MonadReader Env) via ReaderT Env Maybe
. Вы можете обнаружить эти экземпляры с:instances ReaderT Env Maybe
помощью команды4. Это работает, потому
Eval a
что (и по расширениюReaderT Env Maybe a
) не просто изоморфноEnv -> Maybe a
, но представлено идентично во время выполнения. Они называются «репрезентативно равными», и это выражается вCoercible (Eval a) (Env -> Maybe a)
том, что фактическиcoerce :: Coercible a b => a -> b
лежит в основе DerivingVia . Любое объявление newtype запускает такой принудительный экземпляр, поэтомуnewtype Age = MkAge Int
вводитCoercible Age Int
5. И действительно
Age
, можно получать экземпляры черезInt
:newtype Age = MkAge Int deriving (Eq, Ord, Show, Num, ..) via Int