dev*_*ium 10 haskell functional-programming type-inference typeclass
我一直在阅读关于Haskell的内容,我很难理解如何用这种语言处理函数定义.
假设我正在定义一个sum函数:
let sum x y = x + y
Run Code Online (Sandbox Code Playgroud)
如果我查询Haskell的类型
:t sum
Run Code Online (Sandbox Code Playgroud)
我明白了
sum :: (Num a) => a -> a -> a
Run Code Online (Sandbox Code Playgroud)
=>运营商意味着什么?它与lambda表达式有什么关系吗?这就是=>用C#表示运算符后面的内容是一个的信号.a -> a -> a意思?通过对我一直在尝试的许多不同函数进行眼睛检查,看起来最初a -> a是参数,而final -> a是sum函数的结果.如果这是正确的,为什么不这样(a, a) -> a,这看起来更直观?ken*_*ytm 29
0哈斯克尔=>无关用C#的=>.在Haskell中,创建了一个匿名函数
\x -> x * x
Run Code Online (Sandbox Code Playgroud)
另外,不要命名该函数,sum因为Prelude中已经存在这样的函数.我们plus从现在开始称它为避免混淆.
1.无论如何,=>在Haskell提供了一个上下文的类型.例如:
show :: (Show a) => a -> String
Run Code Online (Sandbox Code Playgroud)
这里,Show a =>means a类型必须是类类 的实例Show,这意味着a必须可以转换为字符串.同样,(Num a) => a -> a -> a表示a类型必须是类Num的实例,这意味着a必须像数字一样.这会产生约束,a以便接受show或plus不接受某些不受支持的输入,例如plus "56" "abc".(字符串不像数字.)
类型类与C#的接口类似,或者更具体地说,类似于泛型中的接口基类型约束.有关详细信息,请参阅Haskell中的Explain Type Classes问题.
2. a -> a -> a手段a -> (a -> a).因此,它实际上是一个返回另一个函数的一元函数.
plus x = \y -> x + y
Run Code Online (Sandbox Code Playgroud)
这使得部分应用(currying)非常容易.部分应用程序使用很多esp.使用高阶函数时.例如,我们可以使用
map (plus 4) [1,2,3,4]
Run Code Online (Sandbox Code Playgroud)
添加4到列表的每个元素.实际上我们可以再次使用部分应用来定义:
plusFourToList :: Num a => [a] -> [a]
plusFourToList = map (plus 4)
Run Code Online (Sandbox Code Playgroud)
如果函数(a,b,c,...)->z默认写在表单中,我们必须引入很多lambdas:
plusFourToList = \l -> map(\y -> plus(4,y), l)
Run Code Online (Sandbox Code Playgroud)
Lie*_*yan 10
这是因为
Haskell中的每个函数都接受一个参数并返回单个值
如果一个函数需要取多个值,那么该函数将是一个curried函数,或者它必须采用一个元组.
如果我们添加括号,函数签名将变为:
sum :: (Num a) => a -> (a -> a)
Run Code Online (Sandbox Code Playgroud)
在Haskell中,函数签名:A -> B表示函数的"域"和函数A的"Codomain"的函数B; 或者以程序员的语言,该函数接受一个类型的参数A并返回一个类型的值B.
因此,函数定义sum :: Num -> (Num -> Num)意味着sum是"取一个类型参数a并返回类型函数的函数Num -> Num".
实际上,这导致了currying/partial功能.
currying的概念在像Haskell这样的函数式语言中是必不可少的,因为你会想要做以下事情:
map (sum 5) [1, 2, 3, 5, 3, 1, 3, 4] -- note: it is usually better to use (+ 5)
Run Code Online (Sandbox Code Playgroud)
在该代码中,(sum 5)是一个采用单个参数的函数,将为列表中的每个项调用此函数(sum 5),例如((sum 5)1)返回6.
如果sum有签名sum :: (Num, Num) -> Num,则sum必须同时接收它的两个参数,因为现在sum是"接收a tuple (Num, Num)并返回Num的函数".
现在,第二个问题,Num a => a -> a意味着什么?它基本上是一个简写,表示每次a在签名中看到,用Num或其派生类替换它.
Num a =>表示"在下面,a应引用作为类型类实例的类型Num"(它有点像数字类型的接口).
在=>操作者从所述类型的"体"分开"类型类约束".它有点像whereC#中泛型约束的运算符.您可以将其视为逻辑含义,例如"if a是数字类型,然后sum可以与类型一起使用a -> a -> a".
a -> a -> a表示"一个函数,它接受a并返回一个函数,该函数接受a并返回a".为了理解这一点,你需要理解sum x y解析为(sum x) y.
换句话说:你先sum用参数调用x.然后,您将返回一个新的类型函数a -> a.然后,您拨打的说法是功能y,现在你回来类型的函数a,其中a是类型x和y和必须的一个实例Num的类型类.
如果您想sum拥有类型Num a => (a,a) -> a,可以将其定义为sum (x,y) = x+y.在这种情况下,你有一个函数,它接受一个包含两个as 的元组并返回一个a(其中a又是一个Num类型类的实例).
然而,"咖喱风格"(函数返回函数来模拟多个参数)比元组样式更常用,因为它允许您轻松地部分应用函数.例子map (sum 5) [1,2,3].如果你已经定义sum了一个元组,你必须这样做map (\y -> sum 5 y) [1,2,3].