& the very base: know something's type fn type { "Returns a keyword representing the type of the value passed in." (x) -> base :type (x) } & & some helper type functions fn coll? { "Returns true if a value is a collection: dict, list, tuple, or set." (coll as :dict) -> true (coll as :list) -> true (coll as :tuple) -> true & (coll as :set) -> true (_) -> false } fn ordered? { "Returns true if a value is an indexed collection: list or tuple." (coll as :list) -> true (coll as :tuple) -> true (coll as :string) -> true (_) -> false } fn assoc? { "Returns true if a value is an associative collection: a dict or a pkg." (d as :dict) -> true (_) -> false } & &&& nil: working with nothing fn nil? { "Returns true if a value is nil." (nil) -> true (_) -> false } fn some? { "Returns true if a value is not nil." (nil) -> false (_) -> true } fn some { "Takes a possibly nil value and a default value. Returns the value if it's not nil, returns the default if it's nil." (nil, default) -> default (value, _) -> value } & ...and if two things are the same fn eq? { "Returns true if all arguments have the same value." (x) -> true (x, y) -> base :eq? (x, y) (x, y, ...zs) -> if eq? (x, y) then loop (y, zs) with { (a, [b]) -> and (eq? (a, x), eq? (b, x)) (a, [b, ...cs]) -> if eq? (a, x) then recur (b, cs) else false } else false } & &&& true & false: boolean logic (part the first) fn bool? { "Returns true if a value is of type :boolean." (false) -> true (true) -> true (_) -> false } fn true? { "Returns true if a value is boolean `true`. Useful to distinguish between `true` and anything else." (true) -> true (_) -> false } fn false? { "Returns `true` if a value is `false`, otherwise returns `false`. Useful to distinguish between `false` and `nil`." (false) -> true (_) -> false } fn bool { "Returns false if a value is nil or false, otherwise returns true." (nil) -> false (false) -> false (_) -> true } fn not { "Returns false if a value is truthy, true if a value is falsy." (nil) -> true (false) -> true (_) -> false } & & tuples: not a lot you can do with them functionally fn tuple? { "Returns true if a value is a tuple." (tuple as :tuple) -> true (_) -> false } & &&& functions: getting things done fn fn? { "Returns true if an argument is a function." (f as :fn) -> true (_) -> false } & what we need for some very basic list manipulation fn first { "Retrieves the first element of an ordered collection--a tuple or a list. If the collection is empty, returns nil." ([]) -> nil (()) -> nil & ("") -> nil (xs as :list) -> base :first (xs) (xs as :tuple) -> base :first (xs) & (str as :string) -> base :slice (str, 0, 1) } fn rest { "Returns all but the first element of a list or tuple, as a list." ([]) -> [] (()) -> () (xs as :list) -> base :rest (xs) (xs as :tuple) -> base :rest (xs) & (str as :string) -> base :rest (str) } fn inc { "Increments a number." (x as :number) -> base :inc (x) } fn dec { "Decrements a number." (x as :number) -> base :dec (x) } fn count { "Returns the number of elements in a collection (including string)." (xs as :list) -> base :count (xs) (xs as :tuple) -> base :count (xs) (xs as :dict) -> base :count (xs) (xs as :string) -> base :count (xs) & (xs as :set) -> base :count (xs) } fn empty? { "Returns true if something is empty. Otherwise returns false (including for things that can't logically be empty, like numbers)." ([]) -> true (#{}) -> true & (s as :set) -> eq? (s, ${}) (()) -> true ("") -> true (_) -> false } fn any? { "Returns true if something is not empty, otherwise returns false (including for things that can't be logically full, like numbers)." ([...]) -> true (#{...}) -> true & (s as :set) -> not (empty? (s)) ((...)) -> true (s as :string) -> not (empty? (s)) (_) -> false } fn list? { "Returns true if the value is a list." (l as :list) -> true (_) -> false } fn list { "Takes a value and returns it as a list. For values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Unordered collections do not preserve order: sets and dicts don't have predictable or stable ordering in output. Dicts return lists of (key, value) tuples." (x) -> base :list (x) } fn append { "Adds an element to a list." () -> [] (xs as :list) -> xs (xs as :list, x) -> base :append (xs, x) } fn fold { "Folds a list." (f as :fn, []) -> [] (f as :fn, xs as :list) -> fold (f, xs, f ()) (f as :fn, [], root) -> [] (f as :fn, xs as :list, root) -> loop (root, first (xs), rest (xs)) with { (prev, curr, []) -> f (prev, curr) (prev, curr, remaining) -> recur ( f (prev, curr) first (remaining) rest (remaining) ) } } fn foldr { "Folds a list, right-associatively." (f as :fn, []) -> [] (f as :fn, xs as :list) -> foldr(f, xs, f ()) (f as :fn, [], root) -> [] (f as :fn, xs as :list, root) -> loop (root, first (xs), rest (xs)) with { (prev, curr, []) -> f (curr, prev) (prev, curr, remaining) -> recur ( f (curr, prev) first (remaining) rest (remaining) ) } } fn map { "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." (f as :fn) -> map (f, _) (kw as :keyword) -> map (kw, _) (f as :fn, xs) -> { fn mapper (prev, curr) -> append (prev, f (curr)) fold (mapper, xs, []) } (kw as :keyword, xs) -> { fn mapper (prev, curr) -> append (prev, kw (curr)) fold (mapper, xs, []) } } fn filter { "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." (xs) -> filter (some?, xs) } fn concat { "Combines two lists, strings, or sets." (x as :string, y as :string) -> "{x}{y}" (xs as :list, ys as :list) -> base :concat (xs, ys) & (xs as :set, ys as :set) -> base :concat (xs, ys) (xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys)) } fn contains? { "Returns true if a set or list contains a value." & (value, s as :set) -> bool (base :get (s, value)) (value, l as :list) -> loop (l) with { ([]) -> false ([...xs]) -> if eq? (first(xs), value) then true else recur (rest (xs)) } } fn print! { "Sends a text representation of Ludus values to the console." (...args) -> { base :print! (args) :ok } } fn show { "Returns a text representation of a Ludus value as a string." (x) -> base :show (x) } fn report! { "Prints a value, then returns it." (x) -> { print! (x) x } (msg as :string, x) -> { print! (concat ("{msg} ", show (x))) x } } fn doc! { "Prints the documentation of a function to the console." (f as :fn) -> do f > base :doc! > print! (_) -> :none } &&& strings: harder than they look! fn string? { "Returns true if a value is a string." (x as :string) -> true (_) -> false } fn string { "Converts a value to a string by using `show`. If it is a string, returns it unharmed. Use this to build up strings of different kinds of values." (x as :string) -> x (x) -> show (x) (x, ...xs) -> loop (string (x), xs) with { (out, [y]) -> concat (out, show (y)) (out, [y, ...ys]) -> recur (concat (out, show (y)), ys) } } fn join { "Takes a list of strings, and joins them into a single string, interposing an optional separator." ([]) -> "" ([str as :string]) -> str (strs as :list) -> join (strs, "") ([], separator as :string) -> "" ([str as :string], separator as :string) -> str ([str, ...strs], separator as :string) -> fold ( fn (joined, to_join) -> concat (joined, separator, to_join) strs str ) } fn split { "Takes a string, and turns it into a list of strings, breaking on the separator." (str as :string, splitter as :string) -> base :split (str, splitter) } fn trim { "Trims whitespace from a string. Takes an optional argument, `:left` or `:right`, to trim only on the left or right." (str as :string) -> base :trim (str) (str as :string, :left) -> base :triml (str) (str as :string, :right) -> base :trimr (str) } fn upcase { "Takes a string and returns it in all uppercase. Works only for ascii characters." (str as :string) -> base :upcase (str) } fn downcase { "Takes a string and returns it in all lowercase. Works only for ascii characters." (str as :string) -> base :downcase (str) } fn chars { "Takes a string and returns its characters as a list. Works only for strings with only ascii characters. Panics on any non-ascii characters." (str as :string) -> match base :chars (str) with { (:ok, chrs) -> chrs (:err, msg) -> panic! msg } } fn chars/safe { "Takes a string and returns its characters as a list, wrapped in a result tuple. Works only for strings with only ascii characters. Returns an error tuple on any non-ascii characters." (str as :string) -> base :chars (str) } fn ws? { "Tells if a string is a whitespace character." (" ") -> true ("\n") -> true ("\t") -> true (_) -> false } fn strip { "Removes punctuation from a string, removing all instances of ,.;:?!" ("{x},{y}") -> strip ("{x}{y}") ("{x}.{y}") -> strip ("{x}{y}") ("{x};{y}") -> strip ("{x}{y}") ("{x}:{y}") -> strip ("{x}{y}") ("{x}?{y}") -> strip ("{x}{y}") ("{x}!{y}") -> strip ("{x}{y}") (x) -> x } fn words { "Takes a string and returns a list of the words in the string. Strips all whitespace." (str as :string) -> { let no_punct = strip (str) let strs = split (no_punct, " ") fn worder (l, s) -> if empty? (s) then l else append (l, s) fold (worder, strs, []) } } fn sentence { "Takes a list of words and turns it into a sentence." (strs as :list) -> join (strs, " ") } fn to_number { "Takes a string that presumably contains a representation of a number, and tries to give you back the number represented. Returns a result tuple." (num as :string) -> base :number (num) } &&& boxes: mutable state and state changes fn box? { "Returns true if a value is a box." (b as :box) -> true (_) -> false } fn unbox { "Returns the value that is stored in a box." (b as :box) -> base :unbox (b) } fn store! { "Stores a value in a box, replacing the value that was previously there. Returns the value." (b as :box, value) -> { base :store! (b, value) value } } fn update! { "Updates a box by applying a function to its value. Returns the new value." (b as :box, f as :fn) -> { let current = unbox (b) let new = f (current) store! (b, new) } } &&& numbers, basically: arithmetic and not much else, yet & TODO: add nan?, fn number? { "Returns true if a value is a number." (x as :number) -> true (_) -> false } fn add { "Adds numbers or vectors." () -> 0 (x as :number) -> x (x as :number, y as :number) -> base :add (x, y) (x, y, ...zs) -> fold (add, zs, base :add (x, y)) & add vectors ((x1, y1), (x2, y2)) -> (add (x1, x2), add (y1, y2)) } fn sub { "Subtracts numbers or vectors." () -> 0 (x as :number) -> x (x as :number, y as :number) -> base :sub (x, y) (x, y, ...zs) -> fold (sub, zs, base :sub (x, y)) ((x1, y1), (x2, y2)) -> (base :sub (x1, x2), base :sub (y1, y2)) } fn mult { "Multiplies numbers or vectors." () -> 1 (x as :number) -> x (x as :number, y as :number) -> base :mult (x, y) (x, y, ...zs) -> fold (mult, zs, mult (x, y)) (scalar as :number, (x, y)) -> (mult (x, scalar), mult (y, scalar)) ((x, y), scalar as :number) -> mult (scalar, (x, y)) } fn div { "Divides numbers. Panics on division by zero." (x as :number) -> x (_, 0) -> panic! "Division by zero." (x as :number, y as :number) -> base :div (x, y) (x, y, ...zs) -> { let divisor = fold (mult, zs, y) div (x, divisor) } } fn div/0 { "Divides numbers. Returns 0 on division by zero." (x as :number) -> x (_, 0) -> 0 (x as :number, y as :number) -> base :div (x, y) (x, y, ...zs) -> { let divisor = fold (mult, zs, y) div/0 (x, divisor) } } fn div/safe { "Divides a number. Returns a result tuple." (x as :number) -> (:ok, x) (_, 0) -> (:err, "Division by zero") (x, y) -> (:ok, div (x, y)) (x, y, ...zs) -> { let divisor = fold (mult, zs, y) div/safe (x, divisor) } } fn inv { "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 at { "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." (xs as :list, n as :number) -> base :at (xs, n) (xs as :tuple, n as :number) -> base :at (xs, n) & (str as :string, n as :number) -> { & let raw = base :at (str, n) & when { & nil? (raw) -> nil & gte? (raw, 128) -> panic! "not an ASCII char" & true -> base :str_slice (str, n, inc (n)) & } & } (_) -> nil } & fn first { & "Returns the first element of a list or tuple." & (xs) if ordered? -> at (xs, 0) & } fn second { "Returns the second element of a list or tuple." (xs) if ordered? (xs) -> at (xs, 1) } fn last { "Returns the last element of a list or tuple." (xs) if ordered? (xs) -> at (xs, dec (count (xs))) } 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 { gte? (start, end) -> [] gt? (end, count (xs)) -> slice (xs, start, count (xs)) neg? (start) -> slice (xs, 0, end) true -> base :slice (xs, start, end) } (str as :string, end as :number) -> base :str_slice (str, 0, end) (str as :string, start as :number, end as :number) -> base :str_slice (str, start, end) } fn butlast { "Returns a list, omitting the last element." (xs as :list) -> slice (xs, 0, dec (count (xs))) } &&& keywords: funny names fn keyword? { "Returns true if a value is a keyword, otherwise returns false." (kw as :keyword) -> 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 :keyword, 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) } & TODO: consider merging `get` and `at` 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, _) (k as :keyword, d as :dict) -> base :get (d, k) (k as :keyword, d as :dict, 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 (k, d))) } 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, _) } & TODO: add sets to this? 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, _) (k as :keyword, d as :dict) -> 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." (f! as :fn, []) -> nil (f! as :fn, [x]) -> { f! (x); nil } (f! as :fn, [x, ...xs]) -> { f! (x); each! (f!, xs) } } fn random { "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)) } &&& 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." ((:ok, value)) -> value ((:err, msg)) -> panic! string ("Unwrapped :err! ", msg) (_) -> panic! "Cannot unwrap something that's not an error tuple." } fn unwrap_or { "Takes a value that is a result tuple and a default value. If it's :ok, then it returns the value. If it's :err, returns the default value." ((:ok, value), _) -> value ((:err, _), default) -> default } fn assert! { "Asserts a condition: returns the value if the value is truthy, panics if the value is falsy. Takes an optional message." (value) -> if value then value else panic! "Assert failed: {value}" (msg, value) -> if value then value else panic! "Assert failed: {msg} with {value}" } &&& Turtle & other graphics & some basic colors & these are the "basic" css colors & https://developer.mozilla.org/en-US/docs/Web/CSS/named-color let colors = #{ :black (0, 0, 0, 255) :silver (192, 192, 192, 255) :gray (128, 128, 128, 255) :white (255, 255, 255, 255) :maroon (128, 0, 0, 255) :red (255, 0, 0, 255) :purple (128, 0, 128, 255) :fuchsia (255, 0, 255, 255) :green (0, 128, 0, 255) :lime (0, 255, 0, 255) :olive (128, 128, 0, 255) :yellow (255, 255, 0, 255) :navy (0, 0, 128, 255) :blue (0, 0, 255, 255) :teal (0, 128, 128, 255) :aqua (0, 255, 25, 255) } & the initial turtle state let turtle_init = #{ :position (0, 0) & let's call this the origin for now :heading 0 & this is straight up :pendown? true :pencolor :white :penwidth 1 :visible? true } & turtle states: refs that get modified by calls & turtle_commands is a list of commands, expressed as tuples box turtle_commands = [] box turtle_state = turtle_init fn apply_command fn add_command! (command) -> { update! (turtle_commands, append (_, command)) let prev = unbox (turtle_state) let curr = apply_command (prev, command) store! (turtle_state, curr) :ok } fn forward! { "Moves the turtle forward by a number of steps. Alias: fd!" (steps as :number) -> add_command! ((:forward, steps)) } let fd! = forward! fn back! { "Moves the turtle backward by a number of steps. Alias: bk!" (steps as :number) -> add_command! ((:back, steps)) } let bk! = back! fn left! { "Rotates the turtle left, measured in turns. Alias: lt!" (turns as :number) -> add_command! ((:left, turns)) } let lt! = left! fn right! { "Rotates the turtle right, measured in turns. Alias: rt!" (turns as :number) -> add_command! ((:right, turns)) } let rt! = right! fn penup! { "Lifts the turtle's pen, stopping it from drawing. Alias: pu!" () -> add_command! ((:penup)) } let pu! = penup! fn pendown! { "Lowers the turtle's pen, causing it to draw. Alias: pd!" () -> add_command! ((:pendown)) } let pd! = pendown! fn pencolor! { "Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: pc!" (color as :keyword) -> add_command! ((:pencolor, color)) (gray as :number) -> add_command! ((:pencolor, (gray, gray, gray, 255))) ((r as :number, g as :number, b as :number)) -> add_command! ((:pencolor, (r, g, b, 255))) ((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:pencolor, (r, g, b, a))) } let pc! = pencolor! fn penwidth! { "Sets the width of the turtle's pen, measured in pixels. Alias: pw!" (width as :number) -> add_command! ((:penwidth, width)) } let pw! = penwidth! fn background! { "Sets the background color behind the turtle and path. Alias: bg!" (color as :keyword) -> add_command! ((:background, color)) (gray as :number) -> add_command! ((:background, (gray, gray, gray, 255))) ((r as :number, g as :number, b as :number)) -> add_command! ((:background, (r, g, b, 255))) ((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:background, (r, g, b, a))) } let bg! = background! fn home! { "Sends the turtle home: to the centre of the screen, pointing up. If the pen is down, the turtle will draw a path to home." () -> add_command! ((:home)) } fn clear! { "Clears the canvas and sends the turtle home." () -> add_command! ((:clear)) } fn goto! { "Sends the turtle to (x, y) coordinates. If the pen is down, the turtle will draw a path to its new location." (x as :number, y as :number) -> add_command! ((:goto, (x, y))) ((x, y)) -> goto! (x, y) } fn setheading! { "Sets the turtle's heading. The angle is specified in turns, with 0 pointing up. Increasing values rotate the turtle counter-clockwise." (heading as :number) -> add_command! ((:setheading, heading)) } fn showturtle! { "If the turtle is hidden, shows the turtle. If the turtle is already visible, does nothing." () -> add_command! ((:show)) } fn hideturtle! { "If the turtle is visible, hides it. If the turtle is already hidden, does nothing." () -> add_command! ((:hide)) } fn loadstate! { "Sets the turtle state to a previously saved state." (state) -> { let #{position, heading, pendown?, pencolor, penwidth, visible?} = state add_command! ((:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) } } fn apply_command { "Takes a turtle state and a command and calculates a new state." (state, command) -> match command with { (:goto, (x, y)) -> assoc (state, :position, (x, y)) (:home) -> do state > assoc (_, :position, (0, 0)) > assoc (_, :heading, 0) (:clear) -> do state > assoc (state, :position, (0, 0)) > assoc (_, :heading, 0) (:right, turns) -> update (state, :heading, add (_, turns)) (:left, turns) -> update (state, :heading, sub (_, turns)) (:forward, steps) -> { let #{heading, position, ...} = state let unit = heading/vector (heading) let vect = mult (steps, unit) update (state, :position, add (vect, _)) } (:back, steps) -> { let #{heading, position, ...} = state let unit = heading/vector (heading) let vect = mult (steps, unit) update (state, :position, sub (_, vect)) } (:penup) -> assoc (state, :pendown?, false) (:pendown) -> assoc (state, :pendown?, true) (:penwidth, pixels) -> assoc (state, :penwidth, pixels) (:pencolor, color) -> assoc (state, :pencolor, color) (:setheading, heading) -> assoc (state, :heading, heading) (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor) -> #{position, heading, visible?, pendown?, penwidth, pencolor} (:show) -> assoc (state, :visible?, true) (:hide) -> assoc (state, :visible?, false) (:background, _) -> state } } & position () -> (x, y) fn position { "Returns the turtle's current position." () -> do turtle_state > unbox > :position } fn heading { "Returns the turtle's current heading." () -> do turtle_state > unbox > :heading } fn pendown? { "Returns the turtle's pen state: true if the pen is down." () -> do turtle_state > unbox > :pendown? } fn pencolor { "Returns the turtle's pen color as an (r, g, b, a) tuple or keyword." () -> do turtle_state > unbox > :pencolor } fn penwidth { "Returns the turtle's pen width in pixels." () -> do turtle_state > unbox > :penwidth } box state = nil #{ type coll? ordered? assoc? nil? some? some eq? bool? true? false? bool not tuple? fn? first rest inc dec count empty? any? list? list first fold foldr append map filter keep concat contains? print! show report! doc! string string? join split trim upcase downcase chars chars/safe ws? strip words sentence to_number box? unbox store! update! add sub mult div div/0 div/safe inv inv/0 inv/safe abs neg zero? gt? gte? lt? lte? between? neg? pos? abs pi tau turn/deg deg/turn turn/rad rad/turn deg/rad rad/deg sin cos tan rotate atan/2 angle mod mod/0 mod/safe even? odd? square sqrt sqrt/safe dist heading/vector floor ceil round range at first second last butlast slice keyword? assoc dissoc update keys values get has? dict dict? each! random random_int ok ok? err err? unwrap! unwrap_or assert! colors turtle_init turtle_commands turtle_state forward! fd! back! bk! left! lt! right! rt! penup! pu! pendown! pd! pencolor! pc! penwidth! pw! background! bg! home! clear! goto! setheading! showturtle! hideturtle! loadstate! position heading pendown? pencolor penwidth state }