我是Haskell的初学者。
根据我的学校资料在功能定义中使用的惯例实际上如下
function_name arguments_separated_by_spaces = code_to_do
前:
f a b c = a * b +c
作为一名数学学生,我习惯使用如下的功能
function_name(arguments_separated_by_commas)= code_to_do
前:
f(a,b,c) = a * b + c
它在Haskell工作。
我怀疑它是否适用于所有情况?
我的意思是我可以在Haskell函数定义中使用传统的数学约定吗?
如果错,在哪些特定情况下会出现错误?
提前致谢:)
答案 0 :(得分:13)
假设您要定义一个计算右三角形的低位的平方的函数。以下任一定义均有效
hyp1 a b = a * a + b * b
hyp2(a,b) = a * a + b * b
但是,它们的功能不一样!您可以通过查看GHCI中的类型来判断
>> :type hyp1
hyp1 :: Num a => a -> a -> a
>> :type hyp2
hyp2 :: Num a => (a, a) -> a
首先考虑hyp2
(暂时忽略Num a =>
部分),该类型会告诉您该函数需要一对(a, a)
并返回另一个a
(例如它可能取一对整数并返回另一个整数,或一对实数并返回另一个实数)。你像这样使用它
>> hyp2 (3,4)
25
请注意,括号在这里不是可选的!它们确保参数的类型正确,一对a
s。如果你不包含它们,你会收到一个错误(现在看起来可能会让你感到困惑,但请放心,当你学会了类型类时它会有意义。)
现在查看hyp1
一种方法来读取类型a -> a -> a
,它需要两种a
类型的东西,并返回类型为a
的其他内容。你像这样使用它
>> hyp1 3 4
25
如果你做包括括号,现在你会收到错误!
首先要注意的是,使用该函数的方式必须与您定义它的方式相匹配。如果使用parens定义函数,则每次调用它时都必须使用parens。如果在定义函数时不使用parens,则在调用它时不能使用它们。
所以似乎没有理由偏爱另一个 - 这只是一个品味问题。但实际上我认为 是一个很好的理由而不喜欢一个,而你应该更喜欢没有括号的风格。有三个很好的理由:
它看起来更干净,如果你没有让页面混乱的页面,你的代码也更容易阅读。
如果你到处都使用parens,你将会受到性能影响,因为你需要在每次使用函数时构造和解构一对(尽管编译器可能会优化它 - 我不确定)。 / p>
您希望获得 currying 的好处,即部分应用的功能 *。
最后一点有点微妙。回想一下,我说理解类型a -> a -> a
的函数的一种方法是它需要两个a
类型的东西,并返回另一个a
。但还有另一种方法可以读取该类型,即a -> (a -> a)
。这意味着完全相同的事情,因为->
运算符在Haskell中是右关联的。解释是该函数采用单个a
,并返回类型为a -> a
的函数。这允许您只为函数提供第一个参数,稍后应用第二个参数,例如
>> let f = hyp1 3
>> f 4
25
这在各种情况下实际上都很有用。例如,map
函数允许您将某些函数应用于列表的每个元素 -
>> :type map
map :: (a -> b) -> [a] -> [b]
假设你有(++ "!")
函数可以为任何String
添加爆炸效果。但是你有Strings
的列表,你希望它们都以爆炸结束。没问题!您只需部分应用map
功能
>> let bang = map (++ "!")
现在bang
是**
>> :type bang
bang :: [String] -> [String]
你可以像这样使用它
>> bang ["Ready", "Set", "Go"]
["Ready!", "Set!", "Go!"]
非常有用!
我希望我已经说服你,学校教育材料中使用的惯例有一些非常可靠的原因可供使用。作为一个拥有数学背景的人,我可以看到使用更“传统”语法的吸引力,但我希望随着你在编程过程中的进步,你将能够看到改变为最初有点的东西的优势你不熟悉。
*对于学生的注意事项 - 我知道currying和部分应用并不完全相同。
**实际上GHCI会告诉你类型为bang :: [[Char]] -> [[Char]]
,但由于String
是[Char]
的同义词,因此这些意思相同。
答案 1 :(得分:2)
f(a,b,c) = a * b + c
要理解的关键区别是上面的函数需要三倍并给出结果。你实际做的是三重模式匹配。上述函数的类型如下:
(a, a, a) -> a
如果你写这样的函数:
f a b c = a * b + c
你可以在这个功能中获得自动咖喱。
您可以编写类似let b = f 3 2
之类的内容,它会进行类型检查,但同样的事情不适用于您的初始版本。此外,使用(.)
组合各种功能时,像currying这样的东西可以帮助很多,除非你试图组合三元组,否则再用前一种风格无法实现。
答案 2 :(得分:2)
数学符号不一致。如果使用(,)
为所有函数提供参数,则必须编写(+)((*)(a,b),c)
以将a*b
和c
传递给函数+
- 当然,{{1通过将a*b
和a
传递给函数b
来解决问题。
可以用tupled形式编写所有内容,但定义组合要困难得多。现在你可以指定一个类型*
来覆盖任何arity的函数(因此,你可以将组合定义为类型a->b
的函数),使用元组定义任意arity的函数要复杂得多(现在(b->c)->(a->b)->(a->c)
只表示一个参数的函数;你不能再用许多参数的函数组成许多参数的函数)。因此,技术上可行,但它需要语言功能才能使其简单方便。