Scala 在可变数据结构上操作的函数是否可以是引用透明的?

Scala 在可变数据结构上操作的函数是否可以是引用透明的?,scala,functional-programming,scala-cats,referential-transparency,cats-effect,Scala,Functional Programming,Scala Cats,Referential Transparency,Cats Effect,我正在开发一个网络应用程序,并设计了以下特性来从远程机器读取文件: 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 = _

我正在开发一个网络应用程序,并设计了以下特性来从远程机器读取文件:

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
  )
}
我在这里看到的问题是,实现接受
NetworkContext
作为参数,该参数是可变的,并且包含与网络连接相关的
fileDescriptor
等字段

此函数是否在引用上透明


我认为是的,因为函数本身不提供对可变状态的直接访问(它处于
Sync[F].delay下),即使它接受可变数据结构作为参数

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
然后

在这里,魔法断言就像人脑一样,不会遇到停顿问题,因此能够推断函数行为的相等性,也就是说,应用
f
计算值
(\u=>foo.x)
,它实际上总是等于值
(\u=>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的。通过
f(Foo)(
)中的额外括号运行


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

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
然后

在这里,魔法断言就像人脑一样,不会遇到停顿问题,因此能够推断函数行为的相等性,也就是说,应用
f
计算值
(\u=>foo.x)
,它实际上总是等于值
(\u=>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的。通过
f(Foo)(
)中的额外括号运行


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

不,它不是引用透明的,也不能。这与“可变字段”无关。只有函数返回的内容才重要。如果它总是为相同的参数返回相同的结果,那么它是引用透明的。如果不是,就不是。@n.“代词m。但是它返回相同的(
Sync[F]
)结果,不是吗?所以结果可以被函数调用所代替。嗯,看起来我不明白同步是如何工作的。是单子吗?如果是这样的话,为什么网络上下文需要是可变的呢?@n.“代词是m。”
Sync
实际上是一个类型类,提供了暂停副作用执行的功能,这也是一个
Monad
。特别是,对于
IO
,有一个
Sync[IO]
实例。我选择使用
Sync
而不是
IO
的原因是能够推迟实际选择
Effect
s。在Scala中,我们有不同的effect
IO
实现,来自不同的库(我知道至少有3个常用库),所以它看起来确实是引用透明的,因为它只返回一个命令,在运行时会产生副作用。不,它不是引用透明的,也不可能是引用透明的。这与“可变字段”无关。只有函数返回的内容才重要。如果它总是为相同的参数返回相同的结果,那么它是引用透明的。如果不是,就不是。@n.“代词m。但是它返回相同的(
Sync[F]
)结果,不是吗?所以结果可以被函数调用所代替。嗯,看起来我不明白同步是如何工作的。是单子吗?如果是这样的话,为什么网络上下文需要是可变的呢?@n.“代词是m。”
Sync
实际上是一个类型类,提供了暂停副作用执行的功能,这也是一个
Monad
。特别是,对于
IO
,有一个
Sync[IO]
实例。我选择使用
Sync
而不是
IO
的原因是能够推迟实际选择
Effect
s。在Scala中,我们有不同的effect
IO
实现,来自不同的库(我知道至少有3个常用库),所以它看起来确实是引用透明的,因为它只返回一个命令,当运行时,产生副作用。我们只是声明返回IO类型的函数可能具有任意效果,而没有详细说明这些效果是如何产生的!补充说明,但不在问题本身的范围内。信息技术