Live data from Hacker News

In defense of complicated programming languages

viralinstruction.com

201–210 of 379 posts

Re: In defense of complicated programming languages

#202

I remember first encountering classes. I simply could not understand what they were from reading the documentation. Later, I picked op Bjarne's C++ book, read it, and could not figure out what classes were, either. Finally, I obtained a copy of cfront, which translated C++ to C. I typed in some class code, compiled it, and looked at the emitted C code. There was the extra double-secret hidden 'this' parameter. Ding!…

I'm similar. I've narrowed down my learning to two things I need:

- Principles for how things fit together. This is similar to your comment about digging into the assembly. Understanding how something is built is one way of determining the principles.

- Understanding of why something is needed. I still remember back to first learning to program and not understanding pointers. I pushed through in reading the book I was using and eventually they talked about how to use it and it finally clicked.

Re: In defense of complicated programming languages

#203

Earlier quoted context omitted.

OO implies far far more than simply grouping data in a struct and passing it by reference.

Such as? The only other things that come to mind when I think about "OOP" are: * inheritance, which at this point even lots of proponents of OOP have stopped defending * encapsulation, which usually gets thrown out the window at some point anyway in sizeable codebases * design patterns, which usually lead to loads of boilerplate code, and often hide implementation behind abstractions that serve little purpose other t…

The one thing that is extremely hard to fake well without some sort of language support is multiple dispatch, the ability to call a method on an object x of type X and have the calling expression automatically "know" which method to invoke depending on the actual runtime type of the object (any subtype of X or any interfaces implemented by it etc etc etc...).

This is extremely hard to fake in, say, C. I mean it's not really that hard to fake the common use case demonstrated in classrooms, a rough sketch would be something like

enum vehicle_type {CAR, AIRPLANE} ;

Struct Vehicle {

enum vehicle_type t ;

Union {

struct car_t { } car ;

struct airplane_t { } airplane ;

} data ;

//some common data and behavior

} ;

void func (Vehicle *V) {

switch(V->t) {

   CAR : break ;
   AIRPLANE : break ;
} }

But this is a horrible thing to do. First, it's a repetitive pattern, you're basically working like a human compiler, you have a base template of C code that you're translating your ideas into. Rich source of bugs and confusions. Second, it's probably hiding tons and tons of bugs: are you really sure that every function that needs to switch on a Vehicle's type actually does that? correctly? what about the default case for the enum vehicle_type, which should never ever take any other value besides the valid two? how do you handle the "This Should NEVER Happen" case of it actually taking an invalid value? How do you force the handling of this in a consistent way across the code base that always exposes the problem to the calling code in a loud-yet-safe manner ?

There's a saying called Greenspun's tenth law : "Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp". If you ignore the obnoxious lisp-worshiping at the surface, this aphorism is just trying to say the same thing as the article : there are patterns that appear over and over again in programming, so programming languages try to implement them once and for all, users can just say "I want dynamic polymorphism on Vehicle and its subtypes Car and Airplane" and voila, it's there, correctly and efficiently and invisibly implemented the first time with 0 effort on your part.

If you don't want the language to implement those patterns for you, you're still* going to need to implement them yourselves when you need them (and given how common they are, you will* need them sooner or later), only now haphazardly and repetitively and while in the middle of doing a thousand other thing unrelated to the feature, instead of as a clean set of generic tools that the language meticulously specify, implement and test years before you even write your code.

>inheritance, which at this point even lots of proponents of OOP have stopped defending

Not really, there are two things commonly called "inheritance" : implementation inheritance and interface inheritance. Implementation inheritance is inheriting all of the parent, both state and behaviour. It's not the work of the devil, you just need to be really careful with it, it represents a full is-a relationship, literally anything that makes sense on the parent must make sense on the child. Interface inheritance is a more relaxed version of this, it's just inheriting the behaviour of the parent. It represents a can-be-treated-as relationship.

Interface inheritance is widely and wildly used everywhere, everytime you use an interface in Go you're using inheritance. And it's not like implementation inheritance is a crime either, it's an extremely useful pattern that the Linux kernel, for instance, Greenspuns in C with quirky use of pointers. And it's still used in moderation in the languages that support it natively, its ignorant worshippers who advocate for building entire systems as meticulous 18th century taxonomies of types and subtypes just got bored and found some other trend to hype.

