Clear Object-Oriented Programming? Not in Glass

Todays bit of programming insanity is a bit of a novelty: it's an object-oriented programming language called Glass, with an interpreter available here. So far in all of my Friday Pathological Programming columns, I haven't written about a single object-oriented language. But Glass is something
special. It's actually sort of a warped cross between Smalltalk and Forth - two things that should never have gotten together; in the words of the language designer, Gregor Richards, "No other language is implemented like this, because it would be idiotic to do so."

In general, Glass using single character identifiers and commands. To use any multiple character identifier, you need to wrap it in parens. So, for example, "a" is an identifer; "abc" is three identifiers - a, b, and c; "(abc)" is a single identifier. This has the interesting effect of making the language considerably harder to read, for absolutely no good reason.

A program in Glass, like in most other object-oriented languages is a series of class definitions. A class declaration is enclosed in curly braces, and consists of a class name followed by a list of methods. Each method is enclosed in square brackets, and consists of a method name followed by a sequence of cmmands. So, for example, the "main program" in Glass is the method M of the class M; so a minimal Glass program is:{M[m]}: that's a declaration of class "M", which has one method, named "m", which does nothing.

Within Glass code, there are four different kind of variable:

  1. Global variables: generally used for class names, global variables have names that start with an upper-case letter.
  2. Instance variables: variables local to a particular object. Instance
    variables have names starting with a lower-case letter.
  3. Class variables: variables shared between all instances of a class. Class variables start with a lower-case letter. They look exactly like instance variables,
    but they're accessed using a different operator.
  4. Local variables: variables local to a particular invocation of a method. Local variable names start with an "_"; since identifiers more than one character long must
    be enclosed in parens, all references to local variables must be enclosed in parens.

The basic operators in Glass fall into three groups: object-oriented operators,
stack operators, and control-flow operators. I'll use Forth notation for stack effects: (v1 v2 - v3 v4) means that the top of the stack before the operation consisted of the values v1 and v2 (with v2 on top); and after, the top values were v3 and v4.

The object-oriented operators are:

Create object: ! (varname global --)"
Assumes that there are two identifiers on the stack - a global variable
referring to a class, and any other kind of variable. An instance of the
class named by the global is created, its constructor method (the method named "c__") is called, and the result is assigned to the target variable.
Retrieve method: . (object name -- method)
Retrieve the method named "name" for the object "object", and push
it onto the stack.
Assign self: $ (name -- )
Assign the value of self to the variable whose name is on top of the stack.

The stack operators are:

Push variable name: (name) ( -- name )
Push a variable name onto the stack. The parens are only required if "name" is more than one character long.
Copy stack value: (number) (sn sn-1...s0 -- sn sn-1...s0 sn)
Copy a value from the specified depth onto the top of the stack. The parens are only required in the number is more than one digit.
Push a string: "string" ( -- "string" )
Push a string value onto the stack.
Push a number: <number> ( -- number)
Push a numeric value onto the stack.

as a stack copy command.

Drop: , (v -- )
Discard the top value from the stack.
Retrieve variable value: *. (name -- value)
Retrieve the value of the variable whose name is on top of the stack.

And finally, the control flow operators are:

Return: ^
Return from the current method call.
Execute method: ?
Pop the method on top of the stack, and execute it.
Loop: /(name)commands\
While the variable named "name" has a non-zero or non-empty string value,
execute the commands in the loop body.

There are also a set of built-in classes which contain basic operations:

  • Class "A" contain arithmetic operations: A.a (add), A.s (subtract), A.m (multiply), A.d (divide), A.mod (modulo), A.f (floor), A.e (equality test), A.ne, A.lt, A.le, A.gt. A.ge (comparions).
  • Class "S" contains string operators: S.l (length), S.i (character at index),
    S.a (concatenate), S.d (divide string at a position), S.e (equality test),
    S.ns (number to character conversion), S.sn (character to number conversion).
  • Class "O" contains output operators: O.o (output string), O.on (output number).
  • Class "I" contains input operators: I.l (input a line of text), I.c (input a character), I.e (check for end of file).

So... Finally some sample programs. As usual, we start with Hello world:


{M[m(_o)O!"Hello world"(_o)o.?]}

It looks awful, but it's actually pretty easy. It instantiations class "O", storing the instance in local variable (_o); then it pushes the string "Hello world" onto the stack; then it pushes the "o" method of our instance of "O" onto the stack and executes it.

Now for something much more interesting; the Glass version of a Fibonacci sequence generator.

{F
   [f
    (_a)A!
    (_o)O!
    (_t)$
    (_n)1=,
    (_isle)(_n)*<2>
    (_a)(le).?=
    /(_isle)
       <1>^
    \
    (_n)
    *<1>(_a)s.?
    (_t)f.?
    (_n)*
    <2>(_a)s.?
    (_t)f.?
    (_a)a.?
   ]
}

