Почему экземпляр 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)
Сценарий, который вас действительно волнует, вот пара примеров того, что вы можете сделать с помощью набора значений этого типа:
Сохраняя все функции и отказаться от 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
но это немного сложнее читать...
Думать о <*>
как обычно *
оператор. a * 0 == 0
, право? Не важно что a
является. Таким образом, используя ту же логику, Just (const a) <*> Nothing == Nothing
, Applicative
законы диктуют, что тип данных должен вести себя так.
Причина, по которой это полезно, заключается в том, что Maybe
должен представлять присутствие чего-то, а не отсутствие чего-либо. Если вы передаете Maybe
Значение через цепочку функций, если одна функция выходит из строя, это означает, что произошел сбой, и что процесс необходимо прервать.
Поведение, которое вы предлагаете, нецелесообразно, потому что есть множество проблем с ним:
- Если отказавшая функция должна вернуть свой ввод, она должна иметь тип
a -> a
потому что возвращаемое значение и входное значение должны иметь одинаковый тип для взаимозаменяемости в зависимости от результата функции - Согласно вашей логике, что произойдет, если у вас есть
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
не имеет другого выбора, кроме как вернуть первый элемент пары (или быть неопределенным), и это можно доказать. Это свойство может показаться нелогичным, но коренится в очень ограниченной информации, которую функция имеет о своих полиморфных аргументах (особенно она не может создать ее из воздуха).