Introduction

Welcome to "The Steel Book" — a guide for a dynamic and versatile programming language designed to harmonize with Rust, known as Steel. Born from the Lisp family and descended from Scheme, Steel offers a blend of dynamic expressiveness within Rust's strict environment, creating a powerful synergy.

But why choose a Lisp dialect as an extension language?

Rust provides robustness and safety, ensuring reliable and efficient software development. However, its rigidity can sometimes feel limiting when facing dynamic and evolving requirements, such as those encountered in a text editor, a crucial tool that developers tailor to their needs. Enter Lisp — a language family renowned for its extensibility and dynamic nature. It serves as the ultimate extension language, designed to be extendable itself by allowing developers to create DSLs and tweak the evaluation process to fit any domain or task, even those unforeseen or irrelevant to the original application.

In "The Steel Book," our aim is twofold: to demystify language basics for developers unfamiliar with Scheme intricacies and to teach about Steel in particular. That said, no prior Scheme experience is needed; we've got you covered.

Foundations

The ideas of Scheme are straightforward and easily combined, resulting in a minimalistic syntax that may appear excessive at first glance. While the abundance of parentheses can seem off-putting and archaic, expressions are actually nested lists and atoms, making it a recursive data structure. This inherent structure enables easy manipulation, allowing for straightforward metaprogramming and direct AST manipulations with proper editor support.

Now, let's welcome an S-expression (or sexp for short):

(+ 1 3 (- 7 4) 7)

As mentioned earlier, its structure is recursive; this particular one is comprised of a top-level expression and nested atoms, as well as one nested expression. Such a construction can be manipulated in various ways before being evaluated. Although the list of syntactic rules will be minimally supplemented later in this book, it already provides a stable foundation to fit the language in any domain area.

Evaluation

A key to understanding any Lisp is grasping the idea of how expressions should be evaluated. Roughly, the steps are:

  1. Reader: Code as a string becomes a sexp, either an atom or an expression. Later, we'll discuss that there are forms that users can input, but they look different until processed by the reader into a proper sexp. It will be explicitly mentioned in this book if that's the case. Until then, our string inputs will match the string representation of a resulting sexp.
  2. Macro expansion: Some sexps may become other sexps.
  3. Eval: The final sexp is evaluated.

In subsequent chapters, we'll demystify all of it. For now, steps 1 and 2 will be transparent.

Literals

Literals are evaluated to themselves: numbers and string literals.

Let's fire up a Steel interpreter (or use the online one) to take a look at some examples.

In the following code examples, lambda precedes user input, and => indicates the evaluation result returned from the interpreter.

λ > 5
=> 5

λ > -1337
=> -1337

λ > 1.5
=> 1.5

λ > 0
=> 0

As we can see, an input text read as a sexp in a variant of an atom made of a number literal is evaluated to itself. What about string literals?

λ > "hello"
=> "hello"

λ > "steel"
=> "steel"

Nothing unexpected happens, as for characters:

λ > #\y
=> #\y

While not very fascinating by itself, it starts to become useful when built upon, as we're about to see next.

Function Calls

We're familiar with two variants of S-expressions: atoms and expressions. We've already seen how numbers and string literals, which are examples of atoms, are evaluated. Now, let's delve into the evaluation of expressions.

Let's revisit the previous expression example:

(+ 1 3 (- 7 4) 7)

Skipping the reader and macro expansion steps mentioned in the evaluation chapter, the interpreter will evaluate this expression in the following steps:

Step 1: Evaluate all items

  1. The symbol + evaluates to the value behind it, in this case, the value of the addition function:
λ > +
=> #<function:+>

More details on symbols will follow in the symbols chapter.

  1. As mentioned earlier, numbers evaluate to themselves.
  2. A nested expression is evaluated, similar to the current top-level one.

At this point, the expression is represented as follows (not proper input, but a human-readable representation of the process):

