"Returns the element at index n of a list or tuple, or the byte at index n of a string. Zero-indexed: the first element is at index 0. Returns nil if nothing is found in a list or tuple; returns an empty string if nothing is found in a string."
(i as :number) -> at (_, i)
(xs as :list, i as :number) -> base :at (xs, i)
(xs as :tuple, i as :number) -> base :at (xs, i)
(str as :string, i as :number) -> base :at (str, i)
"Takes a value and returns it as a list. For atomic values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Dicts return lists of `(key, value)`` tuples, but watch out: dicts are not ordered and may spit out their pairs in any order. If you wish to get a list of chars in a string, use `chars`."
"Maps a function over a list: returns a new list with elements that are the result of applying the function to each element in the original list. E.g., `map ([1, 2, 3], inc) &=> [2, 3, 4]`. With one argument, returns a function that is a mapper over lists; with two, it executes the mapping function right away."
"Takes a list and a predicate function, and returns a new list with only the items that produce truthy values when the function is called on them. E.g., `filter ([1, 2, 3, 4], odd?) &=> [1, 3]`."
"Truncates a number towards negative infinity. With positive numbers, it returns the integer part. With negative numbers, returns the next more-negative integer."
(n as :number) -> base :floor (n)
}
fn ceil {
"Truncates a number towards positive infinity. With negative numbers, it returns the integer part. With positive numbers, returns the next more-positive integer."
(n as :number) -> base :ceil (n)
}
fn round {
"Rounds a number to the nearest integer."
(n as :number) -> base :round (n)
}
fn range {
"Returns the set of integers between start (inclusive) and end (exclusive) as a list: [start, end). With one argument, starts at 0. If end is less than start, returns an empty list."
(end as :number) -> base :range (0, end)
(start as :number, end as :number) -> base :range (start, end)
}
& additional list operations now that we have comparitors
fn slice {
"Returns a slice of a list or a string, representing a sub-list or sub-string."
(xs as :list, end as :number) -> slice (xs, 0, end)
(xs as :list, start as :number, end as :number) -> when {
(str as :string, end as :number) -> base :slice (str, 0, end)
(str as :string, start as :number, end as :number) -> base :slice (str, start, end)
}
fn slice_n {
"Returns a slice of a list or a string, representing a sub-list or sub-string."
(xs as :list, n as :number) -> slice (xs, 0, n)
(str as :string, n as :number) -> slice (str, 0, n)
(xs as :list, start as :number, n as :number) -> slice (xs, start, add (start, n))
(str as :string, start as :number, n as :number) -> slice (str, start, add (start, n))
}
fn butlast {
"Returns a list, omitting the last element."
(xs as :list) -> slice (xs, 0, dec (count (xs)))
}
fn indices_of {
"Takes a list or string and returns a list of all the indices where the target appears. Returns an empty list if the target does not appear in the scrutinee."
(scrutinee as :list, target) -> base :indices_of (scrutinee, target)
}
fn index_of {
"Takes a list or string returns the first index at which the scrutinee appears. Returns `nil` if the scrutinee does not appear in the search target."
(scrutinee as :list, target) -> base :index_of (scrutinee, target)
}
&&& keywords: funny names
fn keyword? {
"Returns true if a value is a keyword, otherwise returns false."
(kw as :keyword) -> true
(_) -> false
}
fn key? {
"Returns true if a value can be used as a key in a dict: if it's a string or a keyword."
(kw as :keyword) -> true
(str as :string) -> true
(_) -> false
}
& TODO: determine if Ludus should have a `keyword` function that takes a string and returns a keyword. Too many panics, it has weird memory consequences, etc.
fn assoc {
"Takes a dict, key, and value, and returns a new dict with the key set to value."
() -> #{}
(d as :dict) -> d
(d as :dict, k as :keyword, val) -> base :assoc (d, k, val)
(d as :dict, k as :string, val) -> base :assoc (d, k, val)
(d as :dict, (k as :keyword, val)) -> base :assoc (d, k, val)
(d as :dict, (k as :string, val)) -> base :assoc (d, k, val)
}
fn dissoc {
"Takes a dict and a key, and returns a new dict with the key and associated value omitted."
(d as :dict) -> d
(d as :dict, k as :keyword) -> base :dissoc (d, k)
}
fn get {
"Takes a key, dict, and optional default value; returns the value at key. If the value is not found, returns nil or the default value."
(k as :keyword) -> get (k, _)
(d as :dict, k as :keyword) -> base :get (d, k)
(d as :dict, k as :keyword, default) -> match base :get (d, k) with {
nil -> default
val -> val
}
(k as :string) -> get (k, _)
(d as :dict, k as :string) -> base :get (d, k)
(d as :dict, k as :string, default) -> match base :get (d, k) with {
nil -> default
val -> val
}
}
fn update {
"Takes a dict, key, and function, and returns a new dict with the key set to the result of applying the function to original value held at the key."
(d as :dict) -> d
(d as :dict, k as :keyword, updater as :fn) -> assoc (d, k, updater (get (d, k)))
}
fn keys {
"Takes a dict and returns a list of keys in that dict."
(d as :dict) -> do d > list > map (first, _)
}
fn values {
"Takes a dict and returns a list of values in that dict."
(d as :dict) -> do d > list > map (second, _)
}
fn has? {
"Takes a key and a dict, and returns true if there is a non-`nil` value stored at the key."
(k as :keyword) -> has? (_, k)
(d as :dict, k as :keyword) -> do d > get (k) > some?
}
fn dict {
"Takes a list or tuple of `(key, value)` tuples and returns it as a dict. Returns dicts unharmed."
(d as :dict) -> d
(l as :list) -> fold (assoc, l)
(t as :tuple) -> do t > list > dict
}
fn dict? {
"Returns true if a value is a dict."
(d as :dict) -> true
(_) -> false
}
fn each! {
"Takes a list and applies a function, presumably with side effects, to each element in the list. Returns nil."
"Returns a random something. With zero arguments, returns a random number between 0 (inclusive) and 1 (exclusive). With one argument, returns a random number between 0 and n. With two arguments, returns a random number between m and n. Alternately, given a collection (tuple, list, dict, set), it returns a random member of that collection."
() -> base :random ()
(n as :number) -> mult (n, random ())
(m as :number, n as :number) -> add (m, random (sub (n, m)))
"Returns a random integer. With one argument, returns a random integer between 0 and that number. With two arguments, returns a random integer between them."
(n as :number) -> do n > random > floor
(m as :number, n as :number) -> floor (random (m, n))
}
&&& Results, errors and other unhappy values
fn ok {
"Takes a value and wraps it in an :ok result tuple."
(value) -> (:ok, value)
}
fn ok? {
"Takes a value and returns true if it is an :ok result tuple."
((:ok, _)) -> true
(_) -> false
}
fn err {
"Takes a value and wraps it in an :err result tuple, presumably as an error message."
(msg) -> (:err, msg)
}
fn err? {
"Takes a value and returns true if it is an :err result tuple."
((:err, _)) -> true
(_) -> false
}
fn unwrap! {
"Takes a result tuple. If it's :ok, then returns the value. If it's not :ok, then it panics. If it's not a result tuple, it also panics."
"Returns the current process's pid, as a keyword."
() -> base :process (:self)
}
fn send {
"Sends a message to the specified process and returns the message."
(pid as :keyword, msg) -> {
base :process (:send, pid, msg)
msg
}
}
fn link! {
"Links this process to another process. When either one dies--panics or returns--both are shut down."
(pid as :keyword) -> base :process (:link, pid)
}
fn spawn! {
"Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process."
(f as :fn) -> {
let new_pid = base :process (:spawn, f)
link! (new_pid)
new_pid
}
}
fn fledge! {
"Spawns a process and then immediately unlinks from it. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly fledged process."
(f as :fn) -> base :process (:spawn, f)
}
fn yield! {
"Forces a process to yield, allowing other processes to execute."
() -> base :process (:yield)
}
fn alive? {
"Tells if the passed keyword is the id for a live process."
(pid as :keyword) -> base :process (:alive, pid)
}
fn unlink! {
"Unlinks this process from the other process."
(pid as :keyword) -> base :process (:unlink, pid)
}
fn monitor! {
"Subscribes this process to another process's exit signals. There are two possibilities: a panic or a return. Exit signals are in the form of `(:exit, pid, (:ok, value)/(:err, msg))`."
(pid as :keyword) -> if alive? (pid)
then base :process (:monitor, pid)
else nil
}
fn flush! {
"Clears the current process's mailbox and returns all the messages."
() -> base :process (:flush)
}
fn sleep! {
"Puts the current process to sleep for at least the specified number of milliseconds."
(ms as :number) -> base :process (:sleep, ms)
}
fn await! {
"Parks the current process until it receives an exit signal from the passed process. Returns the result of a successful execution or panics if the awaited process panics. If the other process is not alive, returns `nil`."
(pid as :keyword) -> if monitor! (pid)
then receive {
(:exit, sender, (:ok, result)) if eq? (pid, sender) -> result
(:exit, sender, (:err, msg)) if eq? (pid, sender) -> {
let me = self ()
panic! "Process {me} paniced because {pid} panicked: {msg}"
}
}
else nil
& (pids as :list) -> {
& each! (monitor!, pids)
& fold (
& fn (results, pid) -> {
& let result = await! (pid)
& append (results, result)
& }
& pids
& []
& )
& }
}
fn hibernate! {
"Ensures the current process will never return, allowing other processes to do their thing indefinitely. Does not unlink the process, so panics in linked processes will still bubble up."
() -> receive { _ -> hibernate! () }
}
fn heed! {
"Parks the current process until it receives a reply, and returns whatever is replied. Causes a panic if it gets anything other than a `(:reply, result)` tuple."
() -> receive {
(:reply, result) -> result
}
}
fn send_sync {
"Sends the message to the specified process, and waits for a response in the form of a `(:reply, response)` tuple."
(pid, msg) -> {
send (pid, msg)
receive {
(:reply, res) -> res
}
}
}
& TODO: make this more robust, to handle multiple pending requests w/o data races
fn request_fetch! {
(pid as :keyword, url as :string) -> {
store! (fetch_outbox, url)
request_fetch! (pid)
}
(pid as :keyword) -> {
if empty? (unbox (fetch_inbox))
then {
yield! ()
request_fetch! (pid)
}
else {
send (pid, (:reply, unbox (fetch_inbox)))
store! (fetch_inbox, ())
}
}
}
fn fetch {
"Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`."
(url) -> {
let pid = self ()
spawn! (fn () -> request_fetch! (pid, url))
receive {
(:reply, (_, response)) -> response
}
}
}
fn input_reader! {
(pid as :keyword) -> {
if do input > unbox > not
then {
yield! ()
input_reader! (pid)
}
else {
send (pid, (:reply, unbox (input)))
store! (input, nil)
}
}
}
fn read_input {
"Waits until there is input in the input buffer, and returns it once there is."
"Spawns a new turtle in a new process. Methods on the turtle process mirror those of turtle graphics functions in prelude. Returns the pid of the new turtle."
"Old-timey lisp `cons`. `Cons`tructs a tuple out of two arguments."
(x, y) -> (x, y)
}
fn car {
"Old-timey lisp `car`. Stands for 'contents of the address register.' Returns the first element in a `cons`ed pair (or any two-tuple)."
((x, _)) -> x
}
fn cdr {
"Old-timey list `cdr`. Stands for 'contents of the decrement register.' Returns the second element in a `cons`ed pair, usually representing the rest of the list."
((_, x)) -> x
}
fn llist {
"Makes an old-timey linked list of its arguments, of LISt Processor fame."
(...xs) -> foldr (cons, xs, nil)
}
&&& keyboard input
fn key_pressed? {
"Returns true ie the key is currently pressed. Keys are indicated by strings. For non-alphanumeric keys, consult the documentation to get key codes."
(key as :string) -> do keys_down > unbox > contains? (key, _)