8 ms·
Go func: Reducing cognitive load
- keymone 9y agolast time i tried using funcs a lot it turned out that it's not possible to typedef a function signature and use it to pass funcs as arguments which is very unfortunate. is this by design or is it getting fixed?
- insertnickname 9y agoYou mean like this? https://play.golang.org/p/W9PYFB6LUG https://play.golang.org/p/W9PYFB6LUG
- keymone 9y agonotice that you had to repeat function signature on line 14 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?
- grey-area 9y agoNot sure what this has to do with generics, Go requires types for function arguments. If your anonymous function gets clumsy and too big in place, just use a named function not an anonymous one: print("hello", myprinter) https://play.golang.org/p/3sNOeVIhqi https://play.golang.org/p/3sNOeVIhqi
- keymone 9y agopoint is typedefs aren't reusable at anonymous function declarations. imo that sucks. that's all i wanted to say.
- davidarkemp2 9y agoYou could always declare the method on the Printer type... https://play.golang.org/p/4MG8kgGEGM https://play.golang.org/p/4MG8kgGEGM
- keymone 9y agowhere's the anonymous function with reuse of typedef in there?
- jerf 9y agoNo, it's just a syntax issue, not a generics issue. Are 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.
- keymone 9y agomaybe there are none, but wouldn't that be really nice?
- weberc2 9y agoThis isn't particular to Go, and you should just pass in any variables that you intend to change, rather than depending on a global mutable variable for your state (especially in a concurrency-heavy language like Go).
- laumars 9y agoGenerally yes. But pi is one of those occasions where it only needs to be a constant somewhere and then forgotten about. The last thing you need is developers accidentally mistyping the value of pi causing the output calculations to be wrong. 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.
- weberc2 9y agoPi is not a variable. I was very careful with my wording to say things like "variable", "expect to change", and "mutable state" so my statement would apply generally. :)
- laumars 9y agoYour wording might have been careful but the author did use pi as a variable in his example and discussed wanting the ability to change it. Since you were essentially agreeing with her/his point, it's forgivable if one then assumes you were also agreeing with his example as well. Hence my counterexample (and hence why I opened with "Generally yes" to signify that I don't disagree with your point but the example provided needs tweaking)
- weberc2 9y agoI'm not sure I was agreeing with his point, since I only consider one of his configurations 'valid', and even then only insofar as Pi is a variable (which it isn't). My original statement holds generally and specifically--pass in the things you may want to vary; don't depend on global mutable state.
- 9y ago
- codnee 9y agoYou have a small syntax error on your third example, it should be var DefaultPi float64 = 3.141592
- aant 9y agoThank you! I'll update it as soon as I can.
- grey-area 9y agoIf you're updating it anyway, perhaps consider not using Pi as a config value in the first place.
- white-flame 9y agoNot to mention that there aren't enough digits there to fill the precision of a 64-bit float.
- deleted 9y ago[deleted]
- alecbenzer 9y agoI was so confused with `type DefaultPi float64 = 3.141592`... I thought Go had started supporting defining custom default values for types and then interpreting `type` as its default value when used in expressions....
- wangii 9y agohow about `const DefaultPi = 3.14159265`
- curun1r 9y agoThe funny thing is that there are languages that allow constants into the type system. The feature has a few names, but one of them is pi-types (well, π-types) [1]. Go even uses them for its array types. However like generics, it's not a feature of the type system that can be used outside the built-in types. [1] https://en.wikipedia.org/wiki/Dependent_type#Formal_definition https://en.wikipedia.org/wiki/Dependent_type#Formal_definiti...
- nahname 9y ago>To reduce cognitive load, I want to be able to read a function/method without having to think about where some variable comes from. 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.
- notheguyouthink 9y ago> 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. 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.
- TheAceOfHearts 9y agoYou might wanna give Flow [0] a try. I think the default ReactNative tools include support out of the box. [0] https://flow.org https://flow.org
- thealfreds 9y agoTypescript and as mentioned below Flow are good solutions to this problem. Add appropriate linting to force types to be defined on methods and you just hover over (or however you prefer to open a method definition window) and get a breakdown of types that you can click to go to if you need further definition.
- jmull 9y agoI hate this example (sorry). 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.
- kierenj 9y agoI also find it difficult to understand the example. Why would "pi" be a configurable value? And if something /is/ configurable, the whole point is that the consuming code doesn't care where it's set. Can anyone explain the argument in another way for me?
- eknkc 9y agoThis does not make any sense to me either. 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?
- kibwen 9y agoYou've just reinvented Objectivist-C! http://fdiv.net/2012/04/01/objectivist-c http://fdiv.net/2012/04/01/objectivist-c
- paulddraper 9y agoCross-universe compatible code is the next big thing.
- zappo2938 9y agoOff topic... I tried Google search for 'backend workers' and not much information came up. What are backend workers? Where are they hosted and how do they integrate with APIs or triggered periodically? Or, do they just listen for events?
- alangpierce 9y ago"Backend worker" is a pretty generic term, and I think in this context it just means any server that isn't a web server or API server. Examples: * 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.
- zappo2938 9y agoIt seems that people are using different phrases and words for the similar things, i.e. message broker, message queue, message pipline, message bus, microsevice, service, backend worker, task queue.
- petters 9y agoThis is a really bad example. Allowing constants like π to be passed to a function is the opposite of what you should do. There is only one correct value. Period.
- gkya 9y agoYou can imagine a case where the function has to verify an answer to a problem where the pi is assumed to be 3. Say e.g. writing a maths quiz, an online course, etc.
- jalk 9y agoWhile I really like readability and having things close at hand, I really hate making a change to some constant only to discover later that it had to be updated in a gazillion different places with slightly different names
- laurent123456 9y agoPassing arguments to a function instead of using global state is generally a good idea, but that's a very poor example. Having to pass constants to a function would increase the cognitive load rather than reduce it.
- mannykannot 9y agoThere is a parallel in C++, where one school of thought says you should not use using directives, but instead fully qualify identifiers from outside the current namespace, to make it clear that they are external dependencies. 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.)
- Cshelton 9y agoSo basically you're saying to use pure functions. Something that you see in a functional language, where given the same input, you can expect the same output every time. 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.
- dkarl 9y agoI don't like this example because I'm not sure what kind of realistic situation it's meant to represent. If config.Pi is really a config value, a value set at runtime by whatever configuration method is used by the application (environment variables, config service, whatever) then config.Pi is a very clear way to communicate that fact. If you want to communicate something more concrete in the code about how config.Pi is set and what its default value is, the right place for that information is where it's defined, not in every file where it's used. 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.
- amenghra 9y agoThis example isn't great (PI is too trivial), but it seems to touch on the concept of dependency injection. 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.
- Orangeair 9y agoLike everyone else here, I don't think this is a very good example. Even if it were, other languages can do a better job of accomplishing the same goal. For example in Python: def circumference(r: float, *args, pi=3.14) -> float: return 2 * pi * r
- mabynogy 9y agoI did this kind of thing today in JS: 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.
- kmicklas 9y agoThis has nothing to do with Go, it's just basic software engineering. It reminds me how ignorant the Go community is of the decades of research in programming language and software engineering theory.
- tmaly 9y agoFor me choosing great names for packages, functions, and variables is what reduces the most cognitive load. I continue to work on my food side project BestFoodNearMe.com for just a few hours a week. It is critical that I squeeze every ounce of productivity out of this time. 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.
- magiconair 9y agoI think this is example is too simple and misses the point of a config package. If you have config options being read from several different sources (files, args, env) and need to initialize subsystems which need more than one or two parameters (think server with several timeouts, paths, handlers) then you want to pass this as a struct and you need to manage that struct somewhere. 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.