On Sat, 2009-04-18 at 11:50 +0300, Abdulaziz Ghuloum wrote:
On Apr 18, 2009, at 11:33 AM, Michele Simionato wrote:
> 
> > So there is a different behavior. Who is right?
> 
> Both!  R6RS does not say anything about how many times an imported  
> library is visited/invoked.  It can be 0, 1, or more for reason or no  
> reason.
> 
> Here's something that you may find interesting.
> 
> (library (T0) (export) (import (rnrs)) (display "T0\n"))
> 
> (library (T1) (export) (import (for (T0) run expand)))
> 
> (library (T2) (export) (import (for (T1) run expand)))
> 
> (library (T3) (export) (import (for (T2) run expand)))
> 
> #!r6rs
> (import (T3))
> 
> How many times does T0 get printed under Ikarus, Ypsilon, Larceny,  
> and PLT when you run that script?  0, 1, and more!

I'd like to show how to run this example on the different systems, and
then make some comments.  Please correct me if I say anything wrong.

[...@eep:/tmp]-> cat import-Ts.sps 
#!r6rs
(import (T3))
[...@eep:/tmp]-> for T in T*.sls ; do echo ";;;; $T ;;;;" ; cat $T ; done
;;;; T0.sls ;;;;
#!r6rs
(library (T0) (export) (import (rnrs)) (display "T0\n"))
;;;; T1.sls ;;;;
#!r6rs
(library (T1) (export) (import (for (T0) run expand)))
;;;; T2.sls ;;;;
#!r6rs
(library (T2) (export) (import (for (T1) run expand)))
;;;; T3.sls ;;;;
#!r6rs
(library (T3) (export) (import (for (T2) run expand)))
[...@eep:/tmp]-> 
[...@eep:/tmp]-> ls -l T*/    # these are needed for PLT
T0/:
total 0
lrwxrwxrwx 1 d d 9 2009-04-18 17:03 main.sls -> ../T0.sls

T1/:
total 0
lrwxrwxrwx 1 d d 9 2009-04-18 17:03 main.sls -> ../T1.sls

T2/:
total 0
lrwxrwxrwx 1 d d 9 2009-04-18 17:03 main.sls -> ../T2.sls

T3/:
total 0
lrwxrwxrwx 1 d d 9 2009-04-18 17:03 main.sls -> ../T3.sls
[...@eep:/tmp]-> 
[...@eep:/tmp]-> mzscheme -S . import-Ts.sps 
T0
T0
T0
T0
T0
T0
T0
T0
T0
T0
T0
T0
T0
[...@eep:/tmp]-> ypsilon --sitelib . import-Ts.sps 
T0
[...@eep:/tmp]-> larceny -path . -r6rs -program import-Ts.sps 
T0
[...@eep:/tmp]-> ikarus --r6rs-script import-Ts.sps 
[...@eep:/tmp]-> 

What this demonstrates is (according to my understanding):

- PLT (mzscheme) makes a new instance of a library (T0 in this example)
every time it processes an import of it, and then some.  I understand
why eight of these instantiations happen, but I have no idea why the
other five happen.  The eight instantiations happen because the
program/script imports T3 for one phase level ("run", implicitly), T3
imports T2 for two phase levels ("run" and "expand"), T2 imports T1 for
two phase levels, and T1 imports T0 for two phase levels.  Under the
multiple-instantiation semantics which PLT uniquely has, a library is
freshly instantiated for each phase it's used for.  T1 imports T0 for
two phases and so T0 is instantiated twice and so the message is
displayed twice, for every time T1 is instantiated; T2 imports T1 for
two phases and so T1 is instantiated twice which means T0 is
instantiated four times, for every time T2 is instantiated.  T3 imports
T2 for two phases and so T2 is instantiated twice which means T0 is
instantiated eight times, for every time T3 is instantiated.  The
program/script imports T3 for one phase and so T3 is instantiated once
and hence the eight instantiations of T0 which I understand.  Why the
other five happened I have no idea, and because I have no idea, I'd be
even more discouraged from considering trying to utilize PLT's
multiple-instantiation semantics, even if I was okay with using library
instantiation to accomplish visible side-effects (which I'm not because
I think it's anti-functional and too unpredictable because it's too
complicated).

- Ypsilon and Larceny have single-instantiation semantics, and they make
a single instance of a library whether or not an instance is actually
needed, and that's why in the example they display T0 once.

- Ikarus has single-instantiation semantics, and it instantiates a
library only when it's needed, and that's why in the example above it
displays nothing.

A very important thing to understand is that the instantiation behavior
shown above is happening because the libraries are being expanded and
run in the same Scheme-system instance/OS-process.  Separately compiling
libraries requires expanding them which requires instantiating any of
their "for expand" (or higher) phase level dependencies because the
things of those dependencies are required to make and/or run the macros
needed to expand the libraries being compiled.  So separately compiling
and then running can cause different time, sequence, and/or number of
instantiations than compiling and running in the same process.  For
example:

