((section 2 "Outdated egg!" (p "This is an egg for CHICKEN 4, the unsupported old release.  You're almost certainly looking for " (int-link "/eggref/5/gochan" "the CHICKEN 5 version of this egg") ", if it exists.") (p "If it does not exist, there may be equivalent functionality provided by another egg; have a look at the " (link "https://wiki.call-cc.org/chicken-projects/egg-index-5.html" "egg index") ". Otherwise, please consider porting this egg to the current version of CHICKEN.")) (section 2 "chicken-gochan" (toc) (p (link "http://golang.org/" "Go") "-inspired channels for " (link "http://call-cc.org/" "Chicken Scheme") " (" (link "http://synthcode.com/wiki/chibi-scheme" "Chibi Scheme") " might work too). Essentially thread-safe fifo queues that are useful for thread communication and synchronization.") (section 3 "Dependencies" (ul (li (int-link "wiki.call-cc.org/eggref/4/matchable" "matchable")))) (section 3 "Development Status" (p "Currently supported:") (ul (li "receive and send switch (" (tt "gochan-select") ")") (li "buffered channels") (li "timeouts as ordinary receive on a channel") (li "closable channels with close-reason (aka " (tt "fail-flag") ")") (li "load-balancing when multiple channels have data ready")) (p "Source code can be found " (link "https://github.com/Adellica/chicken-gochan" "here") ".")) (section 3 "Comparison to real Go Channels" (p "The API and behaviour largely follows " (link "http://golang.org/" "Go") "'s channel API, with some exceptions:") (ul (li "channels don't have any type information") (li "sending to a channel that gets closed does not panic, it unblocks all senders immediately with the " (tt "fail") " flag set to non-" (tt "#f") ".") (li "closing an already closed channel does not result in error.") (li (tt "nil") "-channels aren't supported, create new forever-blocking " (tt "(gochan 0)") " instead.") (li "Unlike in " (link "http://golang.org/" "Go") ", you can choose what channels to select on at runtime with " (tt "gochan-select*")))) (section 3 ("Comparison to " (link "https://github.com/clojure/core.async" "core.async")) (p "Honestly, I wish I had stolen the " (link "https://github.com/clojure/core.async" "core.async") " API instead of the " (link "http://golang.org/" "Go") " channel API since that's already a LISP, but here is what we have for now:") (ul (li (tt "alt!") " is " (tt "gochan-select")) (li (tt "alts!") " is " (tt "gochan-select*")) (li (tt "<!") " is " (tt "gochan-recv")) (li (tt ">!") " is " (tt "gochan-send")) (li "There is no distinction between " (tt "park") " and " (tt "block") " because CHICKEN doesn't have native threads.") (li "The operations don't need to be called inside " (tt "(go ...)") " blocks."))) (section 3 "API" (def (sig (procedure " (gochan capacity)" (id gochan))) (p "Construct a channel with a maximum buffer-size of " (tt "capacity") ". If " (tt "capacity") " is " (tt "0") ", the channel is unbuffered and all its operations will block until a remote end sends/receives.")) (def (sig (syntax " (gochan-select ((chan <-|-> msg [ fail ]) body ...) ... [(else body ...])" (id #f))) (p "This is a channel switch that will send or receive on a single channel, picking whichever clause is able to complete soonest. If no clause is ready, " (tt "gochan-select") " will block until one does, unless " (tt "else") " is specified which will by execute its body instead of blocking. Multiple send and receive clauses can be specified interchangeably. Note that only one clause will be served.") (p "Here's an example:") (highlight scheme "    \n(gochan-select\n  ((chan1 -> msg fail) (if fail (print \"chan1 closed!\") (print \"chan1 says \" msg)))\n  ((chan2 -> msg fail) (if fail (print \"chan2 closed!\") (print \"chan2 says \" msg))))") (p "Receive clauses, " (tt "((chan -> msg [fail]) body ...)") ", execute " (tt "body") " with " (tt "msg") " bound to the message object and " (tt "fail") " bound to a flag indicating failure. Receiving from a closed channel immediately completes with this " (tt "fail") " flag set to non-" (tt "#f") ".") (p "Send clauses, " (tt "((chan <- msg [fail]) body ...)") ", execute " (tt "body") " after " (tt "msg") " has been sent to a receiver, successfully buffered onto the channel, or if channel was closed. Sending to a closed channel immediately completes with the " (tt "fail") " flag set to " (tt "#f") ".") (p "A send or receive clause on a closed channel with no " (tt "fail") "-flag binding specified will immediately return " (tt "(void)") " without executing " (tt "body") ". This can be combined with recursion like this:") (highlight scheme "    \n;; loop forever until either chan1 or chan2 closes\n(let loop ()\n   (gochan-select\n    ((chan1 -> msg) (print \"chan1 says \" msg) (loop))\n    ((chan2 <- 123) (print \"chan2 got  \" 123) (loop))))") (p "Or like this:") (highlight scheme "    \n;; loop forever until chan1 closes. replacing chan2 is important to avoid busy-wait!\n(let loop ((chan2 chan2))\n  (gochan-select\n   ((chan1 -> msg)      (print \"chan1 says \" msg) (loop chan2))\n   ((chan2 -> msg fail) (if fail\n                            (begin (print \"chan2 closed, keep going\")\n                                   ;; create new forever-blocking channel\n                                   (loop (gochan 0)))\n                            (begin (print \"chan2 says \" msg)\n                                   (loop chan2))))))") (p (tt "gochan-select") " returns the return-value of the executed clause's body.") (p "To do a non-blocking receive, you can do the following:") (highlight scheme "    \n(gochan-select ((chan1 -> msg fail) (if fail #!eof msg))\n               (else 'eagain))\n")) (def (sig (procedure " (gochan-send chan msg)" (id gochan-send))) (p "This is short for " (tt "(gochan-select ((chan <- msg fail) (values  #f fail #t)))") ".")) (def (sig (procedure " (gochan-recv chan)" (id gochan-recv))) (p "This is short for " (tt "(gochan-select ((chan -> msg fail) (values msg fail #t)))") ".")) (def (sig (procedure " (gochan-close chan [fail-flag])" (id gochan-close))) (p "Close the channel. Note that this will unblock existing receivers and senders waiting for an operation on " (tt "chan") " with the " (tt "fail-flag") " set to a non-" (tt "#f") " value. All " (i "future") " receivers and senders will also immdiately unblock in this way, so watch out for busy-loops.") (p "The optional " (tt "fail-flag") " can be used to specify an alternative to the default " (tt "#t") ". As this value is given to all receivers and senders of " (tt "chan") ", the " (tt "fail-flag") " can be used as a \"broadcast\" mechanism. " (tt "fail-flag") " cannot be " (tt "#f") " as that would indicate a successful message transaction.") (p "Closing an already closed channel will results in its " (tt "fail-flag") " being updated.")) (def (sig (procedure " (gochan-after duration/ms)" (id gochan-after))) (p "Returns a " (tt "gochan") " that will \"send\" a single message after " (tt "duration/ms") " milliseconds of its creation. The message is the " (tt "(current-milliseconds)") " value at the time of the timeout (not when the message was received). Receiving more than once on an " (tt "gochan-after") " channel will block indefinitely or deadlock the second time.") (highlight scheme "    \n(gochan-select\n ((chan1 -> msg)                (print \"chan1 says \" msg))\n (((gochan-after 1000) -> when) (print \"chan1 took too long\")))") (p "You cannot send to or close a timer channel. These are special records that contain information about when the next timer will trigger. Creating timers is a relatively cheap operation, and unlike " (link "https://golang.org/pkg/time/#After" "golang.time.After") ", may be garbage-collected before the timer triggers. Creating a timer does not spawn a new thread.")) (def (sig (procedure " (gochan-tick duration/ms)" (id gochan-tick))) (p "Return a " (tt "gochan") " that will \"send\" a message every " (tt "duration/ms") " milliseconds. The message is the " (tt "(current-milliseconds)") " value at the time of the tick (not when it was received).") (p "See " (link "https://github.com/Adellica/chicken-gochan/blob/master/tests/worker-pool.scm" ((tt "tests/worker-pool.scm"))) " for an example of its use.")) (def (sig (syntax " (go body ...)" (id go))) (p "Starts and returns a new srfi-18 thread. Short for " (tt "(thread-start! (lambda () body ...))") "."))) (section 3 "Samples" (ul (li "See " (link "https://github.com/Adellica/chicken-gochan/blob/master/tests/worker-pool.scm" ((tt "tests/worker-pool.scm"))) " for a port of " (link "https://gobyexample.com/worker-pools" "this Go example") ".") (li "See " (link "https://github.com/Adellica/chicken-gochan/blob/master/tests/secret.scm" ((tt "tests/secret.scm"))) " for a port of " (link "https://blog.jayway.com/2014/09/16/comparing-core-async-and-rx-by-example/" "this") " " (link "https://github.com/clojure/core.async" "core.async") "/" (link "https://github.com/Reactive-Extensions/RxJS" "rxjs") " challenge."))) (section 3 "TODO" (ul (li "Perhaps rename the API to " (link "https://github.com/clojure/core.async" "core.async") "'s?") (li "Add " (tt "go-map") ", " (tt "go-fold") " and friends (hopefully simple because we can also do " (link "http://clojure.github.io/core.async/#clojure.core.async/map" "this") ")") (li "Support customizing buffering behaviour, like " (link "https://github.com/clojure/core.async" "core.async") "'s " (link "http://clojure.github.io/core.async/#clojure.core.async/dropping-buffer" ((tt "dropping-buffer"))) " and " (link "http://clojure.github.io/core.async/#clojure.core.async/sliding-buffer" ((tt "sliding-buffer"))) " (harder!)") (li "Add a priority option to " (tt "gochan-select*") "?") (li "Support cancelling timers")))))