Haskell函数定义约定

时间:2014-03-31 06:17:33

标签: function haskell

我是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函数定义中使用传统的数学约定吗?

如果错,在哪些特定情况下会出现错误?

提前致谢:)

3 个答案:

答案 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,则在调用它时不能使用它们。

所以似乎没有理由偏爱另一个 - 这只是一个品味问题。但实际上我认为 是一个很好的理由而不喜欢一个,而你应该更喜欢没有括号的风格。有三个很好的理由:

  1. 它看起来更干净,如果你没有让页面混乱的页面,你的代码也更容易阅读。

  2. 如果你到处都使用parens,你将会受到性能影响,因为你需要在每次使用函数时构造和解构一对(尽管编译器可能会优化它 - 我不确定)。 / p>

  3. 您希望获得 currying 的好处,即部分应用的功能 *。

  4. 最后一点有点微妙。回想一下,我说理解类型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)

  1. 数学符号不一致。如果使用(,)为所有函数提供参数,则必须编写(+)((*)(a,b),c)以将a*bc传递给函数+ - 当然,{{1通过将a*ba传递给函数b来解决问题。

  2. 可以用tupled形式编写所有内容,但定义组合要困难得多。现在你可以指定一个类型*来覆盖任何arity的函数(因此,你可以将组合定义为类型a->b的函数),使用元组定义任意arity的函数要复杂得多(现在(b->c)->(a->b)->(a->c)只表示一个参数的函数;你不能再用许多参数的函数组成许多参数的函数)。因此,技术上可行,但它需要语言功能才能使其简单方便。