有没有办法在Haskell中捕获高阶函数的元组?

时间:2017-11-05 08:43:12

标签: haskell pattern-matching higher-order-functions higher-order-types

我理解Haskell中的it's impossible to pattern match functions,我完全理解为什么。但是,我有两个密切相关的问题。首先,如果您想部分应用函数以供以后使用,是否有一种方法可以定义和捕获返回值(如果它是一个元组)?或者我错了,这仍然是试图模仿我的鼻子下的匹配功能?

例如,假设我试图获得具有10的不同倍数的值的商和余数。那我怎么写这样的东西呢?

q, r :: Integral a => a -> a
(q, r) = (12345 `quotRem`)

我在这里意识到,存在单独的功能,所以我可以这样做:

q, r :: Integral a => a -> a
q = (12345 `quot`)
r = (12345 `rem`)

然而,这是一个非常具体的案例,并且有无限的其他函数示例返回元组,这些函数可以很好地概括。例如,一个返回列表中的平均数和赔率的函数。

evens, odds :: Integral a => [a] -> Int
(evens, odds) = (length . (filter even), length . (filter odd))

这引出了我的第二个问题。以上在GHCi中工作得很好。

Prelude> let (evens, odds) = (length . (filter even), length . (filter odd))
Prelude> :t evens
evens :: Integral a => [a] -> Int
Prelude> evens [1..10]
5

更令人困惑的是它甚至可以通过“模式匹配”工作,就像我在开始时使用(q, r)一样:

Prelude> let evensOdds = (length . (filter even), length . (filter odd))
Prelude> :t evensOdds
evensOdds :: (Integral a1, Integral a) => ([a1] -> Int, [a] -> Int)
Prelude> let (ev,od) = evensOdds
Prelude> :t ev
ev :: Integral a1 => [a1] -> Int
Prelude> ev [1..10]
5

在加载到GHCi中的实际文件中它也可以正常工作,即使(evens, odds)没有。为什么这两个不同,为什么第二个在GHCi中工作,如果它不能正常工作?可以通过某种方式利用这里的不同之处吗?

2 个答案:

答案 0 :(得分:5)

你从不在一个函数上模式匹配。您始终在对构造函数(,)上进行模式匹配。您的(even, odds)示例

(evens, odds) = (length . (filter even), length . (filter odd))

就像

一样
(first, second) = (x, y)

此时xy的类型并不重要。

由于(q, r)的类型,您的quotRem示例无法正常工作。让我们回想一下,并将其与(q, r)的类型进行比较:

quotRem       :: Integral n => n -> n -> (n     , n)
quotRem 12345 :: Integral n =>      n -> (n     , n)
(q, r)        :: Integral n =>           (n -> n, n -> n)

如您所见,对(q, r)'类型与quotRem'类型不同。仍然可以编写你的函数:

pairify :: (a -> (b, c)) -> (a -> b, a -> c)
pairify f = (fst . f, snd . f)

(q,r) = pairify (quotRem 12345)

但是你可以看到我们从pairify获得的收益太多了。顺便说一句,来自partition的{​​{1}}提供了Data.List功能:

(even, odds)

答案 1 :(得分:3)

查看(12345 `quotRem`)的类型:

Integral a => a -> (a, a)

这是一个返回元组的函数。如果您想将其变为功能元组,可以使用fstsnd撰写:

(q, r) = (fst . f, snd . f)
  where f = (12345 `quotRem`)

如果您想以无点的方式执行此操作,一种方法是使用&&&中的Control.Arrow组合子。它的完全一般类型是:

Arrow a => a b c -> a b d -> a b (c, d)

专门针对->箭头,即:

(b -> c) -> (b -> d) -> b -> (c, d)

因此它需要两个函数,每个函数都采用b类型的值,并在元组中返回它们的结果(类型cd)。所以在这里你可以这样做:

split = (fst .) &&& (snd .)
(q, r) = split (12345 `quotRem`)

然而,如果你看一下(length . filter even, length . filter odd)的类型,它已经是一个元组,

(Integral a, Integral b) => ([a] -> Int, [b] -> Int)

这就是为什么你可以解构这个元组以绑定evensodds