Live data from Hacker News

Simplify your code: Functional core, imperative shell

testing.googleblog.com

181–190 of 219 posts

Re: Simplify your code: Functional core, imperative shell

#181

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?

That is not something that’s necessary for all CQRS systems, but maybe is something you’ve heard for the subset that people call “Event Sourcing”? There it’s a design goal that the system only records events that are occurring, so there’s no domain level validation that can be done on the command path - the user pressed the button whether we like it or not, so to speak. Whether the event has the intended effect is worked out after the event is recorded.

But there’s nothing in the more general idea of “separate reads from writes” that mandates “no validation on writes”

Re: Simplify your code: Functional core, imperative shell

#182
post #14

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…

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

Took me a bit of scrolling to find this. I believe most of the other folks are functional devs or something. The 5 functions on a single line wouldn't pass the code review in most .net/java shops.

The rule I was raised with was: you write the code once and someone in the future (even your future self) reads it 100 times.

You win nothing by having it all smashed together like sardines in a tin. Make it work, make it efficient and make it readable.

Re: Simplify your code: Functional core, imperative shell

#183

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…

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…

If by "described here" you mean the article, yes, it is not about CQRS or CQS. I was responding to hinkley who was referencing CQS as defined by (or at least popularized by) Meyer in his Eiffel language and books on OO programming.

Re: Simplify your code: Functional core, imperative shell

#184
post #162

Earlier quoted context omitted.

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.

(author here) It's exactly this - I do regret using "db" a bit now after reading all of the comments here, as it's taken away focus from the main point. But yes, the post had to fit on a single page, and I needed to pick something that most engineers would be familiar with.

Kinda hints most people haven't used a good ORM, or if they have maybe just don't understand how it really works. Django looks similar to this and would have the same misunderstanding (User.objects.all()), except it actually returns a QuerySet object that would let getExpiredUsers() apply its own criteria and not actually run the query until something tries to read from the object. There's an example up above where someone shows SQLAlchemy doing the same thing.

Re: Simplify your code: Functional core, imperative shell

#185

This sounds to me like the old hexagonal architecture [1] [1] https://en.wikipedia.org/wiki/Hexagonal_architecture_(softwa...

Famously how Doom was written and maybe part of why it was ported to so many platforms.

Hex is kind of a PITA for ground up projects, but if you are doing something where you know multi-platform/cloud/device whatever is important it is cool.

Re: Simplify your code: Functional core, imperative shell

#186
post #145
post #89

Earlier quoted context omitted.

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.

> I think using Date.now() is perfectly ok. This is why we have tests which we need to update every 3 months, because somebody said this. This is of course, after a ton of research went into finding out why the heck our tests broke suddenly.

I would call those badly-written tests. The current date/time exists outside the system and ought to be acceptable for mocks, and in python we have things like freezegun that make it easy to control without the usual pitfalls of mocks.

Re: Simplify your code: Functional core, imperative shell

#187

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

Isn't this saying business layer should not be on the top?

Business layers should be accessible via an explicit interface/shape that is agnostic to the layers above it. So if the org decides to move from mailchimp to some other email provider the business logic can remain untouched and you just need to write some code mapping the new provider to the business logic's interface.

Maybe our visualizations are mixed up, but I always viewed things like cloud providers, libraries etc. as potentially short lived whereas the core logic could stick around forever.

Re: Simplify your code: Functional core, imperative shell

#188

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

The is exactly the way forward: encapsulation (the function), type safety, and dynamic/lazy query construction.

I'm building a new project, Typegres, on this same philosophy for the modern web stack (TypeScript/PostgreSQL).

We can take your example a step further and blur the lines between database columns and computed business logic, building the "functional core" right in the model:

  // This method compiles directly to a SQL expression
  class User extends db.User {
    isExpired() {
    return this.expiresAt.lt(now());
    }
  }

  const expired = await User.where((u) => u.isExpired());
Here's the playground if that looks interesting: https://typegres.com/play/

Re: Simplify your code: Functional core, imperative shell

#189
post #168

Earlier quoted context omitted.

> 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. Another factor, and perhaps the key factor, is that contrary to OP's extraordinary claim there is no such thing as objectively good code, or one single and true way of writing good code. The crispest definition of "good code" is that it's not obviously bad code from a spe…

> 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, the underlying ideas are not strongly married to object orientation and they fit quite nicely in a functional paradigm.

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

The criticism on anemic domain models, which are elevated to the status of anti-pattern, is more than enough to reject any claim on how functional programming is compatible with DDD.

And that's perfectly fine. Not being DDD is not a flaw or a problem. It just means it's something other than DDD.

But the point that this proves is that there is no one true way of producing "good code". There is no single recipe. Anyone who makes this sort of claim is either both very naive and clueless, or is invested in enforcing personal tastes and opinions as laws of nature.

Re: Simplify your code: Functional core, imperative shell

#190
post #146
post #71

Earlier quoted context omitted.

You would deal with this problem in the same way you would with, say, in a REST API. If the transaction object is serializable you can just store it in a DB, for example. If it's some C++ pointer from some 3rd-party library that you can't really serialize and gotta keep open, you gotta keep it in memory and manage its lifetime explicitly, be it a REST web server, in Haskell or in a C++ app.

Right, I think a better way of stating my main assertion here is that you have to be able to partially work in a transaction. If your "shell" pretends that you can always complete the full transaction, either successfully or with a failure, then it is a brittle shell. Sometimes, you can simply make progress on an presumed open transaction.

It doesn’t have to pretend anything you don’t want. If you don’t want this kind of problem/failure possibility, then you have to encode those states in the type system. Functional programming can do the same things you can do in imperative, but you gotta make it when it’s not there. Just like in any other paradigm.
Post reply on HN