无点函数实际上如何 "functions"?
How are point-free functions actually "functions"?
Conal here argues that nullary-constructed types are not functions. However, point-free functions are described as such for example on Wikipedia,当他们在定义中没有明确的参数时,这似乎是 属性 的柯里化。它们的功能究竟如何?
具体来说:f = map
和 f = id . map
在此上下文中有何不同?如,f = map
只是绑定到一个恰好是函数的值,其中 f
只是 "returns" map
(类似于 f = 2
"returns" 2
) 然后接受参数。但是 f = id . map
被称为 函数 因为它是无点的。
Conal 的博客 post 归结为 "non-functions are not functions",例如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
这些都是 同一个函数 的定义(还有无数种方法可以定义与 map
函数等效的东西)。 map1
显然是一个无点定义; map4
显然不是。它们显然都有一个函数类型(同一个!),那么我们怎么能说无点定义不是函数呢?只有当我们将 "function" 的定义更改为不同于 Haskell 程序员通常所指的东西时(即函数是 x -> y
类型的东西,对于某些 x
和 y
;在这种情况下,我们将 a -> b
用作 x
,将 [a] -> [b]
用作 y
).
而map3
的定义是"partially point-free"(减分?);该定义将其第一个参数命名为 f
,但未提及第二个参数。
所有这一切的重点是 "point-free-ness" 是 定义的质量 ,而 "being a function" 是 [=51] 的 属性 =]值。无点 function 的概念实际上没有意义,因为给定的函数可以用多种方式定义(其中一些是无点的,另一些则不是)。每当你看到有人谈论无点函数时,他们指的是无点 definition.
您似乎担心 map1 = map
不是函数,因为它只是对现有值 map
的绑定,就像 x = 2
一样。你在这里混淆了概念。记住函数是第一个-class in Haskell; "things that are functions" 是 "things that are values" 的 子集 ,而不是不同的 class!因此,当 map
是一个现有值 ,它是一个函数 ,那么 map1 = map
只是将一个新名称绑定到一个现有值。 也是定义函数map1
;两者并不相互排斥。
你通过查看 code 来回答问题 "is this point-free";函数的定义。您通过查看 types.
来回答问题 "is this a function"
与某些人可能认为的相反,Haskell 中的所有内容都不是函数。严重地。数字、字符串、布尔值等不是函数。甚至没有零函数。
空函数
nullary 函数是一种不带参数并执行一些“副作用”计算的函数。例如,考虑这个 nullary JavaScript 函数:
main();
function main() {
alert("Hello World!");
alert("My name is Aadit M Shah.");
}
不带参数的函数只有 return 不同的结果(如果有副作用的话)。因此,它们类似于 Haskell 中的 IO 操作,不带参数并执行一些副作用计算:
main = do
putStrLn "Hello World!"
putStrLn "My name is Aadit M Shah."
一元函数
相比之下,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
协变和逆变
Haskell 中的函数是一种数据类型,就像您可以在 Haskell 中定义的任何其他数据类型一样。但是,函数是特殊的,因为它们是 contravariant in the argument type and covariant in the return type.
当您定义新的代数数据类型时,其类型构造函数的所有字段都是协变的(即数据源)而不是逆变的(即数据汇)。协变场产生数据,而逆变场消耗数据。
例如,假设我创建了一个新的数据类型:
data Foo = Bar { field1 :: Char, field2 :: Int }
| Baz { field3 :: Bool }
此处字段 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
现在,考虑函数的定义:
data Function a b = Function { domain :: a -- the argument type
, codomain :: b -- the return type
}
当然,函数实际上并没有按如下方式定义,但我们假设它是这样定义的。一个函数有两个字段 domain
和 codomain
。当我们创建类型为 Function
的值时,我们不知道这两个字段中的任何一个。
- 我们不知道
domain
的值,因为它是逆变的。因此,需要用户提供。
- 我们不知道
codomain
的值,因为虽然它是协变的,但它可能依赖于 domain
而我们不知道 domain
的值。
例如,\x -> x + x
是一个函数,其中domain
的值为x
,codomain
的值为x + x
。这里 domain
是逆变的(即数据汇),因为数据通过 domain
进入函数。同样,codomain
是协变的(即数据源),因为数据通过 codomain
.
从函数中出来
Haskell 中的代数数据结构的字段(就像我们之前定义的 Foo
一样)都是协变的,因为数据通过它们的字段从这些数据结构中产生。数据永远不会像 domain
函数字段那样进入这些结构。因此,它们永远不会是逆变的。
多参数函数
正如我之前解释的,虽然 Haskell 中的所有函数都是一元函数,但我们可以使用柯里化或具有多个数据结构的字段来模拟多参数函数。
为了理解这一点,我将使用一种新的表示法。减号 ([-]
) 表示逆变类型。加号 ([+]
) 表示协变类型。因此,从一种类型到另一种类型的函数表示为:
[-] -> [+]
现在,函数的域和辅域可以分别替换为其他类型。例如在currying中,函数的codomain是另一个函数:
[-] -> ([-] -> [+]) -- an example of currying
请注意,当协变类型被另一种类型替换时,新类型的方差将被保留。这是有道理的,因为这等效于具有两个参数和一个 return 类型的函数。
另一方面,如果我们用另一个函数替换域:
([+] -> [-]) -> [+]
请注意,当我们用另一种类型替换逆变类型时,新类型的方差会被翻转。这是有道理的,因为虽然 ([+] -> [-])
作为一个整体是逆变的,但它的输入类型变成了整个函数的输出,它的输出类型变成了整个函数的输入。例如:
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)
}
柯里化模拟多参数函数,因为当一个函数 return 是另一个函数时,我们得到多个输入和一个输出,因为 return 类型保留了方差:
[-] -> [-] -> [+] -- a binary function
[-] -> [-] -> [-] -> [+] -- a ternary function
具有多个字段作为函数域的数据结构也模拟多参数函数,因为函数的参数类型翻转了方差:
([+], [+]) -- the fields of a tuple are covariant
([-], [-]) -> [+] -- a binary function, variance is flipped for arguments
非函数
现在,如果您看一下数字、字符串和布尔值等值,这些值不是函数。但是,它们仍然是协变的。
例如,5
产生一个值 5
本身。同样,Just 5
生成值 Just 5
,fromJust (Just 5)
生成值 5
。 None 这些表达式消耗一个值,因此 none 是逆变的。但是,在 Just 5
中函数 Just
消耗值 5
并且在 fromJust (Just 5)
中函数 fromJust
消耗值 Just 5
.
所以 Haskell 中的所有内容都是协变的,除了函数的参数(它们是逆变的)。这很重要,因为 Haskell 中的每个表达式都必须计算出一个值(即产生一个值,而不是消耗一个值)。同时我们希望函数消费一个值并产生一个新值(从而方便数据的转换,beta reduction)。
最终结果是我们永远无法拥有逆变表达式。例如,表达式 Just
是协变的,表达式 Just 5
也是协变的。但是,在表达式 Just 5
中,函数 Just
使用值 5
。因此,逆变仅限于函数参数并受函数范围的限制。
因为 Haskell 中的每个表达式都是协变的,所以人们通常认为像 5
这样的非函数值是“空函数”。尽管这种直觉很有见地,但却是错误的。值 5
不是空函数。它是一个不能被减少的表达式。同样,值 fromJust (Just 5)
不是空函数。它是一个可以 beta 化简为 5
的表达式,它不是一个函数。
但是,表达式 fromJust (Just (\x -> x + x))
是一个函数,因为它可以 beta 化简为 \x -> x + x
这是一个函数。
Pointful 和 Pointfree 函数
现在,考虑函数 \x -> x + x
。这是一个有意义的函数,因为我们通过给函数命名 x
.
来显式声明函数的参数
每个函数也可以用 pointfree 风格编写(即不显式声明函数的参数)。例如,函数 \x -> x + x
可以用 pointfree 风格写成 join (+)
,如 .
中所述
请注意 join (+)
是一个函数,因为它 beta 归约为函数 \x -> x + x
。它看起来不像一个函数,因为它没有点(即显式声明的参数)。但是,它仍然是一个函数。
Pointfree 函数与柯里化无关。 Pointfree 函数是关于编写没有点的函数(例如 join (+)
而不是 \x -> x + x
)。柯里化是指一个函数 return 是另一个函数,从而允许部分应用(例如 \x -> \y -> x + y
可以用 pointfree 风格写成 (+)
)。
名称绑定
在绑定 f = map
中,我们只是给 map
替代名称 f
。请注意,f
不是“return” map
。它只是 map
的替代名称。例如,在绑定 x = 5
中我们不会说 x
returns 5
因为它没有。名称 x
不是函数也不是值。它只是一个标识 5
值的名称。同样,在 f = map
中,名称 f
只是标识 map
的值。据说名称 f
表示一个函数,因为 map
表示一个函数。
绑定 f = map
是无点的,因为我们没有显式声明 f
的任何参数。如果我们想那么我们可以写 f g xs = map g xs
。这将是一个有意义的定义,但由于 eta conversion 我们可以将其更简洁地写成无点形式 f = map
。 eta 转换的概念是 \x -> f x
等同于 f
本身,pointful \x -> f x
可以转换为 pointfree f
,反之亦然。请注意 f g xs = map g xs
只是 f = \g xs -> map g xs
.
的语法糖
另一方面,f = id . map
是一个函数,不是因为它是无点的,而是因为 id . map
beta 简化为函数 \x -> id (map x)
。顺便说一句,任何由 id
组成的函数都等同于它自己(即 id . f = f . id = f
)。因此,id . map
等同于 map
本身。 f = map
和 f = id . map
没有区别。
请记住 f
不是“returns” id . map
的函数。它只是为了方便起见给表达式 id . map
起的名字。
P.S。有关 pointfree 函数的介绍,请阅读:
What does (f .) . g mean in Haskell?
Conal here argues that nullary-constructed types are not functions. However, point-free functions are described as such for example on Wikipedia,当他们在定义中没有明确的参数时,这似乎是 属性 的柯里化。它们的功能究竟如何?
具体来说:f = map
和 f = id . map
在此上下文中有何不同?如,f = map
只是绑定到一个恰好是函数的值,其中 f
只是 "returns" map
(类似于 f = 2
"returns" 2
) 然后接受参数。但是 f = id . map
被称为 函数 因为它是无点的。
Conal 的博客 post 归结为 "non-functions are not functions",例如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
这些都是 同一个函数 的定义(还有无数种方法可以定义与 map
函数等效的东西)。 map1
显然是一个无点定义; map4
显然不是。它们显然都有一个函数类型(同一个!),那么我们怎么能说无点定义不是函数呢?只有当我们将 "function" 的定义更改为不同于 Haskell 程序员通常所指的东西时(即函数是 x -> y
类型的东西,对于某些 x
和 y
;在这种情况下,我们将 a -> b
用作 x
,将 [a] -> [b]
用作 y
).
而map3
的定义是"partially point-free"(减分?);该定义将其第一个参数命名为 f
,但未提及第二个参数。
所有这一切的重点是 "point-free-ness" 是 定义的质量 ,而 "being a function" 是 [=51] 的 属性 =]值。无点 function 的概念实际上没有意义,因为给定的函数可以用多种方式定义(其中一些是无点的,另一些则不是)。每当你看到有人谈论无点函数时,他们指的是无点 definition.
您似乎担心 map1 = map
不是函数,因为它只是对现有值 map
的绑定,就像 x = 2
一样。你在这里混淆了概念。记住函数是第一个-class in Haskell; "things that are functions" 是 "things that are values" 的 子集 ,而不是不同的 class!因此,当 map
是一个现有值 ,它是一个函数 ,那么 map1 = map
只是将一个新名称绑定到一个现有值。 也是定义函数map1
;两者并不相互排斥。
你通过查看 code 来回答问题 "is this point-free";函数的定义。您通过查看 types.
来回答问题 "is this a function"与某些人可能认为的相反,Haskell 中的所有内容都不是函数。严重地。数字、字符串、布尔值等不是函数。甚至没有零函数。
空函数
nullary 函数是一种不带参数并执行一些“副作用”计算的函数。例如,考虑这个 nullary JavaScript 函数:
main();
function main() {
alert("Hello World!");
alert("My name is Aadit M Shah.");
}
不带参数的函数只有 return 不同的结果(如果有副作用的话)。因此,它们类似于 Haskell 中的 IO 操作,不带参数并执行一些副作用计算:
main = do
putStrLn "Hello World!"
putStrLn "My name is Aadit M Shah."
一元函数
相比之下,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
协变和逆变
Haskell 中的函数是一种数据类型,就像您可以在 Haskell 中定义的任何其他数据类型一样。但是,函数是特殊的,因为它们是 contravariant in the argument type and covariant in the return type.
当您定义新的代数数据类型时,其类型构造函数的所有字段都是协变的(即数据源)而不是逆变的(即数据汇)。协变场产生数据,而逆变场消耗数据。
例如,假设我创建了一个新的数据类型:
data Foo = Bar { field1 :: Char, field2 :: Int }
| Baz { field3 :: Bool }
此处字段 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
现在,考虑函数的定义:
data Function a b = Function { domain :: a -- the argument type
, codomain :: b -- the return type
}
当然,函数实际上并没有按如下方式定义,但我们假设它是这样定义的。一个函数有两个字段 domain
和 codomain
。当我们创建类型为 Function
的值时,我们不知道这两个字段中的任何一个。
- 我们不知道
domain
的值,因为它是逆变的。因此,需要用户提供。 - 我们不知道
codomain
的值,因为虽然它是协变的,但它可能依赖于domain
而我们不知道domain
的值。
例如,\x -> x + x
是一个函数,其中domain
的值为x
,codomain
的值为x + x
。这里 domain
是逆变的(即数据汇),因为数据通过 domain
进入函数。同样,codomain
是协变的(即数据源),因为数据通过 codomain
.
Haskell 中的代数数据结构的字段(就像我们之前定义的 Foo
一样)都是协变的,因为数据通过它们的字段从这些数据结构中产生。数据永远不会像 domain
函数字段那样进入这些结构。因此,它们永远不会是逆变的。
多参数函数
正如我之前解释的,虽然 Haskell 中的所有函数都是一元函数,但我们可以使用柯里化或具有多个数据结构的字段来模拟多参数函数。
为了理解这一点,我将使用一种新的表示法。减号 ([-]
) 表示逆变类型。加号 ([+]
) 表示协变类型。因此,从一种类型到另一种类型的函数表示为:
[-] -> [+]
现在,函数的域和辅域可以分别替换为其他类型。例如在currying中,函数的codomain是另一个函数:
[-] -> ([-] -> [+]) -- an example of currying
请注意,当协变类型被另一种类型替换时,新类型的方差将被保留。这是有道理的,因为这等效于具有两个参数和一个 return 类型的函数。
另一方面,如果我们用另一个函数替换域:
([+] -> [-]) -> [+]
请注意,当我们用另一种类型替换逆变类型时,新类型的方差会被翻转。这是有道理的,因为虽然 ([+] -> [-])
作为一个整体是逆变的,但它的输入类型变成了整个函数的输出,它的输出类型变成了整个函数的输入。例如:
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)
}
柯里化模拟多参数函数,因为当一个函数 return 是另一个函数时,我们得到多个输入和一个输出,因为 return 类型保留了方差:
[-] -> [-] -> [+] -- a binary function
[-] -> [-] -> [-] -> [+] -- a ternary function
具有多个字段作为函数域的数据结构也模拟多参数函数,因为函数的参数类型翻转了方差:
([+], [+]) -- the fields of a tuple are covariant
([-], [-]) -> [+] -- a binary function, variance is flipped for arguments
非函数
现在,如果您看一下数字、字符串和布尔值等值,这些值不是函数。但是,它们仍然是协变的。
例如,5
产生一个值 5
本身。同样,Just 5
生成值 Just 5
,fromJust (Just 5)
生成值 5
。 None 这些表达式消耗一个值,因此 none 是逆变的。但是,在 Just 5
中函数 Just
消耗值 5
并且在 fromJust (Just 5)
中函数 fromJust
消耗值 Just 5
.
所以 Haskell 中的所有内容都是协变的,除了函数的参数(它们是逆变的)。这很重要,因为 Haskell 中的每个表达式都必须计算出一个值(即产生一个值,而不是消耗一个值)。同时我们希望函数消费一个值并产生一个新值(从而方便数据的转换,beta reduction)。
最终结果是我们永远无法拥有逆变表达式。例如,表达式 Just
是协变的,表达式 Just 5
也是协变的。但是,在表达式 Just 5
中,函数 Just
使用值 5
。因此,逆变仅限于函数参数并受函数范围的限制。
因为 Haskell 中的每个表达式都是协变的,所以人们通常认为像 5
这样的非函数值是“空函数”。尽管这种直觉很有见地,但却是错误的。值 5
不是空函数。它是一个不能被减少的表达式。同样,值 fromJust (Just 5)
不是空函数。它是一个可以 beta 化简为 5
的表达式,它不是一个函数。
但是,表达式 fromJust (Just (\x -> x + x))
是一个函数,因为它可以 beta 化简为 \x -> x + x
这是一个函数。
Pointful 和 Pointfree 函数
现在,考虑函数 \x -> x + x
。这是一个有意义的函数,因为我们通过给函数命名 x
.
每个函数也可以用 pointfree 风格编写(即不显式声明函数的参数)。例如,函数 \x -> x + x
可以用 pointfree 风格写成 join (+)
,如
请注意 join (+)
是一个函数,因为它 beta 归约为函数 \x -> x + x
。它看起来不像一个函数,因为它没有点(即显式声明的参数)。但是,它仍然是一个函数。
Pointfree 函数与柯里化无关。 Pointfree 函数是关于编写没有点的函数(例如 join (+)
而不是 \x -> x + x
)。柯里化是指一个函数 return 是另一个函数,从而允许部分应用(例如 \x -> \y -> x + y
可以用 pointfree 风格写成 (+)
)。
名称绑定
在绑定 f = map
中,我们只是给 map
替代名称 f
。请注意,f
不是“return” map
。它只是 map
的替代名称。例如,在绑定 x = 5
中我们不会说 x
returns 5
因为它没有。名称 x
不是函数也不是值。它只是一个标识 5
值的名称。同样,在 f = map
中,名称 f
只是标识 map
的值。据说名称 f
表示一个函数,因为 map
表示一个函数。
绑定 f = map
是无点的,因为我们没有显式声明 f
的任何参数。如果我们想那么我们可以写 f g xs = map g xs
。这将是一个有意义的定义,但由于 eta conversion 我们可以将其更简洁地写成无点形式 f = map
。 eta 转换的概念是 \x -> f x
等同于 f
本身,pointful \x -> f x
可以转换为 pointfree f
,反之亦然。请注意 f g xs = map g xs
只是 f = \g xs -> map g xs
.
另一方面,f = id . map
是一个函数,不是因为它是无点的,而是因为 id . map
beta 简化为函数 \x -> id (map x)
。顺便说一句,任何由 id
组成的函数都等同于它自己(即 id . f = f . id = f
)。因此,id . map
等同于 map
本身。 f = map
和 f = id . map
没有区别。
请记住 f
不是“returns” id . map
的函数。它只是为了方便起见给表达式 id . map
起的名字。
P.S。有关 pointfree 函数的介绍,请阅读:
What does (f .) . g mean in Haskell?