-
Notifications
You must be signed in to change notification settings - Fork 1
GooAtEighteen
in december 2000, i came up with the idea for designing and implementing a new language for a seminar i planned to teach at mit. during this same time paul graham was starting to come up with ideas for his own language. i met with paul graham to discuss language design ideas. it was clear that we shared fundamental aesthetics but had a number of equally fundamental points of departure based on unique prior experiences.
during the lightweight languages I workshop, paul presented arc at 3 weeks and i presented back to basics with proto, the precursor to goo. i was very impressed with paul's systematic reexamination of lisp. i especially enjoyed paul's tutorial / essay.
at the time of reading of his essay, i was so inspired with his arc at 3 weeks essay, that i wanted to do my own, that is, a goo at 9 months. well, other priorities got the best of me, and so, i now (in time for LL2) present goo at 18 months.
at this time, this presentation assumes familiarity with ''arc at 3 weeks'' and makes many references to it sometimes without copying the referenced examples or prose.
similar words can be spoken about goo. it's unfinished. it's possibly a bit shocking to old school lispers, but maybe a bit conservative to paul graham. i'd love suggestions sent to mailtome.
again, goo attempts to rethink lisp, adding an object system, dynamic types, protocols, and a bit more syntax, but hopefully no onions. it builds quite heavily on dylan's attempt to rethink lisp.
goo is similarly targetted towards good programmers and myself in particular. it is meant to be fun to use. it is also meant to be a programmable programming language, and as such is meant to be able to (1) easily expressed itself in itself and (2) allow users to write libraries on equal footing with builtin goo libraries. for example, users can extend arithmetic operators such as +. finally, the design is also constructed with an eye towards efficient implementation.
goo is meant to maximize a number of design goals, to be: simple, productive, powerful, safe, extensible, dynamic, small, efficient, and real-time. each of these on their own creates a whole set of challenges with little available guidance in the literature and few concrete measures of success. together, these design goals form a daunting design mountain to climb. we hope to make progress on all these fronts and to create a practical programming language implementation.
goo supports the notion that succinctness is power and that succinctness is what languages are for. goo promotes brevity by providing succinct special forms and a measured amount of special syntax in key places. oft-used names are short just as they are in natural languages.
on top of brevity, we also feel that programming languages require clarity and lack of ambiguity. sometimes, there is a tradeoff between clarity and succinctness and in this case goo favors clarity. for example, the introduction of pronouns into arc, increases brevity but at the expense of some clarity.
we agree that languages should be designed with a big application in written in them. goo has been developed while not only writing itself but also electronic music software in it.
goo takes a similar strategy to syntax as arc, that is, it mostly has an sexpr-based syntax augmented with a few abbreviations for oft-used constructs that are awkward or unreadable in their sexpr form. these abbreviations are optional and always translate into an underlying sexpr form. for example, optional types can be tacked onto parameter names after a vertical bar separator:
(fun (x|<int>) x) == (fun ((x <int>)) x)
goo departs from arc in that, at least in the near future, the plan is for the representation for programs to not be sexprs, but for the surface syntax to stay as is. we believe that sexprs are an impoverished representation for a hygienic macro system. fortunately, we believe that the convenience of sexprs can be maintained within a richer representation for syntax by (1) making it a collection obeying the convenient collection protocols and (2) providing wysiwyg mechanisms such as lisp's quasiquote and destructure-bind for creating and matching sexprs.
eval -- quote binding -- loc def set control -- fun if seq esc fin ct definition -- dv dc dg dm dp ds
the core of goo is very lisp/scheme-like but there are many differences. goo sticks with cond as is and introduces seq for lisp's progn. because brevity is so important for blocks, goo provides {} syntax for thunks
'{' ,@forms '}' == (fun () ,@forms)
and seq:
('{' ,@forms '}') == (seq ,@forms)
for example:
(if (a x) ({(post "!") (b x)}) (c x))
goo is much less list-centric than lisp, but instead provides a rich set of collections built in terms of a set of protocols. this encourages goo users to use the appropriate data structure for the given programming task instead of using lists for everything.
goo is very clear about the introduction of new local vars but makes their introduction convenient with def:
(dm find (f|<fun> c|<col> => <any>) (def val (find-or f c $elt-default)) (if (== val $elt-default) (range-error c f) val))
which avoids the curse of the ever indenting program.
simple assignment is provided with set:
(set x (+ x 1))
goo also provides a simple but effective name-based setter mechanism. for example, when the first argument to set is a function call, then the name of the function is transformed to have -set appended to it:
(set (len x) 10) == (len-set 10 x)
this mechanism is used for property assignment and is also available more generally for users.
finally, goo provides an auto setter mechanism for a few functions such as incrementing:
(incf x) == (set x (+ x 1)) (incf (point-x p)) == (set (point-x p) (+ (point-x p) 1))
and a more general auto setter mechanism:
(opf (player-pos p) (mod (+ _ 1) n-beats))
which evaluates the left-hand-side once and binds it to _
as follows:
(set (player-pos p) (mod (+ (player-pos p) 1) n-beats))
goo opts for pronounceable special forms as much as possible utilizing fun instead of fn. because brevity is so important for functions (especially thunks), new {} and op syntaxes are introduced as abbreviations:
{1} == (fun () 1) {x\x} == (fun (x) x) {x y \ x} == (fun (x y) x)
where the backslash is closest ascii character to the greek lambda symbol.
the op syntax makes it easy to construct anonymous functions as is done with currying and function composition. the following syntax:
(OP ,op-arg ...)
creates an anonymous function with implicitly defined arguments, where ,op-arg
is either an implicit required parameter _ or rest parameter ... or an sexpr potentially containing further op-args. the required parameters are ordered according to a depth-first walk of the op-args. the following are typical examples:
((op _) 1) ==> 1 ((op 2)) ==> 2 ((op + _ 1) 3) ==> 4 ((op lst ... 1) 3 2) ==> (3 2 1) ((op tail (tail _)) '(1 2 3)) ==> (3)
goo uses loc instead of rfn and introduce rep as an abbreviation for oft-used iterative loops:
(rep count ((i 99) (c 0)) (if (= i 0) c (count (- i 1) (+ c 1))))
goo requires that macros be statically analyzeable. we believe in procedural macros and provide them:
(ds unless (,test ,@body) `(if (not ,test) (seq ,@body)))
goo provides def for the introduction of new bindings without indenting. parallel binding is provided with the tup variant:
(seq (def (tup x y) (tup 1 2)) (lst x y)) ==> (1 2)
the let construct is still available for those times when a seq body is needed.
(if ok? (let ((x (g y))) (f x x)) y)
goo's collections and iteration are built on an enumeration protocol. this provides a number of advantages. first, it is extensible: new collections and iteration constructs can be introduced. here are some of the builtin iteration constructs (but see below for a more complete treatment):
(for ((i (below 10))) (say out i)) (for ((x '(a b c))) (say out x)) (let ((i 0)) (while (< (incf i) 10) (say out i)))
second, it can be used in isolation (e.g., with rep) with brevity on par with list's head, tail, nul?. For example, summing over a tuple can be expressed as follows:
(rep loop ((s 0) (e (enum #(1 2 3)))) (if (fin? e) s (loop (+ s (now e)) (nxt e))))
goo's solution for accumulation during iteration is to provide a lightweight collecting mechanism, called packers, orthogonal to iteration. it consists of a protocol and macros which provide an extensible approach to collecting results.
(packing (for ((x '("al" "bob" "joe"))) (if (> (len x) 2) (pack x))))
appropriate types (e.g., <int>) have default packers. for example, the following:
(packing-as <int> (for ((x (below 5))) (pack x)))
sums all the packed integers.
the advantage of this approach over arc's implicit collecting mechanism is that (1) multiple packers can be introduced without name collision:
(packing-in (odds sum|<int>) (for ((x '(1 2 4 5 9 11 14))) (pack-in sum x) (when (odd? x) (pack-in odds x))))
and that (2) it is user extensible. we believe that mechanisms should be introduced in an orthogonal fashion so that they can be maximally combined so as to encourage serendipitous outcomes.
goo has a rich set of data types organized into a set of inheriting classes implementing a set of protocols. everything in goo is an object and more specifically an instance of a particular concrete class. this uniformity leads to big user wins, but also implementation challenges.
goo also has an experimental thread mechanism based on pthreads. we're playing with other ideas in this area as we're not completely sold on the shared memory model for lightweight threads.
goo chooses to introduce special syntax for collection access:
["hello" 2] == (elt "hello" 2)
this wins in many different ways. first, it works on the lhs of assignments:
(set [v 1] 2) == (set (elt v 1) 2) == (elt-set 2 v 1)
second, it provide a natural mechanism for python-like slicing:
[v 1 2] == (sub v 1 2)
and
[v 1 *] == (sub* v 1) == (sub v 1 (len v))
finally, it doesn't resort to adding the notion of funcallable objects.
the goo philosophy is to just make operations on all collections be lightweight and in particular the enumeration protocol operations. the reason lists are so convenient in lisp is because the data structure provides its own cursor. consider applying a function to each element in a list (using the conventional lisp list function names):
(rep loop ((e l)) (unless (null? e) (f (car e)) (loop (cdr e))))
the usual way to do the same thing with a string is to walk it using an integer index:
(rep loop ((i 0)) (unless (>= (len s) i) (f [s i]) (loop (+ i 1))))
unfortunately, this is awkward because you have to be more explicit about the cursor and its details and it leads to less tidy code and hinders later refactoring. in contrast, enumeration over any collection in goo is terse. for example, the resulting goo version of the string walking code from above:
(rep loop ((e (enum s))) (unless (fin? e) (f (now e)) (loop (nxt e))))
turns out to be only slightly more verbose than the list version. finally, once the enumeration protocol is available then a whole collection protocol is in turn available.
goo provides a sophisticated multiple inheritance object system and everything in goo is an object. see GooWhy for more information and motivation. a couple features which particularly enhance refactoring are a uniform field access mechanism through generic functions and a uniform factory mechanism based on new.
goo forces a certain discipline by making field access be indistinguishable from any other function call. this means that no call sites need to change if a refactoring turns a field access into a function call. for example, one could first implement a triangle with the length of its three sides as properties:
(dc <triangle> (<any>)) (dp side-a (<triangle> => <num>)) (dp side-b (<triangle> => <num>)) (dp side-c (<triangle> => <num>)) (dm sides-to-angle (a b c) (acos (/ (- (+ (pow b 2) (pow c 2)) (pow a 2)) (* 2 b c)))) (dm angle-c (x|<triangle> => <num>) (sides-to-angle (side-c x) (side-a x) (side-b x))) (dm area (x|<triangle> => <num>) (* (side-a x) (side-b x) (sin (angle-c x)) .5))
and then later simply change its implementation to store two sides and an angle, and to calculate its third side side-a, without changing its interface:
(dc <triangle> (<any>)) (dp angle-a (<triangle> => <num>)) (dp side-b (<triangle> => <num>)) (dp side-c (<triangle> => <num>)) (dm angle+sides-to-side (a b c) (sqrt (- (+ (pow b 2) (pow c 2)) (* 2 b c (cos a))))) (dm side-a (x|<triangle> => <num>) (angle+sides-to-side (angle-a x) (side-b x) (side-c x)))
similarly, goo provides as its lowest level object creation mechanism a generic function called new. again, this means that programmers can easily change a class implementation without changing all the call sites. for example, users could create triangles by specifying the length of its three sides regardless of implementation. the call sites would look like:
(new <triangle> side-a 1 side-b 2 side-c 3)
and the side-angle-side triangle implementation would define the following constructor:
(dm new (t|(t< <triangle>) args|...) (def-keys ((a side-a) (b side-b) (c side-c)) args) (sup t side-b b side-b c angle-a (sides-to-angle a b c)))
allowing the original construction call sites to keep working.
function overloading in goo is natural and consistent. it is based on the notion of a generic function, such as +
:
(dg + (x|<num> y|<num> => <num>))
which can have several implementations (e.g., <int>
and <flo>
):
(dm + (x|<int> y|<int> => <int>) (i+ x y)) (dm + (x|<flo> y|<flo> => <flo>) (f+ x y))
called methods. virtually all functions in goo are overloadable including arithmetic functions and unlike arc, method selection works based on all arguments.
goo calls arc's db's tabs. for example, the arc db example would be rewritten as:
(col <tab> 'x 'b 'y 'b)
where col is the general collection creation function and the following collects all vals into a list:
(packing (for ((x (col <tab> 'x 1 'y 2))) (say x) (pack key)))
goo's answer to not finding something during a lookup is to either error or to provide a version that takes a default value thunk:
[(col <tab> 'a 'b) 'c] ==> key-not-found-error (elt-or (col <tab> 'a 'b) 'c {#f}) ==> #f
in addition there's a method that will store the supplied key / default value in the event that the supplied key does not exist:
(elt! (col <tab> 'a 'b) 'c {'d}) ==> (col <tab> 'a 'b 'c 'd)
we applaud arc's fresh look at parameter lists. we're just starting to come up with a plan for goo in this respect. the challenge is that any goo parameter list design must at least address the issues of parameters' role in multi-method dispatch. the traditional solution (e.g., CLOS and Dylan) has been to exclude these special parameters from dispatch.
we hold out higher hopes for goo. we'd like all parameters to participate in dispatch and to keep parameter properties mutually orthogonal. the plan is to introduce new types that not only perform the destructuring but also specify their subtyping so as to pin down method selection.
currently, goo supports variable numbers of arguments and destructuring constructs for picking off optional and keyword arguments.
not only will goo provide profiling as a user level facility, but its optimizations will automatically optimize hot methods based on runtime profile information. this will allow goo to support a scalable implementation that will run on a range of platforms from embedded to server sized.
give examples of def-opts and def-keys
give an example of object construction through delegation