Live data from Hacker News

Simplify your code: Functional core, imperative shell

testing.googleblog.com

111–120 of 219 posts

Re: Simplify your code: Functional core, imperative shell

#111
post #30

Earlier quoted context omitted.

Command-Query Separation is the term for that. However, I find this statement odd: > having functions that do things without verifying preconditions are exploitable Why would you do this? The separation between commands and queries does not mean that executing a command must succeed. It can still fail. Put queries inside the commands (but do not return the query results, that's the job of the query itself) and branch…

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 an MVCC database transactions aren’t free. They’re just cheaper.

Caches always mess up computational models because they turn all reads into writes. Which makes things you could say with static analysis no longer true. I know a lot of tricks for making systems faster and I’ve hardly ever seen anyone apply most of them to systems after caching was introduced. It has one upside and dozens of downsides as bad or worse than this one.

Re: Simplify your code: Functional core, imperative shell

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

> 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 boundary, and so it can have more business-specific logic.

The definition question is, if you take an application, where is the business logic, is it in the "core" or not? I would say it is, literally what the application's main purpose is its "core". And "shell" is similarly well-defined. For example, UI without an engine implementing the actual logic is just a "shell".

I am not disputing GP's advice as you understand it, although I feel it is perhaps a little bit simplistic if not tautological ("prefer generic building blocks where possible"), and really muddles up what the core and shell is in the FCIS meaning.

Re: Simplify your code: Functional core, imperative shell

#113
post #89

Earlier quoted context omitted.

Not a single person in this thread commented on the use of Date.now() and similar - surely clock.now() - you never ever want to use global time in any code, how could you test it? clock in this case is a thing that was supplied to the class or function. It could just be a function: () -> Instant. (Setting a global mock clock is too evil, so don't suggest that!)

I was just referring to how pipes make these kinds of chained function calls more readable. But on your point, I think using Date.now() is perfectly ok.

What happens during a daylight savings adjustment?

Re: Simplify your code: Functional core, imperative shell

#114
post #112
post #108

Earlier quoted context omitted.

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…

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

> 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 through multiplication as 2 * 4, a program can be analyzed in multiple ways. If you view a program through the lens of FCIS, you expect to see a broad region of the program in which side-effects don't occur and a narrow region in which they do. If you view a program through the lens of GCSS, you expect to see broad parts of the program that solve general problems, and narrower regions in which those problems are instantiated in specific. The narrower regions are all "shell"-shaped, but that doesn't mean they are "the" shell. They have in common simply that they wrap a bulk of functionality to interface it to a larger context.

> At the core, things can be more integrated than at the boundary, and so it can have more business-specific rules.

I tend to disagree. Decomposition is a fundamental part of software engineering: we decompose a large problem into smaller ones, solve those, them compose those solutions into a solution to the large problem (c.f. Parnas' "On the criteria to be used in decomposing systems into modules"). It is often easier to solve a more general problem than the one originally given (Polya's principle). Combining the two yields GCSS.

A solution to each individual small problem can be construed as having its own generic core, and the principles used in composing sibling solutions constitute the specific shells that wrap them, allow them to interface, and together implement a solution to a higher-level problem.

Because there are multiple of these "cores", each solving a decomposed part of the top-level problem, it's hard for me to see how "At the core, things can be more integrated than at the boundary".

> The definition question is, if you take an application, where is the business logic, is it in the "core" or not?

I don't mean to be a sophist, but I think I need a more precise meaning of "business logic" before I can answer this question. In the process of solving successively smaller (and more general) problems, we abstract away from the totality of the business problem being solved, and address smaller and less-specific aspects of that problem. It may be that each subproblem's solution is architected as an individual instance of FCIS, as is often argued for microservice architectures; or that each subproblem is purely functional and only the top-level solution is wrapped in an imperative core; or anywhere in between. Needless to say, I think that choice is orthogonal.

As a result, I would say that the business logic itself has been factorized and distributed across the many subproblems and their solutions, and that indeed the "specific shell"s that are responsible for specializing solutions toward the specific case of the business need may necessarily include business logic. For instance, when automating a business process, one often needs to perform a complex step A before a complex step B. While both A and B might be independently solvable, orchestrating them together is still "business logic", because they need to be performed in order according to business needs.

(In all of this, perhaps you can see why I don't think the "core" and "shell" of FCIS should be identified with the "core" and "shell" of GCSS. Words are allowed to have contextual meanings!)

Re: Simplify your code: Functional core, imperative shell

#115

I like the general idea, but unless you're assuming some very clever language or even more clever ORM that fixes things implicitly, wouldn't email.bulkSend(generateReminderEmails(getExpiredUsers(db.getUsers(), fiveDaysFromNow))); get all users and then filter out the few that will expire in 5 days, on a code level? That doesn't sound like it would scale

Irrelevant. The "bad" code does it too. It's talking about something specific and not "let's fix all the problems with this code".

Re: Simplify your code: Functional core, imperative shell

#116

I like the general idea, but unless you're assuming some very clever language or even more clever ORM that fixes things implicitly, wouldn't email.bulkSend(generateReminderEmails(getExpiredUsers(db.getUsers(), fiveDaysFromNow))); get all users and then filter out the few that will expire in 5 days, on a code level? That doesn't sound like it would scale

I think it's just a contrived example. They probably wanted to show more than a single thing composing in a very short post given it's from their Toilet series.

Replace it with `getUsers(filters)` or even a specialised function, and it starts making more sense.

Re: Simplify your code: Functional core, imperative shell

#117
Interestingly, I have been harping on this for a while. Recently wrote a blog on how to separate business logic from infrastructure code and tie them together by composing functions together - https://rockyj-blogs.web.app/2025/10/25/result-monad.html

I also see that lately "code quality" is the least concern of most (even software product) companies, just ask AI to write code in a single file / module / class - then launch feature and fix if you have to. I could see that in a few years things will be extremely messy (but who can say).

Re: Simplify your code: Functional core, imperative shell

#118
post #94
post #16

This works right up to the point where you try to make the code to support opening transactions functional. :D Some things are flat out imperative in nature. Open/close/acquire/release all come to mind. Yes, the RAI pattern is nice. But it seems to imply the opposite? Functional shell over an imperative core. Indeed, the general idea of imperative assembly comes to mind as the ultimate "core" for most software. Edit:…

> This works right up to the point where you try to make the code to support opening transactions Indeed. It's all well and good to impart some kind of flavour into your code and call it functional, but transactions do not give a crap about style. A transaction needs to be able to 'back out' to fulfill 'all-or-nothing' semantics. Side effects are what make this impossible.

Surely transactions are a pretty good example of where functional core / imperative shell is a good guide. You really don't want to be doing arbitrary side effects inside your transaction because those can't be backed out. Check out STM in Haskell for a good example.

Re: Simplify your code: Functional core, imperative shell

#119
I never really got into Haskell in a big way, but one of the things I liked about the Haskell Wikibook [1] was how they presented Haskell code as being either in pure form or "do" form, and how the latter orchestrates the former, much as presented here. To a beginner like me not interested in monads etc, this was a very simple and explicit way of approaching coding in Haskell.

[1] https://en.wikibooks.org/wiki/Haskell

Re: Simplify your code: Functional core, imperative shell

#120

I like the general idea, but unless you're assuming some very clever language or even more clever ORM that fixes things implicitly, wouldn't email.bulkSend(generateReminderEmails(getExpiredUsers(db.getUsers(), fiveDaysFromNow))); get all users and then filter out the few that will expire in 5 days, on a code level? That doesn't sound like it would scale

Often the answer to "some very clever language" is a lazy functional language like Haskell, where it's quite common to express problems naturally on infinite or near-infinite lists (of numbers, users, whatever...) in this way - and the language's lazy evaluation semantics effectively turning it into an efficient streaming data pipeline of sorts.

But even if not, the example from the article is just hypothetical. `db.getUsers()` could be something that just retrieves rather efficient `[UserEmail, ExpiryTime]` pairs, and then you'd have to have a pretty enormous user base for it to not scale (a couple of million string/date pairs in memory should be no problem).

Post reply on HN