Go func: Reducing cognitive load
antonlindstrom.com
antonlindstrom.com
Reducing cognitive load is an excellent goal, but this seems more about distrusting that some other code does what it is meant to do. Pi is pi. If you're worried about it, command-click into it, or whatever the equivalent is in your tools. Or more generally: it's completely fine to be reading something at an abstract or symbolic level and use some mechanism to drill in when desired. The idea that you wouldn't have higher and lower levels doesn't seem right to me, and I don't think there's any way not having abstractions would lead to more readable code. (Now, it's certainly true that not all abstractions are good, and that vaguely defined and haphazardly layered abstractions make understanding code difficult but that's a whole different question.)
For readability, personally, lemme have some spaces between those tokens.
If you need to pass the value of PI because you somehow do not trust it's origin, how on earth can you trust the exported function named "circumference" to calculate the actual circumference? Also, what if the rule of physics and mathematics change in future and we need to alter the definition of circumference? Maybe we need to pass a function that does the actual multiplication too?
Can anyone explain the argument in another way for me?
I wonder what a marriage of Go's light touch syntax, the golden nail of go/select (but generalized to include IO), and a proper type system would look like.
Go's const is closest to C's, which is ... terrible. const-correctness is one of the things where C++ offers a huge improvement over C.
At work, I use C# from time to time, which has two types of const: const as in compile-time-constant, along with all the restrictions that implies. And readonly which qualifies a variable as "once it has been initialized, it may not be changed".
To be fair, I have not run into major problems for a lack of it, but then again I mostly use Go for toy projects in my free time. Were I to use Go in production code, I would sleep a lot better if I could declare data as immutable.
To reduce cognitive load for me, I should be able to treat the function/method as a black box. Care about what it receives, care about what it returns, don't care about how it goes about accomplishing it.
>When I read the code above, my first question is: How is config.Pi defined?
That line really hammers home the difference. I trust the previous author, the test suite and ultimately, the project. Programmers seem to fall into two camps. Those that need to know details to understand a method and those that need to know the contract to understand a method.
But what happens when I'm debugging a problem in code I may or may not have written myself? Or when I'm debugging code that I wrote so long ago that I no longer remember all the details. That's when I need to be able to follow the code in the function I'm consuming. You are right that when you are consuming code it's good when you can treat that code as a black box. But that's not actually where I spend the most time as a developer. I spend far more time reading code I don't have full state about and trying to figure why something is broken.
Pi was probably a bad example here since we are conditioned to think of it as a constant. But there are similar uses in code where a function is doing bad things and it's not clear why because you didn't realize the function had an undeclared input from global mutable state. I think that is what the author was trying to say in his post.
I couldn't agree more.
This was the massive reason I loved Go when I switched from Node. My cognitive load was massive in Node, because I could not trust a function just by looking at it. Did it return a value? Did it take a callback? Did it return a promise? etc. I always had to have the documentation up. Now with Go, not only is the function signature easily accessible from Code (due to the static nature of it), but most things return values. Async doesn't propagate in an almost sinister way, like it does in JS.
In fact, I'm switching back to JavaScript due to needing to write some ReactNative stuff, and I'm having PTSD of sorts. React & Redux are great, I love them, but they're designed quite synchronously. Async just piles on another layer of complexity, for such a simple task.
Anyway, I'm diverging a bit, apologies.
[0] https://flow.org
I agree with your first point (functions should abstract some things away), but I disagree here. I feel like this falls into a "let's just not do stupid things" category of stuff that we just can't achieve. If people didn't make mistakes, we wouldn't need tests or monitoring.
I trust people but that doesn't mean I assume everything they do will be flawless. What if you're working on this project and you're debugging an issue where the function's not working right?
Or, what if you want to change how the function works? Pi is admittedly a poor example of this but the general concept is sound I think.
> Programmers seem to fall into two camps. Those that need to know details to understand a method and those that need to know the contract to understand a method.
A user of a software module shouldn't need to know the details of a function, but readability is much more for other engineers working on the codebase than it is for users of the codebase.
This is an interesting observation. I seem to fall into the former category, but I'm not sure if that's a side effect of working with proprietary codebases that have less than stellar interfaces.
When stuff ultimately goes sideways, I value being able to relatively quickly reason about the flow without firing up a debugger. Information hiding cuts both ways.
You shouldn't be considering the implementation details every time you want calculate the circumference. Even that pi is involved is WAY more information than the caller needs to know. And if you are debugging you need to know where to look for the answer, not the answer itself.
There is only one correct value. Period.
Configuration is also an external contract since you don't want to break installations because you want to rename a variable. Therefore, you have to distinguish between argument parsing and the actual runtime configuration and it makes a lot of sense to have that in a single package since you can then test it properly.
Configuration is a complex issue for non-trivial projects. Managing this in one place allows to have one source of truth.
If altering the precision of pi is something that matters, then the function should take a parameter for the precision rather than the value of pi.
Another thing that helps quite a bit is very detailed explicit logging messages when something goes wrong. I can read the message and know exactly in the code where I need to fix the issue.
I like that idea, don't get me wrong. I think you are just using a bad example. If a package name is "config", I'd expect things in that package to be a reference point for developers if needed. But most of the time, "config" is just a place variable for something that will change when the config is set, not something the developer needs to add cognitive load for.
Now if it was something else and I wanted to make sure that the output of the function changes someday unexpectedly because something used inside the function has changed even when my inputs are the same, yes. That is now ideal in any language.
var DefaultPi float64 = 3.141592[1] https://en.wikipedia.org/wiki/Dependent_type#Formal_definiti...
More generally, this is the sort of thing that should be addressed through our tools. While it is useful to be able to write code in a basic text editor, we should usually be using more powerful tools, especially as source code is so amenable to analysis. Syntax and style are not the only way to make things better, and we should not be the cobblers whose children go shoeless.
Alan Kay made this point when the US DoD was looking to improve its software development (and picked Ada.)
I would much rather work with a codebase that uses DI than one that doesn't.
In practice, it seems languages like Java have libraries such as Guice and Dagger but in Go you end up manually wiring things up.
def circumference(r: float, *args, pi=3.14) -> float:
return 2 * pi * r* A "users" microservice in a microservice architecture might load relevant information and permissions about the current user. A web or API server that's directly handling a user request would call that microservice for more information.
* In response to some user action, a task is sent to a task queue to do follow-up processing, e.g. reindexing any changed data. The task queue system sends the task to some worker process that specifically is there to handle tasks.
* A daily cron job on a dedicated machine sends an email to everyone who signed up between three and four days ago.
It's possible that the right thing to do is make pi a function parameter, but it wouldn't really change the readability of this file. It would change the readability of the code where Circumference is called, maybe for the better, maybe for the worse.
const min=Math.min;
const max=Math.max;
I'm not fully convinced about that but the code is a (very little) little bit simplified. print("hello", func(msg string) { ... })
when signature is much larger it becomes very annoying. something like print("hello", Printer(msg) { ... })
would be perfect, but something something golang generics something? print("hello", myprinter)
https://play.golang.org/p/3sNOeVIhqiAre there any manifestly-typed languages that work that way, though? I'm having trouble thinking of one, though given the number out there I won't be surprised if there is.