s7 playground
A work-in-progress interactive tutorial of s7 scheme
. For a complete
manual please refer to
https://ccrma.stanford.edu/software/snd/snd/s7.html
Table of Contents
Intro
unbound-variable
Hello there!!
;unbound variable this-will-cause-an-error in this-will-cause-an-error
Math
s7 includes:
- sinh, cosh, tanh, asinh, acosh, atanh
- logior, logxor, logand, lognot, logbit?, ash, integer-decode-float
- random
- nan?, infinite?
(1 0 33)
Other math-related differences between s7 and r5rs:
rational?
andexact
mean integer or ratio (i.e. not floating point), inexact means not exact.floor
,ceiling
,truncate
, andround
return (exact) integer results.#
does not stand for an unknown digit.- the "@" complex number notation is not supported ("@" is an exponent marker in s7).
+i
is not considered a number; include the real part.modulo
,remainder
, andquotient
take integer, ratio, or real arguments.lcm
andgcd
can take integer or ratio arguments.log
takes an optional second argument, the base..
and an exponent can occur in a number in any base.rationalize
returns a ratio!case
is significant in numbers, as elsewhere: #b0 is 0, but #B0 is an error.
(1 0 578)
define*, lambda*
define*
and lambda*
are extensions of define and lambda that
make it easier to deal with optional, keyword, and rest
arguments.
The syntax is very simple: every argument to define*
has a default
value and is automatically available as a keyword argument. The
default value is either #f
if unspecified, or given in a list
whose first member is the argument name. The last argument can be
preceded by :rest
or a dot .
to indicate that all other trailing
arguments should be packaged as a list under that argument's name. A
trailing or rest argument's default value is ()
and can't be
specified in the declaration. The rest argument is not available as
a keyword argument.
((1 32 "hi") (3 2 "hi") (3 2 1))
Macros
Macros is where the lisp languages really shine. They can extend the language in ways otherwise not possible. They are also quite tricky to get, personally it took me some time to understand the concept.
My short explanation that I wish I had read somewhere else:
- When you call a normal function
(my-function (+ 1 2))
, the argument(+ 1 2)
gets evaluated and its result gets passed to the function. So, in the function body, that argument already has the value3
- On the contrary, when you call a macro, the macro accepts exactly
what you have typed. Meaning, upon calling
(my-macro (+ 1 2))
, the argument inside the macro is the exact list you typed, meaning(+ 1 2)
and not3
which is the result you'd get after evaluation. So, in other words, you have to evaluate the arguments inside the macro yourself to get their value.
Demonstrating this in code:
(+ 1 2)
my-function: x is 3 my-macro: x is (+ 1 2)
An example with if
. Let's say if
wasn't available in the
language, and we will construct my-if
. We want to pass 3 arguments
to my-if
- First, will be the test clause.
- Then will be our 2 branches.
If the test clause is not false
, we shall execute (aka evaluate
)
the 1st branch (aka 2nd argument), otherwise the 2nd branch (aka 3rd argument).
To summarize, the signature shall be (my-if test-clause branch-true branch-false)
.
- Q: Could we implement it as a function?
- A: No! As we said above, when we call a function, all its arguments are evaluated. Let me demonstrate:
2
executing branch-true executing branch-false
You see? But in our my-if
we don't want the branch-true to be evaluated if not necessary! That's why we need a macro.
2
executing branch-false
You see the difference? Before we go on explaining how you write a macro, let me show you a different Implementation of my-if
2
executing branch-false
These 2 implentations are essentially the same.
Fun with macros
Internally in this document I'm using the examples
macro to
showcase small snippets and their results. That saves me from
writing the result myself. I only write the code snippets wrapped
around the examples
macro call, and evaluate it to get the <code
snippet> ;; => <result>
output
<unspecified>
(+ 1 2 1) ;; => 4 (/ 10 2) ;; => 5
Benchmarks
Let's see how fibonacci is doing
(9227465 0.192625)
The above (cached) result is with s7
running on my desktop machine. Click eval
to see the performance on the browser (don't forget to eval the previous code block that defines time
)
Below are some benchmark times in my machine.
fibonacci : wasm
Wasm file size / benchmark. (fib x)
time in seconds. File size of .wasm
included as well.
optimization | file size | (fib 38) |
-O2 | 2mb | 5.5 |
-Os | 1mb | 5.5 |
-O0 | 3.8mb | 8.1 |
The equivalent fib
implentation in javascript (running in the browser) took 0.45s
for fib(38)
.
Try opening a console and running benchmarkFib(38)
.
fibonacci : desktop
Desktop comparison (see src/repl.c)
optimization | file size | (fib 38) | (fib 41) |
-O0 | 3.2mb | 1.4 | 6.1 |
-O2 | 10mb | 0.8 | 3.4 |
-Os | 5.3mb | 0.8 | 3.4 |
Similar implementation in other languages, and time in seconds for fib 41
(including boot time)
lang | fib 41 |
node v10.19 | 3 |
chez scheme | 2.5 |