GNU Artanis

Table of Contents

Introduction

logo.png

GNU Artanis aims to be a fast monolithic web application framework for Scheme. The philosophy of Artanis is to be very radical, and to try cutting-edge things. So use it at your own risk…however, playing with it may result in some cool experiences!

Certificated Awesome Project at 2013 Lisp in summer projects

That is no future for mediocre coder. The hacker is one another’s arm. Codes in the editors. Those dying generations - at their song.

Sailing to /dev/null

Usage

(use-modules (artanis artanis))
(init-server)
(get "/hello"(lambda () "hello world"))

;; run it
(run #:port 8080)
curl localhost:8080/hello
==> hello world

Features

  • GPLv3+ & LGPLv3+
  • Very lightweight - easy to hack and learn for newbies.
  • Support JSON/CSV/XML/SXML.
  • A complete web-server implementation, including an error page handler.
  • High concurrent async non-blocking server core based on delimited continuations.
  • Has a Sinatra-like style route, hence the name “Artanis” ;-)
  • Supported databases (through guile-dbi): MySQL/SQLite/PostgreSQL.
  • Nice and easy web cache control.
  • Efficient HTML template parsing.
  • Efficient static file downloading/uploading.

Download

The latest version of GNU Artanis can be downloaded from

Documents

Official GNU Artanis manual: https://www.gnu.org/software/artanis/manual/

Source Code

GNU Artanis is a free & open source software dual licenced under both GPLv3+ & LGPLv3+. You can get involved in the development or obtain a copy of source code of artanis from the below locations.

Officially, GNU Artanis is maintained on Savannah, and folks can discuss on mailing-list. But we also provide GitLab for modern people.

FAQ

What is it?

GNU Artanis is a framework for web authoring - for instance, generating HTML pages dynamically. In other words, a WAF (Web Application Framework).

Who wrote it?

Mu Lei AKA NalaGinrut, a Certified Scheme Nut - someone who would rather face death than not use the Scheme programming language to build a website.

Why write it?

More seriously, Artanis is written using GNU Guile, one of the best implementations of Scheme language.

One day, the folks at GNU were discussing what language they would write the GNU website in - and many chose Python. But I found that strange, because the official extension language of GNU is GNU Guile. And I wondered aloud - why not start a brand new project to provide a web framework written with GNU Guile? To which RMS said, “It’s cool, I like this idea.”

But at that time, it was just an idea without a plan.

Fortunately, a few months later, the Guile community held a hack-potluck to celebrate Guile2 turning two - which is a contest to write a cool program in a few weeks. And so, Artanis was born.

History

  • February 2013 - Artanis born at the GNU Guile hack-potluck.
  • 2013 - Artanis submitted to “Lisp In Summer Projects” contest. Received “Certificated awesome project award” in 2014.
  • August 2014 - Artanis became the official project of the SZDIY community for building web services on their server.
  • 1st January, 2015 - the first stable version Artanis-0.0.1 was released.
  • 19th January, 2015 - SZDIY community offers Artanis to FSF/GNU, and RMS inducts it as an official GNU project. Artanis becomes GNU Artanis.

Who uses it?

GNU Artanis is used to build web services of the SZDIY community. Additionally, some open hardware projects in SZDIY are developing their own web services using GNU Artanis.

Intro To Scheme

;; This gives an introduction to Scheme in 15 minutes
;;
;; First make sure you read this text by Peter Norvig:
;; http://norvig.com/21-days.html
;;
;; Then install GNU Guile
;; NOTE: I won't encourage using Racket but if you really want,
;;       I still give you some hints in this tutorial. But the
;;       tutorial will base on Guile and RnRs.
;;
;; openSUSE: zypper install guile
;; Debian: apt-get install guile-2.0 (or see your distro instructions)
;; MacOSX: Building Guile 2.0 on the Mac
;;         http://irrealblog.blogspot.hk/2011/03/building-guile-2.html
;; Windows try web: http://repl.it/languages/Scheme
;;
;; More general information can be found at:
;; http://www.gnu.org/software/guile

