There is no "dependency injection" in a functional world, take this opportunity to show your colleague how FP makes their life easier. It's just a function.
Instead of a class, implementing an interface, created by a factory, requiring a constructor, all you need is a function.
Anything that was previously a "dependency" in OO terms is now an argument to your function. If you want to "inject" that dependency you simply partially apply your function, the result is then of course a function with that "dependency" "injected" which can then be used as usual. In JavaScript there's even a nifty built-in prototype method on every function called `Function.prototype.bind` which allows you to do the partial application to create the "dependency injected" function!
Example:
```
const iRequireDependencies = (dependencyA, dependencyB, actualArgumentC, actualArgumentD, ...etc) => console.log(dependencyA, dependencyB, actualArgumentC, actualArgumentD, ...etc);
const withRandomDependencies = iRequireDependencies.bind(undefined, 'randomA', 'randomB')
withRandomDependencies('actualA', 'actualB', 'actualC', 'actualD', 'actualE') // etc
// => 'randomA' 'randomB' 'actualA' 'actualB' 'actualC' 'actualD' 'actualE'
```
Sure, there's solutions for this in the FP world, but in my experience they tend to have their own drawbacks. Admittedly, I've only ever used TS on the front-end (with no DI), so I've never really looked at what FP-style libraries exist for this.
I totally agree there are other good/better options (as evidenced by react and many others), but I don't think this is an entirely fair comparison. The main "DI" alternatives in React are hooks, context, and imports, none of which could really replace traditional IoC containers on the backend without some modifications.
I think the main thing that makes automatic DI easy with OOP is the clear separation between dependencies (constructor parameters), and method parameters. Admittedly, this is totally possible with FP, but requires some good conventions and doesn't seem to be nearly as popular.
I also think most OOP langs have terrible syntax for constructors, which makes it look clunkier than it really is. Primary constructors (e.g. kotlin) make this not much more verbose than the FP alternative.
> Angular 1.5 dep system
Yes it's vanilla JS, but I doubt there's many FP people that would call that functional. The examples I saw are all just using JS functions as "discount classes", which definitely aren't pure or functional.
My view on that is that it’s ok to use OOP and define classes if you are really defining an OOP style object. Back in the 90s is was taught that an object has identify, state and behaviour. So you you don’t have all three, it’s not really an object in the OOP style.
Looking at it through this lens helps make it clearer when you should add classes or just stick to function and closures.
Indeed, if you want to use emitDecoratorMetadata for automatic dependency injection, you should use classes. If the library itself takes advantage (again likely due to decorators) of classes e.g. https://typegraphql.com/docs/getting-started.html then yes, classes are again a fine choice.
The general answer is that they're useful when the type also needs to have a run-time representation (and metadata). Otherwise, not really.
A few objects contain state like say a DB connection/client or a RequestContext you pass down through your request handler middleware’s. Those are an OOP class with an interface definition.
Everything else is just functions and closures. We also generate interface objects from our GraphQL types but that’s not a real OOP type, it’s just an interface.
If you keep to that structure, you’ll largely avoid the whole polymorphism OOP type hierarchy hell and all the dangers that come with it.
As for DI (dependency injection), that’s honestly just a fancy form of passing parameters down through function calls. Technically, the RequestContext I mentioned before is a “ball of mud” provider pattern DI code smell. So maybe down the road we will use DI to create more constrained context scopes.
If I do go that route for DI, I would likely strongly follow a CQRS style class pattern to inject objects and keep them nicely named and organized. Would also fit nicely pattern wise with the existing function + closures architecture.
But yeah, overall, stick to functions and closures, use OOP style classes sparingly and you’ll get the best of all worlds.
If you got your first taste of typescript from angular and have a full stack background in c#/ Java class based style will make you feel right at home.
React seems to oscillate between the 2 styles.
My recent work in Svelte send to favor functions and types.
IMO the biggest benefit of classes is the code organization it brings. Have you ever seen a “util” class or folder. That’s what tends to happen to a code base without strong cohesion. It becomes hard to find anything.
With typescript/js you have modules as a pretty good substitute.
What I love about typescript is that you can mix the two.
Mostly classless module based with the occasional class (logger with a constructor to pass in the current module name for example) seems to be what I like most now. Use what makes sense.
It comes down to choice; pick one for the project and be consistent. That’s all that matters.
DI does not require OOP, and you don’t need DI to write good TS/JS code. DI is common in OOP, but if you aren’t using OOP you don’t need DI anyway.
Because DI is just “give me the dependencies I need when I declare I need it” you can use simple classes as scopes similar to CQRS patterns and continue doing functional programming from there.
It’s quite neat how you can interchange between the two and have it work rather nicely.
Technically, you could even do the same thing with closures and avoid OOP style classes all together even.
DI lives on, it just looks a little bit different than the constructor injection we’re used to seeing in OOP.
let sort (iterator: 'b -> 'a list) (collector: 'a list -> 'c) (comparator: 'a -> 'a -> bool) (collection: 'b) -> 'c =
...
I added the type annotations to, hopefully make it clear. The iterator is a helper to convert some arbitrary collection to a list, with the collector turning it back into a collection type again (not necessarily the same.)For example, the iterator could map from a tree to a list, and the collector then to an array. Or if you already have a list and want a list back, you could pass in the identity function for those.
One call could be the following:
sort id id (>) somelist
Hope it isn't too unreadable.Edit: Adjusted the order of the arguments, as the original order wouldn't work too well with partial application.
Though I would be wary of introducing patterns and paradigms that make sense in a different language when Typescript offers an ultimately simpler solution. Working against the grain helps nobody. Goes for both OOP and FP, really.
ES modules, functions, and well designed TS models get you 95% of the way.
0 - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
The biggest argument is that my functional-ish code is always 3x shorter with the same features, though.
Also, even if you didn't want to mock that way, you can get dependency injection with functions just by taking a parameter for a dependency. If dependency injection is the only reason you have to use a class, you probably shouldn't use a class.
Its rarely seen in the ecosystem as a solution, unfortunately (everyone is passing all arguments all the time), but its one of the rare places where this is still useful. I've had bad experience with the alternative (continuation local storage) and its not nearly as elegant.
export const myFn = (...deps) =>
You don't need anything else, for encapsulation you have the EsModule, that what OP meant.
in Java it means "you should implement this contract"
in Typescript it means "this data type has this particular shape. It may have methods, too".
in Go it means "I'd like users of this code to implement these methods" (client interfaces).
In all cases you have to work differently with them. It's not even about OOP, I think, to the point where I'm not sure now if the keyword 'interface' is part of OOP at all.