Programming thread

Interesting that you bring that up in relation to Java. A thought just hit me: is the JVM a very advanced Lisp machine? Or more correctly, a SECD machine? Something here is scratching at the back of my mind. JVM bytecode is all about stack operations. SECD? Forth? We have to investigate.
But the amount of reflection and dynamic shit you can do on the JVM and the CLR does bring them some fraction of the way to Lisp compared to C++, which is why you can have Clojure and Groovy on the JVM.
Haha, I was wondering when we were gonna finally go full Greenspun's in this thread!
Greenspun's 10th Rule of Programming said:
Any sufficiently complicated C or Fortran program contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.


@cecograph made a good point about the JVM's inability to deal with TCO though. Guy Steele's been talking about this for almost a decade, and I remember Rich Hickey noting that it was one of the issues he had to deal with when making Clojure too (recur and trampoline are useful, but not entirely satisfying, compromises). Still haven't been any developments in that direction yet as far as I can tell.

On Ruby, I don't think there's anything immediately stupid about being able to add methods to live objects, so long as you get into the Lisp mindset, where Lisp gets to eat the world. Your IDE and debugger should not only be written in Lisp, they should share the runtime with the programs you're writing. You then realise you want a way to add methods to live objects because you want to be able to write IDE, debugging and patching tools that do this, not because it's a sensible way to solve your initial problem. You can still get a feel of this sort of mindset in Emacs.
That's a fair point. I grill Ruby a lot whenever I have to use it for work, but at the same time I have to admit programming in it always feels really fun for me, in almost the same way that programming in Lisp does. It's definitely borrowed a lot from Kay's Smalltalk philosophy of "everything is an object!" (and in Ruby's case, this includes classes themselves, and also the interpreter top-level).

Your talk of the Lisp mindset reminds me of those old 'war stories' that the early 80-90's programmers tell about how using a Symbolics Lisp Machine was the best development environment that has ever existed on this Earth and that nothing since even compares. Not sure how much of that is just rose-colored glasses, but I almost get second-hand nostalgia hearing how wistful some of these guys get about those machines:
lispmachine.jpg
symbolics.jpg

genera_boot.jpg
 
Rust's memory safety is supposed to come from the type-checker, and that should be verifying type correctness before you move to a representation like SSA/CPS.
The type system allows for memory safety rather than ensures it. The part that does most of the actual checks is called the borrow checker. I figured it would run after SSA because it requires doing things like CFA but apparently you're right that it doesn't. It runs on what's basically an AST and does it's own analysis, so a lot of what it does has to be redone later. They're in the process of fixing that now though.

LLVM is a shit-show.
It's really good in some ways. It's just slow and it hogs memory. I can't really fault them for this. Premature optimization and all that. The Rust people are getting round to it now though. Compile times is at the top of their list of priorities. Apparently they've managed to get Rust compile times down by about 30% in the past year or so and they're nowhere near done.

It seems like it's a bit of a sticky situation for them though. Looking at the architecture they want to move to it doesn't seem optimal either. I mentioned SSA and optimization because a lot of the work that goes into doing that is the same stuff that's needed for their brand of memory safety. This would imply that handing off a high level representation to LLVM and then getting back something that you do the borrow checking on would be optimal. They seem to want a strict Rust -> LLVM architecture though. Still, I imagine that will be fast enough to be practical.

Actually, several unikernel implementations exist that have indeed rewritten the OS in a memory safe language.
Sure, there are full-featured OSs that use memory safe languages. I even mentioned that there's a verified kernel which is safe in every way, not just memory, and yet nobody uses it. The problem isn't just rewriting the OS. It's rewriting everything from the OS up and having it be adopted. There's something of a catch-22 there, nobody adopts systems with no software, nobody writes software for systems nobody has adopted. Obviously that's not an absolute. People do write these things, and maybe, I hope, this time will be the time we get over the hill but I think 20 years is a lot more realistic.

Unikernels are a sore point for me. I love unikernels because I love low to the ground no fluff stuff. I dislike hypervisors for pretty much for the same reasons. I hate cloud shit because I'm very skeptical of using someone else's shared machine. So it's bittersweet that this is the environment where they stand a hope of catching on.

There are problems with memory safe and systems programming as well. There needs to be relatively liberal use of whatever mechanism you have for allowing unsafe stuff and inline assembly is a must. Of course this all gets nicely abstracted away but that should always be the case. The real benefit of memory safety in systems programming isn't that it's better for the domain, it's worse in lot of ways. It's just that security and integrity is so important there.
 
