Helpers for authoring Elixir-side host functions exposed to Scheme.
Schooner does not auto-marshal across the host boundary. A host
function has the same shape as a built-in primitive — it consumes a
list of Schooner.Value.t/0 arguments and returns a
Schooner.Value.t/0 — and uses the helpers in this module to move
between Scheme values and idiomatic Elixir terms. The named
constructors and accessors form the seam that future
representation changes pivot on; even when the underlying impl is
the identity (Schooner strings are bare Elixir binaries today),
host code stays representation-agnostic by going through the
helpers.
Naming convention
Asserting accessors (to_*!/2) raise Schooner.Host.TypeError when
the input does not match the expected shape. They take a keyword
argument with :op set to a host-supplied label for the call site
so the error points at the wrapper, not the bare predicate.
Total accessors (to_*/1) return {:ok, term()} | :error for the
"branch on shape" case where a type mismatch is not an error.
Recommended use pattern
Several accessor names — Schooner.Host.to_string/1 and
Schooner.Host.to_string!/2 — collide with Kernel.to_string/1.
Don't import Schooner.Host. Use alias Schooner.Host and
call the accessors as Host.to_string! etc.; the alias is one
line and the call sites stay explicit.
Worked example
defmodule MyApp.SchemeLib do
alias Schooner.Host
@spec specs() :: [{binary(), Schooner.Value.arity_spec(), fun()}]
def specs do
[
{"info", 1, &info/1},
{"now-ms", 0, &now_ms/1}
]
end
defp info([msg]) do
text = Host.to_string!(msg, op: "myapp/info")
IO.puts(text)
:unspecified
end
defp now_ms([]) do
System.system_time(:millisecond)
end
end
Summary
Functions
Build a Schooner.Library from host-supplied primitive specs and
arbitrary value bindings.
Assert value is a Scheme boolean and return the Elixir
true | false. Note: this differs from "truthy" — only false
is Scheme-falsy, but every other value is not a Scheme boolean.
Use Schooner.Value.truthy?/1 for the truthiness test.
Assert value is a Scheme bytevector and return the underlying
Elixir binary.
Assert value is a Scheme character and return the codepoint
integer.
Assert value is a bare inexact real and return the Elixir float.
Rejects integers, rationals, the float specials (+inf.0,
-inf.0, +nan.0), and complex. Hosts that want "any real
number, coerced" should call to_real!/2 instead.
Assert value is a foreign-wrapped host term and return the
wrapped term. Mirrors Schooner.Value.foreign_ref/1 but raises
the host-facing TypeError instead of ArgumentError.
Assert value is an exact integer and return the bare Elixir
integer. Rejects rationals, floats, the float specials, and
complex.
Assert value is a proper Scheme list and return the underlying
Elixir list. Raises on improper lists and non-list values.
Assert value is callable from Elixir — a closure, a primitive,
or a parameter — and return it unchanged. Use this to validate
the shape of a callback argument before passing it to
Schooner.apply/2.
Assert value is any real number and return it as an Elixir
number. Integers and floats pass through; rationals are coerced
to their float approximation; the float specials are rejected
(no plain BEAM-float representation).
Assert value is a Scheme string and return the underlying Elixir
binary. Rejects symbols, bytevectors, and any non-string.
Assert value is a Scheme symbol and return its name as an Elixir
binary.
Assert value is a Scheme vector and return its elements as an
Elixir list. The list shape is more idiomatic for Elixir
iteration; hosts that want a tuple can call Tuple.to_list/1's
inverse, but most use cases want Enum.
Functions
@spec bool(boolean()) :: Schooner.Value.bool_v()
@spec bytevector([byte()] | binary()) :: Schooner.Value.bytevector_v()
@spec char(non_neg_integer()) :: Schooner.Value.char_v()
@spec foreign(term()) :: Schooner.Value.foreign_v()
@spec library(keyword()) :: Schooner.Library.t()
Build a Schooner.Library from host-supplied primitive specs and
arbitrary value bindings.
Options
:name— canonical library name as a list of binary or non-negative-integer segments. Defaults to[], which marks the library as anonymous: it cannot be reached via Scheme(import ...), and its bindings are applied directly to the runtime environment when the embedder lists it. Named libraries (e.g.["myapp", "log"]) are sandbox-safe — the script must explicitly(import (myapp log))to see them.:primitives— list of{name, arity, fun}triples registered asSchooner.Value.primitive/3bindings.aritymay be an integer,{:at_least, n}, or{:between, lo, hi}.funmust be(list() -> Schooner.Value.t())— the underlying primitive ABI.:values— list of{name, value}pairs for arbitrarySchooner.Value.t/0bindings (constants, foreign-wrapped service handles, pre-built closures). Useful when the host wants to expose data, not just procedures.
Names within a library must be unique across :primitives and
:values combined; a collision raises ArgumentError.
Examples
Named library — script must import:
Schooner.Host.library(
name: ["myapp", "log"],
primitives: [
{"info", 1, &MyApp.SchemeLib.info/1}
]
)Anonymous library — bindings auto-applied, no import required. Sandbox-loosening; list deliberately:
Schooner.Host.library(
primitives: [
{"now-ms", 0, fn [] -> System.system_time(:millisecond) end}
]
)
@spec list([Schooner.Value.t()]) :: Schooner.Value.t()
@spec pair(Schooner.Value.t(), Schooner.Value.t()) :: Schooner.Value.pair_v()
@spec primitive(binary(), Schooner.Value.arity_spec(), (list() -> Schooner.Value.t())) :: Schooner.Value.primitive_v()
@spec string(binary()) :: Schooner.Value.string_v()
@spec symbol(binary()) :: Schooner.Value.sym_v()
@spec to_bool(Schooner.Value.t()) :: {:ok, boolean()} | :error
@spec to_bool!( Schooner.Value.t(), keyword() ) :: boolean()
Assert value is a Scheme boolean and return the Elixir
true | false. Note: this differs from "truthy" — only false
is Scheme-falsy, but every other value is not a Scheme boolean.
Use Schooner.Value.truthy?/1 for the truthiness test.
@spec to_bytevector(Schooner.Value.t()) :: {:ok, binary()} | :error
@spec to_bytevector!( Schooner.Value.t(), keyword() ) :: binary()
Assert value is a Scheme bytevector and return the underlying
Elixir binary.
@spec to_char(Schooner.Value.t()) :: {:ok, non_neg_integer()} | :error
@spec to_char!( Schooner.Value.t(), keyword() ) :: non_neg_integer()
Assert value is a Scheme character and return the codepoint
integer.
@spec to_float(Schooner.Value.t()) :: {:ok, float()} | :error
@spec to_float!( Schooner.Value.t(), keyword() ) :: float()
Assert value is a bare inexact real and return the Elixir float.
Rejects integers, rationals, the float specials (+inf.0,
-inf.0, +nan.0), and complex. Hosts that want "any real
number, coerced" should call to_real!/2 instead.
@spec to_foreign_ref(Schooner.Value.t()) :: {:ok, term()} | :error
@spec to_foreign_ref!( Schooner.Value.t(), keyword() ) :: term()
Assert value is a foreign-wrapped host term and return the
wrapped term. Mirrors Schooner.Value.foreign_ref/1 but raises
the host-facing TypeError instead of ArgumentError.
@spec to_integer(Schooner.Value.t()) :: {:ok, integer()} | :error
@spec to_integer!( Schooner.Value.t(), keyword() ) :: integer()
Assert value is an exact integer and return the bare Elixir
integer. Rejects rationals, floats, the float specials, and
complex.
@spec to_list(Schooner.Value.t()) :: {:ok, [Schooner.Value.t()]} | :error
@spec to_list!( Schooner.Value.t(), keyword() ) :: [Schooner.Value.t()]
Assert value is a proper Scheme list and return the underlying
Elixir list. Raises on improper lists and non-list values.
@spec to_proc(Schooner.Value.t()) :: {:ok, Schooner.Value.t()} | :error
@spec to_proc!( Schooner.Value.t(), keyword() ) :: Schooner.Value.t()
Assert value is callable from Elixir — a closure, a primitive,
or a parameter — and return it unchanged. Use this to validate
the shape of a callback argument before passing it to
Schooner.apply/2.
@spec to_real(Schooner.Value.t()) :: {:ok, number()} | :error
@spec to_real!( Schooner.Value.t(), keyword() ) :: number()
Assert value is any real number and return it as an Elixir
number. Integers and floats pass through; rationals are coerced
to their float approximation; the float specials are rejected
(no plain BEAM-float representation).
@spec to_string(Schooner.Value.t()) :: {:ok, binary()} | :error
@spec to_string!( Schooner.Value.t(), keyword() ) :: binary()
Assert value is a Scheme string and return the underlying Elixir
binary. Rejects symbols, bytevectors, and any non-string.
@spec to_symbol_name(Schooner.Value.t()) :: {:ok, binary()} | :error
@spec to_symbol_name!( Schooner.Value.t(), keyword() ) :: binary()
Assert value is a Scheme symbol and return its name as an Elixir
binary.
@spec to_vector(Schooner.Value.t()) :: {:ok, [Schooner.Value.t()]} | :error
@spec to_vector!( Schooner.Value.t(), keyword() ) :: [Schooner.Value.t()]
Assert value is a Scheme vector and return its elements as an
Elixir list. The list shape is more idiomatic for Elixir
iteration; hosts that want a tuple can call Tuple.to_list/1's
inverse, but most use cases want Enum.
@spec vector([Schooner.Value.t()]) :: Schooner.Value.vector_v()