Почему экземпляр Applicative для Maybe может ничего не давать, когда функция имеет значение Nothing в <*> - PullRequest
14 голосов
/ 25 декабря 2011

Я новичок в haskell и читаю книгу "Learn you a haskell". Я уже некоторое время пытаюсь переварить функторы и аппликативные функторы.

В теме аппликативных функторов реализация экземпляра для Maybe задается как

instance Applicative Maybe where
  pure = Just
  Nothing <*> _ = Nothing
  (Just f) <*> something = fmap f something

Итак, насколько я понимаю, мы получаем Nothing, если левый боковой функтор (для <*>) равен Nothing. Мне кажется, это имеет больше смысла, как

  Nothing <*> something = something

Так что этот аппликативный функтор не действует. Какой смысл, если таковой есть, выдавать Nothing?

Скажем, у меня есть Maybe String, ценность которого я не знаю. Я должен передать эту Maybe сторонней функции, но хочу, чтобы ее результат прошел сначала несколько Maybe (a -> b). Если некоторые из этих функций Nothing, я хочу, чтобы они молча возвращали свои данные, а не выдавали Nothing, что означает потерю данных.

Итак, что стоит за возвращением Nothing в приведенном выше примере?

Ответы [ 4 ]

15 голосов
/ 25 декабря 2011

Как это будет работать?Вот подпись типа:

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

Таким образом, второй аргумент здесь будет иметь тип Maybe a, а результат должен быть типа Maybe b.Вам нужен какой-то способ превратить a в b, что вы можете сделать только в том случае, если первый аргумент не Nothing.

. Единственный способ, как что-то подобное, будет работать, если у вас есть один илибольше значений типа Maybe (a -> a) и хотите применить любые, которые не Nothing.Но это слишком специфично для общего определения (<*>).


Редактировать: Поскольку это, похоже, сценарий Maybe (a -> a), который вас действительно интересует, вот пара примеровиз того, что вы можете сделать с кучей значений этого типа:

Сохраните все функции и отбросьте Nothing s, затем примените их:

applyJust :: [Maybe (a -> a)] -> a -> a
applyJust = foldr (.) id . catMaybes

Функция catMaybesвыдает список, содержащий только значения Just, затем foldr составляет их все вместе, начиная с функции идентификации (что вы получите, если нет функций для применения).

В качестве альтернативы, вы можете использовать функции до нахождения Nothing, а затем выручить:

applyWhileJust :: [Maybe (a -> a)] -> a -> a
applyWhileJust (Just f:fs) = f . applyWhileJust fs
applyWhileJust (Nothing:_) = id

При этом используется та же идея, что и выше, за исключением того, что когда он находит Nothing, он игнорирует остальную часть списка,Если хотите, вы также можете написать это как applyWhileJust = foldr (maybe (const id) (.)) id, но это немного сложнее для чтения ...

8 голосов
/ 25 декабря 2011

Думайте о <*> как о нормальном * операторе. a * 0 == 0, верно? Неважно, что такое a. Таким образом, используя ту же логику, Just (const a) <*> Nothing == Nothing. Законы Applicative диктуют, что тип данных должен вести себя следующим образом.

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

Поведение, которое вы предлагаете, нецелесообразно, поскольку с ним связано множество проблем:

  1. Если отказавшая функция должна вернуть свой ввод, она должна иметь тип a -> a, потому что возвращаемое значение и входное значение должны иметь одинаковый тип для взаимозаменяемости в зависимости от результата функции
  2. Согласно вашей логике, что произойдет, если у вас будет Just (const 2) <*> Just 5? Как можно привести поведение в этом случае в соответствие с делом Nothing?

См. Также Applicative законы .

РЕДАКТИРОВАТЬ: исправлены опечатки кода, и снова

3 голосов
/ 25 декабря 2011

Ну что на счет этого?

Just id <*> Just something

Вариант использования Nothing возникает, когда вы начинаете использовать <*> для работы с функциями с несколькими входами.

(-) <$> readInt "foo" <*> readInt "3"

Если у вас есть функция readInt :: String -> Maybe Int, она превратится в:

(-) <$> Nothing <*> Just 3

<$> это просто fmap, а fmap f Nothing это Nothing, поэтому оно уменьшается до:

Nothing <*> Just 3

Теперь вы видите, почему это ничего не должно производить? Первоначальный смысл выражения заключался в том, чтобы вычесть два числа, но поскольку мы не смогли создать частично примененную функцию после первого ввода, нам нужно распространять эту ошибку вместо того, чтобы просто создать красивую функцию, которая не имеет ничего общего с вычитанием.

1 голос
/ 25 декабря 2011

В дополнение к превосходному ответу К.А. Макканна, я хотел бы отметить, что это может быть случай «бесплатной теоремы», см. http://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf.Суть этой статьи в том, что для некоторых полиморфных функций существует только одна возможная реализация для данной сигнатуры типа, например, fst :: (a,b) -> a не имеет другого выбора , кроме возврата первого элементапара (или быть неопределенным), и это можно доказать.Это свойство может показаться нелогичным, но коренится в очень ограниченной информации, которую функция имеет о своих полиморфных аргументах (особенно она не может создать ее из воздуха).

...