;; Important warning:
;;
;; Going through this tutorial won't damage your computer unless
;; you get so angry that you throw it on the floor.  In that case,
;; I hereby decline any responsability.  Have fun!

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Fire up Scheme:
;; Type 'guile' for GNU Guile
;; Or just use the browser for web version
;;
;; Now look at the prompt:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Semi-colons start comments anywhere on a line.
;;
;; Scheme programs are made of symbolic expressions (s-exps):
(+ 2 2)

;; This symbolic expression reads as "Add 2 to 2".

;; Sexps are enclosed into parentheses, possibly nested:
(+ 2 (+ 1 1))

;; A symbolic expression contains atoms or other symbolic
;; expressions.  In the above examples, 1 and 2 are atoms,
;; (+ 2 (+ 1 1)) and (+ 1 1) are symbolic expressions.

(+ 3 (+ 1 2))
;; => 6

;; `set!' stores a value into a variable:
;; Please define my-name first, or you can't assign it.
(define my-name "unknown")
(set! my-name "NalaGinrut")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 1. Primitive Datatypes and Operators
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; Numbers
9999999999999999999999 ; integers
#b111                  ; binary => 7
#o111                  ; octal => 73
#x111                  ; hexadecimal => 273
3.14                   ; reals
6.02e+23
1/2                    ; rationals
1+2i                   ; complex numbers

;; Function application is written (f x y z ...)
;; where f is a function and x, y, z, ... are operands
;; If you want to create a literal list of data, use ' to stop it from
;; being evaluated
'(+ 1 2) ; => (+ 1 2)
;; Now, some arithmetic operations
(+ 1 1)  ; => 2
(- 8 1)  ; => 7
(* 10 2) ; => 20
(expt 2 3) ; => 8
(quotient 5 2) ; => 2
(remainder 5 2) ; => 1
(/ 35 5) ; => 7
(/ 1 3) ; => 1/3
(exact->inexact 1/3) ; => 0.3333333333333333
(+ 1+2i  2-3i) ; => 3-1i

;;; Booleans
#t ; for true
#f ; for false -- any value other than #f is true
(not #t) ; => #f
(and 0 #f (error "doesn't get here")) ; => #f
(or #f 0 (error "doesn't get here"))  ; => 0

;;; Characters
;; According to RnRs, characters only have two notations:
;; #\ and #\x
;; Racket support #\u, but it's never Scheme.
#\A ; => #\A
#\λ ; => #\λ
#\x03BB ; => #\λ

;;; Strings are fixed-length array of characters.
"Hello, world!"
"Benjamin \"Bugsy\" Siegel"   ; backslash is an escaping character
"Foo\tbar\x21\a\r\n" ; includes C escapes (only support hex)
;; try to print the above string
;; Printing is pretty easy
(display "I'm Guile. Nice to meet you!\n")
;; and unicode escapes
"\u004B" ; => K

;; Strings can be added too!
(string-append "Hello " "world!") ; => "Hello world!"

;; A string can be treated like a list of characters
(string-ref "Apple" 0) ; => #\A

;; format can be used to format strings:
(format #t "~a can be ~a" "strings" "formatted")
;; ==> print "strings can be formatted" on screen
(define str (format #f "~a can be ~a" "strings" "formatted"))
;; str was assigned to "strings can be formatted"

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 2. Variables
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; You can create a variable using define
;; a variable name can use any character except: ()[]{}",'`;#|\
(define some-var 5)
some-var ; => 5

;; Accessing a previously unassigned variable is an exception
; x ; => x: undefined ...

