I think there is sort of a hole in the logic about testing though, since the article talks about testing `runPure` not `run` which is almost useless unless `run` is implemented in terms of `runPure` which introduces its own can of worms because you have to consider how things like laziness, streaming, etc. come into play in the pure version, which is a step in the wrong direction IMO.
What the article says about reasoning is important, but I feel like it's misleading to say that it comes from the using the free monad. It's really about the Functor instance. The fact that `ExitSuccess >> m = ExitSuccess` is a result of the Functor instance for TeletypeF not using (and not being able to use) the `f` parameter in the `fmap f ExitSuccess` definition.
If you don't want to use a free monad, you could also use a type class / monad transformer like `MonadTeletype` which defines the actions of the teletype can perform. Then you write all your teletype functions with a signature like `MonadTeletype m => .... -> m a`. You still get the guarantee that no arbitrary actions are there but you don't have the complexity of the free monads which can be a little daunting for newer Haskellers.
myThing :: Foo -> ProblemDomain Bar
myThing f = do
x <- subthing1 f
y <- subthing2 x 23
return $ g y
to looking like myThing :: Foo -> ProblemDomain Bar
myThing f = do
x <- subthing1 f
y <- subthing2 x 23
return $ g y
There's no difference, don't spend too much time looking for one.The underlying representations of the type change. How you interact with the type does not. There's no mental overhead. There is a small bit of added run time cost. You pay somewhere for adding the ability to interact with the type in additional ways, but not by breaking any existing code.
My question has nothing to do with the language. You are adding a relatively advanced construct which requires a lot of prior knowledge to understand: Free monad, monad, applicative functor, functor, monoid. Then you need to understand the laws that monads must obey, which are not enforced by the type system. Before you get there, a passing grasp with Haskell and higher kinded types will help, and some basics in category theory too.
Saying that adding the free monad doesn't have any cost in understanding is very naïve. You forgot how long it took you to reach that understanding.
But the code is indeed easier to verify for correctness, due to the purity. I wish this style of programming is more prevalent, but alas, it's more difficult to write this way...there aint no free lunch