Open countvajhula opened 2 years ago
I'm back from my honeymoon :) hopefully I have time & energy to do some analysis of the Qi I have floating around this weekend/next week. It's too bad GH doesn't let me put a reminder here of some kind…
It looks like most of the code I need to dig through is in the public https://github.com/benknoble/advent2021, but I have a fair bit of (newer? more experienced?) Qi in a private project, too.
Preliminary gathering of code. LMK what questions you have.
mark
contains
(if _
update-board-board
(const b))
which is sort of the opposite of what I think I normally see? Perhaps this is
another case where having the previous stage produce 0 or 1 values is better,
especially if there were an way idiomatic way to rectify
in the 0 case or
apply a flow in the non-zero case.
incomplete?
grounds results if they are char?
or null?
, otherwise
passing the inputs through (written (if (or char? null?) ground _)
; I might
now write that using the current unless
behavior)part2*
as a stage of computation
(~> sep (amp incomplete?) (amp …) …)
but the two amp
s must be separate, since the first grounds some inputs,
meaning the second would have to be capable of dealing with 0 inputs (it is
not). The reason not to use pass?
is that I grounded the result rather than
returning #f
; perhaps the ?
suffix is misleading here. Additionally, it
doesn't return a "boolean," per se; rather, it returns 0 values OR a list of
characters.
traverse
contains (if (~> car (equal? "end")) _ real-work…)
part2*
has the following switch
(switch
[(~> 1> big?) #t]
[(not member) #t]
[(~> 1> (equal? "start")) #f]
[else (~> 2> has-doubled?)])
I'm remember having trouble deciding how to logic this out so I went with this
form which I thought was clearer. But the boolean literals feel a little out
of place, and the clauses don't have as much parallelism as I would expect
(since the else
clause isn't of the form [? boolean]
).
update-first
uses (if ? f _)
:
(define (update-first n)
(flow (if (~> 1> (>= n))
(~> (== (~>> (- n) abs (- n)) _))
_)))
step-weights
contains (if (> 9) 1 _)
ysoln-t
uses an idiom (if > X _)
to order pairs of numbers for input to
range
try-explode
and try-split
return (values succeeded? new-n)
. Then
reduce-once
(which returns the same shape) does
(~> try-explode
(if 1>
;; if suceeded, return both values
_
;; otherwise try the split
(~> 2> try-split)))
reduce
loops reduce-once
as long as the first value is true:
(define-flow reduce
(~> reduce-once (switch (% 1> 2>)
[_ reduce]
[else _])))
Knowing what I know now, I think I could have dropped the [else _]
here.
reld*
uses pass
to filter out identical pairs of a cartesian product;
then, the remaining pairs are transformed by an amp
flowp-for-s-or-false
does something similarp-for-s-or-false
also uses (if condition? computation #f)
, which I would
now write as (and condition? computation)
.switch
s here that I actually would like to error if the
value meets no condition (which I can do with an else
clause). A
non-matching value would indicated an error in the program, possibly at a
previous step.c-instersect
has another (if ? f #f)
that should be (and ? f)
solve
has an odd phrase (~> sep (amp f) (pass _) collect)
where the intent is to
pass along the non-false results of f
; (pass f)
would pass the originals
if they are non-false when given to f
. I think what I wrote is possibly an
inefficient filter-map
?occupied-in-hall
has
(~> state-creatures-v ;; flow that produces many values
(pass hall?)
(amp hall-h)
set)
which might better be written as
(~> state-creatures-v
(amp (when hall? hall-h))
set)
or some equivalent using switch
(both avoid multiple passes)
blocked-in-hall?
has another instance of
(~> … (if > X _) (== _ add1) range sep …)
moves-for
and moves
use list operations, so some conditionals have to
return null
. If I used values instead, moves
might be written more
naturally and the use of ground might be more appealing.solve
uses a rather odd phrase: it can return either a number (cost) or
#f
, and it is recursive. Thus when computing the new cost we have
(~> (-< cost (~> transition s) solve)
(if 2> + #f))
and we do that for each possible move. Then to find the best we take those results through
(~> … (pass number?)
(if (~> count zero?) ;; today we might use live? or rectify here
#f
min))
Dealing with the #f
and/or lack of values was a bit odd here.
step
uses the (if ? f _)
pattern:
(if (and (~> 2> (eq? 'east))
(~> 1> nx (not (hash-has-key? m _))))
(== nx _)
_)
The duplicate computation of nx
happens to be cheap here, but bugs me upon
review. A similar pattern occurs twice in this function.
Random observation: occasionally the structure of some flows I have written is
not the most efficient (for example: (~> (-< car cdr) (== (~> add1 (modulo x)) _) cons)
). But the structure is suggestive: the written example is clearly
destructuring the data, transforming its components, and reconstructing them.
The more efficient version is (~> (-< (~> car add1 (modulo x)) cdr) cons)
,
which may even be less to type, but loses to my eyes the structure. I happened
to see this type of pattern a lot in my Advent of Code, I think because I was
working with pairs so often.
(if ? f "")
that semantically is "if condition holds, do f;
always produce some string." So another way to write it might be (using the
current definitions):
(~> (when ? f) (rectify ""))
This shows up in several places because I need to produce (e.g.) a valid string for a GUI component.
(if _ f (gen empty))
which I thought was a bit
odd. I need to produce a list using f
if the input is non-false.rectify
in (~> sep (amp car) (rectify -1) max add1)
,
which appears to fit some of the patterns I mentioned earlier?(~> (-< f _) (switch (% 1> _) [? 2>] [else g]))
. In other words, we compute a value from
the input, and depending on a property of it, return either the input
unchanged or pass both through a new transformation.
[Continuing a discussion with @benknoble and @TrueQueenBee from Racket Discord]
There may be cases where we want to transform inputs if a condition is met, and pass them through unchanged, otherwise.
Here are some considerations in designing the solution:
only-when
andonly-unless
forms analogous towhen
andunless
that have this behavior?when
andunless
forms currently ground when the condition is not met. Is this useful behavior?switch
)?when
rather than introduce a new form.if
,when
,unless
,pass
,gate
. Are these forms all distinct enough from one another, or is there overlap that we feel would be better avoided? How would introducingonly-when
/only-unless
vs repurposing the currentwhen
/unless
affect this?To help identify a good solution, we need examples! There are a few patterns here to look out for which may be relevant to consider:
(if (< a b) a b)