Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

single-line function declarations #3548

Closed
CeylonMigrationBot opened this issue Oct 24, 2012 · 20 comments
Closed

single-line function declarations #3548

CeylonMigrationBot opened this issue Oct 24, 2012 · 20 comments

Comments

@CeylonMigrationBot
Copy link

[@gavinking] This is something that came up in tangential discussions in #3469 and #3483.

Currently we let you write:

function intRange(Integer from, Integer to) = Range;

or

value intRange = Range;

both of which define the function intRange() by reference, making intRange essentially an alias for Range. This was something I settled on a very long time ago, without it ever having been exposed to much discussion. I'm now more and more inclined to think that this is not the best way to go. I think we should instead give you the choice between:

function intRange(Integer from, Integer to) = Range(from, to);

and

value intRange = Range;

i.e. the parameters of the function would be in scope on the RHS of = and the type of the RHS expression would be expected to be the return type of the function, not the callable type of the function. This would:

  1. give you a lot more flexibility, allowing shit like:

    function intRange(Integer to) = Range(0, to);
    function sqr(Float x) = x*x;
    class IntRange(Integer to) = Range(0, to);
    

    Indeed, = something; would now just mean { return something; }.

  2. be much more consistent with the way type alias declarations work currently, where the type parameters are in scope on the RHS (but not the ordinary parameters, curiously, which would be fixed under this proposal).

  3. be consistent with the traditional notation in mathematics and, I think, usually more readable.

  4. simply look a lot more symmetric and "balanced".

The only subtle thing that I can think of here is with forward declaration and named arguments. Currently, the following things are natural:

//forward declaration
function intRange(Integer from, Integer to);
intRange = Range;

//named arg
void run(Progress p) { ... }
void repeat(void do(Progress p)) { ... }
repeat { do = run; };

Now, that seems, initially to clash with the new syntax. But not if we let you write the following as well:

//forward declaration
function intRange(Integer to);
intRange(Integer to) = Range(0, to);

//named arg
void run(Progress p) { ... }
void repeat(void do(Progress p)) { ... }
repeat { do(Progress p) = run(p); };

In fact, this seems to work out pretty neatly (assuming it can be easily parsed).

Indeed, given this new syntax repeat { do(Progress p) = run(p); };, we might wind up just simplifying the whole syntax of named argument lists!

[Migrated from ceylon/ceylon-spec#442]
[Closed at 2012-11-16 09:15:01]

@CeylonMigrationBot
Copy link
Author

[@ikasiuk] What about the => notation proposed in #3483 (see comment #3483#issuecomment-7551078)? This seems closely related to the syntax proposed here. Do you consider allowing both, or is the => notation off the table then?

@CeylonMigrationBot
Copy link
Author

[@gavinking] This would be instead of introducing =>. Essentially I'm proposing that the semantics of => be used whenever we have an = after a parameter list.

There is one major advantage that => has: it lets you define lazy getters:

String name => firstName + ' ' + lastName;

But I guess we could achieve the same effect with an annotation:

lazy String name = firstName + ' ' + lastName;

(Or something.)

Do you consider allowing both, or is the => notation off the table then?

I'm trying to avoid introducing => because

  1. It's very slightly cryptic (though with its popularity in newer languages, increasingly less so)
  2. It would give us three syntaxes for defining a function instead of two.

@CeylonMigrationBot
Copy link
Author

[@gavinking]

we might wind up just simplifying the whole syntax of named argument lists!

So I want to go a little further down this path. But first I need to bring up a missing feature of named argument lists that we're going to need when we get up to using them to build user interfaces. What we need is the ability to declare a named value inside a named argument list, and access that value "further down the tree". For example:

Grid {
    value text = Text();
    Cell {
        Label("Name: "), text
    },
    Cell
        Button {
            label = "Greet";
            onClick(Click click) = sayHello(text.value);
        }
    }
}

Given the current syntax for named arg lists, the line value text = Text(); would be interpreted as an argument to a parameter named text of Grid, so we would need some special annotation or some shit to distinguish that this is just an ordinary lexically-scoped declaration.

But if we can now define functional arguments using the new syntax proposed above, i.e.

arg(Param param) = expression(param);

Then perhaps it's not as important to be able to do this anymore:

Button {
    label = "Greet";
    function onClick(Click click) { sayHello(text.value); }
}

And it would be OK to force you to use the actual annotation to distinguish "new" local declarations from argument functions:

Button {
    label = "Greet";
    actual function onClick(Click click) { sayHello(text.value); }
}

Note that this would be extremely consistent with how this stuff works for formal members and refinement! (We interpret a specification statement as refinement, or require you to annotate the declaration actual.)

Thoughts?

@CeylonMigrationBot
Copy link
Author

[@gavinking] i.e. what I'm saying is you could write either:

Grid {
    value text = Text();
    Cell {
        Label("Name: "), text
    },
    Cell
        Button {
            label = "Greet";
            onClick(Click click) = sayHello(text.value);
        }
    }
}

or:

Grid {
    value text = Text();
    Cell {
        Label("Name: "), text
    },
    Cell
        Button {
            label = "Greet";
            actual function onClick(Click click) {
                sayHello(text.value);
            }
        }
    }
}

@CeylonMigrationBot
Copy link
Author

[@gavinking] P.S. Of course, you would also be able to use the syntax for refining formal methods:

