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