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.
IMO, the semantics of
read
areSome say this is a kind of sleight of hand:
For example, consider the following object with mutable state
We can confirm
f
is not referentially transparent becauseHowever re-implementing
f
to "suspend" the effectwhich is similar to
then
Here magical assert is like human brain and does not suffer from halting problem so is able to deduce equality of function behaviour, that is, applying
f
evaluates to value(_ => foo.x)
which is indeed always equal to value(_ => foo.x)
even though at some pointFoo.x
was mutated to-11
.However, running
f
's effect we have(Note how we are simulating
IO.run
via extra parentheses inf(Foo)()
)Hence expression
f(Foo)
is referentially transparent, however expressionf(Foo)()
is not.