Краткий ответ: вам нужна функциональная зависимость от вашего класса.
Длинный ответ :
Когда компилятор видит 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
из этого экземпляра.