Casem; an Experimental Language


Every few years or so, I like to try thinking up a new language for writing software. Mostly, I don’t publish them or even attempt to create a parser. This time. Well, this time, I blew my own mind with this one.

My inspiration was Scala, PHP, and Lisp.

Allow me to demonstrate a simple “Hello World”:

echo "hello world!"

Seems simple enough right? It’s not, what your space actually means is that you want it to be displayed as though it’s left-associative, but it’s really right-associative. So the simple program above is really:

"hello world!".echo

In other words, we are calling “echo” on a String.

This is a language designed for pattern matching, everything is a pattern. Patterns are first-class citizens and behave as such. There are no objects, only patterns, and functions.

A slightly more complex example

Let’s consider two variables, “a” and “b” and check their equality:

val a = 1
val b = 2

val result = a < b {
  case == true:
    echo "a is less than b"
  case == false:
    echo "b is less than a"
}

Knowing that spaces are just a shorthand for switching associativity we can deduce that what that actually says is:

1.=.a.val
2.=.b.val

({
  true.==.case:
    "a is less than b"
  false.==.case:
    "b is less than a"
}.(b.<.a)).=.result.val

We can see what is really going on now, the block of code is applied to the result of what came before the block. We can also see that what I said about spaces is not exactly true, because of the parenthesis, which is due to operator precedence.

case is a special function that takes an input of the block, and applies it almost in-place. So, the true.==.case is more like true.==.(b.<.a). val is also a special function that is cased onto all unknown variables. It tries to resolve the value to an immutable value.

What do I mean by cased onto unknown variables? Here’s a very naive example of what that may look like:

case UnkownIdentifier {
  def val(implicit localSymbolMap): Identifier {
    add(this) to localSymbolMap
  }
}

In reality, this would be implemented in “native” code, and not be implemented in the language itself, but you get the idea.

Types

So, given the example above, you may be wondering about types. You may have noticed I didn’t give the result a type, and I was modifying the behavior of the type UnknownIdentifier.

Well, there are basic types (String, Float, Int, etc), but more advanced types are just like any other typed language and defined by the programmer. That’s not to say there couldn’t be a core library to implement things like arrays, maps, etc. I think there should be, if anyone ever attempts to write a parser/lexer/vm for this language.

Let’s get on to extending behaviors of types…

Extending Behaviors

If we wanted to extend Floats to add a naive rounding function, we could implement it like so:

case Float {
  def round({ case int\.dec }: Float): Int {
    dec.as[String].first(1) {
      case < 5: int
      case _: int + 1
    }
  }
}

math = export

Let’s unpack this a bit and see what it really parses into:

{
  {
    {
      5.<.case: int
      _.case: 1.+.int
    }.(dec.as[String].first(1))
  }.(round({dec\.int.case}: Float).def: Int)
}.(Float.case)

export.=.math

case is a special function that applies “this” to something else. You can think of 5.<.case as “apply the input of this block to such that 5.<.this happens, or this < 5. The : is also special, as a barrier between two blocks where it acts somewhat like an if. Like Scala, the last statement in a block is the “return”.

{ and } or blocks can also be applied to functions by calling them directly. in the case of calling dec.as[String].first(1), we will get back a string, and since we’re calling the block of cases, and remembering that case applies the input as this, we can logically deduce “given these set of statements, apply them to this as input.”

It gets even stranger when we look at the definition of round:

def round({ case int\.dec }: Float): Int {}
// or
{}.round({dec\.int.case }: Float).def: Int

Def is another special function, cased onto undefined things. By calling .def on something undefined, we can define it. We can see that the block (or function body) is applied to the undefined thing, then defined. The parameter list is also interesting. We accept a block, that must be a Float, and decompose it into an integer part and a decimal part. The \ is required to tell the compiler that we’re not interesting in calling dec.int, but rather the two parts.

Even more strange, is to consider this entire block is applied to the Float type, then cased. When case is used in the outermost scope, it applies to the context of the program itself, thereby changing the meaning of a Float from that point forward.

So, how does one create a new type? By either extending an existing type:

case Name is String

or just creating a new one altogether:

case Awesomeness

We can also extend Any, the all-encompassing type:

case Any {
  def of(implicit caller, callee) {
    caller.callee
  }
  
  def to(implicit caller, callee) {
    caller.callee
  }
}

So that we can write almost readable things:

val scoreFile = getContents of file "./scores.json"
// instead of
val scoreFile = file.getContents "./scores.json"

Where file is exported in a module that cases a String.

Takeaway Thoughts

Well, first off, this language is probably impractical. The language would be hard to write for: this.< really means < this, which is the exact opposite meaning.

Remembering when to put a . vs. a (space) is probably something not worth working around. While this language blew my mind (particularly around applying blocks of code to variables and how that might be implemented), I hope it never gets implemented in this form.

I’ve ported a couple of smallish programs to this language, just to see what it might look like. I think I like it, but I’d hate to maintain anything above slightly complex in this language.

PS. There are some logical errors above. I was undecided on how a couple of things should work. This isn’t about making it actually work.


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.