19 ms·
Printf(“%s %s”, dependency, injection)
- encryptluks2 5y agoI feel more ADD after reading this.
- lordofgibbons 5y agoOne of the things I really love about Go is that Dependency Injection isn't idiomatic. You just pass in your dependencies, and I find that immensely easier to wrap my mind around when starting work on an existing project.
- eru 5y agoGo is not the only one. When I asked a few years ago, if someone could explain dependency injection in terms that I understand, ie Haskell terms, I got the reply of 'hmm, maybe it's like giving arguments to your function?'.
- emmab 5y agoHaskell has implicit parameters[1]. [1]: https://www.haskell.org/hugs/pages/users_guide/implicit-parameters.html https://www.haskell.org/hugs/pages/users_guide/implicit-para...
- Athas 5y agoWhile this is true, it should be noted that: * Implicit parameters are a language extension, not part of standard Haskell (although extensions are so ubiquitously used that this does not matter much). * Implicit parameters are rarely used[0]. I would not be surprised if most experienced Haskell programmers have never used them, and while they may know they exist, they might not even remember the syntax or semantics. I belong to that category myself. In Haskell, type classes tend to be used for what other languages might do with implicit parameters. [0]: https://gist.github.com/atondwal/ee869b951b5cf9b6653f7deda0b7dbd8 https://gist.github.com/atondwal/ee869b951b5cf9b6653f7deda0b...
- eru 5y agoIn more orthodox Haskell, you can use eg the 'Reader Monad' with a suitable datastructure (or combination of Applicatives) where other languages would use dynamically scoped variables.
- gpderetta 5y agoNice! I was wondering if implicit parameters were equivalent to dynamic scoping. It seems to be the case. more on the topic: http://blog.ezyang.com/2020/08/dynamic-scoping-is-an-effect-implicit-parameters-are-a-coeffect/ http://blog.ezyang.com/2020/08/dynamic-scoping-is-an-effect-...
- jcelerier 5y ago> Go is not the only one. When I asked a few years ago, if someone could explain dependency injection in terms that I understand, ie Haskell terms, I got the reply of 'hmm, maybe it's like giving arguments to your function?'. not sure it's a really good explanation. You aren't the one giving the arguments to the function, it's the framework in which you are opting into which is going to do that. Imagine you are writing a software for, say, rescaling images. You have hard requirements: - You want people to be able to provide their own rescaling code as external plug-ins / DLLs which you do not know about when compiling your host. - You want your host software to provide the memory allocation primitives to the DLLs so that they can allocate memory for the rescaled images in a way that you will have control of in the host software. - The memory allocator being used can itself be a plug-in too, that the user can choose at runtime in e.g. a GUI settings panel. How do you do that ? I'd wager that whatever solution you find, it will really look like DI.
- jppittma 5y agoI’ll hold the rescale image function as a function pointer and only call vía that pointer when I want to rescale. If you give me a new one via a plug-in or dll, I’ll set the value of the rescale function to your function.
- jcelerier 5y agoYes, and you are going to store the function pointers somewhere in your code right ? To show a GUI menu to your users with all the available plugins
- eru 5y agoWell, naively this would sound to me like you would pass these things in as arguments? (Alternatively, for something more Haskell specific, you can also write your stuff generically against a typeclass, and let people provide their own implementations.)
- im3w1l 5y agoThe old school way of doing that is to have a "plugins" folder. Every library there is loaded. It calls a register-library function and provides a struct full of callbacks (for image rescaling or memory allocation in this case). The plugin directly calls memory allocation routines of the host, which delegates to the one selected in the ui.
- LukeShu 5y agoDependency Injection is just passing arguments. If you're writing a library that does things that involve talking to a database: - Not DI: You `import Database.PostgreSQL.Simple` and your function(s) take the appropriate arguments to pass to postgresql-simple. - DI: Rather than you `import`ing a library to talk to a database, you take an argument that is an object/function (a partially-applied `connectPostgreSQL`? an instance of a typeclass?) that you use to talk to the database. (IDK if the various SQL libraries for Haskell are compatible enough with each other to make that example particularly realistic) Instead of using a library, you take an implementation as an argument. That's all DI is.
- deleted 5y ago[deleted]
- nerdponx 5y agoRight, I think functional programmers like to trivialize the actual usefulness and value of "dependency injection" as a standalone concept. Here's a Python example without DI: class MyClient: def __init__(self): self.http_client = httpx.Client() And the DI version, which is a small change with big consequences (especially for testing; no more patching!): class MyClient: def __init__(self, http_client=None): self.http_client = self.http_client or httpx.Client()
- Sohcahtoa82 5y agoself.http_client = self.http_client or httpx.Client() should be: self.http_client = http_client or httpx.Client() But yeah, once I learned that DI can be summed up into what you put here, I realized that DI is a $1,000 term for a $5 concept that has $100,000 ramifications in terms of what it enables you to do.
- Spivak 5y agoPython also has a really nice pattern using classes for this. I've heard it called the supply chain pattern before. class MyClient(HTTPClient): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def do_some_networking(self, arg): super().http_get("server", arg) class MyClientWithDI(MyClient, MyHTTPClient): pass client = MyClientWithDI() # Will call MyHTTPClient's http_get client.do_some_networking("/status")
- agumonkey 5y agoIn FP everything is an argument anyway, but in OO land .. you always have to come up with a new concept to overcome the many limits. That said, I think Scala did that, separating module/package level parameters and functional parameters can help designing/thinking. my 2 cents
- eru 5y agoIn FP, we still come up with lots of new concepts. Especially when it comes to statically typing these 'just an argument anyway' constructs. But yeah, you probably get something like 80% of the benefit of modern functional programming from just first-class functions, algebraic data types (and pattern matching over them), and immutability by default.
- ngc248 5y agoDI is just passing arguments, but there is another term used along with that, which will better clarify why DI is needed, which is IOC (Inversion of Control).
- pjmlp 5y agoOn the contrary, you get DI via kubernetes orchestrations and WebAPIs.
- jackblemming 5y agoThe underlying meta lesson here is anything taken to an extreme is probably not good. There's many axis on which people can fall prey to this. Here it's flexibility, but it could be robustness, speed, etc.
- matsemann 5y ago> Whenever I heard DI, this is what I thought that referred to. Turns out no. Hmm, I feel they actually are very similar. The map function doesn't care what the mapping function is, as long as it's something it can call on each element returning something, it can do it's job. Same with DI in enterprise apps, my db-service don't care if it's a in-memory-test-db or postgres being injected, as long as the connector allows it to call sql methods on it.
- cseleborg 5y agoWhat the author is really arguing against is configuring objects at construction time with their dependencies, rather than calling a function with one or more parameters being a dependency. The latter is a lot easier to reason about and run in a test.
- eru 5y agoIf you have curried / partial application, 'construction time' and 'calling time' blur a bit.
- cseleborg 5y agoIndeed, it's never quite black and white. The author's obviously referring to the crazy trends in the Java community back when Design Patterns were the only game in town. I have myself worked on code built like this, it's usually very difficult to retrofit into a unit test. Hell, I've even written some AbstractSingletonFactories myself...
- inopinatus 5y agoThis seems congruent to the object<>closure equivalence. Nevertheless the resulting code reads quite differently. Any kind of early binding to dependencies can read like a “COME FROM” at times (especially debugging time), and all that extra state burns memory. In Ruby one of my favourite calling conventions is to pass self to the methods, rather than the constructor, of collaborator objects.
- codingclaws 5y agohttps://news.ycombinator.com/item?id=28640684 https://news.ycombinator.com/item?id=28640684
- arcosdev 5y agoThis article was so helpful in communicating my exact frustration with DI in .NET. "Why does this have to be so complicated? This is so simple in JavaScript." just kept running through my head the entire time.
- capableweb 5y agoWhy can't you just do DI the same way you do it in JS? You can pass Objects as parameters to functions and that's all you need.
- arcosdev 5y agoYou are right, but I was learning they way "it is done" at the time.
- thrower123 5y agoAspNet style dependency injection has dug it's claws in to an insane degree, and it's almost always completely unnecessary. If you're never going to change the implementation of a logger or a database connection, there is almost zero value in standing up the whole DI machinery rather than a simple singleton.
- metafunctor 5y agoI really like how Standard ML handles this with functors [1] (hence my nickname). In SML, a piece of code depends on interfaces, and exposes an interface of it's own. Functors bind together pieces of code with appropriately matching interfaces. [1]: https://smlhelp.github.io/book/concepts/functors.html https://smlhelp.github.io/book/concepts/functors.html
- gadrev 5y ago> Speaking of Haskell, the nice people of the GHC were so kind as to implement this in .NET, in the form of Language-Integrated Query (LINQ): > They called it Select as to not arouse any suspicion, hinting that this was just typed SQL, not functional programming. Sneaky sneaky. Haha.
- gpderetta 5y agoThat's actually even more confusing, as SQL select is the projection operator and it wouldn't take a single transformation function but a mapping of attribute names to mapping functions.
- 9dev 5y agoWhat annoys me about articles of this kind is that they compare the most simple, trivial summation example with full-grown, framework-y dependency injection. There is a reason why this kind of configurable, Interface dependent DI style has developed- because it makes your life easier in large-scale applications. I’d be inclined to agree that you probably don’t need that in your hobby or research projects, but if you ever worked on a serious application with test mocks, production/beta environments, hundreds of libraries that need to play in tandem, and a team of several developers, not having proper DI kills your productivity.
- kgeist 5y agoI agree. When I read this kind of articles, it makes me think: either we're doing it completely wrong, or the author simply doesn't have experience designing and maintaining large projects. In any case, good food for thought.
- fredrikholm 5y agoI don't know on which side I/we stand in your example, but 300K SLOC of a great codebase (without this style of programming) so far! Granted, the team is wonderful and management is top notch. In 2020 we served roughly 4 billion purchases on that platform :)
- dpnmn 5y agoI trust you if you say that your project and code base is great, without DI (of that particular kind). But the comment was about your examples, and they are not great - they appear to be created to show how useless or overcomplicated DI is. And it is overcomplicated - in your toy examples. However, there are plenty of better examples on the net of the benefits of DI, but in order to actually show any usefulness they either have to be large, or depend on the readers to "extrapolate" to larger/more complicated code bases.
- fredrikholm 5y ago
- nine_k 5y agoJava designers have really shot every language user in the foot by eschewing the notion of a function from the language. The only way to have a function is to create a class with a single static method. Hence you can't have function composition, instead you get dependency injection, with an extra layer of XML files in most frameworks to allow injecting different stuff without recompiling the classes without access to the source, or just obviating the need to recompile. DI is a nice concept, and is used to solve a real problem of declaratively configuring closed-source code. I bet that declarative DI implemented for CL or Scheme would look more compact and neat than the typical Java approach.
- IshKebab 5y agoA static class method is exactly the same as a free-standing function, it's just in the class namespace. I don't see why you think that means you can't have function composition.
- gowld 5y agoNot true. It's not first class. You can't pass a static class method to a method and then call it. You have to instantiate a Function instance or equivalent.
- IshKebab 5y agoOk but that's a separate weird limitation that only applies to Java. You can pass static methods as parameters in at least C++, Python and Javascript.
- UncleMeat 5y agoThat is orthogonal. Java could easily have free standing functions that are still not first class.
- native_samples 5y agoThat's not actually true. You can use method handles to do that.
- danidiaz 5y agoOnce the graph of dependencies between components gets complex, passing all those beans as positional arguments might get tiresome, and type-directed autowiring begins to seem more appealing. Most of the information required for constructing the graph resides in the types themselves, after all. DI frameworks also provide aspect-oriented-programming services (transactionality, logging...). Doing that with plain functions in an arity-polymorphic way is possible, but not completely trivial, in my (Haskell) experience. http://web.cecs.pdx.edu/~ntc2/haskell-decorator-paper.pdf http://web.cecs.pdx.edu/~ntc2/haskell-decorator-paper.pdf
- yobbo 5y agoA big point is that a class is testable in isolation if all its dependencies can be "faked". A class with dependencies that can't be faked is problematic, so the class should generally not create any members itself. I think the "injection" part is more literal in frameworks where the injection-container inserts references into objects in a "scope". For example in hibernate, dependencies could be given in annotations, and did not have to given in the constructor.
- pulse7 5y agoThis is based on assumption that class is a unit of testing. For me, unit testing is about testing units with meaningful functionality which is often a set of classes - a module or a component. So dependency injection should be a property of such unit which is many cases is not a class...
- gowld 5y agoIn Java everything is a class or attached to one.
- vbezhenar 5y agoIf you don't like frameworks, just inject dependencies manually. It's very simple, it scales to hundreds of classes and then you can migrate to DI (but probably you won't reach that line ever, microservices are here to stay). class Service1 { ... } class Service2 { private Service2 service2; void setService2(...) void afterPropertiesSet() { requireNonNull(service2, "service2"); } } main() { Service1 service1 = new Service1(); Service1 service2 = new Service2(); ... service2.setService1(service1); ... service1.afterPropertiesSet(); service2.afterPropertiesSet(); ... I used setter injection and afterPropertiesSet callback. It allows not to worry about construction order and loops. Also it allows to fail fast if dependency not set. And it's compatible with Spring, if you ever decide to use it. But using constructor injection is even simpler and for applications with few dozens of services that might be a more clean approach.
- gowld 5y agoHuh? Microservices are why you'd have hundreds of classes and lots of dependencies to coordinate.
- jmull 5y agoService2 has a setService2 method and private Service2 member? Maybe just a typo, but doesn't exactly allay my concerns that this is another spaghetti-code pattern.
- vbezhenar 5y agoYep, it was typo, sorry. Though recursive dependencies sometimes are necessary (usually to work around some AOP quirks).
- Someone1234 5y agoThat example code looks like the worst of all worlds. First off you aren't using interfaces, which makes it unclear how you're writing test versions of your classes, but also you're setting up a massive foot gun by having a dependancy-injector with completely uncoupled consumers later in that same class. For example if I have multiple setService (e.g. serviceA, serviceB, and serviceC) and I want to call .afterPropertiesSet(), how do I know which it depends on externally? Do I have to go inspect afterPropertiesSet()'s code? Or just wait for an exception to be thrown in a Run<->Throw<->Fix endless cycle? Eww. There's a very, very, good reason why all DI frameworks have you use interfaces and the class's constructor to inject. Because if injection happens during class construction, every call after can make the assumption those things exist. By decoupling that assumption from those calls, you've created a development hell (i.e. this is an unmaintainable mess). There're two good ways to do DI: - During class construction. They then always exist in the class's scope. - Every method call individually takes in all explicit dependencies (e.g. GetMagic(iDatabase database, iDateTime dateTime, ...)). Your example is neither of those.
- fredrikholm 5y agoAlways happy to see my articles (or in this case, rant!) posted on HN :) This was written after comparing a ~300K SLOC Go project to it's original ~800K SLOC Java project at work, that we've rewritten over the course of a year or so. In the Go version, test coverage is floating around 90-95%. Performance, and memory use especially, is drastically better. This is from lessons learned, where a large part of those lessons meant reducing this type of 1990s XP/Agile/SOLID style of constant indirections and pass through classes. Yes, the main files where startup allocation happens are huge, but it's all in one file (per service) and can be easily grokked with IntelliSense and general code navigation features. In the Java version, every folder had a large section of subfolders with files that pointed to other files that perhaps did something besides calling other objects. Startup was horrible, memory consumption was high and tests were somehow more verbose than Go (which are already a mouthful). I want to point out that I love SmallTalk, and feel that this style makes sense in that environment. For Java/Microsoft Java, this style is IMO not worth the effort for neither developers nor codebase. Interesting discussions in this thread already, thank you <3
- bobmaxup 5y agoHow is any of this particular to Java or Go? What does eXtreme Programming or Agile have to do with how you pass messages in code?
- fredrikholm 5y agoI've yet to be in a large, enterprise Java/C# project that doesn't subscribe to this style (seen roughly 10 of these projects at 3 companies). Perhaps it's something about my area, but this style is standard idiomatic Java/C#. EDIT: And the opposite statement is (perhaps more) true for Go. Adopting IBeanProxyFactory-type of stuff in there is beyond unidiomatic.
- xh-dude 5y agoBecause passing messages still means dispatch on concrete types - something concrete sends a message, something concrete receives a message. That’s the whole ballgame - get one concrete type to talk to another. Even if ‘concrete’ is in a seemingly typeless language, we can Hindley-Milner our way down to this. I think it’s telling that Go, Rust, as well as more inspired stuff in later editions of C++/Java/C# all seem to be demonstrating that nominal inheritance-first polymorphism is not the only or always the best maze of abstraction for getting to a concrete dispatch from type A to type B in imperative-feeling languages.
- stefan_ 5y agoAll of these articles omit just about the most important part of DI. And I admit it can be subtle, but the biggest thing DI solves is just initialization order. You can call it "Static Initialization Order Fiasco" in C++ or just general dependency handling, but a big problem in any sort of bigger server or framework is just setting up all the parts that make it run. If your handler A depends on B and C which in turn depend on D, E and F each of which can have their own dependencies, writing and maintaining the correct boilerplate that sets them up along the critical path is a problem best left for the fricking computer to figure out itself.
- winkelwagen 5y agoI agree with the main sentiment from the article. Although I do think they are discussing Inversion of control more-so than dependency injection. One of my first languages was .net and I was never able to really understand DI in that context that well. Actually using javascript and ducktyping made me understand what it actually was. I remember a .net job interview where I had to write a micro-service and opted to construct the dependency graph in the main function initialising "all" the classes there. Instead of discussing the pro's and con's of that approach they berated me for not using a DI framework (No I did not land that job, but in hindsight it was the most expensive job interview I've ever had. The room was filled with 8 developers going over my code). The main thing the article glosses over is state. something people with a functional background hide from. But if you look at something like the httpclient in .net. I think it took the .net world like 10 years to start using the httpclient properly. Scope and lifetime of those kind of objects are important. managing connection pools, retry state, throttling or the incoming http request. DI does make that kind of thing easieR (I'm not saying it makes it better) Look at clojure's component(https://github.com/stuartsierra/component https://github.com/stuartsierra/component), I'm not a clojure expert by far. But it is kinda DI/IOC in a functional language. In closing we can agree that it is underused in the right places and overused in the wrong ones.
- csmpltn 5y ago> "If you need to add anything else, it’s right here. No registration, no XML files. Just code. Your code." If it's your code, and yours alone, just do whatever. Simple and terse works great for small code-bases, developed by small teams with like-minded people that have similar tastes and backgrounds. Often, such small groups of people communicate so well with eachother that they also share an excellent common understanding of what they build across the stack. At some point both the team and the code-base will start growing. You'll need strong, opinionated frameworks in-place for accomodating this growth. Without those guardrails in-place, there's nothing preventing the code-base from growing in a thousand different directions. Often times a project starts without those frameworks in-place, because it's so much easier to jump straight into business logic and not waste any time on pesky factories, interfaces and enums. By the time new people come in and start contributing - it's already too late and you've racked up too much code to start fresh - might as-well just keep piling on. A snowball effect. Dependency Injection as presented in the "hold on to your hats" section of the article may seem ugly and overcomplicated - but is one of those patterns used to hold large codebase to a unified, repeatable and persistent standard. "Fun" was never a part of the equation.
- polotics 5y agoWow nobody linked to Enterprise FizzBuzz yet! https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris... Essential!
- deepsun 5y ago> Garbage collector field day. Why? I'm not sure Garbage Collector cares if it's dynamically resolved or not. Also, not all DI is runtime. E.g. Dagger2 uses build-time injection, i.e. it just generates glue code, which is easy on JIT or AOT compilation.