Live data from Hacker News

The compiler is your best friend

blog.daniel-beskin.com

51–60 of 149 posts

Re: The compiler is your best friend

#51

> A common pattern would be to separate pure business logic from data fetching/writing. So instead of intertwining database calls with computation, you split into three separate phases: fetch, compute, store (a tiny ETL). First fetch all the data you need from a database, then you pass it to a (pure) function that produces some output, then pass the output of the pure function to a store procedure. Does anyone have a…

This stuff is quite new to me as I’ve been learning F#, so take this with a pinch of salt. Some of the things you’d want are: - a function to produce a list of customers

- a function or two to retrieve the data, which would be passed into the customer list function. This allows the customer list function to be independent of the data retrieval. This is essentially functional dependency injection

- a function to take a list of customers and return a list of effects: things that should happen

- this is where I wave my hands as I’m not sure of the plumbing. But the final part is something that takes the list of effects and does something with them

With the above you have a core that is ignorant of where its inputs come from and how its effects are achieved - it’s very much a pure domain model, with the messy interfaces with the outside world kept at the edges

Re: The compiler is your best friend

#52

Earlier quoted context omitted.

Not really. A quick git blame (or alternative) will give you the required information about the validity of such proof.

You must have some git plugin I haven’t heard about.

Git blame will show the commit and the date for each line. It’s easy to verify if the snippet has changed since the comment. i use Emacs and it’s builtin vc package that color code each block.

Re: The compiler is your best friend

#53
post #33
post #11

Earlier quoted context omitted.

But if you spell that `assert(false)` instead of as a comment, the intent is equally clear, but the behavior when you're wrong is well-defined.

At least on iOS, asserts become no-ops on release builds

It really depends on the language you use. Personally I like the way rust does this:

- assert!() (always checked),

- debug_assert!() (only run in debug builds)

- unreachable!() (panics)

- unsafe unreachable_unchecked() (tells the compiler it can optimise assuming this is actually unreachable)

- if cfg!(debug_assertions) { … } (Turns into if(0){…} in release mode. There’s also a macro variant if you need debug code to be compiled out.)

This way you can decide on a case by case basis when your asserts are worth keeping in release mode.

And it’s worth noting, sometimes a well placed assert before the start of a loop can improve performance thanks to llvm.

Re: The compiler is your best friend

#54

> A common pattern would be to separate pure business logic from data fetching/writing. So instead of intertwining database calls with computation, you split into three separate phases: fetch, compute, store (a tiny ETL). First fetch all the data you need from a database, then you pass it to a (pure) function that produces some output, then pass the output of the pure function to a store procedure. Does anyone have a…

> there's no way to "fetch all the data" up front.

this is incorrect

I assume there's more nuance and complexity as for why it feels like there's no way. Probably involving larger design decisions that feel difficult to unwind. But data collection, decisions, and actions can all be separated without much difficulty with some intent to do so.

I would suggest caution, before implementating this directly: but imagine a subroutine that all it did was lock some database table, read the current list of pending top up charges required, issue the charge, update the row, and unlock the table. An entirely different subroutine wouldn't need to concern itself with anything other than data collection, and calculating deltas, it has no idea if a customer will be charged, all it does is calculate a reasonable amount. Something smart wouldn't run for deactivated/expiring accounts, but why does this need to be smart? It's not going to charge anything, it's just updating the price, that hypothetically might be used later based on data/logic that's irrelevant to the price calculation.

Once any complexity got involved, this is closer to how I would want to implement it, because this also gives you a clear transcript about which actions happened why. I would want to be able to inspect the metadata around each decision to make a charge.

Re: The compiler is your best friend

#55

Earlier quoted context omitted.

If such an error happens, that would be a compiler bug. Why? Because I usually do checks against the length of the array or have it done as part of the standard functions like `map`. I don't write such assumptions unless I'm really sure about the statements, and even then I don't.

> or have it done as part of the standard functions like `map`. Which are all well and good when they are applicable, which is not always 100% of the time. > Because I usually do checks against the length of the array And what do you have your code do if such "checks" fail? Throw an assertion error? Which is my whole point, I'm advocating in favor of sanity-check exceptions. Or does calling them "checks" instead of "…

