#haskell #monads #traversable
Вопрос:
Рассмотрим эти две функции в Haskell:
replace_snd :: b -> Maybe (a, b) -> Maybe (a, b)
replace_snd y' (Just (x, y)) = Just (x, y')
replace_snd _ Nothing = Nothing
inject_snd :: Maybe b -> (a, b) -> Maybe (a, b)
inject_snd (Just b') (a, b) = Just (a, b')
inject_snd Nothing _ = Nothing
replace_snd
заменяет второй элемент пары или ничего не возвращает, если пары нет:
> replace_snd 30 (Just (1, 2))
Just (1,30)
> replace_snd 30 Nothing
Nothing
inject_snd
заменяет второй элемент или ничего не возвращает, если замены нет:
> inject_snd (Just 30) (1, 2)
Just (1,30)
> inject_snd Nothing (1, 2)
Nothing
Также рассмотрим их симметричные аналоги replace_fst
, inject_fst
которые действуют на первый элемент пары:
replace_fst :: a -> Maybe (a, b) -> Maybe (a, b)
replace_fst x' (Just (x, y)) = Just (x', y)
replace_fst _ Nothing = Nothing
inject_fst :: Maybe a -> (a, b) -> Maybe (a, b)
inject_fst (Just a') (a, b) = Just (a', b)
inject_fst Nothing _ = Nothing
Мой вопрос заключается в следующем: какая из этих четырех функций может быть написана более компактно с использованием встроенных функций, таких как монадические операторы? И как?
Например, я понял , что inject_snd
это просто (mapM . const)
, потому Maybe
что это Monad
и ((,) a)
есть Traversable
:
> (mapM . const) (Just 30) (1, 2)
Just (1,30)
> (mapM . const) Nothing (1, 2)
Nothing
Существуют ли аналогичные компактные эквиваленты для трех других функций?
Ответ №1:
import Control.Arrow
replaceSnd = fmap . second . const
В качестве альтернативы fmap . fmap . const
, который не требует Arrow
импорта… но мне не нравится (a,)
функтор, и это не распространяется на replaceFst
. С Arrow
тем, однако, это так же просто:
replaceFst = fmap . first . const
inject
S немного более неудобны, но все еще могут быть легко выполнены с помощью раздела «Оператор приложения».:
injectSnd y t = fmap (($ t) . second . const) y
injectFst y t = fmap (($ t) . first . const) y
Опять же, вы могли бы заменить second
на fmap
, но, пожалуйста, не делайте этого.
Это также можно записать
injectSnd = flip $ t -> fmap $ ($ t) . second . const
Конечно, если вы не против перевернуть подпись, то в flip
этом нет необходимости:
injectSnd' :: (a, b) -> Maybe b -> Maybe (a, b)
injectSnd' t = fmap $ ($ t) . second . const
Комментарии:
1. Спасибо за быстрый и полезный ответ!
2.
fmap . fmap . const = fmap . (<$)
3. Кстати, я лично ценю
(a,)
функтор, рассматривая его как «ценности, украшенныеa
«. Я знаю, что это что-то вроде священной войны.4. @dfeuer ну, это так
Writer
. Проблема, с которой я сталкиваюсь с(a,)
монадой, и еще больше с(a,)
проходимыми, заключается в том, что кортежи должны интуитивно вести себя симметрично, но не делают этого. Напротив, никто не ожидаетWriter
, что его аргументы будут симметричными.
Ответ №2:
Все они могут быть компактно переписаны. Например replace_fst
(или replace_snd
) является частным случаем fmap :: Functor f => (a -> b) -> f a -> f b
:
replace_fst :: Functor f => a -> f (a, b) -> f (a, b)
replace_fst c = fmap ((,) c . snd)
или мы можем работать с first :: Arrow a => a b c -> a (b, d) (c, d)
:
import Control.Arrow(first)
replace_fst :: Functor f => a -> f (a, b) -> f (a, b)
replace_fst c = fmap (first (const c))
или более компактным:
import Control.Arrow(first)
replace_fst :: Functor f => a -> f (a, b) -> f (a, b)
replace_fst = fmap . first . const
например:
Prelude Control.Arrow> replace_fst 0 Nothing
Nothing
Prelude Control.Arrow> replace_fst 0 (Just (1,2))
Just (0,2)
что касается inject_fst
, то это то же самое, за исключением того, что мы сейчас fmap
перейдем к первому пункту, так что:
import Control.Arrow(first)
inject_fst :: Functor f => f a -> (a, b) -> f (a, b)
inject_fst c x = fmap (($ x) . first . const) c
таким образом, мы выполняем fmap
проверку того c
, где , в случае c
, если a Just y
, мы вернем a Just (first (const y) x)
, и в противном случае, если c
это Nothing
возврат a Nothing
.
например:
Prelude Control.Arrow> inject_fst Nothing (1,2)
Nothing
Prelude Control.Arrow> inject_fst (Just 0) (1,2)
Just (0,2)
Эти функции работают не только с a Maybe
, но и со списком []
и Tree
т. Д.
Комментарии:
1. Спасибо за быстрый и полезный ответ!
2. Обобщение на
Arrow
, похоже, вам здесь не поможет. Вы можете использоватьfirst
fromData.Bifunctor
вместо этого для работы с бифункторами, отличными от(,)
.