Squaring the circle of LENGTH? and LENGTH-OF

The stated goal of Ren-C was experimentation, to try things that may-or-may-not be good ideas... push them around and get experience with them, and possibly revert ideas that turn out to be bad (or create new ideas).

There a long and drawn-out description on the Trello card for getting rid of the name LENGTH?. This is one of the operations for which people were most unhappy with calling it LENGTH-OF. So even though LENGTH is very "nouny" (and the name of several refinement parameters storing lengths, e.g. /PART LENGTH), it was defaulted to a synonym of LENGTH-OF.

But...aesthetics matter. For instance: I am turned off by the look of foo == bar and its deceptive supposed complement foo !== bar (vs. foo != bar which is actually the complement of foo = bar...good luck with that, C programmers). That gives rise to musings on = and IS.

So it should not be surprising that I have suffered a fair amount of cognitive dissonance on things like LENGTH?/LENGTH-OF/LENGTH. We may be used to HEAD and TAIL but not LENGTH, kind of how we're used to FOREACH but not MAPEACH (which doesn't make FORECH good, nor HEAD or TAIL). Still, correcting it pains me, to look at what might be called a kind of -OF pollution, which is throwing extra words in places they do not belong.

I was writing some example code and had this:

foo: func [n /accum a] [
    case [
       not accum [a: 0 | recurse: true]
       n = 0 [return a]
    ]
    frame: context-of 'n
    n: n - 1
    a: a + 2
    redo frame
]

And while I found myself liking the elegance of it on the whole, I kind of couldn't get past how in Red, this would say frame: context? 'n and it would look better. The -OF feels like a "wart".

But the Trello card on LENGTH-OF lays out the facts. e.g. HEAD means give the head of a series, and HEAD? means ask for a LOGIC! of if the series is at its head. So if you say context? system that seems like a yes or no question ("is system a context?") and hence context? 42 should be false.

This got me to wondering if there was some thinking-outside-the-box option which might be a symbolic modifier of the word. context* foo, context& foo, anything that wasn't taken already that might look better than -OF (those don't, really). Backed into a corner I thought of this:

  • Let -> be a system-wide naming convention for GET
  • Let <- be a system-wide naming convention for SET

So if you want a shorthand for GET-LENGTH, it's length->. If you want a shorthand for SET-LENGTH, it's <-length.

GET operations tend to be arity-1, and SET operations are arity-2. I'm imagining that with the arrow-notation, the SETs are enfixed and put the thing to set on the left... but evaluated, not quoted.

>> foo: "gets overwritten"
>> word: 'foo
>> word <- 10
>> print foo
== 10

>> foo: 20
>> word: 'foo
>> -> word
== 20

I propose the <- operation be non-tight, so:

>> words: [foo baz bar]
>> first words <- 30
>> print foo
== 30 ;-- words/1 was set to 30, not `words: 30`

What this paints is a picture of a world where you can write property-> as a way of saying GET-PROPERTY. While it may seem that <-property lines up with GET-PROPERTY better, the leftward "flow" of information of <- seems to make it a better SET than a GET. So that means property-> reads something like property/get.

How does it look in practice? I feel it's an improvement:

foo: func [n /accum a] [
    case [
       not accum [a: 0 | recurse: true]
       n = 0 [return a]
    ]
    frame: context-> 'n
    n: n - 1
    a: a + 2
    redo frame
]

This would then free up context? foo as a synonym for what we today call any-context? foo.

It may appear to compete with TAG!, but there are several thoughts on this matter. One thought is my going theory that <- would not be a valid start of TAG!, nor -> a valid ending, so you wouldn't have to worry about confusion. Another thought from @rgchris is that being prescriptive about symmetry is silly, as 4chan is not a natural but chan4 is, and so <!-- foo bar --> can be a tag even if --> is a standalone WORD! when there's no start tag in effect.

In any case, I guess the point is that I am feeling unsettled with both the Ren-C "-OF warts" and Red's continuation of the non-LOGIC! convention for ?. I don't like either, so I wonder if this idea can lead us out of both traps. And we can reclaim HEAD and TAIL as nouns, e.g. head: head-> s and tail: tail-> s


Note: Previous applications of -> have been experimented of with things like meaning a lambda generator for functions, e.g. f: x -> [print x] being like f: func [x] [print x]. However, there's other ways of writing that, such as f: x >> [print x] ... or even f: x => [print x] (there's some hope of perhaps reclaiming <= and => as arrows, if people don't see them as comparison operators)

(Note: We can see that as with today's GET and SET, the -> and <- operators offer something that GET-WORD! and SET-WORD! do not...to evaluate the word to be set. I might even argue that they obsolete today's GET and SET, to the point that we might imagine those being blankifying functions that soft-quote their first arg.)

>> unset 'foo
>> get foo
== _
>> get (first [foo bar])
== _

>> set foo 10
>> foo
== 10
>> set foo ()
** Error because SET requires a value, unlike <- or foo:

(But for now, put that radical suggestion aside, and assume redundancy of function between SET and <- and GET and -> is fine.)

Just being out of the box - perhaps the "-" is the "wart"?

frame: context of 'n

and

head: head of s

I admit that the "of" looks superfluous in the second example compared with prior experience, but not if one wants "head" as a variable.

Arrows for me are very dramatic aesthetically implying action. This clearly has a set like appearance:

word <- 10

but I find this on first appearance to be confusing:

-> word

though over time one could come to accept it as a dereferencing operator.

Perhaps the problem with it will be when it is used as part of a larger expression:

multiply 3 -> word

but again maybe it is not an issue once familiarity is gained.

My example of:

