Attribute lazy initialization #438

Open
matejonnet opened this Issue Oct 18, 2012 · 29 comments

Comments

Projects
None yet
Member

matejonnet commented Oct 18, 2012

Looking for a shorter way to initialize an object attribute on a first use,
and ability to use immutable attribute instead of variable

variable Foo? foo := null;
Foo getFoo() {
    if (exists f = foo) {
        return f;
    }
    foo := createFoo();
    if (exists f = foo) {
        return f;
    }
    throw;
}

I would like to write this as:

Foo? foo;
Foo getFoo() {
    if (!exists foo) {
        foo = createFoo();
    }
    return foo;
}
Member

matejonnet commented Oct 18, 2012

Or even shorter if it is possible to do something like this:

Foo foo ?= createFoo();

"?=" will cause that createFoo() is called on first usage of foo.

Member

RossTate commented Oct 18, 2012

The non-variable version is bad cuz you're accessing a value before it's been initialized.

variable Foo? foo := null;
Foo getFoo() {
    if (exists f = foo) {
        return f;
    }
    foo := createFoo();
    return foo;
}

would be bad because between foo := createFoo(); and return foo; a concurrently running program could have changed the value of foo to null.

Lazy<Foo> foo = lazy createFoo(); seems a better solution.

Member

luolong commented Oct 19, 2012

+1 for this syntax:

Foo foo ?= createFoo();
Owner

gavinking commented Jan 3, 2013

So, @matejonnet has a point here. The simplest I was able to get was the following:

Integer computeVal() => ... ;

variable Integer? lazyVal:=null;

shared Integer val {
    if (exists val=lazyVal) {
        return val;
    }
    else {
        value val=computeVal();
        lazyVal:=val;
        return val;
    }
}

which sorta took me by surprise. It's a bit better than what @matejonnet writes above, but it's still unacceptably verbose.

The big problem here is that, ages ago, @FroMage asked me to change the type of assignment expressions like lazyVal:=computeVal() from the RHS type (in this case, Integer) to the LHS type (in this case, Integer?) to make things easier on the Java backend. If we had not made this change, then the following code would work:

Integer computeVal() => ... ;

variable Integer? lazyVal:=null;

shared Integer val =>
        lazyVal else lazyVal:=computeVal();

Which I think would be acceptable, no?

@FroMage WDYT, can we change this back?

Owner

gavinking commented Jan 3, 2013

@luolong The following would certainly work:

Integer computeVal() => ... ;

variable Integer? lazyVal:=null;

shared Integer val =>
        lazyVal?=computeVal();

where ?= is an operator that evaluates the RHS and assigns it to the LHS iff the LHS is null. But it's more cryptic and not much less verbose than lazyVal else lazyVal:=computeVal().

Owner

FroMage commented Jan 3, 2013

I'd like a way to define lazy memoised attributes, I've already made that clear.

Perhaps memoised Integer val => computeVal() would be enough, though memoised doesn't speak to most people, but using lazy here wouldn't imply that the value is only computed once, since => is already lazy. Perhaps cached?

I think that ?= is not useful outside of this use-case (lazy memoised attributes) (though feel free to prove me wrong) and that this particular use-case still has too much boilerplate, even Gavin's solution, compared to:

Integer computeVal() => ... ;

cached shared Integer val => computeVal();

I mean, if we introduce ?= only for this use-case, we should fix this use-case better than half-way.

Owner

quintesse commented Jan 3, 2013

once ?

Owner

gavinking commented Jan 3, 2013

FTR, I'm not advocating ?=. I'm advocating we fix the type of assignment expressions back to what it should be!

I'm also not against eventually introducing a Fantom-style once annotation, but I don't want to do it in Ceylon 1.0.

Owner

FroMage commented Jan 3, 2013

Fair enough, but changing the type of := means making it more expensive when compiled to Java. I'm not sure that's worth it.

Owner

gavinking commented Jan 3, 2013

Fair enough, but changing the type of := means making it more expensive when compiled to Java.

Only when:

  • the RHS type is narrower than the LHS type, and
  • the assignment appears as a subexpression (rather than as a statement).
Owner

FroMage commented Jan 3, 2013

Sure.

Member

RossTate commented Jan 3, 2013

Lazy actually typically means memoized. A while back I had proposed that
{...} be memoized so that the singleton case could be used for (memoized)
lazy arguments. You could do the same here:

Lazy foo = {compute()};

