Skip to content

SRFI 160 port for Chez Scheme - #103

Merged
arcfide merged 6 commits into
arcfide:masterfrom
druimalban:srfi-160
Jun 22, 2026
Merged

SRFI 160 port for Chez Scheme#103
arcfide merged 6 commits into
arcfide:masterfrom
druimalban:srfi-160

Conversation

@druimalban

@druimalban druimalban commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

I have been developing this in a separate repository for the past couple of months: https://codeberg.org/dguthrie/srfi-160

This implements:
a) The SRFI 160 base library, equivalent to SRFI 4 with a couple of modifications.
b) The 8, 16, 32 and 64-bit unsigned integer vectors
c) The 8, 16, 32 and 64-bit signed integer vectors
d) The 32 and 64-bit floating-point vectors
e) The 64 and 128-bit complex number vectors

This is based on the following work:
a) Keep's SRFI 4 port. This is the basis of the %3a160/base library.
b) Some of the SRFI 160 reference implementation
c) One or two components of the SRFI 133 (vectors) implementation in this Chez-SRFI grab-bag which I didn't understand how to implement correctly myself.

My contributions are that I employ code generation using syntax-case macros (same approach as SRFI 4 in this repository), the code generation additionally reraises errors as expected for each representation instantiated, and finally I employ SRFI 252 property tests to make sure the entire library works as expected.

Specific questions:

  • Since this is quite a large commit, could I check that I've got the test portion correct, and that it's reasonable to include the additional library code in %3a160/meta? Since it's not part of the main library specifcation, I wonder if it would be better to add it in private.
  • The test suite is pretty large, and it generates a bunch of tests using SRFI 252 (property tests). It takes around 1 minute to run all 203756 tests on my laptop after these modules are compiled.
  • I'm also not sure about licensing - the reference implementation is MIT, and I think I've credited it correctly, but I'm not sure.
  • I use record-writer to get the lexical form in the REPL, it helps a lot for debugging. E.g. u8vectors print like #u8(1 2 3 4). I can remove this if this is inadvisable, it's defined here: https://github.com/druimalban/chez-srfi/blob/srfi-160/%253a160/base.sls#L655

Misc

Code generation

The reference implementation gets around some of the problems of all of the different numeric vector representations by using the @ character and having shell scripts substitute it for e.g. u8. This works up to a point but many of these procedures are most naturally implemented in terms of others (e.g. persistent/functional unfold is implemented with in-place unfold!). Further, the specification frequently uses optional start/end arguments and so the error handling is complex. Not catching the errors as expected leads to pretty baffling error-messages, e.g. "why is my call to s64vector-unfold erroring on s64vector-set!?"

Code generation is via a macro, define/curried. This is the most complex part of the implementation. The main problem when generating code is that we want the representation-specific forms, once defined, to raise errors with the expected who form. Additionally, it will reraise uncaught errors which originate from the representation-specific forms given with a modified who condition.

The idea is to define the shape of procedures which will generate representation-specific forms. Typically, we feed in the basic SRFI 4 forms like subscript/in-place update. This define/curried macro is actually defining another macro which performs the definition. It would be a lot less complex to produce procedures instead here, but it's too early, because we don't have the who form until invocation of this macro with the representation-specific procedures.

Macro usage

For example, a template for in-place unfold can be defined as follows:

(define/curried ((define-numeric-vector-unfold! len upd!) proc vec start end ini)
  "wrap SRFI 160 `@vector-unfold!' procedures"
  ;; [snip]
  (let loop ([i start] [seed ini])
    (when (< i end)
      (let-values ([(x next) (proc i seed)])
	(upd! vec i x)
	(loop (+ i 1) next)))))

In this example, the idea is that len and upd! are representation-specific, e.g. s64vector-length and s64vector-set!. The proc, vec, start, end and ini arguments are for the generated procedure, which might be s64vector-unfold!.

If we ran the above, we would define a macro define-numeric-vector-unfold!, which can then be used to in turn define the representation-specific @vector-unfold! procedures, e.g. for s64vector-unfold!, by feeding in the arguments for len and upd!:

(define-numeric-vector-unfold! s64vector-unfold!
   s64vector-length s64vector-set!)

Additionally:

  • The macro makes a special form who accessible within the generated procedure which is the symbol corresponding to the name above, so that assertions can be made at runtime, e.g. (assume (nonnegative-integer? start)) in the above example.
  • There is an additional, similar form define/curried-case which is similar to case-lambda, and which can additionally bind a special form like in SRFI 31 (A special form rec for recursive evaluation).

I have been developing this in a separate repository: https://codeberg.org/dguthrie/srfi-160