A comment have no semantic value to the code. Having code that check for stuff is different from writing comments as they are executed by the machine. Not read by other humans.

Re: The compiler is your best friend

#56

> How many times did you leave a comment on some branch of code stating "this CANNOT happen" and thrown an exception? Did you ever find yourself surprised when eventually it did happen? I know I did, since then I at least add some logs even if I think I'm sure that it really cannot happen. I'm not sure what the author expects the program to do when there's an internal logic error that has no known cause and no defini…

> At some level, the simplest thing to do is to give up and crash if things are no longer sane.

The problem with this attitude (that many of my co-workers espouse) is that it can have serious consequences for both the user and your business.

- The user may have unsaved data - Your software may gain a reputation of being crash-prone

If a valid alternative is to halt normal operations and present an alert box to the user saying "internal error 573 occurred. please restart the app", then that is much preferred IMO.

Re: The compiler is your best friend

#57

Great read. C# has the concept of nullable reference types[1] which requires you to be explicit if a variable can be null and the compiler is aware of this. I would love to see a similar feature in languages like TypeScript and Go. [1]: https://learn.microsoft.com/en-us/dotnet/csharp/nullable-ref...

TypeScript has disctinct nullable and non-null types if you enable `strictNullChecks` or `strict` in TSConfig (https://www.typescriptlang.org/tsconfig/#strictNullChecks).

Re: The compiler is your best friend

#58

> How many times did you leave a comment on some branch of code stating "this CANNOT happen" and thrown an exception? Did you ever find yourself surprised when eventually it did happen? I know I did, since then I at least add some logs even if I think I'm sure that it really cannot happen. I'm not sure what the author expects the program to do when there's an internal logic error that has no known cause and no defini…

Ideally, if you can convince yourself something cannot happen, you can also convince the compiler, and get rid of the branch entirely by expressing the predicate as part of the type (or a function on the type, etc.) Language support for that varies. Rust is great, but not perfect. Typescript is surprisingly good in many cases. Enums and algebraic type systems are your friend. It'll never be 100% but it sure helps fil…

Not every piece of logic lends itself to being expressed in the type system.

Let's say you're implementing a sorting algorithm. After step X you can be certain that the values at locations A, B, and C are sorted such that A Perhaps some Haskeller will pipe up and tell me that any type system worth using can comfortably describe this PartiallySortedList. But most people have to use systems where encoding that in the type system would, at best, make the code significantly less expressive.

Re: The compiler is your best friend

#59
post #42

I'm not a fan of the recent trend in software development, started by the OOP craze but in the modern day largely driven by Rust advocates, of noun-based programming, where type hierarchies are the primary interface between the programmer and the code, rather than the data or the instructions. It's just so... dogmatic. Inexpressive. It ultimately feels to me like a barrier between intention and reality, another abstr…

The kind of noun-based programming you don’t like is great for large teams and large code bases where there is an inherent communication barrier based on the number of people involved. (N choose 2 = N*(N-1)/2 so it grows quadratically.) Type hierarchies need to be the primary interface between the programmers and the code because it communicates invariants on the data more precisely than words. It is dogmatic, becaus…

That sounds eerily similar to the "OOP is for large teams" defence which is simply not true.

On the contrary, this noun-based programming explodes with complexity on large teams. Yes, interfaces are obviously important, but when every single thing is its own type and you try to solve problems with the type system leading to a combinatoric explosion of types and their interactions, what do you think happens when you scale the team up?

Re: The compiler is your best friend

#60

I'm not a fan of the recent trend in software development, started by the OOP craze but in the modern day largely driven by Rust advocates, of noun-based programming, where type hierarchies are the primary interface between the programmer and the code, rather than the data or the instructions. It's just so... dogmatic. Inexpressive. It ultimately feels to me like a barrier between intention and reality, another abstr…

Agreed. It's often accompanied by the dogma "make invalid states unrepresentable" which sounds good until you start trying to encode into the type system foo.bar being 1-42 unless foo.baz is above 10, where now foo.bar can be -42-1 instead, but if foo.omfg is prefixed with "wtf" then foo.baz needs to be above 20 for its modifiers to kick in.

Yeah good luck doing that in the type system in a way that is maintainable, open to modification, an scales with complexity.

Post reply on HN