Live data from Hacker News

Modern Functional Programming: The Onion Architecture

degoes.net

41–50 of 100 posts

Re: Modern Functional Programming: The Onion Architecture

#41
post #40

Earlier quoted context omitted.

I understand the value of referential transparency and how it makes "certain" things easy, but saying that it automatically makes testing functional code easy is a myth. Sometimes it does, sometimes it doesn't. If you want to stick to referential transparency, you can't use dependency injection: you have to pass all the parameters the function needs. None of these can be implicit or belong to a field on the class sin…

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.

Re: Modern Functional Programming: The Onion Architecture

#42

Earlier quoted context omitted.

When you do dependency injection well, you don't inject every object; that would be horrible/impossible. What you may have noticed is that there are two kinds of objects, ones that you inject and ones that you don't. The ones you don't are things like numbers, strings and maps/sets; things that you treat as values. The other objects do things, I tend to call them services. In order to do a straight-forward conversion…

I agree there is a dichotomy between objects you inject and objects you don't but I think your characterization is incorrect: what decides if an object needs to be injected is not tied to its type but to its role. Sometimes, I inject integers or strings or other primitive types. Other times, I pass them explicitly. The decision is made based on whether that object is a runtime object (i.e. decided by the user or some…

> Either way, this aspect is independent of the point I was making above and which is that functional code is not inherently easier to test than procedural code.

That's true, you can certainly just write procedural code in functional languages and there's no benefit. However, you also have the ability to structure code in a way that is testable and is actually more structured than the equivalent OOP style. By which I mean: the operations on the dependencies are more constrained (since they can't be replaced or duplicated, etc).

Re: Modern Functional Programming: The Onion Architecture

#43
post #25
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.

You can still do a lot with applicatives, like parsing context-free languages... and even parsing context-sensitive languages. https://byorgey.wordpress.com/2012/01/05/parsing-context-sen... But yeah. ApplicativeDo might be useful. You would be able to see the static applicative structure as far as the next monadic bind, which lets you do interesting types of optimization, e.g. Haxl. https://ghc.haskell.org/trac/ghc/…

The thing is we're used to thinking in monads, and used to thinking of certain constructs as equivalent, which ApplicativeDo would break. There's a real tension between being explicit about the performance - in which case many identities no longer hold - and not. I don't know what Haxl itself does, but Fetch (which claims to be a Scala port) is kind of unsatisfactory in that regard.

Re: Modern Functional Programming: The Onion Architecture

#44
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.

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 the behavior.

    -- This function will always return the same thing given the same input
    function1 :: Int -> String
    
    -- This function depends on reading some configuration which 
    -- needs to be provided upstream
    function2 :: Int -> Reader Configuration String
You don't need to pass parameters explicitly in each signature; indeed this is exactly what the reader monad obviates: the details of what is being read are not expressed inside the function (until the point that they they are actually used). This is hardly an onerous burden, in my opinion. And if typing `Reader X Y` is too annoying, you can just make a type alias.

Re: Modern Functional Programming: The Onion Architecture

#45

Earlier quoted context omitted.

> At the center of the application, semantics are encoded using the language of the domain model. Say pg/psql. > Beginning at the center, each layer is translated into one or more languages with lower-level semantics. ORM mapping into Java? > At the outermost layer of the application, the final language is that of the application’s environment — for example, the programming language’s standard library or foreign func…

> Beginning at the center, each layer is translated into one or more languages with lower-level semantics. ORM mapping into Java? An ORM would not have lower-level semantics would it?

Lower in this context does not mean 'weaker'. An ORM maps between the lower level relational semantics and higher level object semantics.

Re: Modern Functional Programming: The Onion Architecture

#46

Earlier quoted context omitted.

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.

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.

Re: Modern Functional Programming: The Onion Architecture

#48

I didn't understand a word, and I bet 99% of people who clicked the link didn't either.

And there's the rub. I don't doubt this stuff is useful, and I'd like to know more. But it seems really hard to bring this stuff "down from the mountain" in a way that makes it easier to understand and utilize for us mere mortals.

Re: Modern Functional Programming: The Onion Architecture

#49
post #19
post #14

Earlier quoted context omitted.

Yeah, the free monad structures involve lots of lambdas. It's not so much that information is "lost", more that you cannot see beyond the next lambda abstraction. From a DSL perspective, it's like you can only inspect the program so far as to know the next statement in the "do" block. To see what the next statement will be, you need to actually evaluate the current one. Instead of free monads, if you want very analyz…

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 looks a lot more like a fold. In that kind of case, i'd point at traversable.

    mapAccumL (\val accumulator -> val + accumulator) 0 [1,2,3,4,5]
which would gather up the sums, [1, 3, 6, 10, 15]

of course, accumulator can be as fancy as you want, hashmap, set, tree, or whatever. If you can formulate a dynamic programming solution, there's generally a way to stuff that into traversable.

This is sort of off the top of my head and my haskell is a bit rusty, so this probably won't compile. But i think it captures the essence.

Re: Modern Functional Programming: The Onion Architecture

#50
post #2

I'd not say this is a feature of functional programming. This is a feature of Object Oriented elements being included in fp languages. These are the same things we were going to happen when using Java and C++ years ago. You can even see similar graphics here: https://docs.oracle.com/javase/tutorial/java/concepts/object... I remember there was another in this tutorial that shared more with the image in this post. Alth…

The graphics are both indeed circular which makes them look alike, but the meaning is completely different.

In the OO graphic, it's representing a single cell organism, hence the circle. This is not the complete application. It's encapsulating the state of a single process, and only through externally interacting with the organism can the state be inspected & changed, all based on time.

The circles in the FP represent domain logic, the inner most circle represents your high level business logic. This is the complete application. Then translating your logic to the next lower level domain, until the physical hardware layer is reached and your program becomes something concrete and runnable. This layering resembles an onion, which is also a circle.

Post reply on HN