That was my initial impression, then I began asking questions about how to write more complex stuff, and began thinking about how it would look. I'm also thinking of trying to write an introductory level document that should take someone who has no background in this stuff to the point where they could think about writing a complex application. (The direction I'm heading right now is a proof of concept asynchronous webserver that uses connection pooling to control how many database connections it uses.)
In that process I realized that if you indent normally, then after any complex sequence of events you are indented off of the right hand side! And what, exactly, does that indentation tell you? Basically that this happens before that happens before the other thing. Nesting of braces is carrying sequencing information, which we normally don't bother indenting at all.
So once you get past the mechanics of what it is doing under the hood and try to think in terms of this library, what you really need to do is imagine that someone added a very small vaguely Lisp-like language to Perl, and that language is used to achieve the asynchronous magic. And once you think of it that way, the indentation makes perfect sense. You indent all of your Perl in a block by 4. Then outdent all of the commands in this second language by 2 (to indicate that they are this other language). Then let your closing braces pile up. In short at this point you're formatting the Perl bits like Perl, and the IO::Lambda bits like they were Lisp. (And once I figured that out, I understood as I never have before why Lisp people universally format their code that way.) | [reply] |
In that process I realized that if you indent normally, then after any complex sequence of events you are indented off of the right hand side! And what, exactly, does that indentation tell you? Basically that this happens before that happens before the other thing. Nesting of braces is carrying sequencing information, which we normally don't bother indenting at all.
Well, rjbs got me converted to 2-space indents, so indentation is less of a beast to me. ;-)
On the serious side, I think as long as this is used for a linear execution sequence, what you say is true. And I think all the examples are pretty linear. But if you ever do multiple predicates at the same level, I would suspect that lack of indentation would make following the execution sequence a bit challenging.
My point is not that it can't be understood, but that one has to actually learn the library first and grok how it works before the code is "skimmable". That's an adoption barrier and probably makes maintenance in a larger team setting problematic.
-xdg
Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.
| [reply] |
Well if you ever do multiple predicates at the same level, you'd have to make it visually clear that you're doing something weird. Certainly the indentation would change. I don't know how though, because I haven't thought through any such examples.
As for difficulty in trying to grok how the library works, my goal it to make that much, much easier than it is now. If I succeed then it should be a fairly short learning curve to come up to speed on the library.
That said, I think there are some features that will turn out to be important that aren't there yet. One is that I'd like to have a "stack context" that you can keep adding to. If a problem happens, it would be nice for debugging purposes to figure out how this sequence of execution got *here*, but right now there isn't a stack backtrace that I know of. I'd like to see better facilities for exception handling, but that is likely to be hard to do. And I'm sure that there are some spit and polish issues that will become apparent once people try to use it.
But I'm interested enough in what's there now to look at it in more depth.
| [reply] |
| [reply] |
++! And I think it would be a very good idea to include reasoning about indentation into the introductory document.
| [reply] |
Well, if indentation makes the learning curve too steep, I have no problems with reformatting code in docs and examples
with the standard indentation, but shall explain the benefits of the {{-style. I agree.
About the choice between on_write, writable, and write, it gets not that simple. While I agree that on_write makes a reader instantly recognize that the coderef is an event handler, it's not that obvious with the higher-level conditions. For example, let's take four conditions declared in IO::Lambda::Socket: connect(), accept(), send(), and recv(). One thing is that they clash the with CORE:: names, which I think is good, at least for the three latter names, because either one uses blocking CORE::send, or non-blocking IO::Lambda::Socket::send. Also, connect() is different by semantics from accept(), send(), and recv(): connect() doesn't do anything, it's basically a wrapped writable(), and is the only one that can be renamed to either on_connect() or connected(), without losing its meaning. However, consider
send() for example. It waits for a handle to become writable, then sends whatever data provided, and returns the CORE::send() return value.
What I'm getting to, is that the imperative names actually have their niche, they, like in declarative programming, actually order to do something. Now I'm getting into shaky ground, because I don't have that command of the english language that allows me to make statements like the following, but please tell me if there is a sense in that or not. Names like on_write, on_execute, on_ready, they, as I understand, are appropriate when a programmer did some setup
and then awaits for an event. This is true for writable and readable, because all the setup is done outside of these conditions. It's not true though for send() and accept(), they themselves do the setup, and it seems to me that there's no place for word other than an imperative to describe their functions.
Let's take for example POE:
POE::Wheel::ListenAccept->new(
Handle => $socket,
AcceptEvent => ...,
)
where AcceptEvent is semantically separated from ListenAccept. In IO::Lambda::Socket::accept, it's not.
So, I was thinking then and also thinking now. on_ and when_ (and for that, Event postfix in POE) have one great property, they unify the event names. Again, my english at best was to counter that with names in imperative modes (is it called modes or moods?), that also are expected to unify
conditions. But conditions are not events, while some do look more like events, like writable and readable, the majority of the others do not.
Finally, there are names that I think are very fitting, f.ex. tail and tails. Would that be better to have them changed into on_lambda_done and on_all_lambdas_done? That's
too far I think.
I'd like then to ask you, and everyone too, to help me find that grammatical or semantic unifying principle, or at least a division line between imperative and non-imperative conditions, that could be unambiguously declared and easily recognized. These features, I agree with you, are important both for learning and extensibility.
And here's the list of the existing conditions:
dns
flock
process
forked
http_request
message
snmpget
signal
pid
spawn
connect
accept
recv
send
rxw
readable
writable
timeout
tail
tails
tailo
any_tail
Out of these signal(), connect(), pid(), rxw(), readable(), writable(), and timeout() are non-imperative. | [reply] [d/l] |