;; Local binding: `me' is bound to "Bob" only within the (let ...)
(let ((me "Bob"))
  "Alice"
  me)
;; => "Bob"

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 3. Structs and Collections
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Record Type (Skip this chapter if you're trying web version
(use-modules (srfi srfi-9))
(define-record-type dog
  (make-dog name breed age)
  dog?
  (name dog-name)
  (breed dog-breed)
  (age dog-age))
(define my-pet
  (make-dog "lassie" "collie" 5))
my-pet ; => #<dog>
(dog? my-pet) ; => #t
(dog-name my-pet) ; => "lassie"

;;; Pairs (immutable)
;; `cons' constructs pairs, `car' and `cdr' extract the first
;; and second elements
(cons 1 2) ; => '(1 . 2)
(car (cons 1 2)) ; => 1
(cdr (cons 1 2)) ; => 2

;;; Lists

;; Lists are linked-list data structures, made of `cons' pairs and end
;; with a '() to mark the end of the list
(cons 1 (cons 2 (cons 3 '()))) ; => '(1 2 3)
;; `list' is a convenience variadic constructor for lists
(list 1 2 3) ; => '(1 2 3)
;; and a quote can also be used for a literal list value
'(1 2 3) ; => '(1 2 3)

;; Can still use `cons' to add an item to the beginning of a list
(cons 4 '(1 2 3)) ; => '(4 1 2 3)

;; Use `append' to add lists together
(append '(1 2) '(3 4)) ; => '(1 2 3 4)

;; Lists are a very basic type, so there is a *lot* of functionality for
;; them, a few examples:
;; For Racket users:
(map add1 '(1 2 3))          ; => '(2 3 4)
;; For Guile users:
(map 1+ '(1 2 3))        ; => '(2 3 4)
;; add1 or 1+ is not a standard primitive, so it depends on implementations.

(map + '(1 2 3) '(10 20 30)) ; => '(11 22 33)

;; filter/count/take/drop are dwell in SRFI-1, so you have to load it first.
;; For Racket users:
(require srfi/1)
;; For Guile users:
(use-modules (srfi srfi-1))

(filter even? '(1 2 3 4))    ; => '(2 4)
(count even? '(1 2 3 4))     ; => 2
(take '(1 2 3 4) 2)          ; => '(1 2)
(drop '(1 2 3 4) 2)          ; => '(3 4)

;;; Vectors

;; Vectors are fixed-length arrays
#(1 2 3) ; => '#(1 2 3)

;; Use `vector-append' to add vectors together
;; NOTE: vector-append is in SRFI-43 which is not supported in Guile-2.0.9
;;       or earlier. And it may not be added in Guile-2.0.10.
;;       But it's proposed in R7RS, and there's a r7rs branch in Guile upstream.
;;       If your Guile doesn't support vector-append, please skip this step.
(vector-append #(1 2 3) #(4 5 6)) ; => #(1 2 3 4 5 6)

;;; Hashes

;; Create mutable hash table
;; For GNU Guile
(define m (make-hash-table))
(hash-set! m 'a 1)
(hash-set! m 'b 2)
(hash-set! m 'c 3)

;; Retrieve a value
(hash-ref m 'a) ; => 1

;; Retrieving a non-present value is an exception
(hash-ref m 'd)
;; => #f

;; You can provide a default value for missing keys
(hash-ref m 'd 0)
;; => 0

;; Use `hash-remove' to remove keys (functional too)
(hash-remove! m 'a) ; => ((b . 2) (c . 3))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 3. Functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Use `lambda' to create functions.
;; A function always returns the value of its last expression
(lambda () "Hello World") ; => #<procedure>

;; Use parens to call all functions, including a lambda expression
((lambda () "Hello World")) ; => "Hello World"
((lambda (x) (+ x x)) 5)      ; => 10

;; Assign a function to a var
(define hello-world (lambda () "Hello World"))
(hello-world) ; => "Hello World"

;; You can shorten this using the function definition syntatcic sugar:
(define (hello-world2) "Hello World")
(hello-world2) ; => "Hello World"

;; The () in the above is the list of arguments for the function
(define hello
  (lambda (name)
    (string-append "Hello " name)))
(hello "Steve") ; => "Hello Steve"
;; ... or equivalently, using a sugared definition:
(define (hello2 name)
  (string-append "Hello " name))

;; You can have multi-variadic functions too, using `case-lambda'
(define hello3
  (case-lambda
    (() "Hello World")
    ((name) (string-append "Hello " name))))
(hello3 "Jake") ; => "Hello Jake"
(hello3) ; => "Hello World"
;; ... or specify optional arguments with a default value expression
(define* (hello4 #:key (name "World"))
  (string-append "Hello " name))

;; Functions can pack extra arguments up in a list
(define (count-args . args)
  (format #t "You passed ~a args: ~a" (length args) args))
(count-args 1 2 3) ; => "You passed 3 args: (1 2 3)"
;; ... or with the unsugared `lambda' form:
(define count-args2
  (lambda args
    (format #t "You passed ~a args: ~a" (length args) args)))

;; You can mix regular and packed arguments
(define (hello-count name . args)
  (format #t "Hello ~a, you passed ~a extra args" name (length args)))
(hello-count "Finn" 1 2 3)
; => "Hello Finn, you passed 3 extra args"
;; ... unsugared:
(define hello-count2
  (lambda (name . args)
    (format #t "Hello ~a, you passed ~a extra args" name (length args))))

;; And with keywords
;; the keywords are those like this #:its-name, sometimes you may see
;; :its-name without '#' in certain Scheme implementation.
;; NOTE: keywords is not in any Scheme standards like RnRs.
;;       But mainstream Scheme implementation often contains it.
;;   This truth also means that different implementation may has
;;   different result.
;;       The code below will follow Guile situation.
(define* (hello #:key (name "World") (greeting "Hello") . args)
  (format #t "~a ~a, ~a extra args~%" greeting name (length args)))
;; 'define*' is similar with 'define', but you may use it for defining
;; optional args or using keyword to specify the value to specified argument.
(hello)                 ; => "Hello World, 0 extra args"
(hello 1 2 3)           ; => "Hello World, 3 extra args"
(hello #:greeting "Hi") ; => "Hi World, 2 extra args"
;; NOTE: In Guile, all the keyword-value pairs are countered as the rest args.
;;       It means 'args' here will be (#:greeting "Hi"), so its length is 2.
;;       If you are in Racket, it's different, so the result will be 0.
;;       Because it's not defined in RnRs, so it's implementation specific.
(hello #:name "Finn" #:greeting "Hey") ; => "Hey Finn, 4 extra args"
(hello 1 2 3 #:greeting "Hi" #:name "Finn" 4 5 6) ; => "Hi Finn, 10 extra args"

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 4. Equality
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; for numbers use `='
(= 3 3.0) ; => #t
(= 2 1) ; => #f

;; for characters use `char=?'
(char=? #\c #\c) ; => #t

;; for object identity use `eq?'
;; WARNING: don't use `eq?' on numbers and characters, the reason is very simple:
;;          because RnRs treat this rule as undefined!
;;(eq? 3 3) ; => Wrong! it's undefined! So it depends on implementation!
;; No matter if you got #t from above, it's a wrong usage!!!
;; Should use (= 3 3) or (eqv? 3 3)

(eq? (list 3) (list 3)) ; => #f
;; Why it's #f? Because comparison between objects depends on their head-pointers.
;; These two lists are different objects, and they have different head-pointers.

(eq? 'a 'a) ; => #t
;; Symbols are the typical objects to compare with their head-pointers

;; eqv?
;; The difference between `eqv?' and `eq':
;; You can compare numbers and characters with `eqv?'
;; When you use `eqv?' for any objects other than numbers&characters, it's the
;; same with `eq?'.
(eqv? 3 3)     ; => #t
(eqv? #\c #\c) ; => #t
(eqv? 'a 'a)   ; => #t

;; for collections use `equal?'
;; `equal?' will compare all the values in a collections type like record or list.
;; You can compare any objects with `equal?' safely, but inefficiency.
(equal? (list 'a 'b) (list 'a 'b)) ; => #t
(equal? (list 'a 'b) (list 'b 'a)) ; => #f

;; Choosing proper equal pred for you code, is one of the art in Scheme programming!

(eqv? "abcd" "abcd") ; => unspecified
;; Obviously, strings are collections type, so...it's your EXECISE now!

;; Another EXECISE, what's the proper 'equal-pred' for functions?
(what-to-use? (lambda (x) (1+ x)) (lambda (x) (1+ x)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 5. Control Flow
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; Conditionals

(if #t               ; test expression
    "this is true"   ; then expression
    "this is false") ; else expression
; => "this is true"

;; In conditionals, all non-#f values are treated as true
(member 'Groucho '(Harpo Groucho Zeppo)) ; => '(Groucho Zeppo)
(if (member 'Groucho '(Harpo Groucho Zeppo))
    'yep
    'nope)
; => 'yep

;; `cond' chains a series of tests to select a result
(cond ((> 2 2) (error "wrong!"))
      ((< 2 2) (error "wrong again!"))
      (else 'ok)) ; => 'ok

;;; Pattern Matching
;; For Racket users:
(require racket/match) ; use match module
(define (fizzbuzz? n)
  (match (list (remainder n 3) (remainder n 5))
    ((list 0 0) 'fizzbuzz)
    ((list 0 _) 'fizz)
    ((list _ 0) 'buzz)
    (else #f)))

;; For Guile users:
(use-modules (ice-9 match)) ; use match module
(define (fizzbuzz? n)
  (match (list (remainder n 3) (remainder n 5))
    ((0 0) 'fizzbuzz)
    ((0 _) 'fizz)
    ((_ 0) 'buzz)
    (else #f)))

(fizzbuzz? 15) ; => 'fizzbuzz
(fizzbuzz? 37) ; => #f

;;; Loops

;; Looping can be done through (tail-) recursion
(define (lp i)
  (when (< i 10)
    (format #t "i=~a\n" i)
    (lp (1+ i))))
(lp 5) ; => i=5, i=6, ...

;; Similarly, with a named let
(let lp ((i 0))
  (when (< i 10)
    (format #t "i=~a\n" i)
    (lp (1+ i)))) ; => i=0, i=1, ...

;; how to get a range? just like range(0, 9)?
;; the original 'iota' only accept one para
(iota 10) ; ==> (0 1 2 3 4 5 6 7 8 9)
;; the 'iota' in srfi-1 was extended
(use-modules (srfi srfi-1))
;; #<procedure iota (count #:optional start step)>
(iota 5 10) ; => (10 11 12 13 14)
;; means from 10 count 5 times, each step +1 (plus one is default)
(iota 5 10 2) ; => (10 12 14 16 18)
;; from 10 count 5 times, each step +2
;; If you need a Python like range(5, 10) ==> (5 6 7 8 9), try:
(define (range from to) (map (lambda (x) (+ from x)) (iota (- to from))))
;; EXECISE: you may find this 'range' implementation is not so good,
;;          please optimize it if you can.
(range 5 10) ; => (5 6 7 8 9)

;; how to do iteration?
(for-each display '(1 2 3 4 5))
;; => 12345
(for-each (lambda (i) (format #t "i=~a\n" i))
          (iota 10)) ; => i=0, i=1, ...
(for-each (lambda (i) (format #t "i=~a\n" i))
          (range 5 10)) ; => i=5, i=6, ...

;;; Iteration Over Other Sequences
;; `for' allows iteration over many other kinds of sequences:
;; lists, vectors, strings, sets, hash tables, etc...
(for-each display '(l i s t))
;; => list
(define vector-for-each (@ (rnrs) vector-for-each))
;; export vector-for-each from rnrs only
(vector-for-each display #(v e c t o r))
;; => vector
(string-for-each display "string")
;; => string
;;; More Iterations
(do ((i 10 (1+ i)) (j '(x y z) (cdr j)))
    ((null? j)) ; if j is '(), just end the loop
  (format #t "~a:~a " i (car j)))
; => 0:x 1:y 2:z

;;; Exceptions

;; To catch exceptions, use the 'catch' form
(catch 'my-error
  (lambda () (throw 'my-error))
  (lambda e (display "oh~my error!\n")))
; => oh~my error!

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 6. Mutation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Use `set!' to assign a new value to an existing variable
(define n 5)
(set! n (1+ n))
n ; => 6

;; Use fluid for explicitly mutable values
(define n* (make-fluid 5))
(fluid-set! n* (1+ (fluid-ref n*)))
(fluid-ref n*) ; => 6

;; Many Guile datatypes are immutable (pairs, lists, etc), some come in
;; both mutable and immutable flavors (strings, vectors, hash tables,
;; etc...)

;; Use `vector' or `make-vector' to create mutable vectors
(define vec (vector 2 2 3 4))
(define wall (make-vector 100 'bottle-of-beer))
;; Use vector-set! to update a slot
(vector-set! vec 0 1)
(vector-set! wall 99 'down)
vec ; => #(1 2 3 4)

;; Create an empty mutable hash table and manipulate it
(define m3 (make-hash-table))
(hash-set! m3 'a 1)
(hash-set! m3 'b 2)
(hash-set! m3 'c 3)
(hash-ref m3 'a)   ; => 1
(hash-ref m3 'd 0) ; => 0
(hash-remove! m3 'a)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 7. Modules
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Modules let you organize code into multiple files and reusable
;; libraries; Make sure put all the module code in one file, since
;; the modules split as files. And the module name should be same
;; with the filename, say, module named (my-cake) is 'my-cake.scm',
;; and module named (mods my-cake) is 'mods/my-cake.scm',
;; (mods submods my-cake) ==> 'mods/submods/my-cake.scm'.
;; ---begin my-cake.scm---
(define-module (my-cake) ; define a `cake' module based on racket/base
  #:use-module (ice-9 format) ; the pre-requisition of current module
  #:export (print-cake)) ; function exported by the module

  (define (show fmt n ch) ; internal function
    (format #t fmt (make-string n ch))
    (newline))

  (define (print-cake n)
    (show "   ~a   " n #\.)
    (show " .-~a-. " n #\|)
    (show " | ~a | " n #\space)
    (show "---~a---" n #\-))
;; --end my-cake.scm---

;; Be sure that the path of 'my-cake.scm' is in your current
;; %load-path list. Use `use-modules' to get all `provide'd names
;; from a module.
(use-modules (my-cake)) ; the ' is for a local submodule
(print-cake 3)
; (show "~a" 1 #\A) ; => error, `show' was not exported

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 8. Classes and Objects
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TODO
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 9. Macros
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Macros let you extend the syntax of the language

;; Let's add a while loop
(define-syntax-rule (while condition body ...)
  (let loop ()
    (when condition
      body ...
      (loop))))

(let ((i 0))
  (while (< i  10)
    (display i)
    (set! i (1+ i))))

;; Macros are hygienic, you cannot clobber existing variables!
(define-syntax-rule (swap! x y) ; -! is idomatic for mutation
  (let ((tmp x))
    (set! x y)
    (set! y tmp)))

(define tmp 1)
(define a 2)
(define b 3)
(swap! a b)
(format #t "tmp = ~a; a = ~a; b = ~a\n" tmp a b) ; tmp is unaffected

;; But they are still code transformations, for example:
(define-syntax-rule (bad-while condition body ...)
  (when condition
    body ...
    (bad-while condition body ...)))
;; this macro is broken: it generates infinite code, if you try to use
;; it, the compiler will get in an infinite loop

Author: Artanis Dev

Created: 2022-02-19 Sat 18:20