eclipse-archived / ceylon

The Ceylon compiler, language module, and command line tools
http://ceylon-lang.org
Apache License 2.0
398 stars 62 forks source link

Allow stateful functions through static variables #7433

Open xkr47 opened 5 years ago

xkr47 commented 5 years ago

Sometimes it would be cool if functions could have static variables, which would be about the same as an object with fields and implementing a java Functional interface.

So I could do:

value x = foo.filter((y) {
    static variable value done = false;
    if (!done) {
      doit();
      done=true;
    }
    return true;
  });

instead of

variable value done = false;
value x = foo.filter((y) {
    if (!done) {
      doit();
      done=true;
    }
    return true;
  });

This was discussed in gitter; (hopefully correct) summary here:

lucaswerkmeister commented 5 years ago

A feature like this might be useful, but I don’t see what this has to do with the existing meaning of static, and I don’t think we should copy C’s habit of reusing keywords for wildly different behavior here. If I saw a static value in a function –

class C() {
    void f() {
        static variable Integer i = 0;
        print(++i);
    }
}

C c = C();

– then I would expect one of the following to be allowed:

Integer i = C.f.i;
Integer i = c.f.i;

Which one? No idea. (I suppose that’s the same as @jvasileff’s concern.)

jvasileff commented 5 years ago

I think it's interesting to explore reducing the differences between classes/objects and functions, and this feature (syntax aside) looks pretty neat. But, I wonder if it can be really justified.

Is memoizeNew below that much better than memoizeOld?

shared Result() memoizeOld<Result>(Result() f)
        given Result satisfies Object {
    variable Result? memo = null;
    return () => memo = memo else f();
}

shared Result() memoizeNew<Result>(Result() f)
        given Result satisfies Object => () {
    static variable Result? memo = null;
    return memo = memo else f();
};

Edit: for completeness, the curried form:

shared Result memoizeNew2<Result>(Result() f)()
        given Result satisfies Object {
    static variable Result? memo = null;
    return memo = memo else f();
}
ghost commented 5 years ago

Doesn’t this have the same problems as classes did before constructors were a thing?

Like, the argument of the function (which is in scope) must not be referenced from the static value’s specifier, which is awkward.

xkr47 commented 5 years ago

Vote for closure!

arseniiv commented 5 years ago

@Zambonifofex Could it be fixed the same way, then? A function with “constructors”, why not? It could even be a way to implement “overloaded” functions (whose types are perfectly expressible from the start).

// has type `Callable<Integer,[String]|[Boolean]>`
shared Integer sillyExample {
    value magic = 3;
    shared new (String s) {
        return s.size * magic;
    }
    shared new (Boolean b) {
        return if (b) then magic else 0;
    }
}

P. S. Oops, I forgot overloading the default constructor in classes seems to not be allowed, my bad. But some analogous idea seems not that crazy, nonetheless.

ghost commented 5 years ago

@arseniiv, I thought about it myself, but I figured that, since Ceylon doesn’t have overloading (except within native("jvm")), it’d only be right to allow named constructors, which don’t really make sense in functions.

I guess it could make sense to allow a single default “constructor” in functions:

Integer hmmm
{
    static variable Integer foo = 0;
    new (Integer i)
    {
        foo++;
        return i + foo;
    }
}

shared void run()
{
    // In lambdas too:
    foo(
        function
        {
            static variable Integer foo = 0;
            new (Integer i)
            {
                foo++;
                return i + foo;
            }
        }
    );
}

Unfortunately, this not only is kinda silly, but, for lambdas, it also collides with the syntax for #7190, which I have grown to like.