Email list hosting service & mailing list manager

Re: SRFI for ports from arbitrary sources or to arbitrary sinks? Alaric Snell-Pym (24 Sep 2019 08:38 UTC)

Re: SRFI for ports from arbitrary sources or to arbitrary sinks? Alaric Snell-Pym 24 Sep 2019 08:38 UTC
On 20/09/2019 18:04, John Cowan wrote:

> But what do we do in Scheme when there's a mismatch between what the SPI
> expects and what the plugin provides?  There is no formal definition of an
> SPI (or any API, for that matter): we cannot even introspect on libraries
> to see what they export.  Ports are particularly bad in this respect,
> because what operations exist may depend on what kind of port this is.
> Directory ports in Gambit, for example, are like any other input ports,
> except that read returns the next directory entry (doesn't parse
> S-expressions at all) and read-char, read-string, read-line just don't
> work.  Furthermore, if the client API changes, all the plugins break
> horribly at run time instead of (as in Java) failing to compile.
>
> What to do, what to do?  I wish I knew.
>

I imagine something like this:

1. In the SPI library (eg, our DBI) define a record type full of
closures to represent a plugin (eg, our DBDs), with all the plugin's.

2. The SPI library hides that record type's constructor and accessors,
but exports procedures to invoke the procedures within it, passing in a
private data field from the struct as well as the arguments, which is
what users of the SPI call:

(define (do-thing db x y z)
  ((db-thing-doer db)
   (db-private-data db)
   x y z))

3. The SPI library also provides constructors for plugins - some
combination of different constructors for different levels of
functionality and being able to pass in `'#f` for a procedure slot to
request default behaviour. Later versions of the SPI will add more
constructors to enable more advanced functionality, without changing the
names of existing ones, so "old" plugins will still work in some
degraded form:

(define (make-basic-plugin-instance-v1 private-data thing-doer
                                                    thang-doer)
   (make-plugin-instance
     private-data
     thing-doer thang-doer
     (lambda (x) (error "This instance cannot thung"))
     (lambda (x) (error "This instance cannot thong"))))

(define (make-full-plugin-instance-v1 private-data thing-doer
                                                   thang-doer
                                                   thung-doer)
   (make-plugin-instance
     private-data
     thing-doer thang-doer thung-doer
     (lambda (x) (error "This instance cannot thong"))))

(define (make-full-plugin-instance-v2 private-data thing-doer
                                                   thang-doer
                                                   thung-doer
                                                   thong-doer)
   (make-plugin-instance
     private-data
     thing-doer thang-doer thung-doer thong-doer))

The "default behaviours" might be to raise an error in this case, or
something smarter (perhaps a thong operation can be implemented,
inefficiently, as a series of thing and thang operations by default?)

4. Plugins provide constructors that do whatever state construction they
need and then call an appropriate make-X-plugin-instance-vY to get a
valid plugin instance object. Those constructors are either exported
from the DBD module for applications to call directly, or the module
body calls a "register" interface in the DBI library to register that
constructor under a symbol (I can't remember what we decided about
module import side-effects in R7RS)

So applications would either do:

(import database)
(import sqlite)

(define db (connect ...)) ;; connect from sqlite

(query db ...) ;; query from database

or:

(import database)
(import sqlite) ;; here just to run the module body, no exported
                ;; bindings used

(define db (connect 'sqlite ...)) ;; connect from database

Is that a reasonably Schemely approach?

--
Alaric Snell-Pym   (M7KIT)
http://www.snell-pym.org.uk/alaric/