This implements:
a) The SRFI 160 base library, equivalent to SRFI 4 with a couple of modifications.
b) The 8, 16, 32 and 64-bit unsigned integer vectors
c) The 8, 16, 32 and 64-bit signed integer vectors
d) The 32 and 64-bit floating-point vectors
e) The 64 and 128-bit complex number vectors

This is based on the following work:
a) Keep's SRFI 4 port.
b) Some of the SRFI 160 reference implementation
c) One or two components of the SRFI 133 (vectors) implementation in this Chez-SRFI grab-bag

My contributions are that I use syntax-case to generate all of the representations (the approach taken by the SRFI 4 implementation of Keep seemed sensible), I employ code generation using a macro which reraises errors as expected, and finally I employ SRFI 252 property tests to make sure the entire library works as expected.

In terms of code generation:

The reference implementation gets around some of the problems of all of the different numeric vector representations by using the `@' character and having shell scripts substitute it for e.g. ``u8''. This works up to a point but many of these procedures are most naturally implemented in terms of others (e.g. persistent/functional ``unfold'' is implemented with in-place ``unfold!''). Further, the specification frequently uses optional start/end arguments and so the error handling is complex. Not catching the errors as expected leads to pretty baffling error-messages, e.g. ``why is my call to `s64vector-unfold' erroring on `s64vector-set!' ?''

Code generation is via a macro, `define/curried'. This is the most complex part of the implementation. The main problem when generating code is that we want the representation-specific forms, once defined, to raise errors with the expected `who' form. Additionally, it will reraise uncaught errors which originate from the representation-specific forms given with a modified `who' condition.

The idea is to define the shape of procedures which will generate representation-specific forms. Typically, we feed in the basic SRFI 4 forms like subscript/in-place update. This `define/curried' macro is actually defining another macro which performs the definition. It would be a lot less complex to produce procedures instead here, but it's too early, because we don't have the 'who' form until invocation of this macro with the representation-specific procedures.

Usage:

For example, a template for in-place unfold can be defined as follows:

(define/curried ((define-numeric-vector-unfold! len upd!) proc vec start end ini)
  "wrap SRFI 160 `@vector-unfold!' procedures"
  ;; [snip]
  (let loop ([i start] [seed ini])
    (when (< i end)
      (let-values ([(x next) (proc i seed)])
	(upd! vec i x)
	(loop (+ i 1) next)))))

This defines a macro `define-numeric-vector-unfold!', which can be used like so:

(define-numeric-vector-unfold! s64vector-unfold!
   s64vector-length s64vector-set!)

Now, we're able to access the name of the bound procedure, like ``s64vector-unfold!'' using the invocation of 'who'.

Additionally:
- The macro makes a special form `who' accessible within the generated procedure which is the symbol corresponding to the name above, so that assertions can be made at runtime, e.g. `(assume (nonnegative-integer? start))` in the above example.
- There is an additional, similar form `define/curried-case' which is similar to case-lambda, and which can additionally bind a special form like in SRFI 31 (A special form `rec' for recursive evaluation)
@arcfide

arcfide commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Other than the failing test suite, I am curious why there is so much code in meta. Given that there appears to be a more or less reference implementation of SRFI-160, why is this one so apparently different (and more complex to boot, based on a very quick skim)? What am I missing?

@druimalban

druimalban commented Jun 13, 2026

Copy link
Copy Markdown
Contributor Author

Ah, sorry! I didn't explain this very well.

The main problem with the SRFI 160 is it defines all of these representations and a large number of utility procedures. These are all structurally identical apart from the elements' numeric representation.

I could define SRFI 160 procedures in a similar style to this, and I tried this first:

(define make-my-map
  (lambda (my-fold my-cons nul)
    (lambda (proc lst)
      (my-fold (lambda (prev x)
                 (my-cons (proc x) prev))
               nul
               lst))))

(define list-map (make-my-map fold-left cons '()))

The error behaviour in this example would likely be raised first by my-cons, perhaps internally by my-fold. I could raise an error at runtime but I would at best be able to signal it with #f or make-my-map, when I would really like to access list-map. Additionally, if I put a guard clause in this template, the names of whatever my-fold and my-cons are not accessible at all.

Here, the representation is a linked list but I could imagine using this on other sequence structures which have an operation like cons.

An equivalent template is as follows:

(define/curried ((define-map my-fold my-cons nul) proc lst)
  (my-fold (lambda (prev x)
             (my-cons (proc x) prev))
           nul
           lst))

(define-map new-list-map fold-left cons '())

Now, if there was error raised with who matching the bound name of my-fold or my-cons (specifically a 'who' in a condition of 'fold-left or 'cons), it would be reraised with 'who' which define-map binds to, which is 'new-list-map here (other errors are reraised using raise-continuable). For SRFI 160 this works well because most of the procedures are structured in terms of iterating across the entire vector up its length, so it lets us catch the assertions made in the simpler procedures. All of the errors are now specific to the representation.

In terms of structure of the meta directory/library:
a) In meta/ the templates are structured in the sections of SRFI 160.
b) The define/curried macro is defined in meta/curried.sls. curried.sls also defines utility procedures which make specific assertions at runtime which are pervasive across SRFI 160
c) meta.sls at the top level is just a macro which invokes the templates to define the representation-specific procedures, e.g. u16.sls is just a call to that using parts of the base library.

