-
Notifications
You must be signed in to change notification settings - Fork 1
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
Syntax for once methods #41
Comments
Isn't this covered here #31? I suggested:
Issue #31 lists other options; this is my current preference
nope. That would also require every object used as an argument to a lazy thing to have (argh. Thought I wrote this earlier but it seems to have got lost?) |
Sorry, you are right, this is covered in #31. My bad. The problem with
which doesn't look so good. It also has the problem of using an annotation to change the semantics. I think that
is more realistic. (We could also make it
if we decide to quit worrying about annotations changing the semantics.) |
but this isn't so bad (thanks to Guy Stele and Niklaus Wirth):
We don't have begin in the library, but we probably should, as normal defs have the same problem.
I'd rather have a coherent, dynamic (non-rewriting based) semantics for annotations, rather than having them not change the semantics. But we don't have any "doubled-up keywords" (aka space-sensitive keywords) so if we don't like def is lazy why not go the full Eiffel:
My preferences would be **def is lazy* over once, because I'd rather describe it as a special sort of def (that is just initialised at a different time to normal defs) rather than a special kind of method that is magically run once. Hmm: I also realise there is an important semantic difference: what does return do in these initialisers? In a def it would try to return from method enclosing the def, in a *once it would presumably give the memorised value... |
You are right, it would be useful to have something equivalent to your My preference is not to use the def syntax for this, for one, two, three ... reasons. First, there is an open issue (#33) here about re-purposing def to be a manifest declaration, so making it also do once methods seems too confusing. Second, traits can't contain defs. But we do want them to contain once methods. There is also a third reason, although it's a weak one — If one wants to illustrate what a once does by re-writing. It will re-write to a method with a hidden variable or two. The fourth reason is that some time in the future, I (or my successors and assigns) might prevail upon you to allow onces to have parameters — which means implementing memoization in the compiler, and requiring that the parameters undersatnd This gives me another reason to like the syntax: method |
BCPL would be valof. I wondered about just value (but we probably don't want to steal value as something that is almost a keyword). BUT: This counts as issue-jacking. Andrew has opened issue #52 for just this point. |
more to the point, can't we do this today, and isn't it enough:
|
Yes, that's right. It's because object initialization is broken that we need once. |
or we could fix object initialisation. I don't know of any initialisation scheme for imperative languages that does what we want. |
was talking about memorization of functions with parameters. To do that for initialisation (e.g for grammars done in parser combinators) requires explicit proxies - we can't get the timing for that right easily without an explicit extra level of indirection. Memomising on a large scale, I don't mind that indirection so much. |
For non-recursive, non-compute intensive cases, I suspect that O'Caml does the right thing — computes initial values before entering the scope of the object. For the rest: yes, we can program up lazy initialization. We've been doing that since at least Smalltalk in 1980. I have to say, though, that I think that Bertrand is right: once captures a fundamental idiom. It deserves to be in the language. No, I would not teach it to novices in the first quarter. But I would certainly use it in my libraries. This post, let me remind you, is not about whether we should have it: we already agreed on that, and I haven't heard any objections. It's about what syntax to choose. |
It's not just "But with what syntax?" but also "And are they allowed to have parameters" and "are they allowed in traits". Eiffel oncers also serve the purpose of being thread local, global, or per object! (which is NOT the default!) Do we need to consider that. |
We already agreed on no parameters for the moment. We may want to revisit it at some future time, but for this round of the spec: no parameters. Yes, they are allowed in traits. Why ever not? We already agreed that "hidden" state is OK in a trait, and this is what once requires. We don't have to decide on local or global, because in Grace, both of these are per-object for some object — one place where we have a better design than Eiffel! As for thread local: yes, we will have it think about that someday. Any other red herrings? |
I'm happy with that.
I don't know. It's not obvious. We have to spec it.
It's not a red herring; it's something we have to think about given the Eiffel design. I also agree we don't need do that, we can use another mechanism if we care. But we should consider it a bit. Which we've done. Syntactically: my preferences currently are (from most preferred)
|
For your number 2: is once an alternative to method, or an alternative to begin, as in Eiffel? |
as above:
I still prefer def is lazy. I half want to stick an = in there, and drop the { } (because why add unnecessary { }s that aren't block delimiters) but again that's why I prefer 'def is lazy' --- and because I'd rather sell them as def-like than method-like I don't like once method or lazy def because we don't have those kinds of adjectival keywords in Grace. |
So the syntax is like a method, with the once keyword replacing method ? |
that's my second preference |
I could go with that. My preferences would be (in order)
so, by minimizing the root-mean-square-dis-preference, we converge on your (2) and my (3). |
On reflection, I prefer once method to once by itself, because it makes it clear that this construct is a method. We do currently have factory method, although that will soon be removed. When taking about them to a class of students, I want to say "once methods" rather than "onces", which isn't even a word. A reason to prefer once method over def ... is |
I don't think we're done with this design yet. In particular, I'm now unsure being lazy/once isn't quite enough, if you want to e.g. create cyclic structures, you need some kind of future or placeholder. The point is that we certainly need to build those kinds of structures with types: better to have one mechanism strong enough to do that too. |
We may well not be finished, but cyclic structures don't need even once methods. Here is a litttle example of infinite (cyclic) lists that runs today in minigrace:
The output is
as you would expect. We already have a way of writing types that allows cyclic structures — type declarations and type literals. We really do need types to be special, because they need to meet restrictions that ensure that they are not only computable but bounded. I agree entirely that we want minimal mechanisms. What do you see a "future" or "placeholder" doing that a once method doesn't do? |
We already have a way of writing types that allows cyclic structures — type declarations and type literals. We really do need types to be special, because they need to meet restrictions that ensure that they are not only computable but bounded. The question about types I guess is: how special? Yes, we can use methods or blocks to work around things (that's what futures and to a lesser extent placeholders do). The combinator parsers, for example does this: def primaryExpression = rule { literal | listOfOuters | implicitSelfRequest | parenExpression }
def parenExpression = rule { lParen ~ rep1sep(drop(opt(ws)) ~ expression, semicolon) ~ rParen } where that "rule" is pretty much a custom proxy that lets me build up the cyclic structure - expression links back to primaryExpression of course. Hmm: then again types have to be more method-like because they can take (generic) arguments where these things don't (so far). Which just gets to me not understanding all the issues in this space right now (except that I don't like once method because we have no other sequences of keywords like that in Grace so far). |
I'm aware of the
so PetitParser uses metaprogramming to declare an instance variable corresponding to each method, and initialize them with proxies, which get eliminated lazily. But Grace doesn't have that problem: we can request methods without self. So you could have written: method primaryExpression { literal | listOfOuters | implicitSelfRequest | parenExpression }
method parenExpression { lParen ~ rep1sep(drop(opt(ws)) ~ expression, semicolon) ~ rParen } Why didn't you? For efficiency reasons: you didn't want to build the same parser over again each time you used it. You want to build it once, the first time that it is needed, and cache it. That's exactly what once method does. What else do you need? The issue that I see with types is that, even if we define a type to be no more than a special type of object with certain methods, we probably want a type to be less than that. That's why we need a type syntax, and why we need to say that programmer's can't create types out of whole cloth (that is, out of methods and defs), but must use the type syntax. Otherwise, they could define a type where the pattern of method names that one finds as one explores the type is the same as the digits of π ... it's computable, but type-checking never terminates, because the type is represented by an infinite, non-cyclic structure. |
Umm, I think because it would loop? calling primary expression would call parenExpression would call expression would call primaryExpression? That's what happens in Kernan:
What's worse, it's not clear that making this a once-ler or lazy or whatever fixes things -- because the call chain gets back to e.g. primary or wheverelse you start before that method has finished executing, i.e. before the cache has been set (because you haven't finishing computing the value yet). And that's where placeholders or futures or proxies work, because you immediately return a proxy, and can snap the links one step at a time, when the proxies are actually used. Either using methods, once-lers or defs, each of them is immediately initialised to the proxy, and only when its is used, the proxies are snapped one link at a time... |
I’m closing this because once methods have been added, with the syntax once method. I had completely forgotten the problem with @kjx’s Combinator parser. It seems to me that the problem must lie in the definition of |
We agreed to add once methods. But with what syntax? And are they allowed to have parameters (which would imply a memo table).
The text was updated successfully, but these errors were encountered: