Live data from Hacker News

Modern Functional Programming: The Onion Architecture

degoes.net

81–90 of 100 posts

Re: Modern Functional Programming: The Onion Architecture

#81

Earlier quoted context omitted.

If `function2` needs to log something, then it should have a type signature which reflects that. Logging is a side-effect. Logging requires configuration to be passed in; it means having access to some file descriptor or other object to interact with, it could potentially fail to connect, or cause a computation to hang, or cause a service to trigger, or make a disk run out of space, etc. If a function wants to log so…

> If `function2` needs to log something, then it should have a type signature which reflects that. Yes if you value referential transparency. No if you value encapsulation. The fact that `function2` is logging stuff is an implementation detail that callers shouldn't care about. They should certainly not be forced to pass that function a logger. What if that function decides that on top of logging, it wants to store s…

> The fact that `function2` is logging stuff is an implementation detail that callers shouldn't care about.

On the contrary, I think it definitely matters. If a function is going to log something, I want to know about it. Those logs could cause me problems (e.g. polluting my stdout or attempting to write to a file they don't have permissions on), or I might want to control where those logs go, what the log level is, what the format is, et cetera. This is absolutely something I want to know about.

> What if that function decides that on top of logging, it wants to store stuff in a database. Should all callers suddenly find some kind of database to pass to that function too?

Yes, a thousand times yes. Why would I want a function to be storing stuff in a database without my knowledge? If a function is going to write to a database, it's all the more important that the caller is aware of that. How can I access whatever it stores? How do I know what database it's writing to? How can I be sure that database is properly initialized and/or torn down? How do I know whether the function is threadsafe? How do I know it's a secure connection? Et cetera.

If you want to write a function which does "arbitrary side effects", easy: just write all of your code in the IO monad.

    -- It reverses a string... and who knows what else!
    reversePlus :: String -> IO String
    reversePlus str = do
      putStrLn ("Hey, I'm reversing " ++ str)
      conn 
Of course, I don't recommend this...

Re: Modern Functional Programming: The Onion Architecture

#82

Earlier quoted context omitted.

There is a very small amount of boilerplate. And I would argue strongly that it's a good thing ; it indicates to the reader of the code that an object's behavior reads from some initialized value, or equivalently that its behavior depends on some initial value which remains fixed through the computation. The reader monad gives you a simple language to express this common pattern, as well as the ability to easily set…

What if `function2` needs to log something? Without dependency injection, you need to pass that logger to the function. With dependency injection, that logger is available without having to pollute the method signature with an implementation detail.

Not sure I agree with you on that, but monads handle your concern nicely. For example, I can write some code that does some database operations, and by parameterizing the code over the type of database actions, my code doesn't care if it's calling a "real" database action or a "fake" one for testing or whatever. That is, the code is completely agnostic as to the implementation details, but we still have full visibility and static checking when we actually run the database code, because we have to specify which database implementation we want to use. Boom, statically verified dependency injection.

Re: Modern Functional Programming: The Onion Architecture

#83

Earlier quoted context omitted.

> If `function2` needs to log something, then it should have a type signature which reflects that. Yes if you value referential transparency. No if you value encapsulation. The fact that `function2` is logging stuff is an implementation detail that callers shouldn't care about. They should certainly not be forced to pass that function a logger. What if that function decides that on top of logging, it wants to store s…

> The fact that `function2` is logging stuff is an implementation detail that callers shouldn't care about. On the contrary, I think it definitely matters. If a function is going to log something, I want to know about it. Those logs could cause me problems (e.g. polluting my stdout or attempting to write to a file they don't have permissions on), or I might want to control where those logs go, what the log level is,…

> > The fact that `function2` is logging stuff is an implementation detail that callers shouldn't care about.

> On the contrary, I think it definitely matters. If a function is going to log something, I want to know about it.

You are missing the forest for the trees.

First of all, why you'd care that a function you're calling is logging stuff is a bit beyond me but fine. Think of something else. Maybe the function is calling memcache, or storing stuff in the database, or sending a UDP packet to a message bus, or is querying the location service. Surely you can agree that there are things this function does that you don't care about if all you need is an Account given a user id, right?

These things you don't care about are called implementation details. Callers shouldn't know about them, therefore they shouldn't have to pass them in parameters.

That's what dependency injection (injection, not passing) does for you. It lets you call

    val account = getAccount(userId)
instead of

    val account = getAccount(userId, logger, memCache, db, messageBus)
The first example is using dependency injection and correctly hides the implementation details of `getAccount` while not being referentially transparent.

The second example is referentially transparent but exposes all kinds of private implementation details, making the callers' life very difficult, if not impossible (how are they supposed to come up with a messageBus when all they have is a user id?).

Re: Modern Functional Programming: The Onion Architecture

#84
post #49
post #19

Earlier quoted context omitted.

The problem is that applicative functors aren't powerful enough to allow computations that depend on previous results. I suspect what we want is something like free ArrowChoice, but I'm not aware of any work in that direction.

I might be completely missing your point (my apologies if i am). applicative is for specific elements of the structure. It's totally reasonable to want access to nearby values, but that requires being a little bit tricky. You could do something like a window of averages windows = map (take 5) tails $ [1,2,3,4,5,6] and then do your fmap across that fmap sum windows For access to prior values, you need something that l…

