Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bringing back Variable Arity Methods #130

Closed
apblack opened this issue Jul 25, 2017 · 15 comments
Closed

Bringing back Variable Arity Methods #130

apblack opened this issue Jul 25, 2017 · 15 comments
Labels

Comments

@apblack
Copy link
Contributor

apblack commented Jul 25, 2017

I would like to bring back variable arity methods, with certain restrictions. Here is the proposal, as we discussed it during today's video meeting.

Syntax

  1. In a method declaration, the final parameter of a method can be written paramName ... or paramName:ParamType ...

  2. Variable arity parameter lists shall be permitted only as the final parameter of a method. So,

method ex (p) why (q...)

is permitted, but

method ex (p...) why (q)

is not permitted.

  1. A method declaration m(p...) shall be treated as declaring methods m(_), m(_,_), m(_,_,_), m(_,_,_,_), and so on, for all methods m(...) with any number of parameters greater than zero. It does not declare a method m with zero parameters. This means that m(p...) overrides inherited declarations of m with any non-zero parameter list.

  2. If m(p...) is inherited, and m(_,_) declared locally, does the declaration of m(_,_) override that of m(p...) with all parameter list lengths, or just that for parameter lists of length 2? Or, is such a local declaration illegal?

Semantics

When a method with a variable arity parameter list is requested, the variable-arity parameter is bound to a Sequence of objects of its annotated type. So, if the method header has the form

method m(n, o, p ...) 

n is bound to the first argument supplied in the request of m(...), o is bound to the second, and p is bound to all of the remaining arguments. p is treated as having type Sequence⟦Unknown⟧.

If the method declaration has the form

method m(n, o, p:Tp...) { 
    ...
} 

p is bound to all of the arguments supplied in the request of m, except for the first two, which are bound to n and o, p is treated as having type Sequence⟦Tp⟧. This may generate a type error if any of the 3rd, 4th, 5th ... arguments in the request does not conform to Tp.

@apblack apblack added the design label Jul 25, 2017
@KimBruce
Copy link
Contributor

KimBruce commented Jul 26, 2017 via email

@apblack
Copy link
Contributor Author

apblack commented Jul 29, 2017

Writing this stuff down, especially the overriding rules, and the following discussion, is tending to make me want to withdraw my recantation. In other words, I'm thinking that I should just put list, set, sequence and dictionary with 1, 2, 3, ... 9 parameters into the collections library, and leave it at that.

The types are particularly troubling, too. In a method with header

method m(n:Tn, p:Tp...) { 
    ...
} 

