Аппликативные: <$> против чистого и <*> - PullRequest
6 голосов
/ 13 января 2020

После некоторого тестирования примеров мне кажется, что myFunction <$> и pure myFunction <*> эквивалентны при работе с классом типа Control.Applicative.

Пример:

(++) <$> Just "foo" <*> Just "bar"
pure (++) <*> Just "foo" <*> Just "bar"

оба дают Just "foobar".

Они действительно эквивалентны или я пропускаю крайний случай? Какой вариант я предпочитаю / более распространен? Хотя подход pure длиннее, он выглядит более общим и правдивым для класса Control.Applicative для меня.

Ответы [ 2 ]

10 голосов
/ 13 января 2020

Это должно быть эквивалентно. Действительно, в документации Applicative класса типов мы читаем:

Как следствие этих l aws, Functor экземпляр для f будет удовлетворять

fmap f x = pure f <*> x

, поскольку (<$>) :: Functor f => (a -> b) -> f a -> f b является:

Синоним инфикса для fmap.

Таким образом, он гласит:

f <$> x = pure f <*> x

Таким образом, оба могут быть использованы для достижения того же самого. Поскольку f <$> x, тем не менее, короче и может быть быстрее (поскольку (<*>) должен иметь дело со всеми f a с), рекомендуется использовать (<$>)). Кроме того, как @ chepner говорит , реализация по умолчанию liftA2 равна liftA2 f x = (<*>) (fmap f x), поэтому здесь также используется fmap (то есть <$>).

5 голосов
/ 13 января 2020

Я просто хотел бы кратко присоединиться здесь, потому что я придерживаюсь многих непопулярных мнений, и это одно из них, и вы так спросили, няаааах. 1003 * стиль лучше. Но я на самом деле предпочитаю pure f <*> x <*> y <*> z. Я считаю довольно распространенным, что строки, написанные в аппликативном стиле, имеют тенденцию быть longi sh, поскольку каждый аргумент часто сам по себе является вызовом функции, поэтому:

fancyParser = FancyConstructor <$> fooParserWith 7 blag <*> barParserSep "hi" (sizzleParser pop) <*> bazParser
-- OR
fancyParser = pure FancyConstructor <*> fooParserWith 7 blag <*> barParserSep "hi" (sizzleParser pop) <*> bazParser

Для удобства чтения я часто разделяю аргументы на свои собственные линии; Я нахожу, что визуальное разделение проясняет границы аргументов, немного расслабляет глаза и снижает вероятность того, что линия будет выглядеть ужасно:

fancyParser = FancyConstructor
    <$> fooParserWith 7 blag
    <*> barParserSep "hi" (sizzleParser pop)
    <*> bazParser
-- OR
fancyParser = pure FancyConstructor
    <*> fooParserWith 7 blag
    <*> barParserSep "hi" (sizzleParser pop)
    <*> bazParser

В этой форме Я думаю, что вполне понятно, почему я предпочитаю pure / <*>: это обеспечивает абсолютно последовательное начало строки. Эта последовательность визуально привлекательна, например. Но что более важно, когда я неизбежно реорганизую FancyConstructor, чтобы изменить порядок полей или вставить новое поле в начале, я могу использовать команды построчного редактирования с полной безнаказанностью. Некоторые поиски регулярных выражений также становятся немного легче. Это только устраняет небольшие трения ... но устранение небольших трений является одной из важных составляющих программирования в целом.

PS Другие непопулярные примеры способов получения согласованных форматов строк, которые я нашел удобными:

-- orthodox
foo a b c d
    = f
    . g
    . h

-- dmwit
foo a b c d = id
    . f
    . g
    . h

-- orthodox
foo a b c d =
    [ x
    , y
    , z
    ]

-- dmwit
foo a b c d = tail [undefined
    , x
    , y
    , z
    ]

-- orthodox
bad = die
    $  "some kind of "
    <> "error that is best described "
    <> "on multiple lines"

-- dmwit
bad = die $ mempty {- or "", if appropriate -}
    <> "some kind of "
    <> "error that is best described "
    <> "on multiple lines"

-- orthodox
foo
    :: arg1
    -> arg2
    -> result

-- dmwit
foo ::
    arg1 ->
    arg2 ->
    result

-- orthodox
foo a b c d =
    [ t
    | u <- v
    , w <- x
    , y <- z
    ]

-- dmwit
foo a b c d =
    [ t | _ <- [()]
    , u <- v
    , w <- x
    , y <- z
    ]
...