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

refine Callable attributes as if they were functions and vice‐versa #6424

Open
ghost opened this issue Aug 14, 2016 · 8 comments
Open

refine Callable attributes as if they were functions and vice‐versa #6424

ghost opened this issue Aug 14, 2016 · 8 comments

Comments

@ghost
Copy link

ghost commented Aug 14, 2016

Derp derp derp. ceylon/ceylon-spec#1472

It’d be nice to be able to refine Callable attributes as if they were functions and vice versa. Things that I’d like to see working:

abstract class Foo()
{
    shared formal String(String) surround;
}

class Bar()
extends Foo()
{
    shared actual String surround(String str) => "\{RIGHT DOUBLE QUOTATION MARK}``str``\{LEFT DOUBLE QUOTATION MARK}";
}
abstract class Foo()
{
    shared formal String id(String str);
}

class Bar()
extends Foo()
{
    id = identity<String>;
    // or
    // shared formal String id = identity<String>;
}
abstract class Foo<T>()
{
    shared formal T member;
}

class Bar()
extends Foo<String(String)>()
{
    shared formal String member(String str) => "\"``string``\"";
}

A little harder to support:

abstract class Foo()
{
    shared formal String member(String str);
}

class Bar()
{
    shared formal String(String) member
    {
        if(random())
        {
            retrun(identity<String>);
        }
        else
        {
            return(function(String str) => str + "!!!");
        }
    }
}
@lucaswerkmeister
Copy link
Contributor

Shortcut refinement supports both directions already:

abstract class Foo()
{
    shared formal String(String) surround;
}

class Bar()
extends Foo()
{
    surround(String str) => "\{RIGHT DOUBLE QUOTATION MARK}``str``\{LEFT DOUBLE QUOTATION MARK}";
}

try online

abstract class Foo()
{
    shared formal String id(String str);
}

class Bar()
extends Foo()
{
    id = identity<String>;
}

try online

abstract class Foo<out T>()
{
    shared formal T member;
}

class Bar()
extends Foo<String(String)>()
{
    member(String str) => "\"``string``\"";
}

try online

@ghost
Copy link
Author

ghost commented Aug 14, 2016

Fascinating. I guess that means it works for locals as well, right? I’m sure this used to not compile:

shared void run()
{
    String(String) foo;
    foo(String str) => "\"``str``\"";
}

Although, I know this did:

shared void run()
{
    String foo(String str);
    foo = identity<String>;
}

Wat about the last example? Can something like this work? It currently doesn’t.

shared void run()
{
    String foo(String str);
    foo => identity<String>; // note the `=>`
}

@ghost
Copy link
Author

ghost commented Aug 14, 2016

It’s also important to note that this doesn’t compile:

shared void run()
{
    abstract class Foo()
    {
        shared formal String(String) surround;
    }

    class Bar()
    extends Foo()
    {
        surround(String str) => "\{RIGHT DOUBLE QUOTATION MARK}``str``\{LEFT DOUBLE QUOTATION MARK}";
    }

    print(Bar().surround{str = "Hello World!";}); // Named arguments not supported for indirect invocations
}

@ghost ghost changed the title refine Callable attributes as if they were functions and vice versa refine Callable attributes as if they were functions and vice‐versa Aug 14, 2016
@jvasileff
Copy link
Contributor

jvasileff commented Aug 14, 2016

A little harder to support:

I think it would be very confusing to allow a method to be refined by a lazy value, since you would normally expect the two examples below to be equivalent:

    Foo f = Bar();

    // example 1: both calls to the *same* randomly selected function
    value fun = f.member;
    fun("");
    fun("");

    // example 2: each call to its own randomly selected function!
    f.member("");
    f.member("");

Further, I don't believe the above can actually be implemented, because if Foo.member is a declared as a method, what would actually happen in the backend is that f.member would result a the creation of a new Callable that delegates to f.member(), so example 1 would actually work like example 2, which I don't believe would be "correct". I mean, if we're to think of member as a value, then it shouldn't be re-calculated after we obtain a reference to it (i.e. after myVal = rng.randomInt, myVal doesn't change every time you read it, despite randomInt being lazy).

OTOH, if Foo.member is a value, everything works just fine, because the backend doesn't have to create a wrapper Callable (we simply obtain the Callable by calling the getter f.member). And, given how values work, the differing behaviors in examples 1 & 2 would not be surprising.

@gavinking
Copy link
Contributor

@Zambonifofex It's 100% intentional that you can't assign to a function using a fat arrow =>.

@gavinking
Copy link
Contributor

@Zambonifofex I agree that it would be nice to make the following code compile:

abstract class WithFunctionAndValueAbstract() {
    shared formal String fun(String str);
    shared formal String(String) val;
}

class WithValueAndFunctionShouldWork()
        extends WithFunctionAndValueAbstract() {
    shared actual String(String) fun = identity<String>;
    shared actual String val(String str) => str;
}

It's especially reasonable, given that it already works for shortcut refinement.

We only decided not to support it in order to ease implementation on the Java backend. I'm not sure how relevant that concern is now.

Having said that, it's not a high priority.

@gavinking gavinking added this to the 1.3 milestone Aug 14, 2016
@jvasileff
Copy link
Contributor

jvasileff commented Aug 15, 2016

I'm not sure allowing the long-form mix-and-match refinements would be an improvement. To me, the following seem unambiguous:

  • In shared actual String(String) fun = ..., fun is a value.
  • In shared actual String val(String str) => ..., val is a function.

This is different from the flexibility offered by shortcut refinements, because, at least the way I read them, shortcut refinements don't re-state declarations, and are therefore less indicative of whether the declarations they are "assigning to" are functions or values.

Why does this matter? In Ceylon, functions and values are not the same things:

  • You can't refine a function to be variable
  • You can't refine a function to be lazy
  • Covariant refinements of the return type of functions and values are valid, but covariant refinement of a value of type Callable can look like a _contra_variant refinement of a function's parameter types, which is not allowed.
  • You can't use named arguments when invoking a Callable value.
  • Other?

I presume with the mix-and-match idea that from outside the defining class or interface, fun will always look like a function (and val/value), but I believe even blurring the line within class and interface definitions will lead to confusion.

I'm not aware of any real practical benefit to this, and I think the change would only hurt from a purity or design standpoint, drawing an equivalence where none exists.

@gavinking
Copy link
Contributor

@jvasileff as I said, I don't perceive this issue as being at all a priority.

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

3 participants