abstract class X() {
    shared formal Float x;
    shared formal Float y(Float z);
}

class Y() extends X() {
    x = 2.0;
    y(Float z) = z**x;
}

That's going to be really convenient.

@CeylonMigrationBot
Copy link
Author

[@ikasiuk] There's one thing that strikes me here: how increasingly similar named argument invocations and class bodies are getting. And especially when considering the planned ad-hoc refinement with named arguments (interface instantiation) it seems that a named argument invocation (of a type) basically is an inline object declaration. I think we should seriously explore if there is a way to merge the syntax of named arguments with that of object bodies.

@CeylonMigrationBot
Copy link
Author

[@gavinking] FTR, I have always considered it a strong goal that they should be very
similar, syntactically and semantically.
On Nov 2, 2012 4:15 PM, "Ivo Kasiuk" [email protected] wrote:

There's one thing that strikes me here: how increasingly similar named
argument invocations and class bodies are getting. And especially when
considering the planned ad-hoc refinement with named arguments (interface
instantiation) it seems that a named argument invocation (of a type)
basically is an inline object declaration. I think we should seriously
explore if there is a way to merge the syntax of named arguments with that
of object bodies.


Reply to this email directly or view it on GitHub<#3548#issuecomment-10017503>.

@CeylonMigrationBot
Copy link
Author

[@FroMage]

function intRange(Integer from, Integer to) = Range(from, to);

I find that confusing because I get the impression that intRange is assigned the result of invoking Range(from, to). I understand that this is more in line with class aliases, but for some reason in the case of class aliases I don't have the same impression that the RHS constructor is invoked when defining the alias.

@CeylonMigrationBot
Copy link
Author

[@FroMage] Regarding allowing local variables in named-param calls, I find it pretty confusing to read. Not only that but if we suddenly require actual annotations in there for regular params, it gets heavy.

I'd rather have a let expression, but even that I'm not really fond of adding at this point.

I think we should wait until the need for something like that arises. I haven't had it yet.

@CeylonMigrationBot
Copy link
Author

[@gavinking]

I think we should wait until the need for something like that arises. I haven't had it yet.

You will need it the very second you first try to write a user interface in Ceylon.

@CeylonMigrationBot
Copy link
Author

[@FroMage] I never got that idea (yet). I am on the other hand pretty sure that I'd do this sort of trick in an HTML template:

html{
 body{
  a{
   if(userIsAdmin){
    id="mainLink";
   }
   href = "...";
  }
  ...
  a{
   if(!userIsAdmin){
    id="mainLink";
   }
   href = "...";
  }
 }
}

@CeylonMigrationBot
Copy link
Author

[@gavinking] I have implemented the first step of this: making stuff like the following work:

function sqrt(Float x) = x**0.5;

And wow, updating the tests, I realize I like this syntax soooo much better!

@CeylonMigrationBot
Copy link
Author

[@gavinking] The following is now essentially working:

function sqrt(Float x);
sqrt(Float x) = x**0.5;

Pretty cool :-)

@CeylonMigrationBot
Copy link
Author

[@ikasiuk] There's something I don't like about this:

Float f() { return x*x; }
Float f() = x*x;
Float f = x*x;
Float f { return x*x; }

One of these behaves different than the others, and I find that not very obvious. That was the big advantage of =>: it makes the difference immediately clear.

Also, I could imagine that getters would be a typical case where the one-line syntax would be useful because getters are often very simple. But as far as I can tell there's currently no way to apply that syntax to getters, isn't it?

@CeylonMigrationBot
Copy link
Author

[@gavinking] @ikasiuk I agree, it's the one downside of this, and as mentioned above, it's likely we'll want to add some kind of lazy annotation or something...

@CeylonMigrationBot
Copy link
Author

[@tombentley] I used to be able to write

Callable<Integer, <Boolean>> f { throw;}
Integer foo(Boolean b) = f;

but this is no longer considered well-typed. I guess a lazy annotation might let me write this, but what I find really surprising is that by deferring the assignment

Callable<Integer, <Boolean>> f { throw;}
Integer foo(Boolean b);
foo = f;

it suddenly becomes well-typed. Is that a bug in the current implementation, or a "feature"?

@CeylonMigrationBot
Copy link
Author

[@gavinking]

Is that a bug in the current implementation, or a "feature"?

That's a feature. You're assigning a value of type Callable<Integer, <Boolean>> to a value of the same type.

Of course the following is not well-typed:

Callable<Integer, <Boolean>> f { throw;}
Integer foo(Boolean b);
foo(Boolean b) = f;

@CeylonMigrationBot
Copy link
Author

[@gavinking] This feature has been implemented:

  • for declarations in ordinary blocks, and
  • for callable parameters.

FTR, @FroMage and @emmanuelbernard talked me into the fat arrow syntax =>. So, for example, it is:

void tenTimes(String f(Integer i) => i.string) =>
        print(", ".join(for (i in 0..10) f(i)));

To finish off this work, we need to do #3572.

We also need to think some more on my idea above:

<#3548#issuecomment-10010767>

@CeylonMigrationBot
Copy link
Author

[@quintesse] What does it mean to have it in the parameter? Is it the default value?

@CeylonMigrationBot
Copy link
Author

[@gavinking]

Is it the default value?

Of course.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

1 participant