Live data from Hacker News

Simplify your code: Functional core, imperative shell

testing.googleblog.com

211–219 of 219 posts

Re: Simplify your code: Functional core, imperative shell

#211
post #210
post #147

Earlier quoted context omitted.

I'd go a little further, though? Transactions have to be able to fail at the very last step. That could just be the "commit" stage. Everything up to that point could have been perfectly fine, but at commit it fails. More, the time of execution of that commit could be fairly far removed from the time of starting the transaction. To that end, any style that tries to move those two time periods closer together in code i…

> I'd go a little further, though? Transactions have to be able to fail at the very last step. That could just be the "commit" stage. I don't know what you mean by going a little further. I said transactions are 'all or nothing', not 'all or nothing or 99%'.

I meant only that there is no styling that can fix it. Some transactions will flat out fail partially done and have to be kicked to a reconciliation process to fix.

I grant that for things that are purely informational, this is not necessarily as accurate. But as the things reasoned about in a program get larger and larger, transactions span longer and longer timelines. With "all or nothing" not being nearly as clear cut as it is in smaller examples.

My go to examples are things like vending machines. (Granted, that almost certainly just shows my bias for state machines all the way down.)

Re: Simplify your code: Functional core, imperative shell

#213

Earlier quoted context omitted.

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

I use Clojure all the time and I haven’t noticed the gripe you’ve got, but these are built in features of (somewhat) popular programming languages. Might not be for you but functional programming isn’t for everyone.

Re: Simplify your code: Functional core, imperative shell

#214
post #168

Earlier quoted context omitted.

> However, DDD has a strong object-oriented core The original 2003 DDD book is very 2003 in that it is mired in object orientation to the point of frequently referencing object databases¹ as a state-of-the-art storage layer. However, the underlying ideas are not strongly married to object orientation and they fit quite nicely in a functional paradigm. In fact, ideas like the entity/value object distinction are rather…

> The original 2003 DDD book is very 2003 in that it is mired in object orientation to the point of frequently referencing object databases¹ as a state-of-the-art storage layer. Irrelevant, as a) that's just your own personal and very subjective opinion, b) DDD is extensively documented as the one true way to write "good code", which means that by posting your comment you are unwittingly proving the point. > However,…

> Irrelevant, as a) that's just your own personal and very subjective opinion

Yes? And it's just your personal, subjective opinion that this is irrelevant. Most meaningful judgments are subjective. Get used to it.

> DDD is extensively documented as the one true way to write "good code"

Who said this? I've seen it described as a good way to write code, and as a way of avoiding problems that can crop up in other styles. But never as the only way to write good code.

> "Underlying ideas" means cherry-picking opinions that suit your fancy while ignoring those that don't.

No it doesn't. What? The only way I can make sense of what you're saying is if you're cynical toward the very concept of analyzing ideas, which is perhaps the most anti-intellectual stance I can imagine.

> The criticism on anemic domain models [...] is more than enough to reject any claim on how functional programming is compatible with DDD.

Why would an author's criticism of a certain style of OOP make a methodology they have written about incompatible with non-OOP paradigms? That's like saying that it's impossible to make strawberry ice cream because the person who invented ice cream hates strawberries.

> But the point that this proves is that there is no one true way of producing "good code".

There's no "one true way" to build a "good bridge," but that doesn't mean bridge design is all a matter of taste. Suspension bridges can carry a lot more than beam bridges; if you want to drive 18-wheelers across a wide river, a beam bridge will collapse, while a suspension bridge will probably be "good."

Re: Simplify your code: Functional core, imperative shell

#215
post #30

Earlier quoted context omitted.

In asynchronous environments, you may not be able to repeat the same query with the same result (unless you control a cache of results, which has its own issues). If some condition is determined by the command’s implementation that subsequent code is interested in (a condition that isn’t preventing the command from succeeding), it’s generally more robust for the command to return that information to the caller, who t…

I can’t decide if this really is the biggest problem with CQS. Certainly the wiki page claims it is, and it’s a reasonable argument. For some simpler cases you could dodge it by wrapping the function pairs/tuples in a lock. Database calls are a bit sketchy, because a transaction only “fixes” the problem if you ignore the elephant in the room which is reduced system parallelism by a measurable amount because even in a…

One of the big benefits of CQRS is that everything becomes asynchronous, and you can handle write-heavy data with stream processing. Implementing distributed locks across your stream processing system is... unpleasant to contemplate.

If you really need locks, that generally locks you out of this kind of architecture, which makes the CQRS value proposition much flimsier.

Re: Simplify your code: Functional core, imperative shell

#216

Earlier quoted context omitted.

So would you then have to do `getActualUsers(db.getUsers())` or `query(db.getUsers())`? Still smells like in such a case the developer avoids the complications of abstraction or OOP by making the user deal with it. That's bad API design due to putting ideology before practicality or ergonomics.

In linq (C#) IEnumerable getExpiredUsers(DbSet users) => users.Where(u => u.ExpiresAt Such simple logical expressions (called expression trees) get converted to SQL queries

And anyone who calls that method may find themselves dealing with the implementation details of Entity Framework and whatever db provider you're using because it's a leaky abstraction.

Re: Simplify your code: Functional core, imperative shell

#217
post #205

Earlier quoted context omitted.

>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

The context was "business", that kind of application is developed quite differently than, say, a cool little hobby terminal emulator or whatever. Even though the business currently doesn't have a need to e.g. support any other currency than USD and EUR, an experienced developer will clearly see that it is unlikely to stay that way for long, so doing some preliminary preparation for generalizing currencies may well wo…

>Even though the business currently doesn't have a need to e.g. support any other currency than USD and EUR

Your regular business requirements are way more complex than just a currency list. This is like trying to justify your point using an oversimplified example imo.

Re: Simplify your code: Functional core, imperative shell

#218
post #200

Earlier quoted context omitted.

>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

Everything "potentially leads" to adverse outcomes if not applied with due care and cognizance. That includes KISS and YAGNI. If you're looking for a principle you can apply in 100% of cases without consideration of context, I'm afraid you'll need to shop elsewhere.

> if not applied with due care and cognizance

That's the gotcha though. Everything applied with due care and cognizance works. This is not what is being discussed here. What the author suggests does lead to overengineering though. Think of stereotypical enterprise Java code if you need examples

Re: Simplify your code: Functional core, imperative shell

#219
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…

Glad to see that other people have come to a similar conclusion as I did. I don't understand why the industry hasn't been able to form much consensus around this.

You look at electronics and vehicles, the components inside are generic, many devices use exactly the same internal components... All the internal components are chosen specifically for their ability to handle a wide range of conditions (pressure, heat, electromagnetic interference) and also based on how broadly compatible they are with other components and tools... But somehow, when it comes to software, we treat it as if it's completely different.

There is a lot of value in using generic components which can handle a wide range of use cases. You want to avoid changing dependencies as much as possible because it takes time and effort to write robust code. You want a solid foundation which can solve a well defined (but not necessarily narrow) range of problems. Some modules can be used to solve many different problems, in completely different business domains but they may have a very simple, well defined interface. Think of a screw... Very simple, well-defined interface, can be applied to a huge range of use cases.

Post reply on HN