From 26023a903cc22130f96a50e6e09d205c412615da Mon Sep 17 00:00:00 2001 From: Stefan Majewsky Date: Wed, 26 Nov 2025 17:40:56 +0100 Subject: add package is --- CHANGELOG.md | 6 ++++++ README.md | 1 + is/comparable.go | 18 +++++++++++++++++ is/doc.go | 28 ++++++++++++++++++++++++++ is/is_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ is/ordered.go | 34 +++++++++++++++++++++++++++++++ is/time.go | 34 +++++++++++++++++++++++++++++++ 7 files changed, 182 insertions(+) create mode 100644 is/comparable.go create mode 100644 is/doc.go create mode 100644 is/is_test.go create mode 100644 is/ordered.go create mode 100644 is/time.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 88344e5..a876250 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ SPDX-FileCopyrightText: 2025 Stefan Majewsky SPDX-License-Identifier: Apache-2.0 --> +# v1.5.0 (TBD) + +Changes: + +- Add package is. + # v1.4.0 (2025-11-18) Changes: diff --git a/README.md b/README.md index aad5a55..90ae517 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ My personal extension of the standard library, mostly containing foundational ge - [assetembed](./assetembed/): HTTP handler for efficiently serving embedded assets using the cache-busting pattern - [jsonmatch](./jsonmatch/): matching of encoded JSON payloads against fixed assertions +- [is](./is/): binary operations that are expressed in a curried style, e.g. `is.LessThan(b)(a) == a < b`, for use with `Option.IsSomeAnd()` etc. - [option](./option/): an Option type with strong isolation - [options](./options/): additional functions for type Option diff --git a/is/comparable.go b/is/comparable.go new file mode 100644 index 0000000..7d639d8 --- /dev/null +++ b/is/comparable.go @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2025 Stefan Majewsky +// SPDX-License-Identifier: Apache-2.0 + +package is + +// EqualTo(b)(a) is the same as a == b. +func EqualTo[T comparable](rhs T) func(T) bool { + return func(lhs T) bool { + return lhs == rhs + } +} + +// DifferentFrom(b)(a) is the same as a != b. +func DifferentFrom[T comparable](rhs T) func(T) bool { + return func(lhs T) bool { + return lhs != rhs + } +} diff --git a/is/doc.go b/is/doc.go new file mode 100644 index 0000000..42af01e --- /dev/null +++ b/is/doc.go @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2025 Stefan Majewsky +// SPDX-License-Identifier: Apache-2.0 + +// Package is contains functions to express binary operations in a curried style. +// For example, "foo < bar" can be rewritten as "is.LessThan(bar)(foo)". +// +// This is not useful on its own, but may significantly improve readability +// when replacing function literals. Consider the following example: +// +// import . "github.com/majewsky/gg/option" +// +// func checkNewVolumeSize(size, usage uint64, maxSize Option[uint64]) error { +// switch { +// case size < usage: +// return errors.New("size cannot be smaller than usage") +// case maxSize.IsSomeAnd(func(value uint64) bool { return maxSize < size }): +// return errors.New("size cannot be larger than maximum") +// } +// } +// +// The IsSomeAnd() check is difficult to read because function literals in Go are clunky. +// This can be rewritten in a clearer way using is.LessThan(): +// +// // original +// case maxSize.IsSomeAnd(func(value uint64) bool { return maxSize < size }): +// // rewritten +// case maxSize.IsSomeAnd(is.LessThan(size)): +package is diff --git a/is/is_test.go b/is/is_test.go new file mode 100644 index 0000000..3c6c7db --- /dev/null +++ b/is/is_test.go @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2025 Stefan Majewsky +// SPDX-License-Identifier: Apache-2.0 + +package is_test + +import ( + "testing" + "time" + + . "github.com/majewsky/gg/internal/test" + "github.com/majewsky/gg/is" + . "github.com/majewsky/gg/option" +) + +func TestComparable(t *testing.T) { + AssertEqual(t, Some("foo").IsSomeAnd(is.EqualTo("foo")), true) + AssertEqual(t, Some("bar").IsSomeAnd(is.EqualTo("foo")), false) + + AssertEqual(t, Some("foo").IsSomeAnd(is.DifferentFrom("foo")), false) + AssertEqual(t, Some("bar").IsSomeAnd(is.DifferentFrom("foo")), true) +} + +func TestOrdered(t *testing.T) { + AssertEqual(t, Some(3).IsSomeAnd(is.Above(4)), false) + AssertEqual(t, Some(4).IsSomeAnd(is.Above(4)), false) + AssertEqual(t, Some(5).IsSomeAnd(is.Above(4)), true) + + AssertEqual(t, Some(3).IsSomeAnd(is.Below(4)), true) + AssertEqual(t, Some(4).IsSomeAnd(is.Below(4)), false) + AssertEqual(t, Some(5).IsSomeAnd(is.Below(4)), false) + + AssertEqual(t, Some(3).IsSomeAnd(is.NotAbove(4)), true) + AssertEqual(t, Some(4).IsSomeAnd(is.NotAbove(4)), true) + AssertEqual(t, Some(5).IsSomeAnd(is.NotAbove(4)), false) + + AssertEqual(t, Some(3).IsSomeAnd(is.NotBelow(4)), false) + AssertEqual(t, Some(4).IsSomeAnd(is.NotBelow(4)), true) + AssertEqual(t, Some(5).IsSomeAnd(is.NotBelow(4)), true) +} + +func TestTime(t *testing.T) { + t1 := time.Now() + t2 := t1.Add(time.Second) + t3 := t2.Add(time.Second) + + AssertEqual(t, Some(t1).IsSomeAnd(is.After(t2)), false) + AssertEqual(t, Some(t2).IsSomeAnd(is.After(t2)), false) + AssertEqual(t, Some(t3).IsSomeAnd(is.After(t2)), true) + + AssertEqual(t, Some(t1).IsSomeAnd(is.Before(t2)), true) + AssertEqual(t, Some(t2).IsSomeAnd(is.Before(t2)), false) + AssertEqual(t, Some(t3).IsSomeAnd(is.Before(t2)), false) + + AssertEqual(t, Some(t1).IsSomeAnd(is.NotAfter(t2)), true) + AssertEqual(t, Some(t2).IsSomeAnd(is.NotAfter(t2)), true) + AssertEqual(t, Some(t3).IsSomeAnd(is.NotAfter(t2)), false) + + AssertEqual(t, Some(t1).IsSomeAnd(is.NotBefore(t2)), false) + AssertEqual(t, Some(t2).IsSomeAnd(is.NotBefore(t2)), true) + AssertEqual(t, Some(t3).IsSomeAnd(is.NotBefore(t2)), true) +} diff --git a/is/ordered.go b/is/ordered.go new file mode 100644 index 0000000..79fada7 --- /dev/null +++ b/is/ordered.go @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Stefan Majewsky +// SPDX-License-Identifier: Apache-2.0 + +package is + +import "cmp" + +// Above(b)(a) is the same as a > b. +func Above[T cmp.Ordered](rhs T) func(T) bool { + return func(lhs T) bool { + return lhs > rhs + } +} + +// Below(b)(a) is the same as a < b. +func Below[T cmp.Ordered](rhs T) func(T) bool { + return func(lhs T) bool { + return lhs < rhs + } +} + +// NotAbove(b)(a) is the same as a <= b. +func NotAbove[T cmp.Ordered](rhs T) func(T) bool { + return func(lhs T) bool { + return lhs <= rhs + } +} + +// NotBelow(b)(a) is the same as a >= b. +func NotBelow[T cmp.Ordered](rhs T) func(T) bool { + return func(lhs T) bool { + return lhs >= rhs + } +} diff --git a/is/time.go b/is/time.go new file mode 100644 index 0000000..3aadaf1 --- /dev/null +++ b/is/time.go @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Stefan Majewsky +// SPDX-License-Identifier: Apache-2.0 + +package is + +import "time" + +// After(b)(a) is the same as a .After(b). +func After(rhs time.Time) func(time.Time) bool { + return func(lhs time.Time) bool { + return lhs.After(rhs) + } +} + +// Before(b)(a) is the same as a.Before(b). +func Before(rhs time.Time) func(time.Time) bool { + return func(lhs time.Time) bool { + return lhs.Before(rhs) + } +} + +// NotAfter(b)(a) is the same as !a.After(b). +func NotAfter(rhs time.Time) func(time.Time) bool { + return func(lhs time.Time) bool { + return !lhs.After(rhs) + } +} + +// NotBefore(b)(a) is the same as !a.Before(b). +func NotBefore(rhs time.Time) func(time.Time) bool { + return func(lhs time.Time) bool { + return !lhs.Before(rhs) + } +} -- cgit v1.2.3