【问题标题】:Haskell, is it possible to create a curry function that can curry any number of tuple elementsHaskell,是否有可能创建一个可以咖喱任意数量的元组元素的咖喱函数
【发布时间】:2015-03-05 13:51:29
【问题描述】:

当前的 curry 函数采用一个接受 2 个元素的元组的函数,并允许对结果函数进行 curry 或部分应用。

let x = curry (\(x, y) -> x + y)
x 1 2 -- 3

是否可以创建一个 curry 函数来处理元组中包含 N 个元素的函数?

我尝试创建它,但我不确定1:类型签名和2:如何反转参数。

curryN f 0 = f
curryN f n = \a -> (curryN (f) (n-1)) a

curryN (\(x, y, z) -> x + y + z) 3
-- I assume it looks something like: \a -> (\a -> (\a -> (f) a) a) a but I'm not sure

curryN f 0 =  f
curryN f n = curryN (\a - > f a) (n -1)

顺便说一句,这样的函数是否可以发现元素的数量而不需要被告知数量是多少?

【问题讨论】:

  • 如果n 是静态已知的(例如,使用代理或类型级数字),这对于类型族来说是一个很好的练习。参见例如Uncurry for n-ary functions。使用嵌套对而不是 n 元组应该允许递归解决方案。
  • 不在标准 Haskell 中(例如 Haskell 98 或 Haskell 2010)。
  • @chi 的评论基本上回答了这个问题。稍微解释一下,问题在于curryN 的第二个参数不是值级别的数字(2 :: Int),而是一个类型级别 的数字。 (区别有点像运行时和编译时的区别。)因此编写curryN 需要高级类型系统功能。如需不同的实用解决方案,请参阅this question 的答案。
  • 感谢您的指点,但如果您能发布答案,我将不胜感激,以便我可以接受这个问题的答案。如果答案能展示实现这一目标的不同方式,包括@ChristianConkle 的实用方式和类型家族的方式,那将是非常棒的。

标签: haskell currying partial-application


【解决方案1】:

实现该功能的方法之一是使用GHC.Generics。使用这种方法,我们甚至不需要传递许多参数(或元组大小)。 这是有效的,因为有一个为元组定义的Generic 实例,它有效地将元组转换为树结构(Rep a 类型),然后我们可以从右到左遍历(在此处使用延续传递样式)沿方式并将这些参数的值打包到相同的Rep a 结构中,然后使用to 函数转换为元组并传递给原始的未咖喱函数参数。此代码仅使用类型级别的参数树(未使用from 函数),因为我们生成元组而不是接收它。 这种方法的唯一限制是 Generic 最多只能为八元素元组定义。

{-# LANGUAGE TypeOperators, MultiParamTypeClasses,
  FlexibleInstances, UndecidableInstances,
  TypeFamilies, ScopedTypeVariables #-}

import GHC.Generics


-- | class for `curryN` function
class CurryN t r where
    type CurriedN t r :: *
    curryN :: (t -> r) -> CurriedN t r

-- | Implementation of curryN which uses GHC.Generics
instance (Generic t, GCurryN (Rep t) r) => CurryN t r where
    type CurriedN t r = GCurriedN (Rep t) r
    curryN f = gcurryN (f . to)

-- | Auxiliary class for generic implementation of `curryN`
--   Generic representation of a tuple is a tree of its elements
--   wrapped into tuple constructor representation
--   We need to fold this tree constructing a curried function
--   with parameters corresponding to every elements of the tuple
class GCurryN f r where
    type GCurriedN f r :: *
    gcurryN :: (f p -> r) -> GCurriedN f r

-- | This matches tuple definition
--   Here we extract tree of tuple parameters and use other instances to "fold" it into function
instance (GCurryN f r) => GCurryN (D1 e1 (C1 e2 f)) r where
    type GCurriedN (D1 e1 (C1 e2 f)) r = GCurriedN f r
    gcurryN c = gcurryN (\t -> c (M1 (M1 t)))

-- | A node of the tree (combines at least two parameters of the tuple)
instance (GCurryN b r, GCurryN a (GCurriedN b r)) => GCurryN (a :*: b) r where
    type GCurriedN (a :*: b) r = GCurriedN a (GCurriedN b r)
    gcurryN c = gcurryN (\a -> gcurryN (\b -> c (a :*: b)))

-- | A leaf of the tree (a single tuple parameter)
instance GCurryN (S1 NoSelector (Rec0 a)) r where
    type GCurriedN (S1 NoSelector (Rec0 a)) r = a -> r
    gcurryN c = \a -> c $ M1 (K1 a)


-- Examples of usage
t2 = curryN (uncurry (&&))

t3 = curryN (\(a,b,c) -> a + b + c)

t4 = curryN (\(a,b,c,d) -> ((a , b) , (c , d)))

tf = curryN (\(f,a,xs) -> foldr f a xs)

t5 = curryN (\(a,b,c,d,e) -> (a ++ b , c - d, not e))

t7 = curryN (\(a1,a2,a3,a4,a5,a6,a7) -> a7)

【讨论】:

    猜你喜欢
    • 2022-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-02
    • 1970-01-01
    • 2018-08-13
    相关资源
    最近更新 更多