From 07c6d2358702df89d87a8e842c2c2f70cdcc97b5 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 15 Jun 2024 21:51:56 -0400 Subject: [PATCH] bugfixes and improvements --- prelude.ld | 229 +++++++++++++++++++++++++---------------------------- 1 file changed, 110 insertions(+), 119 deletions(-) diff --git a/prelude.ld b/prelude.ld index 726c706..c5bc6a7 100644 --- a/prelude.ld +++ b/prelude.ld @@ -9,26 +9,26 @@ & some forward declarations & TODO: fix this so that we don't need (as many of) them -fn first -fn append -fn some? -fn update! -fn string -fn join -fn neg? -fn atan/2 -fn mod -fn assoc & ? -fn dict -fn get -fn unbox -fn store! -fn turn/rad -fn deg/rad -fn floor fn and +fn append fn apply_command +fn assoc & ? +fn atan/2 +fn deg/rad +fn dict +fn first +fn floor +fn get +fn join +fn mod +fn neg? +fn some? fn state/call +fn store! +fn string +fn turn/rad +fn unbox +fn update! & the very base: know something's type fn type { @@ -36,6 +36,30 @@ fn type { (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 + (_) -> false +} + +fn assoc? { + "Returns true if a value is an associative collection: a dict or a pkg." + (assoc as :dict) -> true + (assoc as :pkg) -> true + (_) -> false +} + & ...and if two things are the same fn eq? { "Returns true if all arguments have the same value." @@ -166,25 +190,10 @@ fn list? { } 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. Associative collections return lists of (key, value) tuples." + "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 :to_list (x) } -& TODO: make this work with Janet base -fn set { - "Takes an ordered collection--list or tuple--and turns it into a set." - (xs as :list) -> base :into (${}, xs) - (xs as :tuple) -> base :into (${}, xs) -} - -fn set? { - "Returns true if a value is a set." - (xs as :set) -> true - (_) -> false -} - -& add a contains? or has? function - fn fold { "Folds a list." (f as :fn, xs as :list) -> fold (f, xs, f ()) @@ -245,30 +254,29 @@ fn concat { (xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys)) } -& the console: sending messages to the outside world -& the console is *both* something we send to the host language's console -& ...and also a list of messages. -& box console = [] -& fn flush! { -& "Clears the console, and returns the messages." -& () -> { -& let msgs = unbox (console) -& store! (console, []) -& msgs -& } -& } +fn set { + "Takes an ordered collection--list or tuple--and turns it into a set." + (xs as :list) -> fold (append, xs, ${}) + (xs as :tuple) -> do xs > list > set +} -& fn add_msg! { -& "Adds a message to the console." -& (msg as :string) -> update! (console, append (_, msg)) -& (msgs as :list) -> { -& base :print! (("adding msg", msgs)) -& let msg = do msgs > map (string, _) > join -& base :print! (("msg: ", msg)) -& update! (console, append (_, msg)) -& } -& } +fn set? { + "Returns true if a value is a set." + (xs as :set) -> true + (_) -> false +} + +fn contains? { + "Returns true if a set or list contains a value." + (value, set as :set) -> bool (base :get (set, value)) + (value, list as :list) -> contains? (value, set (list)) +} + +fn omit { + "Returns a new set with the value omitted." + (value, s as :set) -> base :disj (s, value) +} fn print! { "Sends a text representation of Ludus values to the console." @@ -283,15 +291,6 @@ fn show { (x) -> base :show (x) } -fn prn! { - "Prints the underlying Clojure data structure of a Ludus value." - (x) -> { - base :prn (x) - & add_msg! (x) - :ok - } -} - fn report! { "Prints a value, then returns it." (x) -> { @@ -299,7 +298,7 @@ fn report! { x } (msg as :string, x) -> { - print! (concat (msg, show (x))) + print! (concat ("{msg} ", show (x))) x } } @@ -332,7 +331,7 @@ fn join { ([]) -> "" ([str as :string]) -> str (strs as :list) -> join (strs, "") - ([str], separator as :string) -> str + ([str as :string], separator as :string) -> str ([str, ...strs], separator as :string) -> fold ( fn (joined, to_join) -> concat (joined, separator, to_join) strs @@ -398,13 +397,7 @@ fn sentence { (strs as :list) -> join (strs, " ") } - -& in another prelude, with a better actual base language than Java (thanks, Rich), counting strings would be reasonable but complex: count/bytes, count/points, count/glyphs. Java's UTF16 strings make this unweildy. - -& TODO: add trim, trim/left, trim/right; pad/left, pad/right -& ...also a version of at, - -&&& references: mutable state and state changes +&&& boxes: mutable state and state changes fn box? { "Returns true if a value is a box." @@ -650,17 +643,17 @@ fn at { fn first { "Returns the first element of a list or tuple." - (xs) -> at (xs, 0) + (xs) if ordered? (xs) -> at (xs, 0) } fn second { "Returns the second element of a list or tuple." - (xs) -> at (xs, 1) + (xs) if ordered? (xs) -> at (xs, 1) } fn last { "Returns the last element of a list or tuple." - (xs) -> at (xs, dec (count (xs))) + (xs) if ordered? (xs) -> at (xs, dec (count (xs))) } fn butlast { @@ -731,9 +724,6 @@ fn or { (x, y, ...zs) -> fold (or, zs, base :or (x, y)) } -&&& 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." () -> #{} @@ -791,30 +781,6 @@ fn diff { } } -fn coll? { - "Returns true if a value is a collection: dict, list, pkg, tuple, or set." - (coll as :dict) -> true - (coll as :list) -> true - (coll as :tuple) -> true - (coll as :set) -> true - (coll as :pkg) -> true - (_) -> false -} - -fn ordered? { - "Returns true if a value is an indexed collection: list or tuple." - (coll as :list) -> true - (coll as :tuple) -> true - (_) -> false -} - -fn assoc? { - "Returns true if a value is an associative collection: a dict or a pkg." - (assoc as :dict) -> true - (assoc as :pkg) -> true - (_) -> false -} - & 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." @@ -843,15 +809,11 @@ fn dict? { (_) -> false } -& TODO: make this less awkward once we have tail recursion 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, [...xs]) -> loop (xs) with { - ([x]) -> { f! (x); nil } - ([x, ...xs]) -> { f! (x); recur (xs) } - } + (f! as :fn, [x, ...xs]) -> { f! (x); each! (f!, xs) } } &&& Trigonometry functions @@ -938,18 +900,38 @@ fn atan/2 { } fn mod { - "Returns the modulus of num and div. Truncates towards negative infinity." + "Returns the modulus of num and div. Truncates towards negative infinity. Panics if div is 0." + (num as :number, 0) -> panic! "Division by zero." (num as :number, div as :number) -> base :mod (num, div) } +fn mod/0 { + "Returns the modulus of num and div. Truncates towards negative infinity. Returns 0 if div is 0." + (num as :number, 0) -> 0 + (num as :number, div as :number) -> base :mod (num, div) +} + +fn mod/safe { + "Returns the modulus of num and div in a result tuple, or an error if div is 0. Truncates towards negative infinity." + (num as :number, 0) -> (:err, "Division by zero.") + (num as :number, div as :number) -> (:ok, base :mod (num, div)) +} + fn square { "Squares a number." (x as :number) -> mult (x, x) } fn sqrt { - "Returns the square root of a number." - (x as :number) -> base :sqrt (x) + "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 { @@ -957,18 +939,21 @@ fn sum_of_squares { () -> 0 (x as :number) -> square (x) (x as :number, y as :number) -> add (square (x), square (y)) - (x, y, ...zs) -> fold (sum_of_squares, zs, sum_of_squares (x, 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, y)." + "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) } &&& more number functions 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. Alternately, given a list, it returns a random member of that list." + "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 (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))) @@ -980,6 +965,7 @@ fn random { let key = do d > keys > random get (key, d) } + (s as :set) -> do s > list > random } fn random_int { @@ -1048,10 +1034,12 @@ fn unwrap_or { 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! string ("Assert failed:", value) + (value) -> if value + then value + else panic! "Assert failed: {value}" (msg, value) -> if value then value - else panic! string ("Assert failed: ", msg, " with ", value) + else panic! "Assert failed: {msg} with {value}" } &&& Turtle & other graphics @@ -1198,8 +1186,6 @@ fn back! { let bk! = back! -& turtles, like eveyrthing else in Ludus, use turns by default, -& not degrees fn left! { "Rotates the turtle left, measured in turns. Alias: lt!" (turns as :number) -> add_command! ((:left, turns)) @@ -1374,6 +1360,7 @@ pkg Prelude { coll? & dicts lists sets tuples colors & turtles concat & string list set + contains? & list set cos & math count & string list set tuple dict dec & math @@ -1430,6 +1417,8 @@ pkg Prelude { max & math min & math mod & math + mod/0 + mod/safe mult & math neg & math neg? & math @@ -1439,6 +1428,7 @@ pkg Prelude { odd? & math ok & results ok? & results + omit & set or & bool ordered? & lists tuples strings p5_calls & turtles @@ -1455,7 +1445,6 @@ pkg Prelude { pos? & math position & turtles print! & environment - & prn! & environment pu! & turtles pw! & turtles rad/deg & math @@ -1480,6 +1469,8 @@ pkg Prelude { some & values some? & values split & strings + sqrt & math + sqrt/safe & math square & math state & environment store! & boxes