frontpage.
newsnewestaskshowjobs

Made with ♥ by @iamnishanth

Open Source @Github

fp.

Open in hackernews

Simplify Your Code: Functional Core, Imperative Shell

https://testing.googleblog.com/2025/10/simplify-your-code-functional-core.html
97•reqo•2d ago

Comments

hinkley•2h ago
Bertrand Meyer suggested another way to consider this that ends up in a similar place.

For concerns of code complexity and verification, code that asks a question and code that acts on the answers should be separated. Asking can be done as pure code, and if done as such, only ever needs unit tests. The doing is the imperative part, and it requires much slower tests that are much more expensive to evolve with your changing requirements and system design.

The one place this advice falls down is security - having functions that do things without verifying preconditions are exploitable, and they are easy to accidentally expose to third party code through the addition of subsequent features, even if initially they are unreachable. Sun biffed this way a couple of times with Java.

But for non crosscutting concerns this advice can also be a step toward FC/IS, both in structuring the code and acclimating devs to the paradigm. Because you can start extracting pure code sections in place.

Jtsummers•2h ago
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 based on the results. After executing a command which may fail, you can follow it with a query to see if it succeeded and, if not, why not.

https://en.wikipedia.org/wiki/Command%E2%80%93query_separati...

jonahx•1h ago
> Why would you do this?

Performance and re-use are two possible reasons.

You may have a command sub-routine that is used by multiple higher-level commands, or even called multiple times within by a higher-level command. If the validation lives in the subroutine, that validation will be called multiple times, even when it only needs to be called once.

So you are forced to choose either efficiency or the security of colocating validation, which makes it impossible to call the sub-routine with unvalidated input.

Jtsummers•1h ago
Perhaps I was unclear, to add to my comment:

hinkley poses this as a fault in CQS, but CQS does not require your commands to always succeed. Command-Query Separation means your queries return values, but produce no effects, and your commands produce effects, but return no values. Nothing in that requires you to have a command which always succeeds or commands which don't make use of queries (queries cannot make use of commands, though). So a better question than what I originally posed:

My "Why would you do this?" is better expanded to: Why would you use CQS in a way that makes your system less secure (or safe or whatever) when CQS doesn't actually require that?

hinkley•1h ago
The example in the wiki page is far more rudimentary than the ones I encountered when I was shown this concept. Trivial, in fact.

CQS will rely on composition to do any If A Then B work, rather than entangling the two. Nothing forces composition except information hiding. So if you get your interface wrong someone can skip over a query that is meant to short circuit the command. The constraint system in Eiffel I don’t think is up to providing that sort of protection on its own (and the examples I was given very much assumed not). Elixir’s might end up better, but not by a transformative degree. And it remains to be seen how legible that code will be seen as by posterity.

layer8•19m ago
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 then can make use of it. But now the command is also a query.
rcleveng•2h ago
If your language supports generators, this works a lot better than making copies of the entire dataset too.
KlayLay•1h ago
You don't need your programming language to implement generators for you. You can implement them yourself.
akshayshah•1h ago
Sometimes, sure - but sometimes, passing around a fat wrapper around a DB cursor is worse, and the code would be better off paginating and materializing each page of data in memory. As usual, it depends.
hackthemack•2h ago
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 have kept pascals distinction between functions that only return one thing and procedures that go off and manipulate other things and do not give a return value.

https://docs.pascal65.org/en/latest/langref/funcproc/

POiNTx•2h ago
In Elixir this would be written as:

  db.getUsers()
  |> getExpiredUsers(Date.now())
  |> generateExpiryEmails()
  |> email.bulkSend()
I think Elixir hits the nail on the head when it comes to finding the right balance between functional and imperative style code.
montebicyclelo•1h ago

    bulk_send(
        generate_expiry_email(user) 
        for user in db.getUsers() 
        if is_expired(user, date.now())
    )
(...Just another flavour of syntax to look at)
Akronymus•1h ago
Not sure I like how the binding works for user in this example, but tbh, I don't really have any better idea.

Writing custom monad syntax is definitely quite a nice benefit of functional languages IMO.