due to all the mentions of lisp, I decided read what it is all about. I found and read this article that explains the origins of lisp and what kind of status it has in the programming community. it certainly sounds special in its concept. is it true that it is a language above programming languages? what inherently about it made it so well suited for AI research and development?
 
  • Like
Reactions: Yotsubaaa
due to all the mentions of lisp, I decided read what it is all about. I found and read this article that explains the origins of lisp and what kind of status it has in the programming community. it certainly sounds special in its concept. is it true that it is a language above programming languages? what inherently about it made it so well suited for AI research and development?
It really depends if you ask a Lisp Jihadi like me or someone sane. Lisp is one of the only homoiconic languages, which makes metaprogramming trivial, as you can manipulate the language with the language itself (what's called macros in lisp). People used to think it was the holy grail for AI as it opens up the option to write self-modifying code, which "learns". But symbolic AI failed.
 
The (potentially non-final) source code for 1989 NES game "Raid 2020" has been released. It was written with a "engine" called the "NES Quest Game State Machine" by Dan Lawton, founder of Color Dreams, an unlicensed NES game studio. The code has extensive comments that border on documentation: https://archive.org/download/Raid-2020-Source-Code
FUCK YES
I know that color dreams had a bunch of wierd dev tools lying around so this is cool.

They also used some strange multiplatform shit for spiritual warfare.
 
The type system allows for memory safety rather than ensures it. The part that does most of the actual checks is called the borrow checker.
The borrow checker is part of the type checker, and the types it works on appear explicitly in Rust code in the form of lifetime annotations. I haven't looked at the Rust compiler, but normally you do type-checking by transforming an untyped or partially typed AST into a fully typed AST.

When Rust folk want to talk like computer scientists, they'll say that the Rust memory safety comes from its type system being affine. Affine and linear type systems let you prove stuff about ownership of data and lifetimes, but are pretty horrible to work with naively, because you end up having to give up ownership by explicitly handing back data you don't need to your caller. Since the early 90s, when this sort of thing was first tried, it was realised that you can could make this implicit by using a borrow checker.

You may know more than me about this, but I don't see how SSA passes help do what Rust wants to do in regards to memory safety, which includes eliminating use-after-free bugs in a way which imposes restrictions on the sort of code you can write: the compiler needs to be able to prove when heap allocated values are no longer referenced, but there will always be programs that have no use-after-free bugs which cannot be proven so by a given compiler.

Your talk of the Lisp mindset reminds me of those old 'war stories' that the early 80-90's programmers tell about how using a Symbolics Lisp Machine was the best development environment that has ever existed on this Earth and that nothing since even compares.
Oh, I've read loads of those war stories, but I was still a kid in the 90s, and all I had was a shitty PC and C++. I got into Lisp in the early 2000s after reading a lot of internet evangelism from smug Lisp weenies and usenet trolls like Erik Naggum, which included a few war stories from folk still holding onto Lisp Machines and hating everything UNIX.
 
Last edited:
The borrow checker is part of the type checker
Oh, I see. In the architecture of the compiler they are separate components that each have their own pass. In the new architecture the type checking is done before the translation to MIR, the borrow checking afterwards.

Also I was wrong before when I said they were in the process of moving from old to new, they finished a year ago. They just haven't update their documentation.

I don't see how SSA passes help do what Rust wants to do in regards to memory safety
Rust has non-lexical lifetimes so the borrow checker needs to do a CFG. It's not that SSA itself is useful, it's that there's a lot of redundant work between that and the conversion to SSA. I figured they'd let LLVM do that work during its SSA pass and check the results afterwards but I was wrong there.

there will always be programs that have no use-after-free bugs but cannot be proven so by a given compiler.
I know this is a true statement, but I find that in practice nobody actually wants to write those programs anyway. It reminds me of a myth that went around cryptocurrency circles after one of the big hacks. "You can't formally verify a program if it was written in a turing complete language." Not to be confused with the actually true "You can't verify all programs that could be written in a turing complete language." It was pushed by people with an interest in a push down automata based alternative. Considering they were talking in the context of simple financial transactions software it was obviously bunk but it was easy to google up the wrong answer and so it gained a bit of traction.

I don't doubt we'll end up with compilers that can do detailed enough analysis that they can prove the safety of say C programs so long as you don't do something silly like try to do an out of bounds write if ZFC is inconsistent.

You may know more than me about this
No, I don't think I do. Honestly, I just got lucky here. If this was a few years ago before the non-lexical lifetimes, I'd have probably said the same thing (though not the second post) and I'd have been wrong and you'd have been right. When I first spoke on it I was coming from a position of "they do as much analysis as you could ever need anyway and safety checks are either mostly redundant or computationally relatively simple in comparison." I know a lot about compiler design and code optimization and I've worked with LLVM enough to know that it's the speed/memory problem. I didn't know that much about Rust beyond the basics frankly I still don't beyond what I've read recently about the compiler architecture. I was making assumptions and I lucked into being correct. It's a good thing you were here to set me straight.
 
Your talk of the Lisp mindset reminds me of those old 'war stories' that the early 80-90's programmers tell about how using a Symbolics Lisp Machine was the best development environment that has ever existed on this Earth and that nothing since even compares. Not sure how much of that is just rose-colored glasses, but I almost get second-hand nostalgia hearing how wistful some of these guys get about those machines:
Sure, there are full-featured OSs that use memory safe languages. I even mentioned that there's a verified kernel which is safe in every way, not just memory, and yet nobody uses it. The problem isn't just rewriting the OS. It's rewriting everything from the OS up and having it be adopted. There's something of a catch-22 there, nobody adopts systems with no software, nobody writes software for systems nobody has adopted. Obviously that's not an absolute. People do write these things, and maybe, I hope, this time will be the time we get over the hill but I think 20 years is a lot more realistic.
I totally agree, I think a new paradigm OS from the ground up is a pipe dream. (If not a very seductive pipe dream.)

However, with the unikernel system I'm looking at (mirage OS), it's still compatible with the vast majority of Ocaml libraries I use (and probably most ocaml programmers) regularly. Like for example, the most popular, the best (at least to me), the most efficient http implementation (both client and server) is Cohttp. It's already written in an abstract way that permits adapting it to mirage OS. They already have mirage OS libraries.

So basically, if the unikernel system you're looking at is well designed, and if the language community keeps abstraction high in its list of priorities, porting shouldn't be too difficult.

With mirage OS, and with the projects I'm working on, I basically will need to write some special main function, and a different IO module that's mostly the same in structure (if not literally the same in every way). It's basically a few percentage of my overall codebase.
Unikernels are a sore point for me. I love unikernels because I love low to the ground no fluff stuff. I dislike hypervisors for pretty much for the same reasons. I hate cloud shit because I'm very skeptical of using someone else's shared machine. So it's bittersweet that this is the environment where they stand a hope of catching on.
Yeah... cloud shit is kind of unfortunate because inherently speaking, the cloud operator can examine all your memory and storage and CPU state. No way around that. Now, if you want to, you can roll out your own cloud installation with your own hardware. But that's absurdly expensive. Almost eliminates the benefit of using cloud technologies in the first place.

If you want to start up some small operation with a handful of guys, and pay literally only for their rents and some ramen, and get as big as possible, as quickly as possible, you just kinda inherently need to trust AWS or Dreamhost or whatever a little. Shit sucks, but it's kind of the way things are now.
There are problems with memory safe and systems programming as well. There needs to be relatively liberal use of whatever mechanism you have for allowing unsafe stuff and inline assembly is a must. Of course this all gets nicely abstracted away but that should always be the case. The real benefit of memory safety in systems programming isn't that it's better for the domain, it's worse in lot of ways. It's just that security and integrity is so important there.
I agree that problems can arise with purely memory safe systems. Although I disagree that there should be "relatively liberal use". I think it should be pretty rare, but permissible in situations that you've benchmarked, where you are very confident it will help. Most performance issues will come down to a few bottlenecks that you can eliminate with a few well placed native/unsafe optimizations.

And I disagree that memory safety isn't better for the domain as well. I'd argue that unsafe memory approaches, where you are more intimately involved with moving memory around and managing pointers and such, is just unnecessary. If you write 50 libraries, with unsafe memory systems, you're going to be writing low level structure code 50 times that's 99% the same in most cases. You're unlikely to innovate in most of those cases. In a few cases, you might make a structure that's particularly adapted to the issue, but I don't feel like it justifies all the other manual labor you sunk into the other projects.
 
  • Informative
Reactions: Yotsubaaa
Oh, I see. In the architecture of the compiler they are separate components that each have their own pass. In the new architecture the type checking is done before the translation to MIR, the borrow checking afterwards.

Also I was wrong before when I said they were in the process of moving from old to new, they finished a year ago. They just haven't update their documentation.

Rust has non-lexical lifetimes so the borrow checker needs to do a CFG. It's not that SSA itself is useful, it's that there's a lot of redundant work between that and the conversion to SSA. I figured they'd let LLVM do that work during its SSA pass and check the results afterwards but I was wrong there.
Okay, this sounds right to me. Thinking again, it seems that a borrow checker would need to know the evaluation order of your code, and that's the main thing that SSA/CPS spells out.

My feeling on this stuff is that Rust should aim to implement its own backends, and have LLVM as an option where it affords more portability.

I know this is a true statement, but I find that in practice nobody actually wants to write those programs anyway.
Oh, they do, and that's why Rust programmers are expected to get used to working with reference counting pointers. You use reference counting because you can't figure out at compile time when your data can be freed, so you decide the matter at runtime. And even then, you're not going to have a good time with reference counting and cyclic data structures.

What Rust's borrow checker will still do is prove when the reference count is decremented.

I don't doubt we'll end up with compilers that can do detailed enough analysis that they can prove the safety of say C programs so long as you don't do something silly like try to do an out of bounds write if ZFC is inconsistent.
I'm a pessimist on that one. It's a worthwhile point to make that no-one working in verification thinks that the incompleteness theorems are that big a deal for what they're trying to do. But incompleteness isn't the ball-ache; it's the complexity. Being told that my first-order ZFC theorem prover makes just about any mathematical problem semi-decidable is no help to me if it doesn't give me a proof within my lifetime.
 
Last edited:
  • Like
Reactions: Marvin
I think a new paradigm OS from the ground up is a pipe dream. (If not a very seductive pipe dream.)
Yep, sad to say it is. It's also my hobby project but it's not going to go anywhere special because I don't have bell labs behind me.

the cloud operator can examine all your memory and storage and CPU state. No way around that.
Yeah, especially with the new class of intel bugs even the other customers on the same host can read data you're processing. Homomorphic encryption could solve this but it's still a long way from being practical.

in situations that you've benchmarked, where you are very confident it will help.
Oh, it's not about efficiency. It's about necessity. There are all sorts of hardware reasons that you need to manage some memory regions differently. For instance you'll need memory ranges that your ethernet card will read and write packets to and from. Same with the inline assembly, there are instructions to do things like control interrupts that compilers just don't generate.

I agree with the principle though, it's not something to do if you have the option to do it safely.

My feeling on this stuff is that Rust should aim to implement its own backends
Even with their big dev team and budget it would be years of work getting even one target to comparable optimization. I think they'd be better off contributing upstream. LLVM isn't fundamentally flawed, it just has the usual problems like tons of unnecessary copying and a dev team that aren't concerned with efficiency.
 
I read so much here about Lisp that I decided to ask what book would you recommend to a gorilla about Lisp? I have this book on my Amazon wishlist: https://www.amazon.com/Structure-Interpretation-Computer-Programs-Engineering/dp/0262510871, but I'm not sure if it's the best book to start.

I know, I know, you can't learn programming from books. However, currently I work primarily in C, and I want to enlighten myself about other better worlds.

SICP? Just pirate it. No reason to order the dead tree version.

Pirate Let Over Lambda while you're at it.
 
I read so much here about Lisp that I decided to ask what book would you recommend to a gorilla about Lisp? I have this book on my Amazon wishlist: https://www.amazon.com/Structure-Interpretation-Computer-Programs-Engineering/dp/0262510871, but I'm not sure if it's the best book to start.

I know, I know, you can't learn programming from books. However, currently I work primarily in C, and I want to enlighten myself about other better worlds.
No need to pirate it or purchase. It's freely available in a great format here: https://github.com/sarabander/sicp
Second, you'll need an environment to work in. Download Dr Racket here: https://racket-lang.org/, don't forget to configure it to use Scheme.
Afterwards, before or in tandem, you might want to work through The Little Schemer, which you could pirate., for example here: https://7chan.org/pr/src/The_Little_Schemer_4th_2.pdf
The MIT lectures are also available on youtube: https://www.youtube.com/watch?v=-J_xL4IGhJA&list=PLE18841CABEA24090
godspeed, gorilla
 
SICP? Just pirate it. No reason to order the dead tree version.

Pirate Let Over Lambda while you're at it.
Also, this isn't really helpful for merely learning a Lisp, but if you're interested in compilers (for any language, but obviously building a compiler using sexps makes things a lot easier), Lisp In Small Pieces is a fascinating book.
 
Wow, this thread has been popping. So pardon the massive multi-quote.

With some industries in tech it seems like if you're past 40 and don't want to do something different, you might as well kill yourself. I assume many people here are in their 20s and in your 20s you don't think you'll ever be 40+ or even 50+. Believe me, those dates come, they come (and pass) faster than you think and people will start treating you differently automatically no matter how you actually are and even if you still feel a good early-to-mid-30s on the inside.

The freelance game is tough since you have to find your own clients, and sometimes, even if you have lines to a few good ones, they simply don't have any work for you to do. But I have never once had a potential client show concern over my age (I'm vaguely around 40-ish) or did I ever get the impression I didn't get a gig because of it. I can't speak of how real the age discrimination may be in the standard employment side of the industry, but if you're really concerned about it, I suggest transitioning to freelancing when possible.

