Re: thoughts on targetting the web

2021-06-19 Thread Chris Lemmer-Webber
O M G!  So excited to read this email.  Yes!

Andy Wingo writes:

> Hi :)
>
> A brain-dump tonight.  I was thinking about what it would mean for Guile
> to target the web.  The goal would be for Guile to be a useful language
> for programming client-side interaction on web pages.  Now, there are a
> number of takes for Schemes of various stripes to target JS.  I haven't
> made a full survey but my starting point is that in a Guile context, we
> basically want the Guile Scheme language, which is to say:
>
>  1. Tail calls.  The solution must include proper tail calls.
>
>  2. Multiple values.  The solution should have the same semantics that
> Guile has for multiple-value returns, including implicit
> truncation.
>
>  3. Varargs.  The solution should allow for rest arguments, keyword
> arguments, and the like.
>
>  4. Delimited continuations.  The solution should allow non-local exit
> from any function, and should allow for capturing stack slices to
> partial continuations, and should allow those continuations to be
> instantiated multiple times if desired.  We should be able to target
> fibers to the web.  Otherwise, why bother?
>
>  5. Garbage collection.  *We should re-use the host GC*.  Although it
> would be possible to manage a heap in linear memory, that has
> retention problems due to cycles between the Guile heap and the JS
> heap.

It sounds like from when we spoke not long ago that progress towards the
WASM-GC proposal has been happening I think?  So this seems feasible?

However, I do think that a "proof of concept" GC would still be an
advancement.  Like, even lifting the dead-simple version from SICP as a
stopgap.  We'll all know it's not ideal, but it'll still be progress.
Getting somethign going and showing it is helpful.

That said, I also know how meh such a proposed approach can feel. ;)

>  6. Numbers.  We should support Scheme's unbounded integers.  Requires
> bignums in the limit case, but thankfully JS has bignums now.  We
> will still be able to unbox in many cases though.
>
>  7. Target existing web.  We shouldn't depend on some future WebAssembly
> or JS proposal -- the solution should work in the here and now and
> only get better as features are added to the web platform.

Yes.  Though, to jump back again to when you and I talked offline, I
think what I recommended was that as an early proof of concept, a good
idea might be to just have a small number of APIs, or even bet, make a
mini "fantasy console".  Here's an interesting example (and the one we
talked about, off-list):

  https://euhmeuh.github.io/wasm-adventure/

What's interesting about this first is that it's just a Racket program
that generates quasiquoted s-expression representation WASM.  Now,
obviously your compiler will do more interesting things than that.  But
the second interesting thing is the "fantasy console" component... a set
of custom interfaces to render a simple 8-bit aesthetic console.

My argument for this is: supporting "the existing web" is a big step.
Targeting a much smaller number of APIs, and being able to show the
world a demo that Guile's WASM compiler can do something cool... that's
very motivating, I think.

But an even simpler hello world would be to simply have access to
console.log, and write a scheme program that does ye olde Fibonacci
sequence and spits out the results on the page.  Not as exciting, but
that's the reasonable (and classic scheme) first step I think.

But yes, target the existing web... eventually!  Just don't get so hung
up on worrying about those interfaces that it distracts from making
something that can generate some WASM, is my thoughts.

> From a UX perspective, I would expect we would generally want
> whole-program compilation with aggressive DCE / tree-shaking, producing
> a single JS or WebAssembly artifact at build-time.  But that's a later
> issue.

Yes, focus on things working first... major optimizations can come later!

> I have thought about this off and on over the years but in the end was
> flummoxed about how to meet all requirements.  However recently I think
> I have come up with a solution for most of these:
>
>  (1) In JS, tail calls are part of ES2015, but not implemented in all
>  browsers.  In WebAssembly, they are a future plan, but hard for
>  various reasons.  So in practice the solution for today's web is to
>  use a universal trampoline and make all calls tail calls --
>  i.e. call all functions via:
>
>while true:
>  call(pop())
>  
>  Whenever we can target proper tail calls, this will only get
>  faster.
>
>  (2) Neither JS nor WebAssembly have the truncation semantics we want.
>  Therefore we will return values via an explicit array, and then the
>  continuation will be responsible for consuming those values and
>  binding any needed variables.
>
>  (3) Although JS does have varargs support, WebAssembly does not.  But
>  

thoughts on targetting the web

2021-06-19 Thread Andy Wingo
Hi :)

A brain-dump tonight.  I was thinking about what it would mean for Guile
to target the web.  The goal would be for Guile to be a useful language
for programming client-side interaction on web pages.  Now, there are a
number of takes for Schemes of various stripes to target JS.  I haven't
made a full survey but my starting point is that in a Guile context, we
basically want the Guile Scheme language, which is to say:

 1. Tail calls.  The solution must include proper tail calls.

 2. Multiple values.  The solution should have the same semantics that
Guile has for multiple-value returns, including implicit
truncation.

 3. Varargs.  The solution should allow for rest arguments, keyword
arguments, and the like.

 4. Delimited continuations.  The solution should allow non-local exit
from any function, and should allow for capturing stack slices to
partial continuations, and should allow those continuations to be
instantiated multiple times if desired.  We should be able to target
fibers to the web.  Otherwise, why bother?

 5. Garbage collection.  *We should re-use the host GC*.  Although it
would be possible to manage a heap in linear memory, that has
retention problems due to cycles between the Guile heap and the JS
heap.

 6. Numbers.  We should support Scheme's unbounded integers.  Requires
