【问题标题】:Avoid long tuple definitions in haskell避免在 haskell 中使用长元组定义
【发布时间】:2014-03-24 17:55:24
【问题描述】:

对于我与hxt 的合作,我实现了以下功能:

-- | Construction of a 8 argument arrow from a 8-ary function. Same
-- implementation as in @Control.Arrow.ArrowList.arr4@.
arr8 :: ArrowList a => (b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> c)
                -> a (b1, (b2, (b3, (b4, (b5, (b6, (b7, b8))))))) c
arr8 f = arr ( \ ~(x1, ~(x2, ~(x3, ~(x4, ~(x5, ~(x6, ~(x7, x8)))))))
               -> f x1 x2 x3 x4 x5 x6 x7 x8 )

正如黑线鳕评论中提到的,上述函数arr8 采用一个 8 元函数并返回一个 8 参数箭头。我使用这样的函数:(x1 &&& x2 &&& ... x8) >>> arr8 f 其中x1x8 是箭头。

我的问题:有没有办法避免大元组定义? arr8 有更优雅的实现吗?

信息:我使用了与函数 arr4 中相同的代码架构(参见 source code of arr4

【问题讨论】:

    标签: haskell tuples hxt


    【解决方案1】:

    这行得通,尽管它依赖于一些相当深刻和脆弱的类型类魔法。它还要求我们将元组结构更改为更规则一些。特别是,它应该是一个类型级的链表,优先选择(a, (b, (c, ()))) 而不是(a, (b, c))

    {-# LANGUAGE TypeFamilies #-}
    
    import Control.Arrow
    
    -- We need to be able to refer to functions presented as tuples, generically.
    -- This is not possible in any straightforward method, so we introduce a type
    -- family which recursively computes the desired function type. In particular,
    -- we can see that
    --
    --     Fun (a, (b, ())) r ~ a -> b -> r
    
    type family   Fun h      r :: *
    type instance Fun ()     r =  r
    type instance Fun (a, h) r =  a -> Fun h r
    
    -- Then, given our newfound function specification syntax we're now in
    -- the proper form to give a recursive typeclass definition of what we're
    -- after.
    
    class Zup tup where 
      zup :: Fun tup r -> tup -> r
    
    instance Zup () where 
      zup r () = r
    
    -- Note that this recursive instance is simple enough to not require 
    -- UndecidableInstances, but normally techniques like this do. That isn't
    -- a terrible thing, but if UI is used it's up to the author of the typeclass
    -- and its instances to ensure that typechecking terminates.
    
    instance Zup b => Zup (a, b) where 
      zup f ~(a, b) = zup (f a) b
    
    arrTup :: (Arrow a, Zup b) => Fun b c -> a b c
    arrTup = arr . zup
    

    现在我们可以做

    > zup (+) (1, (2, ()))
    3
    
    > :t arrTup (+)
    arrTup (+)
      :: (Num a1, Arrow a, Zup b n, Fun n b c ~ (a1 -> a1 -> a1)) =>
         a b c
    
    > arrTup (+) (1, (2, ()))
    3
    

    如果你想定义具体的变体,它们都只是arrTup

    arr8 
      :: Arrow arr 
      => (a -> b -> c -> d -> e -> f -> g -> h -> r)
      -> arr (a, (b, (c, (d, (e, (f, (g, (h, ())))))))) r
    arr8 = arrTup
    

    最后值得注意的是,如果我们定义一个惰性uncurry

    uncurryL :: (a -> b -> c) -> (a, b) -> c
    uncurryL f ~(a, b) = f a b
    

    然后我们可以写出Zup 的递归分支,以说明这里发生了什么

    instance Zup b => Zup (a, b) where 
      zup f = uncurryL (zup . f)
    

    【讨论】:

    • 我不得不查找~的用法,所以这里是别人的链接:en.wikibooks.org/wiki/Haskell/Laziness#Lazy_pattern_matching
    • 是的!这对这里的核心技术并不重要,只是模仿@tampis 的要求。请注意,我还在其中一个 cmets 中出于不同的原因使用 (~),其中 (a ~ b) 表示“ab 的类型相同”。
    • 我有一个更早的、更复杂的版本,直到我注意到我已经停止使用它:)
    • @J.Abrahamson:感谢您提供优雅的解决方案! ;-)
    • @J.Abrahamson:你知道关于 hackage 的软件包已经为你提供了解决方案吗?
    【解决方案2】:

    我的方法是写作

    arr8 f = arr (uncurry8 f)
    

    我不知道我们是否可以编写一个通用的uncurryN n f 函数(可能不会),但我可以像这样系统地为每个n 提供一个免费的uncurry_n

    uncurry3 f = uncurry ($) . cross (uncurry . f) id
    uncurry4 f = uncurry ($) . cross (uncurry3 . f) id
    ...
    uncurry8 f = uncurry ($) . cross (uncurry7 . f) id
    

    在哪里

    cross f g  = pair (f . fst) (g . snd)
    pair f g x = (f x, g x)
    

    【讨论】:

    猜你喜欢
    • 2016-12-02
    • 2021-05-24
    • 1970-01-01
    • 2020-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多