foo.eval

On Thursday, January 3, 2013, Stéphane Épardaud wrote:

Sure.


Reply to this email directly or view it on GitHubhttps://github.com/ceylon/ceylon-spec/issues/438#issuecomment-11841604.

Owner

gavinking commented Jan 5, 2013

I have made that change to the type of x=y, allowing the following idiom:

variable Integer? lazyVal:=null;
shared Integer val => lazyVal else (lazyVal=computeVal());

Now reducing issue priority and shifting out of Ceylon 1.0. We can re-discuss this stuff for a future version.

Owner

gavinking commented Jan 5, 2013

P.S. the backend needs to be fixed to support this change to the semantics of a nested assignment operator.

Owner

quintesse commented Apr 10, 2015

I see this has been suggested for 1.5 but I've already encountered several times wanting something like this. Maybe we could move it a bit forward to 1.3? It doesn't seem too hard too implement but can still quite useful.

Of course Gavin's 2-liner is not too bad, but nothing can beat:

shared cached Integer val => computeVal();

xkr47 commented Oct 30, 2015

I also vote for shared cached Integer val => computeVal();

kelvio commented Oct 30, 2015

Why not "shared lazy Integer val => computeVal();"?
Em 30/10/2015 9:22 AM, "Jonas Berlin" notifications@github.com escreveu:

I also vote for shared cached Integer val => computeVal();


Reply to this email directly or view it on GitHub
#438 (comment).

xkr47 commented Nov 2, 2015

@kelvio Ok clarification; It is especially the syntax that I am happy with. Regarding the word to use for enabling it should of course make it clear that computeVal() will only be called once. Can't say which is better, you decide..

kelvio commented Nov 2, 2015

Got it.
I think cached really makes it clear that "computeVal()" will only be
called once, however,
lazy makes it clear that this attribute is lazily initialized ...
I'm not quite sure what the best choice.

2015-11-02 10:24 GMT-02:00 Jonas Berlin notifications@github.com:

@kelvio https://github.com/kelvio Ok clarification; It is especially
the syntax that I am happy with. Regarding the word to use for enabling it
should of course make it clear that computeVal() will only be called
once. Can't say which is better, you decide..


Reply to this email directly or view it on GitHub
#438 (comment).

Atenciosamente, Kelvio Matias Santos Silva.

Owner

gavinking commented Nov 2, 2015

I like once, like in fantom.

Owner

quintesse commented Nov 2, 2015

+1 on once

Member

bjansen commented Nov 2, 2015

But "once" is an adverb, and you decided to use adjectives for annotations, as stated in the faq:

The word "override" is a verb, and doesn't read well when combined with other annotations. Annotations read best together when they're all adjectives.

cached makes me think that somehow the cached value could be evicted and recomputed again, whereas lazy clearly shows that the value will be computed once, the first time it's accessed. I'd use "lazy" for initialization and "cached" for data storage.

xkr47 commented Nov 3, 2015

Somehow I feel that => already implies that the value is calculated "on request" i.e. lazily, so in some sense I think all attributes using this construct are in some sense "lazy". The difference here is that the value is only calculated ... once.. But after all, doesn't "how the calculation is done" categorize the modification we want to apply to the statement, in which case an adverb would seem appropriate? Of course one could adjectivize "once" to some weirdo oncecalculated - to which I'd have to say no thanks :)

Member

luolong commented Nov 4, 2015

maybe we should borrow from the functional languages terminology and call it memoized?

Member

luolong commented Nov 4, 2015

so the example would read like this:

shared memoized Integer val => computeVal();

ncorai commented Nov 6, 2015

+1 on once

lazy is too ambiguous and memoized too obscure for laypeople like me.

While using a verb in a Ceylon annotation seems a big no-no, I think people would accomodate the use of an adverb.

gdejohn commented Nov 7, 2015

👍 for memoized.

I vote for cached.

@CeylonMigrationBot CeylonMigrationBot referenced this issue in eclipse/ceylon Nov 14, 2015

Closed

Attribute lazy initialization #3544

I kind of disagree with cached. It implies a cache, and cache implies cache size, eviction policies, etc... too complicated.
once seems confusing for me. It looks like the sentence "doing something once a situation occurs", that is completely misleading.
memoized have my vote. It is an adjective, and expresses the exact behavior.
Also, memoizedfits good for methods, and seeing an immutable attribute as just a getter, everything fits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment