Closed CeylonMigrationBot closed 7 years ago
[@matejonnet] 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] 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] +1 for this syntax:
Foo foo ?= createFoo();
[@gavinking] 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] @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] 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] once
?
[@gavinking] 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] 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]
Fair enough, but changing the type of := means making it more expensive when compiled to Java.
Only when:
[@FroMage] Sure.
[@RossTate] 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.eval
On Thursday, January 3, 2013, Stéphane Épardaud wrote:
Sure.
— Reply to this email directly or view it on GitHub<#3544#issuecomment-11841604>.
[@gavinking] 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] P.S. the backend needs to be fixed to support this change to the semantics of a nested assignment operator.
[@quintesse] 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] I also vote for shared cached Integer val => computeVal();
[@kelvio] 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 <#3544#issuecomment-152499456>.
[@xkr47] @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] 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 <#3544#issuecomment-153002100>.
Atenciosamente, Kelvio Matias Santos Silva.
[@gavinking] I like once
, like in fantom.
[@quintesse] +1 on once
[@bjansen] 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] 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] maybe we should borrow from the functional languages terminology and call it memoized
?
[@luolong] so the example would read like this:
shared memoized Integer val => computeVal();
[@ncorai]
+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] :+1: for memoized
.
[@lukedegruchy] I vote for cached.
+1 for memoized
If someone unfamiliar with the concept encountered memoized
in a piece of Ceylon code, they'd probably google it and the first result would be Memoization. It might be considered a Term of Art, as I've only seen it used in relation to computer science.
@fwgreen I think I prefer lazy
, also like cached
, would be fine with memoized
, but really dislike once
.
Could we have this in the next version? It doesn't look like a big change to implement, once we all agree on a name.
@bjansen I don’t really like this feature that much. It would be convenient, but it isn’t something we can’t already do really easily.
Besides the name, there is another issue: =
vs =>
. When you start to think about this, you start to notice how this will break the nice regularity of the language that says =
is eager and =>
is lazy, by adding an in‐between.
but it isn’t something we can’t already do really easily.
Thread safe initialization (if we go that route) would be something not currently possible without native("jvm")
.
cached value a => func();
would still be lazy, but its initial result would be reused in subsequent calls.
It would be convenient
That's the whole point of this feature :)
I propose the following syntax:
late Float pi = calculatePi();
Where I'm repurposing the late
annotation to mean "initialized lazily" instead of just "initialized later". There's no ambiguity here, since currently a late
field can't have an initializer, and the compiler enforces that.
Reasons why I think this is reasonable:
+1
Even better +1
Would the type checker produce an error if you tried to assign to a specified late value? e.g.
shared late Float pi = calculatePi();
shared void run() {
pi = 22.0/7; // error here?
}
I really dislike this idea. It seems to me like the late
annotation is being used here for two completely different things simply because “it isn’t ambiguous”.
Either way, I don’t think we can have this feature without drastically changing the meaning of either =
or =>
, that’s why I really dislike it. If you guys introduced a new symbol for “memoized assignment” (maybe ~>
) instead of an annotation I wouldn’t mind. But even then it might be a bad idea, because:
Again, it’s not particularly hard to overcome the inexistence of this feature:
variable Integer? lazyVal = null;
shared Integer val => lazyVal else (lazyVal = computeVal());
By the way, if this was something I needed all the time, I wouldn’t mind a little bit of irregularity. But this is something I’ve never actually needed.
Would the type checker produce an error if you tried to assign to a specified late value?
Well, that's something I was wondering about, and I'm not sure, but I guess I think it should be an error, at least for shared
, non-variable
fields. But yeah, that's something that needs thinking-through.
Btw, this use of late
we'll be able to use everywhere, right? I mean, even locally and such.
late
fields can already be variable
(and non-variable
ones can only be assigned once), so I’d say disallowing that for non-variable
late
fields would be consistent.
Another question:
T printIt<T>(T t) { print(t); return t; }
late Integer i = printIt(1);
shared void run() {
i = 2;
value j = i;
}
Does this print something? That is, if the value is assigned before ever being read, is the lazy initializer evaluated?
Btw, this use of late we'll be able to use everywhere, right? I mean, even locally and such
+1 on being able have memoized initialization for locals
@Zambonifofex
I really dislike this idea. It seems to me like the
late
annotation is being used here for two completely different things simply because “it isn’t ambiguous”.
It might "seem" like that to you, but if you expect to be able to convince anybody else that these are two "completely different" things, then you need to present us with an argument for how and why they are semantically distinct and exhibit very different behaviors.
I've already presented a sketch of an argument for how the behavior is very semantically similar. How is my argument incorrect? What differences am I glossing over?
late
fields can already bevariable
(and non-variable
ones can only be assigned once), so I’d say disallowing that for non-variable
late
fields would be consistent.
Probably, yes.
Btw, this use of
late
we'll be able to use everywhere, right? I mean, even locally and such.
I guess. But we might not need that immediately.
@Zambonifofex By the way, to make my argument more explicitly:
The only difference between the two scenarios for late value foo
is that in one scenario, some code has to explicitly assign foo = something
, whereas in the other, = something
is assigned implicitly when needed. This difference is reflected in the existence or nonexistence of an initializer in the declaration.
Otherwise, there is no difference in semantics AFAICT.
it seems to match the English meaning of "late"
Just because it makes sense in English doesn’t mean it makes sense in Ceylon.
the actual code generation is pretty similar: instead of throwing an exception, we initialize it to the given expression
~
Otherwise, there is no difference in semantics AFAICT.
What do you mean by there is no difference
? late
, in its former meaning, is used to tell the typechecker to shut up about the initialization of the value, allowing you to initialize (or to not initialize) the value whenever you want. This is completely different from what memoized
would do.
I guess you are trying to say that late
without the assignment would implicitly be equivalent of this pseudo‐Ceylon:
late String foo = assert(false);
But that’s not correct. As you guys have been discussing, late
allows you to assign things to it whenever you want, memoized
would not. That’s a very different change in semantics in my opinion. I honestly cannot see what you think is similar about these.
it seems to me that issue #6721 applies equally to lazy fields as to late fields (they can both be placed in the declaration section)
Yeah, the can both appear in the declaration section, sure, but for completely different reasons. memoized
values can because they are lazy. late
values can because they are not assigned yet, and don’t run any code. Supposedly, non‐late values that are assigned later also can appear in the declaration section:
class Foo()
{
shared void foo() => ...;
String bar;
shared void baz() => ...;
// end of declaration section
bar = "hello";
}
Sorry, I forgot to at-mention you, @gavinking.
[@matejonnet] Looking for a shorter way to initialize an object attribute on a first use, and ability to use immutable attribute instead of variable
I would like to write this as:
[Migrated from ceylon/ceylon-spec#438]