Ну, тип (>>=)
удобен для десагерирования нотации do
, но несколько неестественно в противном случае.
Цель (>>=)
состоит в том, чтобы взять тип в монаде и функцию, которая использует аргумент этого типа для создания какого-либо другого типа в монаде, а затем объединить их, подняв функцию и сгладивдополнительный слой.Если вы посмотрите на функцию join
в Control.Monad
, она выполняет только этап выравнивания, поэтому, если бы мы взяли ее в качестве примитивной операции, мы могли бы написать (>>=)
так:
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b
m >>= k = join (fmap k m)
Примечание.однако обратный порядок аргументов fmap
.Причина этого становится ясной, если вспомнить монаду Identity
, которая является просто оболочкой нового типа для простых значений.Игнорируя новые типы, fmap
для Identity
является приложением-функцией, а join
ничего не делает, поэтому мы можем распознать (>>=)
как оператор приложения с обратными аргументами.Сравните тип этого оператора, например:
(|>) :: a -> (a -> b) -> b
x |> f = f x
Очень похожий шаблон.Итак, чтобы получить более ясное представление о том, что означает тип (>>=)
, вместо этого мы рассмотрим (=<<)
, который определен в Control.Monad
, который принимает свои аргументы в другом порядке.Сравнивая его с (<*>)
, с Control.Applicative
, fmap
и ($)
, и помня, что (->)
ассоциативно справа, и добавляя лишние скобки:
($) :: (a -> b) -> ( a -> b)
fmap :: (Functor f) => (a -> b) -> (f a -> f b)
(<*>) :: (Applicative f) => f (a -> b) -> (f a -> f b)
(=<<) :: (Monad m) => (a -> m b) -> (m a -> m b)
Итак все четыре из них, по сути, являются функциональными приложениями, последние три представляют собой способы «поднятия» функций для работы со значениями в некотором типе функторов.Различия между ними важны для различия простых значений Functor
и двух классов, основанных на них.В широком смысле сигнатуры типов могут читаться следующим образом:
fmap :: (Functor f) => (a -> b) -> (f a -> f b)
Это означает, что при простой функции a -> b
мы можем преобразовать ее в функцию, которая делает то же самое с типами * 1039.* и f b
.Так что это просто простое преобразование, которое не может изменить или проверить структуру f
, какой бы она ни была.
(<*>) :: (Applicative f) => f (a -> b) -> (f a -> f b)
Точно так же, как fmap
, за исключением того, что она принимает тип функции, который сам по себе ужев f
.Тип функции до сих пор не замечает структуру f
, но сам (<*>)
должен в некотором смысле объединить две f
структуры.Таким образом, это может изменить и проверить структуру, но только способом, определяемым самими структурами, независимо от значений.
(=<<) :: (Monad m) => (a -> m b) -> (m a -> m b)
Это глубокий фундаментальный сдвиг, потому что теперь мы берем функцию, которая создает некоторую m
структуру, которая объединяется со структурой, уже присутствующей в аргументе m a
.Таким образом, (=<<)
может не только изменять структуру, как указано выше, но поднимаемая функция может создавать новую структуру в зависимости от значений.Тем не менее, есть существенное ограничение: функция получает только простое значение и, следовательно, не может проверять общую структуру;он может осмотреть только одно местоположение и затем решить, какую структуру поместить туда.
Итак, вернемся к вашему вопросу:
Имеет ли смысл иметь классы типовс альтернативными подписями t a -> (t a -> t b) -> t b
соотв.t a -> (a -> b) -> t b
?
Если вы переписываете оба этих типа в «стандартном» порядке, как указано выше, вы можете видеть, что первый - это просто ($)
со специализированным типом, а второй - fmap
.Однако есть другие варианты, которые имеют смысл!Вот пара примеров:
contramap :: (Contravariant f) => (a -> b) -> (f b -> f a)
Это контравариантный функтор, который работает "назад".Если тип выглядит поначалу невозможным, подумайте о типе newtype Flipped b a = Flipped (a -> b)
и о том, что вы могли бы с ним сделать.
(<<=) :: (Comonad w) => (w a -> b) -> (w a -> w b)
Это двойственное значение монады - тогда как аргумент (=<<)
может толькоосмотрите локальную область и создайте часть структуры, чтобы поместить ее, аргумент (<<=)
может проверить глобальную структуру и получить итоговое значение.(<<=)
сам в некотором смысле обычно просматривает структуру, принимая итоговое значение с каждой точки зрения, а затем повторно собирает их для создания новой структуры.