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:
- 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.
- Macro expansion: Some sexps may become other sexps.
- 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
- 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.
- As mentioned earlier, numbers evaluate to themselves.
- 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.
- The symbol
-
also evaluates to a subtraction function value, just like the previous case with addition. - Numbers evaluate to themselves, again.
- 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:
- 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. - 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. a
became 100, but only within the last expression nested inlet
, because it introduced a narrower scope. However, it didn't change the previous binding ofa
, but it shadowed it. And as you can probably guess,b
is not defined beyondlet
.
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:
- Create a new Rust library of type "cdylib".
- Define a module and register types and functions to it.
- Build the Steel crate and install it to
$STEEL_HOME/native
. - 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
:
- Set the
crate-type
to"cdylib"
. - Include
steel-core
with thedylibs
feature as a dependency. - Include
abi_stable
as a dependency. This is required by somesteel-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
orctrl+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
requiresB
andC
, andB
requiresC
,C
will be compiled once and shared betweenA
andB
. - 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
change-current-directory!
Change the current working directory
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
parent-name
Gets the parent directory name for a given path
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
change-current-directory!
Change the current working directory
delete-directory!
Deletes the directory
delete-file!
Deletes the file
file-name
Gets the filename for a given path
parent-name
Gets the parent directory name 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:
-
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)
-
All primitive types are unboxed, these include:
- Characters
- booleans
- floats (f64)
- integers (isize)
- the
#<void>
type
-
Mutable values are transformed into
boxes
- which are mutable memory locations managed by the garbage collector. -
Mutable vectos are a special form handled by the garbage collector as well.
-
Mutable structs, are immutable vectors with boxes in each slot for each mutable memory location.
-
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!