Semantics of PORT!s vs. Streams vs. Iterators

I've complained often about PORT! seeming to try and serve two masters... it tries to act as something of an OBJECT!, but also a stream.

Here is the definition of system.standard.port...the template object from which PORT!s are created:

system.standard.port: make object! [
    spec: '
    scheme: '
    actor: '
    awake: ~unset~
    state: '
    data: '
    locals: '

    connections: '  ; currently used only by TCP ports
]

That's an OBJECT!, but the underlying datatype is switched to PORT! when the port is created.

How Do You Interact With This Object as an Object?

The "What is a Port" document says this:

Specific action functions can be applied to a port. Some common actions are:

  • make - create a new port
  • open - initialize the port
  • close - finalize the port
  • read - read data from port
  • write - write data to port
  • query - get other information from port
  • update - detect external changes to the port

But, there are many other actions as well, as generally defined by Rebol datatypes.

What else qualifies as these "many other actions?"

  • Rebol2's ODBC suggests PICK (FIRST), INSERT, and COPY as choices.

  • Source code for File Port in R3-Alpha shows APPEND, DELETE, RENAME, MODIFY, OPEN?, LENGTH?, HEAD, TAIL, NEXT, BACK, SKIP, HEAD?, TAIL?, PAST?, CLEAR.

This is awfully saturated, and it seems nothing is off the table for what this abstract idea of a PORT! might want to react to.

So how do you get at these object fields safely? How do you PICK the SPEC field? How do you POKE the AWAKE function?

More Problems: PORT and 1:1 with STREAM Correspondence

When you're working with a TCP connection, you supposedly think of that as a bidirectional PORT!. You don't open a connection for your input, and one for your output--you read and write to the same port.

If so, why is there system.ports.input and system.ports.output instead of system.ports.stdio?

One technical good reason there is that there are actually two stdio output streams: the stdout and the stderr for error messages. Often these are directed to the same place, but not always.

So here you see that maybe something like stdio would like to be an object aggregating three data streams: 1 for input and two for output.

Difference Between Ports and User Defined Datatypes?

It doesn't really seem like anything is off the table for what you can override on a PORT!.

Can you define what it means to ADD to a PORT!? If not, why not?

What about path selection on a PORT! (or today's field access via TUPLE!, another option).

Once you get into this "anything goes" attitude you are essentially talking about an implementation for user-defined datatypes.

This points out an important aspect of the articulation of any design: You need to be able to say what it isn't, or there's no meaningful definition of what it is.

Streaming/Iterating Is Narrower And Needs a Protocol

While PORT! is slippery, I'm going to be attacking just the more basic questions of streaming and iteration.

We want to be able to say parse some-100-mb-file ["FOO" <stop>] and not have to read 100 megabytes just to know if it started with "FOO"

And we want to be able to do that with something like FOR-EACH as well...

 for-each [x y z] some-100-mb-file [
     all [x = #f, y = #o, z = #o] then [break]
 ]

That shouldn't need to have all 100 megabytes in memory. And a generic solution to this which puts iteration in the mix should allow for streams to be piped and connected to each other to do filtering, encryption/decryption, compression, etc...

So I'm going to focus on the narrower question of how to do that, vs. the muddle of "What is a PORT!", at this time. Wishful thinking isn't going to solve that problem, but rational adaptation of the methods used by other languages that do this might.

3 Likes

I never really liked ports to begin with. I'd more likely create my own object to encapsulate the port and use my own functions to interact with it.

2 Likes

I'll add one extra perspective around the semantics of ports from a user pov, at least for Rebol 2.

Way back when, I got interested in ports because I thought Rebol interpreters were going to talk dialects to each other via ports. I was eventually disappointed. In my only documented port scheme do-pop, I thought a port was a better option than an object because it could cleanup after itself. Nevertheless, I side-stepped the clunky port interface and the cognitive burden of trying to translate the semantic action I wanted into port actions - they don't fit well anyway. Instead I used the port as an bind and evaluator of simple function calls, where the "evaluation" happened remotely. I don't know that it was a great idea, but it was interesting to me because it was like a "relative expression" was being evaluated over the wire transparently.

Wise.

4 Likes