-
Notifications
You must be signed in to change notification settings - Fork 3
Implement Concurrency Primitives #16
Comments
Just quick brainstorm ... I'm really not sure if the following is a good idea... 😅 Nevertheless, #17 introduces the notion of a goroutine into Parens via the But it occurs to me that Lisp is not Go, and that there might be an opportunity for a bit of constructive hand-holding ... maybe. Let us start with the observation that our Why does this matter? Because some bare-bones synchronization might be relatively easy when we don't have to handle arbitrary arities. It should be pretty easy to pass that return value back to the calling thread. Right now func (ge GoExpr) Eval(ctx *Context) (value.Any, error) {
child := ctx.fork()
go func() {
_, _ = child.Eval(ge.Value)
}()
return nil, nil
} This means that if we want to recover the a value from a separate goroutine, we have to wrap the expression of interest in e.g. a second function invocation: ;; pseudocode
(let [ch make-chan]
(go (my-expensive-func ch))
(print (<- ch))) But what if we allow users to recover the value of the expression they've evaluated in a separate thread? We could make type Promise struct{
// both channels are buffered (len=1)
val chan value.Any
err chan error
}
(p Promise) Eval(ctx *Context) (value.Any, err) { return p, nil }
(p Promise) Invoke(ctx *Context, args ...value.Any) (val value.Any, err error) {
select {
case val = <-p.val
case err = <-p.err
}
return
}
type GoExpr struct {
Value value.Any
}
// Eval forks the given context to get a child context and launches goroutine
// with the child context to evaluate the Value.
func (ge GoExpr) Eval(ctx *Context) (value.Any, error) {
child := ctx.fork()
p := Promise{
val: make(chan value.Any, 1)
err: make(chan error, 1)
}
go func() {
// N.B.: we don't need to close any of the channels (see note below).
if val, err := child.Eval(ge.Value); err != nil {
p.err <- err // non-blocking due to buffer
} else {
p.val <- val
}
}()
return nil, nil
} The initial lisp code can now be rewritten as: (print ((go my-expensive-func))) A quick note on performance. Note that because we never have to close the channel, we can recycle these using a clever mix of Just to reiterate, I'm not sure this is a good idea. My gut says that Go's concurrency semantics won't map onto a functional language perfectly, and that we'll have to find some new ways of expressing familiar concepts like In any case, this has been nagging me for a few days, so I wanted to get your thoughts. 🙂 |
Oooh this is interesting. I will think about the concurrency part itself a bit more before commenting. But I am thinking forcing the promise for value shouldn't be done through invokable just because it might be confusing. We could use the Clojure approach may be? |
Agreed.
How about a method on promise? One of my major gripes with Clojure that the default namespace too cluttered. I'd rather not add any globals if we can avoid it. The
👍 Yeah, I'm still mulling this over on my end as well. I agree we should take the time to do it right. Addendum: Whatever solution we go for, it should also support a |
Depends on #12 and #14.
Requirements to be decided.
The text was updated successfully, but these errors were encountered: