Live data from Hacker News

Simplify your code: Functional core, imperative shell

testing.googleblog.com

171–180 of 219 posts

Re: Simplify your code: Functional core, imperative shell

#171
post #112

Earlier quoted context omitted.

> I think they are orthogonal at worst, and in agreement at best. I have considered them being orthogonal, but then the definition of the "shell" and "core" becomes problematic in this comparison. What you call shell in GCSS is not shell in FCIS at all, more like a boundary. Even there it is questionable whether boundary should be more specific than the core. At the core, things can be more integrated than at the bou…

One way to get some intuition with FCIS is to write some Haskell. Because Haskell programs pretty much have to be FCIS or they won't compile. How it plays out is... 1. A Haskell program executes side effects (known as `IO` in Haskell). The type of the `main` program is `IO ()`, meaning it does some IO and doesn't return a value - a program is not a function 2. A Haskell program (code with type `IO`) can call function…

I used to write lots of haskell before deciding it didn't meet my needs. However, the experience provided lots of long-term benefits, including a FCIS design mindset.

Recently, I did a major python refactoring project, converting a prototype/hack/experiment into a production-quality system. The prototype heavily intermixed IO and app logic, and I needed to write unit tests for the production system. Even with fixtures and mocking, unit testing was painful and laborious, so our test coverage was lousy.

Partitioning the core classes into pure and impure components was the big win. Unit testing became trivial and we caught lots of bugs in the original business logic. More recently, we changed the IO from files to a DB and having encapsulated the IO was also a win.

Re: Simplify your code: Functional core, imperative shell

#172

Earlier quoted context omitted.

I think CQRS is something different than what’s being described here. “Query” code in CQRS can still “do stuff”: call an external database, grab locks, audit trail recording etc. What’s being described here is something lower level, that you keep as much code as you can as a side-effect-free “pure functional core”. That pattern is useful both for the “command” and “query” side of a CQRS system, and is not the same th…

It's not about literally doing things (ie logging) it's about the intent. Query and ask are synonyms and represent the same idea in this context.

The fact that “query” and “ask” are synonyms in English does not make the patterns the same.

The key design goal in this thread was to create a pure functional core, which you can “ask” things of. That pattern is useful on both the command and query side of a CQRS system, and a different thing from splitting up mutating and reading operations as CQRS proposes

Maybe I misunderstand you though. Say you have a CQRS system that reads and writes to a database. Are you proposing the query side be implemented in pure side-effect-free functional code? How should the pure code make the network calls to the database?

Re: Simplify your code: Functional core, imperative shell

#173

Earlier quoted context omitted.

One way to get some intuition with FCIS is to write some Haskell. Because Haskell programs pretty much have to be FCIS or they won't compile. How it plays out is... 1. A Haskell program executes side effects (known as `IO` in Haskell). The type of the `main` program is `IO ()`, meaning it does some IO and doesn't return a value - a program is not a function 2. A Haskell program (code with type `IO`) can call function…

I used to write lots of haskell before deciding it didn't meet my needs. However, the experience provided lots of long-term benefits, including a FCIS design mindset. Recently, I did a major python refactoring project, converting a prototype/hack/experiment into a production-quality system. The prototype heavily intermixed IO and app logic, and I needed to write unit tests for the production system. Even with fixture…

May I ask you how you model your functional code in Python, in absence of Haskell's algebraic data types?

Re: Simplify your code: Functional core, imperative shell

#174
post #156

Earlier quoted context omitted.

> Meanwhile there are articles I wrote years ago which explain clearly from first principles why the correct philosophy is ... I think this is a very common mistake. You've spent years, maybe decades, writing code and now you want to magically transfer all that experience in a few succinct articles. But no advice that you give about "the correct philosophy" is going to instantly transfer enough knowledge to make all…

> But no advice that you give about "the correct philosophy" is going to instantly transfer enough knowledge to make all large companies write good code, if only they followed it. Good old "Programming as Theory Building". It's almost impossible to achieve this kind of transfer without already having the requisite lived experience. [0]: https://ratfactor.com/papers/naur1_theory_building.pdf

"The astronomer may speak to you of his understanding of space, but he cannot give you his understanding." ~ Kahlil Gibran ~

Re: Simplify your code: Functional core, imperative shell

#175

Earlier quoted context omitted.

I think CQRS is something different than what’s being described here. “Query” code in CQRS can still “do stuff”: call an external database, grab locks, audit trail recording etc. What’s being described here is something lower level, that you keep as much code as you can as a side-effect-free “pure functional core”. That pattern is useful both for the “command” and “query” side of a CQRS system, and is not the same th…

It's not about literally doing things (ie logging) it's about the intent. Query and ask are synonyms and represent the same idea in this context.

Then why the weird assertion that "command" code can only do things and not validate input?

Re: Simplify your code: Functional core, imperative shell

#176

While I largely agree with the philosophy, the example provided is not very practical. The code snippet `getExpiredUsers(db.getUsers(), Date.now())` is unlikely to occur in real-life scenarios. No one would retrieve all users and then filter them within the program. Instead, it should be `db.getExpiredUsers(Date.now())`. We should never be too extreme on anything, otherwise it would turn good into bad.

Depends on the programming environment: if it's a O(n) operation anyway, meaning you don't the times indexed, and your computation is colocated with the data and the db interface is using lazy sequences...

(Also, real-life systems of course do things inefficiently all the time)

Re: Simplify your code: Functional core, imperative shell

#177
post #108
post #85

Earlier quoted context omitted.

"Generic core, specific shell." Your advice is the opposite of "functional core, imperative shell". The FCIS principle has IS which is generic, to be simple, because it's usually hard to test (it deals with resources and external dependencies). So by being simple, it's more unit testable. On the other hand, FC is where the business logic lives, which can be complex and specific. The reason why you want that "function…

I disagree that these two pieces of advice are opposed. I think they are orthogonal at worst, and in agreement at best. "Functional core, imperative shell" (FCIS) is a matter of implementing individual software components that need to engage with side-effects --- that is, they have some impact on some external resources. Rather than threading representations of the external resources throughout the implementation, FC…

>GCSS tells us we shouldn't simply solve the one and only problem in front of us; we should use our eyes and ears and human brains to understand the context in which that problem exists

This violates KISS and YAGNI and potentially leads to overengineering code and excessive abstraction

Re: Simplify your code: Functional core, imperative shell

#178
post #142
post #114

Earlier quoted context omitted.

> then the definition of the "shell" and "core" becomes problematic in this comparison I agree -- if you're trying to make the words "shell" and "core" mean the same things between FCIS and GCSS, or identify the same parts of a program, then there will be problems. I think FCIS and GCSS are just two different ways of analyzing a program into pieces. Just as the number 8 can be seen through addition as 3 + 8 and throu…

"Words are allowed to have contextual meanings" Sure, but this discussion is about FCIS, that's the context, and the GP should consider that. " think I need a more precise meaning of "business logic" before I can answer this question" Well, some examples. A tax application - the tax calculation according to the law. A word processor - layouting and rendering engine. A video game - something that calculates the state…

> Take the tax calculation for example. That cannot be decomposed into pieces that are generic, and potentially reusable elsewhere. It's just a list of rules and exceptions that need to be implemented as stated. You can decompose it into "1st part of calculation" and "2nd part of calculation", but that's meaningless (unhelpful). (Similarly for the game example above, the rules only exist in the context of other rules.)

As someone who does this himself for taxes, you're looking only at the "specific shell" part. The generic core is the thing that does the math - spreadsheet, database, whatever. The tax rules are then imposed on top of that core.

Re: Simplify your code: Functional core, imperative shell

#179

I never liked encountering code that chains functions calls together like this email.bulkSend(generateExpiryEmails(getExpiredUsers(db.getUsers(), Date.now()))); Many times, it has confused my co-workers when an error creeps in in regards to where is the error happening and why? Of course, this could just be because I have always worked with low effort co-workers, hard to say. I have to wonder if programming should ha…

These chains become easy to read and understand with a small language feature like the pipe operator (elixir) or threading macro (clojure) that takes the output of one line and injects it into the left or rightmost function parameter. For example: (Elixir) "go " |> String.duplicate(3) # "go go go " |> String.upcase() # "GO GO GO " |> String.replace_suffix(" ", "!") # "GO GO GO!" (Clojure) ;; Nested function calls (ma…

If you get an exception, you might not know where it comes from unless you get a stack trace. Code looks nice but not practical imo

Re: Simplify your code: Functional core, imperative shell

#180

Earlier quoted context omitted.

It's not about literally doing things (ie logging) it's about the intent. Query and ask are synonyms and represent the same idea in this context.

Then why the weird assertion that "command" code can only do things and not validate input?

Commands can validate their input in CQS. What they don't do, in strict CQS, is return values. They can set state which can then be queried after execution which can let you retrieve an updated result or check to see if an error occurred during execution or whatever.
Post reply on HN