bignums in the limit case, but thankfully JS has bignums now.  We
will still be able to unbox in many cases though.

 7. Target existing web.  We shouldn't depend on some future WebAssembly
or JS proposal -- the solution should work in the here and now and
only get better as features are added to the web platform.

>From a UX perspective, I would expect we would generally want
whole-program compilation with aggressive DCE / tree-shaking, producing
a single JS or WebAssembly artifact at build-time.  But that's a later
issue.

I have thought about this off and on over the years but in the end was
flummoxed about how to meet all requirements.  However recently I think
I have come up with a solution for most of these:

 (1) In JS, tail calls are part of ES2015, but not implemented in all
 browsers.  In WebAssembly, they are a future plan, but hard for
 various reasons.  So in practice the solution for today's web is to
 use a universal trampoline and make all calls tail calls --
 i.e. call all functions via:

   while true:
 call(pop())
 
 Whenever we can target proper tail calls, this will only get
 faster.

 (2) Neither JS nor WebAssembly have the truncation semantics we want.
 Therefore we will return values via an explicit array, and then the
 continuation will be responsible for consuming those values and
 binding any needed variables.

 (3) Although JS does have varargs support, WebAssembly does not.  But
 we can actually use the same solution here as we use for (1) and
 (2) -- pass arguments on an explicit array + nvalues, and relying
 on the function to parse them appropriately.  In this way we can
 get Guile keyword arguments.  This also makes the type of Scheme
 functions uniform, which is important in WebAssembly contexts.

 (4) This is the interesting bit!  As hinted in (1), we will transform
 the program such that all calls are tail calls.  This is a form of
 minimal CPS conversion -- minimal in the sense that functions are
 broken up into the minimum number of pieces to ensure the
 all-tail-calls property.  Non-tail calls transform to tail calls by
 saving their continuation's state on a stack, which is the same as
 stack allocation for these continuations.  The continuation of the
 call pops the saved state from the stack.  Because the stack is
 explicit, we can manipulate it as data: slice it to capture a
 delimited continuation, drop values to make a non-local exit, splat
 a saved slice back on to compose a captured delimited continuation
 with the current continuation, and so on.  Therefore we have the
 necessary primitives to implement delimited continuations as a
 library.

 (5) Scheme needs a representation that can store any value.  In JS this
 is easy because JS is untyped.  For WebAssembly, I think I would
 lean on externref for this purpose, which effectively boxes all
 values.  There are no fixnums in the current WebAssembly spec, so
 this is suboptimal, and we have to call out to JS to evaluate type
 predicates and access fields.  But, support for structured
 GC-managed types is coming to WebAssembly, which will speed up
 object access.

 (6) The easy solution here is to make JS numbers, which are doubles at
 heart, represent flonums, and use JS bignums for Scheme integers.
 Fine.

 (7) This principle has been taken into account in (1)-(6).

Now, a note on the transformation described in (4), which I call
"tailification".

The first step of tailification computes the set of 

Fwd: guile style

2021-06-19 Thread Stefan Israelsson Tampe
-- Forwarded message -
From: Stefan Israelsson Tampe 
Date: Sat, Jun 19, 2021 at 1:23 PM
Subject: Re: guile style
To: Tim Van den Langenbergh 


I'm a big fan of named let's which are a very general functional construct
and tons of commentaries
about the functional style misses that named let's exists and thinks that
functional programming is just
map, fold, filter etc. It beats me why when other languages go functional,
they consistently do not
implement named let.

On Sat, Jun 19, 2021 at 12:25 PM Tim Van den Langenbergh 
wrote:

> On Saturday, 19 June 2021 02:55:34 CEST jerry wrote:
> > I am fairly new to guile and scheme. People tell me that I should use a
> > functional style.
> >
> > I have 3 solutions for project euler problem #1. The first is
> > functional, the second is imperative and the third is written in "Little
> > Schemer" style.
> >
> > I was hoping other guile users would comment on preferences or the
> > "correct way". Sorry in advance for any wrapping problems that may occur.
> >
> > #!/usr/local/bin/guile  -s
> > !#
> > (use-modules (srfi srfi-1) (jpd stdio)) ;; for folds
> > (define N 1000)
> >
> > (define ans
> >(fold + 0
> >  (filter
> >(lambda (x) (or (= 0 (modulo x 3)) (= 0 (modulo x 5
> >(iota N
> > (print ans)
> >
> > (define ans 0)
> > (for i N
> >(if (or (= 0 (modulo i 3)) (= 0 (modulo i 5))) (set! ans (+ ans i
> > (print ans)
> >
> > (define ans
> >(let loop ((i 1) (ans 0))
> >  (cond
> >((>= i N) ans)
> >((or (= 0 (modulo i 3)) (= 0 (modulo i 5))) (loop (1+ i) (+ ans
> i)))
> >(else (loop (1+ i) ans)) )))
>
> I'm not 100% sure about how Guile does it, but I know that some Scheme
> implementations do some boxing for set! operations, which will make the
> second
> variation poorly optimised. Personally I would use combine the first and
> third
> answers by doing the divisible-by check during the fold, like this:
>
> (use-modules (srfi srfi-1))
>
> (define (divisible-by? divident divisor)
> ~~(zero? (modulo divident divisor)))
>
> (define N 1000)
>
> (define ans
> ~~(fold (lambda (i res)
> ~~(if (or (divisible-by? i 3)
> ~~(divisible-by? i 5))
> (+ i res)
> res))
> 0
> (iota N)))
>
> Vale,
>
> -Tim
>
>
>
>