fedlarm•1h ago
You could write the logic in a more straight forward, but less composable way, so that all the logic resides in one pure function. This way you can also keep the code to only loop over the users once.

email.sendBulk(generateExpiryEmails(db.getUsers(), Date.now()));

tadfisher•1h ago
That's pretty hardcore, like you want to restrict the runtime substitution of function calls with their result values? Even Haskell doesn't go that far.

Generally you'd distinguish which function call introduces the error with the function call stack, which would include the location of each function's call-site, so maybe the "low-effort" label is accurate. But I could see a benefit in immediately knowing which functions are "pure" and "impure" in terms of manipulating non-local state. I don't think it changes any runtime behavior whatsoever, really, unless your runtime schedules function calls on an async queue and relies on the order in code for some reason.

My verdict is, "IDK", but worth investigating!

hackthemack•1h ago
It has been so long since I worked on the code that had chaining functions and caused problems that I am not sure I can do justice to describing the problems.

I vaguely remember the problem was one function returned a very structured array dealing with regex matches. But there was something wrong with the regex where once in a blue moon, it returned something odd.

So, the chained functions did not error. It just did something weird.

Whenever weird problems would pop up, it was always passed to me. And when I looked at it, I said, well...

I am going to rewrite this chain into steps and debug each return. Then run through many different scenarios and that was how I figured out the regex was not quite correct.

sfn42•1h ago
I would have written each statement on its own line:

var users = db.getUsers();

var expiredUsers = getExpiredUsers(users, Date.now());

var expiryEmails = generateExpiryEmails(expiredUsers);

email.bulkSend(expiryEmails);

This is not only much easier to read, it's also easier to follow in a stack trace and it's easier to debug. IMO it's just flat out better unless you're code golfing.

I'd also combine the first two steps by creating a DB query that just gets expired users directly rather than fetching all users and filtering them in memory:

expiredUsers = db.getExpiredUsers(Date.now());

Now I'm probably mostly getting zero or a few users rather than thousands or millions.

hackthemack•1h ago
Yeah. I did not mention what I would do, but what you wrote is pretty much what I prefer. I guess nobody likes it these days because it is old procedural style.
HiPhish•59m ago
> email.bulkSend(generateExpiryEmails(getExpiredUsers(db.getUsers(), Date.now())));

What makes it hard to reason about is that your code is one-dimensional, you have functions like `getExpiredUsers` and `generateExpiryEmails` which could be expressed as composition of more general functions. Here is how I would have written it in JavaScript:

    const emails = db.getUsers()
        .filter(user => user.isExpired(Date.now()))  // Some property every user has
        .map(generateExpiryEmail);  // Maps a single user to a message

    email.bulkSend(emails);
The idea is that you have small but general functions, methods and properties and then use higher-order functions and methods to compose them on the fly. This makes the code two-dimensional. The outer dimension (`filter` and `map`) tells the reader what is done (take all users, pick out only some, then turn each one into something else) while the outer dimension tells you how it is done. Note that there is no function `getExpiredUsers` that receives all users, instead there is a simple and more general `isExpired` method which is combined with `filter` to get the same result.

