首页 > 解决方案 > Can a function operating upon mutable data structure be referentially transparent?

问题描述

I'm working on an network application and designed the following trait to read files from remote machines:

trait ReadFileAlg[F[_], Dataset[_], Context]{
  def read(path: String, ctx: Context): F[Dataset[String]]
}

final class NetworkContext{
  private var fileDescriptor: Int = _
  private var inputStream: InputStream = _
  //etc
}

final class ReadFromRemoteHost[F[_]: Sync] extends ReadFileAlg[F, List, NetworkContext]{
  override def read(path: String, ctx: NetworkContext): F[List[String]] = Sync[F].delay(
    //read data from the remote host
  )
}

The problem I see here is that the implementation accepts NetworkContext as a paramenter which is mutable and contains fields like fileDescriptor which is related to a network connection.

Is this function read referentially transparent?

I think yes, because the function itself does not provide direct access to a mutable state (it is under Sync[F].delay) even though it accepts mutable data structure as an argument.

标签: scalafunctional-programmingscala-catsreferential-transparencycats-effect

解决方案


IMO,的语义read

“当你应用我时,我是纯洁的,但当你运行我时,我有副作用。”

有人说这是一种花招

...我们只是声明一个返回 IO 类型的函数可能具有任意效果,而无需详细说明这些效果是如何产生的。该方案有两个后果:首先,函数的类型告诉您它是引用透明的还是运行时有副作用。

例如,考虑以下具有可变状态的对象

object Foo {
  var x = 42
}

def f(foo: Foo.type): Int = foo.x

我们可以确认f它不是引用透明的,因为

assert(f(Foo) == 42) // OK
assert(f(Foo) == 42) // OK
...
Foo.x = -11
...
assert(f(Foo) == 42) // boom! Expression f(Foo) suddenly means something else

但是重新实现f以“暂停”效果

def f(foo: Foo.type): IO[Int] = IO(foo.x)

这类似于

def f(foo: Foo.type): Unit => Int = _ => foo.x

然后

magicalAssert(f(Foo) == (_ => foo.x)) // OK
magicalAssert(f(Foo) == (_ => foo.x)) // OK
...
Foo.x = -11
...
magicalAssert(f(Foo) == (_ => foo.x)) // Still OK! Expression f(Foo) did not change meaning

这里神奇的断言就像人脑一样,不会遇到停机问题,因此能够推断出函数行为的相等性,也就是说,将f评估应用于(_ => foo.x)确实总是等于 value 的 value (_ => foo.x),即使在某些时候Foo.x被突变为-11

但是,运行f的效果我们有

assert(f(Foo)() == 42) // OK
assert(f(Foo)() == 42) // OK
...
Foo.x = -11
...
assert(f(Foo)() == 42) // boom! expression f(Foo)() suddenly means something else

(注意我们是如何IO.run通过额外的括号来模拟的f(Foo)()

因此表达式f(Foo)是引用透明的,但表达式f(Foo)()不是。


推荐阅读