Once is a programming language I'm designing in my spare time. The best one sentence description I have so far is: "If Dylan is Common Lisp with a Pascal like syntax, then Once is Scheme with a C like syntax."
The name Once comes from the extreme programming principle OnceAndOnlyOnce, and several of the design decisions behind Once have their justification in that principle. Besides that, there isn't anything particularly extreme about Once.
Below I've listed some of the more novel features of Once. I'm listing them here for two reasons: to get feedback (Once is still a work in progress) and to encourage other people to steal them.
Interesting new syntactical features of Once:
- You can tell infix and unary operators apart, just by looking at them. The rule is the one followed by the most common mathematical and programming operators (with unary minus being perhaps the only glaring exception): unary operators are alphanumeric while infix operators aren't. This rule (along with the rule that says unary operators bind right to left, and always more tightly than infix operators) makes it easy for anyone to almost completely parse Once expressions, even in the presence of new operators (which Once allows users to introduce): you may not know what "foo la doe**bar none&?ook zork" does, but your only parsing question will be whether the infix ** operator has a higher or lower precedence than the infix &? operator.
- All builtin names have only one meaning. In C, * can mean multiply or dereference, depending on the context. In many programming languages, parenthesis are used both for grouping and to denote function calls. Well, in Once every builtin token name has exactly one use: parenthesis are only used for grouping and - is only used for infix subtraction, for example.
- Conversely, every semantic role gets only one syntactic expression. Many programming languages, for example, use different syntaxes for naming variables, procedures, functions, and/or recursive functions. In Once, all naming is done through the = operator. Also, overloaded operators are quasi-forced to have similar semantics in each overloaded role: the easiest way to overload + to work with type FOO automatically mandates that + have the properties of an abelian group operation (a+b == b+a, a+0 == a, a + neg a == 0, etc.).
- Parse zones. How program text gets parsed depends on what zone it is in, and there is one standard mechanism by which to embed zones inside of each other. By default, when we talk about how Once parses something, we are talking about the value zone, which can be embedded into other zones with the [$ start and $] end tokens. Other commonly used zones are the string zone ([" to start and "] to end), the comment zone ([# to start and #] to end), and the executable or shell zone ([` to start and `] to end). Here's an example:
[$ a = ["3 + 4 = [$ 3+4 $]\n"]; [# same as b = ["3 + 4 = 7\n"] #]
b = ["ls /proc = [`ls /proc`]\n"] $]
- By now, you've probably correctly guessed that all zones start with [x for some character x and end with x] with x being the same character. This makes it easy to avoid accidently starting or ending a zone inside a string for example: just backslash all literal ['s and ]'s.
New semantic features of Once:
- Guaranteed compiler/interpreter optimizations including PartialEvaluation. Many dialects of Lisp mandate that tail recursive calls be optimized to direct jumps because it enables by making efficient a certain style of programming. Once makes several other styles of programming (including strict type checking in a weakly typed language) possible by mandating other optimizations, with the partial evaluation of pure functions at compile time being the most important.
Are there ways to control how far PartialEvaluation is taken? Otherwise there may be excessive code expansion.
- Four level type system. Once is DynamicallyTyped in the sense that values, and not names, have types. TYPE_OF x returns the type of x (by convention, all type names and functions returning such are in all caps). All types have the type TYPE, so TYPE_OF TYPE_OF x == TYPE for all values x. It doesn't quite end there, however, as you can create mappings from names to types and functions; these mappings are called faces and are the closest Once comes to classes in the object oriented sense. Finally, above faces are interfaces, which are sets of requirements that one or more face may satisfy. Interfaces are coded as functions from faces to boolean which partially test whether the passed face satisfies some of the desired requirements, though a typical interface (CommutativeRing?, for example) will also mandate semantics for which total testing is impractical.
- More and more detail soon!
--
ThomasColthurst
Lots of interesting ideas. Do you have larger code samples anywhere? -- SteveHowell
Agreed, there's some good stuff here. Some comments:
- [unary operators] How do you handle unary minus? This is probably one of the most common unary operators, but isn't alphanumeric.
Unary minus is neg, as in y = neg x.
- [unary operators] Are unary operators somehow equivalent to functions, or will those have a separate syntax? How do you handle ternary and higher operators, or variadic functions for that matter?
In DM, functions all take just one argument. But that argument can be a tuple, which is what the comma operator forms. So you'll see things like y = f(a,b,c) which look like functions of more than one variable, but you could have also said x = (a,b,c); y = f x; . Binary operators are syntactic sugar for function calls: x = 1 + 2; is "really" x = _+ (1,2);
Nothing gets parsed like a ternary operator in DM. However, I'm considering adding some cleverness to the standard library to make things like "if .. _then .. [_else ..]" work. Gory details: _else (if it was part of the expression) would have the highest precedence and make an "else" tuple. _then would have the next highest precedence and make a "then" tuple out of the condition and its right argument (which might be an else tuple). "if" is then a normal function which demands a then tuple and does the correct thing with it. It remains to be seen whether all of this is worth it.
- [builtin names] This is a good idea, but how far are you going to take it? Are integer and floating point arithmetic considered "distinct", as in ObjectiveCaml, or are these basic infix operators overloaded for different numeric types, like almost every other language? Are you allowing polymorphism for user functions?
Not only are integer and floating point arithmetic considered distinct in DM, there are in fact many different integer and floating point (and even fixed point) types. There is IEEE32, IEEE64, INT8, INT16, INT32, INT64, and BIGINT, just to name a few. You can use the operators +, -, and * for any of these, or for any user defined type that implements the CommutativeRing? interface.
But none of this is "builtin" (to the interpreter, it is built into the standard library), so user code can do the same thing. In particular, there are two types of polymorphism in DM. There is "principled" polymorphism where you associate a set of functions and operators with an interface; they are then overloaded for every type that has an active implementation of that interface. (This is the kind of polymorphism used for arithmetic types). There is also "ad hoc" polymorphism in the sense that you can write a function that then dispatches on the type of its argument. DM encourages principled polymorphism over ad hoc polymorphism, and (hopefully) makes it the easier type of polymorphism to use, but ad hoc polymorphism is sometimes necessary (... in the standard library's implementation of principled polymorphism, for example!)
- [semantic roles] How much flexibility will the = operator allow? Will you have full GeneralizedReferences, which require the ability to execute arbitrary code, or will it be restricted to simple value assignment. In general, the more flexibility you grant, the less you can ensure that the programmer didn't shoot himself in the foot. Requiring semantics for certain operators otherwise seems like a great idea; I'm curious how you're accomplishing it.
DM is (semi-)functional, so = is the name binding operator and it's left argument has to be a name. On the other hand, DM does have := which does over-writing assignment like setf, but :='s left argument has to have a box type: x = box 3; x := 4; While :='s left argument is allowed to be any value or expression with box type, it's not a GeneralizedReference. (Version 2.0 of DM may support AspectOrientedProgramming, which would then let you turn :='s into GeneralizedReference s, but in all honesty, even Version 1.0 of DM is a long way off right now.)
Requiring semantics for certain operators is mainly accomplished by associating those operators with an interface. (See above comment on principled polymorphism and below on DM's type system). I should perhaps mention that it isn't an ironclad requirement: when you register your implementation of SPIFFY_NEW_TYPE as a CommutativeRing?, DM doesn't actually check to see if a*(b+c) == a*b + a*c for all values; that's merely a property that CommutativeRing?'s documentation says is required and that functions which use CommutativeRings? are justified in assuming.
- [parse zones] Are you familiar with RebolLanguage? This has a similar concept, called dialects. Are parse zones first-class? If so, you get a lot of power (like Rebol), but the language becomes impossible to compile. If not, take a look at XlLanguage? (http://mozart-dev.sourceforge.net/xl.html), which has something called "compiler plugins" that are very much like this.
I'm not familiar with either Rebol or Xl, but I will take a look at them. Parze zones are not first class (but all non-comment parse zones return first class values).
- [types] Do you mean DynamicTyping instead of WeakTyping ? Weak typing implies arbitrary interpretation of bit-patterns, or at the very least implicit conversions. It's a risky thing to have in a programming language, particularly one that tries to guarantee semantics.
Yes, I mean DynamicTyping instead of WeakTyping. My bad. I've fixed it above. It's perhaps worth noting that there are no implicit conversions in DM, but there is a builtin function that will convert any value into an array of bytes.
- [types] 'Faces' seem like Haskell 'kinds'...it may be worth looking into Haskell's type system. I'm not really sure what you're getting at with faces though...some examples might be nice.
Faces are nothing more than interface implementations. Here's an example:
WeakOrder? = Interface [l _<= l]; [# <= should satisfy: forall a,b : (a <= b) || (b <= a)
forall a,b,c : (a <= b) && (b <= c) _implies (a <= c) #]
Register WeakOrder? INT64_LESS_THAN_OR_EQUAL_TO; [# This is the standard WeakOrder? on integers #]
x = (5 <= 25);[# x = true #]
Register WeakOrder? INT64_DOESNT_DIVIDE; [# This is a nonstandard WeakOrder? on integers #]
x = (5 <= 25);[# x = false #]
UnRegister? WeakOrder? INT64_DOESNT_DIVIDE;
x = (2 <= 8);[# We are back to the standard one, so x = true #]
--
JonathanTang
Thanks for your comments and your excellent questions! -- ThomasColthurst
RE: "[...] there isn't anything particularly extreme about Once."
''You could use XP while writing Once. ;->
Once has been renamed to DM (short for Dead Mathematician). More information about it can be found
in the paper http://www.hacksaw.org/~thomasc/dm_paper.ps --ThomasColthurst
I don't think that was like John Baez on acid. It sounded very much like John Baez like he usually is at a conference, except less talkative. And fewer references to category theory. :-) -- DougMerritt