From 05f52dee6fa27f7db9a8ab9b203203ada1b77381 Mon Sep 17 00:00:00 2001 From: wojpok Date: Mon, 3 Nov 2025 16:53:05 +0100 Subject: [PATCH 1/3] Extended List module --- lib/List.fram | 166 +++++++++++++++++++++++++++++++ test/stdlib/stdlib0003_List.fram | 53 ++++++++++ 2 files changed, 219 insertions(+) diff --git a/lib/List.fram b/lib/List.fram index 1e65e391..7da7c2e2 100644 --- a/lib/List.fram +++ b/lib/List.fram @@ -56,6 +56,40 @@ pub let tlErr { ~onError } xs = | x :: xs => xs end +{## + Returns the list without the last element or returns `None` if it already + was empty. + ##} +pub let dropLast xs = + let rec dropLastAux xs = + match xs with + | [] => impossible () + | [x] => [] + | x :: xs => x :: dropLastAux xs + end in + if isEmpty xs then + None + else + Some (dropLastAux xs) + +{## + Returns the list without the last element or calls `~onError` if it already + was empty. + + @param ~onError Fallback function in case of an empty list. + ##} +pub let dropLastErr { ~onError } xs = + let rec dropLastAux xs = + match xs with + | [] => impossible () + | [x] => [] + | x :: xs => x :: dropLastAux xs + end in + if isEmpty xs then + ~onError () + else + dropLastAux xs + {## Returns the n-th element of a list or `None` if n is negative or the list is too short. The first element is at position 0. @@ -82,6 +116,72 @@ pub let nthErr { ~onError } xs (n : Int) = end in if n < 0 then ~onError () else nthErrAux xs n +{## + Returns the last element of a list or None if the list is empty. + ##} +pub let last xs = + let rec lastAux xs = + match xs with + | [] => None + | [x] => Some x + | _ :: xs => lastAux xs + end in + lastAux xs + +{## + Returns the last element of a list or calls `~onError` if list is empty. + + @param ~onError Fallback for an empty list. + ##} +pub let lastErr { ~onError } xs = + let rec lastErrAux xs = + match xs with + | [] => ~onError () + | [x] => x + | _ :: xs => lastErrAux xs + end in + lastErrAux xs + +{## + Returns pair of combined results of `last` and `dropLast` functions, + but performs this computation in more optimized manner. Returns + `None` for an empty list. + ##} +pub let dropTakeLast xs = + let rec dropTakeLastAux xs = + match xs with + | [] => impossible () + | [x] => ([], x) + | x :: xs => + let (init, last) = dropTakeLastAux xs in + (x :: init, last) + end in + if isEmpty xs then + None + else + Some (dropTakeLastAux xs) + +{## + Returns pair of combined results of `last` and `dropLast` functions, + but performs this computation in more optimized manner. Calls `~onError` for + an empty list. + + @param ~onError Fallback for an empty list. + ##} +pub let dropTakeLastErr { ~onError } xs = + let rec dropTakeLastErrAux xs = + match xs with + | [] => impossible () + | [x] => ([], x) + | x :: xs => + let (init, last) = dropTakeLastErrAux xs in + (x :: init, last) + end in + if isEmpty xs then + ~onError () + else + dropTakeLastErrAux xs + ## Appends `ys` to `xs`. pub let rec append xs ys = match xs with @@ -332,6 +432,28 @@ pub let rec foldLeft f acc xs = | x :: xs => foldLeft f (f acc x) xs end +{## + `foldLeft f [x1, x2, ... ,xn]` is `f (... (f (f x1 x2) x3) ...) xn`. + Returns `None` in case of an empty list. + ##} +pub let rec foldLeft1 f xs = + match xs with + | [] => None + | x :: xs => Some (foldLeft f x xs) + end + +{## + `foldLeft f [x1, x2, ... ,xn]` is `f (... (f (f x1 x2) x3) ...) xn`. + Returns `~onError` in case of an empty list. + + @param ~onError Fallback for an empty list. + ##} +pub let rec foldLeft1Err { ~onError } f xs = + match xs with + | [] => ~onError () + | x :: xs => foldLeft f x xs + end + ## `foldRight f [x1, x2, ..., xn] init` is `f x1 (f x2 (... (f xn init)))`. pub let rec foldRight f xs acc = match xs with @@ -339,6 +461,40 @@ pub let rec foldRight f xs acc = | x :: xs => f x (foldRight f xs acc) end +{## + `foldRight f [x1, x2, ..., xn]` is `f x1 (... (f x(n-1) xn)))`. + Returns `None` for an empty list. + ##} +pub let foldRight1 f xs = + let rec foldRight1Aux xs = + match xs with + | x :: y :: [] => f x y + | x :: xs => f x (foldRight1Aux xs) + | _ => impossible () + end in + if isEmpty xs then + None + else + Some (foldRight1Aux xs) + +{## + `foldRight f [x1, x2, ..., xn]` is `f x1 (... (f x(n-1) xn)))`. + Calls `~onError` for an empty list. + + @param ~onError Fallback for an empty list. + ##} +pub let foldRight1Err { ~onError } f xs = + let rec foldRight1ErrAux xs = + match xs with + | x :: y :: [] => f x y + | x :: xs => f x (foldRight1ErrAux xs) + | _ => impossible () + end in + if isEmpty xs then + ~onError () + else + foldRight1ErrAux xs + {## `foldLeft2 f init xs ys` works the same as `List.foldLeft` but `f` is applied to consecutive pairs of elements from `xs` and `ys`. @@ -610,8 +766,14 @@ pub method hd = hd pub method hdErr = hdErr pub method tl = tl pub method tlErr = tlErr +pub method dropLast = dropLast +pub method dropLastErr = dropLastErr pub method nth = nth pub method nthErr = nthErr +pub method last = last +pub method lastErr = lastErr +pub method dropTakeLast = dropTakeLast +pub method dropTakeLastErr = dropTakeLastErr pub method append = append pub method add = append pub method revAppend = revAppend @@ -631,7 +793,11 @@ pub method dropWhile self p = dropWhile p self pub method iter self f = iter f self pub method iteri { ?i : Int } self f = iteri { ?i = i } f self pub method foldLeft self f acc = foldLeft f acc self +pub method foldLeft1 self f = foldLeft1 self f +pub method foldLeft1Err self f = foldLeft1Err self f pub method foldRight self f acc = foldRight f self acc +pub method foldRight1 self f = foldRight1 f self +pub method foldRight1Err self f = foldRight1Err f self pub method forAll self p = forAll p self pub method exists self p = exists p self pub method find self p = find p self diff --git a/test/stdlib/stdlib0003_List.fram b/test/stdlib/stdlib0003_List.fram index 06bacbee..87541863 100644 --- a/test/stdlib/stdlib0003_List.fram +++ b/test/stdlib/stdlib0003_List.fram @@ -4,6 +4,12 @@ parameter ~onError let ~onError _ = exit 1 let retEmpty _ = [] +let isNone opt = + match opt with + | Some _ => False + | None => True + end + let xs = [1,2,3,4] let ys = [5,6,7,8] @@ -73,6 +79,18 @@ let _ = assert { msg = "tlErr" } (List.isEmpty (List.tlErr { ~onError = retEmpty } ([] : List Int))) +let _ = + assert {msg = "dropLast"} + (List.dropLast [1, 2, 3] >.unwrapOr [] == [1, 2]) +let _ = assert {msg = "dropLast"} (isNone (List.dropLast ([] : List Int))) + +let _ = + assert {msg = "dropLastErr"} + (List.dropLastErr {~onError=retEmpty} [1, 2, 3] == [1, 2]) +let _ = + assert {msg = "dropLastErr"} + (List.dropLastErr {~onError=retEmpty} ([] : List Int) >.isEmpty) + let _ = assert { msg = "nth" } (match List.nth xs 2 with @@ -90,6 +108,12 @@ let _ = assert { msg = "nthErr" } (List.nthErr {~onError = fn _ => -1} xs 10 == -1) +let _ = assert {msg="last"} (isNone (List.last ([] : List Int))) +let _ = assert {msg="last"} (List.last [1, 2] >.unwrapOr 0 == 2) + +let _ = assert {msg="lastErr"} (List.lastErr {~onError = fn _ => 0} [] == 0) +let _ = assert {msg="lastErr"} (List.lastErr {~onError = fn _ => 0} [1, 2] == 2) + let _ = assert { msg = "append" } (xs + ys == [1,2,3,4,5,6,7,8]) let _ = assert { msg = "rev" } (List.rev xs == [4,3,2,1]) @@ -180,9 +204,38 @@ let _ = assert { msg = "init" } (List.init 5 id == [0,1,2,3,4]) let _ = assert { msg = "foldLeft" } (List.foldLeft (fn (a : Int) b => a + b) 0 xs == 10) +let _ = + assert {msg = "foldleft1"} + (List.foldLeft1 (fn (a : Int) b => a + b) xs >.unwrapOr 0 == 10) +let _ = + assert {msg = "foldleft1"} + (List.foldLeft1 (fn (a : Int) b => a + b) ([] : List Int) >.unwrapOr 0 == 0) +let _ = + assert {msg = "foldleft1Err"} + (List.foldLeft1Err {~onError = fn _ => 0} + (fn (a : Int) b => a + b) xs == 10) +let _ = + assert {msg = "foldleft1Err"} + (List.foldLeft1Err {~onError = fn _ => 0} + (fn (a : Int) b => a + b) ([] : List Int) == 0) + let _ = assert { msg = "foldRight" } (List.foldRight (fn (a : Int) b => a + b) xs 0 == 10) +let _ = + assert {msg = "foldRight1"} + (List.foldRight1 (fn (a : Int) b => a + b) xs >.unwrapOr 0 == 10) +let _ = + assert {msg = "foldRight1"} + (List.foldRight1 (fn (a : Int) b => a + b) ([] : List Int) >.unwrapOr 0 == 0) +let _ = + assert {msg = "foldRight1Err"} + (List.foldRight1Err {~onError = fn _ => 0} + (fn (a : Int) b => a + b) xs == 10) +let _ = + assert {msg = "foldRight1Err"} + (List.foldRight1Err {~onError = fn _ => 0} + (fn (a : Int) b => a + b) ([] : List Int) == 0) let _ = assert { msg = "foldLeft2" } (List.foldLeft2 (fn (a : Int) b c => a + b + c) 0 xs ys == 36) From 41c5507db7ee1a65296ca38f45ac6be1724ab8ab Mon Sep 17 00:00:00 2001 From: wojpok Date: Tue, 4 Nov 2025 12:52:34 +0100 Subject: [PATCH 2/3] missing tests, improved recursive functions --- lib/List.fram | 120 +++++++++++++++---------------- test/stdlib/stdlib0003_List.fram | 16 +++++ 2 files changed, 75 insertions(+), 61 deletions(-) diff --git a/lib/List.fram b/lib/List.fram index 7da7c2e2..3c6977ef 100644 --- a/lib/List.fram +++ b/lib/List.fram @@ -61,16 +61,15 @@ pub let tlErr { ~onError } xs = was empty. ##} pub let dropLast xs = - let rec dropLastAux xs = + let rec dropLastAux y xs = match xs with - | [] => impossible () - | [x] => [] - | x :: xs => x :: dropLastAux xs + | [] => [] + | x :: xs => y :: dropLastAux x xs end in - if isEmpty xs then - None - else - Some (dropLastAux xs) + match xs with + | x :: xs => Some (dropLastAux x xs) + | [] => None + end {## Returns the list without the last element or calls `~onError` if it already @@ -79,16 +78,15 @@ pub let dropLast xs = @param ~onError Fallback function in case of an empty list. ##} pub let dropLastErr { ~onError } xs = - let rec dropLastAux xs = + let rec dropLastAux y xs = match xs with - | [] => impossible () - | [x] => [] - | x :: xs => x :: dropLastAux xs + | [] => [] + | x :: xs => y :: dropLastAux x xs end in - if isEmpty xs then - ~onError () - else - dropLastAux xs + match xs with + | x :: xs => dropLastAux x xs + | [] => ~onError () + end {## Returns the n-th element of a list or `None` if n is negative or the list @@ -120,13 +118,15 @@ pub let nthErr { ~onError } xs (n : Int) = Returns the last element of a list or None if the list is empty. ##} pub let last xs = - let rec lastAux xs = + let rec lastAux y xs = match xs with - | [] => None - | [x] => Some x - | _ :: xs => lastAux xs + | [] => y + | x :: xs => lastAux x xs end in - lastAux xs + match xs with + | [] => None + | x :: xs => Some (lastAux x xs) + end {## Returns the last element of a list or calls `~onError` if list is empty. @@ -134,13 +134,15 @@ pub let last xs = @param ~onError Fallback for an empty list. ##} pub let lastErr { ~onError } xs = - let rec lastErrAux xs = + let rec lastErrAux y xs = match xs with - | [] => ~onError () - | [x] => x - | _ :: xs => lastErrAux xs + | [] => y + | x :: xs => lastErrAux x xs end in - lastErrAux xs + match xs with + | [] => ~onError () + | x :: xs => lastErrAux x xs + end {## Returns pair of combined results of `last` and `dropLast` functions, @@ -148,18 +150,17 @@ pub let lastErr { ~onError } xs = `None` for an empty list. ##} pub let dropTakeLast xs = - let rec dropTakeLastAux xs = + let rec dropTakeLastAux y xs = match xs with - | [] => impossible () - | [x] => ([], x) + | [] => ([], y) | x :: xs => - let (init, last) = dropTakeLastAux xs in - (x :: init, last) + let (init, last) = dropTakeLastAux x xs in + (y :: init, last) end in - if isEmpty xs then - None - else - Some (dropTakeLastAux xs) + match xs with + | [] => None + | x :: xs => Some (dropTakeLastAux x xs) + end {## Returns pair of combined results of `last` and `dropLast` functions, @@ -169,18 +170,17 @@ pub let dropTakeLast xs = @param ~onError Fallback for an empty list. ##} pub let dropTakeLastErr { ~onError } xs = - let rec dropTakeLastErrAux xs = + let rec dropTakeLastErrAux y xs = match xs with - | [] => impossible () - | [x] => ([], x) + | [] => ([], y) | x :: xs => - let (init, last) = dropTakeLastErrAux xs in - (x :: init, last) + let (init, last) = dropTakeLastErrAux x xs in + (y :: init, last) end in - if isEmpty xs then - ~onError () - else - dropTakeLastErrAux xs + match xs with + | [] => ~onError () + | x :: xs => dropTakeLastErrAux x xs + end ## Appends `ys` to `xs`. pub let rec append xs ys = @@ -450,7 +450,7 @@ pub let rec foldLeft1 f xs = ##} pub let rec foldLeft1Err { ~onError } f xs = match xs with - | [] => ~onError () + | [] => ~onError () | x :: xs => foldLeft f x xs end @@ -466,16 +466,15 @@ pub let rec foldRight f xs acc = Returns `None` for an empty list. ##} pub let foldRight1 f xs = - let rec foldRight1Aux xs = + let rec foldRight1Aux y xs = match xs with - | x :: y :: [] => f x y - | x :: xs => f x (foldRight1Aux xs) - | _ => impossible () + | [] => y + | x :: xs => f y (foldRight1Aux x xs) end in - if isEmpty xs then - None - else - Some (foldRight1Aux xs) + match xs with + | x :: xs => Some (foldRight1Aux x xs) + | [] => None + end {## `foldRight f [x1, x2, ..., xn]` is `f x1 (... (f x(n-1) xn)))`. @@ -484,16 +483,15 @@ pub let foldRight1 f xs = @param ~onError Fallback for an empty list. ##} pub let foldRight1Err { ~onError } f xs = - let rec foldRight1ErrAux xs = + let rec foldRight1ErrAux y xs = match xs with - | x :: y :: [] => f x y - | x :: xs => f x (foldRight1ErrAux xs) - | _ => impossible () + | [] => y + | x :: xs => f y (foldRight1ErrAux x xs) end in - if isEmpty xs then - ~onError () - else - foldRight1ErrAux xs + match xs with + | x :: xs => foldRight1ErrAux x xs + | [] => ~onError () + end {## `foldLeft2 f init xs ys` works the same as `List.foldLeft` but `f` is diff --git a/test/stdlib/stdlib0003_List.fram b/test/stdlib/stdlib0003_List.fram index 87541863..38304400 100644 --- a/test/stdlib/stdlib0003_List.fram +++ b/test/stdlib/stdlib0003_List.fram @@ -114,6 +114,22 @@ let _ = assert {msg="last"} (List.last [1, 2] >.unwrapOr 0 == 2) let _ = assert {msg="lastErr"} (List.lastErr {~onError = fn _ => 0} [] == 0) let _ = assert {msg="lastErr"} (List.lastErr {~onError = fn _ => 0} [1, 2] == 2) +let _ = assert {msg="dropTakeLast"} + match List.dropTakeLast [1, 2, 3] with + | Some (xs, x) => (3 == x) && ([1, 2] == xs) + | None => False + end +let _ = assert {msg="dropTakeLast"} + (isNone (List.dropTakeLast ([] : List Int))) + +let _ = assert {msg="dropTakeLastErr"} + (let (xs, x) = List.dropTakeLastErr {~onError = fn _ => ([], 0)} [1, 2, 3] + in [1, 2] == xs && 3 == x) + +let _ = assert {msg="dropTakeLastErr"} + (let (xs, x) = List.dropTakeLastErr {~onError = fn _ => ([], 0)} [] + in ([] : List Int) == xs && 0 == x) + let _ = assert { msg = "append" } (xs + ys == [1,2,3,4,5,6,7,8]) let _ = assert { msg = "rev" } (List.rev xs == [4,3,2,1]) From caae80175d110c1ffe11abcdb201d5a58c42512d Mon Sep 17 00:00:00 2001 From: wojpok Date: Tue, 11 Nov 2025 17:49:34 +0100 Subject: [PATCH 3/3] pattern matching consistency, documentation fixes --- lib/List.fram | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/List.fram b/lib/List.fram index 3c6977ef..b2eb018c 100644 --- a/lib/List.fram +++ b/lib/List.fram @@ -67,8 +67,8 @@ pub let dropLast xs = | x :: xs => y :: dropLastAux x xs end in match xs with - | x :: xs => Some (dropLastAux x xs) | [] => None + | x :: xs => Some (dropLastAux x xs) end {## @@ -84,8 +84,8 @@ pub let dropLastErr { ~onError } xs = | x :: xs => y :: dropLastAux x xs end in match xs with - | x :: xs => dropLastAux x xs | [] => ~onError () + | x :: xs => dropLastAux x xs end {## @@ -115,7 +115,7 @@ pub let nthErr { ~onError } xs (n : Int) = in if n < 0 then ~onError () else nthErrAux xs n {## - Returns the last element of a list or None if the list is empty. + Returns the last element of a list or `None` if the list is empty. ##} pub let last xs = let rec lastAux y xs = @@ -433,17 +433,17 @@ pub let rec foldLeft f acc xs = end {## - `foldLeft f [x1, x2, ... ,xn]` is `f (... (f (f x1 x2) x3) ...) xn`. + `foldLeft1 f [x1, x2, ... ,xn]` is `f (... (f (f x1 x2) x3) ...) xn`. Returns `None` in case of an empty list. ##} pub let rec foldLeft1 f xs = match xs with - | [] => None + | [] => None | x :: xs => Some (foldLeft f x xs) end {## - `foldLeft f [x1, x2, ... ,xn]` is `f (... (f (f x1 x2) x3) ...) xn`. + `foldLeft1Err f [x1, x2, ... ,xn]` is `f (... (f (f x1 x2) x3) ...) xn`. Returns `~onError` in case of an empty list. @param ~onError Fallback for an empty list. @@ -462,7 +462,7 @@ pub let rec foldRight f xs acc = end {## - `foldRight f [x1, x2, ..., xn]` is `f x1 (... (f x(n-1) xn)))`. + `foldRight1 f [x1, x2, ..., xn]` is `f x1 (... (f x(n-1) xn)))`. Returns `None` for an empty list. ##} pub let foldRight1 f xs = @@ -472,12 +472,12 @@ pub let foldRight1 f xs = | x :: xs => f y (foldRight1Aux x xs) end in match xs with - | x :: xs => Some (foldRight1Aux x xs) | [] => None + | x :: xs => Some (foldRight1Aux x xs) end {## - `foldRight f [x1, x2, ..., xn]` is `f x1 (... (f x(n-1) xn)))`. + `foldRight1Err f [x1, x2, ..., xn]` is `f x1 (... (f x(n-1) xn)))`. Calls `~onError` for an empty list. @param ~onError Fallback for an empty list. @@ -489,8 +489,8 @@ pub let foldRight1Err { ~onError } f xs = | x :: xs => f y (foldRight1ErrAux x xs) end in match xs with - | x :: xs => foldRight1ErrAux x xs | [] => ~onError () + | x :: xs => foldRight1ErrAux x xs end {##