diff --git a/prelude/__internal__/Function.mad b/prelude/__internal__/Function.mad index 4d29f8efd..14c36405f 100644 --- a/prelude/__internal__/Function.mad +++ b/prelude/__internal__/Function.mad @@ -1,6 +1,4 @@ -export type Step a b - = Loop(a) - | Done(b) +export type Step a b = Loop(a) | Done(b) // inspired from: // https://github.com/purescript/purescript-tailrec/blob/master/src/Control/Monad/Rec/Class.purs @@ -14,7 +12,10 @@ export tailRec = (f, a) => { b } - return pipe(f, go)(a) + return pipe( + f, + go, + )(a) } @@ -105,6 +106,16 @@ export ifElse = (predicate, truthy, falsy, value) => predicate(value) ? truthy(v when :: (a -> Boolean) -> (a -> a) -> a -> a export when = (predicate, truthy, value) => ifElse(predicate, truthy, always(value), value) +/** + * Run a transformation only if the predicate returns false, + * otherwise return the initial value. + * @since 0.23.1 + * @example + * unless(equals(5), (x) => x * 10, 5) // + */ +unless :: (a -> Boolean) -> (a -> a) -> a -> a +export unless = (predicate, falsy, value) => ifElse(predicate, always(value), falsy, value) + /** * Returns the complement of the given Boolean value. * @@ -132,43 +143,6 @@ flip :: (a -> b -> c) -> b -> a -> c export flip = (f) => ((b, a) => f(a, b)) -/** - * Given a predicate and a list of values it returns true if the predicate returns - * true for any of the values in the list. - * - * @since 0.11.0 - * @example - * any((x) => x > 10, [1, 2, 3]) // false - * any((x) => x < 10, [1, 2, 3]) // true - */ -any :: (a -> Boolean) -> List a -> Boolean -export any = (pred, list) => where(list) { - [] => - false - - [x, ...xs] => - pred(x) || any(pred, xs) -} - - -/** - * Given a predicate and a list of values it returns true if the predicate returns - * true for all values in the list. - * - * @since 0.11.0 - * @example - * all((x) => x > 2, [1, 2, 3]) // false - * all((x) => x < 4, [1, 2, 3]) // true - */ -all :: (a -> Boolean) -> List a -> Boolean -export all = (predicate, list) => where(list) { - [] => - true - - [x, ...xs,] => - predicate(x) && all(predicate, xs) -} - /** * Functional "or", given two predicates and a value, return true if either predicate is true. * diff --git a/prelude/__internal__/Function.spec.mad b/prelude/__internal__/Function.spec.mad index e7f8c88ce..fb2ae2143 100644 --- a/prelude/__internal__/Function.spec.mad +++ b/prelude/__internal__/Function.spec.mad @@ -1,16 +1,20 @@ -import { assertEquals, test } from "./Test" -import {} from "List" -import { lt } from "Compare" import type { Maybe } from "Maybe" + +import { lt } from "Compare" +import {} from "List" import { Just, Nothing } from "Maybe" + import { memoize } from "./Memoize" +import { assertEquals, test } from "./Test" + + #iftarget js import { - all, + Done, + Loop, always, - any, both, complement, either, @@ -20,19 +24,19 @@ import { ifElse, not, notEquals, - when, tailRec, - Loop, - Done, + unless, + when, } from "./Function" + #elseif llvm import { - all, + Done, + Loop, always, - any, both, complement, either, @@ -42,168 +46,180 @@ import { ifElse, not, notEquals, - when, tailRec, - Loop, - Done, + unless, + when, } from "./Function" + + #endif divide :: Float -> Float -> Float divide = (a, b) => a / b -test("identity should return whatever it is given", () => pipe( - identity, - assertEquals($, 3) -)(3)) - -test("equals - two equal numbers", () => pipe( - equals($, 3), - assertEquals($, true) -)(3)) - -test("equals - two different numbers", () => pipe( - equals($, 4), - assertEquals($, false) -)(3)) - -test("equals - two equal lists", () => pipe( - equals($, [1, 2, 3]), - assertEquals($, true) -)([1, 2, 3])) - -test("notEquals - two equal numbers", () => pipe( - notEquals($, 3), - assertEquals($, false) -)(3)) - -test("ifElse - truthy", () => pipe( - ifElse(equals(4), (x) => x + 1, (x) => x * 2), - assertEquals($, 5) -)(4)) - -test("ifElse - falsy", () => pipe( - ifElse(equals(4), (x) => x + 1, (x) => x * 2), - assertEquals($, 10) -)(5)) - -test("when - truthy", () => pipe( - when(equals(4), (x) => x * 2), - assertEquals($, 8) -)(4)) - -test("when - falsy", () => pipe( - when(equals(4), (x) => x * 2), - assertEquals($, 5) -)(5)) - -test("not - true", () => pipe( - not, - assertEquals($, false) -)(true)) +test( + "identity should return whatever it is given", + () => pipe( + identity, + assertEquals($, 3), + )(3), +) + +test( + "equals - two equal numbers", + () => pipe( + equals($, 3), + assertEquals($, true), + )(3), +) + +test( + "equals - two different numbers", + () => pipe( + equals($, 4), + assertEquals($, false), + )(3), +) + +test( + "equals - two equal lists", + () => pipe( + equals($, [1, 2, 3]), + assertEquals($, true), + )([1, 2, 3]), +) + +test( + "notEquals - two equal numbers", + () => pipe( + notEquals($, 3), + assertEquals($, false), + )(3), +) + +test( + "ifElse - truthy", + () => pipe( + ifElse(equals(4), (x) => x + 1, (x) => x * 2), + assertEquals($, 5), + )(4), +) + +test( + "ifElse - falsy", + () => pipe( + ifElse(equals(4), (x) => x + 1, (x) => x * 2), + assertEquals($, 10), + )(5), +) + +test( + "when - truthy", + () => pipe( + when(equals(4), (x) => x * 2), + assertEquals($, 8), + )(4), +) + +test( + "when - falsy", + () => pipe( + when(equals(4), (x) => x * 2), + assertEquals($, 5), + )(5), +) + +test( + "not - true", + () => pipe( + not, + assertEquals($, false), + )(true), +) + +test( + "not - false", + () => pipe( + not, + assertEquals($, true), + )(false), +) + +test( + "flip", + () => pipe( + flip, + (flipped) => flipped(4, 2), + assertEquals($, 0.5), + )(divide), +) + +test( + "complement", + () => { + isEven = (x) => x % 2 == 0 + isOdd = complement(isEven) + + return assertEquals(isOdd(2), false) + }, +) -test("not - false", () => pipe( - not, - assertEquals($, true) -)(false)) +test("always", () => assertEquals(always(true, "1"), true)) +test("always - map", () => assertEquals(map(always(true), [1, 2, 3]), [true, true, true])) -test("flip", () => pipe( - flip, - (flipped) => flipped(4, 2), - assertEquals($, 0.5) -)(divide)) +test("either - true", () => assertEquals(either(equals("A"), equals("B"))("B"), true)) -test("complement", () => { - isEven = (x) => x % 2 == 0 - isOdd = complement(isEven) +test("either - false", () => assertEquals(either(equals("A"), equals("B"))("C"), false)) - return assertEquals(isOdd(2), false) -}) +test("both - true", () => assertEquals(both(lt(50), equals(60))(60), true)) -test("always", () => assertEquals(always(true, "1"), true)) -test("always - map", () => assertEquals( - map(always(true), [1, 2 ,3]), - [true, true, true] -)) - -test("any - true", () => assertEquals( - any((x) => x > 4, [1, 3, 5]), - true -)) - -test("any - false", () => assertEquals( - any((x) => x > 4, [1, 3, 2]), - false -)) - -test("all - true", () => assertEquals( - all((x) => x > 4, [5, 8, 10]), - true -)) - -test("all - false", () => assertEquals( - all((x) => x > 4, [1, 2, 10]), - false -)) - -test("either - true", () => assertEquals( - either(equals("A"), equals("B"))("B"), - true -)) - -test("either - false", () => assertEquals( - either(equals("A"), equals("B"))("C"), - false -)) - -test("both - true", () => assertEquals( - both(lt(50), equals(60))(60), - true -)) - -test("both - false", () => assertEquals( - both(lt(50), equals(60))(55), - false -)) - - -test("tailRec - monadic", () => { - maybeMillion :: Integer -> Maybe Integer - maybeMillion = (x) => { - go = (value) => where(value) { - Just(v) => - v < 1000000 - ? Loop(chain((a) => Just(a + 1), value)) - : Done(value) - - Nothing => - Done(Nothing) +test("both - false", () => assertEquals(both(lt(50), equals(60))(55), false)) + +test( + "tailRec - monadic", + () => { + maybeMillion :: Integer -> Maybe Integer + maybeMillion = (x) => { + go = (value) => where(value) { + Just(v) => + v < 1000000 ? Loop(chain((a) => Just(a + 1), value)) : Done(value) + + Nothing => + Done(Nothing) + } + + return tailRec(go, Just(x)) } - return tailRec(go, Just(x)) - } + return assertEquals(maybeMillion(0), Just(1000000)) + }, +) + - return assertEquals(maybeMillion(0), Just(1000000)) -}) +test( + "memoize - should not call the memoized function if called with the same argument", + () => { + count :: Integer + count = 0 + + add :: Integer -> Integer + add = (a) => { + count := count + 1 + return a + } + f = memoize(add) -test("memoize - should not call the memoized function if called with the same argument", () => { - count :: Integer - count = 0 + f(1) + f(1) + f(1) - add :: Integer -> Integer - add = (a) => { - count := count + 1 - return a - } - - f = memoize(add) + return assertEquals(count, 1) + }, +) - f(1) - f(1) - f(1) +test("unless - true", () => assertEquals(unless(equals(5), (x) => x * 2, 5), 5)) - return assertEquals(count, 1) -}) +test("unless - false", () => assertEquals(unless(equals(5), (x) => x * 2, 10), 20)) diff --git a/prelude/__internal__/List.mad b/prelude/__internal__/List.mad index 1f2f04a29..7fbe5fc1f 100644 --- a/prelude/__internal__/List.mad +++ b/prelude/__internal__/List.mad @@ -1032,3 +1032,39 @@ export upsertWith = (pred, updater, list) => where(list) { [] => [updater(Nothing)] } + +/** + * Given a predicate and a list of values it returns true if the predicate returns + * true for any of the values in the list. + * + * @since 0.23.1 + * @example + * any((x) => x > 10, [1, 2, 3]) // false + * any((x) => x < 10, [1, 2, 3]) // true + */ +any :: (a -> Boolean) -> List a -> Boolean +export any = (pred, list) => where(list) { + [] => + false + + [x, ...xs] => + pred(x) || any(pred, xs) +} + +/** + * Given a predicate and a list of values it returns true if the predicate returns + * true for all values in the list. + * + * @since 0.23.1 + * @example + * all((x) => x > 2, [1, 2, 3]) // false + * all((x) => x < 4, [1, 2, 3]) // true + */ +all :: (a -> Boolean) -> List a -> Boolean +export all = (predicate, list) => where(list) { + [] => + true + + [x, ...xs] => + predicate(x) && all(predicate, xs) +} diff --git a/prelude/__internal__/List.spec.mad b/prelude/__internal__/List.spec.mad index 421e3d9a8..592ce9d5f 100644 --- a/prelude/__internal__/List.spec.mad +++ b/prelude/__internal__/List.spec.mad @@ -4,6 +4,8 @@ import {} from "String" import { assertEquals, test } from "Test" import { + all, + any, append, concat, contains, @@ -272,3 +274,12 @@ test( test("slice", () => assertEquals(slice(1, 3, [1, 2, 3, 4, 5, 6]), [2, 3, 4])) test("slice - negative end", () => assertEquals(slice(0, -2, [1, 2, 3, 4, 5, 6]), [1, 2, 3, 4])) + + +test("any - true", () => assertEquals(any((x) => x > 4, [1, 3, 5]), true)) + +test("any - false", () => assertEquals(any((x) => x > 4, [1, 3, 2]), false)) + +test("all - true", () => assertEquals(all((x) => x > 4, [5, 8, 10]), true)) + +test("all - false", () => assertEquals(all((x) => x > 4, [1, 2, 10]), false))