parameter n has type Tn, but parameter p does not have type Tp — it has type Sequence⟦Tp⟧. (That's actually the reason for putting the ... immediately after the type annotation — you have to think of the ... as modifying the annotation.

Of course, we don't request the method with an argument of type Sequence⟦Tp⟧ — in fact, it's impossible to do that.

Dave Ungar suggested that the only reason that I’m suggesting this is that in the year or more since we took out variable arity, I've forgotten just how horrible it was. I think that he has a point.

@kjx
Copy link
Contributor

kjx commented Jul 30, 2017 via email

@KimBruce
Copy link
Contributor

KimBruce commented Jul 31, 2017 via email

@kjx
Copy link
Contributor

kjx commented Aug 1, 2017 via email

@apblack
Copy link
Contributor Author

apblack commented Aug 2, 2017 via email

@kjx
Copy link
Contributor

kjx commented Aug 2, 2017 via email

@apblack
Copy link
Contributor Author

apblack commented Aug 3, 2017

++ already exists — it does the analogous thing for Lists that it does for Strings and Sequences: makes a new collections that's the concatenation of the receiver and the argument.

@gracelang gracelang deleted a comment from KimBruce Aug 13, 2017
@apblack
Copy link
Contributor Author

apblack commented Aug 13, 2017

As far as I recall, the intended resolution of this issue is as follows. Others should correct me.

  1. There should be a method list (set, sequence, dictionary) without arguments that represents the list (etc) –factory object. This is so that the collection in the prelude can be uniform with user-defined collections imported from modules.

  2. These factory objects should have the following methods:

  • empty — returns the appropriate empty collection
  • withAll(existingCollection) — returns a new collection containing the elements of existingCollection
  • with(elm1) — returns a new collection containing elem1
  • with(elm1, elem2) — returns a new collection containing elem1 and elem2
  • with(elm1, elem2, elem3) — returns a new collection containing elem1, elem2, and elem3
  • and so on, up to
  • with(elm1, elem2, elem3, elem4, elem5, elem6, elem7, elem8, elem9) — returns a new collection containing elem1, elem2, elem3, ... elem9.
  1. The square-bracket "array constructor" syntax should be repurposed to create a Sequence, not a Lineup.

  2. Collections already support the infix operator >> for use in pipelines. By an appropriate use of genericity, we should allow an alternative to writing list.withAll [1, 2, 3]: instead one could write [1, 2, 3] >> list.

  3. Dialects, such as beginningStudent, may continue to use list(_), list(_,_), etc. if they so desire.

Oh, I should have added: we will not be re-instituting variable arity methods at this time. The issues with overriding seem too complex.

@apblack
Copy link
Contributor Author

apblack commented Aug 13, 2017

On 8th August 2017, in an email, @kjx wrote

James' latest bad idea:

a variadic definition

method foo( x... ) 

not only generates

method foo(x1)
method foo(x1,x2)
method foo(x1,x2,x3)
method foo(x1,x2,x3,x4)

but also

method fooAll(xs)

and the variadic forms are officially implemented by calling fooAll.

So: if you have a collection you can call fooAll to pass it into the variadic method.

In theory it would be possible to override the fooAll method separately from the others,
but if we do varargs, we should probably have a rule that you have to override the whole
variadic "family" at one go, and fooAll would be part of the family.

@KimBruce
Copy link
Contributor

Andrew's comment above corresponds to my understanding of our agreement.

@kjx
Copy link
Contributor

kjx commented Aug 14, 2017

I can't remember. This doesn't answer the key question(s) we talked about. Kim asked:

  • what should I write in the book?

list(1,2,3) ? list.with(1,2,3) ? or [1,2,3] >> list

  • do we want substantial syntax for a built-in collection? That's a philosophical question, unfortunately, about object-oriented programming.

minor points:

  • There should be a method ... that represents the list (etc) –factory object
    In (most of) this design, we can call it the list module. That's good thing.

  • list (set, sequence, dictionary) without arguments
    I'm not sold on this big distinction between zero-arg and multi-arg methods (list vs list(x) or list(x,x,x)`. I can see the appeal, and can't really think of anything better...

  • The square-bracket "array constructor" syntax should

Which I'd still like to get rid of - go back to Scala type syntax...

  • The square-bracket "array constructor" syntax should be repurposed to create a Sequence, not a Lineup.
    We need to rationalise the collection design, so this mostly makes sense. Entia non sunt multiplicanda praeter necessitatem. If we went to varargs, and we had Java8 style definite streams, we could make the vararg "collection" a definite stream.

  • Collections already support the infix operator >> for use in pipelines.. one could write [1, 2, 3] >> list
    Could or should? what should Kim pick?

  • By an appropriate use of genericity,
    Not just genericity. Type inference also - in fact, I'm pretty sure, it needs subtle and novel work on the interactions between generics, inference, and gradual typing, and almost certainly yet more tradeoffs we won't like.

It's possible we could juggle thing to work in just these cases: [ ... ] or list(...) dynamically work out the some (structural? defined?) lub type for their arguments and then use that type to _ dynamically_ instantiate the actual generic arguments of the underlying collection.

// on some collection object method >>( collectionFactory) { var argType : Type = None // ? do { elem -> argType := argType & elem.magicallyGetMostSpecificType } return collectionFactory.withAll[argType](self) //argType isn't manifest. oops. }

At which point we're probably killed quickly by variance. At least we'd need Ceylon-style separate input (contravariant) and output (covariant) interfaces for collections...

Dialects, such as beginningStudent, may continue to use list(), list(,_), etc. if they so desire.

Oh, I should have added: we will not be re-instituting variable arity methods at this time. The issues with overriding seem too complex.

@KimBruce
Copy link
Contributor

As I understand it from Andrew's write-up, we write [1,2,3,4] for a sequence and list.withAll[1,2,3,4] for the list created from the sequence.

@kjx
Copy link
Contributor

kjx commented Aug 14, 2017

we write [1,2,3,4] for a sequence

Well that's the question. That or seq(1,2,3,4) - which isn't too bad.

@kjx
Copy link
Contributor

kjx commented Nov 9, 2017

this was accepted as per Andrew's design

@kjx kjx closed this as completed Nov 9, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants