Deepening the Lake: / as zero-length PATH!


#1

This is the behavior in Rebol2 and Red:

>> path: 'foo/bar
== foo/bar

>> path: next path
== bar

>> path: next path
==

Visually you can’t tell a length 1 path from a WORD!, which is bad. But then length 0 paths are invisible!, which is really bad.

R3-Alpha still doesn’t visually distinguish length 1 path from WORD!, but empty paths give construction syntax…possibly bringing in the whole path content, even at the end:

>> make path! []
== #[path! []]

>> path: 'foo/bar   
== foo/bar

>> path: next path
== bar

>> path: next path
== #[path! [foo bar] 3]

In some ways, that is probably even worse. :frowning:

I’ve always felt something is way off about / being a natural word. Even worse, the existence of other “words” like // (which I want for line comments). There seems no reason to encourage this line of thinking to have people considering / to be a word character at all.

I propose that SLASH ( / ) be a zero-length PATH!

Note: This post documents the original proposal, and is kept here. But see note in reply to post below about how this can actually be done without breaking division as we know it!

>> type of quote /
== path!

>> length of quote /
== 0

>> make path! 0
== /

Behavior-wise, I imagine the following as all being equivalent:

a/b/c: d
a / b / c: d
a/b / c: d
a / b/c: d

A length-0 path thus wants to behave path-like, but it is missing the things to path upon. So it would get them as operands from the left and right.

By similar logic, a length-1 path could look like foo/ and consider it to only need something on the right, giving more equivalent forms:

a/ b / c: d
a/ b/ c: d

Note: A REFINEMENT! is not a PATH!, so arguing that a /b should act as a/b is not necessarily as consistent as it sounds. I actually think refinements being inert is probably a good thing. So saying that only PATH! does pathing and acquires arguments if it needs them is the better way to say it… it’s path consistency, not slash consistency.

Mechanically, I’ve gotten it to work!

Explaining what has all happened in the evaluator to make this possible would be a long story. It’s rather interesting, and in pushing things around I’ve noticed that Ren-C can likely solve the SET-PATH! ordering issue #396.

Strangely enough, separating it out all the way is likely the more fundamentally efficient in terms of execution speed. If you compare a/b/c: d and a / b / c: d, the former has to set up a “subframe” in order to traverse a new array…while the latter can actually reuse the frame it’s already in, since the data source never changes.

…but programmers are really attached to / as division

There are languages where having numberA / numberB doesn’t perform division. None of those languages are particularly popular

So this raises the question. Why not have numbers do “pathing” by dividing? Since pathing would need to be soft quoted on the left, it would be “tight” like today’s division. It would act mostly the same, though you’d have to parenthesize expressions on the right if they weren’t constants or variables:

>> 2 * 10 / x: 5
** Error: Numeric division via PATH! cannot be assigned

>> 2 * 10 / add 1 3
** Script Error: add is missing its value1 argument

It’s something a programmer can understand if they realize slash is always just pathing, they basically did (2 * 10)/x: 5 and (2 * 10)/add 1 3.

You can work around it with 2 * 10 / (x: 5) and (2 * 10) / (add 1 3), which is backwards compatible with R3-Alpha. It doesn’t seem like the end of the world, and you get some new interesting behaviors:

foo: 2/3 * bar

Though you’d still need to remember the left-to-right rules, as bar * 2/3 would evaluated as (bar * 2) / 3. Again, not hard to remember if you have been trained from the start to think of slash acting the same way with spaces or without… if you read that as being the same as foo: 2 / 3 * bar you realize that it’s your formatting choice of whether to space it out or not; and if you mislead someone, it’s your fault.

Before explaining the drawbacks: “Why rock the boat?”

Rebol is not really about giving people a scripting language that behaves exactly how other languages have taught them to expect.

What it seeks to do is to give a kit of reusable parts that are good and solid, which when executed in one light can give an expressive scripting language. Yet those parts are ultimately intended for multiple applications.

But if you look at the handling in older Rebol of length-1 and length-0 paths, today’s PATH! is a crappy part. It doesn’t have the solidity of BLOCK! or GROUP!. It feels junky and unreliable. Making it feel like a quality part is important for reasons that have nothing to do with path picking or division.

That is why I said that DocKimbel didn’t see the forest for the trees when it came to things like allowing GROUP!s at the head of a path. He didn’t consider this behavior to be a bug:

>> y: load "a/(b c)"
== a/(b c)

