| Often we expect an expression to evaluate to one of a finite set of possibilities. E.g. a list is either a cons or empty. The result of a search is either an object or a notice of failure. A tree is a red node, a black node, or empty. Etc. Since in many (perhaps most) cases the set is "either a value or another thing," we can represent that "other thing" with nil. But this is not a general solution, and it has some problems. For instance, a search function that returns nil on failure is ambiguous if the collection being searched might contain nil. And similarly a function parameter that defaults to nil is ambiguous if the caller might specify nil. What we need is a way to take a value and attach to it some kind of tag to distinguish it from other values. Arc offers many ways to do this; what's missing is (a) a conventional way of representing it, and (b) a concise syntax that abstracts the details of the representation. I propose the following macro: (mac defcon (name . args)
(let gargs (map [uniq] args)
`(def ,name ,gargs
(annotate ',name (list ,@gargs)))))
defcon generates a function that collects its arguments (the number of which is fixed by the number of forms after name) into a list and tags them with annotate. I'm calling such functions 'constructors' because they are analogous to data constructors in ML; suggestions for a better name are welcome.We can now define a find function that will always distinguish between success and failure. (defcon have x)
(defcon none)
(def myfind (f xs)
(if (no xs)
(none)
(if (f (car xs))
(have (car xs))
(myfind f (cdr xs)))))
To use myfind, we need a match macro. (def foldr (f init xs)
(if (no xs)
init
(f (car xs) (foldr f init (cdr xs)))))
(mac match (expr . cases)
(w/uniq (gexpr)
`(let ,gexpr ,expr
,(foldr (fn ((p e) rest)
(if (alist p)
`(if (isa ,gexpr ',(car p))
(let ,(cdr p) (rep ,gexpr) ,e)
,rest)
(isa p 'sym)
`(let ,p ,gexpr ,e)
(err "match: malformed pattern:" p)))
`(err "match failure on" ,gexpr)
(pair cases)))))
We can now safely search for a list of length < n, something we couldn't do with the old find. (= lists '((1 2 3 4) (1 2 3) (1 2) (1) ()))
(= shortlist [< (len _) 3])
arc> (match (myfind shortlist lists)
(have lst) `(the-first-short-list-is ,lst)
(none) '(there-is-no-short-list))
(the-first-short-list-is (1 2))
This can be further simplified with the equivalent of aif: (mac ifhave (expr thenexpr . elseexpr)
`(match ,expr
(have it) ,thenexpr
(none) ,(if (cdr elseexpr)
`(ifhave ,@elseexpr)
(car elseexpr))
x (err "ifhave: input is not a have or a none:" x)))
arc> (ifhave (myfind shortlist lists)
`(the-first-short-list-is ,it)
'(there-is-no-short-list))
(the-first-short-list-is (1 2))
Notice that this is just as concise as the existing Arc idiom, but it is more general and robust.We can use this for optional parameters too. If a parameter lacks a default value, it will be (have x) when a value x is passed, or (none) otherwise. Supplying a default value y for a parameter x will then be equivalent to omitting the default value and wrapping the function body in (let x (ifhave x it y) ...). What do you guys think? |