Re: Announcing 8sync: an asynchronous programming language for Guile

2015-12-05 Thread Christopher Allan Webber
Amirouche Boubekki writes:

>> Le 2015-12-04 03:47, Amirouche Boubekki a écrit :
>> 
>>>Le 2015-12-04 03:15, Amirouche Boubekki a écrit :
>>> 
>>>```
>>>(define (read/ sock)
>>>  (abort-to-prompt 'loop (lambda (cc)
>>>   (loop-add-reader sock (lambda () (cc 
>>> (read
>>>sock)))
>>>```
>>> 
>> 
>>This is a mistake, it *must* be a macro
>> 
>
> This will only help with debugging but not catching error and
> dealing with them at runtime ie. it doesn't propagate the exception
> but it can be done and requires a form similar to 
> propagate-%async-exceptions.
>
> TBH the code still seems complex maybe complicated. FWIW here is
> what I think right now. Be warned that everything should be taken
> with a grain of salt.
>
> * Agenda
>
> ** The canonical callback based event loop API is not visible enough
>
> It should be obvious coming from outside Guile world what/where
> is the event loop. As such, agenda doesn't seem like a good name.

"Agenda" is not a unique name for this.  That's what SICP uses, that's
what Sly uses.

> ** Agenda has both `schedule` and `queue`
>
> For a proof of concept, queue/schedule is not useful to demonstrate
> the purpose of eightsync as it's an optimization.

It's no mere optimization.  "schedule" is future events that haven't
been queued; it's only for time-delayed events.  The queue is for things
that must be done immediately.

This separation is very intentional.

> ** `(current-agenda-prompt)`
>
> No need to throw, there should always be a current agenda.

That's not true, if no agenda has been started, %current-agenda will be
#f, so there will be no way to retrieve the current agenda's prompt tag.

> * 
>
> Schedule should be in its own file. Also, in place of:
>
> ```
> (define-record-type 
>(make-schedule-intern segments)
>schedule?
>(segments schedule-segments set-schedule-segments!))
> ```
>
> I prefer:
>
> ```
> (define-record-type 
>(%make-schedule segments)
>schedule?
>(segments schedule-segments-ref schedule-segments-set!))
> ```

%make-schedule might be better.

> Use of `*-ref` and `*-set!`.

-ref is extra typing.  Doing just schedule-segments fits within
conventions shown elsewhere in the manual.

I don't really want to encourage mutation; getters are the default.

> Or better: `(define-record-type*  segments)` (actually I
> think this macro should be in Guile, since it much easier to
> introduce  to new devs).
>
> ** `time` procedures have equivalent in Guile

Yeah I might change that.

> *** `tdelta` is not useful.

tdelta is used so the the agenda knows to put it a future time from the
current execution of the agenda.

> * ``
>
> ** `run-it`
>
> It's sound like it's a `(loop-call-soon callback #:optional (delay 0))`
> procedure.
>
> Instead it's a `(make-run-request proc #:optional time)`.
>
> It's never used.
>
> I'd rather to use a `delay`, and let the devs compute the correct delay,
> when they wants to run something at an absolute time to limit the
> proliferation of sugar procedure in the library.

You're right, it's never really used at present.  There is a difference
between (run (foo)) and (run-it foo) which isn't just the optional #:when,
which can indeed be handled by using run-at or run-delay instead.

