【问题标题】:Are functions with Try/Option signature a pure function?带有 Try/Option 签名的函数是纯函数吗?
【发布时间】:2021-05-16 10:58:57
【问题描述】:

我正在学习函数式编程并试图理解纯函数。

维基百科说函数是纯的,如果:

  1. 无副作用
  2. 将输入映射到输出

纯函数也满足引用透明性,不访问全局变量,也不改变输入变量。

偏函数是纯函数吗?

具有签名类型的函数

def foo(x: Int): Option[Int] = { if(x <= 0) None else Some(x) }
def bar(x: Int): Try[Int] = Try { require(x <= 0); x }

这些函数是纯函数吗?

它们不会将每个输入都映射到正确的输出。

我知道将数据类型声明为 Option/Try 可以让调用者知道它可能会因某些输入而失败。

上面的函数是纯的吗?

【问题讨论】:

  • 它们确实将每个输入映射到正确的输出。 OptionTry 等效果类型的想法正是以透明引用的方式封装这些东西。
  • def bar(x: Int): Int = { if(x &lt;= 0){ throw new Exception("less than zero")}; x } 不纯。但是通过添加Try 类型使其变得纯粹?怎么样?
  • 那是一个不同的问题,这取决于您认为纯和不纯的含义。但是,通常只有学者关心这一点。专业的 FP 开发人员关心 RT 和组合,因为这些属性在我们的代码库中提供了真正的好处,那就是推理的能力。通过添加Try,您的代码在外部是 RT 并且是可组合的,这很好;有些人(像我自己) 更愿意使用 Either 并手动引发错误,而不是将其扔到 Tryapply 中,但这更多是关于代码风格。

标签: scala functional-programming


【解决方案1】:

TryOption 没有说明是否使用了副作用来创建它们。

def parseLong(str: String) = Try(str.toLong)

这是纯粹的计算——它不会改变全局状态,它的输出只取决于输入。另一方面:

Try {
  // create entry in database
}

会改变外界的状态。因为TryFuture 捕获异常,所以它们在“Scala as better Java”中被用作表示副作用的一种方式,但您不能强制执行此约定或任何其他约定。

如果您将副作用放入 Try { ... },它们将被立即评估并记录结果,因此您将破坏引用透明度。

// tupled is from Cats
val t = Try{ println("foo"); 10 }

(t, t).tupled.map { case (a, b) => println(s"$a  $b") }

不一样

(Try{ println("foo"); 10 }, Try{ println("foo"); 10 }).tupled
  .map { case (a, b) => println(s"$a  $b") }

因此,返回TryOptionFuture 等的函数是否是纯粹的,取决于您是否在它们内部执行副作用。这根本无法从类型中推断出来。它们可能是纯的或不纯的,你只能通过观察来发现。它们是部分的还是全部的也不能告诉你任何事情。

在纯粹的 FP Scala 中,您将使用一些 IO 而不是 TryFuture 来处理可能异步和/或失败并且肯定会产生副作用的代码,因为它只会在每次被要求时计算值你问它。

val t = IO { println("foo"); 10 }

(t, t).tupled.map { case (a, b) => println(s"$a $b") }

相同
(IO { println("foo"); 10 }, IO { println("foo"); 10 }).tupled
  .map { case (a, b) => println(s"$a $b") }

然后你将使用 IO 来处理所有副作用,你会非常小心不要使用其他任何东西来进行不纯的计算(嗯,有时你可能会转换为/来自Futures)。所以在这样的代码库中,返回 Option/Either 的函数几乎总是纯的。至少是程序员可以直接控制的部分(库可以有其他约定)。

【讨论】:

  • 虽然这完全正确,但不确定 OP 是否已经在关注 IO 之类的东西,只是想知道返回 Option 是否被认为是尊重参照透明度。与其对应的 null 相比,它确实做到了这一点,因为两者都关注缺失值的副作用,而不是可变性或 I/O 操作的副作用。
  • Try 不考虑 RT,因为副作用。如果是这样,它就不会违反单子定律,并且可以被认为是一个实际的单子(而不是“具有单子接口”)。 Option 采用纯值,因此 RT 的想法不一定有意义 - 如果有一些副作用,则必须在创建 Option 之前执行。但是 OP 询问了签名中带有 OptionTry 的函数,Scala 不提供任何关于它们的 RT 保证,特别是如果您使用“Scala 作为更好的 Java”代码库。
  • 返回IO 也不能保证不会产生副作用。此外,RT 的概念比传统的副作用定义更大。 I/O 或可变性并不是唯一的副作用,返回null 或抛出异常都是副作用;还包含可变性不是外部调用者的副作用。因此,引用透明度的概念适用于所有这些。 - 此外,Try 确实形成了有效的 Monad,以及 Future。它们没有形成有效的 Sync,它们是不同的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-10-05
  • 2018-04-12
  • 1970-01-01
  • 1970-01-01
  • 2021-05-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多