Live data from Hacker News

Simplify your code: Functional core, imperative shell

testing.googleblog.com

191–200 of 219 posts

Re: Simplify your code: Functional core, imperative shell

#191
post #178
post #142

Earlier quoted context omitted.

"Words are allowed to have contextual meanings" Sure, but this discussion is about FCIS, that's the context, and the GP should consider that. " think I need a more precise meaning of "business logic" before I can answer this question" Well, some examples. A tax application - the tax calculation according to the law. A word processor - layouting and rendering engine. A video game - something that calculates the state…

> Take the tax calculation for example. That cannot be decomposed into pieces that are generic, and potentially reusable elsewhere. It's just a list of rules and exceptions that need to be implemented as stated. You can decompose it into "1st part of calculation" and "2nd part of calculation", but that's meaningless (unhelpful). (Similarly for the game example above, the rules only exist in the context of other rules…

Well you can claim that the core is the programming language, in which we write those tax rules, but that's not a very useful distinction IMHO (for how to write programs in the language).

Re: Simplify your code: Functional core, imperative shell

#192
post #186
post #145

Earlier quoted context omitted.

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

What are those mock pitfalls, which are avoided by freezegun which is a mock according even to them? IoC and Clocks solve the same problem. So what are the pitfalls of using those instead of this other mock?

Re: Simplify your code: Functional core, imperative shell

#193
post #105

Earlier quoted context omitted.

Making a distinction between pure and effectful functions doesnt require any kind of effect system though. Having a language where "func" defines a pure function and "proc" defines a procedure that can performed arbitrary side effects (as in any imperative language really) would still be really useful, I think

> Having a language where "func" defines a pure function and "proc" defines a procedure that can performed arbitrary side effects (as in any imperative language really) would still be really useful, I think Rust tried that in the early days, the problem is no-one can agree on exactly what side effects make a function non-pure. You pay almost all the costs of a full effect system (and even have to add an extra languag…

The definition I’ve used for my own projects is that anything that touches anything outside the function or in any way outlives the function is impure. It works pretty well for me. That is, no i/o, mutability of a function-local variable is okay but no touching other memory state (and that variable cannot outlive the return), the same function on the same input always produces the same output, and there’s no calling of impure code from within pure code. Notice this makes closures and currying impure unless done explicitly during function instantiation, making those things at least nominally part of the input syntactically. YMMV.

Re: Simplify your code: Functional core, imperative shell

#194
post #2

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

Found an example that is closer to how I learned about CQS:

https://hemath.dev/blog/command-query-separation/

Down at the bottom it gets into composition to make utility functions that compose several operations. Any OO system has to be careful not to expose methods that should have been private, so that’s not specific to CQS. It’s just that the opportunities to get it wrong increase and the consequences are higher.

Re: Simplify your code: Functional core, imperative shell

#195
post #192
post #186

Earlier quoted context omitted.

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.

What are those mock pitfalls, which are avoided by freezegun which is a mock according even to them? IoC and Clocks solve the same problem. So what are the pitfalls of using those instead of this other mock?

Applying it to the wrong module, a very common mistake in python due to how imports work.

Re: Simplify your code: Functional core, imperative shell

#196
post #191
post #178

Earlier quoted context omitted.

> Take the tax calculation for example. That cannot be decomposed into pieces that are generic, and potentially reusable elsewhere. It's just a list of rules and exceptions that need to be implemented as stated. You can decompose it into "1st part of calculation" and "2nd part of calculation", but that's meaningless (unhelpful). (Similarly for the game example above, the rules only exist in the context of other rules…

Well you can claim that the core is the programming language, in which we write those tax rules, but that's not a very useful distinction IMHO (for how to write programs in the language).

That's only the case where a usable generic core already exists. A great example where it didn't exist is python's "requests" library: https://requests.readthedocs.io/en/latest/

The example on the homepage is the "specific shell" - simple and easy to use, and by far the most common usage, but if you scroll down the table of contents on the API page (https://requests.readthedocs.io/en/latest/api/) you'll see sections titled "Lower-Level Classes" and "Lower-Lower-Level Classes" - that's the generic core, which the upper level is implemented in terms of.

Re: Simplify your code: Functional core, imperative shell

#197

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".

If you write one method poorly so you select too much and filter in a for loop, you just have a bad method you can fix.

If you pick and recommend a pattern where filtering should happen separately from retrieving, all your code will be bad

Give a man a fish / teach a man to fish, but bad.

Re: Simplify your code: Functional core, imperative shell

#198

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

Modern computers are fast enough that a lot of bad code won't outright break. That doesn't necessarily make it good code

I fixed a performance issue at some point where a missing index meant a scan of millions of rows every login. It worked, and could log in 3 people per second or so. It was still terrible code.

Re: Simplify your code: Functional core, imperative shell

#199
post #190
post #146

Earlier quoted context omitted.

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.

Totally fair. As I said up thread, I am comfortable saying I am imagining bad examples here. Would be interested in reading good examples. Though I couldn't find any of the bad ideas I had in my mind, I also didn't find any good ones on a quick search.

Re: Simplify your code: Functional core, imperative shell

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

>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.
Post reply on HN