I don’t find your “seasoned developer” version ugly at all. It just looks more mature and relaxed. It also has the benefits that you can actually do error handling and have space to add comments.
Maybe people don’t like it because of the repetition of “data =“ but in fact you could use descriptive new variable names making the code even more readable (auto documenting).
I’ve always felt method chaining to look “cramped”, if that’s the right word. Like a person drawing on paper but only using the upper left corner. However, this surely is also a matter of preference or what your used to.
is a hell to read and understand later imo. You have to read a lot of intermediate variables that do not matter in anything else in the code after you set it up, but you do not know in advance necessarily which matter and which don't unless you read and understand all of it. Also, it pollutes your workspace with too much stuff, so while this makes it easier to debug, it makes it also harder to read some time after. Moreover becomes even more crumpy if you need to repeat code. You probably need to define a function block then, which moves the crumpiness there.
What I do now is starting defining the transformation in each step as a pure function, and chain them after once everything works, plus enclosing it into an error handler so that I depend on breakpoint debugging less.
There is certainly a trade off, but as a codebase grows larger and deals with more cases where the same code needs to be applied, the benefits of a concise yet expressive notation shows.
Code in this "named-pipeline" style is already self-documenting: using the same variable name makes it clear that we are dealing with a pipeline/chain. Using more descriptive names for the intermediate steps hides this, making each line more readable (and even then you're likely to end up with `dataStripped = data.map(&:strip)`) at the cost of making the block as a whole less readable.
> Maybe people don’t like it because of the repetition of “data =“
Eh, at first glance it looks "amateurish" due to all the repeated stuff. Chaining explicitly eliminates redundant operations - a more minimal representation of data flow - so it looks more "professional". But I also know better than to act on that impulse. ;)
That said, it really depends on the language at play. Some will compile all the repetition of `data =` away such that the variable's memory isn't re-written until after the last operation in that list; it'll hang out in a register or on the stack somewhere. Others will run the code exactly as written, bouncing data between the heap, stack, and registers - inefficiencies and all.
IMO, a comment like "We wind up debugging this a lot, please keep this syntax" would go a long way to help the next engineer. Assuming that the actual processing dwarfs the overhead present in this section, it would be even better to add discrete exception handling and post-conditions to make it more robust.
There's no such thing as "free" when something is "given" by a public entity. It always costs the people that actually work and are forcibly taxed, something.
Digital purchases are often subscription based which for physical goods (eg newspapers) is also non transferable (as it does not make much sense). In contrast, “one-of” licences are already transferable mostly. Either because the licence keys is a bearer token or companies support transfer (if you’ve registeted).
So you dont accomplish much here, I think, but still I agree it’s a good idea to get the details right and fixed in law.
> The majority of bugs (quantity, not quality/severity) we have are due to the stupid little corner cases in C .... Things like simple overwrites of memory (not that rust can catch all of these by far), error path cleanups, forgetting to check error values, and use-after-free mistakes.
What's the reach here of linters/address san/valgrind?
Or a linter written specifically for the linux kernel? Require (error-path) tests? It feels excessive to plug another language if these are the main arguments? Are there any other arguments for using Rust?
And even without any extra tools to guard against common mistakes, how much effort is solving those bug fixes anyway? Is it an order of magnitude larger than the cognitive load of learning a (not so easy!) language and context-switching continuously between them?
Rust forces you to encode much more explicitly information about lifetimes and ownership into the code. Tools can only do so much without additional information.
Those solve a bunch of problems and can avoid a lot of issues if used properly, but it's time-consuming and cumbersome to say the least. Nowhere near as seamless or holistic as what Rust has to offer.
> but it's time-consuming and cumbersome to say the least
Only when writing code (and not even that: only when doing final or intermediate checks on written code). When reading the code you don't have to use the tools. Code is read at lot more then being written. So if tools are used, the burden is put only on the writer of the code. If Rust is used the burden of learning rust is put both on the writers and readers of the code.
I find reading Rust much easier than writing it. It’s not actually that complicated a language; the complexity is in solving for its lifetime rules, which you only do when coding.
The main reason for bloat not mentioned in the article is that most developers are not interested in the business domain of the company they are working for. They just want to do stuff with computers and software.
Which leads to adding stuff just for the sake of being of interest to developers.
Think kubernetes, microservices etc. Secondly, because the developers are really not interested in the business (problems), they just want to easiest fix they can get away with. Which leads to bloat, as described.
A good test for this are feature specifications: if developers/programmers do not want to write them (or even don't read them with careful attention) then they are not interested in the problem domain of the company. It's an uncomfortable truth.
Another thing I've experienced is that non-technical management somehow wants to see solutions with loads of "technologies" included. I was once asked to give a diagram overview of our IT infrastructure. I agreed mentioning it would be a simple diagram (a reverse proxy, a server and a database). The simplicity of the diagram was frowned upon because if you look at all cloud platforms websites they make you believe that the IT infra for all companies must at least look like that of Amazon and Google.
So it is also a problem of management that does not want to reward simplicity and is victim of the "cargo cult" so eagerly fed by cloud providers.
Rob Pike (a few years older than me) didn't mention a few relevant facts. Those mainframes cost a small fortune, so their owners valued the computing resources. That pressure to control costs, along with the sometimes severe constraints of the hardware, forced programmers who cut their teeth in that era to avoid bloat and questionable features. For the first decade and half of my career I worked at companies that didn't have an "IT" or software development department -- I worked in business units like logistics or accounting, as part of those teams, rather than as a separate "resource." I knew what the business needed because I worked alongside the people who would use the code I worked on.
When I started freelancing about 15 years ago I ran into customer after customer who had got sold (or proposed) crazy over-complicated (and expensive) solutions. Very often it seemed like a shopping list of languages and tools of the day, put together with no regard for real business needs. I have listened to programmers bamboozle their bosses and customers with phrases like "performant" and "maintainable" and "best practices" that don't mean much and don't have objective ways to measure them. I don't usually ascribe that to malice or fraud, just an almost complete disconnect and lack of interest in the business and its needs.
> So it is also a problem of management that does not want to reward simplicity and is victim of the "cargo cult" so eagerly fed by cloud providers.
This absolutely mirrors my experience. I've worked on large number of cloud projects that were monstrosities of containers, serverless functions, NoSQL DBs, CDNs, cross-account pipelines, piles of terraform/CFN etc etc.
Naturally this was all topped off by some React thing with "tailwind" or MaterialUI, so clients needed multiple MB downloaded to display a list or whatever.
These were ALL projects that could have had every requirement met by, like, Ruby on Rails on a Raspberry Pi.
The amount of time sunk into fiddling with IaC issues, or debugging 65 different serverless/container service integration was easily an order of magnitude larger than time spent delivering value to customers and end-users.
Kinda funny that you mention tailwind, as tailwind ships exactly the css that is needed to serve the design. Tailwind does not make your project bigger than not using tailwind.
They probably referred to the percentage of bloated solutions with tailwind (100%) in the mix opposed to the percentage of lean solutions with tailwind (0%) in the mix which makes tailwind an indicator or cornerstone of a mindset rather than a contributor to the actual bloat which is kinda funny too of course.
Correct. I'm referring to the mindset where "tailwind" (or whatever) is just "what you do" irrespective if it is even appropriate or not for the given solution. Same mindset as the architecture approach.
It also the fault of the guys in Marketing and Product Management who want features to match a competitor's, or to beat a competitor on some pointless metric, or to create different versions for different market segments, or because some corporate big wig made an off the cuff remark wouldn't it be nice if ...
> The main reason for bloat not mentioned in the article is that most developers are not interested in the business domain of the company they are working for. They just want to do stuff with computers and software.
Well, at the several investment banks I've worked for, the highest paid developers were those that also had the most knowledge of the bank's business. Of course, if you don't care about pay and just want to do "stuff", that's up to you.
> the IT infra for all companies must at least look like that of Amazon
Well, thanks to AWS, the IT infra for all companies increasingly does look like that of Amazon - because it's Amazon's own infra. Except if they're Azure or GCP customers, of course, but then it's M$'s or Google's infra...
Regarding the immediate effect I am sure your point is valid. But it’s also a bit of a cynical point of view, wouldn’t you say?
People make these statements and pursue these personal lifestyle decisions because of their dreams for a better future - not its immediate effect. Just as companies need a vision to succeed, societies need vision as well. If a lot of people are vocal about something and live it, it has a chance of becoming anchored in laws and so force companies to do the “right thing”. Regulation follows collective values.
A mature (and very effective) structural editor is the FLUID user-interface designer, part of the FLTK UI framework (C++). It outputs C++ and works very well.
I remember dismissing it as a toy back when starting to work professionally with FLTK. Soon I realised I was mistaken; it is excellent to create UI's with for C++/FLKT.
> Implementing generics in this way breaks parametricity. Simply put, parametricity means being
able to reason about functions just from their type signature. You can't do this when the function can do arbitrary computation based on the concrete type a generic type is instantiated with.
Do you mean reasoning about a function in the sense of just understanding what a functions does (or can do), i.e. in the view of the practical programmer, or reasoning about the function in a typed theoretical system (e.g. typed lambda calculus or maybe even more exotic)? Or maybe a bit of both? There is certainly a concern from the theoretical viewpoint but how important is that for a practical programming language?
For example, I believe C++ template programming also breaks "parametricity" by supporting template specialisation. While there are many mundane issues with C++ templates, breaking parametricity is not a very big deal in practice. In contrast, it enables optimisations that are not otherwise possible (for templates). Consider for example std::vector<bool>: implementations can be made that actually store a single bit per vector element (instead of how a bool normally is represented using an int or char). Maybe this is even required by the standard, I don't recall. My point is that in makes sense for C++ to allow this, I think.
In terms of implementation, you can view parametricity as meaning that within the body of a function with a generic type, the only operations that can be applied to values of that type are also arguments to that function.
This means you cannot write
fn sort<A>(elts: Vec<A>): Vec<A>
because you cannot compare values of type A within the implementation of sort with this definition. You can write
fn sort<A>(elts: Vec<A>, lessThan: (A, A) -> Bool): Vec<A>
because a comparison function is now a parameter to sort.
This helps both the programmer and the compiler. The practical upshot is that functions are modular: they specify everything they require. It follows from this that if you can compile a call to a function there is a subset of errors that cannot occur.
In a language without parametricity, functions can work with only a subset of possible calls. If we take the first definition of sort, it means a call to sort could fail at compile-time, or worse, at run-time, because the body of the function doesn't have a case that knows how to compare elements of that particular type. This leads to a language that is full of special cases and arbitrary decisions.
Javascript / Typescript is an example of a language without parametricity. sort in Javascript has what are, to me, insane semantics: converting values to strings and comparing them lexicographically. (See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...) This in turn can lead to amazing bugs, which are only prevented by the programmer remembering to do the right thing. Remembering to do the right thing is fine in the small but it doesn't scale.
Breaking parametricity definitely has uses. The question becomes one about the tradeoffs one makes. That's why I'd rather have a discussion about those tradeoffs than just "constime good" or "parametricity good". Better yet are neat ideas that capture the good parts of both. (E.g. type classes / implicit parameters reduce the notational overhead of calling functions with constrained generic types, but this bring their own tradeoffs around modularity and coherence.)
Do you have a blog or other site where you post your writing? Your explanations are quite good and easy to follow for someone like me, an interested/curious onlooker.
Functions can crash anyway. I don't see how what you describe is different from a function on integers that errors on inputs that are too big. The programmer has to actively choose to make function break parametricity, and they can equally chose not to do that.
In an ideal world, a function that only works for small integers would bake that into its type system. Ie, rather than accepting “any integer”, the function would accept a u8, or a value compile-time guaranteed in the range of 0..10 or something.
Your point still stands though. Modern programming languages don’t constrain you much at all with their type systems.
I spent a little time in Haskell a few years ago and it the kind of reasoning you can do about functions is wild. Eg, if a function has the type signature of A -> A, we know the function has to be the identity function because that’s the only function that matches the type signature. (Well that or the “bottom function”, which loops forever). Lots of functions are like that - where the type definitions alone are enough to reason about a lot of code.
> In an ideal world, a function that only works for small integers would bake that into its type system. Ie, rather than accepting “any integer”, the function would accept a u8, or a value compile-time guaranteed in the range of 0..10 or something.
Haskell has quite a few partial functions in the standard library if I recall. Bog standard (!!), head, and tail for lists; fromJust to unwrap Maybes; and even some more interesting examples like "all" which mightn't work on infinite lists. Indeed any Turing-complete language includes partial functions of that final variety.
Sure, but it's the same thing in 10x fewer words. Having parametric generic types that accept constraints allows your functions to have the best of all worlds.
So a language _with_ that is superior. All zig needs to do is add some way to allow for constraints.
Fair point about parametricity. A language could in the macro expansion do the equivalent of a scala implicit lookup for a sorting function for the type and return an error at macro expansion time if it can't find one. That avoids the doing the right thing requires discipline problem but I agree it is still less clear from the type signature alone what the type requirements are.
> For example, I believe C++ template programming also breaks "parametricity" by supporting template specialisation.
C++ breaks parametricity even with normal templates, since you can e.g. call a method that exists/is valid only on some instantiations of the template.
The issue is that the compiler can't help you check whether your template type checks or not, you will only figure out when you instantiate it with a concrete type. Things get worse when you call a templated function from within another templated function, since the error can then be arbitrarily levels deep.
> My point is that in makes sense for C++ to allow this, I think.
Whether it makes sense or not it's a big pain point and some are trying to move away from it (see e.g. Carbon's approach to generics)
> C++ breaks parametricity even with normal templates
I might be wrong here, but as I understand it "parametricity" means loosely that all instantiations use the same function body. To quote wikipedia:
"parametricity is an abstract uniformity property enjoyed by parametrically polymorphic functions, which captures the intuition that all instances of a polymorphic function act the same way"
In this view, C++ does not break parametricity with "normal" (i.e. non-specialised) templates. Of course, C++ does not type check a template body against its parameters (unless concepts/trairs are used), leading to the problems you describe, but it's a different thing as far as I understand.
To be parametric it needs to be the same body semantically, not just textually. Particularly in C++ with its heavy operator overloading and limited safety, you can very easily write a template whose body will do the right thing for some types and be undefined behaviour for others (e.g. if your template has comparisons in it and then you instantiate it with a pointer or something).
Parametricity is about behavior, not code. A function parametric in a variable should bevave identically for all values of the variable. If one instance of a C++ template fails to compile and another instance of the same template does compile it is a stretch to say they behave identically, even though they use the same code.
> Consider for example std::vector<bool>: implementations can be made that actually store a single bit per vector element (instead of how a bool normally is represented using an int or char).
Your example is considered a misfeature and demonstrates why breaking parametricity is a problem: the specialized vector<bool> is not a standard STL container even though vector<anythingelse> is. That's at best confusing -- and can leads to very confusing problems in generic code. (In this specific case, C++11's "auto" and AAA lessens some of the issues, but even then it can cause hard-to-diagnose performance problems even when the code compiles)
The C++ vector<bool> specialization is bad because breaking many important implicit contracts about taking the address of vector<> elements makes it practically unusable if a normal vector<> is expected, but it isn't specialized incorrectly in a formally meaningful sense: all errors are outside the class (unsatisfied expectations from client code) and implicit (particularly for C++ as it was at the time).
You're not wrong, but is at the very least weird that a specialization doesn't conform to the concept that the general template does. Something which proper parametricity would have avoided -- if it were available.
(The Hysterical Raisins are understandable, esp. given that it wasn't even possible to specify Concepts in C++ until 2020...)
The point is exactly that the "concept" of what the template should do is informal and a careful, detailed specification in a far more expressive language than vintage C++ would be needed to elicit good compilation errors from something like vector<bool>.
Proper parametricity is only a starting point: types that specify alignment, equality and lifetimes would be needed to make it useful.
Vector bool may not have to store a range of space optimized bool values but the interface is still different enough and guarantees different enough that is is largely thought of as a mistake. For one the const reference type is bool and not bool const &. Plus other members like flip… mostly the issue is in generic code expecting a normal vector
one thing you can reason about a function is: does it exist at all? if you don't have parametricity, you can't even be sure about that. in Rust, as long as your type satisfies a generic function's bounds, you can be sure instantiating that function with this type will compile; in C++ you don't have that luxury.
I believe one of the founders of Booking.com has had some similar experience (source: "De Machine", the book about Booking.com) and after 4 years of travelings & yoga etc. he sneaked back in into the company. Most of his coworkers do not know he's a founder. He's been fixing Perl bugs, writing scripts and coding some features at least for some years (maybe he's moved on already, don't know, the book is a couple of years old).
Sounds like a great book. Did you happen to read it in English? Ask because my brief search for it shows at least one review saying it's a bad translation.