Arc Forumnew | comments | leaders | submitlogin
1 point by Pauan 5116 days ago | link | parent

By the way, one reason I'm liking the (stdin 'peek) style rather than (peekc stdin) is that it makes it easy to extend both the built-in ports (stdin, stdout, and stderr) and also makes it easy to extend user-created functions:

  (extend stdin (x) (is x 'line)
    (readline stdin))

  (extend stdin (x) (is x 'byte)
    (coerce (stdin) 'byte))
Though extending readline and readb would be comparable, I think both approaches have merits, in different situations.


1 point by rocketnia 5116 days ago | link

Interesting, you're favoring (stdin 'peek) specifically because you want it to be extensible, whereas I just finished noting that it wouldn't be as extensible as I'd like. XD

You might be right that the approaches have pros and cons. However, I'm not sure what the (stdin 'peek) approach has in its favor. Here's a scenario which is kinda in favor of (peekc stdin):

Suppose someone wanted to introduce a new kind of read unit, like 'xml-node. Technically, they could extend 'stdin and redefine 'instring, and that would probably be enough most of the time. However, I think it would be even nicer to be able to read XML nodes from any stream that supports character reading. If the XML pull parser could work like that, then anyone could integrate it with their own favorite miscellaneous stream libraries, with far less glue code--specifically, without redefining everything in the stream library to support 'xml-node. However, to read XML nodes they have to involve the XML library somehow, and a (my-stream 'xml-node) interface doesn't work.

Actually, there's at least one way to force (my-stream 'xml-node) to work in this case: Somehow establish that every time a stream is called, if it has nothing better to do, it refers to a global framework and looks for a behavior there. This could just be established by convention, and a macro could make it extra convenient for people to create streams that default to the framework.

-----

1 point by Pauan 5116 days ago | link

Why not use a wrapper?

  (xml-read stdin)       -> reads XML nodes from stdin
  (xml-read some-stream) -> reads XML nodes from some-stream
The differences between (readc stdin) and (stdin 'char) are small. One can be easily defined in terms of the other, so it's mostly a matter of what you want to extend.

Let's suppose you want to add a shiny new input type. Since input/output are just functions, this is easy enough to do:

  (def my-stdin ()
     ...)
But now, let's say you wanted to have this function behave differently. There are two ways to do this: call the function with different parameters, or call a function that wraps around it, like readc.

I prefer the former because it avoids having a ton of tiny specialized functions like readc, peekc, etc. In other words, it's the idea of "where is this functionality encapsulated?" The functional approach is, "hey, let's make a function, that can then do stuff to different stuff" ala peekc, map, each, etc.

That's fine, and it's very extensible and generic in most circumstances. But in this particular case, reading or peeking a character are more specific. They only really make sense for streams, so it makes sense to encapsulate that functionality within streams, rather than making it a generic built-in function. Hence (stdin 'char) rather than (readc stdin).

This also makes it easier to special-case behavior for a particular stream. Consider the case where you want to change the behavior of stdin, but not change the behavior of other input streams. Piece of cake:

  (extend stdin (x) (is x 'char)
    ...)
But if you try to extend `readc`, then now you end up with stuff like this:

  (extend readc (x) (is x stdin)
    ...)
...but that won't work if somebody overwrites stdin. So now you need to store it in a let:

  (let old stdin
    (extend readc (x) (is x old)
      ...))
Basically, by passing arguments to the streams, I make it easier to extend an individual stream, but harder to extend all streams. Of course, I could still keep `readc` around, and have it defined to simply call (foo 'char), in which case we could get the best of both worlds: extending individual streams and extending globally (assuming code uses readc, rather than calling streams directly).

Oh, it also lets streams dynamically alter their behavior, without needing to extend global functions, and it makes it easier to define custom behavior for a stream, once again without needing to create more global functions, etc. etc.

I don't think it's a huge enough deal to fight over, though. As I said, the differences between the two approaches are small, and I could settle for either one. My gut says that passing arguments to streams to change their behavior is better, though. I could be wrong.

-----