Dependency injection in functional programming (Kotlin)
tl;dr
DI in FP can be achieved using partial function application or...
For further actions, you may consider blocking this person and/or reporting abuse
I like the Partial application approach, I use it in Typescript with a custom DI container which resolves also functions, not just class instances.
called like so:
Of course you could store the dependency configuration also in some map, instead of writing onto the function, it works fine like this in Typescript, not sure you could do this in other languages.
as to
I'm not sure how the list of todos is considered business logic. So retrieving data is imo something we can do in the app layer. Impure effects however, e.g on 3rd party apis, message busses, etc, I would handle with Domain events - do all the business decisions, logic, policy in the domain, put the decisions in an Event and let the event handlers call the relevant infra.
Thanks for sharing! I'm not familiar with the JS/TS world but out of curiosity and ignorance, why not Lodash or Ramda? Don't they support partial function application? What is the purpose of late-binding (assuming the dependencies are immutable references)?
Agreed, that sounds like an approach where the code would be pure. I was referring to the difference between
vs
In the first version,
getCompletedTodosis referentially transparent only ifrepository::getUserTodosis too, which can't be guaranteed.Thanks for the response!
I'm a fan especially of Rambda (notice
bd), I use it a lot and like it. I throw in some Lodash once in a while, when I need some of their helpers for a specific problem.The problem with the various ways of composition in Typescript is some loss of (generic) type inference. I think Lodash barely tries. Rambda does way better, I would have to look into Ramda to check how they fare. If TS will receive placeholder generic types, this might improve.
Such type inference is very valuable to me, it is one of the things I like the most about Typescript; types are optional - although I use them extensively, and the inference goes a very long way, together with derivative types, to potentially reduce a lot of manual typing (Pick, Omit, Partial, NonNullable, ReturnType, to name few built-in. But one can go extreme; github.com/pirix-gh/ts-toolbelt)
So far i'm using github.com/gcanti/fp-ts (and all it's friends like io-ts etc). But i'm still not sure of the verbose syntax. I can't wait until the pipe operator (|>) in JS/TS lands, in the hopes that will resolve some of the boilerplate and verbosity. But maybe i'm really just ready for Kotlin, F# and friends..
I like to be able to define less abstract functions below my most abstract one (in class world, basically from Public to Private methods). Without late-binding I would have to define those functions above instead.
Also it helps a little bit with some annoying cyclic references between files at times - although those should generally be avoided anyway.
I agree, but that means I still don't understand the sentence.
I read it as:
"If you do this:
You essentially push business logic to the outer layers, for the sake of having a functional core."
But I don't see how doing this is moving business logic.
Perhaps i'm misreading ;-)
Thanks a lot for this very high quality response, it all makes sense. I'm very intrigued by TypeScript right now to be honest, will give it a try :)
Follow-up, I think my confusion comes from our different definition of the Domain.
See for example this naive implementation in Typescript, for how I see it:
gist.github.com/patroza/8f2d634b23...
Main take aways:
Finally, I think that for the Interactor taking the input as parameters and resolving the result by returning it as value, is a shortcut to the Clean Architecture's "Controller" calls Input interface implemented by "Interactor" and "Interactor" calls Output interface implemented by "Presenter", but I found that I hardly need the extra level of complexity it brings if you go literal.
Indeed, we had different definitions of Domain. I was thinking in terms of three layers: Presentation, Domain and Data, where domain includes business rules that are encoded in the use cases or interactors, like depicted here from this post.
If I'm understanding well what you say, your layers are the ones suggested in Clean Architecture by Uncle bob where you have, from inner to outer circles:
Makes a lot of sense too, it reminds me of the "ports and adapters" architecture, where your infra/presentation functions would be the ports and adapters. My understanding of Domain is basically Application + Domain in your case I'd say.
I think we're talking about the same thing. In your code
getCompletedTodosis pure iffgetTodosis pure. To make it 100% referentially transparent you'd have to pass the Todos, but then thegetTodoslogic would be pushed to the infrastructure layer.Small nit: you could make your domain pure by passing in
new Date()instead of instantiating it withincomplete.Few people, few articles, over few years - I couldn't understand how to do DI in FP. I finally get it! Your explanation is great.
I read some opinions in the internet that mocking (which is another term for DI) is a code smell. Would you agree with that?
Thanks a lot, I'm very glad to hear that!
I generally agree that mocking is a smell. Note that mocking refers to a testing technique, it's not a synonym for DI. Strictly speaking, mocking is about verifying that certain methods are called on some object and as such you're testing implementation details. See here.
Testing implementation details has the problem that it makes tests more difficult to maintain. For example, if you change the name of the method you're expecting to be called, you have to change all the tests that depend on this method, but the behavior may be exactly the same.
I prefer feature testing and avoid mocking as much as possible. Check these resources:
blog.twitter.com/engineering/en_us...
blog.kentcdodds.com/write-tests-no...
I mock only I/O related things in my tests. Thus I call it mocking. Probably wrong term usage. Apologies.
Do I understand it right that a way of replacing side effect function is called DI in the article above?
And isn't it just a way to mock that function?
I'm confused.
DI refers to dependency injection, which is basically passing a dependency as a parameter to a function instead of having the function call it directly. For example:
DI and mocking for testing are related in that you can pass mocks (or stubs) to the function under test. If what you're injecting is a side-effectful function, you can indeed replace it with some mock function that doesn't have side-effects, just for the purposes of testing.
Let me know if it's clearer now!
The only difference I see is that with DI the replaced value can be data.
Sorry. I still believe DI and ability to mock is the same concept - ability to replace a thing down the call chain.
Dependency injection is a concept and the ability to mock is a consequence of this concept, but it's by no means the only benefit.
You could, for example, use some class across different sections of an app but have it behave differently depending on the section. Using the dependency injector you can configure the different instances of the class that will be used in each section.
some of the problems you noticed with Reader vanish if you use functions like withReaderT
overall much of this is a problem of composing getters/setters - one of the answers to that is lenses/optics and so maybe you'll find this lens-module interesting too (deals with Readers)
Of course in languages like Elm or F# this might not be an option but Scala, PureScript and - I think - Kotlin with Arrow you might get lucky ;)
Hey Carsten, thanks for the feedback. Could you please provide an example of how
withReaderTwould solve those problems?Really excellent article! helped a lot with my understanding, thanks for taking the trouble :)
WOW! Hands down the most insightful engineering articles I've read about fp/oop relationship. Thank you very much for putting this piece of art together.
That's the kind of devs I'd love to be working with!