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
readareSome say this is a kind of sleight of hand:
For example, consider the following object with mutable state
We can confirm
fis not referentially transparent becauseHowever re-implementing
fto "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
fevaluates to value(_ => foo.x)which is indeed always equal to value(_ => foo.x)even though at some pointFoo.xwas mutated to-11.However, running
f's effect we have(Note how we are simulating
IO.runvia extra parentheses inf(Foo)())Hence expression
f(Foo)is referentially transparent, however expressionf(Foo)()is not.