Haskell函数参数的顺序是否有意义?

aan*_*nrv 8 haskell

我一直在学习Haskell,我注意到许多内置函数接受的命令计数器中的参数直观地符合我的预期.例如:

replicate :: Int -> a -> [a]
Run Code Online (Sandbox Code Playgroud)

如果我想复制7次,我会写replicate 2 7.但是当用英语大声朗读时,函数调用就像是在说"复制2,7次".如果我自己编写了这个函数,我会交换第一个和第二个参数,这样就replicate 7 2可以读取"复制7次,2次".

当我遇到99个Haskell问题时,出现了一些其他的例子.我不得不写一个函数:

dropEvery :: [a] -> Int -> [a]`
Run Code Online (Sandbox Code Playgroud)

它将列表作为第一个参数,将Int第二个参数作为第二个参数.直觉上,我会写的标题为dropEvery :: Int -> [a] -> [a],这样dropEvery 3 [1..100]会为已读:"列表中的每滴第三个元素[1..100],但在问题的例子,它会是什么样子.: dropEvery [1..100] 3.

我也看到过这个我现在找不到的其他功能.由于实际原因以这种方式编写函数是否常见,或者这一切都在我脑海中?

Eri*_*ikR 14

函数以这种方式编写的原因之一是因为他们的curried表单变得有用.

例如,考虑函数mapfilter:

map :: (a -> b) -> [a] -> [b]
filter :: (a -> Bool) -> [a] -> [a]
Run Code Online (Sandbox Code Playgroud)

如果我想将偶数保留在列表中然后除以2,我可以写:

myfunc :: [Int] -> [Int]
myfunc as = map (`div` 2) (filter even as)
Run Code Online (Sandbox Code Playgroud)

也可以这样写:

myfunc = map (`div` 2) . filter even
         \___ 2 ____/    \___ 1 ___/
Run Code Online (Sandbox Code Playgroud)

设想这是一条从右到左的管道:

  • 首先我们保持偶数(步骤1)
  • 然后我们将每个数字除以2(步骤2)

.很像如何-在为连接管线段一起的方式运营|运营商在Unix外壳的作品.

这一切都是可能的,因为list参数mapfilter对这些函数的最后的参数.

如果你dropEvery用这个签名写你:

dropEvery :: Int -> [a] -> [a]
Run Code Online (Sandbox Code Playgroud)

然后我们可以将它包含在其中一个管道中,例如:

myfunc2 = dropEvery 3 . map (`div` 2) . filter even
Run Code Online (Sandbox Code Playgroud)


Ben*_*Ben 12

在Haskell中通常的做法是对函数参数进行排序,以便"配置"操作的参数首先出现,并且"正在操作的主要事物"是最后的.这通常是来自其他语言的反直觉,因为它往往意味着您最终会首先传递"最不重要"的信息.来自OO的特别刺耳,其中"main"参数通常是调用该方法的对象,在调用的早期发生它完全超出参数列表!

但是有一种方法让我们疯狂.我们这样做的原因是部分应用(通过currying)非常容易并且在Haskell中广泛使用.说我有像foo :: Some -> Config -> Parameters -> DataStrucutre -> DataStructure和的功能bar :: Differnt -> Config -> DataStructure -> DataStructure.如果您不习惯高阶思维,那么您只需将这些视为转换数据结构的内容.但您可以将它们中的任何一个用作"DataStructure变换器"的工厂:该类型的功能DataStructure -> DataStructure.

很可能还有其他操作这些DataStructure -> DataStructure功能配置; 至少fmap将DataStructures的变换器转换为DataStructures(列表,Maybes,IO等)仿函数的变换器.

我们有时也可以更进一步.foo :: Some -> Config -> Parameters -> DataStructure -> DataStructure再考虑一下.如果我期望的呼叫者foo会经常使用相同的调用它多次SomeConfig,但不同的Parameters,那么偶数更局部的应用成材.

当然,即使我的部分应用程序的参数处于"错误"的顺序,我仍然可以使用组合器flip和/或创建包装函数/ lambdas来完成它.但是这会在我的代码中产生很多"噪音",这意味着读者必须能够弄清楚正在做什么的"重要"事情和什么只是调整接口.

因此,基本理论是函数编写者试图预测函数的使用模式,并按照从"最稳定"到"最不稳定"的顺序列出其参数.当然,这不是唯一的考虑因素,通常存在冲突的模式,没有明确的"最佳"顺序.

但是"参数将在描述函数调用的英语句子中列出的顺序"不是我在设计函数时会给予很多重视(而不是在其他语言中).Haskell代码看起来不像英语(在大多数其他编程语言中也没有代码),并且在少数情况下试图使它更接近并没有真正帮助.

对于您的具体示例:

  1. 因为replicate,在我看来,a参数是"主"参数,所以我会把它放在最后,就像标准库那样.尽管如此,它并不是很多; 它似乎并不十分有用得多先选择复制的数量,并具有a -> [a]功能比这将是第一选择复制的元素,并有一个Int -> [a]功能.

  2. dropEvery确实似乎是以一种不稳定的顺序来看待它的论点,但不是因为我们用英语说"将每个第N个元素放在一个列表中".采用数据结构并返回"相同结构的修改版本"的函数几乎总是将数据结构作为它们的最后一个参数,其中配置"修改"的参数首先出现.

  • @luqui是的,但是他们的参数顺序还有其他原因.`div`和`mod`与数学(和其他编程语言)的现有符号系统太相关,无法反转顺序.再加上中缀运算符,第一个参数就像第二个参数一样容易. (3认同)