Live data from Hacker News

Simplify your code: Functional core, imperative shell

testing.googleblog.com

81–90 of 219 posts

Re: Simplify your code: Functional core, imperative shell

#81

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…

These are great and succinct, yours and your teammate’s. I still find myself debating this internally, but one objective metric is how smoothly my longer PTOs go: The only times I haven’t received a single emergency call were when I left teammates a a large and extremely specific set of shell scripts and/or executables that do exactly one thing. No configs, no args/opts (or ridiculously minimal), each named something…

That is pretty convincing advice!! Maybe I’ll try writing more specific scripts.

Re: Simplify your code: Functional core, imperative shell

#82
I like the idea but the example doesn't make much sense.

In what application would you load all users into memory from database and then filter them with TypeScript functions? And that is the problem with the otherwise sound idea "Functional core, imperative shell". The shell penetrates the core.

Maybe some filters don't match the way database is laid out, what if you have a lot of users, how do you deal with email batching and error handing?

So you have to write the functional core with the side effect context in mind, for example using query builder or DSL that matches the database conventions. Then weave it with the intricacies of your email sender logic, maybe you want iterator over the right size batches of emails to send at once, can it send multiple batches in parallel?

Re: Simplify your code: Functional core, imperative shell

#84

I like the idea but the example doesn't make much sense. In what application would you load all users into memory from database and then filter them with TypeScript functions? And that is the problem with the otherwise sound idea "Functional core, imperative shell". The shell penetrates the core. Maybe some filters don't match the way database is laid out, what if you have a lot of users, how do you deal with email b…

> In what application would you load all users into memory from database and then filter them with TypeScript functions?

You’d be surprised! I have worked on a legacy PHP service which did something very similar

Re: Simplify your code: Functional core, imperative shell

#85

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…

"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 "functional" (really just another name for "composable from small blocks") is because it can be tested for validity without external dependencies.

So the IS shields you from technicalities of external dependencies, like what kind of quirks your DB has, or are we sending data over network or writing to the file, or does the user inputs comands in spanish or english, do you display the green square or blue triangle to indicate the report is ready, etc.

On the other hand, FC deals with the actual business logic (what you want to do), which can be both generic and specific. These are just different types of building blocks (we call them functions) living in the FC.

FCIS is exemplified by user-shell interaction. The user (FC) dictates the commands and interprets the output, according to her "business needs". While the shell (IS) simply runs the commands, without any questions of their purpose. It's not the job of IS to verify or handle user errors caused by wrong commands.

But the user doesn't do stuff on her own; you could take her to a pub and she would tell you the same sequence of commands when facing the same situation. In that sense, the user is "functional" - independent on the actual state of the computer system, like the return value of a mathematical function is only dependent on the arguments.

Another example is MVC, where M is the FC and VC is the IS. Although it's not always exactly like that, for variety of reasons.

You can think of IS as a translator to a different language, understood by "the other systems", while the FC is there to supply what is actually being communicated.

Re: Simplify your code: Functional core, imperative shell

#86
post #6

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…

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.

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!)

Re: Simplify your code: Functional core, imperative shell

#87
The MirageOS project [0] is a great collection of functionality pure OCaml libraries that are useful outside of unikernels. I've used the DNS library with an effectful layer for various nameserver experiments [1].

[0] https://mirage.io/

[1] https://ryan.freumh.org/eon.html

Re: Simplify your code: Functional core, imperative shell

#89
post #6

Earlier quoted context omitted.

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.

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.

Re: Simplify your code: Functional core, imperative shell

#90
post #14

Earlier quoted context omitted.

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…

Glad to see this. This style seems like it’s out of vogue now, but I find it much, much easier to reason about.

I agree because it reads as it will process in the direction I normally read. But I do think one of the benefits of the function approach is that the scope isn't cluttered with staging variables.

For these reasons one of the things I like to do in Swift is set up a function called ƒ that takes a single closure parameter. This is super minimal because Swift doesn't require parenthesis for the trailing closure. It allows me to do the above inline without cluttering the scope while also not increasing the amount of redirection using discrete function declarations would cause.

The above then just looks like this:

  ƒ { 
    var users = db.getUsers();
    var expiredUsers = getExpiredUsers(users, Date.now());
    var expiryEmails = generateExpiryEmails(expiredUsers);\
    email.bulkSend(expiryEmails);
  }
Post reply on HN