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