如何更改参数的顺序?

24

如果我想改变函数参数的顺序怎么办?

有一个flip函数可以使用:

flip :: (a -> b -> c) -> b -> a -> c

但是我不知道如何使其适用于更多的参数。是否有一种通用方法可以排列参数?


@phimuemue 这可能不是一个函数,而是 TH 宏。是的,可以编写这样的宏,但简单的 lambda 函数几乎同样短小。 - permeakra
5
如果你倾向于经常发生争论,你可能会错过创造合适的数据类型的机会。或者你只是一个爱抱怨的人。 - Landei
2个回答

41
如果你想在编写函数后进行编辑,那么你真的应该阅读Conal Elliott的优秀博客文章语义编辑组合器

http://conal.net/blog/posts/semantic-editor-combinators

实际上,无论如何每个人都应该阅读它。这是一种真正有用的方法(我在这里滥用了它)。Conal使用比仅仅resultflip更多的结构来达到非常灵活的效果。
result :: (b -> b') -> ((a -> b) -> (a -> b'))
result =  (.)

假设我有一个使用3个参数的函数。
use3 :: Char -> Double -> Int -> String
use3 c d i = c: show (d^i)

“如果我想交换前两个,就像你说的那样使用flip use3,但是如果我想交换第二个和第三个,我想要的是将flip应用于将use3应用于其第一个参数的结果。”
use3' :: Char -> Int -> Double -> String
use3' = (result) flip use3

让我们继续并交换一个使用5的函数use5的第四个和第五个参数。
use5  :: Char -> Double -> Int -> (Int,Char) -> String     -> String
use5' :: Char -> Double -> Int -> String     -> (Int,Char) -> String

use5 c d i (n,c') s = c : show (d ^ i) ++ replicate n c' ++ s

我们需要对将其前三个参数应用于use5的结果应用flip,这样就得到了结果的结果的结果:
use5' = (result.result.result) flip use5

为什么不先把思考留到以后,然后再定义呢?
swap_1_2 :: (a1 -> a2 -> other) -> (a2 -> a1 -> other)
swap_2_3 :: (a1 -> a2 -> a3 -> other) -> (a1 -> a3 -> a2 -> other)
--skip a few type signatures and daydream about scrap-your-boilerplate and Template Haskell    

swap_1_2 = flip    
swap_2_3 = result flip
swap_3_4 = (result.result) flip
swap_4_5 = (result.result.result) flip
swap_5_6 = (result.result.result.result) flip

...这就是如果你喜欢简单和优雅,你应该停下来的地方。 请注意,类型other可以是b -> c -> d,因此由于神奇的Curry和->的右结合性, swap_2_3适用于接受两个以上参数的函数。 对于更复杂的情况,您确实应该手动编写一个排列函数。 接下来只是为了满足知识好奇心。
现在,怎么交换第二个和第四个参数? [旁注:我记得我的代数讲座上有一个定理,即任何置换都可以作为相邻项交换的组合。]
我们可以这样做: 步骤1:将2移动到4旁边(swap_2_3
a1 -> a2 -> a3 -> a4 -> otherstuff
a1 -> a3 -> a2 -> a4 -> otherstuff

将它们交换到那里,使用swap_3_4函数。
a1 -> a3 -> a2 -> a4 -> otherstuff
a1 -> a3 -> a4 -> a2 -> otherstuff

然后再次使用swap_2_3将4交换回位置2:
a1 -> a3 -> a4 -> a2 -> otherstuff
a1 -> a4 -> a3 -> a2 -> otherstuff

so

swap_2_4 = swap_2_3.swap_3_4.swap_2_3

也许有一种更简洁的方法可以直接得到很多结果并翻转,但是随机尝试没有为我找到它!
同样地,要交换1和5,我们可以将1移动到4,与5交换,再将5从4移回1。
swap_1_5 = swap_1_2.swap_2_3.swap_3_4 . swap_4_5 . swap_3_4.swap_2_3.swap_1_2

或者,如果您愿意,您可以通过在两端翻转(将1和2互换,将5和4互换)来重复使用swap_2_4,然后再次在两端翻转。
swap_1_5' = swap_1_2.swap_4_5. swap_2_4 .swap_4_5.swap_1_2

当然,定义起来更容易。
swap_1_5'' f  a b c d e = f  e b c d a

这段话的意思是:“这种方法的好处是清晰、简洁、高效,并且在ghci中具有有用的类型签名,而无需显式注释它。”另外,作者还表示这是一个非常有趣的问题,感谢提问者。

4
或者,对于不那么理智的人,“swap_5_6 = ((((翻转)。)。))”和“swap_2_4 =(翻转)。((翻转)。)。(翻转)”。 - Vaelus

12

通常最好的方法是手动完成。假设您有一个函数

f :: Arg1 -> Arg2 -> Arg3 -> Arg4 -> Res

而且您希望

g :: Arg4 -> Arg1 -> Arg3 -> Arg2 -> Res

然后你写

g x4 x1 x3 x2 = f x1 x2 x3 x4

如果您需要多次使用特定的排列,那么您当然可以从中抽象出来,就像flip在两个参数情况下所做的一样:

myflip :: (a4 -> a1 -> a3 -> a2 -> r) -> a1 -> a2 -> a3 -> a4 -> r
myflip f x4 x1 x3 x2 = f x1 x2 x3 x4

2
我认为这是最好的方法,如果您需要对参数进行专门的重新排列,则最容易定义自己的函数来执行此操作,但我怀疑任何这些都可以通过flip的组合和类似于(\x -> flip $ f x)的lambda生成,该lambda部分应用函数以将除第一和第二个参数以外的参数翻转。 - Dan Feltey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接