>> x: load "(a b)/c"
== [(a b) /c]

In his view, groups at the head of the path were “inconvenient for the evaluator”. So if you wanted the latter to do “pathing” you should say:

 temp: (a b)
 temp/c

Yet when trying to build something that was CMake-like, there were good reasons to want to be able to put expressions at the heads of paths of things that were to represent file paths. It wasn’t going to run with DO…these were file lists, processed by the dialect.

So the goal is to make PATH! more solid and pleasing, while not losing too much when the evaluator is applying its voodoo.

Quick reminder: PATH! is already voodoo

>> foo: [a 10 1 20]

>> foo/a
== 10

>> select foo a
== 10

>> foo/1
== a

Is pathing selection, or array indexing? “It’s like, whatever you want it to be, man.” If you’d wanted to feed 1 in and get 20 out, you would need to use a SELECT.

PATH!s now support things like a/[b], so I’ve wondered if forcing select semantics could be done with something like that.

>> foo: [a 10 1 20]

>> foo/[1]
== 20

And I feel like it should probably reduce the block you give it. But it’s hard to say. The main point is that pathing is weird, and it has sort of “evolved” to meet usage needs while still sharing much of the code and behavior of BLOCK! and GROUP!.

So what breaks with the divide?

If SET-WORD! at the end of a chain of / indicates the behavior of a SET-PATH!, I’ve already shown that 2 * 10 / x: 5 would have to be 2 * 10 / (x: 5). Which is not too bad because you get an error and it’s easy to work around, in the scheme of Rebol things.

But things like TIME! present a challenge to the duality of division and picking for /. Consider today’s behavior:

>> t: now/time
== 23:11:01

>> hour: 2
== 2

>> t/hour
== 23

>> t / hour
== 11:35:30.5

You can divide a time by a variable called hour. And you can also pick the hour component out of a time. To a unified pathing strategy, these both just look like a WORD! coming in on the right…and it can’t do both.

One might argue that hour of t looks pretty good, and so you could use that for reading the properties, saving / for division. But that doesn’t let you change a variable’s time field, e.g. t/hour: 4. The questions reach out to other things like DATE! even though they don’t have division available, because one wouldn’t think you’d access the month of a date with a different syntax to the hour of a time.

If time keeps today’s behavior, there’s an error message to guide you to use divide. You can only pick the words “hour”, “minute”, and “second” out of it. Anything else and t / foo will tell you that t has no foo field, which guides you to DIVIDE. If you want a quantity on the left you now have the option of SHOVE:

 begin: now
 loop 10 [print "timing..."]
 half-time: (now - begin) -> divide 2

Is it a dealbreaker?

I see letting / act like division ever as a compromise. It already was a compromise in the sense of making it a WORD!..you couldn’t say /: or :/ or '/. Now you’ll be able to say all those things… empty SET-PATH!, empty GET-PATH!, empty LIT-PATH!.

Basically, Rebol is already weird. If it gets slightly weirder while PATH!s get substantially more solid and flexible, that is probably a good trade.

I actually would say that with UTF-8, we should probably enfix divide as ÷ and encourage people to make that their keyboard binding for Ctrl-/. Then make Ctrl-8 be × and be done with it.

For people who find putting keyboard bindings in their editor too oppressive, we can enfix divide as dv. (div is generally integer division) Rebol could even come with a “mathify” utility which processes a source file that used DV during development, and turns dv => ÷.

My point being that math is great, but Rebol’s PATH! story needs to be the main story. The parts have to be solid, not flaky.


The Coming of User-Defined Types
#2

Turns out we can have our PATH! and divide it too!

I’ve found a compromise that gives 100% legacy compatibility with how / worked when it was a tight enfix divide…while still letting it be a 0-length path.

And it’s fairly obvious in retrospect: Tell the path dispatcher if there’s a gap or not. Also, if there’s a gap then use tight normal evaluation vs. quoting when providing the path picker.

What we lose from that is the ability to say a / b: c to act like the SET-PATH! assignment a/b: c. But that isn’t anything people were asking to be able to do. Also, to the extent it would have been able to work, it would have been pretty limited… a / b: default c could not have acted like a/b: default c, because DEFAULT isn’t part of path dispatch–it would left quote and see only the b:.

So a / b: c goes back to being a synonym for a / (b: c) as it has been in historical Rebol. Division as you know it is now safe… and PATH!s still get cleaned up. win-win.