Annot+ : an annotation and inference type system for Scheme

Bonjour,

i present Annot+ an annotation and inference type system for scheme/racket/scheme+

CC BY-SA 3.0

I used a one pass method parsing the AST (abstract syntax tree) of a scheme program (racket module) taking apart annotation types declaration and using them to rewrite code and doing this recursively.
Naive idea ! things being very more complex in reality. You have to be very careful with the core of the algorithm.I did not used known theory (like Hindley Milner). I used my own method instead.

Annot+ is written itself in curly-infix/scheme+ using bootstrapping method.

lisplogo_warning_128 Public Domain Lisp Logo Set By Conrad Barski, M.D.

Here is the scheme+ source code of annot+.rkt :

Annot+ can speed up arithmetic operations in scheme,also with overloaded procedures or operators ,then as fast as if it was pure scheme code and does the same for [ ] indexed access in scheme+.

Annot+ dispatch overloaded procedure with their specialized one depending on argument type at parsing stage: the consequence is there is no more lost of time at runtime.

Here is a little example using the type annotation:

(type [(ℝ) procedure? ()] random) ; random is a procedure returning a racket/flonum? = ℝ

; return a random number between [inf, sup]
(define (uniform-interval inf sup)
  
  (type ℝ inf sup) ; inf and sup are some ℝ
  
  {gap ← sup - inf}
  {inf + gap * (random)})

the above annotated scheme+ code is parsed and the resulting scheme code is :

(define (uniform-interval inf sup)
    (← gap (fl- sup inf))
    (fl+ inf (fl* gap (random))))

The benchmarks talk by themselves:

program (same source) Scheme Scheme+ Scheme+ annotated Typed Racket gain
floats: Horner, sigmoid, 2·10⁷ 753 ms 777 ms 223 ms 226 ms 3.4 ×
matrices 200×200, 20 products 1065 ms 1066 ms 606 ms 578 ms 1.8 ×
integers: Syracuse up to 10⁶ 986 ms 985 ms 382 ms 402 / 1099 ms 2.6 ×
trapezoids, 2·10⁷ steps 359 ms 349 ms 68 ms 69 ms 5.3 ×

Type annotation is an option you can type only a portion of code when you need speed acceleration, you do not need to make a type annotation for all your variables or for all the code. (even in an ANNOTATION-ON module)

Another more complex example about a neural network retropropagation loop:

#reader SRFI-110 ; 105 ; curly-infix 
#!curly-infix

ANNOTATION-ON

(require Scheme)

(struct matrix-vect (v)) ;; matrix based on vector of vectors
; modify coefficients layer

;; define getter,setter
(define (matrix-vect-ref M lin col)
  (type vector? v {v[]}) ; a vector of vectors : v[lin][col] is specialized for vectors
  (type ℕ lin col)
  {v <- (matrix-vect-v M)}
  {v[lin][col]})

(define (matrix-vect-set! M lin col x)
  (type vector? v {v[]}) ; a vector of vectors : v[lin][col] is specialized for vectors
  (type ℕ lin col)
  {v <- (matrix-vect-v M)}
  {v[lin][col] <- x})

;; overload [ ] 
(overload-square-brackets matrix-vect-ref matrix-vect-set!  (matrix-vect? number? number?))

(define (modification_des_poids M_i_o η z_input z_output z̃_output ᐁ_i_o მzⳆმz̃) ; derivative of activation function of the layer

	  (type matrix-vect? M_i_o) ; M_i_o[j (i + 1)] calls matrix-vect-ref / matrix-vect-set! directly
	  ; M_i_o[j k] is a flonum by the return type in require-overload-square-brackets
	  (type ℝ η)
	  (type #(ℝ) ᐁ_i_o z_input z_output z̃_output)
	  (type ((ℝ) procedure? (ℝ ℝ)) მzⳆმz̃) ; the parameter მzⳆმz̃ hides the one of the class : its type is written here
	 
	  ; the length of output and input layer with coeff. used for bias update
	  {(len_layer_output len_layer_input_plus1forBias) <+ (dim-matrix-vect M_i_o)} ; use values and define-values to create bindings
        
	  {len_layer_input <- len_layer_input_plus1forBias - 1}

	  (type ℕ j)
	  (for ([j (range len_layer_output)]) ; line

		(type ℕ i)
		(for ([i (range len_layer_input)]) ; column , parcours les colonnes de la ligne sauf le bias
		   
		    {M_i_o[j (i + 1)]  <-  M_i_o[j (i + 1)] + η · z_input[i] · მzⳆმz̃(z_output[j] z̃_output[j]) · ᐁ_i_o[j]})
		   
		; and update the bias
            	
		{M_i_o[j 0]  <-  M_i_o[j 0] + η * 1.0 * მzⳆმz̃(z_output[j] z̃_output[j]) * ᐁ_i_o[j]}))
	

notes about the above example: it is not n but η (but η displays as n in code region over discourse)

the example is extracted from module files so do not expect it to work as is : what really works ! are the complete files in examples subdirectories of project.

On the above example of neural retropropagation activating (pragma ANNOTATION-ON) or disabling the annotation (pragma ANNOTATION-OFF) pass changes the speed in a factor of x7 : with type annotation and inference the program is 7 times faster.

Last note:

type annotation works also with nested composite types: the composite types #(T) (a vector) and (T) (a list) compose at any depth: the type of the elements T can itself be a composite type. Some examples of nested composite annotations:


(type #(#(ℝ)) M)            ; vector of vectors of floats (a matrix)
(type #(#(#(ℝ))) T)         ; vector of vectors of vectors of floats (a 3D array)
(type #(#(ℕ)) grid)         ; vector of vectors of natural fixnums
(type (#(ℤ)) Lv)            ; list of vectors of fixnums
(type #((ℝ)) VL)            ; vector of lists of floats
(type ((ℝ)) LL)             ; list of lists of floats
(type ((#(ℝ))) LLv)         ; list of lists of vectors of floats
(type #(#()) V2)            ; vector of vectors with untyped elements
(type #(flomat?) Ms)        ; vector of flomat matrices
(type (#(string?)) Ls)      ; list of vectors of strings

The documentation and packages can be find here:

Why annot name? because it is annotation and also for a mountain village near me :

CC BY-SA 3.0

update: source file