IRC channel logs

2013-12-17.log

back to list of logs

<davexunit>hey guilers
<mark_weaver>hi davexunit!
<davexunit>did some more hacking on my FRP-ish module. encountered some interesting problems with my design.
<mark_weaver>what problems?
<davexunit>each "signal" is a hook (though not a guile hook) with some number of dependent signals that receive new values..
<davexunit>so the result is a tree structure of signals.
<davexunit>this creates problems for development at the REPL
<davexunit>let's say I have a signal foo: (define foo (make-root-signal 'foo))
<davexunit>and I connect a signal bar to it: (define bar (signal-constant 'bar foo))
<davexunit>but now I decide that I want to redefine what bar does: (define bar (signal-constant 'different-bar foo))
<davexunit>I'm no longer using the original value of bar, but since it's connected to foo, it stays around.
<mark_weaver>hmm
<davexunit>if we pretend that computing bar is an expensive operation, I'm now doing an expensive operation that I no longer care about.
<mark_weaver>that 'define-signal' macro that you were thinking about the other day might help here, no?
<davexunit>I make a design decision that signals would push new values to their connected signals, rather than signals having to poll periodically for new values.
<davexunit>mark_weaver: yeah, it might, but it's still a bit complicated, at least how things are currently designed.
<mark_weaver>it's definitely better to push new values; polling is terrible.
<davexunit>(define foo (make-root-signal (cons 2 4)) (define bar (signal-map (cut * 2 <>) (signal-map car foo)))
<davexunit>in this example, I've composed several signals together.
<davexunit>perhaps each signal needs to carry an additional procedure that can succesfully disconnect from the other signals it depends on.
<davexunit>or carry a list of dependencies.
<davexunit>which turns a signal into a doubly-linked list of sorts.
<mark_weaver>here's a thought: maybe the pointers to the dependent signals (the downstream ones) should be weak pointers.
<davexunit>but I'm not sure that it's a good route to take. I'm worried about leaking memory and doing unncessary computation.
<mark_weaver>so they wouldn't keep the downstream signals alive.
<davexunit>upstream signals. signals propagate from the bottom up.
*davexunit looks up weak pointers
<davexunit>I see docs on weak key hash tables and weak vectors.
<mark_weaver>well, I think of the water flowing in the stream as an analogy to information. so the direction of information flow is the same as the flow of the water in a stream. so the propagation would be from top to bottom. but whatever :)
<davexunit>that makes sense.
<mark_weaver>yeah, you'd have to use one of those.
<davexunit>I'll just use a weak vector then.
<davexunit>I totally forgot about that feature
<davexunit>I used weak key dictionaries in python before.
<mark_weaver>of course it means that you'd need to keep explicit pointers to the signals that you care about at the leaves of the tree.
<mark_weaver>this may or may not be a problem. just something to think about.
<mark_weaver>I'm not sure this is a good idea.. just something that popped in my head and out my fingers :)
<mark_weaver>(I'm not saying it's a bad idea either; just unsure)
<davexunit>mark_weaver: I'll try it.
<davexunit>I think it will work, because I have pointers to signals in closures.
<mark_weaver>okay
<davexunit>I imagine that I'll find out relatively quickly if it works or if everything blows up.
<davexunit>thanks for following along, mark_weaver
<mark_weaver>sure :)
<davexunit>this has been a lot of fun to hack on
<mark_weaver>just make sure that the references you keep are really guaranteed to stay alive. a clever compiler might null out references to objects if it can prove that those objects will never again me referenced.
<mark_weaver>and our compiler will presumably get more clever with time.
<mark_weaver>s/me/be/
<davexunit>here's what I have so far (wip of course): https://gitorious.org/guile-2d/guile-2d/source/b4d1d462da6f4d0d5ff271df3d9c4e0de6eed6d1:2d/signals.scm
<mark_weaver>the thing is, when you redefine a signal at the REPL, in general it will be somewhere in the middle of an existing tree.
<mark_weaver>if you want things to work nicely, it seems like you'll really need to do something like 'signal-set!' anyway.
<mark_weaver>weak references will take care of eventually disconnecting the upstream signals from the old one, but you've still got the problem of reconnecting all the downstream signals to the new one.
<mark_weaver>(i'm using upstream and downstream according to water==information flow here)
<mark_weaver>so, on second thought, maybe your doubly-linked list idea is the better one anyway)
<davexunit>heh
<mark_weaver>SICP includes a simple constraint system that might be worth looking at.
<davexunit>mark_weaver: I've read it, implemented it, and pulled influence from it.
<davexunit>:)
<mark_weaver>okay
<davexunit>my system is a bit different than the constraints, but it was a really useful starting point.
<mark_weaver>*nod*
<mark_weaver>yeah, in the constraint system information flows in every direction, whereas in your system the direction of information flow is fixed.
<davexunit>yeah, that simplified things quite a bit.
<mark_weaver>the digital circuit simulator in SICP is actually closer to what you're doing.
<davexunit>yeah
<davexunit>I was also after an API that was similar to streams.
<davexunit>so you can just map, fold, and filter time-varying values easily.
<mark_weaver>sounds good!
<davexunit>the circuit simulator is where I got the agenda from.
<mark_weaver>I'm sorry I haven't had time to take a closer look, but the work you're doing sounds really great to me.
<davexunit>SICP has been a treasure trove of useful code for me.
<davexunit>that's quite alright, you are doing all sorts of other, more important work.
<davexunit>I'll experiment with weak vectors, see what happens.
<mark_weaver>well, I don't know if it's more important; we're just working on different areas of the guile community.
<davexunit>otherwise I'll go the doubly-linked list route.
<mark_weaver>writing exciting libraries for guile is very important to helping guile be successful.
<davexunit>absolutely.
<davexunit>I haven't had a whole lot of hacking time lately, but I'm slowly making headway.
<mark_weaver>generally, I try to avoid having pointers go in two directions of the same data structure. but if anything, having pointers in the upstream direction is the more natural way that they should point.
<mark_weaver>I think for something like this, it is okay to have bidirectional pointers.
<davexunit>yeah, it's more natural, but then you can't push values
<mark_weaver>*nod*
<davexunit>I was trying to avoid pointers in both directions.
<davexunit>but with a signal-disconnect! procedure that propagates upstream I can work from the REPL without making my program slower and slower over time.
<davexunit>and with a define-signal macro (should I ever figure it out) it can happen automagically. :)
<mark_weaver>for now, I think you should make 'define-signal' work only at top-level.
<davexunit>that's the only place it can really work, in practice.
<mark_weaver>so, I'm thinking about your example: (define foo (make-root-signal (cons 2 4)) (define bar (signal-map (cut * 2 <>) (signal-map car foo)))
<mark_weaver>and how to make it easy to rebind 'bar' to something else.
<mark_weaver>you want the old bar (and its intermediate things like the (signal-map car foo)) to be freed and disconnected from the upstreams.
<davexunit>yes.
<mark_weaver>and you also want anything downstream of the old 'bar' to remain connected to the new 'bar'.
<davexunit>that would be a nice feature of define-signal, yes.
<davexunit>I have a signal-connect! procedure to facilitate that
<mark_weaver>when you define a signal, you explicitly specify the upstreams. so all of the upstreams can be explicitly disconnected from the old version of 'bar' first.
<mark_weaver>and your example shows that this might need to cascade multiple levels.
<mark_weaver>there's an intermediate signal in there, which I will call 'bar*', which is (signal-map car foo)
<mark_weaver>so first, you'd disconnect the immediate upstreams from the old 'bar'. and then it should notice that 'bar*' no longer has any downstreams, so perhaps it should then destroy that one too. hmm.
<mark_weaver>but on the other hand, when you first create a signal, it has no downstreams.
<mark_weaver>so indeed, maybe weak references are the best way to solve that part of the problem.
<mark_weaver>the other part of the problem is to make sure that the new 'bar' is connected to everything downstream that was connected to the old 'bar'.
<mark_weaver>for this, you need somethign like 'signal-set!', but maybe that's not quite the right thing, because that only sets the value, not the description its upstreams and how to compute the value from the upstreams.
<mark_weaver>maybe you should introduce an additional level of indirection (not sure if it's needed; I haven't looked closely at your code).
<davexunit>hmmm
<davexunit>a lot to think about.
<mark_weaver>basically, the downstreams would point to an object that would retain its identity even if you redefine 'bar' to be something computed in a different way, and/or with different upstreams.
<mark_weaver>actually, I see that all of your signals use the same record type. is that right?
<mark_weaver>hmm. this is a bit tricky.
<mark_weaver>the problem is that you have a lot of different ways to create signals, and of course they are always going to create fresh new signal objects, as is natural.
<mark_weaver>now, if you knew for sure that there would be no other pointers to these fresh new signal objects, then you could simply have 'define-signal' copy the fields from the newly created signal into the old one, and discard the fresh new signal.
<mark_weaver>but I don't know if that's a safe assumption in general.
<mark_weaver>if you did this, in combination with making the downstream pointers weak, that might work.
<davexunit>yeah this is quite a tricky problem.
<mark_weaver>I have to go afk for a while. good luck!
<davexunit>thanks for the help.
<davexunit>I will be re-reading what you've said a few times until it all sinks in.
<davexunit>I'll see what I can come up with. :)
<mark_weaver>here's one last thought: if you can't safely assume that the newly created signal object has no other pointers to it, then you could *merge* the new signals together into (effectively) a single object, using a the union-find algorithm. See http://en.wikipedia.org/wiki/Disjoint-set_data_structure
*mark_weaver goes afk. happy hacking!
***fangism is now known as fangism-ctrl-Z
<nalaginrut>morning guilers~
<davexunit>morning
<nalaginrut>heya
<jmd>According to the manual, I can create a uri with (string->uri "http://www.gnu.org")
<jmd>But there wouls seem to be no such procedure.
<jmd>In procedure module-lookup: Unbound variable: string->uri
<jmd>Ditto for http-head
<nalaginrut>(use-modules (web uri))
<nalaginrut>and for http-head, you need (web http)
<b4283>that's what i thought
<jmd>Yes, I have both of those.
<nalaginrut>hmm? it shouldn't be
<jmd> In procedure module-lookup: Unboun\\
<jmd>Oh http-get works
<jmd>But throws an exception
<jmd>Isn't there a simple high level way to make a http request in guile?
<nalaginrut>isn't it http-get?
<jmd>It seems to be problematic
<nalaginrut>(http-get "http://gnu.org")
<jmd>I don't think so.
<nalaginrut>it shouldn't be, please describe it in detail
<nalaginrut>and your Guile version
<jmd>It is supposed to take a uri, not a string.
<nalaginrut>jmd: no, in the latest Guile, it takes both
<jmd>I'm using 2.0.5
<nalaginrut>ok, I see, web modules in 2.0.5 is not so good
<nalaginrut>but most of the problems were fixed in 2.0.9
<nalaginrut>when I'm hack my Artanis web-framework, I realized if you want to do something HTTP, you'd better choose 2.0.9
<jmd>(http-get (string->uri "http://www.gnu.org"))
<jmd>throws an exception
<nalaginrut>could you upgrade your Guile to 2.0.9
<nalaginrut>?
<nalaginrut>I think the bug maybe fixed in 2.0.9
<jmd>It's only a few weeks ago that I moved from 1.8
<jmd>Is there not a way to work around the bug?
<nalaginrut>dunno, maybe, but as I said, 2.0.9 made a lot of fixes in web modules
<nalaginrut>so I can't say if there's a simple patch for you
<nalaginrut>it doesn't work if you just copy web modules from 2.0.9
<nalaginrut>someone ever tried it
<jmd>On a completely different problem, what does this indicate:
<jmd> 374: 1 [eval #<memoized cairo> ()]
<jmd>In unknown file:
<jmd> ?: 0 [memoize-variable-access! #<memoized cairo> #<directory # 869a288>]
<jmd>
<nalaginrut>I'm not sure, have you loaded (cairo cairo) or something similar I don't remember
<jmd>Well it's evaluating `(("cairo" ,cairo)) I think
<nalaginrut>maybe, but it could be the way 'use-modules' works
<nalaginrut>have you tried (use-modules (cairo cairo)) ?
<jmd>There is no such module. The issue is, that the procedure cairo is defined later in the same file.
<jmd>I didn't think the order of the defines was important.
*nalaginrut haven't tried guile-cairo
<civodul>Hello Guilers!
<dsmith-work>Hey hey