Skip to content

Instantly share code, notes, and snippets.

@greggirwin
Last active May 14, 2020 10:59
Show Gist options
  • Save greggirwin/29836d25de0c68eaba0e6dbd268a20f5 to your computer and use it in GitHub Desktop.
Save greggirwin/29836d25de0c68eaba0e6dbd268a20f5 to your computer and use it in GitHub Desktop.
INJECT func experiment. Alternative to REDUCE or COMPOSE.
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]
]
@greggirwin
Copy link
Author

I deeply appreciate the time put into it. 👍

@greggirwin
Copy link
Author

Reading through, without context, the various indirections, and the original use case for the function itself, make it hard to visualize. That is, where are parens being passed through to the result? Having not looked at build/with beyond a glance when Bolek posted, I have to get my head in that space as well. Also have to look up your when func it seems.

@hiiamboris
Copy link

hiiamboris commented May 14, 2020

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.

That is, where are parens being passed through to the result?

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).

Horz scrolling in the browser makes comparison painful.

Tip: click on the wheel ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment