Почему я получаю сообщение об ошибке «Перекрывающиеся экземпляры», если один из них не совпадает?

Я пытаюсь разрешить встраивание монады состояния в мою свободную монаду; вот моя простая попытка:

{-# language FlexibleInstances, MultiParamTypeClasses #-}
module Main where

import Control.Monad.Free
import Control.Monad.State
import Data.Bifunctor

data Toy state next =
      Output String next
    | LiftState (state -> (next, state))
    | Done

instance Functor (Toy s) where
  fmap f (Output str next) = Output str $ f next
  fmap f (LiftState stateF) = LiftState (first f . stateF)
  fmap f Done = Done

instance MonadState s (Free (Toy s)) where
  state = overState

overState :: (s -> (a, s)) -> Free (Toy s) a
overState = liftF . LiftState

output :: Show a => a -> Free (Toy s) ()
output x = liftF $ Output (show x) ()

done :: Free (Toy s) r
done = liftF Done

program :: Free (Toy Int) ()
program = do
  start <- get
  output start
  modify ((+10) :: (Int -> Int))
  end <- get
  output end
  done

interpret :: (Show r) => Free (Toy s) r -> s -> IO ()
interpret (Free (LiftState stateF)) s = let (next, newS) = stateF s
                                         in interpret next newS
interpret (Free (Output str next)) s = print str >> interpret next s
interpret (Free Done) s = return ()
interpret (Pure x) s = print x

main :: IO ()
main = interpret program (5 :: Int)

Я получаю сообщение об ошибке:

• Overlapping instances for MonadState Int (Free (Toy Int))
    arising from a use of ‘get’
  Matching instances:
    instance [safe] (Functor m, MonadState s m) =>
                    MonadState s (Free m)
      -- Defined in ‘Control.Monad.Free’
    instance MonadState s (Free (Toy s))
      -- Defined at app/Main.hs:18:10
• In a stmt of a 'do' block: start <- get
  In the expression:
    do { start <- get;
         output start;
         modify ((+ 10) :: Int -> Int);
         end <- get;
         .... }
  In an equation for ‘program’:
      program
        = do { start <- get;
               output start;
               modify ((+ 10) :: Int -> Int);
               .... }

Насколько я могу понять; он пытается применить этот экземпляр:

(Functor m, MonadState s m) => MonadState s (Free m)

из здесь; однако в этом случае он должен соответствовать Free (Toy s), а MonadState s (Toy s) не требуется, поэтому я не понимаю, почему он считает, что он применим.

Если я удалю определение своего экземпляра, я получу:

• No instance for (MonadState Int (Toy Int))
    arising from a use of ‘modify’

Что поддерживает мою мысль о том, что другой экземпляр на самом деле не применяется; Как я могу заставить это скомпилировать, используя мой указанный экземпляр? А можете объяснить, почему это происходит? Это связано с использованием FlexibleInstances?

Спасибо!


person Chris Penner    schedule 19.01.2017    source источник
comment
Мета-комментарий: наверняка все эти вопросы должны быть дубликатом чего-то?   -  person Reid Barton    schedule 20.01.2017


Ответы (1)


Контекст экземпляра (бит (Functor m, MonadState s m)) просто игнорируется при выборе экземпляров. Это делается для того, чтобы компилятору не приходилось выполнять потенциально дорогостоящий поиск с возвратом для выбора экземпляра. Поэтому, если применяются два экземпляра, а один исключен только из-за контекста экземпляра, как в вашем случае, это перекрытие.

Это неудачная часть дизайна mtl, и я думаю, что каждый программист на Haskell сталкивался с этим в тот или иной момент. Вариантов исправлений не так много; обычно вы добавляете новый тип и даете свой экземпляр, как в

newtype FreeToy s a = FreeToy (Free (Toy s) a)
instance MonadState s (FreeToy s) where -- ...
person Daniel Wagner    schedule 19.01.2017
comment
Это здорово знать; но как я могу это исправить? - person Chris Penner; 20.01.2017
comment
@ChrisPenner Не так много вариантов. Я немного добавлю к своему ответу. - person Daniel Wagner; 20.01.2017
comment
Суть вопроса в том, что я хотел бы встроить монады состояния в свою свободную монаду; поэтому, если я могу сделать это чисто, я счастлив; хотя это кажется немного недосмотром, если нет способа заставить это работать; - person Chris Penner; 20.01.2017
comment
@ChrisPenner Вы определенно не можете внедрить монаду состояния s и использовать MonadState - fundep на MonadState говорит, что любая данная монада может иметь только один тип состояния. Это важно для уменьшения количества аннотаций типов, необходимых при использовании методов класса. - person Daniel Wagner; 20.01.2017
comment
Если я депараметризирую тип только до Toy и ограничу его до Int в качестве состояния, тогда MonadState сможет однозначно определить Toy -> Int; это правильно? Мне все еще трудно поверить, что вы не можете встроить операции mtl в Free; кажется, такой полезный случай! Как все остальные делают состояние внутри своих бесплатных монад и DSL? Версия newtype выглядит не так уж плохо, так что я попробую! - person Chris Penner; 20.01.2017
comment
Newtypes работают без особых проблем! Мне все еще интересно узнать о других дискуссиях по этому поводу; поэтому я собираюсь оставить вопрос открытым немного дольше, но это определенно помогло! Спасибо! - person Chris Penner; 20.01.2017
comment
@ChrisPenner Наличие состояния a — это нормально (даже если выбор параметризован, как в вашем примере), с использованием нового типа, как в этом ответе. Это просто несколько типов состояний, которые являются проблемой. - person Daniel Wagner; 20.01.2017