Haskell: Как <*> объявлен? [закрытый]

Как делают Вы объявляете эти функции в Применимом typeclass:

(<*>) :: f (a -> b) -> f a -> f b
(*>)  :: f a -> f b -> f b
(<*)  :: f a -> f b -> f a

(Таким образом, если они не были операторами, чем их можно было бы назвать?)

Как примечание стороны, если Вы могли бы переименовать pure к чему-то более дружественному по отношению к нематематикам, что Вы назвали бы им?

101
задан Cerbrus 15 February 2018 в 10:20
поделиться

2 ответа

Извините, я не совсем разбираюсь в математике, поэтому мне любопытно, как произносятся функции в Аппликативном классе типов

Знание вашей математики здесь в значительной степени не имеет значения, Думаю. Как вы, вероятно, знаете, Haskell заимствует некоторые термины из различных областей абстрактной математики, в первую очередь Теория категорий , откуда мы получаем функторы и монады. Использование этих терминов в Haskell несколько отличается от формальных математических определений, но обычно они достаточно близки, чтобы в любом случае быть хорошими описательными терминами.

Класс типа Applicative находится где-то между Functor и Monad , поэтому можно было бы ожидать, что он имеет аналогичную математическую основу. Документация для модуля Control.Applicative начинается с:

Этот модуль описывает структуру, промежуточную между функтором и монадой: он обеспечивает чистые выражения и последовательность, но без привязки. (Технически, сильный нестрогий моноидальный функтор.)

Хм.

class (Functor f) => StrongLaxMonoidalFunctor f where
    . . .

Я думаю, не так броско, как Монада .

По сути, все это сводится к тому, что Аппликатив не соответствует какой-либо концепции, которая особенно интересна математически, поэтому нет никаких готовых терминов, которые отражают путь он используется в Haskell. Итак, отложим пока математику.


Если мы хотим знать, как назвать (<*>) , это может помочь узнать, что это в основном означает.

Так что же случилось с аппликативом , и почему делает мы его так называем?

То, что Applicative означает на практике, способ поднимать произвольных функций в Функтор . Рассмотрим комбинацию Maybe (возможно, простейший нетривиальный Functor ) и Bool (аналогично простейшему нетривиальному типу данных).

maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not

Функция fmap позволяет нам переместить not из работы над Bool в работу над Maybe Bool .Но что, если мы хотим поднять (&&) ?

maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd' = fmap (&&)

Ну, это совсем не то, что нам нужно ! Фактически, это бесполезно. Мы можем попытаться проявить смекалку и провести еще один Бул в Может быть через спину ...

maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)

... но это бесполезно. Во-первых, это неправильно. Во-вторых, это некрасиво . Мы могли бы продолжать попытки, но оказывается, что нет способа поднять функцию с несколькими аргументами для работы с произвольным функтором . Раздражающий!

С другой стороны, мы могли бы сделать это легко, если бы использовали экземпляр Monad ]:

maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
                  y' <- y
                  return (x' && y')

Теперь это много проблем, просто чтобы перевести простую функцию - вот почему Control.Monad предоставляет функцию, которая делает это автоматически, liftM2 . 2 в его названии указывает на то, что он работает с функциями ровно с двумя аргументами; аналогичные функции существуют для функций с 3, 4 и 5 аргументами. Эти функции лучше , но не идеальны, и указывать количество аргументов некрасиво и неуклюже.

Это подводит нас к статье , в которой был представлен класс типа Applicative .В нем авторы делают по существу два наблюдения:

  • Преобразование функций с несколькими аргументами в Функтор - это очень естественная вещь
  • . Для этого не требуются все возможности ] Monad

Нормальное приложение-функция написано простым сопоставлением терминов, поэтому, чтобы сделать «расширенное приложение» как можно более простым и естественным, в статье вводятся инфиксные операторы , заменяющие приложение, перенесенные в Functor и класс типа, чтобы предоставить все необходимое для этого.

Все это подводит нас к следующему пункту: (<*>) просто представляет приложение функции -Так зачем произносить это слово иначе, чем вы произносите пробел «оператор сопоставления»?

Но если это не очень удовлетворительно, мы можем заметить, что модуль Control.Monad также предоставляет функцию, которая делает то же самое для монад:

ap :: (Monad m) => m (a -> b) -> m a -> m b