You can compare them by reflecting on a general problem we're all trying to solve in programming: I want to write a function that takes a value, and that value can conform to one of several different and incompatible layouts. Say, in one case, the value is an integer. In another case, it is a string paired with a floating point value. In a third case, it's an array. How do I model this?

The object oriented approach is to use runtime polymorphism: have it so that I talk to the three different kinds of value through a common interface.

The C approach is to mash the three layouts together into a union and have some extra means of determining which of the three layouts you're working with, possibly using a type tag field. If you fuck this up, expect your program to break horribly. This is where C fails badly in terms of type-safety.

If it's not too much trouble, do you think you could give us a code example of this? It's been a long time since I've earnestly used C so I *think* I get what you're saying here, but I'm not sure.

1) Don't be afraid to lie on your CV, fake it until you make it, worst thing, that can happen is that you do not get the job - tried and tested by yours truly, worked for me when I moved from support monkey to dev, I've now worked on several multi-million projects successfully.
The pajeets do it, quite brazenly sometimes. Seems to work for them. I will neither confirm or deny that I too have overstated my competence at certain skills on an initial interview and then actually bought books or otherwise boned up on said skills right after hanging up.
This brings up a question I'd like to throw to the wider audience - when do you get to call yourself a Senior Developer on your CV / Linkdin?

