无点功能如何实际"功能"?

Sho*_*hou 6 haskell functional-programming pointfree

Conal在这里认为,nullary构造的类型不是函数.然而,无点函数在维基百科上被描述为这样,当它们在它们的定义中没有明确的参数时,它看起来更像是一个currying的属性.它们究竟是如何运作的?

具体做法是:如何f = mapf = 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,对于一些xy;在这种情况下,我们正在使用a -> bx[a] -> [b]y).

并且定义map3是"部分无点"(点减少?); 该定义命名其第一个参数f,但未提及第二个参数.

所有这一点的要点是"无点自由"是一种定义的质量,而"作为一种功能"是价值的属性.无点函数的概念实际上没有意义,因为给定函数可以通过多种方式定义(其中一些是无点的,另一些则不是).每当你看到有人在谈论无点函数时,它们就意味着一个无点定义.

你似乎担心它map1 = map不是一个函数,因为它只是对现有值的绑定map,就像x = 2.你这里的想法很混乱.请记住,函数在Haskell中是一流的; "作为功能的东西"是"价值观的东西"的一个子集,而不是一类不同的东西!因此,当map一个现有值是一个函数时,yes map1 = map就是将新名称绑定到现有值.它定义了这个功能map1; 两者并不相互排斥.

你通过查看代码回答"这是免费的"这个问题; 一个函数的定义.您可以通过查看类型来回答"这是一个功能"的问题.

  • @dfeuer这不是我喜欢考虑数字的方式!或者输入类.从字典计算是一个实现问题,而不是`7 :: Num a => a`*表示*.但即使考虑到这一点,`7`也不符合"功能"的定义,即"具有功能类型的东西".你不应该将`7 :: Num a => a`应用于计算`7 :: Int`的东西,就像你将`not`应用于'False`来计算'True`一样; 你只需要在类型约束的位置引用它.我认为把它称为功能只是让水更混乱而不是澄清. (5认同)
  • `7`似乎是一个很糟糕的例子,因为它实际上是一个函数,类型为`Num a => a`(它需要一个类型和一个`Num`字典并产生一个值).但是,一旦应用了该函数,结果(比如`7 :: Int`)就不是函数. (2认同)

Aad*_*hah 5

与某些人可能认为Haskell中的所有内容都不相关的内容相反.认真.数字,字符串,布尔值等不是函数.甚至没有nullary函数.

Nullary Functions

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,IntBool分别.考虑:

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)

当然,函数实际上并没有如下定义,但我们假设它是.一个函数有两个字段domaincodomain.当我们创建该类型的值时,Function我们不知道这两个字段中的任何一个.

  1. 我们不知道它的价值,domain因为它是逆变的.因此,它需要由用户提供.
  2. 我们不知道它的价值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 5fromJust (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对于无点函数的介绍,请阅读:

什么(f.).在Haskell中意味着什么?