bracket operator

bracket operator
Photo by Marek Studzinski / Unsplash

Right now () creates a subexpression. It is an expression in the parse tree which means something like foo () has no meaning in the language where foo is a variable.

Given that foo[index] does have a meaning, and foo {a: b} also has a meaning (array indexing and object focusing) why not give foo () a meaning too? The main reason not to would be language complexity. But this is a bike shed painting exercise so let's dive in.

In the last post I did something weird - I used an object inside the foo[index] syntax to create a specialised subtype:

ArrayConfiguration :: {of: Class, length: UnsignedInteger}.
[Array[configuration]]
  [Array: Class, configuration: ArrayConfiguration -> Class |
    {...Array,
     #element-class: configuration of,
     #length: configuration length}].

two-integers: Array[{of: Integer, length: 2}] = {1, 2}

This is as close to a tuple as the language has right now. Tuples are interesting in that they let you make a compact structure where each member is nameless and accessible via index only.

This reduces expression considerably, but makes certain short-hands more convenient when you program with them a lot. For instance, it would be nice to declare an array like this: Array[Integer, 2] which is succinct. Where it falls down is if you have multiple variations of array creation. Which one are you really calling?

If we had a tuple type the code above could be rewritten as:

ArrayConfiguration :: Class, UnsignedInteger.
[Array[configuration]]
  [Array: Class, configuration: ArrayConfiguration -> Class |
    {...Array,
     #element-class: configuration[0],
     #length: configuration[1]}].

two-integers: Array[Integer, 2] = {1, 2}

The first line Class, UnsignedInteger is something we can write in a type declaration right now. That means it is valid STZ code. The comma , operator sent between two classes creates a tuple type. Adding a → to the end of it turns the tuple in to a method type instead.

The only thing we'd need to add to the language to make tuples work is to allow , to match against a tuple as well as an array type. That's a compiler decision and one I'm favouring right now.

Okay, so let's assume that comma , does work that way for all objects. It will either create an array or it will create a tuple. Let's jump back to the first question of what to do with foo ().

FILE *
fmemopen(void * restrict buf, size_t size, const char * restrict mode);

This is the function declaration for mapping memory to act like a file in bsd. Let's recreate it using our new tuple powers in STZ and attempt to use it:

[system fmemopen(args)]
  [system: System,
   args: (Pointer[Byte], UnsignedInteger, CString)
   -> &File |
     __external fmemopen(args...)].

buffer: Array[Byte, 256].
file := system fmemopen(buffer offset, buffer length, "w")

One of the odd things here is how general the signature is. We could create a library level method that creates these things for us. We could even add a compilation scope method that declares it external:

external
fmemopen :: (Pointer[Byte], UnsignedInteger, CString) -> &File.

buffer: Array[Byte, 256].
file := fmemopen(buffer offset, buffer length, "w")

This might be the preferred way to declare external interfaces.

The other would be to do zero type checking and pass things through to the C compiler and have it throw an error if there's a problem. That's less ideal, if easier to implement.

The distance between STZ and calling external C functions has just shrunk down considerably. I'm going to sit on this and let it digest for a bit, but I think I like what has happened here. The real question is - what other funky stuff does it allow?

And still - are tuples a good idea for the language. I think I'll leave that on a . . .