в Хаскелле, как заменить элементы пары при наличии возможных значений?

#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 from Data.Bifunctor вместо этого для работы с бифункторами, отличными от (,) .