{M
   [m
     (_a)A!
      (_f)F!
      (_o)O!
      (_n)<1>=
      (_nlm)<1>=
      /(_nlm)
         (_n)*
         (_f)f.?
         (_o)(on).?
         " "(_o)o.?
         (_n)(_n)*
         <1>(_a)a.?=
         (_nlm)(_n)*
         <20>(_a)(le).?=
      \
   ]
}

That's pretty long and cryptic, even with my efforts to format it cleanly. So let's take it apart, bit by bit.

  1. It starts by declaring class "F". "F" has one method, "f":
    1. "F.f" instantiates an A, and an O, and stores a reference to itself in (_t).
    2. It copies the value on top of the stack, and stores it in (_n). This is
      the number of the element of the fibonacci series that it's trying to generate.
    3. It compares (_n) with 2 and stores the result in (_isle), and then if
      _n was less than or equal to 2, it returns 1.
    4. Otherwise, it subtracts one from (_n), and gets that fibonacci number (f(n-1));
      the subtracts two from (_n), and returns that fibonacci number (f(n-2)), and then adds them together.
  2. Then it defines a main method - class M, method m, which instantiates an "A", an "F", and an "o"; and loops through calling f for each number from 1 to 20.

See? Not so hard!

How about the infamous 99 bottles of beer? Without my help in reformatting
to make it legible?

{B[b<99>^]}{P[(c__)oO!t$aA!][n<10>s(ns).?oo.?][poo.?tn.?][b(_m)1=,(_x)<0>
(_m)*ae.?=(_y)<1>=/(_x)"No more"oo.?(_x)0=(_y)0=\/(_y)(_m)*o(on).?(_y)0=\
" bottle"oo.?(_x)<1>(_m)*ae.?=/(_x)^(_x)0=\"s"oo.?]}{C[(c__)oO!aA!sS!pP!t
$][gn*][xn1=,][dnn*<1>as.?=][vn*pb.?" of beer on the wall,\n"pp.?n*pb.?qe
" of beer,\n"pp.?"Take one down, pass it around\n"pp.?ln*<1>as.?=l*pb.?wu
" of beer on the wall.\n\n"pp.?pn.?]}{M[moO!cC!bB!bb.?cx.?fcg.?=/fcv.?cd.
?fcg.?=\]}

That shouldn't be too hard to figure out. The key is really just a question of formatting - once you separate it out so that it's one statement per line, it's remarkably easy to follow.

Finally, I'll close with a quine:

{M[m(_s)S!(_o)0O!o.(_s)(ns).?"{M[m(_s)S!(_o)0O!o.(_s)(ns).?"
"14?24?14?24?24?04?24?04?]}"14?24?14?24?24?04?24?04?]}

More like this

At first glance this looks a lot like K or J but upon further inspection it seems not. K defines a coherent alphabet and lets you define words and it also groups things into ideas of nouns and verbs which is really cool. While at first you scratch your head wondering what the heck it does, once you can read the alphabet it starts to make a lot more sense which is pretty cool. Glass seems more like an exercise like Brainfuck to see how minimal something can be and still functional.

K also provides some really awesome ways to do GUI programming that I haven't seen in other languages, perhaps you could do an article on it if you get a chance.

Here is some basic info:

http://en.wikipedia.org/wiki/K_programming_language

This K function sorts a list of strings by their length:
x@>#:'x

Again, very terse but it makes a lot of sense once you understand the alphabet.

Off all topics, but because it's Friday (and I got soaked walking to the office), I feel obligated to pass along a link to 16 Things We Understand, and 4 We Don't. It's a mildly not-safe-for-work riff on a classic Pink Floyd poster.

Extra credit if you can identify as many of the equations as I can! Without looking at the xkcd forum where I found the picture and posted the answers, of course.

As for bizarre languages, C-INTERCAL has to be my favourite. I mean, it has a COME FROM control statement! Designed to do away with GOTO...

I know there are far more bizarre ones, like malbolge, but those are just over my head :)

So, for example, "a" is an identifer; "abc" is three identifiers - a, b, and c; "(abc)" is a single identifier. This has the interesting effect of making the language considerably harder to read, for absolutely no good reason.
I have a reminiscence that in Algol-68 whitespace can be inserted at absolutely any point (to "improve readability") even in the middle of an identifier. As a result, a lot of stuff had to be quoted to avoid ambiguity.

I find OOP hard even with sensible languages...I've been struggling with R for a while

Glass is hardly transparent

> So, for example, "a" is an identifer; "abc" is three identifiers - a, b, and c; "(abc)" is a single identifier.

Ah, lovely regexp-like consiseness!

Actually, I think regexps are a pathological programming language by themselves. It seems I never manage to escape things the right number of times, and almost everything requires an escape in practice. I've actually wondered if I couldn't manage to design an alternative syntax with quotes as the only escaping mechanism.