in reply to IO::Lambda: call for participation

I stared at IO::Lambda, and it made my head hurt.

I'm pretty good at event loops, and POE, and even coderefs and closures. But I'm just not getting it from your docs and examples... too many things are magical. I think it's because you're referencing currying and monads and other stuff that I never totally understood.

I'm saying this because I want to understand, but can't. I'm looking for something that's easier and deeper than POE. But I still think I'd have to stare at this for a long time before I could even start.

Maybe if you remove most of the "just like X in Y" references in the docs, and make the docs more standalone. Maybe I could just go through the docs and point out every external reference I don't know about. I realize it must seem tedious to describe something that you already know from somewhere else, and you think everyone else must already know, but we don't all have your experience.

Replies are listed 'Best First'.
Re^2: IO::Lambda: call for participation
by dk (Chaplain) on Jan 05, 2009 at 09:32 UTC
    Oh. Thanks a lot for the response! What's most disheartening, is that even for a person with your experience, the doc is overly complicated, and I of course too can relate to reading docs that are totally incomprehensible. What I can say in my defence, is that I absolutely didn't want to make the doc so that it appears aloof and to pose as if I have huge experience in functional programming, which I don't. What it shows rather, that I'm a lousy documentation writer, if even my best intentions turn into such a result.

    In the long run, I know what to do. Learn to write better, write more, all the usual advices for the aspiring writers. However, that'd be really sorry NOT to rewrite this doc and leave it in the current state when it is hard to understand what it is about. I'll try to rephrase and remove "X like Y", thank you for the advice. And I'll definitely try to rewrite hard sections into simpler ones, but here's a question to you, as a writer: where is the acceptable level of simplification? It won't possibly be a good idea to explain functional programming tricks to make the reader fully appreciate the tricks IO::Lambda does. However, without referring to the concepts from the functional programming, a single paragraph can easily explode into twenty. Possibly that's not a bad thing though.

    Also, I don't really understand what you mean by "make the docs more standalone". Is your idea to split the large document into smaller pods for readability?

    Finally, thank you for using time on bringing up problems with the docs. But I shall need to ask you and everyone interested, I need your help with docs too. I didn't realize that until now, but apparently the documentation is the biggest showstopper. Would anyone like to write, or help me write a gentler introduction to the module? I'll help all I can.

      Likewise, after puzzling my way through the documentation, I'm intrigued, but see a steep learning curve before I'd understand how I'd use this in practice. I don't think this is unique to IO::Lambda -- POE has a similar, perhaps even steeper learning curve. It wasn't until I struggled through POE::Kernel and POE::Session and similar documentation that I really understood what was going on.

      Some reactions:

      In general, I think you assume too much knowledge of functional programming and use too much unfamilar jargon (e.g. "predicates") without clearly explaining each one. The whole "apologetics" section should be moved to the end as it distracts from explaining how to use the module.

      The synopsis itself is too complex or else insufficiently commented to explain what is going on.

      Many of your examples might be clearer if you were explicit about return and fixed up some indentation. E.g. edited from the synopsis:

      # return an IO::Lambda object for a given $host sub http { my $host = shift; my $socket = IO::Socket::INET-> new( PeerAddr => $host, PeerPort => 80 ); return lambda { context $socket; # argument stack for other calls # register a callback for when $socket is writable write { print $socket "GET /index.html HTTP/1.0\r\n\r\n"; my $buf = ''; # register a callback for when $socket is readable read { return $buf unless sysread( $socket, $buf, 1024, length($buf)); # restart the "read" state if more is available again; } } } }

      There are too many ways of doing things described too early and without context (no pun intended). For example:

      A lambda is initially called with some arguments passed from the outside. These arguments can be stored using the call method; wait and tail also issue call internally, thus replacing any previous data stored by call. Inside the lambda these arguments are available as @_.

      The part in bold is extraneous and distracting, as it forces the reader to ponder the relationship between different ways of calling a lambda -- it exposes implementation details that are irrelevant to initial understanding. Moreover, I think you mean that the arguments are made available as @_ in the callback attached to the lambda object. "Inside the lambda" is a bit vague.

      Overall, I think you might need to explain more of the core principles for async programming and why they are necessary. Likewise, I think you need to explain subtleties in your examples, like why the "read" predicate is inside the "write" predicate. (Presumably, we don't want to start listening until we've actually sent the request via the write predicate.)

      Then I think you need to walk through what happens in an example. So the "http" lambda is executed via "wait" -- a "write" state callback is registered. (Is it executed immediately? Does it execute after the lambda callback executes?) The "write" state is the only one registered and it's immediately valid (socket is writeable) so the "write" callback is executed. During that callback, a "read" state callback is registered. When the "write" callback finishes, the socket is readable so the "read" callback is executed one or more time. And so on. (Why does execution stop?)

      So my general advice is to start very small, very simple and explicit and then build up from there.

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

        Thank you really so much, cannot stress enough, this is by far the most valuable response I've got with regards to the factual problems in the docs. I assume there are more problems of this sort, but I hope I should be able to figure them out myself, given guidelines I extract from your (and of course not only your) advices.

        What I've realized, is that starting small and going baby steps was something that I unintentionally avoided, as I thought that explaining obvious things would be an offense to the reader. Also, no one (until now) told me that it's not bad at all. Thus, you also partially answered the question I asked Randall, where is the generally acceptable simplification level I can begin from.

        Thank you again. Your analysis is valuable to me.

      This might be just me - but I stumbled at 'context'. It is introduced in the examples - but the explanation is way further and even reading that explanation I cannot fully internalize what it is.
        In short, context is an alternative stack. Contrary to values on the normal, everyday perl stack, that come and go as subroutines are being called, context is retained within a lambda. Predicates use context instead of the perl stack to receive their parameters. In the following code

        context $a, $b, $c; predicate1 { predicate2 { }}
        Both predicate1 and predicate2 use values $a, $b, $c from the context, even though predicate2 is called after code that called predicate1 is finished. That code is identical to the following:

        context $a, $b, $c; predicate1 { context $a, $b, $c; predicate2 { }}

        From the architectural point of view, context is syntactic sugar. The same effects can be achieved without using it, but at the price of more complex and/or ugly code.

Re^2: IO::Lambda: call for participation
by zby (Vicar) on Jan 04, 2009 at 17:47 UTC
    I concur. Reading that documentation was a humbling experience :)

    Many of those operations there are a higher order functions - perhaps listing their signatures (types) would help people in understanding what they do?

      Thanks! Would that be too much to ask you to help me with the docs, by telling what exactly felt hard? The signatures are actually included for all higher-level functions, but apparently either not visible or not explained enough.
Continuation-Passing Style
by awwaiid (Friar) on Jan 07, 2009 at 16:11 UTC

    The key words that are missing from all of this, including the documentation, are "Continuation-passing style". As far as I can tell, that is exactly what IO::Lambda provides helpers for.

    See this Wikipedia Article on Continuation-Passing Style

    dk, I suggestion you mention this in the documentation. Folks who are familiar with the concept should grok it immediately, and folks who aren't have a nice phrase to google.

      You're absolutely right, IO::Lambda is and and was all about CP-style from the very beginning. However, I remember I was reluctant to mention monads because they strangely spook people, so I thought, if I'd tell about CPS that would be even worse :) Still, I agree, for those who know about CPS, the analogy will be immediately clear. Thank you for the advice, I'll try to include it so that it won't scare off newcomers but will still be visible.

      btw, adding links to wikipedia inside a pod might be just the right thing to to.