[...@eep:/tmp/t0]-> cat use-Ls.sps 
#!r6rs
(import (rnrs) (L0))
(display "Running program: ") (write (list x y)) (newline)
[...@eep:/tmp/t0]-> 
[...@eep:/tmp/t0]-> for L in L*.sls ; do echo ";;;; $L ;;;;" ; cat $L ; done
;;;; L0.sls ;;;;
#!r6rs
(library (L0)
  (export
    x y)
  (import
    (rnrs)
    (for (L1) expand))

  (define-syntax m0 (make-macro-transformer))
  (define x (m0))
  (define y (m0))
  (display "Invoked L0\n")
)
;;;; L1.sls ;;;;
#!r6rs
(library (L1)
  (export
    make-macro-transformer)
  (import
    (rnrs)
    (for (only (rnrs) begin display) (meta -1)))

  (define (make-macro-transformer)
    (display "Making macro transformer\n")
    (lambda (stx)
      (display "Running macro transformer\n")
      (syntax
       (begin
         (display "Running macro's output source-code\n")
         1))))

  (display "Invoked L1\n")
)
[...@eep:/tmp/t0]-> 
[...@eep:/tmp/t0]-> ls -l L*/    # these are needed for PLT
L0/:
total 0
lrwxrwxrwx 1 d d 9 2009-04-18 17:39 main.sls -> ../L0.sls

L1/:
total 0
lrwxrwxrwx 1 d d 9 2009-04-18 17:39 main.sls -> ../L1.sls
[...@eep:/tmp/t0]-> 

Not separately compiling:

[...@eep:/tmp/t0]-> plt-r6rs ++path . use-Ls.sps 
Invoked L1
Making macro transformer
Running macro transformer
Running macro transformer
Invoked L1
Making macro transformer
Running macro's output source-code
Running macro's output source-code
Invoked L0
Running program: (1 1)
[...@eep:/tmp/t0]-> ypsilon --sitelib . use-Ls.sps 
Invoked L1
Making macro transformer
Running macro transformer
Running macro transformer
Running macro's output source-code
Running macro's output source-code
Invoked L0
Making macro transformer
Running program: (1 1)
[...@eep:/tmp/t0]-> larceny -path . -r6rs -program use-Ls.sps 
Invoked L1
Making macro transformer
Running macro transformer
Running macro transformer
Making macro transformer
Running macro's output source-code
Running macro's output source-code
Invoked L0
Running program: (1 1)
[...@eep:/tmp/t0]-> ikarus --r6rs-script use-Ls.sps
Invoked L1
Making macro transformer
Running macro transformer
Running macro transformer
Running macro's output source-code
Running macro's output source-code
Invoked L0
Running program: (1 1)
[...@eep:/tmp/t0]-> 

Separately compiling and then running:

[...@eep:/tmp/t0]-> plt-r6rs ++path . --compile use-Ls.sps 
 [Compiling /tmp/t0/use-Ls.sps]
 [Compiling /tmp/t0/L0/main.sls]
 [Compiling /tmp/t0/L1/main.sls]
Invoked L1
Making macro transformer
Running macro transformer
Running macro transformer
Invoked L1
Making macro transformer
[...@eep:/tmp/t0]-> plt-r6rs ++path . use-Ls.sps 
Running macro's output source-code
Running macro's output source-code
Invoked L0
Running program: (1 1)
[...@eep:/tmp/t0]-> 

