I’m not sure that the suggested solution, free monads, is that good as they have a big performance penalty.
Yeah, sorry, I didn’t mean to suggest delving into free monads and such. Not for performance reasons, but because I don’t think such a level of abstract-ness is particularly productive. It’s usually better to use more purpose-built constructs IMO.
Take the Elm Architecture as an example. Its
Msg type is basically a description of high-level intent, that is translated into state transitions and low-level commands, which are also descriptions telling the runtime what to do, by the
update function. No monad in sight, and yet completely pure. The devil is in the runtime of course, which isn’t implemented in Elm itself, but it would be fairly easy to implement in Reason.
I also don’t really see the difference between passing partial functions and mocking and something like MTL from Haskell as the typeclasses are essentially being used as dependency injection there as well.
I’m not all that sophisticated with Haskell, so I might not understand all the implications. But from what do I understand, using type classes is essentially the same as passing first-class modules around in Reason (instead of separate functions), except it’s done implicitly. That implicitness makes it a very convenient approach in Haskell (but also tends to make the code harder to understand). Using a (module) functor is a middle ground where you pass those modules (as analogues to type class instances) as arguments at the module level instead of at the function level. But since you can’t define functors alongside toplevel modules, that approach isn’t very convenient either.
The article you link argues that it isn’t somehow a functional approach but I’m not sure where the problem is in this approach yet.
Since Haskell is inherently pure and every side-effect encapsulated in a monad already I don’t think there really is a difference in that sense. But in an impure language, unless you create your own IO monad and interpreter to push side-effects to the boundary, the “dependencies” you pass in are impure and will make every function that uses them, directly or indirectly, impure as well.
In the context of testing, the difference is that you have to mock impure dependencies, whereas you can inspect and compare the return value of pure functions directly. As long as you have access to the content of those values at least, which I suppose you don’t in Haskell, hence the need for mocking. Both the tests and the functions themselves become simpler and easier to reason about when they’re pure, and if you don’t have any dependencies you don’t have to pass them around either.