Last active
May 14, 2020 10:59
-
-
Save greggirwin/29836d25de0c68eaba0e6dbd268a20f5 to your computer and use it in GitHub Desktop.
INJECT func experiment. Alternative to REDUCE or COMPOSE.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Red [ | |
name: 'inject | |
file: %inject.red | |
author: "Gregg Irwin" | |
notes: { | |
Red version of Ladislav Mecir's R2 `build` func. I can't find his on | |
the net, to link to, but can post his original if desired. His point | |
was that `compose` isn't always easy to use, when parens are part of | |
block you're composing, or how your blocks are structured, whether you | |
can use `/only` with `compose`. e.g. | |
Given: | |
a: [block a] | |
Wanting: | |
[[[block a] (f: fail) | (f: none)] f] | |
Compose: | |
compose/deep [[(reduce [a]) ([(f: fail) | (f: none)])] f] | |
Build: | |
build [[only a (f: fail) | (f: none)] f] | |
The idea is great, but too often I forget about it. Part of the reason | |
was that I had my own `build` func, which did something very different. | |
So I'm reviving it for Red, and thinking about names. It may not be as | |
important in Red, as we have the `quote` function, which let's you not | |
compose a given paren. For example: | |
>> compose/deep [[(reduce [a]) (quote (f: fail)) | (quote (f: none))] f] | |
== [[[block a] (f: fail) | (f: none)] f] | |
Still not great. | |
Possible names, in order of my personal preference: | |
inject place inset embed inlay sub-in fabricate implant prefab | |
cut-in introduce incorporate combine blend meld alloy | |
compound admix fuse intermix integrate synthesize amalgamate | |
I like `inject` with the caveat that it may appear to deal with | |
Dependency Injection, or a Smalltalk-like fold func to the uninitiated. | |
`Inset` is a good word, but very close to `insert`, which is a concern. | |
Both allow blocks as their first argument, which may make mistakes less | |
obvious. The other problem with `inject` as a name is that it implies | |
more that it's changing the series in place, not producing a new one. | |
Once we have the function name, we also need the keywords. `Build` used | |
`insert` and `only` which were very clear choices for their behavior. | |
The escape mechanism was to use `only`. | |
build [only 'only only 'insert] ; == [only insert] | |
Those keywords may be common, which is what makes them clear. But it may | |
also make them hide in plain sight. Also, the expressions are evaluated. | |
Should we use words that have a clue about that? What about a base | |
keyword with an `/only` "refinement". Longer though. That rules out using | |
issue! values...or does it? | |
>> parse [#insert/only] [#insert /only] | |
== true | |
Currently, email! will keep the path part, but a new ref! type may change | |
things. | |
>> parse [@/only] [@/only] | |
== true | |
Should we have a default, but let them pass in a keyword of their own? | |
No. At least not in v1. | |
[@ @/only #-- do get /only do_ get_ /do /get __ __/only] | |
} | |
] | |
admix: function [ | |
"Builds a block, like COMPOSE, but using 'only and 'insert rather than parens." | |
input [block! paren!] | |
/local value | |
][ | |
; TBD: consider copying/deep the input and using parse `change`. | |
collect/into [ | |
parse :input [ ; use get-word! as input may be a paren! | |
any [ | |
set op ['insert | 'only] position: ( | |
set/any 'value do/next :position 'position | |
if value? 'value [ | |
either op = 'only [keep/only :value][keep :value] | |
] | |
) :position | |
| set value [block! | paren!] (keep/only admix :value) | |
| set value skip (if value? 'value [keep/only :value]) | |
] | |
] | |
] make :input length? :input ; so we return a paren if given one | |
] | |
inject: function [ | |
"Modifies a block, using 'only and 'insert keywords" | |
input [block! paren!] "(modified)" | |
;/local value | |
][ | |
parse :input rule: [ ; use get-word! as input may be a paren! | |
any [ | |
;!! Unset results appear in the output using this approach | |
change ['insert pos: any-type!] (do/next :pos 'pos) :pos | |
| change only ['only pos: any-type!] (do/next :pos 'pos) :pos | |
| ahead [block! | paren!] into rule | |
| skip | |
] | |
] | |
:input | |
] | |
inject-x: function [ | |
"Modifies a block, using 'only and 'insert keywords" | |
input [block! paren!] "(modified)" | |
/local value | |
][ | |
parse :input rule: [ ; use get-word! as input may be a paren! | |
any [ | |
;!! Unset results DO NOT appear in the output using this approach | |
set op s: ['insert | 'only] pos: ( | |
set/any 'value do/next :pos 'pos | |
pos: either value? 'value [ | |
either op = 'only [change/only/part s :value 2][change/part s :value 2] | |
][remove/part s 2] | |
) :pos | |
| ahead [block! | paren!] into rule | |
| skip | |
] | |
] | |
:input | |
] | |
e.g.: :comment | |
e.g. [ | |
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 first [([insert a (f: fail) | (f: none)] f)] | |
inject [only 'only only 'insert] | |
; paths needn't be escaped | |
inject [insert/only] ; == [insert/only] | |
; to escape a whole subblock, i.e. to prevent BUILD to modify it do | |
inject [only [insert only]] ; == [[insert only]] | |
; to escape a whole paren! i.e. to forbid BUILD to modify it do | |
inject [insert [(insert only)]] ; == [(insert only)] | |
;inject [[#only a (f: fail) | (f: none)] f] | |
;inject [[#insert a (f: fail) | (f: none)] f] | |
; | |
;inject [[#set a (f: fail) | (f: none)] f] | |
;inject [[#set-only a (f: fail) | (f: none)] f] | |
; | |
;inject [[ONLY a (f: fail) | (f: none)] f] | |
;inject [[/only a (f: fail) | (f: none)] f] | |
;inject [[INSERT a (f: fail) | (f: none)] f] | |
;inject [[/insert a (f: fail) | (f: none)] f] | |
; only> only-> only_> | |
;inject [[only> a (f: fail) | (f: none)] f] | |
;inject [[insert> a (f: fail) | (f: none)] f] | |
] | |
; Can we make sensical short keywords/markers? The thought here is that we | |
; already have `inject` as the func name, then we use `insert`, which is | |
; kind of redundant. | |
; (insert) #_ #___ #here | |
; (only) #__ #|_| #._. #only | |
; Not a real macro approach of course, just uses #keywords to look like one. | |
inject-mac: function [ | |
"Modifies a block, using #only and #insert keywords" | |
input [block! paren!] "(modified)" | |
/any "Insert unset values, rather than omitting them" | |
/local value | |
][ | |
;!! Watch all the `any` uses here. There's parse usage and also a refinement | |
;!! that means we have to use `system/words/any` in action logic. | |
parse :input rule: [ ; use get-word! as input may be a paren! | |
any [ | |
;!! Unset results DO NOT appear in the output by default. | |
;!! You have to use /any to force them in. | |
set op s: [#insert | #only] pos: ( | |
set/any 'value do/next :pos 'pos | |
; Check for `/any` refinement here, which forces an `change`. | |
; If that's not used, unset values cause a `remove`. | |
pos: either system/words/any [any value? 'value] [ | |
either op = #only [change/only/part s :value 2][change/part s :value 2] | |
][remove/part s 2] | |
) :pos | |
| ahead [block! | paren!] into rule | |
| skip | |
] | |
] | |
:input | |
] | |
e.g.: :comment | |
e.g. [ | |
a: [block a] | |
inject-mac [[#only a (f: fail) | (f: none)] f] ;== [[[block a] (f: fail) | (f: none)] f] | |
inject-mac [[#insert a (f: fail) | (f: none)] f] ;== [[block a (f: fail) | (f: none)] f] | |
inject-mac [[#insert () (f: fail) | (f: none)] f] ;== [[(f: fail) | (f: none)] f] | |
inject-mac [[#only () (f: fail) | (f: none)] f] ;== [[(f: fail) | (f: none)] f] | |
inject-mac/any [[#insert () (f: fail) | (f: none)] f] ;== [[unset block a (f: fail) | (f: none)] f] | |
inject-mac/any [[#only () (f: fail) | (f: none)] f] ;== [[unset block a (f: fail) | (f: none)] f] | |
inject-mac first [([#insert a (f: fail) | (f: none)] f)] | |
inject-mac [#only 'only #only 'insert] | |
; paths needn't be escaped | |
inject-mac [insert/only] ; == [insert/only] | |
; to escape a whole subblock, i.e. to prevent BUILD to modify it do | |
inject-mac [#only [insert only]] ; == [[insert only]] | |
; to escape a whole paren! i.e. to forbid BUILD to modify it do | |
inject-mac [#insert [(insert only)]] ; == [(insert only)] | |
;inject [[#only a (f: fail) | (f: none)] f] | |
;inject [[#insert a (f: fail) | (f: none)] f] | |
; | |
;inject [[#set a (f: fail) | (f: none)] f] | |
;inject [[#set-only a (f: fail) | (f: none)] f] | |
; | |
;inject [[ONLY a (f: fail) | (f: none)] f] | |
;inject [[/only a (f: fail) | (f: none)] f] | |
;inject [[INSERT a (f: fail) | (f: none)] f] | |
;inject [[/insert a (f: fail) | (f: none)] f] | |
; only> only-> only_> | |
;inject [[only> a (f: fail) | (f: none)] f] | |
;inject [[insert> a (f: fail) | (f: none)] f] | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
when: make op! func [value test] [either :test [:value][[]]]
A common thing when including code conditionally. Ideally it shouldn't be used in comparison as makes
compose
look better than it usually is ;) But I just copy/pasted it from real code.This case study is mostly about deeply built code, not a show of how ugly parens become within
compose
. I'll write a wiki soon, to go with the implementation (I chose 4th).Tip: click on the wheel ;)