思考它的一个好方法是“我是否更改了任何以后的代码(包括稍后再次运行相同的函数)可能看到的任何东西,而不是我返回的值?” 如果是这样,那就是副作用。如果没有,那么您可以知道没有。
所以,像:
let inc_nosf v = v+1
没有副作用,因为它只返回一个比整数 v 大一的新值。因此,如果您在 ocaml 顶层运行以下代码,您会得到相应的结果:
# let x = 5;;
val x : int = 5
# inc_nosf x;;
- : int = 6
# x;;
- : int = 5
如您所见,x 的值没有改变。因此,由于我们没有保存返回值,所以没有真正增加任何内容。我们的函数本身只修改返回值,而不是 x 本身。所以要将它保存到 x 中,我们必须这样做:
# let x = inc_nosf x;;
val x : int = 6
# x;;
- : int = 6
由于 inc_nosf 函数没有副作用(也就是说,它只使用其返回值与外界通信,而不是通过进行任何其他更改)。
但是像:
let inc_sf r = r := !r+1
有副作用,因为它改变了存储在由 r 表示的引用中的值。所以如果你在顶层运行类似的代码,你会得到这个,而不是:
# let y = ref 5;;
val y : int ref = {contents = 5}
# inc_sf y;;
- : unit = ()
# y;;
- : int ref = {contents = 6}
因此,在这种情况下,即使我们仍然不保存返回值,它还是会递增。这意味着必须对返回值以外的其他内容进行更改。:=
在这种情况下,该更改是使用它更改了 ref 的存储值的分配。
作为一个好的经验法则,在 Ocaml 中,如果您避免使用 refs、记录、类、字符串、数组和哈希表,那么您将避免任何副作用风险。尽管只要避免使用 String.set 或 String.fill 等函数就地修改字符串,您就可以安全地使用字符串文字。基本上,任何可以就地修改数据类型的函数都会产生副作用。