如何理解 Control.Arrow 的“&&&”运算符的 Haskell 类型签名
How to make sense of the Haskell type signature for Control.Arrow's '&&&' operator
我正在努力思考 Haskell 的 Control.Arrow
的 &&&
是如何工作的,但担心我正处于迷路的边缘。
具体来说,我(作为初学者)对如何从类型签名中理解它的行为感到困惑
(&&&) :: a b c -> a b c' -> a b (c, c')
在
import Control.Arrow
(negate &&& (+5)) <$> [1,2,3]
甚至只是
(negate &&& (+5)) 5
例如,第一个参数是 "missing" b
和 c
,而第二个参数只缺少 c'
,结果在我看来是 (c, c')
,不是 a b (c, c')
.
谁能告诉我 &&&
在其类型的上下文中是如何工作的?
我一直认为 &&&
是拆分和应用操作。您有一个箭头值,您将对其应用两个函数(抱歉,箭头,但它与函数一起使用并使解释更容易),并保留两个结果,从而拆分流。
简单的例子:
λ> (succ &&& pred) 42
(43,41)
在那里走类型,我们有
succ &&& pred :: Arrow a, Enum b => a b (b,b)
一个更复杂的例子,其中不全是 b:
show &&& (== 42) :: Arrow a, Show b, Eq b, Num b => a b (String,Bool)
所以用简单的英语来说:&&&
接受两个函数,并将它们组合成一个函数,该函数接受输入,将两个函数应用于它,returns 结果对。
但它是在箭头上定义的,而不是函数。然而它的工作原理完全相同:它需要两个箭头,并将它们组合成一个接受输入的箭头,将两个箭头应用于它,然后 returns 结果对。
arrowOne :: Arrow a => a b c
arrowTwo :: Arrow a => a b c'
arrowOne &&& arrowTwo :: Arrow a => a b (c,c')
附录:部分让您感到困惑的是 a
类型是否仍然出现在类型签名中。这里的经验法则是,它的工作原理与您在函数类型中看到 ->
时的效果相同:只要未应用它就会显示。
我记得读过一些 Arrow 文献,将箭头写成 b ~> c
(注意波浪号而不是破折号)而不是 a b c
,以使与函数的平行更明显。
签名说,
(&&&) :: Arrow a => a b c -> a b c' -> a b (c, c')
和 (->)
is an instance of Arrow
type class. 所以,重写专门用于 (->)
类型的签名:
(&&&) :: ((->) b c) -> ((->) b c') -> ((->) b (c, c'))
在中缀形式中,它看起来像:
(b -> c) -> (b -> c') -> (b -> (c, c'))
这就是
(&&&) :: (b -> c) -- given a function from type `b` to type `c`
-> (b -> c') -- and another function from type `b` to type `c'`
-> (b -> (c, c')) -- returns a function which combines the result of
-- first and second function into a tuple
一个简单的复制是:
(&:&) :: ((->) b c) -> ((->) b c') -> ((->) b (c, c'))
f &:& g = \x -> (f x, g x)
这将以相同的方式工作:
\> (negate &:& (+5)) <$> [1, 2, 3]
[(-1,6),(-2,7),(-3,8)]
跳过一些关于箭头工作原理的解释并查看 (negate &&& (+5))
:
的类型可能更容易
> :t (negate &&& (+5))
(negate &&& (+5)) :: Num a => a -> (a, a)
对于negate :: a -> a
和(+5) :: a -> a
,&&&
创建的函数取一个a
类型的值,return是一对值,都是也有类型a
。
当将函数视为 Arrow
的实例时,b
和 c
只是分别为参数和 return 类型指定的名称。也就是说,如果 f :: b -> c
和 g :: b -> c'
是两个接受相同类型参数但 return 可能不同类型值的函数,则 ( f &&& g ) :: b -> (c, c')
,这意味着新函数接受公共输入类型的单个值,然后 returns 由每个原始函数的 return 值组成的一对,或 ( f &&& g ) x = (f x, g x)
.
你有很多很好的答案;我只是想通过只看语法.
来补充如何理解这个东西
Haskell 类型语言的规则与 Haskell 的普通语言相同:f a b = (f a) b
是某种类型函数 f
应用于某种类型-argument a
生成另一个类型函数 f a
,它被应用于某些类型参数 b
。然后是一个运算符(一些编译器标志允许您启用其他运算符)->
表示期望第一种类型作为输入的函数类型,return 第二种类型作为输出。
所以表达式 (&&&) :: (Arrow a) => a b c -> a b c' -> a b (c, c')
的意思是“从 a b c
到 a b c'
到 a b (c, c')
的函数类型,加上 a
是Arrow 类型类的成员。
有很多不同种类的箭头;这表示 "I take two arrows of the same sort, one from b
to c
and another from b
to c'
, and combine them together into an arrow of that same sort, from b
to (c, c')
."
直觉上,我们会说它由两个箭头 "in parallel" 组成,因此它们都作用于相同的输入,产生不同的输出,然后我们 "join" 这些输出与我们的非齐次-pair数据结构(,)
.
对于箭头为(->)
的特殊情况,函数箭头,这显然是(f &&& g) = \b -> (f b, g b)
。这个运算符 (&&&)
是 Arrow
类型类定义的一部分,所以如果有某种方法可以执行类似的 "parallel operation" 两个箭头。
我正在努力思考 Haskell 的 Control.Arrow
的 &&&
是如何工作的,但担心我正处于迷路的边缘。
具体来说,我(作为初学者)对如何从类型签名中理解它的行为感到困惑
(&&&) :: a b c -> a b c' -> a b (c, c')
在
import Control.Arrow
(negate &&& (+5)) <$> [1,2,3]
甚至只是
(negate &&& (+5)) 5
例如,第一个参数是 "missing" b
和 c
,而第二个参数只缺少 c'
,结果在我看来是 (c, c')
,不是 a b (c, c')
.
谁能告诉我 &&&
在其类型的上下文中是如何工作的?
我一直认为 &&&
是拆分和应用操作。您有一个箭头值,您将对其应用两个函数(抱歉,箭头,但它与函数一起使用并使解释更容易),并保留两个结果,从而拆分流。
简单的例子:
λ> (succ &&& pred) 42
(43,41)
在那里走类型,我们有
succ &&& pred :: Arrow a, Enum b => a b (b,b)
一个更复杂的例子,其中不全是 b:
show &&& (== 42) :: Arrow a, Show b, Eq b, Num b => a b (String,Bool)
所以用简单的英语来说:&&&
接受两个函数,并将它们组合成一个函数,该函数接受输入,将两个函数应用于它,returns 结果对。
但它是在箭头上定义的,而不是函数。然而它的工作原理完全相同:它需要两个箭头,并将它们组合成一个接受输入的箭头,将两个箭头应用于它,然后 returns 结果对。
arrowOne :: Arrow a => a b c
arrowTwo :: Arrow a => a b c'
arrowOne &&& arrowTwo :: Arrow a => a b (c,c')
附录:部分让您感到困惑的是 a
类型是否仍然出现在类型签名中。这里的经验法则是,它的工作原理与您在函数类型中看到 ->
时的效果相同:只要未应用它就会显示。
我记得读过一些 Arrow 文献,将箭头写成 b ~> c
(注意波浪号而不是破折号)而不是 a b c
,以使与函数的平行更明显。
签名说,
(&&&) :: Arrow a => a b c -> a b c' -> a b (c, c')
和 (->)
is an instance of Arrow
type class. 所以,重写专门用于 (->)
类型的签名:
(&&&) :: ((->) b c) -> ((->) b c') -> ((->) b (c, c'))
在中缀形式中,它看起来像:
(b -> c) -> (b -> c') -> (b -> (c, c'))
这就是
(&&&) :: (b -> c) -- given a function from type `b` to type `c`
-> (b -> c') -- and another function from type `b` to type `c'`
-> (b -> (c, c')) -- returns a function which combines the result of
-- first and second function into a tuple
一个简单的复制是:
(&:&) :: ((->) b c) -> ((->) b c') -> ((->) b (c, c'))
f &:& g = \x -> (f x, g x)
这将以相同的方式工作:
\> (negate &:& (+5)) <$> [1, 2, 3]
[(-1,6),(-2,7),(-3,8)]
跳过一些关于箭头工作原理的解释并查看 (negate &&& (+5))
:
> :t (negate &&& (+5))
(negate &&& (+5)) :: Num a => a -> (a, a)
对于negate :: a -> a
和(+5) :: a -> a
,&&&
创建的函数取一个a
类型的值,return是一对值,都是也有类型a
。
当将函数视为 Arrow
的实例时,b
和 c
只是分别为参数和 return 类型指定的名称。也就是说,如果 f :: b -> c
和 g :: b -> c'
是两个接受相同类型参数但 return 可能不同类型值的函数,则 ( f &&& g ) :: b -> (c, c')
,这意味着新函数接受公共输入类型的单个值,然后 returns 由每个原始函数的 return 值组成的一对,或 ( f &&& g ) x = (f x, g x)
.
你有很多很好的答案;我只是想通过只看语法.
来补充如何理解这个东西Haskell 类型语言的规则与 Haskell 的普通语言相同:f a b = (f a) b
是某种类型函数 f
应用于某种类型-argument a
生成另一个类型函数 f a
,它被应用于某些类型参数 b
。然后是一个运算符(一些编译器标志允许您启用其他运算符)->
表示期望第一种类型作为输入的函数类型,return 第二种类型作为输出。
所以表达式 (&&&) :: (Arrow a) => a b c -> a b c' -> a b (c, c')
的意思是“从 a b c
到 a b c'
到 a b (c, c')
的函数类型,加上 a
是Arrow 类型类的成员。
有很多不同种类的箭头;这表示 "I take two arrows of the same sort, one from b
to c
and another from b
to c'
, and combine them together into an arrow of that same sort, from b
to (c, c')
."
直觉上,我们会说它由两个箭头 "in parallel" 组成,因此它们都作用于相同的输入,产生不同的输出,然后我们 "join" 这些输出与我们的非齐次-pair数据结构(,)
.
对于箭头为(->)
的特殊情况,函数箭头,这显然是(f &&& g) = \b -> (f b, g b)
。这个运算符 (&&&)
是 Arrow
类型类定义的一部分,所以如果有某种方法可以执行类似的 "parallel operation" 两个箭头。