# Steel
### Packages If you would like to install and use packages, please set the `STEEL_HOME` environment variable. This will be the location that packages get installed to. Steel currently does not assume any default. ## About `Steel` is an embeddable scheme interpreter, with a standalone cli included as well. Inspired largely by Racket and Clojure, the language seeks to be ergonomic scheme variant helpful for embedding in applications, or to be used on its own with high performance functions implemented in Rust. The language implementation itself contains a fairly powerful macro system based on the `syntax-rules` style and a bytecode virtual machine. At the moment, it is not explicitly compliant with any individual scheme specification. > **Warning** > The API is very unstable with no guarantees, and may change at any time while pre 1.0. There are undoubtedly bugs that exist, and I wouldn't consider Steel to be production ready. That being said, I do use it as a daily driver for many scripting tasks myself. ## Features * Limited `syntax-rules` style macros are supported * Easy integration with Rust functions and structs * Easily call a script from rust or via a separate file * Efficient - common functions and data structures are optimized for performance (`map`, `filter`, etc) * Higher order Contracts * Built in immutable data structures include: * lists * vectors * hashmaps * hashsets ## Contracts Inspired by Racket's higher order contracts, `Steel` implements\* higher order contracts to enable design by contract, made easy with a `define/contract` macro for easier ergonomics. Racket makes use of a concept known as _blame_ which seeks to identify the violating party - `Steel` does not quite have fully fleshed out blame but that is a work in progress. Here are some examples: ```scheme ;; Simple flat contracts (define/contract (test x y) (->/c even? even? odd?) (+ x y 1)) (test 2 2) ;; => 5 (define/contract (test-violation x y) (->/c even? even? odd?) (+ x y 1)) (test-violation 1 2) ;; contract violation ``` Contracts are implemented as _values_, so they are bound to functions. This enables the use of contract checking on functions themselves since functions can be passed around: ```scheme ;; Higher order contracts, check on application (define/contract (higher-order func y) (->/c (->/c even? odd?) even? even?) (+ 1 (func y))) (higher-order (lambda (x) (+ x 1)) 2) ;; => 4 (define/contract (higher-order-violation func y) (->/c (->/c even? odd?) even? even?) (+ 1 (func y))) (higher-order-violation (lambda (x) (+ x 2)) 2) ;; contract violation ``` Contracts on functions do not get checked until they are applied, so a function returning a _contracted_ function won't cause a violation until that function is actually used: ```scheme ;; More higher order contracts, get checked on application (define/contract (output) (->/c (->/c string? int?)) (lambda (x) 10)) (define/contract (accept func) (->/c (->/c string? int?) string?) "cool cool cool") (accept (output)) ;; => "cool cool cool" ;; different contracts on the argument (define/contract (accept-violation func) (->/c (->/c string? string?) string?) (func "applesauce") "cool cool cool") (accept-violation (output)) ;; contract violation ;; generates a function (define/contract (generate-closure) (->/c (->/c string? int?)) (lambda (x) 10)) ;; calls generate-closure which should result in a contract violation (define/contract (accept-violation) (->/c (->/c string? string?)) (generate-closure)) ((accept-violation) "test") ;; contract violation ``` Perhaps a more nuanced case: ```scheme (define/contract (output) (->/c (->/c string? int?)) (lambda (x) 10.2)) (define/contract (accept) (->/c (->/c string? number?)) (output)) ((accept) "test") ;; contract violation 10.2 satisfies number? but _not_ int? ``` \* Very much a work in progress ## Transducers Inspired by clojure's transducers, `Steel` has a similar object that is somewhere half way in between transducers and iterators. Consider the following: ```scheme (mapping (lambda (x) (+ x 1))) ;; => <#iterator> (filtering even?) ;; => <#iterator> (taking 15) ;; => <#iterator> (compose (mapping add1) (filtering odd?) (taking 15)) ;; => <#iterator> ``` Each of these expressions emit an `<#iterator>` object, which means they're compatible with `transduce`. `transduce` takes a transducer (i.e. `<#iterator>`) and a collection that can be iterated (`list`, `vector`, `stream`, `hashset`, `hashmap`, `string`, `struct`) and applies the transducer. ```scheme ;; Accepts lists (transduce (list 1 2 3 4 5) (mapping (lambda (x) (+ x 1))) (into-list)) ;; => '(2 3 4 5 6) ;; Accepts vectors (transduce (vector 1 2 3 4 5) (mapping (lambda (x) (+ x 1))) (into-vector)) ;; '#(2 3 4 5 6) ;; Even accepts streams! (define (integers n) (stream-cons n (lambda () (integers (+ 1 n))))) (transduce (integers 0) (taking 5) (into-list)) ;; => '(0 1 2 3 4) ``` Transduce accepts a reducer function as well. Above we used `into-list` and `into-vector`, but below we can use any arbitrary reducer: ```scheme ;; (-> transducer reducing-function initial-value iterable) (transduce (list 0 1 2 3) (mapping (lambda (x) (+ x 1))) (into-reducer + 0)) ;; => 10 ``` Compose just combines the iterator functions and lets us avoid intermediate allocation. The composition works left to right - it chains each value through the functions and then accumulates into the output type. See the following: ```scheme (define xf (compose (mapping add1) (filtering odd?) (taking 5))) (transduce (range 0 100) xf (into-list)) ;; => '(1 3 5 7 9) ``` ## Syntax Choices `Steel` is mildly opinionated in that there a few ways to define variables and functions. These choices are fairly arbitrary except for the shorthand function syntax, which I borrowed from Racket. `defn` and `fn` were really encouraged by me wanting to type less characters. ```scheme ;; All of the following are equivalent (define (foo x) (+ x 1)) (define foo (lambda (x) (+ x 1))) (defn (foo x) (+ x 1)) (defn foo (lambda (x) (+ x 1))) ;; All of the following are equivalent (lambda (x) (+ x 1)) (λ (x) (+ x 1)) (fn (x) (+ x 1)) ``` ## Modules In order to support a growing codebase, Steel has module support for projects spanning multiple files. Steel files can `provide` values (with contracts attached) and `require` modules from other files: ```scheme ;; main.scm (require "provide.scm") (even->odd 10) ;; provide.scm (provide (contract/out even->odd (->/c even? odd?)) no-contract flat-value) (define (even->odd x) (+ x 1)) (define (accept-number x) (+ x 10)) (define (no-contract) "cool cool cool") (define flat-value 15) (displayln "Calling even->odd with some bad inputs but its okay") (displayln (even->odd 1)) ``` Here we can see if we were to run `main` that it would include the contents of `provide`, and only provided values would be accessible from `main`. The contract is attached at the contract boundary, so inside the `provide` module, you can violate the contract, but outside the module the contract will be applied. A few notes on modules: * Cyclical dependencies are not allowed * Modules will be only compiled once and used across multiple files. If `A` requires `B` and `C`, and `B` requires `C`, `C` will be compiled once and shared between `A` and `B`. * Modules will be recompiled when changed, and any dependent files will also be recompiled as necessary ## Performance Preliminary benchmarks show the following on my machine: | Benchmark | Steel | Python | | --------- | -------- | -------- | | (fib 28) | 63.383ms | 65.10 ms | | (ack 3 3) | 0.303 ms | 0.195 ms | ## Examples of embedding Rust values in the virtual machine Rust values, types, and functions are easily embedded into Steel. Using the `register_fn` call, you can embed functions easily: ```rust use steel_vm::engine::Engine; use steel_vm::register_fn::RegisterFn; fn external_function(arg1: usize, arg2: usize) -> usize { arg1 + arg2 } fn option_function(arg1: Option