[...@eep:/tmp/t0]-> rm -v -f ~/.ypsilon/*
removed `/home/d/.ypsilon/L0.cache'
removed `/home/d/.ypsilon/L0.cache.time'
removed `/home/d/.ypsilon/L1.cache'
removed `/home/d/.ypsilon/L1.cache.time'
[...@eep:/tmp/t0]-> ypsilon -v --sitelib .
Ypsilon 0.9.6-trunk/r418 Copyright (c) 2009 Y.Fujita, LittleWing Company 
Limited.
;; YPSILON_ACC unspecified
;; YPSILON_SITELIB=/home/d/zone/scheme
;; YPSILON_LOADPATH unspecified
;; (auto-compile-cache) => "/home/d/.ypsilon"
;; (scheme-library-paths) => ("/tmp/t0" "/home/d/zone/scheme" 
"/home/d/share/ypsilon/sitelib")
;; (scheme-load-paths) => ()
> (import (L0))  ;; just to compile the libraries
;; compile "/tmp/t0/L0.sls"
;; compile "/tmp/t0/L1.sls"
Invoked L1
Making macro transformer
Running macro transformer
Running macro transformer
Running macro's output source-code
Running macro's output source-code
Invoked L0
Making macro transformer
> [...@eep:/tmp/t0]-> ypsilon --sitelib . use-Ls.sps 
Invoked L1
Running macro's output source-code
Running macro's output source-code
Invoked L0
Making macro transformer
Running program: (1 1)
[...@eep:/tmp/t0]-> 

[...@eep:/tmp/t0]-> rm -f L0 L1
[...@eep:/tmp/t0]-> larceny -path . -err5rs
Larceny v0.97a4 (alpha test) (Apr 12 2009 02:13:46, precise:Linux:unified)
larceny.heap, built on Sun Apr 12 02:15:42 PDT 2009
ERR5RS mode (no libraries have been imported)

> (import (rnrs) (larceny compiler))
Autoloading (rnrs)
Autoloading (larceny compiler)
Autoloading (err5rs load)
Autoloading (rnrs load)
Autoloading (rnrs enums)
Autoloading (rnrs lists)
Autoloading (rnrs syntax-case)
Autoloading (rnrs conditions)
Autoloading (err5rs records procedural)
Autoloading (rnrs exceptions)
Autoloading (rnrs hashtables)
Autoloading (rnrs arithmetic bitwise)
Autoloading (rnrs programs)
Autoloading (rnrs files)
Autoloading (rnrs io ports)
Autoloading (larceny deprecated)
Autoloading (rnrs records syntactic)
Autoloading (rnrs records procedural)
Autoloading (rnrs control)
Autoloading (rnrs sorting)
Autoloading (rnrs bytevectors)
Autoloading (rnrs unicode)

> (compile-stale-libraries)
Compiling L0.sls
Autoloading (L1)
Compiling /tmp/t0/./L1.sls
Invoked L1
Making macro transformer
Running macro transformer
Running macro transformer

> 
[...@eep:/tmp/t0]-> larceny -path . -r6rs -program use-Ls.sps 
Invoked L1
Making macro transformer
Running macro's output source-code
Running macro's output source-code
Invoked L0
Running program: (1 1)
[...@eep:/tmp/t0]-> 

[...@eep:/tmp/t0]-> ikarus --compile-dependencies use-Ls.sps 
Invoked L1
Making macro transformer
Running macro transformer
Running macro transformer
Serializing "./L0.sls.ikarus-fasl" ...
Serializing "./L1.sls.ikarus-fasl" ...
[...@eep:/tmp/t0]-> ikarus --r6rs-script use-Ls.sps 
Running macro's output source-code
Running macro's output source-code
Invoked L0
Running program: (1 1)
[...@eep:/tmp/t0]-> 

Note how many differences there are between what is executed and when!

So, because of all this complication about time and number of
instantiation, which exists at least to some extent for every one of
these R6RS implementations, the only library instantiation side-effects
I feel I can ever hope to have a chance of managing are those used for
initializing library-private data, the initialization of which has no
visible side-effects and is not dependent on the time or number of
instantiations.  This is the usual rationale for adhering to functional
techniques -- side-effects just get too complicated too fast to use in
any but the most encapsulated and controlled ways.  I imagine Ikarus's
only-when-needed single-instantiation semantics is based on this
reasoning.  Yes, if every detail of the behavior was nailed down it
would be more predictable, but then implementations would be much more
restricted in what they can do to cater to different markets of users
and to what extent they can leverage their investment in past design
decisions.

I like Ikarus's only-when-needed semantics because it seems better for
supporting libraries which grow to have a ton of dependencies because
they liberally use a thing from here and a thing from there (as they
should be able to, IMO) and those dependencies have a lot of
dependencies which aren't actually used by the things a particular
program uses and so those transitive dependencies don't need to be
instantiated because they're not actually used.  And I like it because I
favor functional programming techniques.

On Sat, 2009-04-18 at 13:09 +0200, Michele Simionato wrote:
> Indeed. I think a comprehensive document is beyond my forces (also because
> R6RS implementations are a moving target) but at least I can document the
> difficulties I encountered when porting a library involving nontrivial macros
> across different implementations. I can give some advice about what is 
> portable
> and what is not, describe the differences between implementations with phase
> separation and without, discuss some issues with side effects and
> other such things.

The portable intersection of this library instantiation issue is the
no-visible-side-effects convention.  I think that's all you should write
about this issue in your series, so that you don't overwhelm the
readers.  Because Pythonistas are used to Python's
module-bodies-are-always-executed, I'd tell them about how the
no-visible-side-effects convention follows functional language ideals,
and tell them they need to manually do their own initialization
visible-side-effects if they need to, maybe by doing it first thing in
the program start-up, and this way it's sure to happen at the right time
and in the right order, which is what I'd probably always do anyways
even if the library instantiation was predictable because this way it's
clear where and in what order visible side-effects are happening,
otherwise, even if it was predictable, you're depending on the unique
transitive order of instantiations, which would not be the same between
different programs with different imports, and so visible library
instantiation side-effects would still not be reliable for supporting
general purpose arbitrary composition with unknown other libraries,
because what if the order of the (unforeseen) combined side-effects must
be a certain way? -- you won't be able to control it.

(Again, please correct me if I've said anything wrong.)

-- 
: Derick
----------------------------------------------------------------


Reply via email to