I would never call myself that, but before freelancing I had positions that had that title before, and they're listed on my CV as such. (The first time I was a "senior developer," I was definitely the most junior dev there in terms of years of professional experience, and objectively less skilled than most of my co-workers as well… yet I was still a "senior developer." Come to think of it, I don't think we had any developers that weren't "senior.")

I'm an idiot who googles shit all the time.
It pays well.

rlwzi9sjpt141.jpg

And don't you want to be able to finish half-assed class implementations at runtime by idiomatically redefining method_missing like a fucking degenerate, data security and encapsulation be damned?

This is basically how objects already work in JavaScript. God, I hate JavaScript.
 
If it's not too much trouble, do you think you could give us a code example of this? It's been a long time since I've earnestly used C so I *think* I get what you're saying here, but I'm not sure.
Here's the example from "The C Programming Language":

Code:
struct {
  char *name;
  int flags;
  int utype;
  union {
    int ival;
    float fval;
    char *sval;
  } u;
} symtab[NSYM];
It's supposed to represent an example from a compiler where you want to define the constant symbol table. The union says that a constant is either an int, a float, or a string. A function which traverses the symtab array will very likely inspect the "utype" of each element to see what exactly is held in the union, and act appropriately.

In Rust, you would implement the same thing using an enum, and the possible values of "utype" would become the variants of the enum. In Java, it would be normal to replace the structure with an interface and have the three different sorts of constant be three different classes implementing the interface. The C way is very C in having no type-safety and blowing up in your face if you misuse the utype field. The Java way is very Java in being so painfully verbose that you want to kill yourself if you ever have to write this sort of code. The Rust way is the correct way, because it's actually a well-designed language.

I read so much here about Lisp that I decided to ask what book would you recommend to a gorilla about Lisp? I have this book on my Amazon wishlist: https://www.amazon.com/Structure-Interpretation-Computer-Programs-Engineering/dp/0262510871, but I'm not sure if it's the best book to start.

I know, I know, you can't learn programming from books. However, currently I work primarily in C, and I want to enlighten myself about other better worlds.
"Structure and Interpretation of Computer Programs" is very very good, but more of a schemers book. Paul Graham's book "ANSI Common Lisp" is pretty good and somewhat evangelical. "Let over Lambda" looks cool.

The most mind-fuckery book on Lisp I have read is "The Art of the Metaobject Protocol," which Alan Kay described as the best book on OOP in ten years. It's a book which ostensibly teaches the Common Lisp Object System. However, it expects you already to be pretty familiar with the Common Lisp Object System. This is not a mistake.
 
Last edited:
Back