http://www.perlmonks.org?node_id=736510


in reply to IO::Lambda: call for participation

To summarize my general reaction to several of the other threads, I think that the particular code layout you use -- breaking the usual expectations of indentation for nested code blocks -- is getting in the way of the "elegance" you're trying to offer.

It may seem elegant to you, but because it's very different, you're making the learning curve a lot steeper than it needs to be.

I think the same is true of syntax. I think the move to 'readable' and 'writeable' and so on is a step in the right direction, but those names don't naturally communicate that they set a one-time event callback. I know you were looking to move away from 'on_' or 'when_' prefixes, but I really wonder if a more expressive name would make it easier for people to learn.

For many people in a work context, I suspect that maintainability will be an important consideration. So making IO::Lambda more expressive and easier to learn may seem less 'elegant' but ultimately may make it more useful to others.

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

Replies are listed 'Best First'.
Re^2: IO::Lambda: call for participation
by tilly (Archbishop) on Jan 16, 2009 at 05:49 UTC
    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.)

      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.

        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.

        I agree with your arguments that this is an adoption barrier. However it would be pity to abandon that style only because it is a barrier. Perhaps, introductory documents and examples indeed shall feature only the usual indentation, while formatting of all other code would be up to the programmers involved.

        On the side note, even though having multiple predicates is perfectly viable, it is very rarely needed.

      ++! And I think it would be a very good idea to include reasoning about indentation into the introductory document.
Re^2: IO::Lambda: call for participation
by dk (Chaplain) on Jan 15, 2009 at 15:47 UTC
    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.