Почему экземпляр Applicative для Maybe может ничего не давать, когда функция имеет значение Nothing в <*>

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

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

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

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

  Nothing <*> something = something

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

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

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

4 ответа

Решение

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

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

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

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


Изменить: так как это, кажется, Maybe (a -> a) Сценарий, который вас действительно волнует, вот пара примеров того, что вы можете сделать с помощью набора значений этого типа:

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

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 но это немного сложнее читать...

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

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

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

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

Смотрите также Applicative законы.

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

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

Just id <*> Just something

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

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

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

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

<$> просто fmap, а также fmap f Nothing является Nothing, так что сводится к:

Nothing <*> Just 3

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

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

Другие вопросы по тегам