10 ms·
The thing I always loved about C was its simplicity, but in practice it's actually very complex with tons of nuance. Are there any low level languages like C th
by 90s_dev 2y ago
The thing I always loved about C was its simplicity, but in practice it's actually very complex with tons of nuance. Are there any low level languages like C that actually are simple, through and through? I looked into Zig and it seems to approach that simplicity, but I have reservations that I can't quite put my finger on...
- bewo001 2y agoforth would come to mind, some people have build surprising stuff with it though I find it too low-level.
- ChrisRR 2y agoIt depends what you mean by simple. C still is simple, but it doesn't include a lot of features that other languages do, and to implement them in C is not simple. C is simple for some use cases, and not for others.
- 90s_dev 2y agoThe appeal of C is that you're just operating on raw memory, with some slight conveniences like structs and arrays. That's the beauty of its simplicity. That's why casting a struct to its first argument works, why everything has an address, or why pointer arithmetic is so natural. Higher level langs like C++ and Go try to retain the usefulness of these features while abstracting away the actuality of them, which is simultaneously sad and helpful.
- colejohnson66 2y ago> That's why casting a struct to its first argument works Until WG14 makes everything you love about C "undefined behavior" in the name of performance.
- worik 2y ago> Until WG14 makes everything you love about C "undefined behavior" in the name of performance. What do you mean? I just looked up WG14 and I cannot see what you mean A link perhaps? Am I going to have to "pin" my C compiler version?
- tialaramex 2y agoSome people have this idea that when they write utter nonsense it should do what they meant because - ie they're missing out the whole discipline of programming and going straight from "I want it to work" to "It should work" and don't understand what they're doing wrong. For some of these people WG14 (the C language sub-committee of SC22, the programming language sub-committee of JTC1, the Joint Technical Commitee between ISO and the IEC) is the problem because somehow they've taken this wonderful language where you just write stuff and it definitely works and does what you meant and turned into something awful. This doesn't make a whole lot of sense, but hey, they wrote nonsense and they're angry that it didn't work, do we expect high quality arguments from people who mumble nonsense and make wild gestures on the street because they've imagined they are wizards? We do not. There are others who blame the compiler vendors, this at least makes a little more sense, the people who write Clang are literally responsible for how your nonsense C is translated into machine code which does... something. They probably couldn't have read your mind and ensured the machine code did what you wanted, especially because your nonsense doesn't mean that, but you can make an argument that they might do a better job of communicating the problem (C is pretty hostile to this, and C programmers no less so) For a long time I thought the best idea was to give these people what they ostensibly "want" a language where it does something very specific, as a result it's slow and clunky and maybe after you've spent so much effort to produce a bigger, slower version of the software a friend wrote in Python so easily these C programmers will snap out of it. But then I read some essays by C programmers who had genuinely set out on this path and realised to their horror that their fellow C programmers don't actually agree what their C programs mean, the ambiguity isn't some conspiracy by WG14 or the compiler vendors, it's their reality, they are bad at writing software. The whole point of software is that we need to explain exactly what the machine is supposed to do, when we write ambiguous programs we are doing a bad job of that.
- windward 2y agoA typical C program of useful length typically includes a spattering of implicit type conversions that the programmer never intended or considered. It's the consequence of a feature that abstracts away how the type system and memory really[1] acts. [1]for certain definitions of 'really'
- zokier 2y ago> The appeal of C is that you're just operating on raw memory ... why everything has an address, or why pointer arithmetic is so natural That is just an illusion to trip unsuspecting programmers who have false mental models. Pointers are not addresses, and pointer arithmetic is rife with pitfalls. There is the whole pointer provenance thing, but that's more like the tip of the iceberg. That is really the problem with C; it feels like you can do all sorts of stuff, but in reality you are just invoking nasal demons. The real rules on what you can and can not do are far more intricate and arcane, and nothing about them is very obvious on the surface level.
- acuozzo 2y ago> C still is simple Syntactically, yes. Semantically, no. There are languages with tons of "features" with far, far less semantic overhead than C. https://blog.regehr.org/archives/767 https://blog.regehr.org/archives/767 FWIW, writing programs in C has been my day job for a long time.
- drob518 2y agoExactly. There is a lot happening implicitly in a C program that the programmer has to be aware of and keep in mind. And it’s made worse by valid compile implementation choices. I remember chasing a bug for a day that was based on me forgetting that the particular implementation I was working with had signed characters and was sign extending something at an inopportune time.
- kstrauser 2y agoC is simple like SMTP.
- acuozzo 2y agoEXACTLY!
- groos 2y agoAs someone who has had to parse C syntax for a living, I'd argue that it's not syntactically simple either. (Declarators are particularly nasty in C and even more so in C++).
- ChrisRR 2y agoEntirely my point. Simpler in some ways, more difficult in others. Totally depends on the use case
- SAI_Peregrinus 2y agoI've said before that C is small, but not simple. Turing Tarpits like Brainfuck or the Binary Lambda Calculus are a more extreme demonstration of the distinction, they can be very tiny languages but are extremely difficult to actually use for anything non-trivial. I think difficulty follows a "bathtub" curve when plotted against language size. The smallest languages are really hard to use, as more features get added to a language it gets easier to use, up to a point where it becomes difficult to keep track of all the things the language does and it starts getting more difficult again.
- LPisGood 2y agoIt’s not really clear to me how you could have a simple low level language without tons of nuance. Something like Go is certainly simple without tons of nuance, but it’s not low level, and I think extending it to be low level might add a lot of nuance.
- bluetomcat 2y agoThe perceived complexity from a semantic standpoint comes from the weakly-typed nature of the language. When the operands of an expression have different types, implicit promotions and conversions take place. This can be avoided by using the appropriate types in the first place. Modern compilers have warning flags that can spot such dodgy conversions. The rest of the complexity stems from the language being a thin layer over a von Neumann abstract machine. You can mess up your memory freely, and the language doesn’t guarantee anything.
- grandempire 2y agoC is simple. Representing computation as words of a fixed bit length, in random access memory, is not (See The Art of Computer Programming). And the extent to which other languages simplify is creating simpler memory models.
- mort96 2y agoWhat about C is simple? Its syntax is certainly not simple, it's hard to grok and hard to implement parsers for, and parsing depends on semantic analysis. Its macro system is certainly not simple; implementing a C preprocessor is a huge job in itself, it's much more complex than what appears to be necessary for a macro system or even general text processor. Its semantics are not simple, with complex aliasing rules which just exist as a hacky trade-off between programming flexibility and optimizer implementer freedom. C forces programs to be simple, because C doesn't offer ways to build powerful abstractions. And as an occasional C programmer, I enjoy that about it. But I don't think it's simple, certainly not from an implementer's perspective.
- grandempire 2y agoEach of these elements is even worse in every other language I can think of. What language do you think is simple in comparison?
- mort96 2y agoGo, Rust, Zig? I'm curious, what language do you know of with a more complex macro system than the whole C preprocessor? EDIT: To be clear to prospective downvoters, I'm not just throwing these languages out because they're hype or whatever. They all have a grammar that's much simpler to parse. Notably, you can construct a parse tree without a semantic analyser which is capable of running in lockstep with the parser to provide semantic information to the parser. You can just write a parser which makes a parse tree.
- unclad5968 2y ago
- ioma8 2y agoI would say rust. When you learn the basics, rust is very simple and will point to you any errors you have, so you get basically no runtime errors. Also the type system is extremely clean, making the code very readable. But also C itself is very simple language. I do not mean C++, but pure C. I would probably start with this. Yes, you will crash at runtime errors, but besides that its very very simple language, which will give you good understanding of memory allocation, pointers etc.
- deleted 2y ago[deleted]
- ForOldHack 2y agoGot through C and K&R with no runtime errors, on four platforms, but the first platform... Someone asked the teacher why a struct would not work on Lattice C. The instructor looked at the code, sat down at the students computer, typed in a small program compiled it, and camly put the disks in the box with the manual and threw it in the garbage. "We will have a new compiler next week." We switched to Manx C, which is what we had on the Amiga. Structs worked on MS C, which I thought was the lettuce compiler. ( Apparently a different fork of the portable C compiler, but later they admitted that it was still bigendian years later ) Best programming joke. Teacher said when your code becomes "recalcitrent", we had no idea what he meant. This was in the bottom floor of the library, so on break, we went upstairs and used the dictionary. Recalcitrant means not obeying authority. We laughed out loud, and then went silent. Opps. The instructor was a commentator on the cryptic-C challenges, and would often say... "That will not do what you think it will do" and then go on and explain why. Wow. We learned a lot about the pre-processor, and more about how to write clean and useful code.
- icedchai 2y agoLattice C (on the Amiga) was my first C compiler! Do you remember what the struct issue you ran into? This was a pretty late version... like 5.x.
- steveklabnik 2y agoThe reality is, the only languages that are truly simple are Turing tarpits, like Brainfuck. Reality is not simple. Every language that’s used for real work has to deal with reality. It’s about how the language helps you manage complexity, not how complex the language is. Maybe Forth gets a pass but there’s good reason why it’s effectively used in very limited circumstances.
- 1wd 2y agoOberon?
- 90s_dev 2y agoI did not realize how intrinsically I also meant simple in syntax until you suggested that.
- timewizard 2y ago> but in practice it's actually very complex with tons of nuance That's because computers are very complex with tons of nuance.
- fads_go 2y agoLisp is build from a few simple axioms. Would that make it simple?
- dwattttt 2y agoSo are entire branches of mathematics, and I feel safe in saying they are not "simple"
- spauldo 2y agoLisp could be simple... but there's a lot of reasons it isn't. It uses a different memory model than current hardware, which is optimized for C. While I don't know what goes on under SBCL's hood, the simpler Lisps I'm familiar with usually have a chunk of space for cons cells and a chunk of "vector" space kinda like a heap. Lisp follows s-expression rules... except when it doesn't. Special forms, macros, and fexprs can basically do anything, and it's up to the programmer to know when sexpr syntax applies and when it doesn't. Lisp offers simple primitives, but often also very complex functionality as part of the language. Just look at all the crazy stuff that's available in the COMMON-LISP package, for instance. This isn't really all that different than most high level languages, but no one would consider those "simple" either. Lisp has a habit of using "unusual" practices. Consider Sceme's continuations and use of recursion, for example. Some of those - like first-class functions - have worked their way into modern languages, but image how they would have seemed to a Pascal programmer in 1990. Finally, Lisp's compiler is way out there. Being able to recompile individual functions during execution is just plain nuts (in a good way). But it's also the reason you have EVAL-WHEN. All that said, I haven't invested microcontroller Lisps. There may be one or more of those that would qualify as "simple."
- kazinator 2y agoMostly we have eval-when because of outdated defaults that are worth re-examining. A Lisp compiler today should by default evaluate every top level form that are compiles, unless the program opts out of it. I made the decision in TXR Lisp and it's so much nicer that way. There are fewer surprises and less need for boilerplate for compile time evaluation control. The most you usually have to do is tell the compiler not to run that form which starts your program: for instance (compile-only (main)). In a big program with many files that could well be the one and only piece of evaluation control for the file compiler. The downside of evaluating everything is that these definitions sit in the compiler's environment. This pollution would have been a big deal when the entire machine is running a single Lisp image. Today I can spin up a process for the compiling. All those definitions that are not relevant to the compile job go away when that exits. My compiler uses a fraction of the memory of something like GCC, so I don't have to worry that these definitions are taking up space during compilation; i.e. that things which could be written to the object file and then discarded from memory are not being discarded. Note how when eval-when is used, it's the club sandwich 99% of the time: all three toppings, :compile-toplevel, :load-toplevel, :execute are present. The ergonomics are not very good. There are situations in which it would make sense to only use some of these but they rarely come up.
- int_19h 2y agoModula-2 is a language operating on the same level (direct memory addressing, no GC etc) but with saner syntax and semantics. It's still a tad more complicated than it needs to be - e.g. you could drop non-0-based arrays, and perhaps sets and even enums.
- pjmlp 2y agoModula-2, Object Pascal, Oberon specially Oberon-07. I would say Zig is the spiritual follower from the first two, while Go follows up the Oberon and Limbo heritage.