Проблема в том, что подпись типа act
обещает, что она будет работать на любом клиенте, но здесь вы пытаетесь ограничить ее работу только на указанном c клиенте, называемом Printer
. Это нарушает определение класса Model
.
Обычный шаблон, которому вы, очевидно, пытаетесь следовать, - это определение Model
и Client
в одной и той же монаде, например так :
class Monad m => Model m where
act :: String -> m a -> m a
class Monad m => Client m where
addServer :: String -> m ()
У этого есть хорошая, легко понятная семантика, что и act
и addServer
являются операциями "окружающего контекста", которые "доступны в монаде m
". Они почти похожи на «глобальные функции», но все еще могут быть поддельными.
Тогда Printer
может быть одним из примеров такой монады, реализующей как Client
, так и Model
. И тогда ваш производственный стек - например, ReaderT Config IO
или что-то еще у вас - может быть другим примером такой монады.
Однако, если вы настаиваете на определении Model
и Client
для разных монад единственный способ заставить работать типы - это снять ограничение Client c
с сигнатуры act
до сигнатуры класса Model
:
class (Monad m, Client c) => Model m c where
act :: String -> c a -> m a
Что будет иметь значение " каждая "модель" монада работает с определенным набором "клиентских" монад, но не только с любой случайной "клиентской" монадой".
Тогда вы можете определить экземпляр Printer
следующим образом :
instance Model Printer Printer where
act name action = do
tell [name ++ ":"]
action
И типы будут работать.
Сказав это, я еще раз хочу повторить, что ваше решение определить Client
и Model
для разных монад это запах для меня. Я настоятельно рекомендую вам пересмотреть свой дизайн, как предложено выше.