Где ap , конечно, сокращение от «применять». Поскольку любая монада может быть аппликативной , а ap нуждается только в подмножестве функций, присутствующих в последнем, мы, возможно, можем сказать, что if (<*>) не был оператором, он должен называться ap .


Мы также можем подойти к вещам с другой стороны. Операция подъема функтора называется fmap , потому что это обобщение операции map для списков. Какая функция для списков будет работать как (<*>) ? Конечно, есть то, что ap делает со списками, но само по себе это не особенно полезно.

На самом деле, для списков существует, пожалуй, более естественная интерпретация. Что приходит в голову, когда вы смотрите на следующую сигнатуру типа?

listApply :: [a -> b] -> [a] -> [b]

Есть что-то настолько заманчивое в идее выстраивать списки параллельно, применяя каждую функцию в первом к соответствующему элементу второго. К сожалению для нашего старого друга Монады , эта простая операция нарушает законы монад , если списки имеют разную длину.Но он налагает штраф Applicative , и в этом случае (<*>) становится способом связать вместе обобщенную версию zipWith , так что, возможно, мы можем представить себе, как назовем его fzipWith ?


Эта идея архивирования фактически завершает круг. Помните ту математику о моноидальных функторах? Как следует из названия, это способ объединения структуры моноидов и функторов, оба из которых являются знакомыми классами типов Haskell:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

class Monoid a where
    mempty :: a
    mappend :: a -> a -> a

Как бы они выглядели, если бы вы поместили их в коробку вместе и немного встряхнули? Из Functor мы сохраним идею структуры независимо от ее параметра типа , а из Monoid мы сохраним общую форму функций:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ?
    mfAppend :: f ? -> f ? -> f ?

Мы не хотим предполагать, что существует способ создать действительно «пустой» Функтор , и мы не можем вызвать значение произвольного типа, поэтому мы исправим тип mfEmpty как f () .

Мы также не хотим, чтобы mfAppend требовал согласованного параметра типа, поэтому теперь у нас есть следующее:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f ?

Какой тип результата для mfAppend ? У нас есть два произвольных типа, о которых мы ничего не знаем, поэтому у нас не так много вариантов.Наиболее разумно просто сохранить оба:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f (a, b)

В этот момент mfAppend теперь явно является обобщенной версией zip в списках, и мы можем восстановить Applicative ] легко:

mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)

Это также показывает нам, что pure связан с элементом идентичности Моноида , поэтому другими хорошими названиями для него могут быть любые, предполагающие значение единицы, нуль операция или что-то в этом роде.


Это было много, поэтому подведем итог:

  • (<*>) - это просто модифицированное приложение-функция, поэтому вы можете прочитать его как «ap» или «применить», или полностью исключить его. вы бы нормальное приложение функции.
  • (<*>) также примерно обобщает zipWith в списках, поэтому вы можете читать его как «zip-функторы с», аналогично чтению fmap как «map a функтор с ".

Первый ближе к назначению класса типа Applicative - как следует из названия - так что я рекомендую именно это.

На самом деле, я рекомендую либеральное использование без произношения всех расширенных операторов приложения :

  • (<$>) , которые переводят функцию с одним аргументом в Функтор
  • (<*>) , который связывает функцию с несколькими аргументами через Applicative
  • (= <<) , который связывает функцию, входящую в Монада на существующее вычисление

Все три, по сути, являются просто обычным функциональным приложением, немного приправленным.

238
ответ дан 24 November 2019 в 04:37
поделиться

Поскольку у меня нет амбиций по улучшению C. A. McCann's technical answer, я займусь более пушистым ответом:

Если бы вы могли переименовать pure во что-то более дружелюбное для таких недоумков, как я, как бы вы его назвали?

В качестве альтернативы, тем более, что нет конца постоянным воплям, наполненным гневом и предательством, против версии монады под названием "return", я предлагаю другое название, которое предполагает ее функцию таким образом, что может удовлетворить самого императивного из императивных программистов, и самого функционального из... ну, надеюсь, все могут жаловаться одинаково: inject.

Возьмите значение. "Впрыскиваем" его в Functor, Applicative, Monad, или что там у вас. Я голосую за "inject", и я одобрил это сообщение.

21
ответ дан 24 November 2019 в 04:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: