Нет экземпляра для (Show (Eval Int)), возникающего из-за использования ‘print’

#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 и in main :: 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