Type checking and error handling in Gerbil.
While writing https://github.com/vyzo/gerbil/pull/297 I wondered how to approach type checking in Gerbil and Scheme in general. This issue should serve to organize discussion around this topic until it becomes official documentation, if ever.
How do we implement type checking and error handling in gerbil?
Philosophically, I think it is important that we settle on an official solution in order to avoid repetition as well as facilitate library usage. I will expect a third-party library author to use Gerbil's type checking/error handling and not a roll-your-own. However I also expect to be able to use my own checking/handling in my own code if I want to.
Technically, how do we do it? Can the checks be turned off? Are some checks mandatory? At what level are errors raised and types checked?
After reading about the R6/R7RS debate, I now realize this is an (unresolved) issue about Scheme itself.
I still think it would be beneficial to have at least a guideline as to how we want errors to be managed. Do we want a global list of error messages or do we leave complete control to the implementor? For example I would like to have reasonable expectations about what errors mean if I am to use 3rd party libs.
If you're going to Do The Right Thing, you may want to study the latest works by racketeers on how to properly combine types and macros. e.g. "Type Systems as Macros" by Stephen Chang, Alex Knauth, and Ben Greenman http://www.ccs.neu.edu/home/stchang/pubs/ckg-popl2017.pdf
Thanks for the ref. I am currently using a simple macro that parses arguments of the form arg:type and adds a guard to the function. From a quick glance I don't think I have the chops to implement the "Right Thing" but I will (try to) go through that paper.
Maybe "our" Right Thing is to, in the end, do nothing. I'm still not sure.
Here is the very very naïve implementation of function guards I mentioned. The guard is simply there to catch bad argument types earlier, at runtime. During execution, your function could execute for some time before actually raising an error on a wrong argument type. This merely blocks execution from proceeding if the types are wrong.
(defsyntax (pred-lambda stx)
(def (split-parg stx)
(datum->syntax stx
(stx-map string->symbol
(string-split (symbol->string (stx-e stx)) #\:))))
;; same form as in gerbil/expander/stx.ss
(def (stx-cadr stx)
(declare (safe))
(cadr (stx-e stx)))
(syntax-case stx ()
((macro pargs body ...)
(identifier-list? #'pargs)
(with-syntax* ((split (stx-map split-parg #'pargs))
(args (stx-map stx-car #'split))
(preds (stx-map stx-cadr #'split))
(clauses (cons 'and (stx-map (lambda (x y) [y x]) #'args #'preds))))
#'(lambda args
(unless clauses
(error "Arguments do not respect signature."))
body ...)))))
(defrules defp ()
((_ (id . args) body ...)
(identifier? #'id)
(define-values (id)
(pred-lambda args body ...)))
((_ id expr)
(identifier? #'id)
(define-values (id) expr)))
(defp (func1 a:integer? b:integer?)
(let ((x 0))
(+ a b x)))
;; > (func1 1 2)
;; 3
;; > (func1 1 "a")
;; *** ERROR IN func1 -- Arguments do not respect signature.
;; 1>,d
;; > (+ 1 "a" 0)
;; *** ERROR IN (console)@4.1 -- (Argument 2) NUMBER expected
;; (+ 1 "a" 0)
We will have something that resolves these issues for v0.19 with the contracts work.