head: head of s

is also meant to point to the dual meanings of the word "head", each meaning valid for different contextual notions. Mixing the same symbols from different contextual notions in the same expression is not something that has been craked in Rebol yet, imo. I'm still hanging out for the "relative expression" promis to be fulfilled :slight_smile:

3 Likes

Great minds think alike! I was actually thinking about using -> to do a pick out of a context, like head: head -> s would be interpreted as head: getters/head s or somesuch.

But that was steamrolling ahead with my use of the arrow. I was just frustrated looking at things like context$ and length~ and similar, and wanting to break out of that. The arrow seemed like the least offensive of the symbolic brainstorms I was doing, with some history of PUT, and so maybe GET too.

Yet this spin on OF is certainly better looking--and not as far flung! I like the way you put it, that it's - that's the wart.

Mechanically we now have soft quoting on left enfix, so you could do:

x: (pick [length context] i) of 'n

It is "weird", but this is actually how "actions" work (like APPEND), they're really symbol dispatches on the word. And since we both had the same style of idea in the same 12-hour period when looking at the same question, it may not be completely bonkers.

I'll point out that with specialization and OneFunction, if you were in a situation where you needed a single arity function you can always make one:

takes-single-arity: func [f [function!]] [
    print (f "test")
]
takes-single-arity (specialize 'of [property: 'length])

So if you ever needed LENGTH-OF you can make it.

I say we keep thinking on this, because as I say, I'm just not happy with LENGTH? or LENGTH or LENGTH-OF...and already I feel more compelled to put up with some level of weirdness about this kind of strategy vs. going with any of those.

One question to ask: most enfixed operators have prefix forms. (X + Y is ADD X Y, X ELSE Y is EITHER-TEST-VALUE X Y). Does OF have a prefix name? PROPERTY-OF 'LENGTH STR?

1 Like

It didn't occur to me at first, but this concept already existed in R3-Alpha's REFLECT. It had the same characteristic of taking a symbol and then doing a type-based dispatch to get it.

(Even though LENGTH and HEAD were "actions" and not "reflectors", this spirit of dispatch was already how they worked. Each type had its own dispatch which received the symbol for a WORD! to react to in order to retrieve the property. REFLECT just introduced another level in the dispatch which produces a kind of "reflection namespace", so you wouldn't be taking away a global name for it. words obj must have seemed over the top, so reflect obj 'words meant WORDS itself wasn't an action, but then people wanted less typing so you got words-of.)

Not many people knew of REFLECT's existence, because they only used the specializations like SPEC-OF, BODY-OF, WORDS-OF, VALUES-OF... which were in the base definitions:

Very few properties were extracted this way, and the mechanisms were somewhat sketchy (par for the course with the sketchiness of most of the port and action code). However, it was an existing mechanic that would need to be addressed one way or another.

Thus OF is just an enfix left-quoting convenience version of REFLECT.

You are right to say it is not "solved". But pointing out that what we're asking for here is a "relative expression" effect certainly bolsters the argument that attacking the problem is well within the scope of the language mission.

I only had to use head of (or more frequently length of) for a short time before becoming sold. One has to consider the consequences of not figuring out how to do this...because the verbification of LENGTH caused a lot of devils in the details:

charset: function [
    "Makes a bitset of chars for the parse function."
    chars [string! block! binary! char! integer!]
    /length "Preallocate this many bits"
    len [integer!] "Must be > 0"
][
    ;-- CHARSET function historically has a refinement called /LENGTH, that
    ;-- is used to preallocate bits.  Yet the LENGTH? function has been
    ;-- changed to use just the word LENGTH.  We could change this to
    ;-- /CAPACITY SIZE or something similar, but keep it working for now.
    ;--
    length_CHARSET: length      ; refinement passed in
    unset 'length               ; helps avoid overlooking the ambiguity

    either length_CHARSET [append make bitset! len chars] [make bitset! chars]
]

...and those devils are still there for reused verbs like /ALL and aren't going away. But when LENGTH crossed the verbification line it seemed more drastic and hard to predict.

To try this vision, I twisted the code around and hacked it up for our common examples. It involved holding my nose a bit and digging in the bodies of code outside the evaluator. Definitely a hack...but at least a hack that piggy-backs on existing unsolved problems, without really introducing any new ones.

While jumping in with this half-baked might be more akin to the kinds of half-thought-through behaviors I usually would ascribe to ... other developers ... :stuck_out_tongue: I say we give it a chance. People can still use specializations if they like (I point out they'd already been effectively doing this with things like WORDS-OF, so it's not that mind-blowing to have TYPE-OF be a reflector specialization).

By and large, I think the enfixed OF has been a big win.

There are some questions it brings up of how you get a list of reflectors for a type. This isn't a particularly new problem, and it parallels questions like "if I pass TO an INTEGER! how do I know which types it can accept as input"...generic actions hide information. This was one of the arguments for why TO-INTEGER was useful, because of exposure of the interface. One might argue that LENGTH-OF might be a similar hack.

The question of how reflectors are extended is pretty much exactly like the question of what R3-Alpha called "actions" are extended. One problem I just noticed is that since NEXT is implemented currently as a specialization of SKIP, there's a question of how that might get wedged in as next of series. Further how might parameterizations work? How would I say [skip 3] of series, for instance? Should I want to say that?

But the elegance of length of series can't be denied, and I don't think the total count of "design puzzles to solve in Rebol" went up all that much. Certainly balanced out on how much we hated length x or length? x or length-of x. Just wanted to mention the specialization issue, because I was pondering if NEXT might someday become NEXT OF SERIES to free up the variable "next" for common use.