>encapsulation, which usually gets thrown out the window at some point anyway in sizeable codebases

I don't understand this, like at all. Encapsulation is having some state private to a class, only code inside the class can view it and (possibly) mutate it. Do you have a different definition than me? How, by the definition above, does Encapsulation not matter in sizable code? it's invented for large code.

>design patterns, which usually lead to loads of boilerplate code

100% Agreement here, OOP design patterns are some of the worst and most ignorant things the software industry ever stumbled upon. It reminds me of pseudoscience : "What's new isn't true, and what's true isn't new". The 'patterns' are either useful, but completely obvious and trivial, like the Singleton or the Proxy. Or they are non-obvious, but entirely ridiculous and convoluted, like the infamous Abstract Factory. This is without even getting into vague atrocities like MVC, which I swear that I can summon 10 conflicting definitions of with a single Google search.

The idea of a "design pattern" itself is completely normal and inevitable, the above C code for example is a design pattern for generically implementing a (poor man's version of) dynamic dispatch mechanism in a language that doesn't support one. A design pattern is any repetitive template for doing something that is not immediately obvious in the language, it's given a name for easy reference and recognition among the language community. It doesn't have anything to do with OOP or "clean code" or whatever nonsense clichés repeated mindlessly and performatively in technical job interviews. OOP hype in the 1990s and early 2000s poisoned the idea with so much falsehoods and ignorance that it's hard to discuss the reasonable version of it anymore. But that's not OOP problem, it's us faulty humans who see a good solution to a problem and run around like children trying to apply it to every other problem.

Re: In defense of complicated programming languages

#204
post #29
post #19

Earlier quoted context omitted.

Try learning Rust, then you reach this point https://stackoverflow.com/questions/39558633/higher-rank-lif... I still don't understand what higher rank lifetime bound means and this affects whether I can write code that actually compiles

When I was writing my comment I thought about Scala, where I was certainly learning useful new techniques 7 years into using the language, but that wasn't really about learning new parts of the language. I think Rust has picked an overly specific way to represent lifetimes, that makes them more of a special case than they should be. But maybe that distinction doesn't actually matter. (Higher rank lifetime bounds are…

Rust is in this weird place, because it needs to be palatable to C/C++ programmers. C has pointers and a vague notion of their lifetimes, but barely any type system to speak of. That's the lens through which Rust needs to present the problem.

Re: In defense of complicated programming languages

#205
post #70
post #17

> In video game design there is a saying: Show locked doors before you show a key This is something I've tried putting into words many times. I'll try to solve a problem and get to know its challenges deeply. Then a tool is introduced that brings it all together. In these cases, I seem to quickly get a full grasp of the operation and essence of the tool. I wish education was based around this principle. A bit like wh…

But I think that's the exact reason we need to learn simple languages here. So that we know the closed doors first (the abstraction problem), then we will create a key or find a key elsewhere. Otherwise if "Class" is made the part of the language, like Java, you will not appreciate the importance of it to any extent and have a overall bad experience....

You can start with the simple features of a language and use them to introduce the more complex features.

Re: In defense of complicated programming languages

#206
post #20

Earlier quoted context omitted.

Then they are quite bad developers, because that is exactly what a team of ours did in a two day hackacton. Some learned C++ on the Arduino, some went with TypeScript and Angular, others with JavaScript and AWS Lambdas, together we got a robot arm to pick and drop pieces with a Web dashboard and remote control, talking with each other via the "cloud". Naturally all of them had several years of coding experience, but…

It's definitely going to vary according to your familiarity with other languages in the family. I came into a C# shop late last year from never touching C# before and was writing more or less idiomatic C# very quickly, but I've worked with lots of semi-colon languages so this is mostly familiar territory. On the other hand if you've never seen a Lisp before, ten years of C++ and VisualBasic won't prepare you to get a…

It was an hackton, so all of that was throwaway code anyway.

Re: In defense of complicated programming languages

#207

I remember first encountering classes. I simply could not understand what they were from reading the documentation. Later, I picked op Bjarne's C++ book, read it, and could not figure out what classes were, either. Finally, I obtained a copy of cfront, which translated C++ to C. I typed in some class code, compiled it, and looked at the emitted C code. There was the extra double-secret hidden 'this' parameter. Ding!…

I understand this thought process, but in my opinion it's the wrong way to think about software concepts. Understanding what a bridge is doesn't mean knowing how to build one, and in fact tying your understanding to a certain implementation of a bridge just limits your ideas about what is, in fact, an abstract concept. We understood functions as "mappings" between objects for hundreds of years, and when programming c…

Yes, you are correct. But you are wrong too.

People need complete understanding of their tools. And complete understanding includes both how to use the concepts they represent and how those concepts map into real world objects. If you don't know both of those, you will be caught by surprise in a situation where you can't understand what is happening.

That focus on the high level only is the reason we had a generation of C++ developers that didn't understand the v-table while being perfectly capable of creating one by hand. It's also why we have framework-only developers that can't write the exact same code outside of the magical framework, even when it's adding no value at all.

Re: In defense of complicated programming languages

#208

One difficult thing to accept in programming language design is that what mathematically is simple, consistent, and straightforward is not simple, consistent, or straightforward to people. For example, a simple, consistent, and straightforward expression syntax would be RPN (Reverse Polish Notation). But humans dislike, and much prefer infix with its complicated operator precedences. The real trick to programming lan…

Some humans dislike RPN. I am yet to find whether they really dislike or just find it unfamiliar. I am convinced that unfamiliar gets conflated with unintuitive and hard all the time.

'unfamiliar' is 'unintuitive'. You only have an intuition for things that are similar to things you have encountered. Some unfamiliar or unintuitive things might be simpler than familiar things. But, at first, it is often easier to use familiar things, even if they are less simple.

Regarding RPN, I am not convinced that they are actually simpler to a human. In order to understand an RPN expression I try to keep the stack of operands and intermediate results in my ultra short term memory. This can easily exceeds its typical capacity, whereas when trying to understand an infix expression, I can replace subtrees with an abstraction, i.e. a short name, in my mind. But perhaps I have just not yet seen the light and spend enough time working with RPN expressions.

On the other hand, RPN expressions are certainly far simpler to implement.

Re: In defense of complicated programming languages

#209

I remember first encountering classes. I simply could not understand what they were from reading the documentation. Later, I picked op Bjarne's C++ book, read it, and could not figure out what classes were, either. Finally, I obtained a copy of cfront, which translated C++ to C. I typed in some class code, compiled it, and looked at the emitted C code. There was the extra double-secret hidden 'this' parameter. Ding!…

I had a similar experience in law school in one of my tax classes. I hit a couple things where I could just not get what the tax code, the IRS regulations, or my textbook were trying to tell me.

I went to the university bookstore and found the textbook section for the university's undergraduate business degree programs, and bought the textbook for an accounting class.

Seeing the coverage of those tax areas from the accounting point of view cleared up what was going on, and then I understood what was going on in the code and regulations.

Re: In defense of complicated programming languages

#210

Earlier quoted context omitted.

Classes are closures just as much as closures are classes (as Java showed). What classes are at their most core is bundles of behavior, possibly with encapsulated state, and usually some way to decide at runtime which of a family of classes' behavior to use. Closures are naturally tied to 1 single behavior (though of course you can invent ad-hoc methods of extending that, such as a "methodName" parameter, but no one…

Closures are the superior mechanism though, you can implement classes plus all the crazy decorators / metaclasses / mixins / etc. The reverse is true of course but in the dumbest way possible. A closure-based implementation of a class is just a function that returns a structure full of closures (an interface if you will) all with the same shared state. But now because a class is just a function, you can do all kinds…

> Classes are not just unnecessary, they're a bad abstraction.

And yet, there are approximately 0 examples of the "closure-backed classes" example you give, while every vaguely popular language except C has some variant of polymorphic classes, from StandardML to Lisp.

Your closure example is not used in any langauge because it's extremely un-ergonomic. If the langauge doesn't have some kind of concept of "interface" (abstract class, type class, trait, structural typing etc), then you'll either miss polymorphism (return a single specific type), or be too dynamic (return an object that could do anything). It also risks being inefficient if you try to naively add function pointer members to each object to support runtime dispatch.

The closure-based alternative also obscures the relationship between product data types (structs/record types) and classes (structs with methods).

Closure-backed classes are a neat experiment, but just like manual virtual table management in C, they are not a realistic solution to the problems that classes address, if you can possibly avoid them.

Post reply on HN