Librarian of Alexandria

2013-10-01

Matzo Feature Consideration

I'm still working on the Matzo language, and I need to finish the parser (as I can now evaluate more programs than I can parse, and have code written to an as-yet unimplemented module spec.) Here are a few features I'm considering but haven't implemented yet, and how they might interact:

Dynamic Default Variables

This opens a small can of worms, so to be clear, dynamic variables will reside in a different namespace than normal variables. I don't know yet whether said namespace will be denoted with a sigil (say #x) or whether you might have to pass a symbol to a function (like get.X) to access their values. The idea is that certain functions will have sensible defaults that one might want to override, and rather than explicitly matching on arity, one can instead pass them in dynamically using a particular syntax. In the examples below, I'll use an explicit syntax for lookup of a dynamically scoped variable; in particular, get.x.y will look up a dynamically scoped variable x (typically a symbol), and if it hasn't been supplied, will use the default value y.

foo :=
  let combine := get.Combine.cat in
    combine."a"."b"
bar := foo with Combine := (\ x y . x ";" y)

puts foo
(* prints "ab", as it uses the default combiner `cat` *)
puts bar
(* prints "a;b" *)

This will continue down dynamically, so

s1 := (get.Pn."e") " stares intently."
s2 := "It is clear that " (get.Pn."e") 
        " is interested in what is happening."
sent := se.<s1,s2>
puts sent;
puts sent with fixed Pn := "he" | "she" | "e"

will force Pn to be the chosen value in all subexpressions evaluated underneath the with clause. (Notice that nondeterminism still works in dynamically computed variables, so one must declare them as fixed at the binding site if you want them to be computed exactly once.)

Text Combinators

I've used one of these above: right now, I have a few planned.

puts wd.<"a","b","c">
(* prints "abc" *)

puts nm.<"a","b","c">
(* prints "Abc" *)

puts se.<"a","b","c">
(* prints "A b c" *)

puts pa.<"a","b","c">
(* prints "A. B. C." *)

So effectively, they function as ways of combining strings in a more intelligent way. I plan for them to do some analysis of the strings so that they don't, say, produce extraneous spaces or punctuation. (The names are of course short for word, name, sentence, and paragraph, respectively, and may very well be alises for those longer names.)

Rebindable Syntax

The conjunction of the previous two suggests that certain bits of syntax should be rebindable. A prominent example is concatenation, e.g.

puts "foo" "bar" "baz"
(* prints "foobarbaz" *)

puts "foo" "bar" "baz" with Cat := se
(* prints "Foo bar baz.", as normal string concatenation
 * has been overloaded by `se` *)

puts "foo" "bar" "baz" with Cat := fold.(\ x y . x ";" y)
(* prints "foo;bar;baz", as normal string concatenation
 * has been overloaded by a custom function *)

This could lead to some pretty phenomenal weirdness, though:

f := \ x y . add.x.y
puts f.<1,2> where App := \ f x . fold.(\ g y . g.y).(append.f.x)
(* prints 3, as we have overloaded function application to
 * automatically uncurry functions *)

...so maybe there should be a limit on it.

Error Handling

Still not sure on this one. I don't particularly want to bring monads into this, mostly because I want the language to be a DSL for strings and not a general-purpose programming language, but at the same time, it might be nice to have a simple exception-like mechanism. One idea I was playing with was to implement a backtracking system so that errors (both raised by users and by built-in problems) could simply resume at particular points and retry until some retry limit is reached. For example, you could reject certain unlikely combinations:

x ::= a b c d
wd := let result := x x x x
      in if eq.result."aaaa" then raise Retry else result
puts mark Retry in wd

Here, mark exp in wd corresponds roughly to the following imperative pseudocode:

{ retry_count := 0
; while (retry_count < retry_max)
    { try { return wd; }
      catch (exp) { retry_count ++; }
    }
}

It's a much more limited form of exception handling, which may or may not be desirable, but does give you some ability to recover from errors so long as at least some execution of your program will be error-less.

All this is heavily open to change, so we'll see.