(#<function:+> 1 3 (- 7 4) 7)

Step 2: Evaluate all items, level + 1

Here, we address the nested expression.

  1. The symbol - also evaluates to a subtraction function value, just like the previous case with addition.
  2. Numbers evaluate to themselves, again.
  3. Function application occurs.

Expression evaluation follows a scheme where the first (0th, or more aptly, "the head") value represents a function value. This results in a function call familiar to us. With scheme, we have the freedom to instruct the interpreter not to evaluate it and leave it as is, but we'll address this case later in the book.

Thus, the expression (- 7 4) transitions from (#<function:-> 7 4) to 3 due to function application.

Step 3: Returning to level 0

After reducing the nested expression, we have:

(#<function:+> 1 3 3 7)

The evaluation of an expression eventually results in a function application unless instructed otherwise. Here, there's no exception:

=> 14

Symbols

We've previously learned that + is not merely a function, but rather a symbol representing a function. But what exactly is a symbol?

In Scheme, a symbol is a fundamental data type—a sequence of characters, distinct from a string. Entities are named using symbols rather than strings because symbols serve as identifiers and are evaluated differently.

An analogy from Rust can shed light on the concept of symbols. Consider the question "what is a in let a = 2;" from a metaprogramming perspective. In this context, a can be understood as an identifier. Similarly, when invoking a declarative macro like hello!(b) with a macro definition of macro_rules! hello { ($x:ident) => {} }, it won't raise an error if b is not defined. In this scenario, b acts akin to a symbol, the one we don't evaluate to any value behind it, but it still exists and usable, just on a different level.

Symbols can reference values depending on the context in which they are evaluated. However, in some cases, symbols are used as themselves and may be compared directly. For example, when defining a domain-specific language (DSL), certain symbols may act as "operators" or "keywords" without having a value associated with them. Instead, they impart meaning within the code that processes the DSL. We'll delve deeper into this topic later in the discussion on macros because, by default, symbols are evaluated, which may not always align with our intended usage, especially if we assign them a "keyword" meaning.

Next, we'll explore how to assign values to symbols to give them something to evaluate to.

Bindings and Scopes

How do we define symbols? And more importantly, how do we assign values to them? Let's address these questions now.

λ > a

Congratulations, we've defined our first symbol.

error[E02]: FreeIdentifier
  ┌─ :1:1
  │
1 │ a
  │ ^ a

Well, not so fast.

As mentioned earlier in the book, the interpreter evaluates S-expressions that it encounters, and the symbol a is no exception. Since symbol evaluation involves substituting it with a bound value, and there is none bound to it, we get an error.

Let's change that.

define

This is how we bind values to symbols, using (define symbol value):

λ > (define a 1337)
λ > a
=> 1337

Here, we've created a binding between the symbol a and a number literal. Consequently, further evaluation of a yields the elite integer. It's worth noting that regardless of whether a is bound and used across different expressions (in separate interpreter entries), it still retains its value. Why? Because the Steel session we're in introduces a scope, a context used in evaluation to resolve symbol bindings.

Another example:

λ > (define c (+ 1000 337))
λ > c
=> 1337

Although evaluation rules differ for define (symbol c is not evaluated, otherwise it would result in an error and make no sense), the value part follows standard rules.

let and local scope define

If define seems reminiscent of global variables to you, you're not alone. While it's suitable for defining function bindings (more on that later) or constants, there must be a better way for local bindings.

Coming from Scheme, Steel has two ways to do it: one is a Lisp classic, let bindings, and the other is local scope define.

let

Allow me to introduce let, a term familiar from our host language, Rust, with the same meaning.

let is used as follows: (let ((symbol-a value-a) (symbol-b value-b) ...) body).

λ > (let ((a (* 25 4))
          (b 3))
      (* a b))
=> 300
λ > a
=> 1337

Three points to note here:

  1. We're dealing with an altered evaluation: the let symbol has no function bound to it, and in nested forms, only certain things get evaluated. This is the effect macros give us.
  2. We've defined two symbols and bound values to them, which have been evaluated to these values in the latest expression inside the let expression.
  3. a became 100, but only within the last expression nested in let, because it introduced a narrower scope. However, it didn't change the previous binding of a, but it shadowed it. And as you can probably guess, b is not defined beyond let.

Local scope define

For the sake of example, we'll use let once again to define a local scope. However, we won't define any symbols with it but use define within the body of let (this shall work with other ways to define a local scope, but we don't know about them yet, so this one should work just enough):

λ > (let ()
      (define a (+ 100 100))
      (define b (+ 2 3))
      (* a b))
=> 1000
λ > a
error[E02]: FreeIdentifier
  ┌─ :1:1
  │
1 │ a
  │ ^ a

λ > b
error[E02]: FreeIdentifier
  ┌─ :1:1
  │
1 │ b
  │ ^ b

As we can see, in Steel, define inside of a local scope could be used instead of let bindings, and it won't alter the outer scope.

Steel

Features

Licensing

Getting Started

Online Playground

The Steel Playground allows you to try Steel out directly from your browser. Visit the Steel Playground at:

https://mattwparas.github.io/steel-playground/dev/

The Steel Playground's environment is as follows:

  • Builtin modules are supported and are automatically imported.
  • Dylibs are not supported.

Output

The output prints the results of all expressions. Additional information can be printed out with the display and displayln functions.

Bytecode

Bytecode renders the Bytecode that Steel generates from the Steel code. The Bytecode is a low level representation of the code that is executed by the Steel interpretter.

Raw AST

Raw AST exposes the parsed AST. This expands some macros. For example:

(define (foo bar)
  (+ bar bar))

(define baz '(1 2 3))

is actually shorthand for

(define foo
  (λ (bar)
    (+ bar bar)))

(define baz (quote 1 2 3))

Expanded AST

This is similar to the Raw AST but provides more detailed information.

Using Steel on its own

Making a Steel Module in Rust

Sometimes you may want to create a Steel module by calling Rust. This can be useful to:

  • Optimize a performance critical codepath with Rust.
  • Take advantage of Rust's rich ecosystem.

Whatever the case, Steel provides facilities to create reusable modules based on Rust code. This process involves compiling Rust into a dylib.

Guide

In this guide, we'll make a dylib that will wrap the sys-info crate. There are roughly 3 steps:

  1. Create a new Rust library of type "cdylib".
  2. Define a module and register types and functions to it.
  3. Build the Steel crate and install it to $STEEL_HOME/native.
  4. Use the library from Steel with #%require-dylib.

Creating a cdylib library

To start, create a new library using cargo:

$ cargo new --lib steel-sys-info

This should create a directory structure as follows:

├── Cargo.toml
├── src
│   └── lib.rs

We'll want to make this a cdylib library for Steel, so we'll perform the following adjustments in Cargo.toml:

  1. Set the crate-type to "cdylib".
  2. Include steel-core with the dylibs feature as a dependency.
  3. Include abi_stable as a dependency. This is required by some steel-core macros.
[package]
name = "steel-sys-info"
version.workspace = true
edition = "2021"

[lib]
name = "steel_sys_info"
crate-type = ["cdylib"]

[dependencies]
# I'm running this example based on the `steel-sys-info` library found in the steel repo. If you're
# running this on your own, use whichever steel version you'd like to target and pin to that.
steel-core = { workspace = true, features = ["dylibs"] }
abi_stable = "0.11.1"
sys-info = "0.9.1"

This means that when we run cargo build we'll produce a shared library (.so file). The shared library can be loaded into other programs, Steel in our case.

Creating a module

For the purposes of this example, we'll create a module that wraps the MemInfo struct, and expose the information there. Since we'll be implementing traits that are defined inside the steel crate, we'll need to create a struct to wrap the sys_info::MemInfo struct:

struct MemoryInfo {
    info: sys_info::MemInfo,
}

impl MemoryInfo {
    fn total(&self) -> isize {
        self.info.total as isize
    }

    fn avail(&self) -> isize {
        self.info.avail as isize
    }

    fn free(&self) -> isize {
        self.info.free as isize
    }

    fn buffers(&self) -> isize {
        self.info.buffers as isize
    }

    fn cached(&self) -> isize {
        self.info.cached as isize
    }

    fn swap_total(&self) -> isize {
        self.info.swap_total as isize
    }

    fn swap_free(&self) -> isize {
        self.info.swap_free as isize
    }
}

Now that we've done that, we can expose this to steel by implementing the Custom type for the struct, and declaring an FFIModule:

// Using ABI Stable types is very important
use steel::{
    declare_module,
    rvals::Custom,
    steel_vm::ffi::{FFIModule, RegisterFFIFn},
};

impl Custom for MemoryInfo {}

declare_module!(create_module);

fn create_module() -> FFIModule {
    let mut module = FFIModule::new("steel/sys-info");

    module.register_fn("mem-info", || MemoryInfo {
        info: sys_info::mem_info().unwrap(),
    });

    module
        .register_fn("MemoryInfo-total", MemoryInfo::total)
        .register_fn("MemoryInfo-avail", MemoryInfo::avail)
        .register_fn("MemoryInfo-free", MemoryInfo::free)
        .register_fn("MemoryInfo-buffers", MemoryInfo::buffers)
        .register_fn("MemoryInfo-cached", MemoryInfo::cached)
        .register_fn("MemoryInfo-swap-total", MemoryInfo::swap_total)
        .register_fn("MemoryInfo-swap-free", MemoryInfo::swap_free);

    module
}

The register_fn API will perform all of the necessary coercions necessary to make this as safe as possible. At the end of the day, this is FFI and we are loading shared libraries, so there is some unsafe Rust code, however steel uses the underlying abi_stable library in order to make interactions with the shared library as safe as possible.

Installing the library

To install the dylib in a location where the steel interpreter will find it, from the root of the library just run:

$ cargo steel-lib

This will build the crate, and copy the resulting dylib to $STEEL_HOME/native.

Using the library from Steel

To load the library, use the syntax #%require-dylib - This operates similary to a standard require, in that all of the modifiers you're used to using work, such as only-in and prefix-in. However, the argument is no longer the path to the library, but rather the name of the library without the extension. By default, the library will be named the [lib] name used in the toml, prefixed with lib.

(#%require-dylib "libsteel_sys_info"
                 (only-in mem-info
                          MemoryInfo-total
                          MemoryInfo-avail
                          MemoryInfo-free
                          MemoryInfo-buffers
                          MemoryInfo-cached
                          MemoryInfo-swap-total
                          MemoryInfo-swap-free))

(provide current-memory-usage memory-usage-as-percentage)

(define (current-memory-usage #:memory-info (memory-info (mem-info)))
  (- (MemoryInfo-total memory-info) (MemoryInfo-free memory-info) (MemoryInfo-cached memory-info)))

(define (memory-usage-as-percentage #:memory-info (memory-info (mem-info)))
  (/ (current-memory-usage #:memory-info memory-info) (MemoryInfo-total memory-info)))

This can then be installed as a library itself on the machine, and required just like any other library, using a cog.scm file for the manifest.

Using Steel as an embedded scripting engine

Steel can be used as a scripting language within a Rust program. You can achieve this by creating a Steel Engine object. The Engine object allows you to execute Scheme code and interact with it from your Rust program. For more details about Engine, see the Engine API.

Steel Engine

The Steel Virtual Machine is provided by the steel-core trait.

[dependencies]
steel-core = { git="https://github.com/mattwparas/steel.git", branch = "master" }

The following example runs a few expressions in a Steel Engine and asserts that the results are as expected.

use steel::steel_vm::engine::Engine;
use steel::SteelVal;

fn main() {
    let mut steel_engine = Engine::new();
    let answer = steel_engine.run(
        (r#"
      (+ 1 2 3 4)
      (+ 5 6 7 8)
    "#),
    );
    assert_eq!(answer, vec![SteelVal::IntV(10), SteelVal::IntV(26)])
}

Engine::new

Creates a new engine. The Engine is used to run Steel Scheme code. Note that the Engine is not Send or Sync. This means it is bound to the current thread and cannot be shared or sent across other threads.

let mut steel_engine = Engine::new();

Engine::run

Runs a Steel expression and returns the result as a Vec<SteelVal>. If any error occurs, then Err(SteelErr) is returned.

let mut steel_engine = Engine::new();
assert_eq!(steel_engine.run("(+ 1 1)"), Ok(vec![SteelVal::IntV(2)]));
assert!(steel_engine.run("(+ 1 undefined-identifier)").is_err());

Embedding The Steel REPL

Repl functionality is provided by the steel-repl crate.

[dependencies]
steel-repl = { git="https://github.com/mattwparas/steel.git", branch = "master" }

run_repl

run_repl runs the Steel repl until an IO error is encountered or the user exits the repl. The repl may be exited by:

  • Running the (quit) Steel Scheme function.
  • Pressing either ctrl+c or ctrl+d within the repl.
let steel_engine = Engine::new();
steel_repl::run_repl(steel_engine).unwrap();

The Engine API

Registering functions

Embedding values

Rust values, types, and functions are easily embedded into Steel. Using the register_fn call, you can embed functions easily:

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<String>) -> Option<String> {
    arg1
}

fn result_function(arg1: Option<String>) -> Result<String, String> {
    if let Some(inner) = arg1 {
        Ok(inner)
    } else {
        Err("Got a none".to_string())
    }
}

pub fn main() {
    let mut vm = Engine::new();

    // Here we can register functions
    // Any function can accept parameters that implement `FromSteelVal` and
    // return values that implement `IntoSteelVal`
    vm.register_fn("external-function", external_function);

    // See the docs for more information about `FromSteelVal` and `IntoSteelVal`
    // but we can see even functions that accept/return Option<T> or Result<T,E>
    // can be registered
    vm.register_fn("option-function", option_function);

    // Result values will map directly to errors in the VM and bubble back up
    vm.register_fn("result-function", result_function);

    vm.run(
        r#"
        (define foo (external-function 10 25))
        (define bar (option-function "applesauce"))
        (define baz (result-function "bananas"))
    "#,
    )
    .unwrap();

    let foo = vm.extract::<usize>("foo").unwrap();
    println!("foo: {}", foo);
    assert_eq!(35, foo);

    // Can also extract a value by specifying the type on the variable
    let bar: String = vm.extract("bar").unwrap();
    println!("bar: {}", bar);
    assert_eq!("applesauce".to_string(), bar);

    let baz: String = vm.extract("baz").unwrap();
    println!("baz: {}", baz);
    assert_eq!("bananas".to_string(), baz);
}

We can also embed structs themselves:

use steel_vm::engine::Engine;
use steel_vm::register_fn::RegisterFn;

use steel_derive::Steel;

// In order to register a type with Steel,
// it must implement Clone, Debug, and Steel
#[derive(Clone, Debug, Steel, PartialEq)]
pub struct ExternalStruct {
    foo: usize,
    bar: String,
    baz: f64,
}

impl ExternalStruct {
    pub fn new(foo: usize, bar: String, baz: f64) -> Self {
        ExternalStruct { foo, bar, baz }
    }

    // Embedding functions that take self must take by value
    pub fn method_by_value(self) -> usize {
        self.foo
    }

    // Setters should update the value and return a new instance (functional set)
    pub fn set_foo(mut self, foo: usize) -> Self {
        self.foo = foo;
        self
    }
}

pub fn main() {
    let mut vm = Engine::new();

    // Registering a type gives access to a predicate for the type
    vm.register_type::<ExternalStruct>("ExternalStruct?");

    // Structs in steel typically have a constructor that is the name of the struct
    vm.register_fn("ExternalStruct", ExternalStruct::new);

    // register_fn can be chained
    vm.register_fn("method-by-value", ExternalStruct::method_by_value)
        .register_fn("set-foo", ExternalStruct::set_foo);

    let external_struct = ExternalStruct::new(1, "foo".to_string(), 12.4);

    // Registering an external value is fallible if the conversion fails for some reason
    // For instance, registering an Err(T) is fallible. However, most implementation outside of manual
    // ones should not fail
    vm.register_external_value("external-struct", external_struct)
        .unwrap();

    let output = vm
        .run(
            r#"
            (define new-external-struct (set-foo external-struct 100))
            (define get-output (method-by-value external-struct))
            (define second-new-external-struct (ExternalStruct 50 "bananas" 72.6))
            "last-result"
        "#,
        )
        .unwrap();

    let new_external_struct = vm.extract::<ExternalStruct>("new-external-struct").unwrap();
    println!("new_external_struct: {:?}", new_external_struct);
    assert_eq!(
        ExternalStruct::new(100, "foo".to_string(), 12.4),
        new_external_struct
    );

    // Can also extract a value by specifying the type on the variable
    let get_output: usize = vm.extract("get-output").unwrap();
    println!("get_output: {}", get_output);
    assert_eq!(1, get_output);

    let second_new_external_struct: ExternalStruct =
        vm.extract("second-new-external-struct").unwrap();
    println!(
        "second_new_external_struct: {:?}",
        second_new_external_struct
    );
    assert_eq!(
        ExternalStruct::new(50, "bananas".to_string(), 72.6),
        second_new_external_struct
    );

    // We also get the output of the VM as the value of every expression run
    // we can inspect the results just by printing like so
    println!("{:?}", output);
}

IntoSteelVal and FromSteelVal

Types that implement IntoSteelVal and FromSteelVal and be returned and passed into rust functions, respectively. Take the following for example:

#![allow(unused)]

fn main() {
fn foo(value: isize) -> String {
    ...
}
    
}

This means that steel values will attempt to be coerced to the type in the function signature, and the value that this function returns will then attempt to be coerced into a that Steel understands.

There are some special case conversions that happen specifically:

IntoSteelVal

  • Vec -> Steel list
  • HashMap<K, V> -> Steel hashmap
  • HashSet -> Steel hashset
  • Result<T, E> -> if Ok(T) then T else (error E)
  • Option -> if Some(T) then T else #false

Language Reference

Keywords

Syntax

Macros

Steel contains a limited form of the syntax-rules that scheme provides. These macros build on the small primary language constructs that exist. Consider the following:

(define-syntax or
  (syntax-rules ()
    [(or) #f]
    [(or x) x]
    [(or x y) (let ([z x])
                (if z z y))]
    [(or x y ...) (or x (or y ...))]))

(or #f #f #t)

This will actually expand into something like this

((λ (__z)
     (if __z __z ((λ (__z) (if __z __z #t)) #f)))
   #f)

These macros allow for a simple extension of Steel to however you see fit - defining macros in terms of the syntax rules format is fairly straightforward.

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:

;; 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:

;; 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:

;; 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:

(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?

Transducers

Inspired by clojure's transducers, Steel has a similar object that is somewhere half way in between transducers and iterators. Consider the following:


(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 execute and transduce. Execute takes a transducer (i.e. <#iterator>) and a collection that can be iterated (list, vector, or stream) and applies the transducer.

;; Accepts lists
(execute (mapping (lambda (x) (+ x 1))) (list 1 2 3 4 5)) ;; => '(2 3 4 5 6)

;; Accepts vectors
(execute (mapping (lambda (x) (+ x 1))) (vector 1 2 3 4 5)) ;; '#(2 3 4 5 6)

;; Even accepts streams!
(define (integers n)
    (stream-cons n (lambda () (integers (+ 1 n)))))

(execute (taking 5) (integers 0)) ;; => '(0 1 2 3 4)

Transduce is just reduce with more bells and whistles and works similarly:

;; (-> transducer reducing-function initial-value iterable)
(transduce (mapping (lambda (x) (+ x 1))) + 0 (list 0 1 2 3)) ;; => 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:

(define xf 
    (compose 
        (mapping add1)
        (filtering odd?)
        (taking 5)))

(execute xf (range 0 100)) ;; => '(1 3 5 7 9)

By default, execute outputs to the same type that was passed in. In other words, if you execute a list, it will return a list. However, if you so choose, you can pass in a symbol specifying the output type of your choosing like so:

(define xf 
    (compose 
        (mapping add1)
        (filtering odd?)
        (taking 5)))

;; Takes a list and returns a vector
(execute xf (range 0 100) 'vector) ;; => '#(1 3 5 7 9)

;; Takes a vector and returns a list
(execute xf (vector 0 1 2 3 4 5 6 7 8 9 10) 'list) ;; => '(1 3 5 7 9)

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:

;; 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

Modifiers for requiring


(require (prefix-in export. ;; prefix-in will prefix all of the bound identifiers with the given prefix
                    (only-in "export.scm" ;; only-in will only introduce the identifiers listed.
                             thing-should-not-escape
                             Applesauce
                             bananas
                             new-identifier
                             my-fun-contracted-function
                             contract-out-test)))

If no modifiers are given, require-ing a module will introduce all of the provided values into the top level scope.

Providing and requiring macros;

Macros defined using define-syntax can be handled just like normal values:

;; main.scm
(require "provide.scm")

(foo-bar x) ;; Will expand into (displayln "Hello world")

;; provide.scm
(provide foo-bar)

(define-syntax foo-bar 
    (syntax-rules ()
        [(foo-bar x) (displayln "Hello world!")]))

The module system will take care to keep the namespaces separate - any macros that expand into macros that exist in the originating module will be expanded, but those will not be available to the requiring module. In addition, macros can expand into private values (those that are not provided), and the will still be inaccessible from the requiring module.

Functions

keyword?


(keyword? v) -> boolean?

v: any/c

mutable-vector


(mutable-vector args ...) -> mutable-vector?

struct

Macro for creating a new struct, in the form of: (struct <struct-name> (fields ...) options ...) The options can consist of the following:

Single variable options (those which their presence indicates #true)

  • #:mutable
  • #:transparent

Other options must be presented as key value pairs, and will get stored in the struct instance. They will also be bound to the variable ___<struct-name>-options___ in the same lexical environment where the struct was defined. For example:

λ > (struct Applesauce (a b c) #:mutable #:transparent #:unrecognized-option 1234)
λ > ___Applesauce-options___
=> #<hashmap {
    '#:fields: '(a b c),
    '#:mutable: #false,
    '#:transparent: #false,
    '#:unrecognized-option: 1234,
}> 

By default, structs are immutable, which means setter functions will not be generated. Also by default, structs are not transparent, which means printing them will result in an opaque struct that does not list the fields

cogs/logging/log.scm

log!

(log! level arg-list)

Log directly on the specified level the with arguments, as a list

log/info!

(log/info! . args)

Log the arguments using the info target, i.e. log on INFO

log/warn!

(log/warn! . args)

Log the arguments using the warn target, i.e. log on WARN

log/debug!

(log/debug! . args)

Log the arguments using the debug target, i.e. log on DEBUG

log/error!

(log/error! . args)

Log the arguments using the error target, i.e. log on ERROR

log/trace!

(log/trace! . args)

Log the arguments using the trace target, i.e. log on TRACE

steel/core/option

steel/core/result

steel/identity

steel/hash

hash-try-get

Gets the key from the given map. Returns #false if the key does not exist.

(hash-try-get map key) -> (or any/c #false)

  • map : hash?
  • key : any/c

Examples

> (hash-try-get (hash 'a 10 'b 20) 'b) ;; => 20
> (hash-try-get (hash 'a 10 'b 20) 'does-not-exist) ;; => #false

hash-length

Returns the number of key value pairs in the map

(hash-length map) -> (and positive? int?)

  • map : hash?

Examples

> (hash-length (hash 'a 10 'b 20)) ;; => 2

hash-ref

Gets the key from the given map. Raises an error if the key does not exist. hash-get is an alias for this.

(hash-ref map key) -> any/c

  • map : hash?
  • key : any/c

Examples

> (hash-get (hash 'a 10 'b 20) 'b) ;; => 20

hash-contains?

Checks whether the given map contains the given key. Key must be hashable.

(hash-contains? map key) -> bool?

  • map : hash?
  • key : hashable?

Example

> (hash-contains? (hash 'a 10 'b 20) 'a) ;; => #true
> (hash-contains? (hash 'a 10 'b 20) 'not-there) ;; => #false

hash-insert

Returns a new hashmap with the additional key value pair added. Performs a functional update, so the old hash map is still accessible.

(hash-insert map key val) -> hash?

  • map : hash?
  • key : any/c
  • val : any/c

Examples

> (hash-insert (hash 'a 10 'b 20) 'c 30)

=> #<hashmap {
'a: 10,
'b: 20,
'c: 30
}>

steel/random

steel/io

steel/equality

steel/ord

steel/json

steel/sets

steel/time

steel/time

steel/time

Contains direct wrappers around the Rust std::time::Instant and std::time::Duration modules. For example, to measure the time something takes:

(define t (instant/now))
(displayln "Hello world")
(displayln (instant/elapsed t))

steel/vectors

steel/ports

read-port-to-string

Takes a port and reads the entire content into a string

(read-port-to-string port) -> string?

  • port : input-port?

output-port?

Checks if a given value is an output port

(output-port? any/c) -> bool?

Examples

> (define output (open-output-file "foo.txt"))
> (output-port? output) ;; => #true

open-input-file

Takes a filename path referring to an existing file and returns an input port. Raises an error if the file does not exist

(open-input-file string?) -> input-port?

Examples

> (open-input-file "foo-bar.txt") ;; => #<port>
> (open-input-file "file-does-not-exist.txt")
error[E08]: Io
┌─ :1:2
│
1 │ (open-input-file "foo-bar.txt")
│  ^^^^^^^^^^^^^^^ No such file or directory (os error 2)

stdin

Gets the port handle to stdin

(stdin) -> input-port?

Examples

> (stdin) ;; => #<port>

input-port?

Checks if a given value is an input port

(input-port? any/c) -> bool?

Examples

> (input-port? (stdin)) ;; => #true
> (input-port? "foo") ;; => #false

open-output-file

Takes a filename path referring to a file to be created and returns an output port.

(open-output-file string?) -> output-port?

Examples

> (open-output-file "foo-bar.txt") ;; => #<port>

steel/strings

starts-with?

Checks if the input string starts with a prefix

(starts-with? input pattern) -> bool?

input : string? pattern: string?

Examples

> (starts-with? "foobar" "foo") ;; => #true
> (starts-with? "foobar" "bar") ;; => #false

split-whitespace

Returns a list of strings from the original string split on the whitespace

(split-whitespace string?) -> (listof string?)

Examples

(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")

trim-end

Returns a new string with the trailing whitespace removed.

(trim string?) -> string?

Examples

> (trim "   foo     ") ;; => "   foo"

string->int

Converts a string into an int. Raises an error if the string cannot be converted to an integer.

(string->int string?) -> int?

Examples

> (string->int "100") ;; => 10
> (string->int "not-an-int") ;; error

ends-with?

Checks if the input string ends with a given suffix

(ends-with? input pattern) -> bool?

input : string? pattern: string?

Examples

> (ends-with? "foobar" "foo") ;; => #false
> (ends-with? "foobar" "bar") ;; => #true

string-length

Get the length of the given string

(string-length string?) -> int?

Examples

> (string-length "apples") ;; => 6

trim-start

Returns a new string with the leading whitespace removed.

(trim string?) -> string?

Examples

> (trim "   foo     ") ;; => "foo     "

string->upper

Creates a new uppercased version of the input string

(string->upper string?) -> string?

Examples

> (string->upper "lower") ;; => "LOWER"

trim

Returns a new string with the leading and trailing whitespace removed.

(trim string?) -> string?

Examples

> (trim "   foo     ") ;; => "foo"

string->list

Converts a string into a list of characters.

(string->list string?) -> (listof char?)

Examples

> (string->list "hello") ;; => '(#\h #\e #\l #\l #\o)

string->symbol

Converts a string into a symbol.

(string->symbol string?) -> symbol?

Examples

> (string->symbol "FooBar") ;; => 'FooBar

int->string

Converts an integer into a string.

(int->string int?) -> string?

Examples

> (int->string 10) ;; => "10"

string-append

Concatenates all of the given strings into one

(string-append strs...) -> string?

  • strs ... : string?

Examples

> (string-append) ;; => ""
> (string-append "foo" "bar") ;; => "foobar"
### **string->lower**
Creates a new lowercased version of the input string

(string->lower string?) -> string?

#### Examples

```scheme
> (string->lower "sPonGeBoB tExT") ;; => "spongebob text"

to-string

Concatenatives all of the inputs to their string representation, separated by spaces.

(to-string xs ...)

  • xs : any/c

Examples

> (to-string 10) ;; => "10"
> (to-string "hello" "world") ;; => "hello world"

steel/filesystem

steel/syntax

steel/transducers

steel/symbols

steel/strings/colors

steel/meta

steel/base

read-port-to-string

Takes a port and reads the entire content into a string

(read-port-to-string port) -> string?

  • port : input-port?

hash-contains?

Checks whether the given map contains the given key. Key must be hashable.

(hash-contains? map key) -> bool?

  • map : hash?
  • key : hashable?

Example

> (hash-contains? (hash 'a 10 'b 20) 'a) ;; => #true
> (hash-contains? (hash 'a 10 'b 20) 'not-there) ;; => #false

list-ref

Returns the value located at the given index. Will raise an error if you try to index out of bounds.

Note: Runs in time proportional to the length of the list, however lists in Steel are implemented in such a fashion that the time complexity is O(n/64). Meaning, for small lists this can be constant.

(list-ref lst index) -> list?

  • lst : list?
  • index : (and/c int? positive?)

Examples

> (list-ref (list 1 2 3 4) 2) ;; => 3
> (list-ref (range 0 100) 42) ;; => 42"
> (list-ref (list 1 2 3 4) 10)
error[E11]: Generic
┌─ :1:2
│
1 │ (list-ref (list 1 2 3 4) 10)
│  ^^^^^^^^ out of bounds index in list-ref - list length: 4, index: 10

second

Get the second element of the list. Raises an error if the list does not have an element in the second position.

(second l) -> any/c

  • l : list?

Examples

> (second '(1 2 3)) ;; => 2
> (second '())
error[E11]: Generic
┌─ :1:2
│
1 │ (second '())
│  ^^^^^^ second: index out of bounds - list did not have an element in the second position: []
### **to-string**
Concatenatives all of the inputs to their string representation, separated by spaces.

(to-string xs ...)

* xs : any/c

#### Examples
```scheme
> (to-string 10) ;; => "10"
> (to-string "hello" "world") ;; => "hello world"

output-port?

Checks if a given value is an output port

(output-port? any/c) -> bool?

Examples

> (define output (open-output-file "foo.txt"))
> (output-port? output) ;; => #true

open-output-file

Takes a filename path referring to a file to be created and returns an output port.

(open-output-file string?) -> output-port?

Examples

> (open-output-file "foo-bar.txt") ;; => #<port>

range

Returns a newly allocated list of the elements in the range (n, m]

(range n m) -> (listof int?)

  • n : int?
  • m : int?
> (range 0 10) ;; => '(0 1 2 3 4 5 6 7 8 9)

string-length

Get the length of the given string

(string-length string?) -> int?

Examples

> (string-length "apples") ;; => 6

car

Returns the first element of the list l.

(car l) -> any/c

  • l : list?

Examples

> (car '(1 2)) ;; => 1
> (car (cons 2 3)) ;; => 2

hash-ref

Gets the key from the given map. Raises an error if the key does not exist. hash-get is an alias for this.

(hash-ref map key) -> any/c

  • map : hash?
  • key : any/c

Examples

> (hash-get (hash 'a 10 'b 20) 'b) ;; => 20

first

Returns the first element of the list l.

(first l) -> any/c

  • l : list?

Examples

> (first '(1 2)) ;; => 1
> (first (cons 2 3)) ;; => 2

split-whitespace

Returns a list of strings from the original string split on the whitespace

(split-whitespace string?) -> (listof string?)

Examples

(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")

list

Returns a newly allocated list containing the vs as its elements.

(list v ...) -> list?

  • v : any/c

Examples

> (list 1 2 3 4 5) ;; => '(1 2 3 4 5)
> (list (list 1 2) (list 3 4)) ;; => '((1 2) (3 4))

hash-try-get

Gets the key from the given map. Returns #false if the key does not exist.

(hash-try-get map key) -> (or any/c #false)

  • map : hash?
  • key : any/c

Examples

> (hash-try-get (hash 'a 10 'b 20) 'b) ;; => 20
> (hash-try-get (hash 'a 10 'b 20) 'does-not-exist) ;; => #false

pair?

Checks if the given value can be treated as a pair. Note - there are no improper lists in steel, so any list with at least one element is considered a pair.

(pair? any/c) -> bool?

Examples

> (pair? '(10 20)) ;; => #true
> (pair? '(10)) ;; => #true
> (pair? '()) ;; => #false

steel/lists

steel/lists

Lists in Steel have an interface that matches those of classic schemes or lisps. At face value, they appear to be implemented as cons cells - however, under the hood they are actually implemented as unrolled linked lists.

This means that for most practical purposes, interaction with lists is the same. That being said, there are no improper lists, meaning, pairs are actually just lists of two elements.

Indexing into a list also takes O(n/64) - which means you'll get constant time indexing on small lists.

(list 10 20 30 40) ;; => '(10 20 30 40)

third

Get the third element of the list. Raises an error if the list does not have an element in the third position.

(third l) -> any/c

  • l : list?

Examples

> (third '(1 2 3)) ;; => 3
> (third '())
error[E11]: Generic
┌─ :1:2
│
1 │ (third '())
│  ^^^^^^ third: index out of bounds - list did not have an element in the second position: []

hash-length

Returns the number of key value pairs in the map

(hash-length map) -> (and positive? int?)

  • map : hash?

Examples

> (hash-length (hash 'a 10 'b 20)) ;; => 2

string->upper

Creates a new uppercased version of the input string

(string->upper string?) -> string?

Examples

> (string->upper "lower") ;; => "LOWER"

trim

Returns a new string with the leading and trailing whitespace removed.

(trim string?) -> string?

Examples

> (trim "   foo     ") ;; => "foo"

starts-with?

Checks if the input string starts with a prefix

(starts-with? input pattern) -> bool?

input : string? pattern: string?

Examples

> (starts-with? "foobar" "foo") ;; => #true
> (starts-with? "foobar" "bar") ;; => #false

open-input-file

Takes a filename path referring to an existing file and returns an input port. Raises an error if the file does not exist

(open-input-file string?) -> input-port?

Examples

> (open-input-file "foo-bar.txt") ;; => #<port>
> (open-input-file "file-does-not-exist.txt")
error[E08]: Io
┌─ :1:2
│
1 │ (open-input-file "foo-bar.txt")
│  ^^^^^^^^^^^^^^^ No such file or directory (os error 2)

input-port?

Checks if a given value is an input port

(input-port? any/c) -> bool?

Examples

> (input-port? (stdin)) ;; => #true
> (input-port? "foo") ;; => #false

ends-with?

Checks if the input string ends with a given suffix

(ends-with? input pattern) -> bool?

input : string? pattern: string?

Examples

> (ends-with? "foobar" "foo") ;; => #false
> (ends-with? "foobar" "bar") ;; => #true

string-append

Concatenates all of the given strings into one

(string-append strs...) -> string?

  • strs ... : string?

Examples

> (string-append) ;; => ""
> (string-append "foo" "bar") ;; => "foobar"
### **steel/time**


#### steel/time
    
Contains direct wrappers around the Rust `std::time::Instant` and `std::time::Duration` modules. 
For example, to measure the time something takes:

```scheme
(define t (instant/now))
(displayln "Hello world")
(displayln (instant/elapsed t))

trim-end

Returns a new string with the trailing whitespace removed.

(trim string?) -> string?

Examples

> (trim "   foo     ") ;; => "   foo"

string->list

Converts a string into a list of characters.

(string->list string?) -> (listof char?)

Examples

> (string->list "hello") ;; => '(#\h #\e #\l #\l #\o)

empty?

Checks if the list is empty

(empty? lst) -> bool?

  • lst: list?

Examples

> (empty? (list 1 2 3 4 5)) ;; => #false
> (empty? '()) ;; => #true

int->string

Converts an integer into a string.

(int->string int?) -> string?

Examples

> (int->string 10) ;; => "10"

trim-start

Returns a new string with the leading whitespace removed.

(trim string?) -> string?

Examples

> (trim "   foo     ") ;; => "foo     "

stdin

Gets the port handle to stdin

(stdin) -> input-port?

Examples

> (stdin) ;; => #<port>

hash-insert

Returns a new hashmap with the additional key value pair added. Performs a functional update, so the old hash map is still accessible.

(hash-insert map key val) -> hash?

  • map : hash?
  • key : any/c
  • val : any/c

Examples

> (hash-insert (hash 'a 10 'b 20) 'c 30)

=> #<hashmap {
'a: 10,
'b: 20,
'c: 30
}>

string->lower

Creates a new lowercased version of the input string

(string->lower string?) -> string?

Examples

> (string->lower "sPonGeBoB tExT") ;; => "spongebob text"

string->int

Converts a string into an int. Raises an error if the string cannot be converted to an integer.

(string->int string?) -> int?

Examples

> (string->int "100") ;; => 10
> (string->int "not-an-int") ;; error

string->symbol

Converts a string into a symbol.

(string->symbol string?) -> symbol?

Examples

> (string->symbol "FooBar") ;; => 'FooBar

steel/process

steel/numbers

steel/streams

steel/constants

steel/threads

steel/lists

steel/lists

steel/lists

Lists in Steel have an interface that matches those of classic schemes or lisps. At face value, they appear to be implemented as cons cells - however, under the hood they are actually implemented as unrolled linked lists.

This means that for most practical purposes, interaction with lists is the same. That being said, there are no improper lists, meaning, pairs are actually just lists of two elements.

Indexing into a list also takes O(n/64) - which means you'll get constant time indexing on small lists.

(list 10 20 30 40) ;; => '(10 20 30 40)

pair?

Checks if the given value can be treated as a pair. Note - there are no improper lists in steel, so any list with at least one element is considered a pair.

(pair? any/c) -> bool?

Examples

> (pair? '(10 20)) ;; => #true
> (pair? '(10)) ;; => #true
> (pair? '()) ;; => #false

car

Returns the first element of the list l.

(car l) -> any/c

  • l : list?

Examples

> (car '(1 2)) ;; => 1
> (car (cons 2 3)) ;; => 2

third

Get the third element of the list. Raises an error if the list does not have an element in the third position.

(third l) -> any/c

  • l : list?

Examples

> (third '(1 2 3)) ;; => 3
> (third '())
error[E11]: Generic
┌─ :1:2
│
1 │ (third '())
│  ^^^^^^ third: index out of bounds - list did not have an element in the second position: []

empty?

Checks if the list is empty

(empty? lst) -> bool?

  • lst: list?

Examples

> (empty? (list 1 2 3 4 5)) ;; => #false
> (empty? '()) ;; => #true

range

Returns a newly allocated list of the elements in the range (n, m]

(range n m) -> (listof int?)

  • n : int?
  • m : int?
> (range 0 10) ;; => '(0 1 2 3 4 5 6 7 8 9)

first

Returns the first element of the list l.

(first l) -> any/c

  • l : list?

Examples

> (first '(1 2)) ;; => 1
> (first (cons 2 3)) ;; => 2

list

Returns a newly allocated list containing the vs as its elements.

(list v ...) -> list?

  • v : any/c

Examples

> (list 1 2 3 4 5) ;; => '(1 2 3 4 5)
> (list (list 1 2) (list 3 4)) ;; => '((1 2) (3 4))

second

Get the second element of the list. Raises an error if the list does not have an element in the second position.

(second l) -> any/c

  • l : list?

Examples

> (second '(1 2 3)) ;; => 2
> (second '())
error[E11]: Generic
┌─ :1:2
│
1 │ (second '())
│  ^^^^^^ second: index out of bounds - list did not have an element in the second position: []
### **list-ref**
Returns the value located at the given index. Will raise an error if you try to index out of bounds.

Note: Runs in time proportional to the length of the list, however lists in Steel are implemented in such a fashion that the
time complexity is O(n/64). Meaning, for small lists this can be constant.

(list-ref lst index) -> list?

* lst : list?
* index : (and/c int? positive?)

#### Examples
```scheme
> (list-ref (list 1 2 3 4) 2) ;; => 3
> (list-ref (range 0 100) 42) ;; => 42"
> (list-ref (list 1 2 3 4) 10)
error[E11]: Generic
┌─ :1:2
│
1 │ (list-ref (list 1 2 3 4) 10)
│  ^^^^^^^^ out of bounds index in list-ref - list length: 4, index: 10

steel/contracts

steel/base

*

Multiplies the given numbers.

(* . nums) -> number?

  • nums : number? - The numbers to multiply. Can have any number of arguments including zero.

Examples

> (* 5 3) ;; => 15
> (* 10 3 2) ;; => 60
> (*) ;; => 1

+

Adds the given numbers.

(+ . nums) -> number?

  • nums : number? - The numbers to add. Can have any number of arguments including zero.

Examples

> (+ 5 3) ;; => 8
> (+ 10 3 2) ;; => 15
> (+) ;; => 0

-

Subtracts the given numbers.

(- . nums) -> number?

  • nums : number? - The numbers to subtract. Must have at least one number.

Examples

> (- 5 3) ;; => 2
> (- 10 3 2) ;; => 5
> (- -5) ;; => 5

/

Divides the given numbers.

(/ . nums) -> number?

  • nums : number? - The numbers to divide. Must have at least one number.

Examples

> (/ 10 2) ;; => 5
> (/ 10 2 2.0) ;; => 2.5
> (/ 1 3.0) ;; => 0.3333333333333333
> (/ 1 3) ;; => 1/3

<

Compares real numbers to check if any number is less than the subsequent.

(< x . rest) -> bool?

  • x : real? - The first real number to compare.
  • rest : real? - The rest of the numbers to compare.

Examples

> (< 1) ;; => #t
> (< 3 2) ;; => #f
> (< 2 3) ;; => #t
> (< 3/2 1.5) ;; => #f
> (< 2.5 3/2) ;; => #t
> (< 2 5/2 3) ;; #t

<=

Compares real numbers to check if any number is less than or equal than the subsequent.

(<= x . rest) -> bool?

  • x : real? - The first real number to compare.
  • rest : real? - The rest of the numbers to compare.

Examples

> (<= 1) ;; => #t
> (<= 3 2) ;; => #f
> (<= 2 3) ;; => #t
> (<= 3/2 1.5) ;; => #t
> (<= 2.5 3/2) ;; => #f
> (<= 2 6/2 3) ;; #t

>

Compares real numbers to check if any number is greater than the subsequent.

(> x . rest) -> bool?

  • x : real? - The first real number to compare.
  • rest : real? - The rest of the numbers to compare.

Examples

> (> 1) ;; => #t
> (> 3 2) ;; => #t
> (> 1 1) ;; => #f
> (> 3/2 1.5) ;; => #f
> (> 3/2 1.4) ;; => #t
> (> 3 4/2 1) ;; #t

>=

Compares real numbers to check if any number is greater than or equal than the subsequent.

(>= x . rest) -> bool?

  • x : real? - The first real number to compare.
  • rest : real? - The rest of the numbers to compare.

Examples

> (>= 1) ;; => #t
> (>= 3 2) ;; => #t
> (>= 2 3) ;; => #f
> (>= 3/2 1.5) ;; => #t
> (>= 3/2 1.4) ;; => #t
> (>= 2 4/2 1) ;; #t

abs

Computes the absolute value of the given number.

(abs number) -> number?

  • number : number? - The number to compute the absolute value of.

Examples

> (abs 42) ;; => 42
> (abs -42) ;; => 42
> (abs 0) ;; => 0

append

Appends the given lists together. If provided with no lists, will return the empty list.

(append lst ...)

lst : list?

Examples

> (append (list 1 2) (list 3 4)) ;; => '(1 2 3 4)
> (append) ;; => '()

apply

Applies the given function with arguments as the contents of the list.

(apply function lst) -> any?

  • function : function?
  • list: list?

Examples

> (apply + (list 1 2 3 4)) ;; => 10
> (apply list (list 1 2 3 4)) ;; => '(1 2 3 4)

byte?

Returns #t if the given value is a byte, meaning an exact integer between 0 and 255 inclusive, #f otherwise.

Examples

(byte? 65) ;; => #t
(byte? 0) ;; => #t
(byte? 256) ;; => #f
(byte? 100000) ;; => #f
(byte? -1) ;; => #f

bytes

Returns a new mutable vector with each byte as the given arguments. Each argument must satisfy the byte? predicate, meaning it is an exact integer range from 0 - 255 (inclusive)

(bytes b ...)

  • b : byte?

Examples

(bytes 65 112 112 108 101)

bytes->list

Converts the bytevector to the equivalent list representation.

Examples

(bytes->list (bytes 0 1 2 3 4 5)) ;; => '(0 1 2 3 4 5)

bytes->string/utf8

Decodes a string from a bytevector containing valid UTF-8.

(bytes->string/utf8 buf [start] [end]) -> string?

  • buf : bytes?
  • start: int? = 0
  • end: int? = (bytes-length buf)

Examples

(bytes->string/utf8 (bytes #xe5 #x8d #x83 #xe8 #x91 #x89)) ;; => "千葉"

bytes-append

Append multiple byte vectors into a new bytevector.

Examples

(bytes-append #u8(0 1 2) #u8(3 4 5)) ;; => #u8(#x00 #x01 #x02 #x03 #x04 #x05)

(bytes-append #u8(0) #u8(1) #u8() #u8(2)) ;; => #u8(#x00 #x01 #x02)

bytes-length

Returns the length of the given byte vector

Examples

(bytes-length (bytes 1 2 3 4 5)) ;; => 5

bytes-ref

Fetches the byte at the given index within the bytevector. If the index is out of bounds, this will error.

(bytes-ref vector index)

  • vector : bytes?
  • index: (and exact? int?)

Examples

(bytes-ref (bytes 0 1 2 3 4 5) 3) ;; => 4
(bytes-ref (bytes) 10) ;; error

bytes-set!

Sets the byte at the given index to the given byte. Will error if the index is out of bounds.

(bytes-set! vector index byte)

  • vector : bytes?
  • index: (and exact? int?)
  • byte: byte?

Examples

(define my-bytes (bytes 0 1 2 3 4 5))
(bytes-set! my-bytes 0 100)
(bytes-ref my-bytes 0) ;; => 100

bytes?

Returns #t if this value is a bytevector

Examples

(bytes? (bytes 0 1 2)) ;; => #t
(bytes? (list 10 20 30)) ;; => #f

bytevector

Returns a new mutable vector with each byte as the given arguments. Each argument must satisfy the byte? predicate, meaning it is an exact integer range from 0 - 255 (inclusive)

(bytevector b ...)

  • b : byte?

Examples

(bytevector 65 112 112 108 101)

bytevector-copy

Creates a copy of a bytevector.

(bytevector-copy vector [start end]) -> bytes?

  • vector : bytes?
  • start: int? = 0
  • end: int? = (bytes-length vector)

Examples

(define vec (bytes 1 2 3 4 5))

(bytevector-copy vec) ;; => (bytes 1 2 3 4 5)
(bytevector-copy vec 1 3) ;; => (bytes 2 3)

canonicalize-path

Returns canonical path with all components normalized

car

Returns the first element of the list l.

(car l) -> any/c

  • l : list?

Examples

> (car '(1 2)) ;; => 1
> (car (cons 2 3)) ;; => 2

cdr

Returns the rest of the list. Will raise an error if the list is empty.

(cdr l) -> list?

  • l : list?

Examples

> (cdr (list 10 20 30)) ;; => '(20 30)
> (cdr (list 10)) ;; => '()
> (cdr '())
error[E11]: Generic
┌─ :1:2
│
1 │ (cdr '())
│  ^^^ cdr expects a non empty list

ceiling

Rounds the given number up to the nearest integer not less than it.

(ceiling number) -> integer?

  • number : number? - The number to round up.

Examples

> (ceiling 42) ;; => 42
> (ceiling 42.1) ;; => 43
> (ceiling -42.1) ;; => -42

char->integer

Returns the Unicode codepoint of a given character.

(char->integer char?) -> integer?

char->number

Attemps to convert the character into a decimal digit, and returns #f on failure.

char-digit?

Returns #t if the character is a decimal digit.

char-downcase

Returns the lower case version of a character, if defined by Unicode, or the same character otherwise.

char-upcase

Returns the upper case version of a character, if defined by Unicode, or the same character otherwise.

char-whitespace?

Returns #t if the character is a whitespace character.

char<=?

Compares characters according to their codepoints, in a "less-than-or-equal" fashion.

(char<=? char1 char2 ... ) -> bool?

  • char1 : char?
  • char2 : char?

char<?

Compares characters according to their codepoints, in a "less-than" fashion.

(char<? char1 char2 ... ) -> bool?

  • char1 : char?
  • char2 : char?

char=?

Checks if all characters are equal.

Requires that all inputs are characters, and will otherwise raise an error.

(char=? char1 char2 ...) -> bool?

  • char1 : char?
  • char2 : char?

char>=?

Compares characters according to their codepoints, in a "greater-than-or-equal" fashion.

(char>=? char1 char2 ... ) -> bool?

  • char1 : char?
  • char2 : char?

char>?

Compares characters according to their codepoints, in a "greater-than" fashion.

(char>? char1 char2 ... ) -> bool?

  • char1 : char?
  • char2 : char?

command-line

Returns the command line passed to this process, including the command name as first argument.

complex?

Checks if the given value is a complex number

(complex? value) -> boolean?

  • value : any - The value to check

Examples

> (complex? 3+4i) ;; => #t
> (complex? 42) ;; => #t
> (complex? "hello") ;; => #f

cons

Returns a newly allocated list whose first element is a and second element is d.

(cons a d) -> list?

  • a : any/c
  • d : any/c

Examples

> (cons 1 2) ;; => '(1 . 2)
> (cons 1 '()) ;; => '(1)

copy-directory-recursively!

Recursively copies the directory from source to destination

create-directory!

Creates the directory

current-directory

Check the current working directory

current-inexact-milliseconds

Returns the number of milliseconds since the Unix epoch as an inexact number.

(current-inexact-milliseconds) -> inexact?

current-milliseconds

Returns the number of milliseconds since the Unix epoch as an integer.

(current-milliseconds) -> int?

current-second

Returns the number of seconds since the Unix epoch as an integer.

(current-second) -> int?

delete-directory!

Deletes the directory

delete-file!

Deletes the file

denominator

Retrieves the denominator of the given rational number.

(denominator number) -> integer?

  • number : number? - The rational number to retrieve the denominator from.

Examples

> (denominator 1/2) ;; => 2
> (denominator 3/4) ;; => 4
> (denominator 4) ;; => 1

disconnected-channel-object?

Returns #t if the value is an disconnected-channel object.

(eof-object? any/c) -> bool?

duration->string

Returns a string representation of a duration

(duration->string dur)

  • dur : duration?

empty-channel-object?

Returns #t if the value is an empty-channel object.

(empty-channel-object? any/c) -> bool?

empty?

Checks if the list is empty

(empty? lst) -> bool?

  • lst: list?

Examples

> (empty? (list 1 2 3 4 5)) ;; => #false
> (empty? '()) ;; => #true

ends-with?

Checks if the input string ends with a given suffix

(ends-with? input pattern) -> bool?

input : string? pattern: string?

Examples

> (ends-with? "foobar" "foo") ;; => #false
> (ends-with? "foobar" "bar") ;; => #true

eof-object

Returns an EOF object.

(eof-object) -> eof-object?

eof-object?

Returns #t if the value is an EOF object.

(eof-object? any/c) -> bool?

error-object-message

Returns the message of an error object.

(error-object-message error?) -> string?

exact->inexact

Converts an exact number to an inexact number.

(exact->inexact num) -> number?

  • num : number? - The number to convert from exact to inexact.

Examples

> (exact->inexact 10) ;; => 10
> (exact->inexact 1/2) ;; => 0.5
> (exact->inexact 1+2i) ;; => 1+2i

exact-integer-sqrt

Computes the integer square root of the given non-negative integer.

(exact-integer-sqrt number) -> (integer? integer?)

  • number : (and/c integer? positive?) - The non-negative integer to compute the square root for.

Examples

> (exact-integer-sqrt 25) ;; => (5 0)
> (exact-integer-sqrt 35) ;; => (5 10)

exact-integer?

Checks if the given value is an exact integer

(exact-integer? value) -> boolean?

  • value : any - The value to check

Examples

> (exact-integer? 42) ;; => #t
> (exact-integer? -42) ;; => #t
> (exact-integer? 4.0) ;; => #f

exact?

Checks if the given value is exact.

(exact? val) -> boolean?

  • val : any - The value to check for exactness.

Examples

> (exact? 42) ;; => #t
> (exact? 3.14) ;; => #f
> (exact? "hello") ;; => #f

exp

Returns Euler’s number raised to the power of z.

(exp z) -> number?

  • z : number? - The number to raise e to the power of.

Examples

> (exp 0) ;; => 1
> (exp 2) ;; => 7.38905609893065
> (exp 1.5) ;; => 4.4816890703380645

expt

Raises the left operand to the power of the right operand.

(expt base exponent) -> number?

  • base : number? - The base number.
  • exponent : number? - The exponent to raise the base to.

Examples

> (expt 2 3) ;; => 8
> (expt 2.0 0.5) ;; => 1.4142135623730951
> (expt 9 0.5) ;; => 3

file-name

Gets the filename for a given path

finite?

Returns #t if the given number is finite.

(finite? number) -> boolean?

  • number : number? - The number to check for finiteness.

Examples

> (finite? 42) ;; => #t
> (finite? 0.1) ;; => #t
> (finite? +inf.0) ;; => #f
> (finite? -inf.0) ;; => #f
> (finite? +nan.0) ;; => #f

first

Returns the first element of the list l.

(first l) -> any/c

  • l : list?

Examples

> (first '(1 2)) ;; => 1
> (first (cons 2 3)) ;; => 2

float?

Checks if the given value is a floating-point number

(float? value) -> boolean?

  • value : any - The value to check

Examples

> (float? 42) ;; => #f
> (float? 3.14) ;; => #t
> (float? #t) ;; => #f

floor

Computes the largest integer less than or equal to the given number.

(floor number) -> number?

  • number : number? - The number to compute the floor for.

Examples

> (floor 3.14) ;; => 3
> (floor 4.99) ;; => 4
> (floor -2.5) ;; => -3

get-output-bytevector

Extracts the contents from a port created with open-output-bytevector.

(get-output-bytevector port?) -> bytes?

get-output-string

Extracts the string contents from a port created with open-output-string.

(get-output-string port?) -> string?

get-tls

Get the value out of the thread local storage slot.

hash

Creates an immutable hash table with each given key mapped to the following val. Each key must have a val, so the total number of arguments must be even.

(hash key val ...) -> hash?

key : hashable? val : any/c

Note: the keys must be hashable.

Examples

> (hash 'a 10 'b 20)",
r#"=> #<hashmap {
'a: 10,
'b: 20,
}>"#,

hash-clear

Clears the entries out of the existing hashmap. Will attempt to reuse the existing memory if there are no other references to the hashmap.

(hash-clear h) -> hash?

h: hash?

Examples

> (hash-clear (hash 'a 10 'b 20))
=> '#hash()

hash-contains?

Checks whether the given map contains the given key. Key must be hashable.

(hash-contains? map key) -> bool?

  • map : hash?
  • key : hashable?

Example

> (hash-contains? (hash 'a 10 'b 20) 'a) ;; => #true
> (hash-contains? (hash 'a 10 'b 20) 'not-there) ;; => #false

hash-empty?

Checks whether the hash map is empty

(hash-empty? m) -> bool?

m: hash?

Examples

> (hash-empty? (hash 'a 10)) ;; => #f
> (hash-emptY? (hash)) ;; => #true

hash-insert

Returns a new hashmap with the additional key value pair added. Performs a functional update, so the old hash map is still accessible.

(hash-insert map key val) -> hash?

  • map : hash?
  • key : any/c
  • val : any/c

Examples

> (hash-insert (hash 'a 10 'b 20) 'c 30)

=> #<hashmap {
'a: 10,
'b: 20,
'c: 30
}>

hash-keys->list

Returns the keys of the given hash map as a list.

(hash-keys->list map) -> (listof hashable?)
  • map : hash?

Examples

> (hash-keys->list? (hash 'a 'b 20)) ;; => '(a b)

hash-keys->vector

Returns the keys of the given hash map as an immutable vector

(hash-keys->vector map) -> (vectorof any/c)?

map: hash?

Examples

> (hash-keys->vector (hash 'a 10 'b 20)),
=> ['a 'b]",

hash-length

Returns the number of key value pairs in the map

(hash-length map) -> (and positive? int?)

  • map : hash?

Examples

> (hash-length (hash 'a 10 'b 20)) ;; => 2

hash-ref

Gets the key from the given map. Raises an error if the key does not exist. hash-get is an alias for this.

(hash-ref map key) -> any/c

  • map : hash?
  • key : any/c

Examples

> (hash-ref (hash 'a 10 'b 20) 'b) ;; => 20

hash-try-get

Gets the key from the given map. Returns #false if the key does not exist.

(hash-try-get map key) -> (or any/c #false)

  • map : hash?
  • key : any/c

Examples

> (hash-try-get (hash 'a 10 'b 20) 'b) ;; => 20
> (hash-try-get (hash 'a 10 'b 20) 'does-not-exist) ;; => #false

hash-union

Constructs the union of two hashmaps, keeping the values in the left map when the keys exist in both maps.

Will reuse memory where possible.

(hash-union l r) -> hash?

Examples

> (hash-union (hash 'a 10) (hash 'b 20)) ;; => '#hash((a . 10) (b . 20))

hash-values->list

Returns the values of the given hash map as a list

(hash-values->list? map) -> (listof any/c)?

map: hash?

Examples

> (hash-values->list? (hash 'a 10 'b 20)),
=> '(10 20)",

hash-values->vector

Returns the values of the given hash map as an immutable vector

(hash-values->vector map) -> (vectorof any/c)?

map: hash?

Examples

> (hash-keys->vector (hash 'a 10 'b 20)),
=> [10 10]",

inexact->exact

Converts an inexact number to an exact number.

(inexact->exact num) -> number?

  • num : number? - The number to convert from inexact to exact.

Examples

> (inexact->exact 10.0) ;; => 10
> (inexact->exact 1.5) ;; => 3/2
> (inexact->exact 1.5+2.5i) ;; => 3/2+5/2i

inexact?

Checks if the given value is inexact.

(inexact? val) -> boolean?

  • val : any - The value to check for inexactness.

Examples

> (inexact? 42) ;; => #f
> (inexact? 3.14) ;; => #t

infinite?

Returns #t if the given number is infinite.

(infinite? number) -> boolean?

  • number : number? - The number to check for infiniteness.

Examples

> (infinite? 42) ;; => #f
> (infinite? -nan.0) ;; => #f
> (infinite? +inf.0) ;; => #t

input-port?

Checks if a given value is an input port

(input-port? any/c) -> bool?

Examples

> (input-port? (stdin)) ;; => #true
> (input-port? "foo") ;; => #false

int->string

Converts an integer into a string.

(int->string int?) -> string?

Examples

> (int->string 10) ;; => "10"

int?

Checks if the given value is an integer, an alias for integer?

(int? value) -> boolean?

  • value : any - The value to check

Examples

> (int? 42) ;; => #t
> (int? 3.14) ;; => #f
> (int? "hello") ;; => #f

integer->char

Returns the character corresponding to a given Unicode codepoint.

(integer->char integer?) -> char?

integer?

Checks if the given value is an integer, an alias for int?

(integer? value) -> boolean?

  • value : any - The value to check

Examples

> (integer? 42) ;; => #t
> (integer? 3.14) ;; => #f
> (integer? "hello") ;; => #f

is-dir?

Checks if a path is a directory

is-file?

Checks if a path is a file

last

Returns the last element in the list. Takes time proportional to the length of the list.

(last l) -> any/c

  • l : list?

Examples

> (list (list 1 2 3 4)) ;; => 4

length

Returns the length of the list.

(length l) -> int?

  • l : list?

Examples

> (length (list 10 20 30)) ;; => 3

list

Returns a newly allocated list containing the vs as its elements.

(list v ...) -> list?

  • v : any/c

Examples

> (list 1 2 3 4 5) ;; => '(1 2 3 4 5)
> (list (list 1 2) (list 3 4)) ;; => '((1 2) (3 4))

list->bytes

Converts the list of bytes to the equivalent bytevector representation. The list must contain only values which satisfy the byte? predicate, otherwise this function will error.

Examples

(list->bytes (list 0 1 2 3 4 5)) ;; => (bytes 0 1 2 3 4 5)

list-ref

Returns the value located at the given index. Will raise an error if you try to index out of bounds.

Note: Runs in time proportional to the length of the list, however lists in Steel are implemented in such a fashion that the time complexity is O(n/64). Meaning, for small lists this can be constant.

(list-ref lst index) -> list?

  • lst : list?
  • index : (and/c int? positive?)

Examples

> (list-ref (list 1 2 3 4) 2) ;; => 3
> (list-ref (range 0 100) 42) ;; => 42"
> (list-ref (list 1 2 3 4) 10)
error[E11]: Generic
┌─ :1:2
│
1 │ (list-ref (list 1 2 3 4) 10)
│  ^^^^^^^^ out of bounds index in list-ref - list length: 4, index: 10

local-time/now!

Returns the local time in the format given by the input string (using chrono::Local::format).

(local-time/now! fmt) -> string?

  • fmt : string?

lock-acquire!

Lock the given mutex

lock-release!

Unlock the given mutex

log

Computes the natural logarithm of the given number.

(log number [base]) -> number?

  • number : number? - The number to compute the logarithm for.
  • base : number? - The base of the logarithm. If not provided, defaults to Euler's number (e).

Examples

> (log 10) ;; => 2.302585092994046
> (log 100 10) ;; => 2
> (log 27 3) ;; => 3

magnitude

Computes the magnitude of the given number.

(magnitude number) -> number?

  • number : number? - The number to compute the magnitude for.

Examples

> (magnitude 3+4i) ;; => 5
> (magnitude 5) ;; => 5
> (magnitude -5) ;; => 5

make-bytes

Creates a bytevector given a length and a default value.

(make-bytes len default) -> bytes?

  • len : int?
  • default : byte?

Examples

(make-bytes 6 42) ;; => (bytes 42 42 42 42 42)

make-string

Creates a string of a given length, filled with an optional character (which defaults to #\0).

(make-string len [char]) -> string?

  • len : int?
  • char : char? = #\0

make-tls

Creates a thread local storage slot. These slots are static, and will not be reclaimed.

When spawning a new thread, the value inside will be shared into that slot, however future updates to the slot will be local to that thread.

mutex

Construct a new mutex

nan?

Returns #t if the real number is Nan.

(nan? value) -> boolean?

  • value : real? - The value to check
(nan? +nan.0) => #t
(nan? 100000) => #f

negative?

Checks if the given real number is negative.

(negative? num) -> boolean?

  • num : real? - The real number to check for negativity.

Examples

> (negative? 0) ;; => #f
> (negative? 1) ;; => #f
> (negative? -1) ;; => #t

number->string

Converts the given number to a string.

number?

Checks if the given value is a number

(number? value) -> boolean?

  • value : any - The value to check

Examples

> (number? 42) ;; => #t
> (number? "hello") ;; => #f
> (number? 'symbol) ;; => #f

numerator

Retrieves the numerator of the given rational number.

(numerator number) -> number?

  • number : number? - The rational number to retrieve the numerator from.

Examples

> (numerator 3/4) ;; => 3
> (numerator 5/2) ;; => 5
> (numerator -2) ;; => -2

open-input-bytevector

Creates an input port from a bytevector, that will return the bytevector contents.

(open-input-bytevector bytes?) -> input-port?

open-input-file

Takes a filename path referring to an existing file and returns an input port. Raises an error if the file does not exist

(open-input-file string?) -> input-port?

Examples

> (open-input-file "foo-bar.txt") ;; => #<port>
> (open-input-file "file-does-not-exist.txt")
error[E08]: Io
┌─ :1:2
│
1 │ (open-input-file "foo-bar.txt")
│  ^^^^^^^^^^^^^^^ No such file or directory (os error 2)

open-input-string

Creates an input port from a string, that will return the string contents.

(open-input-string string?) -> input-port?

open-output-bytevector

Creates an output port that accumulates what is written into a bytevector. These bytes can be recovered calling get-output-bytevector.

(open-output-bytevector) -> output-port?

Examples

(define out (open-output-bytevector))


(write-byte 30 out)
(write-byte 250 out)

(get-output-bytevector out) ;; => (bytes 30 250)

open-output-file

Takes a filename path referring to a file to be created and returns an output port.

(open-output-file string?) -> output-port?

Examples

> (open-output-file "foo-bar.txt") ;; => #<port>

open-output-string

Creates an output port that accumulates what is written into a string. This string can be recovered calling get-output-string.

(open-output-string) -> output-port?

Examples

(define out (open-output-string))


(write-char "α" out)
(write-char "ω" out)

(get-output-string out) ;; => "αω"

output-port?

Checks if a given value is an output port

(output-port? any/c) -> bool?

Examples

> (define output (open-output-file "foo.txt"))
> (output-port? output) ;; => #true

pair?

Checks if the given value can be treated as a pair.

(pair? any/c) -> bool?

Examples

> (pair? '(10 20)) ;; => #true
> (pair? '(10)) ;; => #true
> (pair? '()) ;; => #false

path->extension

Gets the extension from a path

path-exists?

Checks if a path exists

peek-byte

Peeks the next byte from an input port.

(peek-byte [port]) -> byte?

  • port : input-port? = (current-input-port)

positive?

Checks if the given real number is positive.

(positive? num) -> boolean?

  • num : real? - The real number to check for positivity.

Examples

> (positive? 0) ;; => #f
> (positive? 1) ;; => #t
> (positive? -1) ;; => #f

quotient

Returns quotient of dividing numerator by denomintator.

(quotient numerator denominator) -> integer?

  • numerator : integer? - The numerator.
  • denominator : integer? - The denominator.

Examples

> (quotient 11 2) ;; => 5
> (quotient 10 2) ;; => 5
> (quotient -10 2) ;; => -5

range

Returns a newly allocated list of the elements in the range (n, m]

(range n m) -> (listof int?)

  • n : int?
  • m : int?
> (range 0 10) ;; => '(0 1 2 3 4 5 6 7 8 9)

rational?

Returns #t if obj is a rational number, #f otherwise. Rational numbers are numbers that can be expressed as the quotient of two numbers. For example, 3/4, -5/2, 0.25, and 0 are rational numbers.

(rational? value) -> bool?

  • value : any - The value to check

Examples:

> (rational? (/ 0.0)) ;; => #f
> (rational? 3.5) ;; => #t
> (rational? 6/10) ;; => #t
> (rational? +nan.0) ;; => #f

read-byte

Reads a single byte from an input port.

(read-byte [port]) -> byte?

  • port : input-port? = (current-input-port)

read-char

Reads the next character from an input port.

(read-char [port]) -> char?

  • port : input-port? = (current-input-port)

read-dir

Returns the contents of the directory as a list

read-port-to-string

Takes a port and reads the entire content into a string

(read-port-to-string port) -> string?

  • port : input-port?

real?

Checks if the given value is a real number

(real? value) -> boolean?

  • value : any - The value to check

Examples

> (real? 42) ;; => #t
> (real? 3+4i) ;; => #f
> (real? "hello") ;; => #f

receivers-select

Blocks until one of the channels passed in is ready to receive. Returns the index of the channel arguments passed in which is ready.

Using this directly is not recommended.

rest

Returns the rest of the list. Will raise an error if the list is empty.

(rest l) -> list?

  • l : list?

Examples

> (rest (list 10 20 30)) ;; => '(20 30)
> (rest (list 10)) ;; => '()
> (rest (list 10))
error[E11]: Generic
┌─ :1:2
│
1 │ (rest '())
│  ^^^^ rest expects a non empty list

reverse

Returns a list that has the same elements as lst, but in reverse order. This function takes time proportional to the length of lst.

(reverse lst) -> list?

  • l : list?

Examples

> (reverse (list 1 2 3 4)) ;; '(4 3 2 1)

round

Rounds the given number to the nearest integer.

(round number) -> number?

  • number : number? - The number to round.

Examples

> (round 3.14) ;; => 3
> (round 4.6) ;; => 5
> (round -2.5) ;; => -3

second

Get the second element of the list. Raises an error if the list does not have an element in the second position.

(second l) -> any/c

  • l : list?

Examples

> (second '(1 2 3)) ;; => 2
> (second '())
error[E11]: Generic
┌─ :1:2
│
1 │ (second '())
│  ^^^^^^ second: index out of bounds - list did not have an element in the second position: []
### **set-tls!**
Set the value in the the thread local storage. Only this thread will see the updates associated
with this TLS.
### **split-many**
Splits a string given a separator pattern into a list of strings.

(split-many str pat) -> (listof string?)

* str : string?
* pat : string?

#### Examples
```scheme
(split-many "foo,bar,baz" ",") ;; => '("foo" "bar" "baz")
(split-many "foo|bar|" "|") ;; => '("foo" "bar" "")
(split-many "" "&") ;; => '("")

split-once

Splits a string given a separator at most once, yielding a list with at most 2 elements.

(split-once str pat) -> string?

  • str : string?
  • pat : string?

Examples

(split-once "foo,bar,baz" ",") ;; => '("foo" "bar,baz")
(split-once "foo|bar|" "|") ;; => '("foo" "bar|")
(split-once "" "&") ;; => '("")

split-whitespace

Returns a list of strings from the original string split on the whitespace

(split-whitespace string?) -> (listof string?)

Examples

(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")

sqrt

Computes the square root of the given number.

(sqrt number) -> number?

  • number : number? - The number to compute the square root for.

Examples

> (sqrt 4) ;; => 2
> (sqrt 2) ;; => 1.4142135623730951
> (sqrt -1) ;; => 0+1i

square

Computes the square of the given number.

(square number) -> number?

  • number : number? - The number to square.

Examples

> (square 5) ;; => 25
> (square -3) ;; => 9
> (square 2.5) ;; => 6.25

starts-with?

Checks if the input string starts with a prefix

(starts-with? input pattern) -> bool?

  • input : string?
  • pattern: string?

Examples

> (starts-with? "foobar" "foo") ;; => #true
> (starts-with? "foobar" "bar") ;; => #false

stdin

Gets the port handle to stdin

(stdin) -> input-port?

Examples

> (stdin) ;; => #<port>

string

Constructs a string from the given characters

string->bytes

Encodes a string as UTF-8 into a bytevector.

(string->bytes string?) -> bytes?

Examples

(string->bytes "Apple") ;; => (bytes 65 112 112 108 101)

string->int

Converts a string into an int. Raises an error if the string cannot be converted to an integer.

(string->int string?) -> int?

Examples

> (string->int "100") ;; => 10
> (string->int "not-an-int") ;; error

string->jsexpr

Deserializes a JSON string into a Steel value.

(string->jsexpr json) -> any/c

  • json : string?

Examples

(string->jsexpr "{\"foo\": [3]}") ;; => '#hash((foo . (3)))

string->list

Converts a string into a list of characters.

(string->list s [start] [end]) -> (listof char?)

  • s : string?
  • start : int? = 0
  • end : int?

Examples

> (string->list "hello") ;; => '(#\h #\e #\l #\l #\o)

string->lower

Creates a new lowercased version of the input string

(string->lower string?) -> string?

Examples

> (string->lower "sPonGeBoB tExT") ;; => "spongebob text"

string->number

Converts the given string to a number, with an optional radix. On failure, it returns #f

(string->number digits [radix]) -> (or/c number? boolean?)

  • digits : string?
  • radix : number?

string->symbol

Converts a string into a symbol.

(string->symbol string?) -> symbol?

Examples

> (string->symbol "FooBar") ;; => 'FooBar

string->upper

Creates a new uppercased version of the input string

(string->upper string?) -> string?

Examples

> (string->upper "lower") ;; => "LOWER"

string->utf8

Alias of string->bytes.

string->vector

Returns a vector containing the characters of a given string

(string->vector string?) -> vector?

Examples

(string->vector "hello") ;; => '#(#\h #\e #\l #\l #\o)

string-append

Concatenates all of the given strings into one

(string-append strs...) -> string?

  • strs ... : string?

Examples

> (string-append) ;; => ""
> (string-append "foo" "bar") ;; => "foobar"

string-ci<=?

Compares strings lexicographically (as in"less-than-or-equal"), in a case insensitive fashion.

(string-ci<=? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string-ci<?

Compares strings lexicographically (as in"less-than"), in a case insensitive fashion.

(string-ci<? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string-ci=?

Compares strings for equality, in a case insensitive fashion.

string-ci>=?

Compares strings lexicographically (as in"greater-than-or-equal"), in a case insensitive fashion.

(string-ci>=? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string-ci>?

Compares strings lexicographically (as in"greater-than"), in a case insensitive fashion.

(string-ci>? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string-contains?

Searches a string to check if it contains the second argument.

(string-contains? string? string?) -> bool?

Examples

(string-contains? "hello" "lo") ;;=> #t
(string-contains? "hello" "world") ;;=> #f

string-join

Joins the given list of strings, with an optional separator.

(string-join strings [sep]) -> string?

  • strings : (listof string?)
  • sep : string? = ""

Examples

(string-join '("a" "b" "c")) ;; => "abc"
(string-join '("one" "two" "three") ", ") ;; => "one, two, three"

string-length

Get the length of the given string in UTF-8 bytes.

(string-length string?) -> int?

Examples

> (string-length "apples") ;; => 6
> (string-length "✅") ;; => 3
> (string-length "🤖") ;; => 4

string-ref

Extracts the nth character out of a given string.

(string-ref str n) -> char?

  • str : string?
  • n : int?

string-replace

Replaces all occurrences of a pattern into the given string

(string-replace str from to) -> string?

  • str : string?
  • from : string?
  • to : string?

Examples

(string-replace "hello world" "o" "@") ;; => "hell@ w@rld"

string<=?

Compares strings lexicographically (as in"less-than-equal-to").

(string<=? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string<?

Compares strings lexicographically (as in"less-than").

(string<? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string=?

Compares strings for equality.

(string=? string1 string2 ...) -> bool?

  • string1 : string?
  • string2 : string?

string>=?

Compares strings lexicographically (as in"greater-than-or-equal").

(string>=? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string>?

Compares strings lexicographically (as in"greater-than").

(string>? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

substring

Creates a substring slicing the characters between two indices.

(substring str start end) -> string?

  • str: string?
  • start : int?
  • end : int?

Examples

(substring "hello" 1 4) ;; => "ell"
(substring "hello" 10 15) ;; => error

take

Returns the first n elements of the list l as a new list.

(take l n) -> list?

  • l : list?
  • n : (and/c positive? int?)

Examples

> (take '(1 2 3 4) 2) ;; => '(0 1)
> (take (range 0 10) 4) ;; => '(0 1 2 3)

third

Get the third element of the list. Raises an error if the list does not have an element in the third position.

(third l) -> any/c

  • l : list?

Examples

> (third '(1 2 3)) ;; => 3
> (third '())
error[E11]: Generic
┌─ :1:2
│
1 │ (third '())
│  ^^^^^^ third: index out of bounds - list did not have an element in the second position: []

thread-finished?

Check if the given thread is finished running.

thread-interrupt

Interrupts the thread. Note, this will not interrupt any native code that is potentially running in the thread, and will attempt to block at the next bytecode instruction that is running.

thread-join!

Block until this thread finishes.

thread-resume

Resume a suspended thread. This does nothing if the thread is already joined.

thread-suspend

Suspend the thread. Note, this will not interrupt any native code that is potentially running in the thread, and will attempt to block at the next bytecode instruction that is running.

time/sleep-ms

Sleeps the thread for a given number of milliseconds.

(time/sleep-ms ms)

  • ms : int?

to-string

Concatenates all of the inputs to their string representation, separated by spaces.

(to-string xs ...)

  • xs : any/c

Examples

> (to-string 10) ;; => "10"
> (to-string 10 20) ;; => "10 20"
> (to-string "hello" "world") ;; => "hello world"

trim

Returns a new string with the leading and trailing whitespace removed.

(trim string?) -> string?

Examples

> (trim "   foo     ") ;; => "foo"

trim-end

Returns a new string with the trailing whitespace removed.

(trim string?) -> string?

Examples

> (trim "   foo     ") ;; => "   foo"

trim-end-matches

Returns a new string with the given pat repeatedly removed from the end of the string

(trim-end-matches string? string?) -> string?

Examples

> (trim-end-matches "123foo1bar123123" "123") ;; => "123foo1bar"

trim-start

Returns a new string with the leading whitespace removed.

(trim string?) -> string?

Examples

> (trim "   foo     ") ;; => "foo     "

trim-start-matches

Returns a new string with the given pat repeatedly removed from the start of the string

(trim-start-matches string? string?) -> string?

Examples

> (trim-start-matches "123foo1bar123123" "123") ;; => "foo1bar123123"

utf8->string

Alias of bytes->string/utf8.

value->jsexpr-string

Serializes a Steel value into a string.

(value->jsexpr-string any/c) -> string?

Examples

(value->jsexpr-string `(,(hash "foo" #t))) ;; => "[{\"foo\":true}]"

void

The void value, returned by many forms with side effects, such as define.

write-byte

Writes a single byte to an output port.

(write-byte b [port])

  • b : byte?
  • port : output-port? = (current-output-port)

write-bytes

Writes the contents of a bytevector into an output port.

(write-bytes buf [port])

  • buf : bytes?
  • port : output-port? = (current-output-port)

zero?

Checks if the given real number is zero.

(zero? num) -> boolean?

  • num : real? - The number to check for zero.

Examples

> (zero? 0) ;; => #t
> (zero? 0.0) ;; => #t
> (zero? 0.1) ;; => #f

%#interner-memory-usage

%iterator?

%keyword-hash

=

Engine::add-module

Engine::clone

Engine::modules->list

Engine::new

Engine::raise_error

Err

Err->value

Err?

None

None?

Ok

Ok->value

Ok?

Some

Some->value

Some?

TypeId?

active-object-count

arithmetic-shift

arity?

assert!

atom?

attach-contract-struct!

block-on

bool?

boolean?

box

box-strong

breakpoint!

call-with-current-continuation

call-with-exception-handler

call/cc

cdr-null?

channel->recv

channel->send

channel->try-recv

channel/recv

channel/send

channel/try-recv

channels-receiver

channels-sender

channels/new

char?

child-stderr

child-stdin

child-stdout

close-output-port

command

compose

concat-symbols

continuation?

current-function-span

current-os!

dropping

duration->seconds

duration-since

empty-stream

enumerating

env-var

eq?

equal?

eqv?

error-object?

error-with-span

eval!

even?

expand!

extending

f+

filtering

flat-mapping

flattening

flush-output-port

function-name

function?

future?

get-contract-struct

get-test-mode

hash-get

hash?

hashset

hashset->list

hashset->vector

hashset-clear

hashset-contains?

hashset-insert

hashset-length

hashset-subset?

inspect-bytecode

instant/elapsed

instant/now

interleaving

into-count

into-for-each

into-hashmap

into-hashset

into-last

into-list

into-max

into-min

into-nth

into-product

into-reducer

into-string

into-sum

into-vector

iter-next!

join!

list->hashset

list->string

list->vector

list-tail

list?

local-executor/block-on

make-channels

make-struct-type

make-vector

mapping

maybe-get-env-var

memory-address

multi-arity?

mut-vec-len

mut-vector-ref

mutable-vector

mutable-vector->clear

mutable-vector->list

mutable-vector->string

mutable-vector-pop!

mutable-vector?

not

null?

odd?

poll!

pop-front

port?

procedure?

push

push-back

push-front

raise-error

raise-error-with-span

range-vec

read!

read-line-from-port

read-to-string

run!

set-box!

set-current-dir!

set-env-var!

set-piped-stdout!

set-strong-box!

set-test-mode!

set?

spawn-native-thread

spawn-process

spawn-thread!

stdout

stdout-simple-displayln

steel-home-location

stream-car

stream-cons

stream-empty?

string?

struct->list

struct?

symbol->string

symbol?

syntax->datum

syntax-e

syntax-loc

syntax-span

syntax/loc

syntax?

taking

thread/available-parallelism

thread::current/id

transduce

try-list-ref

unbox

unbox-strong

value->iterator

value->string

vec-append

vec-rest

vector

vector-append!

vector-length

vector-push!

vector-ref

vector-set!

vector?

void?

wait

wait->stdout

which

write-line!

zipping

steel/bytevectors

byte?

Returns #t if the given value is a byte, meaning an exact integer between 0 and 255 inclusive, #f otherwise.

Examples

(byte? 65) ;; => #t
(byte? 0) ;; => #t
(byte? 256) ;; => #f
(byte? 100000) ;; => #f
(byte? -1) ;; => #f

bytes

Returns a new mutable vector with each byte as the given arguments. Each argument must satisfy the byte? predicate, meaning it is an exact integer range from 0 - 255 (inclusive)

(bytes b ...)

  • b : byte?

Examples

(bytes 65 112 112 108 101)

bytes->list

Converts the bytevector to the equivalent list representation.

Examples

(bytes->list (bytes 0 1 2 3 4 5)) ;; => '(0 1 2 3 4 5)

bytes->string/utf8

Decodes a string from a bytevector containing valid UTF-8.

(bytes->string/utf8 buf [start] [end]) -> string?

  • buf : bytes?
  • start: int? = 0
  • end: int? = (bytes-length buf)

Examples

(bytes->string/utf8 (bytes #xe5 #x8d #x83 #xe8 #x91 #x89)) ;; => "千葉"

bytes-append

Append multiple byte vectors into a new bytevector.

Examples

(bytes-append #u8(0 1 2) #u8(3 4 5)) ;; => #u8(#x00 #x01 #x02 #x03 #x04 #x05)

(bytes-append #u8(0) #u8(1) #u8() #u8(2)) ;; => #u8(#x00 #x01 #x02)

bytes-length

Returns the length of the given byte vector

Examples

(bytes-length (bytes 1 2 3 4 5)) ;; => 5

bytes-ref

Fetches the byte at the given index within the bytevector. If the index is out of bounds, this will error.

(bytes-ref vector index)

  • vector : bytes?
  • index: (and exact? int?)

Examples

(bytes-ref (bytes 0 1 2 3 4 5) 3) ;; => 4
(bytes-ref (bytes) 10) ;; error

bytes-set!

Sets the byte at the given index to the given byte. Will error if the index is out of bounds.

(bytes-set! vector index byte)

  • vector : bytes?
  • index: (and exact? int?)
  • byte: byte?

Examples

(define my-bytes (bytes 0 1 2 3 4 5))
(bytes-set! my-bytes 0 100)
(bytes-ref my-bytes 0) ;; => 100

bytes?

Returns #t if this value is a bytevector

Examples

(bytes? (bytes 0 1 2)) ;; => #t
(bytes? (list 10 20 30)) ;; => #f

bytevector

Returns a new mutable vector with each byte as the given arguments. Each argument must satisfy the byte? predicate, meaning it is an exact integer range from 0 - 255 (inclusive)

(bytevector b ...)

  • b : byte?

Examples

(bytevector 65 112 112 108 101)

bytevector-copy

Creates a copy of a bytevector.

(bytevector-copy vector [start end]) -> bytes?

  • vector : bytes?
  • start: int? = 0
  • end: int? = (bytes-length vector)

Examples

(define vec (bytes 1 2 3 4 5))

(bytevector-copy vec) ;; => (bytes 1 2 3 4 5)
(bytevector-copy vec 1 3) ;; => (bytes 2 3)

list->bytes

Converts the list of bytes to the equivalent bytevector representation. The list must contain only values which satisfy the byte? predicate, otherwise this function will error.

Examples

(list->bytes (list 0 1 2 3 4 5)) ;; => (bytes 0 1 2 3 4 5)

make-bytes

Creates a bytevector given a length and a default value.

(make-bytes len default) -> bytes?

  • len : int?
  • default : byte?

Examples

(make-bytes 6 42) ;; => (bytes 42 42 42 42 42)

utf8->string

Alias of bytes->string/utf8.

steel/constants

Miscellaneous constants

void

The void value, returned by many forms with side effects, such as define.

steel/core/option

None

None?

Some

Some->value

Some?

steel/core/result

Err

Err->value

Err?

Ok

Ok->value

Ok?

steel/core/types

TypeId?

steel/equality

=

eq?

equal?

eqv?

steel/filesystem

Filesystem functions, mostly just thin wrappers around the std::fs functions in the Rust std library.

canonicalize-path

Returns canonical path with all components normalized

copy-directory-recursively!

Recursively copies the directory from source to destination

create-directory!

Creates the directory

current-directory

Check the current working directory

delete-directory!

Deletes the directory

delete-file!

Deletes the file

file-name

Gets the filename for a given path

is-dir?

Checks if a path is a directory

is-file?

Checks if a path is a file

path->extension

Gets the extension from a path

path-exists?

Checks if a path exists

read-dir

Returns the contents of the directory as a list

steel/hash

hash

Creates an immutable hash table with each given key mapped to the following val. Each key must have a val, so the total number of arguments must be even.

(hash key val ...) -> hash?

key : hashable? val : any/c

Note: the keys must be hashable.

Examples

> (hash 'a 10 'b 20)",
r#"=> #<hashmap {
'a: 10,
'b: 20,
}>"#,

hash-clear

Clears the entries out of the existing hashmap. Will attempt to reuse the existing memory if there are no other references to the hashmap.

(hash-clear h) -> hash?

h: hash?

Examples

> (hash-clear (hash 'a 10 'b 20))
=> '#hash()

hash-contains?

Checks whether the given map contains the given key. Key must be hashable.

(hash-contains? map key) -> bool?

  • map : hash?
  • key : hashable?

Example

> (hash-contains? (hash 'a 10 'b 20) 'a) ;; => #true
> (hash-contains? (hash 'a 10 'b 20) 'not-there) ;; => #false

hash-empty?

Checks whether the hash map is empty

(hash-empty? m) -> bool?

m: hash?

Examples

> (hash-empty? (hash 'a 10)) ;; => #f
> (hash-emptY? (hash)) ;; => #true

hash-insert

Returns a new hashmap with the additional key value pair added. Performs a functional update, so the old hash map is still accessible.

(hash-insert map key val) -> hash?

  • map : hash?
  • key : any/c
  • val : any/c

Examples

> (hash-insert (hash 'a 10 'b 20) 'c 30)

=> #<hashmap {
'a: 10,
'b: 20,
'c: 30
}>

hash-keys->list

Returns the keys of the given hash map as a list.

(hash-keys->list map) -> (listof hashable?)
  • map : hash?

Examples

> (hash-keys->list? (hash 'a 'b 20)) ;; => '(a b)

hash-keys->vector

Returns the keys of the given hash map as an immutable vector

(hash-keys->vector map) -> (vectorof any/c)?

map: hash?

Examples

> (hash-keys->vector (hash 'a 10 'b 20)),
=> ['a 'b]",

hash-length

Returns the number of key value pairs in the map

(hash-length map) -> (and positive? int?)

  • map : hash?

Examples

> (hash-length (hash 'a 10 'b 20)) ;; => 2

hash-ref

Gets the key from the given map. Raises an error if the key does not exist. hash-get is an alias for this.

(hash-ref map key) -> any/c

  • map : hash?
  • key : any/c

Examples

> (hash-ref (hash 'a 10 'b 20) 'b) ;; => 20

hash-try-get

Gets the key from the given map. Returns #false if the key does not exist.

(hash-try-get map key) -> (or any/c #false)

  • map : hash?
  • key : any/c

Examples

> (hash-try-get (hash 'a 10 'b 20) 'b) ;; => 20
> (hash-try-get (hash 'a 10 'b 20) 'does-not-exist) ;; => #false

hash-union

Constructs the union of two hashmaps, keeping the values in the left map when the keys exist in both maps.

Will reuse memory where possible.

(hash-union l r) -> hash?

Examples

> (hash-union (hash 'a 10) (hash 'b 20)) ;; => '#hash((a . 10) (b . 20))

hash-values->list

Returns the values of the given hash map as a list

(hash-values->list? map) -> (listof any/c)?

map: hash?

Examples

> (hash-values->list? (hash 'a 10 'b 20)),
=> '(10 20)",

hash-values->vector

Returns the values of the given hash map as an immutable vector

(hash-values->vector map) -> (vectorof any/c)?

map: hash?

Examples

> (hash-keys->vector (hash 'a 10 'b 20)),
=> [10 10]",

%keyword-hash

hash-get

steel/identity

complex?

Checks if the given value is a complex number

(complex? value) -> boolean?

  • value : any - The value to check

Examples

> (complex? 3+4i) ;; => #t
> (complex? 42) ;; => #t
> (complex? "hello") ;; => #f

eof-object?

Returns #t if the value is an EOF object.

(eof-object? any/c) -> bool?

exact-integer?

Checks if the given value is an exact integer

(exact-integer? value) -> boolean?

  • value : any - The value to check

Examples

> (exact-integer? 42) ;; => #t
> (exact-integer? -42) ;; => #t
> (exact-integer? 4.0) ;; => #f

float?

Checks if the given value is a floating-point number

(float? value) -> boolean?

  • value : any - The value to check

Examples

> (float? 42) ;; => #f
> (float? 3.14) ;; => #t
> (float? #t) ;; => #f

int?

Checks if the given value is an integer, an alias for integer?

(int? value) -> boolean?

  • value : any - The value to check

Examples

> (int? 42) ;; => #t
> (int? 3.14) ;; => #f
> (int? "hello") ;; => #f

integer?

Checks if the given value is an integer, an alias for int?

(integer? value) -> boolean?

  • value : any - The value to check

Examples

> (integer? 42) ;; => #t
> (integer? 3.14) ;; => #f
> (integer? "hello") ;; => #f

number?

Checks if the given value is a number

(number? value) -> boolean?

  • value : any - The value to check

Examples

> (number? 42) ;; => #t
> (number? "hello") ;; => #f
> (number? 'symbol) ;; => #f

rational?

Returns #t if obj is a rational number, #f otherwise. Rational numbers are numbers that can be expressed as the quotient of two numbers. For example, 3/4, -5/2, 0.25, and 0 are rational numbers.

(rational? value) -> bool?

  • value : any - The value to check

Examples:

> (rational? (/ 0.0)) ;; => #f
> (rational? 3.5) ;; => #t
> (rational? 6/10) ;; => #t
> (rational? +nan.0) ;; => #f

real?

Checks if the given value is a real number

(real? value) -> boolean?

  • value : any - The value to check

Examples

> (real? 42) ;; => #t
> (real? 3+4i) ;; => #f
> (real? "hello") ;; => #f

atom?

bool?

boolean?

char?

continuation?

error-object?

function?

future?

hash?

list?

mutable-vector?

not

port?

procedure?

set?

string?

struct?

symbol?

vector?

void?

steel/io

read-to-string

stdout-simple-displayln

steel/immutable-vectors

immutable-vector-push

Pushes a value to the back of the vector, returning a new vector.

immutable-vector->list

immutable-vector->string

immutable-vector-append

immutable-vector-copy

immutable-vector-drop

immutable-vector-rest

immutable-vector-set

immutable-vector-take

make-immutable-vector

vector

vector-push-front

steel/json

De/serialization from/to JSON.

string->jsexpr

Deserializes a JSON string into a Steel value.

(string->jsexpr json) -> any/c

  • json : string?

Examples

(string->jsexpr "{\"foo\": [3]}") ;; => '#hash((foo . (3)))

value->jsexpr-string

Serializes a Steel value into a string.

(value->jsexpr-string any/c) -> string?

Examples

(value->jsexpr-string `(,(hash "foo" #t))) ;; => "[{\"foo\":true}]"

steel/lists

Lists in Steel have an interface that matches those of classic schemes or lisps. At face value, they appear to be implemented as cons cells - however, under the hood they are actually implemented as unrolled linked lists.

This means that for most practical purposes, interaction with lists is the same. That being said, there are no improper lists, meaning, pairs are actually just lists of two elements.

Indexing into a list also takes O(n/64) - which means you'll get constant time indexing on small lists.

(list 10 20 30 40) ;; => '(10 20 30 40)

append

Appends the given lists together. If provided with no lists, will return the empty list.

(append lst ...)

lst : list?

Examples

> (append (list 1 2) (list 3 4)) ;; => '(1 2 3 4)
> (append) ;; => '()

apply

Applies the given function with arguments as the contents of the list.

(apply function lst) -> any?

  • function : function?
  • list: list?

Examples

> (apply + (list 1 2 3 4)) ;; => 10
> (apply list (list 1 2 3 4)) ;; => '(1 2 3 4)

car

Returns the first element of the list l.

(car l) -> any/c

  • l : list?

Examples

> (car '(1 2)) ;; => 1
> (car (cons 2 3)) ;; => 2

cdr

Returns the rest of the list. Will raise an error if the list is empty.

(cdr l) -> list?

  • l : list?

Examples

> (cdr (list 10 20 30)) ;; => '(20 30)
> (cdr (list 10)) ;; => '()
> (cdr '())
error[E11]: Generic
┌─ :1:2
│
1 │ (cdr '())
│  ^^^ cdr expects a non empty list

cons

Returns a newly allocated list whose first element is a and second element is d.

(cons a d) -> list?

  • a : any/c
  • d : any/c

Examples

> (cons 1 2) ;; => '(1 . 2)
> (cons 1 '()) ;; => '(1)

empty?

Checks if the list is empty

(empty? lst) -> bool?

  • lst: list?

Examples

> (empty? (list 1 2 3 4 5)) ;; => #false
> (empty? '()) ;; => #true

first

Returns the first element of the list l.

(first l) -> any/c

  • l : list?

Examples

> (first '(1 2)) ;; => 1
> (first (cons 2 3)) ;; => 2

last

Returns the last element in the list. Takes time proportional to the length of the list.

(last l) -> any/c

  • l : list?

Examples

> (list (list 1 2 3 4)) ;; => 4

length

Returns the length of the list.

(length l) -> int?

  • l : list?

Examples

> (length (list 10 20 30)) ;; => 3

list

Returns a newly allocated list containing the vs as its elements.

(list v ...) -> list?

  • v : any/c

Examples

> (list 1 2 3 4 5) ;; => '(1 2 3 4 5)
> (list (list 1 2) (list 3 4)) ;; => '((1 2) (3 4))

list-ref

Returns the value located at the given index. Will raise an error if you try to index out of bounds.

Note: Runs in time proportional to the length of the list, however lists in Steel are implemented in such a fashion that the time complexity is O(n/64). Meaning, for small lists this can be constant.

(list-ref lst index) -> list?

  • lst : list?
  • index : (and/c int? positive?)

Examples

> (list-ref (list 1 2 3 4) 2) ;; => 3
> (list-ref (range 0 100) 42) ;; => 42"
> (list-ref (list 1 2 3 4) 10)
error[E11]: Generic
┌─ :1:2
│
1 │ (list-ref (list 1 2 3 4) 10)
│  ^^^^^^^^ out of bounds index in list-ref - list length: 4, index: 10

pair?

Checks if the given value can be treated as a pair.

(pair? any/c) -> bool?

Examples

> (pair? '(10 20)) ;; => #true
> (pair? '(10)) ;; => #true
> (pair? '()) ;; => #false

range

Returns a newly allocated list of the elements in the range (n, m]

(range n m) -> (listof int?)

  • n : int?
  • m : int?
> (range 0 10) ;; => '(0 1 2 3 4 5 6 7 8 9)

rest

Returns the rest of the list. Will raise an error if the list is empty.

(rest l) -> list?

  • l : list?

Examples

> (rest (list 10 20 30)) ;; => '(20 30)
> (rest (list 10)) ;; => '()
> (rest (list 10))
error[E11]: Generic
┌─ :1:2
│
1 │ (rest '())
│  ^^^^ rest expects a non empty list

reverse

Returns a list that has the same elements as lst, but in reverse order. This function takes time proportional to the length of lst.

(reverse lst) -> list?

  • l : list?

Examples

> (reverse (list 1 2 3 4)) ;; '(4 3 2 1)

second

Get the second element of the list. Raises an error if the list does not have an element in the second position.

(second l) -> any/c

  • l : list?

Examples

> (second '(1 2 3)) ;; => 2
> (second '())
error[E11]: Generic
┌─ :1:2
│
1 │ (second '())
│  ^^^^^^ second: index out of bounds - list did not have an element in the second position: []
### **take**
Returns the first n elements of the list l as a new list.

(take l n) -> list?

* l : list?
* n : (and/c positive? int?)

#### Examples

```scheme
> (take '(1 2 3 4) 2) ;; => '(0 1)
> (take (range 0 10) 4) ;; => '(0 1 2 3)

third

Get the third element of the list. Raises an error if the list does not have an element in the third position.

(third l) -> any/c

  • l : list?

Examples

> (third '(1 2 3)) ;; => 3
> (third '())
error[E11]: Generic
┌─ :1:2
│
1 │ (third '())
│  ^^^^^^ third: index out of bounds - list did not have an element in the second position: []

cdr-null?

list->string

list->vector

list-tail

push-back

transduce

try-list-ref

steel/meta

command-line

Returns the command line passed to this process, including the command name as first argument.

error-object-message

Returns the message of an error object.

(error-object-message error?) -> string?

%#interner-memory-usage

%iterator?

Engine::add-module

Engine::clone

Engine::modules->list

Engine::new

Engine::raise_error

active-object-count

arity?

assert!

attach-contract-struct!

block-on

box

box-strong

breakpoint!

call-with-current-continuation

call-with-exception-handler

call/cc

current-function-span

current-os!

env-var

error-with-span

eval!

expand!

function-name

get-contract-struct

get-test-mode

inspect-bytecode

iter-next!

join!

local-executor/block-on

make-struct-type

maybe-get-env-var

memory-address

multi-arity?

poll!

raise-error

raise-error-with-span

read!

run!

set-box!

set-env-var!

set-strong-box!

set-test-mode!

steel-home-location

struct->list

unbox

unbox-strong

value->iterator

value->string

steel/numbers

*

Multiplies the given numbers.

(* . nums) -> number?

  • nums : number? - The numbers to multiply. Can have any number of arguments including zero.

Examples

> (* 5 3) ;; => 15
> (* 10 3 2) ;; => 60
> (*) ;; => 1

+

Adds the given numbers.

(+ . nums) -> number?

  • nums : number? - The numbers to add. Can have any number of arguments including zero.

Examples

> (+ 5 3) ;; => 8
> (+ 10 3 2) ;; => 15
> (+) ;; => 0

-

Subtracts the given numbers.

(- . nums) -> number?

  • nums : number? - The numbers to subtract. Must have at least one number.

Examples

> (- 5 3) ;; => 2
> (- 10 3 2) ;; => 5
> (- -5) ;; => 5

/

Divides the given numbers.

(/ . nums) -> number?

  • nums : number? - The numbers to divide. Must have at least one number.

Examples

> (/ 10 2) ;; => 5
> (/ 10 2 2.0) ;; => 2.5
> (/ 1 3.0) ;; => 0.3333333333333333
> (/ 1 3) ;; => 1/3

abs

Computes the absolute value of the given number.

(abs number) -> number?

  • number : number? - The number to compute the absolute value of.

Examples

> (abs 42) ;; => 42
> (abs -42) ;; => 42
> (abs 0) ;; => 0

ceiling

Rounds the given number up to the nearest integer not less than it.

(ceiling number) -> integer?

  • number : number? - The number to round up.

Examples

> (ceiling 42) ;; => 42
> (ceiling 42.1) ;; => 43
> (ceiling -42.1) ;; => -42

denominator

Retrieves the denominator of the given rational number.

(denominator number) -> integer?

  • number : number? - The rational number to retrieve the denominator from.

Examples

> (denominator 1/2) ;; => 2
> (denominator 3/4) ;; => 4
> (denominator 4) ;; => 1

exact->inexact

Converts an exact number to an inexact number.

(exact->inexact num) -> number?

  • num : number? - The number to convert from exact to inexact.

Examples

> (exact->inexact 10) ;; => 10
> (exact->inexact 1/2) ;; => 0.5
> (exact->inexact 1+2i) ;; => 1+2i

exact-integer-sqrt

Computes the integer square root of the given non-negative integer.

(exact-integer-sqrt number) -> (integer? integer?)

  • number : (and/c integer? positive?) - The non-negative integer to compute the square root for.

Examples

> (exact-integer-sqrt 25) ;; => (5 0)
> (exact-integer-sqrt 35) ;; => (5 10)

exact?

Checks if the given value is exact.

(exact? val) -> boolean?

  • val : any - The value to check for exactness.

Examples

> (exact? 42) ;; => #t
> (exact? 3.14) ;; => #f
> (exact? "hello") ;; => #f

exp

Returns Euler’s number raised to the power of z.

(exp z) -> number?

  • z : number? - The number to raise e to the power of.

Examples

> (exp 0) ;; => 1
> (exp 2) ;; => 7.38905609893065
> (exp 1.5) ;; => 4.4816890703380645

expt

Raises the left operand to the power of the right operand.

(expt base exponent) -> number?

  • base : number? - The base number.
  • exponent : number? - The exponent to raise the base to.

Examples

> (expt 2 3) ;; => 8
> (expt 2.0 0.5) ;; => 1.4142135623730951
> (expt 9 0.5) ;; => 3

finite?

Returns #t if the given number is finite.

(finite? number) -> boolean?

  • number : number? - The number to check for finiteness.

Examples

> (finite? 42) ;; => #t
> (finite? 0.1) ;; => #t
> (finite? +inf.0) ;; => #f
> (finite? -inf.0) ;; => #f
> (finite? +nan.0) ;; => #f

floor

Computes the largest integer less than or equal to the given number.

(floor number) -> number?

  • number : number? - The number to compute the floor for.

Examples

> (floor 3.14) ;; => 3
> (floor 4.99) ;; => 4
> (floor -2.5) ;; => -3

inexact->exact

Converts an inexact number to an exact number.

(inexact->exact num) -> number?

  • num : number? - The number to convert from inexact to exact.

Examples

> (inexact->exact 10.0) ;; => 10
> (inexact->exact 1.5) ;; => 3/2
> (inexact->exact 1.5+2.5i) ;; => 3/2+5/2i

inexact?

Checks if the given value is inexact.

(inexact? val) -> boolean?

  • val : any - The value to check for inexactness.

Examples

> (inexact? 42) ;; => #f
> (inexact? 3.14) ;; => #t

infinite?

Returns #t if the given number is infinite.

(infinite? number) -> boolean?

  • number : number? - The number to check for infiniteness.

Examples

> (infinite? 42) ;; => #f
> (infinite? -nan.0) ;; => #f
> (infinite? +inf.0) ;; => #t

log

Computes the natural logarithm of the given number.

(log number [base]) -> number?

  • number : number? - The number to compute the logarithm for.
  • base : number? - The base of the logarithm. If not provided, defaults to Euler's number (e).

Examples

> (log 10) ;; => 2.302585092994046
> (log 100 10) ;; => 2
> (log 27 3) ;; => 3

magnitude

Computes the magnitude of the given number.

(magnitude number) -> number?

  • number : number? - The number to compute the magnitude for.

Examples

> (magnitude 3+4i) ;; => 5
> (magnitude 5) ;; => 5
> (magnitude -5) ;; => 5

nan?

Returns #t if the real number is Nan.

(nan? value) -> boolean?

  • value : real? - The value to check
(nan? +nan.0) => #t
(nan? 100000) => #f

negative?

Checks if the given real number is negative.

(negative? num) -> boolean?

  • num : real? - The real number to check for negativity.

Examples

> (negative? 0) ;; => #f
> (negative? 1) ;; => #f
> (negative? -1) ;; => #t

numerator

Retrieves the numerator of the given rational number.

(numerator number) -> number?

  • number : number? - The rational number to retrieve the numerator from.

Examples

> (numerator 3/4) ;; => 3
> (numerator 5/2) ;; => 5
> (numerator -2) ;; => -2

positive?

Checks if the given real number is positive.

(positive? num) -> boolean?

  • num : real? - The real number to check for positivity.

Examples

> (positive? 0) ;; => #f
> (positive? 1) ;; => #t
> (positive? -1) ;; => #f

quotient

Returns quotient of dividing numerator by denomintator.

(quotient numerator denominator) -> integer?

  • numerator : integer? - The numerator.
  • denominator : integer? - The denominator.

Examples

> (quotient 11 2) ;; => 5
> (quotient 10 2) ;; => 5
> (quotient -10 2) ;; => -5

round

Rounds the given number to the nearest integer.

(round number) -> number?

  • number : number? - The number to round.

Examples

> (round 3.14) ;; => 3
> (round 4.6) ;; => 5
> (round -2.5) ;; => -3

sqrt

Computes the square root of the given number.

(sqrt number) -> number?

  • number : number? - The number to compute the square root for.

Examples

> (sqrt 4) ;; => 2
> (sqrt 2) ;; => 1.4142135623730951
> (sqrt -1) ;; => 0+1i

square

Computes the square of the given number.

(square number) -> number?

  • number : number? - The number to square.

Examples

> (square 5) ;; => 25
> (square -3) ;; => 9
> (square 2.5) ;; => 6.25

zero?

Checks if the given real number is zero.

(zero? num) -> boolean?

  • num : real? - The number to check for zero.

Examples

> (zero? 0) ;; => #t
> (zero? 0.0) ;; => #t
> (zero? 0.1) ;; => #f

arithmetic-shift

even?

f+

odd?

steel/ord

Real numbers ordering module.

<

Compares real numbers to check if any number is less than the subsequent.

(< x . rest) -> bool?

  • x : real? - The first real number to compare.
  • rest : real? - The rest of the numbers to compare.

Examples

> (< 1) ;; => #t
> (< 3 2) ;; => #f
> (< 2 3) ;; => #t
> (< 3/2 1.5) ;; => #f
> (< 2.5 3/2) ;; => #t
> (< 2 5/2 3) ;; #t

<=

Compares real numbers to check if any number is less than or equal than the subsequent.

(<= x . rest) -> bool?

  • x : real? - The first real number to compare.
  • rest : real? - The rest of the numbers to compare.

Examples

> (<= 1) ;; => #t
> (<= 3 2) ;; => #f
> (<= 2 3) ;; => #t
> (<= 3/2 1.5) ;; => #t
> (<= 2.5 3/2) ;; => #f
> (<= 2 6/2 3) ;; #t

>

Compares real numbers to check if any number is greater than the subsequent.

(> x . rest) -> bool?

  • x : real? - The first real number to compare.
  • rest : real? - The rest of the numbers to compare.

Examples

> (> 1) ;; => #t
> (> 3 2) ;; => #t
> (> 1 1) ;; => #f
> (> 3/2 1.5) ;; => #f
> (> 3/2 1.4) ;; => #t
> (> 3 4/2 1) ;; #t

>=

Compares real numbers to check if any number is greater than or equal than the subsequent.

(>= x . rest) -> bool?

  • x : real? - The first real number to compare.
  • rest : real? - The rest of the numbers to compare.

Examples

> (>= 1) ;; => #t
> (>= 3 2) ;; => #t
> (>= 2 3) ;; => #f
> (>= 3/2 1.5) ;; => #t
> (>= 3/2 1.4) ;; => #t
> (>= 2 4/2 1) ;; #t

steel/ports

eof-object

Returns an EOF object.

(eof-object) -> eof-object?

get-output-bytevector

Extracts the contents from a port created with open-output-bytevector.

(get-output-bytevector port?) -> bytes?

get-output-string

Extracts the string contents from a port created with open-output-string.

(get-output-string port?) -> string?

input-port?

Checks if a given value is an input port

(input-port? any/c) -> bool?

Examples

> (input-port? (stdin)) ;; => #true
> (input-port? "foo") ;; => #false

open-input-bytevector

Creates an input port from a bytevector, that will return the bytevector contents.

(open-input-bytevector bytes?) -> input-port?

open-input-file

Takes a filename path referring to an existing file and returns an input port. Raises an error if the file does not exist

(open-input-file string?) -> input-port?

Examples

> (open-input-file "foo-bar.txt") ;; => #<port>
> (open-input-file "file-does-not-exist.txt")
error[E08]: Io
┌─ :1:2
│
1 │ (open-input-file "foo-bar.txt")
│  ^^^^^^^^^^^^^^^ No such file or directory (os error 2)

open-input-string

Creates an input port from a string, that will return the string contents.

(open-input-string string?) -> input-port?

open-output-bytevector

Creates an output port that accumulates what is written into a bytevector. These bytes can be recovered calling get-output-bytevector.

(open-output-bytevector) -> output-port?

Examples

(define out (open-output-bytevector))


(write-byte 30 out)
(write-byte 250 out)

(get-output-bytevector out) ;; => (bytes 30 250)

open-output-file

Takes a filename path referring to a file to be created and returns an output port.

(open-output-file string?) -> output-port?

Examples

> (open-output-file "foo-bar.txt") ;; => #<port>

open-output-string

Creates an output port that accumulates what is written into a string. This string can be recovered calling get-output-string.

(open-output-string) -> output-port?

Examples

(define out (open-output-string))


(write-char "α" out)
(write-char "ω" out)

(get-output-string out) ;; => "αω"

output-port?

Checks if a given value is an output port

(output-port? any/c) -> bool?

Examples

> (define output (open-output-file "foo.txt"))
> (output-port? output) ;; => #true

peek-byte

Peeks the next byte from an input port.

(peek-byte [port]) -> byte?

  • port : input-port? = (current-input-port)

read-byte

Reads a single byte from an input port.

(read-byte [port]) -> byte?

  • port : input-port? = (current-input-port)

read-char

Reads the next character from an input port.

(read-char [port]) -> char?

  • port : input-port? = (current-input-port)

read-port-to-string

Takes a port and reads the entire content into a string

(read-port-to-string port) -> string?

  • port : input-port?

stdin

Gets the port handle to stdin

(stdin) -> input-port?

Examples

> (stdin) ;; => #<port>

write-byte

Writes a single byte to an output port.

(write-byte b [port])

  • b : byte?
  • port : output-port? = (current-output-port)

write-bytes

Writes the contents of a bytevector into an output port.

(write-bytes buf [port])

  • buf : bytes?
  • port : output-port? = (current-output-port)

close-output-port

flush-output-port

read-line-from-port

stdout

write-line!

steel/process

child-stderr

child-stdin

child-stdout

command

set-current-dir!

set-piped-stdout!

spawn-process

wait

wait->stdout

which

steel/random

rng->gen-range

rng->gen-usize

steel/sets

hashset

hashset->list

hashset->vector

hashset-clear

hashset-contains?

hashset-insert

hashset-length

hashset-subset?

list->hashset

steel/streams

empty-stream

stream-car

stream-cons

stream-empty?

steel/strings

Strings in Steel are immutable, fixed length arrays of characters. They are heap allocated, and are implemented under the hood as referenced counted Rust Strings. Rust Strings are stored as UTF-8 encoded bytes.

char->integer

Returns the Unicode codepoint of a given character.

(char->integer char?) -> integer?

char->number

Attemps to convert the character into a decimal digit, and returns #f on failure.

char-digit?

Returns #t if the character is a decimal digit.

char-downcase

Returns the lower case version of a character, if defined by Unicode, or the same character otherwise.

char-upcase

Returns the upper case version of a character, if defined by Unicode, or the same character otherwise.

char-whitespace?

Returns #t if the character is a whitespace character.

char<=?

Compares characters according to their codepoints, in a "less-than-or-equal" fashion.

(char<=? char1 char2 ... ) -> bool?

  • char1 : char?
  • char2 : char?

char<?

Compares characters according to their codepoints, in a "less-than" fashion.

(char<? char1 char2 ... ) -> bool?

  • char1 : char?
  • char2 : char?

char=?

Checks if all characters are equal.

Requires that all inputs are characters, and will otherwise raise an error.

(char=? char1 char2 ...) -> bool?

  • char1 : char?
  • char2 : char?

char>=?

Compares characters according to their codepoints, in a "greater-than-or-equal" fashion.

(char>=? char1 char2 ... ) -> bool?

  • char1 : char?
  • char2 : char?

char>?

Compares characters according to their codepoints, in a "greater-than" fashion.

(char>? char1 char2 ... ) -> bool?

  • char1 : char?
  • char2 : char?

ends-with?

Checks if the input string ends with a given suffix

(ends-with? input pattern) -> bool?

input : string? pattern: string?

Examples

> (ends-with? "foobar" "foo") ;; => #false
> (ends-with? "foobar" "bar") ;; => #true

int->string

Converts an integer into a string.

(int->string int?) -> string?

Examples

> (int->string 10) ;; => "10"

integer->char

Returns the character corresponding to a given Unicode codepoint.

(integer->char integer?) -> char?

make-string

Creates a string of a given length, filled with an optional character (which defaults to #\0).

(make-string len [char]) -> string?

  • len : int?
  • char : char? = #\0

number->string

Converts the given number to a string.

split-many

Splits a string given a separator pattern into a list of strings.

(split-many str pat) -> (listof string?)

  • str : string?
  • pat : string?

Examples

(split-many "foo,bar,baz" ",") ;; => '("foo" "bar" "baz")
(split-many "foo|bar|" "|") ;; => '("foo" "bar" "")
(split-many "" "&") ;; => '("")

split-once

Splits a string given a separator at most once, yielding a list with at most 2 elements.

(split-once str pat) -> string?

  • str : string?
  • pat : string?

Examples

(split-once "foo,bar,baz" ",") ;; => '("foo" "bar,baz")
(split-once "foo|bar|" "|") ;; => '("foo" "bar|")
(split-once "" "&") ;; => '("")

split-whitespace

Returns a list of strings from the original string split on the whitespace

(split-whitespace string?) -> (listof string?)

Examples

(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")

starts-with?

Checks if the input string starts with a prefix

(starts-with? input pattern) -> bool?

  • input : string?
  • pattern: string?

Examples

> (starts-with? "foobar" "foo") ;; => #true
> (starts-with? "foobar" "bar") ;; => #false

string

Constructs a string from the given characters

string->bytes

Encodes a string as UTF-8 into a bytevector.

(string->bytes string?) -> bytes?

Examples

(string->bytes "Apple") ;; => (bytes 65 112 112 108 101)

string->int

Converts a string into an int. Raises an error if the string cannot be converted to an integer.

(string->int string?) -> int?

Examples

> (string->int "100") ;; => 10
> (string->int "not-an-int") ;; error

string->list

Converts a string into a list of characters.

(string->list s [start] [end]) -> (listof char?)

  • s : string?
  • start : int? = 0
  • end : int?

Examples

> (string->list "hello") ;; => '(#\h #\e #\l #\l #\o)

string->lower

Creates a new lowercased version of the input string

(string->lower string?) -> string?

Examples

> (string->lower "sPonGeBoB tExT") ;; => "spongebob text"

string->number

Converts the given string to a number, with an optional radix. On failure, it returns #f

(string->number digits [radix]) -> (or/c number? boolean?)

  • digits : string?
  • radix : number?

string->symbol

Converts a string into a symbol.

(string->symbol string?) -> symbol?

Examples

> (string->symbol "FooBar") ;; => 'FooBar

string->upper

Creates a new uppercased version of the input string

(string->upper string?) -> string?

Examples

> (string->upper "lower") ;; => "LOWER"

string->utf8

Alias of string->bytes.

string->vector

Returns a vector containing the characters of a given string

(string->vector string?) -> vector?

Examples

(string->vector "hello") ;; => '#(#\h #\e #\l #\l #\o)

string-append

Concatenates all of the given strings into one

(string-append strs...) -> string?

  • strs ... : string?

Examples

> (string-append) ;; => ""
> (string-append "foo" "bar") ;; => "foobar"

string-ci<=?

Compares strings lexicographically (as in"less-than-or-equal"), in a case insensitive fashion.

(string-ci<=? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string-ci<?

Compares strings lexicographically (as in"less-than"), in a case insensitive fashion.

(string-ci<? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string-ci=?

Compares strings for equality, in a case insensitive fashion.

string-ci>=?

Compares strings lexicographically (as in"greater-than-or-equal"), in a case insensitive fashion.

(string-ci>=? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string-ci>?

Compares strings lexicographically (as in"greater-than"), in a case insensitive fashion.

(string-ci>? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string-contains?

Searches a string to check if it contains the second argument.

(string-contains? string? string?) -> bool?

Examples

(string-contains? "hello" "lo") ;;=> #t
(string-contains? "hello" "world") ;;=> #f

string-join

Joins the given list of strings, with an optional separator.

(string-join strings [sep]) -> string?

  • strings : (listof string?)
  • sep : string? = ""

Examples

(string-join '("a" "b" "c")) ;; => "abc"
(string-join '("one" "two" "three") ", ") ;; => "one, two, three"

string-length

Get the length of the given string in UTF-8 bytes.

(string-length string?) -> int?

Examples

> (string-length "apples") ;; => 6
> (string-length "✅") ;; => 3
> (string-length "🤖") ;; => 4

string-ref

Extracts the nth character out of a given string.

(string-ref str n) -> char?

  • str : string?
  • n : int?

string-replace

Replaces all occurrences of a pattern into the given string

(string-replace str from to) -> string?

  • str : string?
  • from : string?
  • to : string?

Examples

(string-replace "hello world" "o" "@") ;; => "hell@ w@rld"

string<=?

Compares strings lexicographically (as in"less-than-equal-to").

(string<=? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string<?

Compares strings lexicographically (as in"less-than").

(string<? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string=?

Compares strings for equality.

(string=? string1 string2 ...) -> bool?

  • string1 : string?
  • string2 : string?

string>=?

Compares strings lexicographically (as in"greater-than-or-equal").

(string>=? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

string>?

Compares strings lexicographically (as in"greater-than").

(string>? s1 s2 ... ) -> bool?

  • s1 : string?
  • s2 : string?

substring

Creates a substring slicing the characters between two indices.

(substring str start end) -> string?

  • str: string?
  • start : int?
  • end : int?

Examples

(substring "hello" 1 4) ;; => "ell"
(substring "hello" 10 15) ;; => error

to-string

Concatenates all of the inputs to their string representation, separated by spaces.

(to-string xs ...)

  • xs : any/c

Examples

> (to-string 10) ;; => "10"
> (to-string 10 20) ;; => "10 20"
> (to-string "hello" "world") ;; => "hello world"

trim

Returns a new string with the leading and trailing whitespace removed.

(trim string?) -> string?

Examples

> (trim "   foo     ") ;; => "foo"

trim-end

Returns a new string with the trailing whitespace removed.

(trim string?) -> string?

Examples

> (trim "   foo     ") ;; => "   foo"

trim-end-matches

Returns a new string with the given pat repeatedly removed from the end of the string

(trim-end-matches string? string?) -> string?

Examples

> (trim-end-matches "123foo1bar123123" "123") ;; => "123foo1bar"

trim-start

Returns a new string with the leading whitespace removed.

(trim string?) -> string?

Examples

> (trim "   foo     ") ;; => "foo     "

trim-start-matches

Returns a new string with the given pat repeatedly removed from the start of the string

(trim-start-matches string? string?) -> string?

Examples

> (trim-start-matches "123foo1bar123123" "123") ;; => "foo1bar123123"

steel/symbols

concat-symbols

symbol->string

steel/syntax

syntax->datum

syntax-e

syntax-loc

syntax-span

syntax/loc

syntax?

steel/time

Contains direct wrappers around the Rust std::time::Instant and std::time::Duration modules. For example, to measure the time something takes:

(define t (instant/now))
(displayln "Hello world")
(displayln (instant/elapsed t))

current-inexact-milliseconds

Returns the number of milliseconds since the Unix epoch as an inexact number.

(current-inexact-milliseconds) -> inexact?

current-milliseconds

Returns the number of milliseconds since the Unix epoch as an integer.

(current-milliseconds) -> int?

current-second

Returns the number of seconds since the Unix epoch as an integer.

(current-second) -> int?

duration->string

Returns a string representation of a duration

(duration->string dur)

  • dur : duration?

local-time/now!

Returns the local time in the format given by the input string (using chrono::Local::format).

(local-time/now! fmt) -> string?

  • fmt : string?

time/sleep-ms

Sleeps the thread for a given number of milliseconds.

(time/sleep-ms ms)

  • ms : int?

duration->seconds

duration-since

instant/elapsed

instant/now

steel/transducers

compose

dropping

enumerating

extending

filtering

flat-mapping

flattening

interleaving

into-count

into-for-each

into-hashmap

into-hashset

into-last

into-list

into-max

into-min

into-nth

into-product

into-reducer

into-string

into-sum

into-vector

mapping

taking

zipping

steel/vectors

make-vector

mut-vec-len

mut-vector-ref

mutable-vector

mutable-vector->clear

mutable-vector->list

mutable-vector->string

mutable-vector-pop!

null?

pop-front

push

push-front

range-vec

vec-append

vec-rest

vector

vector-append!

vector-length

vector-push!

vector-ref

vector-set!

Bytecode

Optimizations

Benchmarks

Common Patterns

Interior Mutability

Async

Memory management

Steel uses a combination of reference counting and a mark and sweep garbage collector in order to manage memory. Important notes regarding this:

  1. All immutable values are reference counted, this includes built in data structures such as:

    • Lists
    • Immutable vectors
    • Hash maps
    • Hash sets
    • Strings
    • Pairs
    • Big integers
    • Symbols (although, most are interned)
  2. All primitive types are unboxed, these include:

    • Characters
    • booleans
    • floats (f64)
    • integers (isize)
    • the #<void> type
  3. Mutable values are transformed into boxes - which are mutable memory locations managed by the garbage collector.

  4. Mutable vectos are a special form handled by the garbage collector as well.

  5. Mutable structs, are immutable vectors with boxes in each slot for each mutable memory location.

  6. Heap allocated boxes are weak references to a reference counted pointer living in the heap. Thus, even memory managed by the garbage collector is also reference counted. This means that unboxing a mutable memory location requires two pointer jumps - first the weak pointer to the heap, then the strong pointer to the underlying value.

    • A nice consequence of this is that we can perform fast collections for unreachable values managed by the heap without having to perform a mark and sweep.

Reference counting optimizations

One consequence of using reference counted variables is that there will be a non trivial amount of time spent performing reference count operations on values coming and going from the stack. The steel compiler and vm performs a few optimizations to reduce the reference counting thrash, and also to improve the usage of the functional data structures built in.

Consider the following:


(define (reverse input output)
  (if (null? input)
    output
    (reverse (cdr input) (cons (car input) output))))

(reverse (list 10 20 30 40) '()) ;; => (list 40 30 20 10)

This is a simple function that takes an input list and reverses it. It does so recursively, calling reverse in tail position, meaning we'll get a tail call optimization and this gets converted into a JUMP in the VM.

Lists in Steel aren't your classic cons cells - they're implemented more like unrolled linked lists or vlists - meaning they're more like chunks of large contiguous vectors strung together. Copying those on each write to it would be silly, so the compiler analyzes a function and attempts to find the last usage of variable. For every last usage of a variable, the VM doesn't just copy the value from the stack, it actually moves it off of the VM stack - meaning the reference count has the potential to be 1 by the time it hits the last usage of a variable.

When the reference count is 1, we can perform an in-place mutation of the resulting list - which gives us a nice performance win! So in the above snippet, we see the following bytecode:

0     SDEF           : 0      reverse
1     PUREFUNC       : 21     lambda
2     PASS           : 0      
3     PASS           : 256    
4     READLOCAL0     : 0      input
5     CALLGLOBAL     : 155    null?
6     FUNC           : 1      null?
7     IF             : 6      
8     READLOCAL1     : 1      output
9     JMP            : 17     
10    READLOCAL0     : 0      input
11    CALLGLOBAL     : 86     cdr
12    FUNC           : 1      cdr
13    MOVEREADLOCAL0 : 0      input ;; <- Last usage of input
14    CALLGLOBAL     : 92     car
15    FUNC           : 1      car
16    MOVEREADLOCAL1 : 1      output ;; <- last usage of output
17    CALLGLOBAL     : 94     cons
18    FUNC           : 2      cons
19    TCOJMP         : 2      reverse
20    PASS           : 0      
21    POPPURE        : 2      
22    ECLOSURE       : 2      
23    EDEF           : 0      
24    BIND           : 975    reverse
25    VOID           : 0      
26    POPPURE        : 0      

Which corresponds to these call sites:


(define (reverse input output)
  (if (null? input)
    output
    (reverse (cdr input) (cons (car input) output))))
                                    ^^^^^  ^^^^^^

In this case, the mutation of output is great - since we can cons onto the list and this under the hood is just a push onto the underlying vector. However, for input it doesn't mean much; car is a reference operation and has no optimizations associated with it. The compiler isn't quite smart enough to do this yet, but rewriting this slightly, we can get the optimization we want:


(define (reverse input output)
  (if (null? input)
      output
      (let ([first-element (car input)]) 
        (reverse (cdr input) (cons first-element output)))))
                                    

Which results in this bytecode:

0     SDEF           : 0      reverse
1     PUREFUNC       : 24     lambda
2     PASS           : 0      
3     PASS           : 256    
4     READLOCAL0     : 0      input
5     CALLGLOBAL     : 156    null?
6     FUNC           : 1      null?
7     IF             : 6      
8     READLOCAL1     : 1      output
9     JMP            : 20     
10    BEGINSCOPE     : 0      
11    READLOCAL0     : 0      input
12    CALLGLOBAL     : 98     car
13    FUNC           : 1      car
14    MOVEREADLOCAL0 : 0      input ;; <- Here
15    CALLGLOBAL     : 97     cdr
16    FUNC           : 1      cdr
17    READLOCAL2     : 2      first-element
18    MOVEREADLOCAL1 : 1      output ;; <- Here
19    CALLGLOBAL     : 86     cons
20    FUNC           : 2      cons
21    TCOJMP         : 2      reverse
22    PASS           : 0      
23    LETENDSCOPE    : 2      
24    POPPURE        : 2      
25    ECLOSURE       : 2      
26    EDEF           : 0      
27    BIND           : 975    reverse
28    VOID           : 0      
29    POPPURE        : 0      

cdr when coupled with a unique list will just move the view of the underlying storage over one element - the element is still there - but we don't see it, and it means we don't have to allocate a new list (since in this new world, cdr can allocate). But - we get some nice cache locality for this!

The first example takes 123 ms to reverse a list of 100000 - whereas the second example takes only 23 ms! In racket, the equivalent code takes somewhere between 2 and 5 ms.

Another toy example of this optimization can be seen here, comparing Steel to Racket:


(define values-to-insert 
  (map (lambda (n) 
    (cons (number->string n) n)) (range 0 10000)))

(define (hash-insert-loop-test hmap values)
  (if (null? values)
      hmap
      (let ([p (car values)])
        (hash-insert-loop-test (hash-insert hmap (car p) (cdr p)) (cdr values)))))

(hash-insert-loop-test (hash) values-to-insert)

This snippet takes about 10-20 ms on my machine, and the equivalent body of code in Racket (replacing hash-insert with hash-set) takes about 8 ms. So this is a nice win. Without this optimization for hash maps, the same code took 5 seconds!