Designing a programming language is fun.
First, there are a million choices to be made — it’s like if you’re buying a new car. You pick the make and model, sure, but then you pick the color, the trim, the interior…except there are a million more choices to be made here. ♾
Let’s start with a simple question: what does the language look like? 👀
It may look like Ruby, or Rust, where we break free from the shackles of C-like syntax a bit. No semicolons, fewer parentheses — it just looks and feels cleaner. Or if you want to go to the extreme in that direction, Python is a good example. Instead of what early C proponents called its “free form” syntax, you have significant white space like some very early programming languages. Everything has its proper place.
It could look like a Lisp-like language, such as Clojure, Scheme, or Racket. Sure, there are parentheses everywhere, but you get major benefits from homoiconicity (more on that later (it has to do with parentheses)).
Or, you could go completely off the wall. You could look at lesser known, strange languages, like Elixir, Erlang, Haskell (the functional gang). Or older languages like Smalltalk, Pascal, Algol, FORTRAN, COBOL, where many have found inspiration before. Or make the next Brainfuck, or Malbolge! That’s the fun part: there are a million decisions to be made and it’s totally your call.
In the case of Foxie, I’m exploring a Rust-like syntax, with touches of C++, and a heaping spoonful of Ruby and Lua for scripting goodness (not to mention some lispyness (just a bit (I promise))). In the end, it’s not a systems language, after all — it’s literally for scripts.
Syntax is like the “make” of your shiny new programming language. It determines which styles and models are available to you. It affects which decisions you can make down the line. We’ll get to that, but first, let’s explore more of the choices available to us in the horribly named world of “paradigms”.
Once you select the basic look-and-feel of your syntax, major paradigms are probably the next big call to make. There are quite a few, and they’re not all mutually exclusive. For example, you can have a strongly, statically typed, object-oriented language with functional aspects (like you could pass functions as values, but not prevent side effects or data mutations).
Let’s start with the 800-lb. gorilla of programming paradigms (I swear, it’s the last time I’m using that word). Object-oriented programming (OO or OOP for short) has pretty much eaten the world of modern mainstream programming languages. JS is object oriented. Java, C#, and C++ are OO. In Ruby, "everything is an object"! Even Rust has structs that can have traits, i.e. data and functions — pretty much the same thing as objects (although I prefer Rust’s method).
OOP has completely overtaken everyone’s problem solving centers, in my opinion. Yes, attaching functions to data is a powerful idea. However, it can also be useful to flip the script – maybe you want data attached to functions! Maybe you want the two to be completely separate. My point is, object-oriented is not the end all be all of programming, not like we were promised in the 90s at least (I’m looking at you, Java).
With that in mind, Foxie will not be an object oriented language in the sense that C++ or Java are OO. It’ll be more like Rust, in that yes, you can associate functions and data, but it can go both ways. More on that in the future, but in short, I don’t want to enforce any particular technique if I can help it.
What about other techniques, though?
Let’s talk functional programming, because that, in my opinion, is where the meat and potatoes are (for the most part). First of all, the concept of “functions as values” is a winning idea. Passing a function to another function, or returning a function, currying, memoizing, and other techniques are definitely something I want to have. Recursion can be incredibly powerful, in this case, maybe for traversing conversation trees, decision graphs, and so on.
I’m not too big on enforcing immutability and an absence of side effects and all that stuff though. The only hard rule I’d use in this case is that all function expressions are usable as values, a rule which increases the developer’s expressiveness, it doesn’t limit it.
Speaking of which, let’s talk about parsing, and expressions vs. statements.
For the uninitiated, statements are things like assignments, or a function statement in JS, or an if statement. Statements don’t evaluate to anything. Expressions are the opposite — when they’re evaluated, they return something.
In Foxie, everything will be an expression.
Assignments, for example, will evaluate to their value. Function declarations will be usable in any place an identifier or variable is allowed. Conditional statements will return the result of the last expression in the executed block. When you allow anything to be an expression, you enable things like assigning the value of an if statement to a variable, or anonymous functions for callbacks.
This brings me to another extremely important aspect of language design: typing. I mean, everything is typed, it’s just a matter of whether or not you have control and insight into what types things actually are.
Problem is, sometimes that’ll bite you in the ass. You’ll get a number passed in as a string when you’re expecting an int (or a float, in this case, even when it looks like an int). You’ll get a null when you expect a truthy value. Or you’ll use a boolean comparison on a number, when you really want to cast to bool, and it gives you a false negative on 0 or other falsey values.
On the other hand, typing is a pain when you’re just prototyping. In Foxie, I’m aiming for optional type hints, for the developer’s benefit only. This will enable Intellisense-type completion and corrections, not to mention warnings, when you do have types in place. If you don’t, we’ll just assume you know what you’re doing.
Where do we go from here?
I'm torn on this. For now, I'd like to avoid static typing, but I'd also like the ease of dynamic typing. I feel like when you have literal values, it should figure it out for you, obviously. What would be nice is, if the first time you use a value, it gets defined for you...
Maybe we auto-generate type files?
What if, the first time you use a compound data structure, it gets defined in a "header" file, say, like in C, except it's auto-generated the first time around?
But what if a usage gets added or deleted? Or changed? It should update the usage...this implies there is some sort of program that's linking between the definition and the usage – there could be! That sounds like a job for the compiler.
This is a lot to think on...I'll have to carefully consider how to build such a system. In future posts, I'm hoping to get more into what the language will accomplish once it's actually working, what the APIs will look like.
For now, though, I leave you with another post-blog song: