Gauche Devlog

2020/05/22

Next release will be 0.9.10

We haven't reached what we expected for 1.0, but we've got quite a few useful changes so we're gonna put 0.9.10 out. Some notable features to be included:

  • All libraries of R7RS-large Red and Tangerine edition. (Exact complex numbers aren't in yet.)
  • Immutable pairs are supported natively.
  • PEG parser library will be finally documented and ``offical''.
  • Native string cursor support, thanks to @pclouds.
  • Input editing feature is enhanced to include online key-binding help. Probably still early to make it default, but we'll probably add command-line switch to test it easier.

Tag: 0.9.10

2020/01/23

Remaining tasks for 1.0

In the last entry I listed a couple of items for 1.0. After that I remembered a few more, so I put them down here.

  • Enhancement of extended pairs. Currently we only use extended pairs to keep source information (source code location and the macro input). One big drawback of extended pairs is that it is costly to check if a given pair is extended (occupies 4 words) or not (occupies 2 words). Currently we need to look up the allocation region table to see the size of the object.
    If we can make the check faster, we can let modifiers (set-car! and set-cdr!) check if the pair has some special attributes (while cost of car and cdr stays the same.) It opens up a few interesting possibilities:
    • We can implement immutable pairs. Just raise an error in the modifiers.
    • We can implement typed lists, that is, a list whose elements are guaranteed to have certain type. A constructor, say, (typed-cons type car cdr) can check if car is of type and cdr is of type List type, and modifiers can check the type constraint.
  • Integrate pattern matcher into core. Most of my code now impors util.match. It's so fundamental and I think it should be supported as built-in.
    • An interesting possibility is to support pattern match in formal arguments by default.

Depending on how quick I can work on them, we might have another 0.9.x release.

Tag: 1.0

2019/12/14

0.9.9 is out

Release notes.

What's left for 1.0?

The goal of 0.9.x releases has been to stabilize the binary API, so that after 1.0 release we can be sure that extension libraries won't break during 1.0.x series.

By now, we feel API is pretty stable; just want to make sure the exposed structure has enough information so that it won't hinder future development of various runtime analyzing tools such as debuggers.

Other than that, the items on the table so far are:

  • Full support of R7RS-Large Red and Tangerine Edition
    • This includes support of exact complex numbers
  • Tweaking continuation frame handling. We expect it to open up several pending features, such as the issue of lost stack trace ( https://github.com/shirok/Gauche/issues/521 ), support of continuation marks (srfi-157), integration of stack trace and call trace, and better inspector-debugger.

Well, it doesn't seem a lot, but you never know.

So we hope the next release to be 1.0, but it can be 0.9.10 if we're sidetracked by some unforeseen issues. Let's see.

Tag: 0.9.9

2019/12/08

Definition is now a compile-time construct

As of 0.9.9, toplevel definitions insert bindings to the current module at compile time. Of course the value of the binding isn't known at compile time, so first the variable is marked as uninitialized. At runtime, the actual value is calculated and bound to the variable.

This is for consistent behaivor with modern module systems (the issue https://github.com/shirok/Gauche/issues/549 was the trigger of this change). Basically, defines in the same toplevel must first make those names in the same scope, then proceed to calculate the values. R6RS is clear about it, while R7RS allows implementations some leeway.

If you happen to use the value of the variable before it is bound to actual value, you'll get an error saying the variable is not initialized.

This doesn't make any difference if each definition is a toplevel form by its own. However, if multiple toplevel definitions are enclosed in a form such as begin, define-module or define-library, you'll see the difference.


This may affect an idiom, once popular until R5RS era:

(define orig-error error)
(define (error . args)
  (write args) (newline)
  (apply orig-error args))

The intention is to save the original error procedure in orig-error, then redefines error to show the arguments then calls the original error.

In R5RS where there's one toplevel, the (define (error ...) ...) is understood as reassignment to the original variable, so it works.

However, since R6RS, we have multiple toplevels as separate lexical scopes, and we have ambiguity.

(import (scheme base)      ; imports 'error' binding (in R6RS, its (rnrs))
        (scheme write))
(define orig-error error)  ; which 'error' should we refer?
(define (error . args)
  (write args) (newline)
  (apply orig-error args))

With the lexical scoping rule (toplevel definitions are treated as if in letrec* bindings; see R6RS section 10, for example), the error in the second line must refer to the error defined in the third line. And it's a violation to take a value of a variable that hasn't been calculated, hence the code above is invalid. In R7RS, it's implementation dependent.

Actually, R6RS also prohibits defining a toplevel variable that conflicts with imported names, so the above code can't work in that sense, too. In R7RS, it's implementation dependent so portable code can't do that.

The proper way is to use renaming import:

(import (except (scheme base) error)
        (rename (scheme base) (error r7rs:error))
        (scheme write))
(define (error . args)
  (write args) (newline)
  (apply r7rs:error args))

Now, Gauche has been rather permissive to this kind of implementation-dependent behaviors. And the above orig-error example still works if the file is loaded---in which case, Gauche processes each toplevel form one by one, so when it sees (define orig-error error) it doesn't know yet if error would be defined in this scope or not. So it refers to the imported error.

However, if the file is included (which effectively wraps all the forms by begin), or you write similar code within define-library or define-module, all the definitions are compiled at once, and then executed. In that case you'll see "uninitialized variable" error in 0.9.9.

We strongly recommend to avoid such ambiguous code. However, in case if you're using existing code that happens to rely on the old behavior, you can switch back to the old behavior by defining an enviornment variable GAUCHE_LEGACY_DEFINE.

Tags: 0.9.9, toplevel

2019/07/22

Nasty undefined

tl;dr - We discourage using #<undef> where generalized boolean is expected. Defining GAUCHE_CHECK_UNDEFINED_TEST environment variable turns on the warning.


The other day I was shaving yak, and encountered a nasty tick in the wool. Its name is undefined.

Sometimes, a Scheme expression is defined to be evaluated to an unspecified value. It means it can be any value, at the discretion of the implementation. However, if an ordinary looking value such as #f is returned, the users may depend on it. Such code has a hazard that it breaks when ported to other implementation.

Partly because of that, many implementations choose to have a special value returned from an expression whose return value is not specified. In Gauche, we use an undefined value, or #<undef> when printed. It is just a placeholder indicating the value doesn't (and shoudn't) matter.

A few days ago I was tweaking a precompiler and made a trivial change in the generation of .sci (interface) file. The change broke the precompiler with no obvious cause. I reverted the change and started gradually introducing the new code step by step. The breakage was reproducible, but the reason was incomprehensible. For example, emitting a newline to the file or not made the diffence.

It took a few hours, plus one night sleep, to finally identify the cause. The closure I was touching had called a procedure at the tail position that returned #<undef>. So I assumed its return value didn't matter. I changed the closure in a way that it retuned #f.

However, in other part of the code, the closure was called as something like this:

(or (and (some-condition) 
         (the-closure) 
         #t)
    (some-other-action))

Since #<undef> counts as a true value, it used to execute only the first arm of or. After the change (the-closure) returned #f, so (some-other-action) was also executed.

D'oh.

If #<undef> implies the value doesn't matter, we shouldn't rely on it being counted as true. Any code that tests the return value of the procedure whose value is undefined in the conditional has a time bomb.

Upon this realization, I added a check in Gauche VM to warn when #<undef> appears in the context of boolean test in the conditionals. Then...

I found a lot of such cases in Gauche code itself. Typical pitfall is and-let* forms in which one of the test expression yields #<undef>. And sometimes they were hard to track down, for the source of #<undef> may not always be obvious. (It was one of relatively uncommon occasions when I did miss static typing.)

The warnings can be very bothering, so it isn't turned on by default. You can turn it on by defining the environment variable GAUCHE_CHECK_UNDEFINED_TEST. This is an experimental feature and I might change my mind later, but the current plan is to enable it during unit tests eventually.

Note: There are occasions that #<undef> is used intentionally; one of them is to use it as a placeholder indicating the value isn't given. Such usage is not desirable but tolerated, and in general those values are checked with undefined?. Gauche only warns when #<undef> appears as the result of the test expression of conditionals.

Tags: undefined, 0.9.9

More entries ...