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

Haskell函数参数的顺序是否有意义?,haskell,Haskell,我一直在学习Haskell,我注意到许多内置函数接受参数的顺序与我预期的相反。例如: replicate :: Int -> a -> [a] 如果我想复制7两次,我会编写replicate27。但当用英语大声朗读时,函数调用感觉就像在说“复制2,7次”。如果我自己编写这个函数,我会交换第一个和第二个参数,以便replicate 7 2将读取“replicate 7,2次” 其他一些例子出现在我阅读时。我必须写一个函数: dropEvery :: [a] -> Int -&g

我一直在学习Haskell,我注意到许多内置函数接受参数的顺序与我预期的相反。例如:

replicate :: Int -> a -> [a]
如果我想复制7两次,我会编写
replicate27
。但当用英语大声朗读时,函数调用感觉就像在说“复制2,7次”。如果我自己编写这个函数,我会交换第一个和第二个参数,以便
replicate 7 2
将读取“replicate 7,2次”

其他一些例子出现在我阅读时。我必须写一个函数:

dropEvery :: [a] -> Int -> [a]`
它将列表作为第一个参数,将
Int
作为第二个参数。直观地说,我会将标题写为
dropEvery::Int->[a]->[a]
,这样
dropEvery 3[1..100]
将读为:“在列表中每隔三个元素拖放
[1..100]
。但是在问题的示例中,它看起来像:
dropEvery[1..100]3


我也在其他我现在找不到的函数中看到了这一点。由于实际原因,以这种方式编写函数是很常见的,还是这只是我的想法?

函数以这种方式编写的原因之一是它们的货币形式很有用

例如,考虑函数<代码>图和<代码>过滤器< /代码>:

map :: (a -> b) -> [a] -> [b]
filter :: (a -> Bool) -> [a] -> [a]
如果我想在列表中保留偶数,然后将它们除以2,我可以写:

myfunc :: [Int] -> [Int]
myfunc as = map (`div` 2) (filter even as)
也可以这样写:

myfunc = map (`div` 2) . filter even
         \___ 2 ____/    \___ 1 ___/
将其设想为从右向左的管道:

  • 首先,我们保留偶数(步骤1)
  • 然后我们将每个数字除以2(步骤2)
处的
操作符是将管道段连接在一起的一种方式,非常类似于Unix shell中的
操作符的工作方式

这都是可能的,因为
map
filter
的列表参数是这些函数的最后一个参数

如果您用此签名书写您的
dropEvery

dropEvery :: Int -> [a] -> [a]
然后我们可以将其包括在其中一条管道中,例如:

myfunc2 = dropEvery 3 . map (`div` 2) . filter even

在Haskell中,通常的做法是对函数参数进行排序,使“配置”操作的参数排在第一位,而“操作的主要对象”排在最后。这通常与其他语言的直觉相反,因为它往往意味着您最终通过了“最不重要的”参数“信息优先。尤其是来自OO的不和谐,“main”参数通常是调用方法的对象,在调用的早期出现,完全不在参数列表中

不过,我们的疯狂是有办法的。我们这样做的原因是部分应用程序(通过curry)非常简单,并且在Haskell中使用非常广泛。假设我有一个函数,比如
foo::Some->Config->Parameters->DataStructure->DataStructure
bar::different->Config->DataStructure->DataStructure
。当您不习惯于高阶思维时,您只会将这些视为您调用以转换数据结构的东西。但您也可以将它们中的任何一个用作“数据结构转换器”的工厂:类型为
DataStructure->DataStructure
的函数

很可能还有其他操作是由这样的
DataStructure->DataStructure
函数配置的;至少有
fmap
可以将数据结构的转换器转换为数据结构的函子(列表、可能、IOs等)的转换器

我们有时也可以更进一步。考虑<代码> Fo::----> CONFIG->参数->数据结构>数据结构< /代码>。如果我期望
foo
的调用者经常使用相同的
一些
Config
多次调用它,但会改变
参数,那么更多的部分应用程序将变得有用

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

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

但是“参数在描述函数调用的英语句子中列出的顺序”在设计函数时(在其他语言中也是如此),我不会给予太多的重视。Haskell代码的阅读方式和英语不一样(大多数其他编程语言中的代码也不一样),在少数情况下,试图使其更接近英语并没有真正的帮助

对于您的具体示例:

  • 对于
    replicate
    ,在我看来
    a
    参数是“main”参数,所以我会像标准库一样将其放在最后。不过里面没有太多东西;首先选择复制次数并使用
    a->[a]
    函数似乎没有比首先选择复制元素并使用
    Int->[a]
    函数更有用

  • dropEvery
    似乎确实以一种不稳定的顺序接受了它的参数,但这并不是因为我们在英语中说“在列表中每n个元素删除一次”。采用数据结构并返回“同一结构的修改版本”的函数几乎总是将数据结构作为其最后一个参数,配置“修改”的参数排在第一位


  • 如果希望翻转参数,只需使用Prelude中的
    flip
    函数即可

    replicate' = flip replicate
    
    > :t replicate'
    replicate' :: a -> Int -> [a]
    
    加入ot
    f some little bits $
      big honking calculation
      over several lines