diff --git a/src/ludus/prelude.ld b/src/ludus/prelude.ld index 5afd761..3a0bb72 100644 --- a/src/ludus/prelude.ld +++ b/src/ludus/prelude.ld @@ -136,7 +136,7 @@ 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, ${}) + (s as :set) -> eq? (s, ${}) (()) -> true ("") -> true (_) -> false @@ -165,6 +165,8 @@ fn set? { (_) -> false } +& add a contains? or has? function + fn fold { "Folds a list." (f as :fn, xs as :list) -> fold (f, xs, f ()) @@ -179,7 +181,7 @@ fn fold { } fn map { - "Maps over a list." + "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]`." (f as :fn, xs) -> { fn mapper (prev, curr) -> append (prev, f (curr)) fold (mapper, xs, []) @@ -191,7 +193,7 @@ fn map { } 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." + "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, xs) -> { fn filterer (filtered, x) -> if p? (x) then append (filtered, x) @@ -375,7 +377,7 @@ fn mult { () -> 1 (x as :number) -> x (x as :number, y as :number) -> base :mult (x, y) - (x, y, ...zs) -> fold (base :mult, mult (x, y), zs) + (x, y, ...zs) -> fold (base :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)) } @@ -413,10 +415,20 @@ fn div/safe { } } +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 abs { "Returns the absolute value of a number." (0) -> 0 - (n) -> if neg? (n) then mult (-1, n) else n + (n as :number) -> if neg? (n) then mult (-1, n) else n } fn neg { @@ -483,6 +495,14 @@ fn lte? { } } +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 @@ -497,16 +517,30 @@ fn pos? { fn even? { "Returns true if a value is an even number, otherwise returns false." - (x as :number) if eq (0, mod (x, 2)) -> true + (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 + (x as :number) if eq? (1, mod (x, 2)) -> true (_) -> false } +fn min { + "Returns the number in its arguments that is closest to negative infinity." + (x as :number) -> x + (x as :number, y as :number) -> if lt? (x, y) then x else y + (x, y, ...zs) -> fold (min, zs, min (x, y)) +} + +fn max { + "Returns the number in its arguments that is closest to positive infinity." + (x as :number) -> x + (x as :number, y as :number) -> if gt? (x, y) then x else y + (x, y, ...zs) -> fold (max, zs, max (x, y)) +} + &&& keywords: funny names fn keyword? { "Returns true if a value is a keyword, otherwise returns false." @@ -590,6 +624,7 @@ fn or { &&& associative collections: dicts, structs, namespaces & TODO?: get_in, update_in, merge +& TODO?: consider renaming these: put & take, not assoc/dissoc fn assoc { "Takes a dict, key, and value, and returns a new dict with the key set to value." () -> #{} @@ -677,6 +712,7 @@ fn assoc? { (_) -> false } +& TODO: consider merging `get` and `at` fn get { "Takes a dict or struct, key, and optional default value; returns the value at key. If the value is not found, returns nil or the default value. Returns nil or default if the first argument is not a dict or struct." (key as :keyword) -> get (key, _) @@ -684,6 +720,7 @@ fn get { (key as :keyword, coll, default) -> base :get (key, coll, default) } +& 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." (key as :keyword) -> has? (key, _) @@ -691,8 +728,7 @@ fn has? { } fn dict { - "Takes a struct or ns, and returns it as a dict. Or, takes a list or tuple of (key, value) tuples and returns it as a dict. Returns dicts unharmed." - (struct as :struct) -> base :to_dict (struct) + "Takes an ns, and returns it as a dict. Or, takes a list or tuple of (key, value) tuples and returns it as a dict. Returns dicts unharmed." (ns_ as :ns) -> base :to_dict (ns_) (dict as :dict) -> dict (list as :list) -> fold (assoc, list) @@ -829,12 +865,25 @@ fn dist { } &&& more number functions -& TODO: add max, min fn random { - "Returns a random number. 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." + "Returns a random number. 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 list, it returns a random member of that list." () -> base :random () (n as :number) -> base :random (n) (m as :number, n as :number) -> add (m, random (n)) + (l as :list) -> { + let i = do l > count > random > floor + at (l, i) + } + (d as :dict) -> { + let key = do d > keys > random + get (d, key) + } +} + +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)) } fn floor { @@ -853,7 +902,7 @@ fn round { } fn range { - "Returns the set of integers between start (inclusive) and end (exclusive) as a list. With one argument, starts at 0. If end is less than start, returns an empty list." + "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) } @@ -906,15 +955,25 @@ fn assert! { &&& Turtle & other graphics & some basic colors -&&& TODO: add colors +& these are the "basic" css colors +& https://developer.mozilla.org/en-US/docs/Web/CSS/named-color let colors = #{ - :white (255, 255, 255, 255) - :light_gray (150, 150, 150, 255) - :dark_gray (50, 50, 50, 255) - :red (255, 0, 0, 255) - :green (0, 255, 0, 255) - :blue (0, 0, 255, 255) :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 @@ -986,6 +1045,7 @@ fn render_turtle! () -> { add_call! ((:vertex, x2, y2)) add_call! ((:vertex, x3, y3)) add_call! ((:endShape)) + & there's a happy bug here: the stroke will be the same width as the pen width. Keep this for now. Consider also showing the pen colour here? add_call! ((:stroke, 0)) add_call! ((:line, 0, 0, x1, y1)) add_call! ((:pop)) @@ -1219,6 +1279,8 @@ ns prelude { div div/0 div/safe + inv + inv/0 angle abs neg @@ -1231,6 +1293,9 @@ ns prelude { gte? lt? lte? + min + max + between? keyword? nil? some? @@ -1269,6 +1334,7 @@ ns prelude { sum_of_squares dist random + random_int pi tau floor