[英]What are applicative effects?
有效的应用程序编程中效果的概念是什么意思?
例如,下面表达式的哪些部分是效果?
[(+1)] <*> [2,3]
Just (+1) <*> Nothing
在 FP 世界中,效果是任何类型的构造函数,例如Maybe
、 []
、 IO
等。不要将效果与副作用混淆。 直观地说,效果是您正在计算的值的附加属性。 Maybe Int
意味着您的程序计算了一个具有失败影响的Int
,或者[Int]
意味着您的程序计算了一个Int
但具有非确定性影响(非确定性结果在这里建模为可能结果的列表)。
从这里开始,我们有术语applicative effects和monadic effects ,这意味着所述效果具有Applicative
和Monad
实例。
我找不到任何权威信息,这只是我在我的经验中收集到的。
不幸的名字选择引起了很多混乱,这在 Haskell 中很常见(想想“ return
”,更好地命名为“ emit
”)。
pure x
不纯, pure
x
是纯的, pure
只是注入。 它被设想用于pure f <*> a <*> b <*> ...
模式,让我们有效地应用纯函数f
。 (1)
[]
应用性(2)让我们“非确定性”应用( <*>
而不是$
)非确定性值(而不是两个值,在你的例子),以非确定性函数; 非确定性就是效果。 (3)
在列表应用中, [(+1), (+2)]
是一个非确定性函数,它可能将一个值增加 1,也可能将它增加 2。 [3,4,5]
是一个非确定性值列出了其可能的值。 正如我们应用正常的实体(+1)
和3
通常,如(+1) $ 3
,所以可以我们应用非确定性值的非确定性,如[(+1)] <*> [3]
或[(+1),(+2)] <*> [3,4,5]
。
与Maybe
失败的可能性是效果。
(1)正如论文在介绍中所说: “我们收集一些有效计算的值,然后将其用作纯函数的参数(:)
”
(2) []
本身不是一个应用, ([], pure :: a -> [a], (<*>) :: [a -> b] -> [a] -> [b])
考虑到pure
和(<*>)
一些(合法)实现,是一种应用。
(3) x
是纯的(如“Haskell 是纯的”); pure x
代表有效计算产生x
而实际上没有任何附加效果。 “没有影响”指的是pure x *> u == u
定律,即pure x
不会在u
贡献之上的组合计算中添加任何影响。 但效果的可能性是存在的。
pure 7 :: IO Int
当然不是纯(如“Haskell 是纯的”)值7
,它是有效上下文( IO
)中的纯值7
。 即使它在那个上下文中没有采取有效的动作,它仍然在那个上下文中( IO
)。
另一方面,与pure
的目的无关,当然任何Haskell 值都是纯的并且引用透明。 getLine
是一个纯粹的、引用透明的 Haskell值。 它代表有效的 I/O计算,从用户那里获取输入行并将其作为结果供下一个 I/O 计算使用。
print 7
是一个纯粹的引用透明 Haskell 值。 这不是这里所说的一种“纯”。 [1,2]
是一个纯值,但从另一个角度来看,它是一个不确定值,具有两个可能的纯值1
和2
。 [1]
。 它仍然可以解释为具有一个可能的纯值1
的不确定值。
[1,2] *> [10,20]
= [10,20,10,20]
; [1] *> [10,20]
= [10,20]
。 因此,与[1,2]
不同, [1]
不会在[10,20]
描述的非确定性计算中添加任何不确定性。 但它仍然是一个不确定的值,它可以参与*>
。 1
不能。 ( [1]
当然与pure 1
相同)。
我们通过它可以参与的交互类型来了解类型。
或者,正如用户@bob所说的( 在评论中),“ pure x
将纯x
置于有效上下文中,而不实际执行 [任何] 效果”。
也可以看看:
我们可以说fa
类型的效果是任何不能写成pure x
其中x :: a
。
在[]
应用中, pure x = [x]
,因此[(+1)] = pure (+1)
可能不应被视为效果。 同样,在Maybe
应用中, pure = Just
,所以Just (+1)
不是效果。
这将[2,3]
和Nothing
作为您各自示例中的效果。 从[]
表示非确定性计算的角度来看,这具有直观意义: [2,3]
在 2 和 3 之间非确定性地选择; 以及Maybe
表示计算失败的观点: Nothing
失败计算。
我使用的定义是效果(也许“副作用”会是一个更好的词)不能写成pure x
只是使您的问题变得精确的一种摆动,并不代表任何形式的共识或标准定义。 Will Ness 的回答给出了一个不同的观点, pure
从一个纯值生成一个有效的计算,它有一个很好的数学环 - 即这个定义可能更容易在精确设置中使用。
有效的应用程序编程可以被认为是进行常规的无效计算并为其添加效果。 这些被实现为Applicative
实例。 因此,虽然Int
是常规值,但A Int
是具有某种效果的Int
A
,而A
是Applicative
的实例。
考虑这个表达式:
x + y :: Int
这个表达是无效的; 可以这么说,它只处理常规的、简单的值。 但是我们也可以有有效的加法。
一种影响是失败; 计算可能失败或成功。 如果失败,则停止计算。 这只是Maybe
类型。
Just (+1) <*> Nothing :: Maybe Int
除了常规值之外,您只需将数字相加即可。 但是现在,我们有可能会失败的加法。 所以我们必须把这些数字加在一起,前提是计算没有失败。 我们在这个表达式中看到计算将失败,因为第二个操作数是Nothing
。
如果您的计算失败的原因不止一个,您可能需要错误消息来报告所发生的失败类型。 然后你可以使用一个错误效果,它可能表示为类似Either String
( String
是错误消息的类型)。 此Applicative
的实现与Maybe
Applicative
行为类似。
效果的另一个例子是解析。 只需使用构造函数并使其有效就可以实现解析。 假设您想实现一种带有加法和乘法的简单算术语言。 这可能是您的抽象语法树 (AST):
data Exp = Num Int | Var String
data AST = Add Exp Exp | Multiply Exp Exp
您只需使用这些构造函数即可构建 AST。 但问题是你还需要真正解析文本,那么解析的行为呢? 如何跟踪您已阅读了多少文本? 如果解析失败,因为文本不符合您的语法怎么办? 好吧,在像Parsec
这样的库中,这就是解析效果。 您使用一些Parse
数据类型(即Applicative
的实例)并将构造函数提升到有效的Parse AST
世界中。 现在,您可以在实际解析文本的同时构建 AST,因为解析是添加到 AST 构建中的效果。
请注意, Parse
类型比Maybe
和Either String
实例都复杂; 解析器具有跟踪状态的作用,例如已经消耗了多少输入文本,以及解析失败,这会产生错误消息。 Applicative
效果可以像这样组合在一起。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.