As Kim recently pointed out, we have several inconsistent ways of
declaring “things with parameters”:
- Methods. For example:
12345method + other:Complex -> ComplexComplex.new(self.real + other.real , self.imag + other.imag)method between(l:Number)and(u:Number) -> Boolean(self > l) and {self < u}Methods are not first class. Still, a typical method request,
16.between(5)and(9)looks a bit like a function application.
- Blocks, which are first-class values. For example:
1const isNearby := { x:Complex -> (self-x).abs < 10 }Blocks denote objects, with a single method named apply that takes as many arguments as the block has parameters, in this case, exactly one. Note that the self inside that above block denotes not the block object, but the object in which the block itself lives.
This block would be used as follows:
123if isNearby.apply(vertex)then { doSomething }else { doSomethingElse }
- Class constructors, which are also first-class values.
1234567891011const Complex := class { re:Number , im:Number ->const real:Number := reconst imag:Number := immethod + other:self.type -> self.type {Complex.new(real + other.real , imag + other.imag) }method - other:self.type -> self.type {Complex.new(real - other.real , imag - other.imag) }method abs -> Number {(real.squared + imag.squared).sqrt } }method conj -> self.type {real - imag.i }Class constructors denote objects too; these objects have a single method named new that takes as many arguments as the class constructor has parameters. You can see examples of the application of the resulting class in the bodies of the methods + and -. Note that the self inside the above class constructor denotes not the class object that it constructs, but the object that will be created when the method new is invoked on the class. Note that we may want, in addition to the above, for this form to also bind Complex to a type that looks like the body of the class constructor:
12345678type {method real-> Numbermethod imag -> Numbermethod + other:self.type -> self.typemethod - other:self.type -> self.typemethod abs -> Numbermethod conj -> self.type}
- Fields, which are really a shorthand for method declarations. For example, in the above object constructor, we have
1const real:Number := rewhich can be seen as a shorthand for
1method real -> Number { re }But, you say, constant reader methods don’t have parameters. Quite true. However, variable writer methods do, and
1var address:Stringcan be seen as a shorthand for
1method address := s:String { < <address>> := s }which does have a parameter.
Notational Differences
There are several dimensions of difference in the above four notations:
- nominal versus anonymous. Method and field declarations always construct named entities; block and class constructors always create anonymous entities, which are typically returned as answers or bound to names in a separate step;
- parameter placement – the nominal forms put the parameters with the name; the anonymous forms put them after an invisible lambda;
- the meaning of self; and
- the presence or absence of a keyword; blocks alone have no keyword.
Open Questions
The elephant underlying this post is the question: should we abolish one or more of these forms, or seek to unify them? At present Andrew proposes that the answer to both of these questions should be no. We introduced each one for a good reason; I believe that we need them all. And I don’t think that a uniform syntax will make programs more readable; Beta did that, and I don’t think that it works.
Still, I may be wrong, and I may change my mind. Or we may want to discuss this further. This post provides a place for that discussion.