| While working through Essentials of Programming Languages (a wonderful textbook on programming languages and implementing interpreters by Daniel Friedman, Mitchell Wand, and Christopher Haynes), I saw tagged unions/discriminated unions/variant records/algebraic data types[1] for the first time and decided that they were excellent. (See below for an explanation of what they are.) I've since dabbled with Haskell and seen them yet more there (they're Haskell's data Type = Var1 A B | Var2 C). At any rate, I then decided to implement them in Arc. The implementation was pushed to Anarki as lib/tagged-union.arc; it requires Anarki's Arc-side ssyntax extension. A tagged union, for those of you not familiar with them, is a data type which has different "variants," each of which is constructed by a different function. For instance, here's a type you use every day: the (proper) list, with constructor functions cons and (fn () nil). The parameters of the constructor functions are typed (or, to be precise, validated by given predicates). Using my library, this would be written (using different names) (tunion list
lcons (car object)
(cdr list?)
lnil)
so that (lcons 'a (lnil)) is the analog of (cons 'a nil) or '(a). Note the syntax here: (tunion TYPE-NAME
VARIANT-1 (ELEMENT-1-1 PREDICATE-1-1)
(ELEMENT-1-2 PREDICATE-1-2)
...
(ELEMENT-1-M PREDICATE-1-M)
...
VARIANT-N (ELEMENT-2-1 PREDICATE-1-1)
(ELEMENT-2-2 PREDICATE-1-2)
...
(ELEMENT-2-P PREDICATE-1-P))
TYPE-NAME is the type you are declaring. You can declare any number of variants, and they can have any number (including 0) of fields/members/arguments. When constructing a variant, each element must satisfy the given predicate; thus, (lcons 'a 'b) would return nil, since 'b doesn't satisfy the predicate list?[2] For some more examples, see the comment at the top of lib/tagged-union.arc.When tunion is called, it populates the global namespace with the functions VARIANT-1...N, and TYPE-NAME-VARIANT-1...N, which are the constructors; it redefines string so that the objects can be stringified (in a readable but ugly manner); and it redefines sref so that you can write things like (= (my-list 'car) 3). defcall is used so that you can also write things like (= x (my-list 'car)). Also note that all tagged union objects satisfy (isa tu 'TYPE-NAME) and (isa (rep tu) 'VARIANT-NAME). The other macro provided by lib/tagged-union.arc is tcase, which does matching based on tagged union types. An example: (tcase my-list list
lcons (do (prn "car: " (string car)) (prn "cdr: " (string cdr)))
lnil (prn "nil")
(w/stdout (stderr) (prn "Not a list!")))
If my-list is not list (or a variant of list that was not covered, though that doesn't apply here), then the last statement will run. Otherwise, if my-list was created by lnil, then the corresponding statement will run, and if it was created by lcons, that statement will run; the members of that variant are bound to their names in that statement, so (prn ... car) won't print "#<procedure:.../arc-wiki/ac.scm:700:11>", but will instead print what you want. The other form of syntax for tcase is (tcase var
(list
lnil (prn "nil")
(w/stdout (stderr) (prn "Non-empty list.")))
(maybe ; Another tagged union.
just (prn value)
nothing (prn "Nothing at all."))
else
(w/stdout (stderr) (prn "Unrecognized type!")))
Here, if var is a list, then things run as though you had written (tcase var list
lnil (prn "nil")
(w/stdout (stderr) (prn "Non-empty list.")))
, and similarly if var is a maybe. If var is neither of these things, then the statement after the else will be run.Sorry that this was so long... at any rate, I hope that someone finds this useful! [1]: As far as I can tell, these are all different names for the same thing; if this isn't right, please correct me! [2]: Predicates of the form type? are from the Arc-side ssyntax package, and mean [isa _ 'type]. You can also write that directly, or you can write 'type (which tunion will convert into [isa _ 'type]). |