"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]`."
(p? as :fn) -> filter (p?, _)
(p? as :fn, xs) -> {
fn filterer (filtered, x) -> if p? (x)
then append (filtered, x)
else filtered
fold (filterer, xs, [])
}
}
fn keep {
"Takes a list and returns a new list with any `nil` values omitted."
"Returns the inverse of a number: 1/n or `div (1, n)`. Panics on division by zero."
(x as :number) -> div (1, x)
}
fn inv/0 {
"Returns the inverse of a number: 1/n or `div/0 (1, n)`. Returns 0 on division by zero."
(x as :number) -> div/0 (1, x)
}
fn inv/safe {
"Returns the inverse of a number: 1/n or `div/safe (1, n)`. Returns a result tuple."
(x as :number) -> div/safe (1, x)
}
fn neg {
"Multiplies a number by -1, negating it."
(n as :number) -> mult (n, -1)
}
fn zero? {
"Returns true if a number is 0."
(0) -> true
(_) -> false
}
fn gt? {
"Returns true if numbers are in decreasing order."
(x as :number) -> true
(x as :number, y as :number) -> base :gt? (x, y)
(x, y, ...zs) -> loop (y, zs) with {
(a, [b]) -> base :gt? (a, b)
(a, [b, ...cs]) -> if base :gt? (a, b)
then recur (b, cs)
else false
}
}
fn gte? {
"Returns true if numbers are in decreasing or flat order."
(x as :number) -> true
(x as :number, y as :number) -> base :gte? (x, y)
(x, y, ...zs) -> loop (y, zs) with {
(a, [b]) -> base :gte? (a, b)
(a, [b, ...cs]) -> if base :gte? (a, b)
then recur (b, cs)
else false
}
}
fn lt? {
"Returns true if numbers are in increasing order."
(x as :number) -> true
(x as :number, y as :number) -> base :lt? (x, y)
(x, y, ...zs) -> loop (y, zs) with {
(a, [b]) -> base :lt? (a, b)
(a, [b, ...cs]) -> if base :lt? (a, b)
then recur (b, cs)
else false
}
}
fn lte? {
"Returns true if numbers are in increasing or flat order."
(x as :number) -> true
(x as :number, y as :number) -> base :lte? (x, y)
(x, y, ...zs) -> loop (y, zs) with {
(a, [b]) -> base :lte? (a, b)
(a, [b, ...cs]) -> if base :lte? (a, b)
then recur (b, cs)
else false
}
}
fn between? {
"Returns true if a number is in the range [lower, higher): greater than or equal to the lower number, less than the higher."
(lower as :number, higher as :number, x as :number) -> and (
gte? (x, lower)
lt? (x, higher)
)
}
fn neg? {
"Returns true if a value is a negative number, otherwise returns false."
(x as :number) if lt? (x, 0) -> true
(_) -> false
}
fn pos? {
"Returns true if a value is a positive number, otherwise returns false."
(x as :number) if gt? (x, 0) -> true
(_) -> false
}
fn abs {
"Returns the absolute value of a number."
(0) -> 0
(n as :number) -> if neg? (n) then mult (-1, n) else n
}
&&& Trigonometry functions
& Ludus uses turns as its default unit to measure angles
& However, anything that takes an angle can also take a
& units argument, that's a keyword of :turns, :degrees, or :radians
let pi = base :pi
let tau = mult (2, pi)
fn turn/deg {
"Converts an angle in turns to an angle in degrees."
(a as :number) -> mult (a, 360)
}
fn deg/turn {
"Converts an angle in degrees to an angle in turns."
(a as :number) -> div (a, 360)
}
fn turn/rad {
"Converts an angle in turns to an angle in radians."
(a as :number) -> mult (a, tau)
}
fn rad/turn {
"Converts an angle in radians to an angle in turns."
(a as :number) -> div (a, tau)
}
fn deg/rad {
"Converts an angle in degrees to an angle in radians."
(a as :number) -> mult (tau, div (a, 360))
}
fn rad/deg {
"Converts an angle in radians to an angle in degrees."
(a as :number) -> mult (360, div (a, tau))
}
fn sin {
"Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in."
(a as :number) -> do a > turn/rad > base :sin
(a as :number, :turns) -> do a > turn/rad > base :sin
(a as :number, :degrees) -> do a > deg/rad > base :sin
(a as :number, :radians) -> base :sin (a)
}
fn cos {
"Returns the cosine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in."
(a as :number) -> do a > turn/rad > base :cos
(a as :number, :turns) -> do a > turn/rad > base :cos
(a as :number, :degrees) -> do a > deg/rad > base :cos
(a as :number, :radians) -> base :cos (a)
}
fn tan {
"Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in."
(a as :number) -> do a > turn/rad > base :tan
(a as :number, :turns) -> do a > turn/rad > base :tan
(a as :number, :degrees) -> do a > deg/rad > base :tan
(a as :number, :radians) -> base :tan (a)
}
fn rotate {
"Rotates a vector by an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in."
((x, y), a) -> rotate ((x, y), a, :turns)
((x, y), a, units as :keyword) -> (
sub (mult (x, cos (a, units)), mult (y, sin (a, units)))
add (mult (x, sin (a, units)), mult (y, cos (a, units)))
)
}
fn atan/2 {
"Returns an angle from a slope. Takes an optional keyword argument to specify units. Takes either two numbers or a vector tuple."
(x as :number, y as :number) -> do base :atan_2 (x, y) > rad/turn
(x, y, :turns) -> atan/2 (x, y)
(x, y, :radians) -> base :atan_2 (x, y)
(x, y, :degrees) -> do base :atan_2 (x, y) > rad/deg
((x, y)) -> atan/2 (x, y)
((x, y), units as :keyword) -> atan/2 (x, y, units)
}
fn angle {
"Calculates the angle between two vectors."
(v1, v2) -> sub (atan/2 (v2), atan/2 (v1))
}
fn mod {
"Returns the modulus of x and y. Truncates towards negative infinity. Panics if y is 0."
(x as :number, 0) -> panic! "Division by zero."
(x as :number, y as :number) -> base :mod (x, y)
}
fn mod/0 {
"Returns the modulus of x and y. Truncates towards negative infinity. Returns 0 if y is 0."
(x as :number, 0) -> 0
(x as :number, y as :number) -> base :mod (x, y)
}
fn mod/safe {
"Returns the modulus of x and y in a result tuple, or an error if y is 0. Truncates towards negative infinity."
(x as :number, 0) -> (:err, "Division by zero.")
(x as :number, y as :number) -> (:ok, base :mod (x, y))
}
fn even? {
"Returns true if a value is an even number, otherwise returns false."
(x as :number) if eq? (0, mod (x, 2)) -> true
(_) -> false
}
fn odd? {
"Returns true if a value is an odd number, otherwise returns false."
(x as :number) if eq? (1, mod (x, 2)) -> true
(_) -> false
}
fn square {
"Squares a number."
(x as :number) -> mult (x, x)
}
fn sqrt {
"Returns the square root of a number. Panics if the number is negative."
(x as :number) if not (neg? (x)) -> base :sqrt (x)
}
fn sqrt/safe {
"Returns a result containing the square root of a number, or an error if the number is negative."
(x as :number) -> if not (neg? (x))
then (:ok, base :sqrt (x))
else (:err, "sqrt of negative number")
}
fn sum_of_squares {
"Returns the sum of squares of numbers."
() -> 0
(x as :number) -> square (x)
(x as :number, y as :number) -> add (square (x), square (y))
(x, y, ...zs) -> fold (
fn (sum, z) -> add (sum, square (z))
zs
sum_of_squares (x, y))
}
fn dist {
"Returns the distance from the origin to a point described by x and y, or by the vector (x, y)."
(x as :number, y as :number) -> sqrt (sum_of_squares (x, y))
((x, y)) -> dist (x, y)
}
fn heading/vector {
"Takes a turtle heading, and returns a unit vector of that heading."
(heading) -> {
& 0 is 90º/0.25T, 0.25 is 180º/0.5T, 0.5 is 270º/0.75T, 0.75 is 0º/0T
let a = add (neg (heading), 0.25)
(cos (a), sin (a))
}
}
fn floor {
"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 {
"Takes a list or string and returns a list of all the indices where the scrutinee appears. Returns an empty list if the scrutinee does not appear in the search target."
(target as :list, scrutinee) -> {
fn searcher ((i, indices), curr) -> if eq? (scrutinee, curr)
then (inc (i), append (indices, i))
else (inc (i), indices)
let (_, idxes) = fold (searcher, target, (0, []))
idxes
}
& (target as :string, scrutinee as :string) -> {
& let scrut_len = count (scrutinee)
& fn searcher ((i, indices), curr) -> {
& let srch_substr = slice_n (remaining, scrut_len)
& if eq? (scrutinee, srch_substr)
& then (inc (i), append (indices, i))
& else (inc (i), indices)
& }
& let (_, idxes) = fold (searcher, target, (0, []))
& idxes
& }
}
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."
(target as :list, scrutinee) -> first (indices_of (target, scrutinee))
(target as :string, scrutinee as :string) -> first (indices_of (target, scrutinee))
}
& (target as :list, scrutinee) -> loop (0) with {
& (i) if gte? (i, count (target)) -> nil
& (i) -> if eq? (scrutinee, at (target, i))
& then i
& else recur (inc (i))
& }
& (target as :string, scrutinee as :string) -> {
& let scrut_len = count (scrutinee)
& loop (0, target) with {
& (i, "") -> nil
& (i, remaining) -> {
& let srch_substr = slice_n (remaining, scrut_len)
& 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)
"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)))
(l as :list) -> {
let i = do l > count > random > floor
at (l, i)
}
(t as :tuple) -> {
let i = do t > count > random > floor
at (t, i)
}
(d as :dict) -> {
let key = do d > keys > random
get (key, d)
}
& (s as :set) -> do s > list > random
}
fn random_int {
"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))
"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 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."
"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 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) -> base :process (:monitor, pid)
"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."
(pid as :keyword) -> {
monitor! (pid)
receive {
(:exit, _, (:ok, result)) -> result
(:exit, _, (:err, msg)) -> {
panic! "Monitored process {pid} panicked with {msg}" }
}
}
}
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."
"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 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."