【问题标题】:in Haskell, how to replace elements of a pair in the presence of Maybe values?在 Haskell 中,如何在存在 Maybe 值的情况下替换一对元素?
【发布时间】:2021-04-17 11:08:35
【问题描述】:

考虑一下 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 替换对的第二个元素,如果没有对则返回 Nothing:

> replace_snd 30 (Just (1, 2))
Just (1,30)
> replace_snd 30 Nothing
Nothing

inject_snd 替换第二个元素,如果没有替换则返回 Nothing:

> inject_snd (Just 30) (1, 2)
Just (1,30)
> inject_snd Nothing (1, 2)
Nothing

还要考虑它们的对称对应物replace_fstinject_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),因为MaybeMonad((,) a)Traversable

> (mapM . const) (Just 30) (1, 2)
Just (1,30)
> (mapM . const) Nothing (1, 2)
Nothing

其他三个功能是否有类似的紧凑等价物?

【问题讨论】:

    标签: haskell monads traversable


    【解决方案1】:
    import Control.Arrow
    
    replaceSnd = fmap . second . const
    

    或者fmap . fmap . const,它不需要Arrow 导入...但我不喜欢(a,) 函子,这不会扩展到replaceFst。但是,使用Arrow 也同样简单:

    replaceFst = fmap . first . const
    

    injects 有点尴尬,但仍然可以通过应用程序操作符部分轻松完成:

    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
    

    【讨论】:

    • 感谢您的快速而有帮助的回复!
    • fmap . fmap . const = fmap . (<$)
    • 顺便说一句,我个人很欣赏(a,)函子,将其视为“用a装饰的值”。我知道这是一场圣战。
    • @dfeuer 好吧,那是Writer。我对(a,) monad 以及(a,) traversable 的问题是,元组应该直观地表现对称,但不是。相比之下,没有人期望 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

    因此,我们在c 上执行fmaping,如果cJust y,我们将返回Just (first (const y) x),否则如果cNothing,则返回@ 987654341@.

    例如:

    Prelude Control.Arrow> inject_fst Nothing (1,2)
    Nothing
    Prelude Control.Arrow> inject_fst (Just 0) (1,2)
    Just (0,2)
    

    这些函数不仅适用于Maybe,还适用于列表[]Tree 等。

    【讨论】:

    • 感谢您的快速而有帮助的回复!
    • Arrow 的概括在这里似乎对您没有帮助。您可以使用Data.Bifunctor 中的first 来代替(,) 以外的双函子。
    猜你喜欢
    • 1970-01-01
    • 2017-06-21
    • 1970-01-01
    • 2021-09-28
    • 2020-06-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-20
    相关资源
    最近更新 更多