bracket operator
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 . . .