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

Attribute lazy initialization #438

Open
matejonnet opened this issue Oct 18, 2012 · 31 comments
Open

Attribute lazy initialization #438

matejonnet opened this issue Oct 18, 2012 · 31 comments

Comments

@matejonnet
Copy link
Member

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;
}
@matejonnet
Copy link
Member Author

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.

@RossTate
Copy link
Member

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.

@luolong
Copy link
Member

luolong commented Oct 19, 2012

+1 for this syntax:

Foo foo ?= createFoo();

@gavinking
Copy link
Member

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?

@gavinking
Copy link
Member

@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().

@FroMage
Copy link
Member

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.

@quintesse
Copy link
Member

once ?

@gavinking
Copy link
Member

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.

@FroMage
Copy link
Member

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.

@gavinking
Copy link
Member

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).

@FroMage
Copy link
Member

FroMage commented Jan 3, 2013

Sure.

@RossTate
Copy link
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//issues/438#issuecomment-11841604.

@gavinking
Copy link
Member

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.

@gavinking
Copy link
Member

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

@quintesse
Copy link
Member

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
Copy link

xkr47 commented Oct 30, 2015

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

@kelvio
Copy link

kelvio commented Oct 30, 2015

Why not "shared lazy Integer val => computeVal();"?
Em 30/10/2015 9:22 AM, "Jonas Berlin" [email protected] escreveu:

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


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

@xkr47
Copy link

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
Copy link

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 [email protected]:

@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.

@gavinking
Copy link
Member

I like once, like in fantom.

@quintesse
Copy link
Member

+1 on once

@bjansen
Copy link
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
Copy link

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 :)

@luolong
Copy link
Member

luolong commented Nov 4, 2015

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

@luolong
Copy link
Member

luolong commented Nov 4, 2015

so the example would read like this:

shared memoized Integer val => computeVal();

@ncorai
Copy link

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
Copy link

gdejohn commented Nov 7, 2015

👍 for memoized.

@lukedegruchy
Copy link

I vote for cached.

@someth2say
Copy link

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.

@CPColin
Copy link

CPColin commented Dec 21, 2018

Just wished for this again, so bumping to add my support for memoized.

@gavinking
Copy link
Member

Folks this issue migrated to eclipse-archived/ceylon#3544.

@ceylon ceylon locked and limited conversation to collaborators Jan 13, 2019
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