Gauche Devlog

2023/09/30

Pipeworks

Ports are very handy abstraction of data source and sink. In Gauche libraries, you can find many utitlies that reads from input port or writes to output port, and then another utilities (e.g. convert from/to string) are built on top of them.

While they are useful, it becomes tricky when you want to compose those utilities. Suppose you have a procedure f that writes to an output port, and a procedure g that read from an input port. You want to feed the output of f to g while make f and g run concurrently, so some threading is involved. You can write such a pipe using procedural ports but it is cumbersome to do so for every occasion. I want something that's as easy as Unix pipe.

So I initially started to writing a pipe utility using procedural ports. Then I realised I also want a device dual to it; while a pipe flows data from an output port to an input port, the co-pipe, or pump, pulls data from an input port and push it to an output port. An example is that you run a subprocess and feed its error output to your current output port. When you invoke a subprocess (ref:gauche.process), you can get its error output from an input port. So you need to read it actively and feed the data to your current output port.

Then you might want to peek the error output to find out a specific error message appears. So your contraption reads actively an input port, and feed the data to an output port, and you can read whatever data flows through it from another input port to monitor.

There are many variations, and mulling over it for some time, I wrote a library that abstracts any of such configurations. I call the device plumbing (draft:control.plumbing).

You can also create an output port that feeds the data to multiple outputs, or gather multiple input port into one input port. Refer to the manual to see what you can do.

Tags: 0.9.13, control.plumbing

2023/09/29

Real numerical functions

Scheme devines a set of elementary functions that can handle complex numbers. In Gauche, complex elementary functions is built on top of real domain functions. Up to 0.9.12, we had real-only version with the name such as %sin or %exp. As the percent prefix suggests, they are not meant to be used directly; sin ro exp are built on top of them.

However, sometimes you want to use real-only versions to avoid overhead of type testing and dispatching complex numbers. srfi:94 defines real-domain functions, so we decided to adapt them. Now you have real-sin, real-exp etc. (draft:real-exp) as built-in.

Note that scheme.flonum also provides "flonum-only" version of elementary functions, e.g. flsin (ref:scheme.flonum). They won't even accept exact numbers. Since it is in R7RS-large, you may want to use them for portable code.

Although the names %sin etc. are undocumented and not meant to be directly used, they were visible by default, so some existing code are relying on it. It needs some effort to rewrite all occurrences of such functions with the new real-sin etc, so we provide a compatibility module, compat.real-elementary-functions. Just using it in your code provides compatibility names. If you want to make your code work on both 0.9.12 and 0.9.13, you can use cond-expand:

(cond-expand
  ((library compat.real-elementary-functions)
   (use compat.real-elementary-functions))
  (else))

Tags: 0.9.13, NumericFunctions

2023/09/28

Pretty print indentation

Yet another small thing good to have. You can now specify base indentation to the pretty printer (ref:pprint). It is applied to the second line and after.

gosh> (pprint (make-list 100 'abc) :indent 20)
(abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc)
#<undef>

To say more precisely, when the pretty printer spills data to another line, it inserts "a newline + whitespace * indent", plus any indent computed by the pretty printer.

The benefit easiest to see is when the pretty printer is used inside format. When a pretty printing triggered by the ~:w directive, it sets the base indentation at the column it starts printing. Hence the entire pretty print is indented to align nicely:

gosh> (format #t "Long list: ~:w\n" (make-list 100 'abc))
Long list: (abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc
            abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc
            abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc
            abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc
            abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc
            abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc
            abc abc)
#<undef>

Since pretty printing is built-in to the core printer (pprint is just a simple interface to use), other output routines such as write can also use base indentation. You can set indent slot of a write-controls.

gosh> (write (make-list 100 'abc) (make-write-controls :pretty #t :indent 20 :width 79))
(abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc abc abc abc abc abc abc abc abc abc abc abc abc
                     abc abc)#<undef>

Tags: 0.9.13, pretty-printing

2023/09/26

Segmented completion

Another little something on REPL. It can now complete symbols like call-with-current-continuation from c-w-c-c. This is an old tradition of Lisp environment.

[image]

We added a new module text.segmented-match (draft:text.segmented-match) to support this.

Tags: 0.9.13, REPL

2023/09/26

Hints for unbound variable error

While working on REPL, sometimes you accidentally try to evaluate a variable that isn't visible from your current module. It is a bit annoying if you know the module is loaded, just you forget to use it in the current module.

So we added a little feature. When REPL reports an unbound varriable error, it also lists if there are variables of that name, exported from modules that are loaded into the process but not visible from the evaluating module:

gosh$ (thread-start! (make-thread (^[] (print "Hi"))))
*** UNBOUND-VARIABLE-ERROR: unbound variable: make-thread
    NOTE: `make-thread' is exported from the following module:
     - gauche.threads
Stack Trace:
_______________________________________
  0  (report-error e)
  1  (make-thread (^ () (print "Hi")))
        at "(input string port)":1

It may be nice to show modules that aren't even loaded, too, but that would be too costly so we avoided it. It also doesn't show non-exported variables, which is debatable--sometimes you forgot to export one and that caused this error. Let's use this for a while and see if we need non-exported ones, too.


This is realized in a general mechanism in error reporting. We haven't documented it yet, for we may tweak the interface, but I'll show it to give the general idea.

The error message in REPL, including the stack trace, is produced by report-error (ref:report-error). It prints *** ... line, with the condition class name and error message, then calls a generic function report-additional-condition on the condition. We have a specialized method for <unbound-variable-error> which searches the name in the loaded modules and prints the hint.

If the thrown condition is a compound condition, report-additional-condition is called over each component of the compound condition. This allows custom report for each component. When you load a file that has a statically detectable error, you get the additional information (While compiling ...). It is also realized by the same mechanism. The compiler and the loader adds the location information as a compound condition, and report-error calls report-additional-condition on them, which shows those additional messages.

gosh> ,l ./foo
*** ERROR: wrong number of arguments: cons requires 2, but got 1
    While compiling "./foo.scm" at line 1: (define (bar x) (cons x))
    While loading "./foo.scm" at line 2
Stack Trace:
_______________________________________
  0  (report-error e)
  1  (errorf "wrong number of arguments: ~a requires ~a, but got ~"...
  2  (pass1/expand-inliner program id gval cenv)

Tags: 0.9.13, REPL, report-error

More entries ...