Owl Lisp is a purely functional dialect of Scheme. It is based on the applicable subset of R7RS standard, extending it mainly with threads and data structures necessary for purely functional operation. Owl can be used on most UNIX-like systems, such as Linux, BSDs and OS X. Programs are typically compiled via C to standalone binaries, so Owl isn't needed to run programs written in it.
Owl project originally got started both as an attempt to extend R5RS Scheme with some necessary features, such as threads and modules, and as an experiment on how being purely functional influences the runtime and use of an applicative order purely functional language. While things have been added to Scheme, Owl tries to keep the core language as simple as possible.
Implementationwise the goal was to get a small portable system which could be used to ship programs easily. This is currently accomplished by using a small register-based virtual machine, which can be extended with program-specific instructions to reduce interpretive overhead.
Owl consists of a single binary
ol, which contains the REPL, compiler and builtin libraries. Releases have a precompiled
ol.c file, which can be downloaded and used as follows:
$ curl https://gitlab.com/owl-lisp/owl/uploads/87ffa9dad2bc59fc0ac5efcd90db4c7c/ol-0.1.16.c.gz \ | gzip -d \ | gcc -x c -O2 -o ol - $ ./ol You see a prompt. > (cons 1 (list 2)) '(1 2) > ,quit bye bye _o/~
Alternatively you can download all of the sources and make a traditional install.
$ git clone https://gitlab.com/owl-lisp/owl.git $ cd owl-lisp $ make $ sudo make install
Owl currently runs on top of a small virtual machine. The VM boot consists of decoding a FASL-encoded value, converting the command line arguments to a list, and calling the decoded value with the list. The value to boot can be stored either initially one Owl value, which is assumed to be a function of one argument, converts the command line inputs to a list of strings, and then calls the decoded value with the list. If a value to be decoded is not stored to the executable (meaning
heap is null), then the VM tries to read a FASL-encoded value from the first argument and omits the VM from the arguments passed to the program. This way all three ways to run programs with arguments work the same.
$ echo '(lambda (args) (print args) 0)' > program.scm $ ol -o program.fasl program.scm $ ol -o program.c program.scm && cc -o program program.c $ ol --run program.scm 1 2 3 (ol 1 2 3) $ bin/vm program.fasl 1 2 3 (program.fasl 1 2 3) $ ./program 1 2 3 (./program 1 2 3)
FASL-encoded values compiled with ol contain also the thread scheduler, which brings in much of bignum arithmetic, IO and other dependent code. FASL-encoding can also be done directly, if necessary.
$ echo '(lambda (args) 42)' | ol -o test-1.fasl - $ ol -e '(vector->file (list->vector (fasl-encode (lambda (x) 42))) "test-2.fasl")' #true $ bin/vm test-1.fasl; echo $? 42 $ bin/vm test-2.fasl; echo $? 42 $ wc -c test-* 53825 test-1.fasl 14 test-2.fasl 53839 total
When Owl code is checked out, the source tree contains a bytecode image of some previous state of the Owl world in
fasl/init.fasl. The VM uses this image to load the sources from
owl/*.scm and outputs the final value as the next world image. This is repeated until a fixed point is reached, meaning the compiler compiles an identical version of itself. The initial FASL image is occasionally updated to the current fixed point image, when the build starts to take too many such rounds.
Owl tries to keep the core language elegant and orthogonal, which has resulted in some rather rare features:
(λ (a b) (or (eq? a b) (lesser? a b)))is a complete partial order for all values.
Q: Where can I get help?: You can stop by at &34;owl-lisp on freenode, file tickets to gitlab or send email to email@example.com.
Q: What does functional even mean?!: Sigh. Owl is functional in the sense that there is no way to ever mutate or otherwise modify a value once it has been defined. Ever. Not even in the VM. Not even self-referential closures.
Q: But Owl does I/O!: It would be kind of pointless if it didn't. The semantics of IO are the same as the semantics of message passing between threads. The implementation details have varied and don't matter. Every program that interfaces
Q: But Scheme is functional too!: No it's not, and it's not supposed to be. Scheme is a multi-paradigm language that is also well suited for functional programming.
Q: But C++ is functional too!: ...
Q: But what's the point?: To implement what I wanted and didn't exist at the time, and to share the result.
Q: Is there a type checker?: Not for now.
Q: How can I use third party libraries?: Grab them to source directory and include.
(import (foo bar)) attempts to load
./foo/bar.scm, which should have
(define-library (foo bar) ...)
Q: The error messages suck.: True, at least for now.
Q: Why is it called / not called a Scheme?: Owl was initially going to be a R5RS-based Scheme system, but when R6RS took a step into a valid but different direction than what Owl had in mind, it was made a different language. This made it easy to add necessary features and experiment things I'd wanted to try out, like dropping mutations in favor of λ-calculus -based semantics and using Erlang-style threads to encapsulate state and handle asynchronous computations. Surprisingly R7RS ended up being a lot like Owl. Owl ended up being one of the first systems to implement R7RS modules, some syntax extensions, and the intention is to keep growing the intersection of the languages while keeping owl-specific code in (owl ...) modules. This can be accomplished with R7RS feature flags quite easily. The goals isn't to fragment Scheme community, but to try out semantics and features which might even one day end up in Scheme. I wouldn't be surprised if R9RS Scheme would end up having e.g. finite functions, threads or separate definition and referral of mutable values.
Q: Is this the last question?: No, that was already asked.