This talk would be a Scala port of my YLJ16 talk "Stop paying for free monads".
The FP answer to "how do we do dependency injection" is to turn the question around and instead ask, what operations would you perform if you had these dependencies? Now we can reframe the question as being about defining languages of operations, and interpreters for those languages. To achieve reuse, both languages and interpreters need to be modular, i.e. support a notion of composition. So how do we do this?
We'll compare and contrast two solutions of this problem. The first, popularised in Wouter Swierstra's Functional Pearl "Datatypes à la Carte" (and familiar to some through Rúnar Bjarnason's talk "Compositional application architecture with reasonably priced free monads") builds up a data structure of abstract operations, which are then run by an interpreter.
The second approach takes advantage of Scala's support for higher-kinded types. Rather than building up and tearing down a data structure, we simply add a type class constraint to our program. "Interpretation" is now a compile-time operation: it's just specialization to a type that satisfies the constraints. This is both simpler and more efficient, and sufficient for many purposes.
This talk would be a Scala port of my YLJ16 talk "Stop paying for free monads".
The FP answer to "how do we do dependency injection" is to turn the question around and instead ask, what operations would you perform if you had these dependencies? Now we can reframe the question as being about defining languages of operations, and interpreters for those languages. To achieve reuse, both languages and interpreters need to be modular, i.e. support a notion of composition. So how do we do this?
We'll compare and contrast two solutions of this problem. The first, popularised in Wouter Swierstra's Functional Pearl "Datatypes à la Carte" (and familiar to some through Rúnar Bjarnason's talk "Compositional application architecture with reasonably priced free monads") builds up a data structure of abstract operations, which are then run by an interpreter.
The second approach takes advantage of Scala's support for higher-kinded types. Rather than building up and tearing down a data structure, we simply add a type class constraint to our program. "Interpretation" is now a compile-time operation: it's just specialization to a type that satisfies the constraints. This is both simpler and more efficient, and sufficient for many purposes.