-
Notifications
You must be signed in to change notification settings - Fork 7
Less expensive lazy seq forcing #2
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
Conversation
Hi, (instance? clojure.lang.IPending
(first (doall (for [x [1]]
(for [x [0]]
x)))))
=> true Again, let me emphasize that I'm very glad to see a pull request and hope to see more. |
You mean essentially that it doesn't force recursively? That can be fixed... |
On reflection, this doesn't cover all the cases, back to the drawing board again... |
My first thought was to use walk, but I have seen it fail in scenarios where it should not. The dream would be to have an eagerize function that could be called multiple times on the same structure and only traverse it once. |
I'll have a think and a play and see what I come up with. |
We'll have to figure out some smart way of testing it |
Not sure, but would this
Other thought: @clojureman d'you remember in which case postwalk didn't do well? |
postwalk won't work if a lazy thing is contained by a java object that clojure doesn't recognise as being a sequence of some kind. |
right... so the code I posted above will probably not work either? |
No, in fact I don't think it's generally solveable. The str hack isn't even guaranteed to work, it's just significantly more likely to. If you were to put an IPending in a java object whose tostring didn't actually print that value, it would not be realised. |
Allright, so to summarize:
If both don't solve the wrapped Other questions:
|
More or less. The current fix is probabilistic. It will work so long as you use only objects which happen to do the right thing under stringification (which is most clojure data!) Personally I don't think we should care so much about laziness, because laziness is the devil and there are already so many circumstances under which it can go wrong that what's one more? Answers:
|
Right... agreed. |
That depends upon the user. I wasn't prepared to make that call for someone else's project :) |
Right. So would you consider the 'eagerization'-function as something to be more pluggable (choosing between Anyway, as a reminder for myself (and maybe others may be interested as well), this is how one can reproduce it (not checking it with
|
It's not my call, it's not my repository. The only thing approaching a solution I came up with was to have a protocol or multimethod and default to pr-str if we don't have a better option, but if it fails, it will fail silently, which rather defeats the purpose. My ultimate conclusion is that laziness in clojure is mostly a bad idea and you should default to eagerness in the general case. |
Right. Makes sense to me. So, instead of having an efficient |
The library has already made that choice for you. If you don't agree with it, either fork it or use one of the alternatives that make a different choice. As it happens, I no longer agree with it as a decision, so it's probably time to close this issue. |
tnx, both of you for the comments. I could change the function manage-with-courage from private to public if that would help you. |
Hey... we're all convinced about the eagerize-step. The question is more how to do it and how to handle the 'impossible to capture' lazy seqs (see my example above). When it comes to making it pluggable, here are 2 ways I can think of:
It all together would look like this (for the case of passing an eagerize-fn, similar for using multimethods):
Also, I'd add some stuff to the docs, about the cases it won't work, and examples of eagerize-functions with postwalk etc. |
If manage-with-courage is made public, everyone is free to use it to make
their own eagerized version, and the advantage over polymorphism is that
you can also choose to use it as is, if you know you're not in a situation
where laziness will break or you already have an eager collection.
…On Sun, Jul 23, 2017, 11:21 kurtosys ***@***.***> wrote:
Hey... we're all convinced about the eagerize-step. The question is more
how to do it and how to handle the 'impossible to capture' lazy seqs (see
my example above). When it comes to making it pluggable, here are 2 ways I
can think of:
1. make it a multimethod, so one could provide an own implementation.
Add a (first?) parameter to manage to tell what method should be used.
2. pass in a eager-function directly into manage.
It all together would look like this (for the case of passing an
eagerize-fn, similar for using multimethods):
(defn- eagerize
... ;;default implementation
)
(defn manage-dispatch
"Takes an eagerize-function, a function f and an \"inlined\" map of conditions and keywords.
Returns a function in which these conditions are managed.
The returned function can safely be called from another thread
than the one in which it was created.
f is allowed to be lazy, but the result must be finite, as it will
always be fully realized. In other words: manage returns an eager function."
[eager-fn f & restarts]
(apply manage-with-courage (eager-fn f) restarts))
;; use a default the manage, don't break compatability
(def manage (partial manage-dispatch eagerize))
Also, I'd add some stuff to the docs, about the cases it won't work, and
examples of eagerize-functions with postwalk etc.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#2 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAktxEVzVJTr6g64YBP4DfrpEua61y2qks5sQ48bgaJpZM4MhYt3>
.
|
If a function is public, well... making your own version would mean redefining it, right? I don't see the point of this - or, well, please provide me an example of how this makes more sense than using function passing or multimethods. I don't think it's necessary to make an own version of In the code I propose, you still have the default, or you can choose to add another eagerization. |
I think @didibus' point was that It's a practical choice. There is nothing to stop you from implementing eagerize on top of it. Personally, I don't care for eagerisation because I think laziness in clojure is a terrible idea. |
OK... I know there's nothing to stop me from forking, or implementing eagerize on top of a function that's called somewhere else in a library. It just makes the library less useful. tbh, I think you do care about eagerisation, 'cause that's why you forked and implemented your own version. It's pretty clear that the discussion is about eagerisation and nothing else, actually. To avoid a dozen of forks of the same library, it makes more sense to me to let people choose how to eagerize, if that's the variable part (which it is). So, I still would prefer to have this library being flexible enough to have a custom eager function - that's really not hard to implement, and would allow for everyone who likes so to avoid It can be documented how the default eager-function works, and why this default is chosen. It can also be documented what alternatives there may be (or some alternatives might even be embedded in the library, again, documenting the trade-offs). I agree laziness can be a terrible idea in many cases, and I don't expect a lot of issues with it either (especially laziness embedded in an object). This has been discussed already: if one avoids laziness, one would prefer an efficient eager-function, not |
I forked it because it looked interesting and the Might I suggest a fresh issue? |
Oh, I'm not dissapointed... I fully agree with you! I have no stake in its future either. I just think it's a bit weird if people start to fork because about 1 or 2 lines of code are well... up to the user of the lib to decide. |
Tnx I'll make a decision within a week and release a new version |
Doesn't it work to just capture the binding in a closure to solve the lazy issue? Like this:
Just a thought. |
Nevermind, this works in simpler cases. The lexical scope won't reach far enough for condition to see it I believe. I still vote for manage-with-courage to be made public. I would change its name to I'm ok with manage also being modified to be configurable about its eagerization scheme though, both are not mutually exclusive. In practice, I don't think that lazyness is an issue. In fact, forced eagerization can be surprising to someone knowingly being lazy. Even Clojure's try/catch can not handle lazyness in that way. If someone wants to restart lazy conditions, he should call manage from inside the lazy construct. |
Agreed on changing the name of |
Actually a ! (bang) suggests mutability, so maybe it would confuse some people. |
:) @clojureman would you go for multimethods or function passing for custom eagerization? (My personal preference is the multimethod route, since it's looks easier to me to embed some different strategies - see #8 ) |
See #8 (comment) for my concern about a global setting for customisation. |
see #8 (comment) - where does the idea of a global setting comes from? I guess nobody wants that. Looks pretty terrible to me. |
The clojure style guide says:
Lazyness is not safe inside an STM transaction for the same reason it isn't in special. I've also seen ! used more often to simply mean "be careful with this function". The use of it for purity is not consistent. That's just my two cents. I'll be okay with any name. |
@didibus Good point! My first thought was to use |
Additionally this change should also force delays.