Sho*_*hou 6 haskell functional-programming pointfree
Conal在这里认为,nullary构造的类型不是函数.然而,无点函数在维基百科上被描述为这样,当它们在它们的定义中没有明确的参数时,它看起来更像是一个currying的属性.它们究竟是如何运作的?
具体做法是:如何f = map和f = id . map在这方面有什么不同?就像在,f = map只是一个绑定到一个值恰好是一个函数,f只是简单地"返回" map(类似于f = 2"返回" 2)然后接受参数.但f = id . map它被称为函数,因为它是无点的.
Ben*_*Ben 16
Conal的博客文章归结为"非功能不是功能",例如False不是功能.这很明显; 如果你考虑所有可能的值并删除具有函数类型的值,那么剩下的是......不是函数.
这与无点定义的概念完全无关.
请考虑以下函数定义:
map1, map2, map3, map4 :: (a -> b) -> [a] -> [b]
map1 = map
map2 = id . map
map3 f = map f
map4 _ [] = []
map4 f (x:xs) = f x : map4 f xs
Run Code Online (Sandbox Code Playgroud)
这些都是同一个函数的定义(并且有无限多种方法来定义与map函数等效的东西).map1显然是一个无点的定义; map4显然不是.它们显然都有一个函数类型(同一个!),那么我们怎么能说无点定义不是函数呢?只有当我们改变我们的"功能"的定义比通常是由Haskell新手的意思(这是一个函数的类型是什么别的东西x -> y,对于一些x和y;在这种情况下,我们正在使用a -> b的x和[a] -> [b]供y).
并且定义map3是"部分无点"(点减少?); 该定义命名其第一个参数f,但未提及第二个参数.
所有这一点的要点是"无点自由"是一种定义的质量,而"作为一种功能"是价值的属性.无点函数的概念实际上没有意义,因为给定函数可以通过多种方式定义(其中一些是无点的,另一些则不是).每当你看到有人在谈论无点函数时,它们就意味着一个无点定义.
你似乎担心它map1 = map不是一个函数,因为它只是对现有值的绑定map,就像x = 2.你这里的想法很混乱.请记住,函数在Haskell中是一流的; "作为功能的东西"是"价值观的东西"的一个子集,而不是一类不同的东西!因此,当map一个现有值是一个函数时,yes map1 = map就是将新名称绑定到现有值.它也定义了这个功能map1; 两者并不相互排斥.
你通过查看代码回答"这是免费的"这个问题; 一个函数的定义.您可以通过查看类型来回答"这是一个功能"的问题.
与某些人可能认为Haskell中的所有内容都不相关的内容相反.认真.数字,字符串,布尔值等不是函数.甚至没有nullary函数.
nullary函数是一个不带参数并执行一些"副作用"计算的函数.例如,考虑这个Nullary JavaScript函数:
main();
function main() {
alert("Hello World!");
alert("My name is Aadit M Shah.");
}Run Code Online (Sandbox Code Playgroud)
不带参数的函数只有在有效的情况下才能返回不同的结果.因此,它们类似于Haskell中的IO动作,它们不带参数并执行一些有效的计算:
main = do
putStrLn "Hello World!"
putStrLn "My name is Aadit M Shah."
Run Code Online (Sandbox Code Playgroud)
相比之下,Haskell中的函数永远不会是无效的.实际上,Haskell中的函数总是一元的.Haskell中的函数总是只有一个参数.Haskell中的多参数函数可以使用currying或使用具有多个字段的数据结构来模拟.
add' :: Int -> Int -> Int -- an example of using currying
add' x y = x + y
add'' :: (Int, Int) -> Int -- an example of using multi-field data structures
add'' (x, y) = x + y
Run Code Online (Sandbox Code Playgroud)
Haskell中的函数是一种数据类型,就像您在Haskell中定义的任何其他数据类型一样.但是,函数是特殊的,因为它们在参数类型中是逆变的,而在返回类型中是协变的.
当您定义新的代数数据类型时,其类型构造函数的所有字段都是协变的(即数据源)而不是逆变(即数据的接收器).协变字段产生数据,而逆变字段消耗数据.
例如,假设我创建了一个新的数据类型:
data Foo = Bar { field1 :: Char, field2 :: Int }
| Baz { field3 :: Bool }
Run Code Online (Sandbox Code Playgroud)
在此领域field1,field2并且field3是协变的.他们生产的类型的数据Char,Int并Bool分别.考虑:
let x = Baz True -- I create a new value of type Foo
in field3 x -- I can access the value of field3 because it is covariant
Run Code Online (Sandbox Code Playgroud)
现在,考虑函数的定义:
data Function a b = Function { domain :: a -- the argument type
, codomain :: b -- the return type
}
Run Code Online (Sandbox Code Playgroud)
当然,函数实际上并没有如下定义,但我们假设它是.一个函数有两个字段domain和codomain.当我们创建该类型的值时,Function我们不知道这两个字段中的任何一个.
domain因为它是逆变的.因此,它需要由用户提供.codomain因为虽然它是协变的但它可能依赖于domain我们并且我们不知道它的价值domain.例如,\x -> x + xis是is的值和domainis x的值的codomain函数x + x.这里domain是逆变(即数据汇),因为数据通过数据进入函数domain.类似地,codomain协变(即数据源)是因为数据是通过函数出来的codomain.
Haskell中的代数数据结构领域(就像Foo我们之前定义的那样)都是协变的,因为数据是通过它们的字段从这些数据结构中产生的.数据永远不会像它对domain函数领域那样进入这些结构.因此,它们永远不会逆变.
正如我之前解释的那样,尽管Haskell中的所有函数都是一元的,但我们可以使用currying或具有多个数据结构的字段来模拟多参数函数.
为了理解这一点,我将使用一种新的符号.减号([-])表示逆变类型.加号([+])表示协变类型.因此,从一种类型到另一种类型的函数表示为:
[-] -> [+]
Run Code Online (Sandbox Code Playgroud)
现在,函数的域和codomain可以分别用其他类型替换.例如,在currying中,函数的codomain是另一个函数:
[-] -> ([-] -> [+]) -- an example of currying
Run Code Online (Sandbox Code Playgroud)
请注意,当协变类型替换为其他类型时,将保留新类型的方差.这是有道理的,因为这相当于一个带有两个参数和一个返回类型的函数.
另一方面,如果我们用另一个函数替换域:
([+] -> [-]) -> [+]
Run Code Online (Sandbox Code Playgroud)
请注意,当我们用另一种类型替换逆变类型时,则会翻转新类型的方差.这是有道理的,因为虽然([+] -> [-])整体上是逆变的,但它的输入类型成为整个函数的输出,其输出类型成为整个函数的输入.例如:
function f(g) { // g is contravariant for f (an input value for f)
return g(x) + 10; // x is covariant for f (an output value for f)
// x is contravariant for g (an input value for g)
// g(x) is contravariant for f (an input value for f)
// g(x) is covariant for g (an output value for g)
// g(x) + 10 is covariant for f (an output value for f)
}
Run Code Online (Sandbox Code Playgroud)
Currying模拟多参数函数,因为当一个函数返回另一个函数时,我们得到多个输入和一个输出,因为返回类型保留了方差:
[-] -> [-] -> [+] -- a binary function
[-] -> [-] -> [-] -> [+] -- a ternary function
Run Code Online (Sandbox Code Playgroud)
具有多个字段作为函数域的数据结构也会模拟多参数函数,因为函数的参数类型会翻转方差:
([+], [+]) -- the fields of a tuple are covariant
([-], [-]) -> [+] -- a binary function, variance is flipped for arguments
Run Code Online (Sandbox Code Playgroud)
现在,如果您查看数字,字符串和布尔值等值,这些值不是函数.但是,它们仍然是协变的.
例如,5生成5自己的值.同样,Just 5产生一个值Just 5并fromJust (Just 5)产生一个值5.这些表达式都不会消耗一个值,因此它们都不是逆变的.但是,在Just 5函数中Just消耗值5并在fromJust (Just 5)函数中fromJust消耗该值Just 5.
所以Haskell中的所有东西都是协变的,除了函数的参数(它们是逆变的).这很重要,因为Haskell中的每个表达式都必须求值为一个值(即产生一个值,而不是消耗一个值).同时,我们希望函数消耗一个值并产生一个新值(从而促进数据转换,降低beta).
最终结果是我们永远不会有逆变表达.例如,表达式Just是协变的,表达式Just 5也是协变的.但是,在表达式中Just 5,函数Just会消耗该值5.因此,逆变仅限于函数参数,并受函数范围的限制.
因为Haskell中的每个表达式都是协变的,所以人们通常会将非函数值5视为"nullary函数".虽然这种直觉很有洞察力但却是错误的.该值5不是一个必然的函数.这是一种不能降低β的表达.同样,该值fromJust (Just 5)不是一个无效函数.它是一个可以被β减少的表达式5,这不是一个函数.
但是,表达式fromJust (Just (\x -> x + x))是一个函数,因为它可以被β减少为\x -> x + x函数.
现在,考虑一下这个功能\x -> x + x.这是一个有点函数,因为我们通过给它命名来明确声明函数的参数x.
每个函数也可以用无点样式编写(即没有明确声明函数的参数).例如,该功能\x -> x + x可以在pointfree风格写成join (+)如描述如下回答.
请注意,这join (+)是一个函数,因为它将beta缩减为函数\x -> x + x.它看起来不像函数,因为它没有点(即显式声明的参数).但是,它仍然是一个功能.
无点功能与currying无关.Pointfree函数是关于编写没有点的函数(例如join (+)代替\x -> x + x).Currying是当一个函数返回另一个函数时,从而允许部分应用(例如\x -> \y -> x + y,可以以无点样式写入(+)).
在绑定中,f = map我们只是给出map替代名称f.请注意,f不会"返回" map.它只是一个替代名称map.例如,在绑定中x = 5我们没有说x返回,5因为它没有.名称x不是函数也不是值.它只是一个标识价值的名称5.同样,在f = map名称中f只标识值map.f据说该名称表示函数,因为map表示函数.
绑定f = map是无点的,因为我们没有显式声明任何参数f.如果我们想要那么我们可以写f g xs = map g xs.这将是一个有意义的定义,但由于eta转换,我们可以更简洁地以无点形式编写它f = map.eta转换的概念与其自身\x -> f x相同,f并且有点\x -> f x可以转换为无点f,反之亦然.请注意,这f g xs = map g xs只是语法糖f = \g xs -> map g xs.
另一方面f = id . map,功能不是因为它是无点的,而是因为id . mapbeta减少了功能\x -> id (map x).顺便说一下,任何由它组成的函数id都等同于它自己(即id . f = f . id = f).因此,id . map等同于map它自己.f = map和之间没有区别f = id . map.
请记住,这f不是"返回"的功能id . map.id . map为方便起见,它只是表达式的名称.
PS对于无点函数的介绍,请阅读:
| 归档时间: |
|
| 查看次数: |
396 次 |
| 最近记录: |