"chain" 将加法运算符添加到接受 3 个参数的函数中?
"chain" the addition operator into a function that accepts 3 arguments?
我想编写加法运算符 (+)
来创建这种类型的函数:
Num a => a -> a -> a -> a
就像,相当于:
(\a b c -> a + b + c)
但不必求助于 lambda。
我已经试过了
((+) . (+))
我本以为可以工作,但令人惊讶的是没有。
虽然这引入了一些噪音,但您可以使用 uncurry :: (a -> b -> c) -> (a,b) -> c
和 curry :: ((a,b) -> c) -> a -> b -> c
将第二个加号的参数临时存储在一个元组中:
curry $ (+) . uncurry (+) :: Num a => a -> a -> a -> a
或者可能更具语义可读性:
curry ((+) . uncurry (+)) :: Num a => a -> a -> a -> a
uncurry
因此接受一个函数(这里是 (+)
)并将其转换为一个函数:uncurry (+) :: Num a => (a,a) -> a
。因此,您已将 (+)
转换为一个 函数,该函数接受一个元组 .
现在我们可以用(.)
与第一个(+)
:
进行合成
(+) . uncurry (+) :: Num a => (a,a) -> (a -> a)
所以现在我们有一个函数接受一个参数(元组 (a,a)
)并生成一个函数接受 a
(第一个 (+)
的第二个操作数)和计算总和。问题当然是我们想摆脱元组。我们可以通过将函数传递给 curry
来实现。这将 tuple-function ((a,a) -> (a -> a)
) 转换为单独接受参数的函数 (a -> (a -> (a -> a))
)。
http://pointfree.io 给出 \a b c -> a + b + c
的 point-free 版本为 ((+) .) . (+)
.
非正式地,组合仅适用于 "intuitively" first-order 函数,它既不将函数作为参数也不将 return 函数作为值。 (+)
是一个 higher-order 函数;它采用 Num a => a
类型的值,并且 return 是 Num a => a -> a
类型的函数。当您尝试以天真的方式组合 higher-order 函数时,结果不是您所期望的:
:t (+) . (+)
(+) . (+) :: (Num a, Num (a -> a)) => a -> (a -> a) -> a -> a
考虑两个函数的定义:
(+) :: Num z => z -> z -> z
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \x -> f (g x)
然后
(+) . (+) == (.) (+) (+)
== \x -> (+) ((+) x)
由于柯里化,您最终传递了一个函数,而不是数字,作为第一个 (+)
的第一个参数。
那么我们如何从 h a b c = a + b + c
到 h = ((+) .) . (+)
?首先将中缀表达式重写为前缀表达式,利用 (+)
是 left-associative.
的事实
\a b c -> a + b + c
== \a b c -> ((+) a b ) + c
== \a b c -> (+) ((+) a b) c
接下来,我们交替应用 eta 转换来消除参数和组合以将参数移动到要消除的位置。我试图非常明确地确定用于合成应用程序的函数。
== \a b -> (+) ((+) a b) -- eta conversion to eliminate c
== \a b -> (+) (((+) a) b) -- parentheses justified by currying
-- f g -- f = (+), g = ((+) a)
-- \a b -> f ( g b)
-- \a b -> (f . g) b -- definition of (.)
== \a b -> ((+) . ((+) a)) b
== \a -> (+) . ((+) a) -- eta conversion to eliminate b
== \a -> (.) (+) ((+) a) -- prefix notation
== \a -> ((.) (+)) ((+) a) -- parentheses justified by currying
== \a -> ((+) . )((+) a) -- back to a section of (.)
-- f g -- f = ((+) .), g = (+)
-- \a -> f (g a)
-- \a -> ( f . g) a -- definition of (.)
== \a -> (((+) .) . (+)) a
== ((+) .) . (+) -- eta conversion to eliminate a
注意函数组合运算符的签名:
(.) :: (b -> c) -> (a -> b) -> a -> c
^ ^ ^
Functions
它接受 2 个函数,每个函数接受 1 个参数,returns 一个函数接受与第二个函数相同类型的参数,returns 与第一个函数相同类型。
您尝试组合两个 +
的尝试没有成功,因为 +
需要 2 个参数,因此如果没有一些 hackish/creative 解决方法,这是不可能的。
在这一点上,我想说当它不适合问题时强行组合只会让你的生活更加困难。
如果你想对多个数字求和,你可以写一个这样的函数:
sum :: [Int] -> Int
sum nums = foldl (+) 0 nums
或者,由于 nums
出现在定义的后面,它可以完全删除,产生 "point-free" 形式:
sum :: [Int] -> Int
sum = foldl (+) 0
它 reduces/folds +
遍历了一个数字列表。如果您还没有使用过折叠,现在就研究一下。它们是Haskell中实现循环的主要方式之一。当您处理列表或任何其他可迭代对象时,它本质上是 "implicit recursion" 。
定义了上述函数后,您可以像这样使用它:
sum [1, 2 3, 4, 5]
您需要这个奇怪的运算符 (.).(.)
,它有时被定义为 .:
(想想 3 个点...)
在 ghci
Prelude> let (.:) = (.).(.)
Prelude> let f = (+) .: (+)
Prelude> f 1 2 3
> 6
请注意,此运算符也可以定义为 <$$> = fmap . fmap
。
我想编写加法运算符 (+)
来创建这种类型的函数:
Num a => a -> a -> a -> a
就像,相当于:
(\a b c -> a + b + c)
但不必求助于 lambda。
我已经试过了
((+) . (+))
我本以为可以工作,但令人惊讶的是没有。
虽然这引入了一些噪音,但您可以使用 uncurry :: (a -> b -> c) -> (a,b) -> c
和 curry :: ((a,b) -> c) -> a -> b -> c
将第二个加号的参数临时存储在一个元组中:
curry $ (+) . uncurry (+) :: Num a => a -> a -> a -> a
或者可能更具语义可读性:
curry ((+) . uncurry (+)) :: Num a => a -> a -> a -> a
uncurry
因此接受一个函数(这里是 (+)
)并将其转换为一个函数:uncurry (+) :: Num a => (a,a) -> a
。因此,您已将 (+)
转换为一个 函数,该函数接受一个元组 .
现在我们可以用(.)
与第一个(+)
:
(+) . uncurry (+) :: Num a => (a,a) -> (a -> a)
所以现在我们有一个函数接受一个参数(元组 (a,a)
)并生成一个函数接受 a
(第一个 (+)
的第二个操作数)和计算总和。问题当然是我们想摆脱元组。我们可以通过将函数传递给 curry
来实现。这将 tuple-function ((a,a) -> (a -> a)
) 转换为单独接受参数的函数 (a -> (a -> (a -> a))
)。
http://pointfree.io 给出 \a b c -> a + b + c
的 point-free 版本为 ((+) .) . (+)
.
非正式地,组合仅适用于 "intuitively" first-order 函数,它既不将函数作为参数也不将 return 函数作为值。 (+)
是一个 higher-order 函数;它采用 Num a => a
类型的值,并且 return 是 Num a => a -> a
类型的函数。当您尝试以天真的方式组合 higher-order 函数时,结果不是您所期望的:
:t (+) . (+)
(+) . (+) :: (Num a, Num (a -> a)) => a -> (a -> a) -> a -> a
考虑两个函数的定义:
(+) :: Num z => z -> z -> z
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \x -> f (g x)
然后
(+) . (+) == (.) (+) (+)
== \x -> (+) ((+) x)
由于柯里化,您最终传递了一个函数,而不是数字,作为第一个 (+)
的第一个参数。
那么我们如何从 h a b c = a + b + c
到 h = ((+) .) . (+)
?首先将中缀表达式重写为前缀表达式,利用 (+)
是 left-associative.
\a b c -> a + b + c
== \a b c -> ((+) a b ) + c
== \a b c -> (+) ((+) a b) c
接下来,我们交替应用 eta 转换来消除参数和组合以将参数移动到要消除的位置。我试图非常明确地确定用于合成应用程序的函数。
== \a b -> (+) ((+) a b) -- eta conversion to eliminate c
== \a b -> (+) (((+) a) b) -- parentheses justified by currying
-- f g -- f = (+), g = ((+) a)
-- \a b -> f ( g b)
-- \a b -> (f . g) b -- definition of (.)
== \a b -> ((+) . ((+) a)) b
== \a -> (+) . ((+) a) -- eta conversion to eliminate b
== \a -> (.) (+) ((+) a) -- prefix notation
== \a -> ((.) (+)) ((+) a) -- parentheses justified by currying
== \a -> ((+) . )((+) a) -- back to a section of (.)
-- f g -- f = ((+) .), g = (+)
-- \a -> f (g a)
-- \a -> ( f . g) a -- definition of (.)
== \a -> (((+) .) . (+)) a
== ((+) .) . (+) -- eta conversion to eliminate a
注意函数组合运算符的签名:
(.) :: (b -> c) -> (a -> b) -> a -> c
^ ^ ^
Functions
它接受 2 个函数,每个函数接受 1 个参数,returns 一个函数接受与第二个函数相同类型的参数,returns 与第一个函数相同类型。
您尝试组合两个 +
的尝试没有成功,因为 +
需要 2 个参数,因此如果没有一些 hackish/creative 解决方法,这是不可能的。
在这一点上,我想说当它不适合问题时强行组合只会让你的生活更加困难。
如果你想对多个数字求和,你可以写一个这样的函数:
sum :: [Int] -> Int
sum nums = foldl (+) 0 nums
或者,由于 nums
出现在定义的后面,它可以完全删除,产生 "point-free" 形式:
sum :: [Int] -> Int
sum = foldl (+) 0
它 reduces/folds +
遍历了一个数字列表。如果您还没有使用过折叠,现在就研究一下。它们是Haskell中实现循环的主要方式之一。当您处理列表或任何其他可迭代对象时,它本质上是 "implicit recursion" 。
定义了上述函数后,您可以像这样使用它:
sum [1, 2 3, 4, 5]
您需要这个奇怪的运算符 (.).(.)
,它有时被定义为 .:
(想想 3 个点...)
在 ghci
Prelude> let (.:) = (.).(.)
Prelude> let f = (+) .: (+)
Prelude> f 1 2 3
> 6
请注意,此运算符也可以定义为 <$$> = fmap . fmap
。