Ladislav's BUILD Dialect (COMPOSE Alternative)

Note: there is no "official" copy of Ladislav's circa-2006 dialect on the web to point to, but Rebolek had a copy. Mirroring it here too:

https://gist.github.com/rebolek/edb7ba63bbaddde099cb3b1fd95c2d2c


Rebol [
    Title: "Build"
    Author: ["Ladislav Mecir" "Brian Hawley"]
    File: %build.r
    Date: 2-May-2006/13:36:38+2:00
    History: [
        7/apr/2003/19:02 {using INSERT and ONLY keywords}
        30/Oct/2004/12:55 {intermediate version - alpha}
        31/Oct/2004/9:49 {intermediate - word - insert/only, :word - insert}
        4/Nov/2004/6:55 {word - insert and evaluate, :word - insert/only}
        4/Nov/2004/15:00 {/with - refinement name}
        2-May-2006/10:51:09+2:00 {
			lit-path preservation,
			'ENCL and 'DTTO made local
			examples added
		}
		2-May-2006/13:08:57+2:00 {one more lit-path related change by Brian}
		2-May-2006/13:36:38+2:00 {example correction by Brian}
    ]
    Purpose: {Build a block comfortably}
]

comment [
    ; Usage
    ; This dialect looks very human-readable, doesn't it?
    ; (I found a lot of possible usages for it).
    build/with [x (y) [z] :u v] [
        x: 1 y: 1 + 1 z: 1 + 2 u: [contents of u] v: [contents of v]
    ] ; == [1 (2) [3] [contents of u] contents of v]
    ; You can easily "stack" it like:
    build/with [let's see what] build/with [what: [the rise of the (y)]] [
        y: 'sun
    ] ; == [let's see the rise of the (sun)]
    ; or use the evaluation feature like:
    build [ins 2 + 2] ; == [4]
    build [only reduce [3 * 4 5 + 6]] ; == [[12 11]]
    ; you can even define and use your own functions the same way
	; using the /WITH refinement
]

use [encl dtto spc] [
	; patch the IN function if needed
    spc: find/only third :in reduce [word!]
    if spc [change spc/1 any-word!]

	encl: func [
	    {"a value in a block" function}
	    value [any-type!]
	] [head insert/only copy [] get/any 'value]

	dtto: func [
	    {a "pass the value over" function}
	    value [any-type!]
	] [return get/any 'value]

	build: func [
	    {Build a block using given values}
	    block [block! paren! path! lit-path! set-path!]
	    /with
	    values [block!]
	    /local context inner
	] [
	    values: any [values [only: :encl ins: :dtto]]
	    context: make object! values
	    inner: func [block /local item item' pos result] [
	        result: make :block length? :block
	        parse :block [
	            any [
	                pos: set item word! (
	                    either all [item': in context item item <> 'self] [
	                        change pos item'
	                        set/any [item pos] do/next pos
	                        insert tail :result get/any 'item
	                    ] [insert tail :result item pos: next pos]
	                ) :pos | set item get-word! (
	                    either all [item': in context item item <> 'self] [
	                        insert/only tail :result get/any item'
	                    ] [insert tail :result item]
	                ) | set item [
	                    block! | paren! | path! | set-path! | lit-path!
	                ] (
	                    insert/only tail :result inner :item
	                ) | set item skip (insert/only tail :result get/any 'item)
	            ]
	        ]
	        :result
	    ]
	    inner :block
	]
]

So this is an alternative to COMPOSE, that's sort of hybridized with REWORD.

As with REWORD, you can give it a list of variables (or functions) you want to use in substitution. But like COMPOSE you can put "instructions" in the template to run code or process the substitutions.

Well, okay. I don't see any reason why REWORD shouldn't work on blocks, and Ren-C can substitute splices:

>> reword:deep [x (y) [z] u v] {
      x: 1
      y: 1 + 1
      z: 1 + 2
      u: [contents of u]
      v: spread [contents of v]
  }
== [1 (2) [3] [contents of u] contents of v]

That's cool I guess. But for the uses I tend to have, I much prefer to have some indication on the template where substitutions will occur.

Also: I can't think of all that many cases where I'd have a separate list of substitutions like this (as opposed to values already in variables). Writing up that list seems like make-work when things are usually in variables already.

Although I will point out that using {FENCE!} is now slated to let you import variables and their values into an object by @name, which would give you a little more usability with this pattern:

>> x: 1
>> y: 1 + 1
; show Z being defined inline
>> u: [contents of u]
>> v: spread [contents of v]

>> reword:deep [x (y) [z] u v] {@x @y, z: 1 + 2, @u @v}
== [1 (2) [3] [contents of u] contents of v]

So maybe that would make me more likely to use it. Not sure. :man_shrugging:

I wouldn't think of there being "instructions" that REWORD executes in the data. If you have instructions you want to put in the template itself, you manage that with a separate COMPOSE step.

Anyway... if you aren't using REWORD, Ren-C's COMPOSE can still actually do this anyway. Though you have to point out what you want composed, and that's 4 words and 1 list (leaving only two lists uncomposed). 5/7 things carrying a marking.

How it can work: is that it has a trick up its sleeve: using decorations in order to work across different substitution types ($ can decorate a WORD!, a GROUP!, a BLOCK!, etc). If you COMPOSE:DEEP and use something like a Sigil or Quote to call out the places you want composition, it works quite well:

>> compose:deep:$ [$x ($y) [$z] $u $(spread v)]
== [1 (2) [3] [contents of u] contents of v]

(Note that it doesn't matter if you said $(spread v) or $[spread v] or ${spread v}... they're all equivalent, so use whichever makes sense...but GROUP! is probably the least jarring unless you have a reason to avoid GROUP! due to something particular about the rest of the code.)

If you are composing something with lots of $ in it, you have other choices:

>> compose:deep:^ [^x (^y) [^z] ^u ^(spread v) $undisturbed]
== [1 (2) [3] [contents of u] contents of v $undisturbed]

>> compose:deep:@ [@x (@y) [@z] @u @(spread v) $undisturbed]
== [1 (2) [3] [contents of u] contents of v $undisturbed]

>> compose:deep:~ [~x~ (~y~) [~z~] ~u~ ~(spread v)~ $undisturbed]
== [1 (2) [3] [contents of u] contents of v $undisturbed]

Any number of quotes is legal too, or quote-Sigil combos:

>> compose:deep:' ['x ('y) ['z] 'u '(spread v) $undisturbed]
== [1 (2) [3] [contents of u] contents of v $undisturbed]

>> compose:deep:'' [''x (''y) [''z] ''u ''(spread v) $undisturbed 'undisturbed]
== [1 (2) [3] [contents of u] contents of v $undisturbed 'undisturbed]

>> compose:deep:'$ ['$x ('$y) ['$z] '$u '$(spread v) $undisturbed 'undisturbed]
== [1 (2) [3] [contents of u] contents of v $undisturbed 'undisturbed]

So mechanically, there's always going to be a way to do it. Though this is certainly a pathological case, more suited to REWORD.

(I'll remind people that patterns are historically just lists or lists with an item in them, like compose:{} or compose:(<*>), and COMPOSE still defaults to plain old parentheses in both lists and strings. But these newfangled Sigil/Quote/Quasi things do pack a punch with their flexibility, and are starting to seem like the go-to choice for many scenarios.)

I don't really understand the point here. As I said above, the most interesting "multi-phase" concept is probably if you want to split work between REWORD and COMPOSE.

But again... for the things I have historically done, it's much clearer to my eyes if you decorate the places you're doing substitutions. This example isn't motivating for me.

Today's COMPOSE doesn't do this. But I guess... it could. Why not? What else would a standalone decoration mean, besides do an eval step inline?

>> compose:$ [$ 2 + 2]
== 4

>> compose:' [' 2 + 2]
== 4

Okay, that's an interesting idea. I like it. :+1:

With SPREAD you can splice or not, as you wish. As-is, is of course, the correct default:

>> compose:$ [$ reduce [3 * 4 5 + 6]]
== [[12 11]]

>> compose:$ [$ spread reduce [3 * 4 5 + 6]]
== [12 11]

COMPOSE uses the current binding context (not a list of substitutions like REWORD). So in the Ren-C world, you just declare things in scope and it will see them, even if the block itself is unbound material (this is why you can COMPOSE inside strings, even though they have no binding):

let x: ...
let y: ...
compose '[blah blah $x $y blah blah]  ; quoted block won't be bound

If you wanted to create or use an environment specific for the COMPOSE... e.g. some object that you don't want in your local scope for whatever reason...there could be a refinement to pass it as an argument:

compose:with '[blah blah $x $y blah blah] {x: ... y: ...}

But I'm not sure that's the right way to do it, having every such function carry a refinement like that. I've written up some about how that "context switching" might be done in a more general way.

Glad I Looked Because Of The Lone Sigil Idea

I'll add that. It's neat to be able to point out expressions you want to evaluate without using any container, as just an inline eval step mid-COMPOSE.

And I'll make a good-faith effort to try considering "would this COMPOSE be better done with REWORD", now that the seed is planted. I'll keep an open mind about it. But as evidence that I probably won't use it much: I do all my string interpolation in COMPOSE also, and don't touch REWORD for strings.

1 Like

In terms of COMPOSE with fewer decorations...

I've thought before about COMPOSE:NOT ("Compose... NOT!")

That is to say: apply evaluation to everything except what you decorate.

>> x: 10
>> y: 20

>> compose:not:' [x 'y (x + y) '(x + y) y 'x]
== [10 y 30 (x + y) 20 x]

I've actually needed that before, but my case was more like the following...where you need two patterns...a main pattern and an exception:

>> compose:():not:(<*>) [(1 + 2) a (<*> 3 + 4) b (5 + 6)]
== [3 a (3 + 4) b 11]

So that's saying that () still is what you compose, but not if it is (<*>).

But is there any applicability to something like the example?

compose:deep:$ [$x ($y) [$z] $u $(spread v)]

The problem is that you want to keep parts of the structure, and not decorate, but the only rule here is that you want to compose things that are words.

So I don't think my COMPOSE:NOT would help avoid the decorations. If you want REWORD, you have to do this like REWORD.

I'll keep COMPOSE:NOT in the back of my mind, but don't think I'm quite comfortable enough to implement it yet.

I'll briefly mention that Gregg references Ladislav's BUILD when presenting his own builder dialect, that he calls INJECT:

https://gist.github.com/greggirwin/29836d25de0c68eaba0e6dbd268a20f5

>> a: [block a]

>> inject [[only a (f: fail) | (f: none)] f]
== [[[block a] (f: fail) | (f: none)] f]

>> inject [[insert a (f: fail) | (f: none)] f]
== [[block a (f: fail) | (f: none)] f]

>> inject [insert/only]  ; paths need not be escaped
== [insert/only]

>> inject [only [insert only]]  ; to escape a whole subblock
== [[insert only]]

>> inject [insert [(insert only)]]  ; to escape a whole paren! 
== [(insert only)]

The less said about this, the better, but...

This makes me furious.

I'm not furious that it was made. It was posted in May 2020 (and prior to being ported to Red, originated far earlier than that...Ladislav's BUILD script is dated 20 years ago). The first inklings of isotopes didn't happen until November 2020. SPLICE! antiforms weren't a done deal until the full death of /ONLY in July 2022.

So nothing wrong with brainstorming about INJECT in 2020, or even 2022. That's generally fine (albeit there's a real lack of "something is fundamentally wrong, here" awareness--the kind of awareness that simmered and percolated among Ren-C thinkers to birth the isotopic model.)

What makes me furious about it is that the answers in Ren-C have been established art now for more than three years.

And yet somehow Red developers...here--in 2025--can somehow still think epicycles of this trash are a better answer, and keep going that route.

It's willful ignorance. Pure and simple.

1 Like

All other things being equal, Red devs are probably there for the native x-platform GUI.

I'll mention that I'm thinking that MAP! would be handled differently in REWORD, as it can have non-WORD! keys:

>> reword [#foo baz bar:] {{ #foo 10 bar: 20 }}  ; proposed MAP! construction
== [10 baz 20]

So if MAP! does arbitrary equality matches, and OBJECT! looks specifically for WORD!s, is there any interesting unique role for the meaning of BLOCK!?

The... REWORD dialect? What would such a thing be?

One deficiency of the MAP! and OBJECT! based specifications is that there's no way to specify subtleties or pattern matches. What's this?

>> reword [a a: @a] {a: 1020}

Does it narrowly sub in just the 1020 for the plain a?

== [1020 a: @a]

Or are the decorated ones included?

== [1020 1020: @1020]

I'm presuming the MAP! form would only do exact matches and replace them in their entirety

>> reword [a a: @a] {{ a: 1020 }}
== [a 1020 @a]

Where a BLOCK! specification might come in handy would be to lay out more subtle rules and instructions. I don't know what that would look like, but it could look like parse rules, and if the rule matched it would do a substitution. Unsure.

>> reword:deep [a: [b: 10] [c: 20] a:b:c:d] [
        [subparse chain! [word! _]] <replaced>
   ]
== [<replaced> [<replaced> 10] [<replaced> 20] a:b:c:d]

Just making something up there to get a thought process going.

Another place BLOCK! might go well could be using numbered substitutions, which seems like convenient default:

>> reword "Foo $1 Bar $2" [1000 + 20, 300 + 4]
== "Foo 1020 Bar 304"

But... dialects are powerful.

I dunno. I kind of like the "you get what you pay for" idea... if your specification is nuanced and mean, you should have the burden of more decorations or refinements... you're the weirdo :ogre:

So this seems like the best use of BLOCK! as the reword spec.