@arcfide

arcfide commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Oh, also, why is there a patch to SRFI 235 in here?

@druimalban

Copy link
Copy Markdown
Contributor Author

I was trying to fix the test suite erroring in this commit, and since it was a one-line patch I put it here. Would it be better to force-push back to 4ef1de7 and commit that separately?

@arcfide

arcfide commented Jun 13, 2026 via email

Copy link
Copy Markdown
Owner

@arcfide

arcfide commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Just wanted to check that you know that the tests are currently failing for this, right?

@druimalban

druimalban commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

Thanks - I've been trying to reproduce the failure locally, I'll commit to the PR branch when I've got it working.

@druimalban

Copy link
Copy Markdown
Contributor Author

OK, I think that's it fixed.

I have one more set of changes which is an optimisation to use fixnum arithmetic everywhere. This is implicit in the construction of these vectors in the SRFI 4 / SRFI 160 base library.

Is tha appropriate to include in this pull request, or would it be better to commit that separately?

@arcfide

arcfide commented Jun 20, 2026

Copy link
Copy Markdown
Owner

I think since we haven't merged this yet, it's fine to go ahead and integrate the optimization into this PR.

@arcfide

arcfide commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Going through this in more detail, I'm also a little uncomfortable with a few things. The design and file structure seems quite a bit overly complex, and possibly "incorrect." I'm surprised that there is an exposed (srfi :160 meta) library or (srfi :160 test aux). I don't think these should be exposed interfaces to the SRFI. Moreover, the files in meta seem to be unnecessary, and I think it would be better to find a way to inline them or put them into a single meta file that isn't exposed, as it's an awful lot of code to expose like that. Testing shouldn't need to access those files.

@druimalban

Copy link
Copy Markdown
Contributor Author

It might be possible to inline it in the same way as Keep's SRFI 4, and if so, could also eliminate the curried template macro taht way. I'll see what I can come up with as the library structure is mostly because of phasing issues in the template macro-writing-macro.

@arcfide

arcfide commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Thanks for taking a look. I don't think things have to be so inlined that you don't have a separate file, but I do think it would at least be better to have meta as a single file instead of a bunch of smaller files in a directory.

There is already an implicit restriction to the fixnum range in the original SRFI 4
implementation, and the modifications I made to produce the SRFI 160 base library.
So make the restriction explicit.
@druimalban

Copy link
Copy Markdown
Contributor Author

That's the changes for the fixnum arithmetic for now. It strikes me as better to commitit before collating/inlining as it will be harder to separate these two changes

@druimalban

druimalban commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

This feels a bit better with respect to the meta directory. I inlined most of it into the macro in meta.sls, and this let me modify the macro so that it no longer writes a macro.

The modified definition macro is still set up so that who accessible in the procedure body. This seems to be necessary because with a regular define, there's no way to quote the bound procedure name within its body.

It seems necessary to provide a second utility library which is used by the macro invocation in meta.sls which defines these macros, as phasing prevents them from being defined in the same file. So the macros which were in meta/curried.sls are now in meta/utils.sls and I've eliminated the curried.sls.

The test suite still does the paramaterised macro-writing-macro thing as most of the test properties are boiler plate where the only varying thing about an error message being raised is a particular procedure. I have eliminated (test aux) altogether as well, which does seem cleaner.

@arcfide

arcfide commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Thanks, I think this is sufficient, and I appreciate the rework. If there's any way you can find to re-engineer this to not expose meta from :160 I would probably think that's more faithful to the SRFI, but having just meta and meta-utils is probably okay. If you want to work on it more, I'd accept another PR, but I'll go ahead and merge this for now.

@arcfide
arcfide merged commit 80256a9 into arcfide:master Jun 22, 2026
1 check passed
@druimalban

Copy link
Copy Markdown
Contributor Author

I'll have a think about it and see what I can come up with, it seems like it might be possible to make meta.sls a file which is just included in each of the upper directories. Most of the utilities could also probably be inlined now as well.

Thanks very much for reviewing and helping get that merged as it was a particularly large set of commits! I really appreciate it. I am still a little astonished at how large the SRFI 160 specification is(!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants