Hi All,
Just following up, I've been contemplating the idea of dynamic
values/time-varying state a lot in the context of my previous email. If
something that is deref-able (IDeref) is considered something that is
time-varying, then it also leaks that what uses it is impure.
If IDeref becomes something representing a time-varying value, then we
could have something like:
(def input (r!*! read-line))
(defn process [src]
(loop [v @src]
(when v
(println Read: v)
(recur @src
With (process input), this ends up blocking on the deref of input, awaiting
on the read-line. If on the other hand, we don't use a IDeref, but a
0-arity function:
(defn process [func]
(loop [v (func)]
(when v
(println Read: v)
(recur (func)
We get the notion with (process read-line) that func is impure and returns
different values over time, but it's not as visible IMO as compared to
@src.
Extending this line of thought, I then thought about core.async's channels
as just another source of values over time. For example, if channels were
deref-able, then this:
(*go* (*while* true
(*let* [v (! ch)]
(println Read: v
could be rewritten as:
(*go* (*while* true
(*let* [v @ch]
(println Read: v
If the code above was not written in a go block, then it would look very
similar to the process function earlier. It would also mean you could pass in
ch as a channel, or pass in the (r!*! read-line), or pass in a deref-able such
as:
*(*defn seq-iter *[*s*]**(let* *[*head *(**atom* s*)]*
*(*reify IDeref
*(**deref* *[*_*]* *(let* *[[*x ** xs*]* *@*head*]*
*(**reset!* head xs*)*
x*)*
(process (seq-iter [a b c))
And the process code would work with all three sources of time-varying values.
(The last could be useful for mocking changes in values over time). It also
doesn't matter to the process function whether the passed in IDeref is an atom,
ref, channel, or some other source of value; or whether it blocks or not. The
code then becomes generalized to code that works with mutable data, and we can
pass in arbitrary data sources.
Something I both like and dislike about the !*! notation is that it is highly
visible in the code. I don't know if it is as clear as something like the
conventions of *earmuffs* on a var to show something dynamic is used, but at
least the code using it signifies: this function and code is not like the
others, it is impure and deals with mutable values. Using @ also denotes
working with something involving change and time, which seems to make the @ and
r!*! blocks have a natural fit.
On the one hand, I'm not sure if generalizing IDeref to time-varying values,
and the things mentioned above, would make things more complicated or less. On
the other hand, IDeref also has multiple meanings as it is (i.e. get me the
current value in time of a thing, get me the one and only value but when it's
ready). This is to just to say, I'm not sure about all this yet, still just
very curious.
Thanks!
steven
On Wednesday, December 3, 2014 12:21:54 AM UTC-5, Steven Yi wrote:
Hi All,
I was working on a music notation issue with my music libraries Pink
and Score and came upon a solution I thought was curious. It certainly
solved a real problem I had with delayed function calls, and I thought
maybe others might find a use for it too.
The scenario I had is that in writing events, I needed to find a way
to have certain arguments be dynamic at the time of application of the
function, rather than the time of event creation. For example:
[fm 2.5 0.3 (env [0.0 500 0.5 1000]) 0.3]
when processed by Score and Pink, would yield an event data structure
where the 3rd argument to the fm function would be a stateful function
generated by (env ...). That would be fine if that the event was used
once, but if the event was reused (i.e. I want to listen to that block
of score again...), the function returned by (env) would have already
been processed.
The code I came up with is at [1]. It allowed so that I could use:
[fm 2.5 0.3 (!*! env [0.0 500 0.5 1000]) 0.3]
and everytime the fm was applied, the code within the !*! was run at
apply time. This ends up using the apply!*! function from [1] instead
of apply to resolve that argument when applying the fm function.
I found this curious as it meant that I didn't have to write a new
wrapper function just to dynamically create arguments to call the fm
function, such as:
[#(fm 0.3 (env [0.0 500 0.5 1000]) 0.3) 0.25]
which seemed a bit more noisy and less clear to the intent of the event
code.
I tried to think of a more general scenario where this might be
useful. I came up