Dialecting Power: DECORATION OF & DECORATE

It's somewhat fundamental in Rebol that people have been driven by the types of things to do dialecting.

But it has always been a little hard when you want to transfer properties that are sort of "meta-type". For instance: how would you do this in Rebol2?

rebol2>> map-each item [word pa/th] [<??? what code goes here ???>]
== [:word :pa/th]
  • You can turn a WORD! into a GET-WORD! with to get-word!

  • You can turn a PATH! into a GET-PATH! with to get-path!

  • ...but how do you turn something that might be a WORD! or a PATH! into a GET-WORD! or a GET-PATH! as appropriate?

:thinking:

Ren-C's More Uniform Substrate...

I've reimagined the ideas of what used to be things like GET-WORD! or GET-PATH! into a generalized concept of CHAIN!s, which can have a SPACE in their first slot.

This ushered in new composition possibilities without bringing in a wave of new types. :(a) isn't a GET-GROUP!, it's just a CHAIN! with a space in its first slot, and a GROUP! in the second. And COMPOSE got smart about these things:

>> word: 'foo

>> compose ':(word)
== :foo

This would appear to give us the answer to our question about how to turn something that's either a WORD! or a TUPLE! into a version with a leading colon (for reasons of hierarchy, you can't put PATHs in CHAINs, but under the evaluator logic of what paths and chains are for you wouldn't want to, you'd put a tuple in...)

>> map-each item [word tu.p.le] [compose ':(item)]
== [:word :tu.p.le]

That particular case looks pretty good. But we have a little bit of a puzzle now with things that used to be easy that became hard.

For example, let's talk about the change of things like /foo from being REFINEMENT! to being a PATH!. You used to be able to TO WORD! on a REFINEMENT! or vice-versa, but under the new rules you can't... so how to do this:

>> map-each item [@word :tu.p.le] [<??? what code goes here ???>]
== [/word /tu.p.le]

This gets a little sticky. Of course you have all the machinery in your hand to write it (just as you could have always written a Rebol2 to-getlike that would accept either a WORD! or PATH! and make a GET-WORD! or GET-TUPLE!). But that's kind of unsatisfying...we want something easy.

Meet the DECORATION Functions!

Decoration is a concept that subsumes:

  • Quoting
  • Sigils
  • Leading-Space Sequences

You can ask a value for its decoration, and if it has any of the above you get a composite from that. For instance, if you ask for the decoration of a CHAIN! with a space in its first position, you'll get the special WORD! of : as a proxy representative of the chain interstitial:

>> d: decoration of the :tu.p.le
== :

Then you can take that decoration, and apply it to something else:

>> decorate d [a b c]
== :[a b c]

And you can remove decorations as well:

>> undecorate the :tu.p.le
== tu.p.le

It generalizes to where quoting is considered a decoration too:

>> decoration of the 'foo
== '

>> decoration of the ''bar
== ''

So are Sigils.

>> decoration of the $tie
== $

>> decoration of the ^meta
== ^

>> decoration of the @pin
== @

(Those are quoted and sigil'd SPACE respectively...these kinds of cases are what motivate why the underscore disappears when you decorate a SPACE rune.)

So this means you can generalize a dialect which wants to be parameterized--e.g. by letting you pass in what decoration it wants to use--and not be concerned about whether that decoration is implemented by quoting, or sigils, or a sequence with its interstitial in the leading position due to a space in the first slot.

You can even grab composite decorations and manipulate them easily:

>> d: decoration of the ''@/foobar
== ''@/

>> d: noquote d
== @/

>> pinned? d
== \~okay~\  ; antiform

>> d: unpin d
== /

>> meta d
== ^/

>> decorate (meta d) [x y z] 
== ^/[x y z]

There's handling for when decorating doesn't merely create a 2-element sequence, but when the decoration indicates the same type as what its decorating so the sequence just gets longer...

>> decoration of the .member
== .

>> decorate '. the member.submember
== .member.submember  ; merges sequence if same type

(I'll mention that having .member act like pick . member, and using . as the "self" or "this" in objects, is working out really well...especially since when you're not in a member function you can define dot to anything you want and still use the shorthand!)

So We Have An Answer To The Puzzle

I asked above how to turn [@word :tu.p.le] into [/word /tuple].

Now we can:

>> map-each item [@word :tu.p.le] [decorate '/ undecorate item]
== [/word /tu.p.le]

And a shortened helper that does UNDECORATE followed by DECORATE is of course called REDECORATE.

>> map-each item [@word :tu.p.le] [redecorate '/ item]
== [/word /tu.p.le]

:star:

Added Bonus: Applies Decorations From A PARAMETER!

The first iteration of DECORATE was made to let you reconstitute a function spec parameter from the parameter itself. Let's say if something was a refinement:

>> append.dup
== &[parameter! :[any-number? pair!]]

Although you know that in the spec of APPEND the DUP has a colon as :DUP, here you see the decoration mechanic was used to transfer that onto the BLOCK! (because it's a parameter property, not a property of the name which is in the key of the frame).

You can do this yourself:

>> decorate 'dup append.dup
== :dup

>> append.dup.spec
== [any-number? pair!]

>> decorate append.dup append.dup.spec
== :[any-number? pair!]

See this post for applications and explanations:

https://rebol.metaeducation.com/t/how-to-get-spec-of-or-parameters-of-functions/2578

:star_struck:

1 Like

It occurs to me that it may be possible to merge this functionality into something like JOIN.

I'll point out there'd also be something called FUSE:

You really would be able to fuse blocks together:

>> fuse [a b c] [d e f]
== [a b c][d e f]

I am assuming splices would just be a splice of things to fuse on:

>> join [a b c] spread [1 two]
== [a b c]1two

:shaking_face:

The problem with FUSE sticking on decorations would be that what you'd get back wouldn't necessarily be a FUSED!

>> fuse ': [a b c]
== :[a b c]  ; a CHAIN!, not a FUSED!

>> fuse [a b c] ':
== [a b c]:  ; also a CHAIN!

So maybe this suggests that JOIN would be better in terms of being agnostic as a decorator that can stick things on the head or tail:

>> join ': [a b c]
== :[a b c] 

>> join [a b c] ':
== [a b c]:

I don't know, but still leaning toward thinking FUSED! is going to be a thing. Stay tuned.

1 Like