What I do miss though (dearly) is that there is a certain cost as to what can be encapsulated. For instance, in Haskell, it is no longer so easy to hide side effects behind a pure interface.
What I do miss though (dearly) is that there is a certain cost as to what can be encapsulated. For instance, in Haskell, it is no longer so easy to hide side effects behind a pure interface.
This is not true. There are a number of ways to do this in Haskell.
Also this is tantamount to, "my programming language won't let me lie", and is often associated with an infacility with Haskell. I mean no offense, just speaking from experience.
You don't need to hide effects behind a pure interface once you're comfortable with the abstractions provided.
Edit: tel's comment is quite good as-is, but I'd like to add that part of what makes ST nice isn't just having an eliminator, but also so you have a more precise delineation of what "effects" could happen. IO is kind of a big type. Still very valuable.
Now I know that you can hide unsafePerformIO in the interface, and in this case I expect it is fine, but this is not easy, it feels like a hack.
Another example that I have personally watched play itself out in disbelief was in a fairly large Haskell project: There was need of a lookup table, with some 10000 elements. In such cases, I would have stored the lookup in a database, or a separate file at least, but because they wanted to maintain purity so badly, the solution was to write a 10000 line long Haskell file. But wouldn't it be nice if you could load a file and tell Haskell: don't worry, this won't change, so treat reads to this purely.
I never said such things were impossible, merely not easy, and I don't believe this is by accident. I am almost certain that such things are made difficult to prevent abusing such features, but that doesn't stop me from missing them :) .
import Control.Monad.ST
import Data.STRef
data Counter s = Counter { get :: ST s a, inc :: ST s () }
mkCounter :: STRef s Int -> Counter s
mkCounter v = Counter (readSTRef v) (incRef v) where
incRef ref = do
i <- readSTRef v
writeSTRef v (i + 1)
A little noisier since we note that reading the ref is an impure action and we track a phantom `s` parameter around in order to convince Haskell that certain uses of counters are pure. Now comp :: ST s Int
comp = do
v <- newSTRef 0
let c = mkCounter v
inc c
inc c
inc c
get c
This is technically a pure computation despite using an impure mechanism internally. We can get Haskell to prove this for us pureComp :: Int
pureComp = runST comp
Here `runST :: (forall s . ST s a) -> a` completes the proof that if the `s` variables in a computation are completely undetermined then that computation must be pure.