The trouble is that applicative doesn't let you use the result of one effect to compute the next effect. (Indeed sometimes there is no there there: Const is a valid applicative). Monads have bind, but that's a little too powerful to implement efficiently. Like I said, I suspect ArrowChoice or the like might be the missing intermediate construct.

Re: Modern Functional Programming: The Onion Architecture

#85
post #59
post #19

Earlier quoted context omitted.

The problem is that applicative functors aren't powerful enough to allow computations that depend on previous results. I suspect what we want is something like free ArrowChoice, but I'm not aware of any work in that direction.

AplicativeDo solves this a little bit through using Applicative until the results are necessary for the next instruction, then opting for Monad instances

Right, but at that point you're no longer Free. (Unless you declare the inefficient implementation and the efficient implementation to be equivalent, but if you do that then every refactor risks destroying your performance).

Re: Modern Functional Programming: The Onion Architecture

#86

Earlier quoted context omitted.

> The fact that `function2` is logging stuff is an implementation detail that callers shouldn't care about. On the contrary, I think it definitely matters. If a function is going to log something, I want to know about it. Those logs could cause me problems (e.g. polluting my stdout or attempting to write to a file they don't have permissions on), or I might want to control where those logs go, what the log level is,…

> > The fact that `function2` is logging stuff is an implementation detail that callers shouldn't care about. > On the contrary, I think it definitely matters. If a function is going to log something, I want to know about it. You are missing the forest for the trees. First of all, why you'd care that a function you're calling is logging stuff is a bit beyond me but fine. Think of something else. Maybe the function is…

I agree on the benefits of the first example but is that really dependency injection? Or is it just an abstraction layer?

eg. what if getAccount was hard coded to initialize all the other dependencies it needed on the fly for each call?

If that's still considered DI then it's a much looser term than I understood it to be.

Re: Modern Functional Programming: The Onion Architecture

#87
post #4

Earlier quoted context omitted.

The difference is that a "layer interpreter" in functional style is just a pure function that transforms values to values, so it's very easy to test in a very direct way. And we have laws that guarantee that composition works correctly (i.e. the interpretation of a composition is the composition of the interpretations). Whereas it's a lot fiddlier to confirm that an object that uses other objects behaves correctly (y…

Thank you for writing 'functional style' and not 'functional language'. While the latter is usually, and quite naturally, better at expressing the former, there is usually a lot to gain simply by adapting the style to the issue at hand, which not _necessarily_ implies changing languages. What follows is not necessarily of high value, I'm simply a working programmer since 20 odd years that's bit weary and sad that the…

The functional mindset views programming languages almost as families or toolkits: solutions should be written in the language of the domain, and successively interpreted to the language of the machine. Thus you don't change language to adapt to a different domain; rather you write a new domain sublanguage. The challenge is if anything to avoid going too far in the other direction; lisp in particular is notorious for being so flexible that no two people's lisp styles end up compatible.

I think there's a happy medium to be found. As my programming career has progressed I've become more and more in favour of Scala for everything - I think it gets pretty close to striking the right balance between the flexibility to express any given domain and the consistency to allow programmers to collaborate.

Re: Modern Functional Programming: The Onion Architecture

#88
post #37

Earlier quoted context omitted.

Yes, because some purely functional approaches cannot beat imperative ones when it comes to resource usage.

Could you give a concrete example?

I was disappointed to learn that Quicksort implemented functionally is almost always much much slower than if implemented procedurally, to the point that functional langs use other sorts such as mergesort. What makes it extra annoying is that Quicksort implemented functionally is so damn elegant!

http://stackoverflow.com/questions/7717691/why-is-the-minima...

Re: Modern Functional Programming: The Onion Architecture

#89

Gary Bernhardt describes a similar architecture using a "Functional Core, Imperative Shell" in his Boundaries talk[1]. "Purely functional code makes some things easier to understand: because values don't change, you can call functions and know that only their return value matters—they don't change anything outside themselves. But this makes many real-world applications difficult: how do you write to a database, or to…

This is one of the best talks I've ever seen, highly recommend anything by Gary Bernhardt

Re: Modern Functional Programming: The Onion Architecture

#90
post #40

Earlier quoted context omitted.

The reader monad removes 90% of the syntactic overhead of dependency passing. That's the point. If you want dependency injection as you've defined it, you can use (if we're talking about Haskell) typeclasses or, by extension, implicit parameters, to do dependency injection in the way you like. It's still much safer and easier to reason about than Java-style dynamic dependency injection.

Actually, `Reader` adds a lot of boiler plate that's not present with traditional @Inject injection: - All your functions now need to return a Reader[C,A] instead of just A - You need to pass all the parameters explicitly in each method signature as opposed to passing just the ones that don't need to be injected.

You might be interested in reflection/implicit configurations: https://hackage.haskell.org/package/reflection

This (ab)uses Haskell's type class mechanism to essentially implement dependency injection directly. The implementation looks a bit dirty, but this is a feature that more modern approaches to generic programming can handle natively (e.g., http://homepages.inf.ed.ac.uk/wadler/papers/implicits/implic... ).

In particular, there is nothing shady about the semantics of implicitly passing configuration values/dependencies. Your functions are still referentially transparent if you treat the implicit dependencies as additional parameters (which is what they are, no matter how you implement it).

Post reply on HN