what i wanna see
Some people like to ruminate on their 'ideal programming
language'. I get it! It's fun. I'd love to do that. Static
types with perfect inference with instant compiling and amazing
optimisations... But the shadowy cabal
that control my website
say that I have to write about actual languages and how I use/would
like to use them. Damn those meddlers!
I think there's around 3 general types of language that I would
have a use case for. These I can present - by god - in the Business
Eight Lambda Effective/Effectful Matrix for Workgroups. Please...
enjoy... (the room darkens and the whirr of a slide projector is
heard)
BELE/EMfW |
"Interpreted" |
"Compiled" |
Static types |
"Workhorse", fairly major projects that don't rely on very high
speed/very low requirements |
Systems programming, very fast e.g. competitive
programming |
Dynamic types |
One and done things, quick scripts, quick REPL computation,
etc. |
Can't see usecase - with my definition of "Compiled" |
Now, there's quite a few questions one could ask about this
table. Let me just pre-empt the most important one now and answer
the others when we get to them. So, why are there quotes around
"Interpreted" and "Compiled"? That's because I use "interpreted"
and "compiled" in the colloquial sense as a distinction of
languages, a colloquial sense that actually bundles it up with many
other - arguably more important - properties of a programming
language that are arguably orthogonal to each-other but seem in
practice to "go together". These properties are - in order of
importance - "has a REPL", "has a garbage collector", "has a fairly
fast write-run cycle". I put having a REPL first because I think it
is by far the biggest qualitative change to how programming in a
language works. A REPL enables exploratory programming, just poking
around and having a go and seeing what happens. This is great in so
many ways. Having a garbage collector "free"s you from tedious and
error-prone manual memory management, and similarly tedious but
non-error-prone Rust semantics. The final entry in my list, a fast
write-run cycle, is what is often the actual effect of an
interpreter on a language (apart from how it affects design
considerations). But a fast AOT compiler or a good JIT can have the
same effect - enabling iterative programming. So for example,
Haskell would be 'interpreted', but Go would be 'compiled'.
Oh, one more thing - I will be classifying languages without any
capabilities for procedural programming in a seperate section at
the end. One less charitable would say that it is not in the
Business Matrix because Haskal no jobs, but really it's just
because though I like languages like Haskell and Idris [and
presumably Forth] for hobbies, I am more comfortable when there is
a mutable 'escape hatch' [or... uhh... you know... variables] in my
daily programming.
criterion collection
paradigm
I like a healthy mix of procedural features and functional
features in my languages. A language without any functional
features is out-of-date, in my opinion, since functional languages
have been driving almost all of the research in programming
languages today (and not without reason - they are beautiful). A
language without any procedural features is Haskell, which I have
dabbled in enough to understand monads but not enough to be truly
comfortable in. (Plus mutable data structures are so often the
correct way.)
I'm fairly agnostic to object-oriented features. The good bits
about them are easy to emulate with any fairly capable type
system.
performance
Performance is fairly important to me. My model for performance
is essentially "not Python." Python is dog shit slow. Performance
is self-evidently important in systems programming, but it's also
important for just your daily programming. Why? First of all,
multi-tasking real. On my laptop I have many programs open at one
time - and two of these will be running browser processes, one the
browser and one fucking Discord. Both of these programs are
disrespectful to my CPU and especially my RAM - although in the
case of the browser it's really the many terrible webapps I use
that are disrespectful. So I will be running this program, most
likely, in an environment that can go down to as little as 500 megs
of free memory (although when it gets that bad, I close some tabs),
and possibly some software-decoding-video amount of CPU used.
Secondly, waste of resources. I never waste food put in front of me
- why should my computer do the same? 8 gigabytes of RAM was once
'the dream' for me, but now that I have got it my browser tabs have
expanded to fill 7 gigs memory and 3 gigs swap. Eight logical cores
would also have been a fantasy to me, and yet shit so often runs
like shit. Why? Stop it. Finally, server cost. I'm using a shitty
ass free AWS server right now. If I have to pay for a better server
I will kill myself.
network effect
In other words, libraries. Libraries are great. Me,
doing less work... could it really be possible? Good, high
quality libraries are a function of the user base of the language.
Hence Python and Javascript have a lot of libraries, while Julia
and SML do not.
concurrency
When you need this, you really need this. Python sucks at
CPU-level concurrency, but async/await is a good facility for
IO-level concurrency.
Dynamic interpreted languages
In the Business Matrix I said that I would use these for "quick
scripts". Currently I customarily use Python for these projects.
Looking at the Python scripts I have on my computer, I have a lot
of prototypes for programs that I eventually rewrote in C++ for
informatics, some quick jokes I made for my friends, and some
one-off scripts to do some data processing/print some specific
unicode sequence/apply a map projection to an image/etc. In
addition, when I've wanted to play around with a string/some data
and find some metrics/transform it in some way, I usually reach for
the Python REPL. This is precisely the use case that these
languages are meant for.
- Python: I'm very familiar with this, and I can
very quickly translate my ideas into code. In addition, its
philosophy of 'explicit is better than implicit' keeps things
simple. The problem here is the speed of CPython. CPython runs on a
bytecode interpreter with essentially no optimisations. It
can easily run 100 to 1000 times slower than native code. PyPy is
only around 4x better, which is abysmal for a JIT. In addition, the
infamous GIL rules out any shared-memory concurrency. And let's not
even get into package management.
JavaECMAScript: JS has the best JIT for
this class of language, in the form of V8. It also has a lot of
libraries. Unfortunately, it is held back by 20 years of cruft
(===, var/let, automatic semicolon insertion, only doubles), the
most egregious of which is the ubiquitous implicit coercions to
string. Further, the node package ecosystem is notoriously
unhealthy. Also node is like a gig wtf
- Julia: Julia has an impressive record of
performance and seems to have a mostly sane feature set. For
numeric data munging, it seems unsurpassed. Its main drawback is
its small user base and hence small library base. The amount of
libraries is crucial for this use case, since often what one does
with a scripting language is glue things - libraries - together.
This could be a good language for workhorse programming for someone
who prefers dynamic languages. I don't, though.
- Ruby: is about as fast as Python, and has less
libraries. It also seems to think 'implicit is better than
explicit'. My code doesn't need any help to become an unreadable
mess.
- Perl: again, 'implicit is better than
explicit'. Sometimes I use this to do sed over multiple lines.
- PHP: lol
- Common Lisp: The powerful metaprogramming
facilities of Lisp shine in medium-to-large projects, but are best
avoided for scripts. This also does not have many libraries. It can
be compiled AOT, which is very nice and also quite performant.
Workhorse possibility for dynamic-lovers who can get over the
parentheses.
- Scheme: Scheme is a great programming language
for educational use. It does not have enough libraries for most
purposes. Let me, though it is incorrect, also count Racket as a
Scheme: Racket, though a great development environment, is at its
heart a research project. Not great to write scripts in.
- Smalltalk: Smalltalk really wants to take over
your entire computer. It's a great idea, but not how computing
shook out.
- sh: is another thing I use to write scripts
frequently. Is great if most or all of the program is possible by
simply calling other programs. For anything more complex, move to
an actual scripting language.
- R: is for data scientists. I don't actually do
proper statistics, teacher's t tests and everything. Not for
me.
- Raku: aka Perl 6. Tries to be everything to
everyone. Hasn't got many libraries.
Verdict: stay with Python for glue code, look into Julia for
pure data munging.
Static interpreted languages
I am currently 'between languages' for this category, the
"workhorse" category. Literally! The higher level stuff I write in
Python, the lower level in Rust. e.g. I wrote a platform for
playing UNO Nomic (possible post?) with my friends in Python, but
an interface to the game of Whist in Rust. Keep in mind that these
languages can very well be compiled so long as there is a REPL and
the compiler is reasonably fast.
- Haxe: I wrote a game with a friend a while ago
in this. I came away with the impression that the language was
really half-assed.
- Scala: A JVM language! This has, from my
limited understanding, a type system that includes subtyping. I
really like Haskell, and Scala is similar to Haskell without the
necessary purity. However, I don't want to use a JVM language as my
workhorse. Native versions would lose the great advantage of Scala,
which is the Java libraries that can be used in Scala.
- OCaml: MLs are also very similar (very very
similar) to Haskell without purity and with eager evaluation. OCaml
has a larger userbase, but SML (below) has (imo) taken (marginally)
better decisions and also runs faster. OCaml's performance is
nothing to sniff at, though. Libraries for old things, not for new
things. Gripes: ML's generic syntax is pretty unfamiliar to someone
who has done quite a bit of Haskell before: 'a list? In addition:
Typeclasses are a better facility than modules/functors IMO. My
ideal ML would be very similar to simply a garbage-collected Rust,
with all the memory-management types ripped out. -- OCaml had
something very similar to Python's GIL - until just 2 days
ago.
- SML: see above. MLton is a very nice
whole-program compiler.
- F#: An ML like OCaml. Would be nice if I were
into the whole Microsoft world.
- TypeScript: This seems to have a very general
type facility. Not a fan of the gradual typing. Needs JS runtime,
which is large.
- Nim: Nim is very Python-like, with all that
that entails. Not many libraries.
Verdict: Look into OCaml and SML (probably OCaml).
Static compiled languages
I use Rust here for my own projects and C++ for informatics.
There's really only 3 languages that matter here, imo.
- Rust: Rust is a great language. Unboxed ML.
Has many many libraries. Very performant. Hard, though, to
implement some data structures that use pointers.
- C++: C++ has many great facilities. Does that
make it a good language? Not really. Does that make it OK for
informatics? Hell yes. In competitive programming we don't free
shit. No memory unsafety - just like Rust! More seriously, there's
advantages in using the same language everyone else is using -
especially the advantage of being able to submit code to the online
grading system.
- C: I'm going to make a rather bold statement
here: C will never go away because C is how the computer works.
Now, there's been a lot of blather about how 'C is not how the
computer works'. This is correct - the microarchitecture that your
CPU runs on does not correspond very well to C. But Intel employs a
legion of engineers precisely in order to hide that microcode from
you and expose an interface very similar to a "fast PDP-11". If you
understand C, then you understand the fundamental model that your
processor is trying to make it look like you run on. Now, is it a
good thing that there is all of this microarchitecture and
speculative execution and out-of-order execution and caching in
order to make this remotely fast? Maybe not. But this is the path
that the industry went down - VLIW didn't work, for technical or
social reasons - and it seems that it is the path that we, for the
foreseeable future, are stuck with. To learn the model, it really
helps to know C. And to learn how the CPU really works, it really
helps to know the reason that all of these mechanisms are
implemented (C) and why they break down. -- Also every library ever
that can be used via FFI is written in C.
- Go: Go has fearlessly entered 2002 with its
adoption of generics, we hope one day it discovers sum types. Lol
GC
- C#: Could be good; I won't enter
Microsoft
- Java: 8 billion devices are WRONG. Boilerplate
hell. Really into OOP - "what's functional?"
Verdict: Continue as usual: Rust for personal, C++ for
informatics, C for very interoperable libraries.
The weird stuff
I love Haskell. I love Forth. I love Intercal(?). But I also
like mutation, I like variables, and I like, errr, life. Maybe I'm
addicted. But you forget one thing, evangelists... I don't want
to quit.
- Haskell: Laziness is really fun. Purity is
funner. Monads are great, but they ain't state. Type system is a
bit of a mess with extensions.
- Idris: I really love playing with Idris.
Hole-driven development is a game-changer. I've probably - no,
definitely - used Idris more than Haskell. Idris 1 is very slow,
but Idris 2 is quickly speeding up to Chez Scheme speeds.
Unfortunately, though provin theorems is fun, it's not something I
want to do while writing a project. No libraries - really!
- Agda: The Unicode symbols break my inner
parser.
- Forth: Libraries? Which Forth system? I use
this on my calculator. If I ever had to get an operating system up
in 8KiB of RAM, I would write it in threaded-code FORTH.
- Intercal: No flaws
Verdict: Have fun!
to end...
Whew, I'm exhausted. I'm going to look into OCaml and Julia. See
you, shadow government !
back