Here's the difference: run uses (wrap) to do a friendly
wrap-in-a-thunk.  So you can do to this:


  (define (my-handler)
(let ((foo (bar)))
   (run (mork foo

and behind the scenes, it constructs (lambda () (mork foo)) there,
preserving lexically scoped varibles.

If you already have a thunk though, you can just pass it in.  In such a
case you'd normally need to give another layer of indirection with
(run (my-thunk)).

It's an optimization that might not be necessary.  I might remove
it... I'm going to wait and see.

> *** `(wrap e ...)` and `(wrap-apply body)`
>
> I'm pretty sure that `wrap` can be written using `wrap-apply`. This
> looks suspicious for several reasons.

There's a difference between them:

 - (run) uses (wrap) to wrap all body contents in a thunk, preserving
   the illusion that everything looks the same as running code inline,
   as described above.  For most code, that's what you want.  (wrap)
   basically makes making inline thunks a little cleaner, and hands that
   off to other macros which make thunks too.

   This is nice because you can even do something like:

 (run (cond (foo bar)
(baz basil)))

 - However, things that handle ports aren't thunks... they're callbacks
   that take an argument of whatever port it is.  Hence, making a thunk
   doesn't make sense.  But I still want an easy way to provide the
   clean appearance of inline code.  So (wrap-apply) passes along
   whatever arguments.

   You can't pass certain syntactic forms to (apply), for example cond
   above, so it's not as desirable generally.

> First it looks like a delayed call, so why not use force/delay.

It might be 

Re: Announcing 8sync: an asynchronous programming language for Guile

2015-12-05 Thread Amirouche Boubekki

Le 2015-12-05 15:58, Christopher Allan Webber a écrit :

Amirouche Boubekki writes:


*** `tdelta` is not useful.


tdelta is used so the the agenda knows to put it a future time from the
current execution of the agenda.



I wanted to write that it's only a shorcut:

```
(define tdelta make-time-delta)
```



Re: Announcing 8sync: an asynchronous programming language for Guile

2015-12-05 Thread Amirouche Boubekki

Le 2015-12-05 15:58, Christopher Allan Webber a écrit :

Amirouche Boubekki writes:


* %8sync

This is the main macro, here is it's definition:

```
(define-syntax-rule (%8sync async-request)
   (propagate-%async-exceptions
(abort-to-prompt (current-agenda-prompt) async-request)))
```

I'm wondering whether (current-agenda-prompt) is useful.
I think the code will abort to the prompt of the current dynamic
context. So except if there is multiple agenda in the same thread,
it's not useful.


You're right that this is the context in which this becomes useful.  I
might have composed agendas at some point.  I'm not sure.


It's comparable to the way I define blocking procedures in async.scm

```
(define-public (read/ sock)
   (abort-to-prompt 'loop async-request
```


Right, that means that 'loop is always for "the agenda", and there will
always be one.


where `async-request` is in `async.scm`:

```
(lambda (cc) (loop-add-reader sock (lambda () (cc (read sock)
```

It's the way `async.scm` does blocking calls (and *only* blocking
calls).  `read/` and its `async-request` is missing catch around
`read` and something like `propagate-%async-exceptions`.


8sync doesn't block though.  Its use of select means you only need to
get information once it's available.

The code you have here will block other code that is available to run
while it's waiting for code.


I wrote "blocking" code in the sens that it is a call that blocks
when the port is not ready. In this case `read` is called
when the port is ready via the lambda registered as callback
with `loop-add-reader` which is called via select only when the port is 
ready.


It's similar to 8sync code, except there is not later dispatch like
in setup-async-request. Instead it's directly registers in the handler
of call-with-prompt.

The argument of the prompt is:


(lambda (cc) (loop-add-reader sock
   (lambda ()
 (cc (read sock)


Which is called by the handle of call-with-prompt as callback:

```
(call-with-prompt 'loop
  loop-run-once
  (lambda (cc callback) (callback cc)
```

Here cc is the continuation of the prompt handler.

tldr: There is certain number of proc that call each other
  in some order for the better good :)



8sync has two types of async-request:

** run-requests, which implements kind of a *coroutine* behavior.

It pause the execution of the current procedure and schedule
the provided lambda to be run soonish; This doesn't exists in
async.scm. The only thing useful this can do, is break the callstack
to allow deeper recursion.


That's not true.  See the problems of "callback hell" people get into 
in

node.js.  8sync mitigates this.


Yes, but this must be mitigated only for calls that would block without
select.


As for the callback stack, it does get broken that's true... but that's
why 8sync gives you copies of all the stacks that were recursively
called as it walks back through its ~futures upon some exception.


This is nice.



I realize I disagreed with a lot of what you said, but a lot of the
things you challenged are intentional designs in 8sync, not accidents.

I do appreciate the feedback though, it helped me clarify some of the
design decisions in 8sync, which will be useful for docs-writing!



Thanks for taking the time to respond it helps better understand.




Re: Announcing 8sync: an asynchronous programming language for Guile

2015-12-05 Thread Amirouche Boubekki

Le 2015-12-04 03:47, Amirouche Boubekki a écrit :


   Le 2015-12-04 03:15, Amirouche Boubekki a écrit :

   ```
   (define (read/ sock)
 (abort-to-prompt 'loop (lambda (cc)
  (loop-add-reader sock (lambda () (cc 
(read

   sock)))
   ```



   This is a mistake, it *must* be a macro



This will only help with debugging but not catching error and
dealing with them at runtime ie. it doesn't propagate the exception
but it can be done and requires a form similar to 
propagate-%async-exceptions.


TBH the code still seems complex maybe complicated. FWIW here is
what I think right now. Be warned that everything should be taken
with a grain of salt.

* Agenda

** The canonical callback based event loop API is not visible enough

It should be obvious coming from outside Guile world what/where
is the event loop. As such, agenda doesn't seem like a good name.

** Agenda has both `schedule` and `queue`

For a proof of concept, queue/schedule is not useful to demonstrate
the purpose of eightsync as it's an optimization.

** `(current-agenda-prompt)`

No need to throw, there should always be a current agenda.

* 

Schedule should be in its own file. Also, in place of:

```
(define-record-type 
  (make-schedule-intern segments)
  schedule?
  (segments schedule-segments set-schedule-segments!))
```

I prefer:

```
(define-record-type 
  (%make-schedule segments)
  schedule?
  (segments schedule-segments-ref schedule-segments-set!))
```

Use of `*-ref` and `*-set!`.

Or better: `(define-record-type*  segments)` (actually I
think this macro should be in Guile, since it much easier to
introduce  to new devs).

** `time` procedures have equivalent in Guile

*** `tdelta` is not useful.

* ``

** `run-it`

It's sound like it's a `(loop-call-soon callback #:optional (delay 0))`
procedure.

Instead it's a `(make-run-request proc #:optional time)`.

It's never used.

I'd rather to use a `delay`, and let the devs compute the correct delay,
when they wants to run something at an absolute time to limit the
proliferation of sugar procedure in the library.

*** `(wrap e ...)` and `(wrap-apply body)`

I'm pretty sure that `wrap` can be written using `wrap-apply`. This 
looks

suspicious for several reasons.

First it looks like a delayed call, so why not use force/delay.

Also wrap-apply is never used.

I think it's not schemey to hide a lambda this way, especially 
`wrap-apply`

which breaks lexical scope.

`wrap` is used with `(make-run-request proc time)` in `run`, `run-at`
and `run-delay`, e.g.:

```
(define-syntax-rule (%run-delay body ... delay-time)
  (%run-at body ... (tdelta delay-time)))
```

Again, `run` and others are building a datastructure not registering
a callback so the naming is not good.

* `(make-port-request port #:key read write except)`

In `async.scm` I don't need to create a datastructure to subscribe to 
select events.


** `port-request` defined at L452 is never used

* %8sync

This is the main macro, here is it's definition:

```
(define-syntax-rule (%8sync async-request)
  (propagate-%async-exceptions
   (abort-to-prompt (current-agenda-prompt) async-request)))
```

I'm wondering whether (current-agenda-prompt) is useful.
I think the code will abort to the prompt of the current dynamic
context. So except if there is multiple agenda in the same thread,
it's not useful.


It's comparable to the way I define blocking procedures in async.scm

```
(define-public (read/ sock)
  (abort-to-prompt 'loop async-request
```

where `async-request` is in `async.scm`:

```
(lambda (cc) (loop-add-reader sock (lambda () (cc (read sock)
```

It's the way `async.scm` does blocking calls (and *only* blocking 
calls).
`read/` and its `async-request` is missing catch around `read` and 
something

like `propagate-%async-exceptions`.

8sync has two types of async-request:

** run-requests, which implements kind of a *coroutine* behavior.

It pause the execution of the current procedure and schedule
the provided lambda to be run soonish; This doesn't exists in
async.scm. The only thing useful this can do, is break the callstack
to allow deeper recursion.

** port-requests, which implements imperative asynchronous calls of 
blocking operations


** %8sync usage example

```
(%8sync (%run (handle-line socket ready-line username)))
```

Where %run is a macro that implements exception catch,
complementary to `propagate-%async-exceptions`.

** Last note

This doesn't look nice:

```
(define (ports->procs ports port-map)
  (lambda (initial-procs)
(fold
 (lambda (port prev)
   (cons (lambda ()
   ((hash-ref port-map port) port))
 prev))
 initial-procs
 ports)))
```

then:

```
   ((compose (ports->procs
  read-ports
  (agenda-read-port-map agenda))
 (ports->procs
  write-ports
  (agenda-write-port-map