Low-level is easy
yosefk.com
yosefk.com
You get some similar benefits if you are working on code where you have some control over the levels above or below, or if your project has any form of self-hosting. For example, Firefox, where most of the UI is rendered by Gecko and some of it even runs within a browser tab. If you're a web developer and you run into a rendering engine bug, you report it and then ship a workaround for three years as you wait for enough people to update. If you're a Firefox front-end developer, you can just fix the Gecko bug and ship the fix along with your feature.
I'm not seeing the difference other than you probably run into that kind of breakage more frequently with the web.
Your comment brings up a good point that writing portable code (cross-browser, cross-OS, across compilers, across processors, etc.) adds to the difficulty level because it requires you to target a higher-level abstraction (web standards, POSIX, ANSI C...) rather than a concrete implementation, while still dealing with implementation-specific bugs that leak through that abstraction.
- Compilers: I'm taking the Stanford course on Coursera right now. It just started two weeks ago so you can still catch up. Here's the link: https://class.coursera.org/compilers-004 Here's some literature on compilers/interpreters/JIT (Just In Time compilation): http://www.stephendiehl.com/llvm/ (I've heard good things about LLVM!) http://blog.reverberate.org/2012/12/hello-jit-world-joy-of-s... (Just saw this today) Tutorial on building a Lisp in C, from two days ago: http://www.buildyourownlisp.com/
- Operating Systems: Definitely check out MIT's open courseware on xv6! It's an implementation of Unix version 6. The course includes a git project that you can clone and play with yourself, as well as some labs. Here's the link: http://pdos.csail.mit.edu/6.828/2012/index.html OSDev wiki had some resources too: wiki.osdev.org If you have a Raspberry Pi or are patient enough to try to emulate the hardware, here are some tutorials on ARM assembly for RasPi: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/
- Networking: Here, you'll probably want to look for tutorials on socket programming and the TCP/UDP protocol. People recommend Beej's tutorials, which are quite comprehensive and a good source of information, but I found some of his server code confusing and convoluted (deeply nested loops and if statements). Here's a link to his site: http://beej.us/guide/bgnet/ Here's a easy to learn server/client tutorial: http://www.thegeekstuff.com/2011/12/c-socket-programming/
- Graphics: I have the least amount of exposure to this, but a good resource is at the website open.gl. Basically you'll want to learn about shader languages and how the graphics pipeline works.
Hope this helps!
I wouldn't say it is easier by any means.
The first time you open up the debugger and your v-table pointer is null (presuming you know what a v-table pointer is!) things start to be interesting.
Or there was that time I printf'd a 64bit number and a random stack variable was corrupted. That was a lot of fun.
Memory protection? Haha. No.
For that matter, my team just came across a bug in our own code a couple weeks ago, we were performing a DMA memcopy operation (for which you get to setup your own DMA descriptors yourself of course) over a bus and at the same time sending a command packet to the same peripheral that was being DMA'd to.
oops.
Expected things to be put into order for you? Nope. Not unless you implement your own queuing system. (Which we are doing for that particular component now.)
All in all it is a ton of fun though. I'm loving it. Having seen an entire system be built up from setting up a clock tree to init'ing peripherals to our very own dear while(1). (We actually moved away from while 1, async is where it is at baby! Callback hell in kilobytes of RAM! oooh yaaaah)
The amount of stuff they do is incredible, and everything is interconnected in unexpected and intricate ways.
I loaded up the datasheet for the M3's when typing up this reply. 384 pages. Contrast that with the TI 74181 ALU datasheet from the days of old: 18 pages, most of which are just detailed diagrams of the distances between the pinouts. The logic diagrams fit on a single page. You can build a simple CPU using one of these machines in a few hours in your basement.
Hardware is only going to get more complicated. At what point does it become so complicated that no one person can reasonably understand how a computer works "under the hood", even from an abstract level?
At the bottom, it really felt like programming a machine, and I found it all good fun, much like solving a puzzle (with occasional obscure headaches, mostly compiler related). At the top everything is pretty abstract, and there's more freedom to do conceptually interesting things (for example we were doing quite a bit of adaptive control type stuff, which would have been a nightmare to write with lower-level languages).
In the middle, however, it seemed to me to be largely just a complicated network of interacting conventions. Those systems are neither firmly grounded in "reality", because they are generally trying to abstract away those details, nor are they "theoretically pure", because they need to be efficient (though there is a lot of interesting stuff there). What that means is that you simply have to learn and understand all those human-defined conventions to know what you're doing, and that makes solving problems at that level more difficult, or perhaps rather it requires much more hard-won expertise (which I never got much of - I just bugged other people until they would help me out!).
Obviously, my views are coloured by my personal experience, so make of them what you will.
To produce much of value at the low-end you need a really comprehensive understanding of the technology at the level you are working at which has significant upfront learning (and likely just plain aptitude) costs that aren't really addressed too much here. Once you have that knowledge, then yes, you get far less surprises, but acquiring it in the first place is not at all trivial or "easy" (though it may seem like it if you're a geek that's been banging away with assembler for years... you've just forgotten how much effort you expended at that stage, probably because it felt fun to learn).
At the higher-end you can string together a bunch of frameworks and glue code you cut and pasted off Stack Overflow and get something that pretty much does what you want, most of the time, maybe, while barely understanding the underlying technology.
Which is "easier" or "harder" depends a lot on what you mean by those terms.
Also the assumption that "high-level" means HTML/CSS/JavaScript is not that useful for the overall debate since not all high-level development is as annoyingly unpredictable as HTML/CSS/JavaScript.
I haven't responded by going as far down the stack as this guy has -- although I've done a fair bit of assembly and enjoyed it, I spend most of my time in Python and Erlang -- but coding web apps can be such a ghetto.
// my perspective: I started with low level c and assembly, mainly on x86 and do web programming nowadays.
It's sort of like gcc where layers keep getting put on top of layers and only like 5 wizards from MIT know how it actually works.
In other words: design your clean, orthogonal RISC ISA however you want: at some point in the future, some processor designer is going to end up translating those instructions into something else.
As for layers upon layers of abstractions: welcome to computer science. ;)
One flavor can be seen in the A20 gate
http://en.wikipedia.org/wiki/A20_line
30 years after it was inserted into the architecture to work around some software issues/bugs, bootloaders still have to fool around with it every time a x86 machine is booted. Lovely.
If you could allocate skill points like an MMO, the violinist is spending 3 points on instrument mastery, and 7 points on musical mastery, while the pianist spends 1 and 9.
I hate spending my limited skill points on "browser mastery," so I mostly do lower level things.
An acoustic piano has 88 keys, and classical or jazz piano music tends use many of them. Whereas a 60 key electronic keyboard is often subject to the Flock of Seagulls treatment, wherein one key at a time is held down. :) It's roughly like touch typing vs. hunt and peck.
But aside from that example, yes, I think you're onto something.
As it is pointed out in the comments above, x86 has surprisingly much of a duct tape in it. There is a lot of much more saner platforms, but programming still lives happily on x86. Why? Because it does not matter at all so often. x86 is hidden from us with OS, compilers and virtual machines. That's where the beauty of programming shines: if you have a lame implementation below your level, just abstract it away as far as resources allow you (e.g. full-scale managed memory programming is theoretically possible in DOS but noone did it because of CPU/memory constraints). Another example: Win32 API and MFC and other native stuff is horrible, but .NET platform seems to be quite sane. Javascript is horrible, but jQuery is, um, beautiful.
On the other hand, a hardware guy still operates with an abstraction of a microcontroller. If memory is corrupted, his abstraction fails also. It's just that the HW guy does not have to invent good abstractions often (or rather at all).
The point is, abstracting may be self-healing: if something is broken at some level, a level above can use the good parts to build a fully functional emulation.
I have recently had to work with C++ on some systems-level code. It's the lowest-level I've had to work on for a while. I find that having to think about things like memory management and system peculiarities like buffer alignments gets in the way of thinking about the higher level algorithm or problem. In this case I did not have the luxury of being able to build abstractions around some of the quirks I encountered.
On the other hand, when I work at a high level with a suitably high-level language, I can recognize patterns and abstract them away. For example, I've seen many instances of the pattern
b = a.f(); if (b != null) { c = b.g(); if (c != null) {...}}
This pattern is fairly common, and in a suitably rich high-level language I can abstract it and just write c = a.f().g();
It saves me a lot of thinking and honestly feels a lot easier.* high-level is more productive in the short run, but may hinder you in the long run
* mid-level (C-ish level) tend to be more portable and more future proof
* low-level will probably give you better resource usage (speed, memory etc), but not necessarily these days
Personally, I find C++ (plus open source libraries) gives the best trade-off, but that's probably dependent on the task (I do audio/video analysis/synthesis).
Another way to look at it is, just as in all other spheres, I enjoy the abstract intellectual part and hate the human part.
I can relate to your description, as it seems to be similar to my experience between back end development using Django / Python, which I found well put together and well thought out.
Compare that to front end Javascript frameworks, and (it may just be my lack of experience in JS) everything seems far more fragile, poorly documented, incomplete.
Says somebody who clearly never had to deal with cheapass webcams in Linux... :)
Sure, there's an ubersmooth learning curve with HTML/CSS/Javascript, but once you get to heavy client side SPAs and supporting every device and format under the sun and then someone say "it's not good enough, try and make it feel like a 100% native iOS app - and no, you can't just write an iOS app" ...and good luck hiring a good front-end guy (at least if you don't do the expensive way of "hire 5 instead and fire 4 of them after two months" route).
Also, as a caution against asserting low level means "easy", I will take this opportunity to drop one of Murphy's Laws:
"An easily understood, workable falsehood is more useful than a complex, incomprehensible truth."
sometimes written as "All abstractions are wrong, some abstractions are useful."
As an example of a low level abstraction that is both useful and wrong, consider the libc strtod() function, which converts a decimal string to a native floating point representation. If I were to give you the pre-parsed integer significand and integer exponent, base 10, then you'll find there's no mechanism in the C standard library to convert those two integers to the correct double value, despite strtod() having to do the very same thing, at some point after the parsing stage. If all you ever want to do are string to double conversions then this function will always have appeared quite low level, but the reality is that this is only the case because it's always been so damned useful.Low level things are intrinsically useful and the more useful something is the less wrong it seems.
Yeah, the higher level is things that are also rejected at large by the industry and moribond in research like DSL, code generation, modelisation, algebra stuff and proved programs. Things that exists more or less at every level but widespread in CPU making.
If I understand correctly, the OP is basically saying the same thing. The complexity in the higher-levels comes in when we try to create an API further up the stack which is responsible for manipulating the more understandable data into something that the device understands at a low level. The difference in retrieving a file stream vs getting the file in standard utf-8 format, which anybody can read.
What are your thoughts on this. Have I got it right? Or am I on the right track?
IIRC pg has an essay about this.
How floating points are represented in computers is such a neat hack, yet it can be done with a simple formula on pen and paper.
Doesn't mean you can come up with.
Everyone knows E= mc^2. It's easy, But you didn't come up with it.
However, there are aspects of low level programming which are far more technical than anything you are likely to run into programming UI's or the like. Thread scheduling, OS development, compiler development, standard library stuff, etc. tend to be quite challenging from a technical perspective (I've done all but one of them.)
The author is picking one type of low level development and painting the rest with the same brush. Low level development occurs on more complicated architectures as well.
Of course, high level development can be challenging as well, but often in a different way. The challenge here lies in understanding the quirks of your libraries, creating a good user experience (terribly hard at times, but not often technically challenging), working around oddities of your platform, etc.
>And it sucks when you change a variable and then the other processor decides to write back its outdated cache line, overwriting your update.
Well... that's why you use memory fences (volatile if your language supports it) even for writes on types which would be atomic.
My observation of the programmers with whom I've worked, and whom I know socially, has been that a given hacker's degree of intelligence and capability tends to be inversely proportional to the thickness of the stack on which he chooses to base his efforts; the smartest person I know, who has multiple doctorates and is currently busy breaking new ground in bioinformatics, doesn't even use libraries if he can help it, and complains constantly about the frustrating misbehaviors of those which turn out to be irreducibly necessary. Perhaps I've merely been observing a constellation of coincidence, but on the whole I rather doubt it.
(P.S. for the benefit of anyone who feels like I've just called him stupid: my last gig was a Rails job, and if you took all the programmers I've ever known and broke their smarts up into quintiles, I'd be somewhere in the upper second, maybe the lower third. It doesn't take genius to recognize genius at work; I've merely been uncommonly fortunate, for the most part, in my choice of friends and colleagues.)
(P.P.S. for general use: Speaking of recognizing genius at work, it scintillates in every jot and tittle of the article here under discussion.)