【问题标题】:Add elements of two tuples in Haskell在 Haskell 中添加两个元组的元素
【发布时间】:2021-10-03 06:26:37
【问题描述】:

如何在 Haskell 中添加两个元组的元素来给我第三个元组。签名类似于,

Add :: (Int,Int) -> (Int,Int) ->(Int,Int)
Add a b = ....

到目前为止,我只能想到这个:

Add a b = [(x, y) | a = (x1, y1), b = (x2, y2), x=x1+x2, y =y1+y2n]

但是,我对 Haskell 很陌生,所以我所做的是否正确?

【问题讨论】:

    标签: haskell functional-programming sum tuples


    【解决方案1】:

    你所做的不正确。列表推导式不是这样做的正确方法。

    使用模式匹配来提取元组的元素:

    add :: (Int, Int) -> (Int, Int) -> (Int, Int)
    add (x, y) (u, v) = (x+u, y+v)
    

    使用fstsnd提取元组的元素:

    add2 :: (Int, Int) -> (Int, Int) -> (Int, Int)
    add2 x y = (fst x + fst y, snd x + snd y)
    

    另外请记住,Haskell 中的函数不能以大写字母开头。

    【讨论】:

    • 这两个中,add函数的类型签名是什么?我可以看到 add2 将具有与我正在使用的原始“Add”相同的类型签名,但是您定义的这个 add 的类型签名呢?非常感谢!
    • 两者的签名相同。 add 乍一看可能并不明显,原因是它正在动态解构传入的元组,这可能看起来是不同的类型签名。我要把它添加到答案中。
    【解决方案2】:

    关于您的尝试,请尝试在 ghci 中运行它,您会看到一个错误。至于为什么,您尝试使用的语法被称为 list comprehension,并记录在 here;创建 列表 是句法附加值,例如[1,2,3],因此它不是适合您的工具,因为它能够对类型为(·,·),而不是[·]· 我的意思是“任何类型”)。

    以下是编写您想要的函数的非基本(并且可能是矫枉过正)的方法。但设定目标理解它可能是一种迫使自己深入 Haskell 的方法。

    这里是:

    import Data.Bifunctor (bimap)
    import Data.Tuple.Extra (both)
    
    sumTwoTuples x y = uncurry bimap (both (+) x) y
    

    这是如何工作的?让我们看看

    > :t both
    both :: (a -> b) -> (a, a) -> (b, b)
    

    所以both 接受一个函数并将其应用于一对的两个元素;因此both (+) 将在第一对x 的两侧应用(+);如果那是(3,4),你会得到((3+), (4+))(是的,我会把它写成(3+,4+),但这是非法的语法)。

    现在我们有了这对函数both (+) x,我们想将它们中的每一个应用到这对函数y的一侧。

    这里是bimap

    > :t bimap
    bimap :: Bifunctor p => (a -> b) -> (c -> d) -> p a c -> p b d
    

    所以它需要 2 个函数并将其应用于 Bifunctor 的每一侧(在我们的例子中,Bifunctor 类型 p(,))。

    这几乎是我们所需要的,但它将两个函数作为两个单独的参数,而不是包含它们的一对。

    uncurry 提供了一种调整方式:

    > :t uncurry 
    uncurry :: (a -> b -> c) -> (a, b) -> c
    

    确实,uncurry bimap 有这种类型:

    > :t uncurry bimap 
    uncurry bimap :: Bifunctor p => (a -> b, c -> d) -> p a c -> p b d
    

    因此它需要一对函数并将每个函数应用于y 对的对应侧,它的类型为p b d,在我们的例子中p(,)

    【讨论】:

    • 聪明,但与@user1984 提供的答案相比有点矫枉过正,因为 OP 在函数式编程方面的专业水平可能很高。
    • @Chris,当然,但对于一个新的 Haskeller 来说,这是很多想法:D
    • 这里的Bifunctor p(,)。此外,带括号的((3+),(4+)) 成为有效语法。
    • 你能澄清评论的第一部分吗? (第二部分已修复,谢谢。)
    • p 中的Bifunctor p(,)uncurry bimap :: Bifunctor p => (a -> b, c -> d) -> p a c -> p b d 类型具体变为 uncurry bimap :: (a -> b, c -> d) -> (,) a c -> (,) b d,因为您将它与对一起使用。这不是一对b。对 b(,) b d 类型的值。 (对于类型 var 和 value 使用相同的名称 b 可能会造成混淆,也许切换到 xy?)
    【解决方案3】:

    函数最终生成值的类型以( 开头,但列表的类型以[ 开头。他们不能匹配,所以你的方法不可能是正确的。但我们可以修复它。

    您尝试将值与变量的元组进行模式匹配是正确的,但实际上模式在等号的左边,值在等号的右边。而且它必须在let:

    add1 :: (Int,Int) -> (Int,Int) -> [(Int,Int)]   -- NB: added brackets!
    add1 a b = [(x, y) | let (x1, y1) = a ; (x2, y2) = b ; 
                             x = x1 + x2 ; y = y1 + y2 ]
    

    实际上我们不需要let 列表理解中,

    add2 :: (Int,Int) -> (Int,Int) -> [(Int,Int)]
    add2 a b = let { (x1, y1) = a ; (x2, y2) = b ; 
                     x = x1 + x2 ; y = y1 + y2 }    in
           [(x, y)] 
    

    现在我们可以去掉那些括号,得到你想要的值和类型。


    还有另一种有点棘手的方法可以让您的原始代码在不更改任何内容in的情况下工作(除了修复add 的错误大写并使其成为正确的let 语法) .

    我们只需添加一个词并启用一个扩展,它就可以工作:

    {-# LANGUAGE MonadComprehensions #-}
    
    import Data.Function.Identity
    
    add :: (Int,Int) -> (Int,Int) -> (Int,Int)
    add a b = magicWord
      [(x, y) | let { (x1, y1) = a ; (x2, y2) = b ; 
                      x = x1 + x2 ; y = y1 + y2 } ]
    

    神奇的词是

    magicWord = runIdentity
    

    使用该扩展,推断的定义类型是

    add :: (Num t1, Num t, Monad m) 
        => (t, t1) -> (t, t1) -> m (t, t1)
    

    自从

    runIdentity :: Identity a -> a
    

    使用它会强制使用m ~ Identity,它就可以工作:派生类型是

    (Num t1, Num t, Monad m) 
        => (t, t1) -> (t, t1) -> m  (t, t1)
                                 Identity a -> a
    --------------------------------------------
                               m ~ Identity        , Monad Identity
                                  (t,t1) ~ a
    --------------------------------------------
    (Num t1, Num t) 
        => (t, t1) -> (t, t1) ->           (t, t1)
    

    与您给定的类型签名匹配

         (Int, Int) -> (Int, Int) ->     (Int, Int)
    

    因为IntNum中,

    Identity 确实是一个 monad,什么都不做,

    newtype Identity a = Identity {runIdentity :: a}
    fmap f (Identity a) = Identity (f a)
    join (Identity (Identity a)) = Identity a
    

    除了用它的标签标记值,它甚至会立即消失,因为类型Identity a被定义为newtype,而不是data

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-17
      • 1970-01-01
      • 2012-04-28
      相关资源
      最近更新 更多