2

I am a dev trying to bend my mind around the typelevel stack (Cats and Cats-Effect)after years of experience with less functional scala.

I'm having a real trouble bending my head around the Reader monad. I can somewhat follow the examples here: https://dev.to/riccardo_cardin/and-monads-for-almost-all-the-reader-monad-1ife , but one point that totally baffles me (as a person from a more OO background) is WHY are we doing all this? It seems to me that in the end, we just end up with a bunch of functions of the type

def myMethod(arg1: A, arg2: B): Reader[MyContext, MyMethodReturnType]
def myMethod2(arg1: A): Reader[MyContext, SomeOtherReturnType]

Which to me sounds like the exact equivalent of the OO concept of a class:

class MyContext(/*class dependencies used by the methods*/) {
    
    def myMethod(arg1: A, arg2: B):  MyMethodReturnType
    def myMethod2(arg1: A): SomeOtherReturnType
}

Quoting the article:

So, Stocks has a dependency upon StockRepository. How can we express such fact in the code? We don't want to use the constructor injection mechanism or anything related to it. We want to stay functional.

So, my question is why? The quote above seems to suggest that "staying functional" is its own reward, but can you give me an example that showcases how not using constructor injection is an advantage?

Thanks!

Emil D
  • 1,864
  • 4
  • 23
  • 40
  • 3
    Nobody doing serious development on the **typelevel** stack that I know uses `Reader` as a way to manage dependency injection. Actually, we usually just recommend folks to avoid any complex solution like implicits, **macwire**, `Reader`, or runtime reflection in favor of just passing dependencies to constructors. – Luis Miguel Mejía Suárez Oct 28 '22 at 19:40
  • 1
    Well, in the first case, you have two completely independent methods, both of which can be reconfigured with their own `MyContext`, if needed. In the second case, you have two methods chained together and bolted down to the enclosing class instance that you have to instantiate somewhere (in particular, `method1` and `method2` cannot come from different packages). Lose coupling vs. tight coupling. Do you expect an explanation for why tight coupling is bad? – Andrey Tyukin Oct 28 '22 at 19:40
  • @AndreyTyukin in both cases, the functions are dependent on their context, so it seems like the same level of coupling. The context has to be instantiated on both cases in order to call the method. I suppose in the reader monad case you can put both methods in different packages, but they would transitively depend on the package of the context. – Emil D Oct 28 '22 at 19:56
  • _"The context has to be instantiated on both cases in order to **call** the method."_ - Well, yes, but in the second case, the `MyContext` has to be instantiated _before you even **have**_ a `myMethod`. If your `MyContext` is instantiated once in the very beginning, it doesn't matter. If you have to use `myMethod` in some computation that is passed to a framework and is re-ran in thousand different contexts, then your second solution would be insufficient. – Andrey Tyukin Oct 28 '22 at 20:02

1 Answers1

1

So, Stocks has a dependency upon StockRepository. How can we express such fact in the code? We don't want to use the constructor injection mechanism or anything related to it. We want to stay functional.

I'm going to contest the article here. We do want to use constructors. Objects are not anti-FP. Mutable state is anti-FP, but that doesn't mean we can't use objects.

This is not just my opinion - the authors of the libraries in the typelevel ecosystem also tell you to do it this way, both explicitly (seriously, go ask them) and implicitly (observe how almost every typelevel library doesn't use Kleisli at all)

Using Reader makes sense in haskell, not in scala.

I've been working on typelevel ecosystem codebases for over 5 years, and I've almost never seen Reader (aka Kleisli) used for this, and any time I did, it was bad.

Just use function parameters or class constructors. They aren't scary. Class constructors are roughly isomorphic to Kleisli anyway. You even note that in your question, "sounds like the exact equivalent of the OO concept of a class"

Use Kleisli when you want a value whose api is related to composing functions. A good example where Kleisli makes sense is the http4s library.

Using Kleisli instead of class constructors is a great way to make your team hate scala and advocate for using a different language.

Daenyth
  • 35,856
  • 13
  • 85
  • 124
  • So, it is indeed not a common thing to do, it's not just me being an old fart stuck in my old ways :) Good to know ! – Emil D Jun 08 '23 at 14:32