In a functional language with pipes it could be written in an arguably even more elegant design:

    db.getUsers() |> filter(User.isExpired(Date.now()) |> map(generateExpiryEmail) |> email.bulkSend
I also like Python's generator expressions which can express `map` and `filter` as a single expression:

    email.bulk_send(generate_expiry_email(user) for user in db.get_users() if user.is_expired(Date.now())
hackthemack•30m ago
I guess I just never encounter code like this in the big enterprise code bases I have had to weed through.

Question. If you want to do one email for expired users and another for non expired users and another email for users that somehow have a date problem in their data....

Do you just do the const emails =

three different times?

In my coding world it looks a lot like doing a SELECT * ON users WHERE isExpired < Date.now

but in some cases you just grab it all, loop through it all, and do little switches to do different things based on different isExpired.

rahimnathwani•9m ago

  If you want to do one email for expired users and another for non expired users and another email for users that somehow have a date problem in their data....
Well, in that case you wouldn't want to pipe them all through generateExpiryEmail.

But perhaps you can write a more generic function like generateExpiryEmailOrWhatever that understands the user object and contains the logic for what type of email to draft. It might need to output some flag if, for a particular user, there is no need to send an email. Then you could add a filter before the final (send) step.

taeric•1h ago
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: I certainly think having some sort of affordance in place to indicate if you are in different sections is nice.

agentultra•1h ago
whispers in monads

It can be done "functionally" but doesn't necessarily have to be done in an FP paradigm to use this pattern.

There are other strategies to push resource handling to the edges of the program: pools, allocators, etc.

taeric•1h ago
Right, but even in those, you typically have the more imperative operations as the lower levels, no? Especially when you have things where the life cycle of what you are starting is longer than the life cycle of the code that you use to do it?

Consider your basic point of sale terminal. They get a payment token from your provider using the chip, but they don't resolve the transaction with your card/chip still inserted. I don't know any monad trick that would let that general flow appear in a static piece of the code?

zkmon•1h ago
I think it's just your way of looking at things.

What if a FCF (functional core function) calls another FCF which calls another FCF? Or do we do we rule out such calls?

Object Orientation is only a skin-deep thing and it boils down to functions with call stack. The functions, in turn, boil down to a sequenced list of statements with IF and GOTO here and there. All that boils boils down to machine instructions.

So, at function level, it's all a tree of calls all the way down. Not just two layers of crust and core.

skydhash•55m ago
Functional core usually means pure functional functions, aka the return value is know if the arguments is known, no side effects required. All the side effects is then pushed up the imperative shell.

You’ll find usually that side effect in imperative actions is usually tied to the dependencies (database, storage, ui, network connections). It can be quite easy to isolate those dependencies then.

It’s ok to have several layers of core. But usually, it’s quite easy to have the actual dependency tree with interfaces and have the implementation as leaves for each node. But the actual benefits is very easy testing and validation. Also fast feedback due to only unit tests is needed for your business logic.

bitwize•57m ago
I invented this pattern when I was working on a small ecommerce system (written in Scheme, yay!) in the early 2000s. It just became much easier to do all the pricing calculations, which were subject to market conditions and customer choices, if I broke it up into steps and verified each step as a side-effect-free, data-in-data-out function.

Of course by "invented" I mean that far smarter people than me probably invented it far earlier, kinda like how I "invented" intrusive linked lists in my mid-teens to manage the set of sprites for a game. The idea came from my head as the most natural solution to the problem. But it did happen well before the programming blogosphere started making the pattern popular.

socketcluster•27m ago
Even large companies are still grasping at straws when it comes to good code. Meanwhile there are articles I wrote years ago which explain clearly from first principles why the correct philosophy is "Generic core, specific shell."

I actually remember early in my career working for a small engineering/manufacturing prototyping firm which did its own software, there was a senior developer there who didn't speak very good English but he kept insisting that the "Business layer" should be on top. How right he was. I couldn't imagine how much wisdom and experience was packed in such simple, malformed sentences. Nothing else matters really. Functional vs imperative is a very minor point IMO, mostly a distraction.

benoitg•17m ago
I’d love to know more, do you have any links to your articles?
foofoo12•15m ago
> Even large companies are still grasping at straws when it comes to good code

Probably many reasons for this, but what I've seen often is that once the code base has been degraded, it's a slippery slope downhill after that.

Adding functionality often requires more hacks. The alternative is to fix the mess, but that's not part of the task at hand.

semiinfinitely•17m ago
this looks like a post from 2007 im shocked at the date

Easy RISC-V

https://dramforever.github.io/easyriscv/
75•todsacerdoti•1h ago•4 comments

Claude for Excel

https://www.claude.com/claude-for-excel
362•meetpateltech•6h ago•278 comments

JetKVM – Control any computer remotely

https://jetkvm.com/
212•elashri•5h ago•123 comments

Study finds growing social circles may fuel polarization

https://phys.org/news/2025-10-friends-division-social-circles-fuel.html
66•geox•3h ago•44 comments

10M people watched a YouTuber shim a lock; the lock company sued him – bad idea

https://arstechnica.com/tech-policy/2025/10/suing-a-popular-youtuber-who-shimmed-a-130-lock-what-...
552•Brajeshwar•9h ago•227 comments

Simplify Your Code: Functional Core, Imperative Shell

https://testing.googleblog.com/2025/10/simplify-your-code-functional-core.html
97•reqo•2d ago•31 comments

Pyrex catalog from from 1938 with hand-drawn lab glassware [pdf]

https://exhibitdb.cmog.org/opacimages/Images/Pyrex/Rakow_1000132877.pdf
235•speckx•7h ago•56 comments

The new calculus of AI-based coding

https://blog.joemag.dev/2025/10/the-new-calculus-of-ai-based-coding.html
40•todsacerdoti•5h ago•14 comments

MCP-Scanner – Scan MCP Servers for vulnerabilities

https://github.com/cisco-ai-defense/mcp-scanner
79•hsanthan•5h ago•22 comments

Why Busy Beaver hunters fear the Antihydra

https://benbrubaker.com/why-busy-beaver-hunters-fear-the-antihydra/
109•Bogdanp•5h ago•22 comments

Go beyond Goroutines: introducing the Reactive paradigm

https://samuelberthe.substack.com/p/go-beyond-goroutines-introducing
13•samber•1w ago•5 comments

TOON – Token Oriented Object Notation

https://github.com/johannschopplich/toon
48•royosherove•1d ago•18 comments

Rust cross-platform GPUI components

https://github.com/longbridge/gpui-component
436•xvilka•12h ago•182 comments

Tags to make HTML work like you expect

https://blog.jim-nielsen.com/2025/dont-forget-these-html-tags/
367•FromTheArchives•12h ago•196 comments

It's not always DNS

https://notes.pault.ag/its-not-always-dns/
23•todsacerdoti•5h ago•14 comments

Avoid 2:00 and 3:00 am cron jobs (2013)

https://www.endpointdev.com/blog/2013/04/avoid-200-and-300-am-cron-jobs/
223•pera•5h ago•207 comments

Sieve (YC X25) is hiring engineers to build video datasets for frontier AI

https://www.sievedata.com/
1•mvoodarla•5h ago

Solving regex crosswords with Z3

https://blog.nelhage.com/post/regex-crosswords-z3/
34•atilimcetin•6d ago•0 comments

Image Dithering: Eleven Algorithms and Source Code (2012)

https://tannerhelland.com/2012/12/28/dithering-eleven-algorithms-source-code.html
30•Bogdanp•3d ago•8 comments

When 'perfect' code fails

https://marma.dev/articles/2025/when-perfect-code-fails
15•vinhnx•8h ago•12 comments

PSF has withdrawn $1.5M proposal to US Government grant program

https://pyfound.blogspot.com/2025/10/NSF-funding-statement.html
373•lumpa•7h ago•307 comments

Show HN: Erdos – open-source, AI data science IDE

https://www.lotas.ai/erdos
38•jorgeoguerra•6h ago•21 comments

fnox, a secret manager that pairs well with mise

https://github.com/jdx/mise/discussions/6779
94•bpierre•5h ago•21 comments

Why Nigeria accepted GMOs

https://www.asimov.press/p/nigeria-crops
36•surprisetalk•4h ago•60 comments

The last European train that travels by sea

https://www.bbc.com/travel/article/20251024-the-last-european-train-that-travels-by-sea
123•1659447091•13h ago•119 comments

Should LLMs just treat text content as an image?

https://www.seangoedecke.com/text-tokens-as-image-tokens/
129•ingve•6d ago•79 comments

Let the little guys in: A context sharing runtime for the personalised web

https://arjun.md/little-guys
52•louisbarclay•5h ago•11 comments

Eight Million Copies of Moby-Dick (2014)

https://thevoltablog.wordpress.com/2014/01/27/nicolas-mugaveros-eight-million-copies-of-moby-dick...
26•awalias•4d ago•10 comments

Artificial Writing and Automated Detection [pdf]

https://www.nber.org/system/files/working_papers/w34223/w34223.pdf
33•mathattack•5h ago•19 comments

Show HN: Dlog – Journaling and AI coach that learns what drives well-being (Mac)

https://dlog.pro/
7•dr-j•5h ago•1 comments