From 51a575edf3c13351ec136441e5924ad2441335c2 Mon Sep 17 00:00:00 2001 From: "jacob h. brown" Date: Thu, 21 Dec 2023 20:42:05 +0000 Subject: [PATCH 1/6] Added ValueDeref --- README.md | 4 +++- value.go | 7 +++++++ value_example_test.go | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d580c44..1c09a01 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,10 @@ var p *int truthy.Value(p) // false p = new(int) -// truthy does not check value underlying pointer! +// Value does not check the underlying truthy value for a pointer! truthy.Value(p) // true +// but ValueDeref does! +truthy.ValueDeref(p) // false // Ever wish Go had ? : ternary operators? // Now it has a ternary function. diff --git a/value.go b/value.go index cf22386..3bd25b1 100644 --- a/value.go +++ b/value.go @@ -51,3 +51,10 @@ func ValueMap[K comparable, V any, M ~map[K]V](v M) bool { func Value[T comparable](v T) bool { return v != *new(T) } + +// ValueDeref returns the truthy value of comparable types. +// Values are truthy if they are not equal to the +// zero value for the dereferenced type. +func ValueDeref[T comparable](p *T) bool { + return Value(Deref(p)) +} diff --git a/value_example_test.go b/value_example_test.go index b59c6d9..3874c7e 100644 --- a/value_example_test.go +++ b/value_example_test.go @@ -70,3 +70,18 @@ func ExampleValue() { // false // true } + +func ExampleValueDeref() { + var np *int + fmt.Println(truthy.ValueDeref(np)) + zero := 0 + np = &zero + fmt.Println(truthy.ValueDeref(np)) + one := 1 + np = &one + fmt.Println(truthy.ValueDeref(np)) + // Output: + // false + // false + // true +} From 25d2fc880576de8e1fa826289bb16c70cda23608 Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Mon, 8 Jan 2024 18:37:15 -0600 Subject: [PATCH 2/6] Separate into packages --- README.md | 125 ++++++++++-------- coalesce_example_test.go | 28 ---- cond.go | 10 -- condition/condition.go | 10 ++ .../condition_example_test.go | 11 +- default.go | 37 ------ defaults/defaults.go | 39 ++++++ .../defaults_example_test.go | 16 +-- value.go => is/is.go | 62 +++++---- value_bench_test.go => is/is_bench_test.go | 16 +-- is/is_example_test.go | 78 +++++++++++ value_test.go => is/is_for_any_test.go | 8 +- coalesce.go => pointers/deref.go | 2 +- pointers/deref_test.go | 28 ++++ pointers/first.go | 11 ++ pointers/first_example_test.go | 21 +++ pointers/new.go | 6 + pointers/new_example_test.go | 32 +++++ value_example_test.go | 87 ------------ 19 files changed, 353 insertions(+), 274 deletions(-) delete mode 100644 coalesce_example_test.go delete mode 100644 cond.go create mode 100644 condition/condition.go rename cond_example_test.go => condition/condition_example_test.go (63%) delete mode 100644 default.go create mode 100644 defaults/defaults.go rename default_example_test.go => defaults/defaults_example_test.go (68%) rename value.go => is/is.go (56%) rename value_bench_test.go => is/is_bench_test.go (87%) create mode 100644 is/is_example_test.go rename value_test.go => is/is_for_any_test.go (89%) rename coalesce.go => pointers/deref.go (93%) create mode 100644 pointers/deref_test.go create mode 100644 pointers/first.go create mode 100644 pointers/first_example_test.go create mode 100644 pointers/new.go create mode 100644 pointers/new_example_test.go delete mode 100644 value_example_test.go diff --git a/README.md b/README.md index 1c09a01..61bb93a 100644 --- a/README.md +++ b/README.md @@ -2,56 +2,19 @@ ![Truthiness](https://user-images.githubusercontent.com/222245/136619462-f2bc5858-067f-4277-a813-b95c64b3cdac.png) -Truthy is a package which uses generics to create useful boolean tests of truthiness and helper functions. +truthy is a set of packages that use generics to create useful boolean tests of truthiness and also contain some nice helper functions -## Examples - -``` -// truthy.Value returns whether a comparable value -// is equal to the zero value for its type. -// -// truthy.ValueAny returns the truthiness of any argument. -// If the value's type has a Bool() bool method, the method is called and returned. -// If the type has an IsZero() bool method, the opposite value is returned. -// Slices and maps are truthy if they have a length greater than zero. -// All other types are truthy if they are not their zero value. - -truthy.Value(0) // false -truthy.Value(1) // true - -truthy.Value("") // false -truthy.Value(" ") // true - -truthy.ValueSlice([]byte(``)) // false -truthy.ValueSlice([]byte(` `)) // true - -truthy.ValueSlice([]int{}) // false -truthy.ValueSlice([]int{1, 2, 3}) // true - -var err error -truthy.Value(err) // false -truthy.Value(errors.New("hi")) // true -if truthy.Value(err) { - panic(err) -} - - -var p *int -truthy.Value(p) // false - -p = new(int) -// Value does not check the underlying truthy value for a pointer! -truthy.Value(p) // true -// but ValueDeref does! -truthy.ValueDeref(p) // false +## Package Examples +### `truthy/condition` +```go // Ever wish Go had ? : ternary operators? // Now it has a ternary function. -x := truthy.Cond(truthy.Value(""), 1, 10) // x == 10 +x := condition.Evaluate(is.Truthy(""), 1, 10) // x == 10 -// truthy.Cond cannot lazily evaluate its arguments, +// condition.Evaluate cannot lazily evaluate its arguments, // but you can use a closure to fake it. -s := truthy.Cond(truthy.ValueSlice([]string{""}), +s := condition.Evaluate(is.TruthySlice([]string{""}), func() string { // do some calculation return "foo" @@ -61,32 +24,80 @@ s := truthy.Cond(truthy.ValueSlice([]string{""}), return "bar" })() // s == "foo" +``` - +### `truthy/defaults` +```go // How about an equivalent of the nullish coalescing operator ?? // as seen in C#, JavaScript, PHP, etc.: var s string -truthy.First(s, "default") // "default" +defaults.GetFirst(s, "default") // "default" s = "something" -truthy.First(s, "default") // "something" -truthy.First(0, 0*1, 1-1, 0x10-10) // 6 +defaults.GetFirst(s, "default") // "something" +defaults.GetFirst(0, 0*1, 1-1, 0x10-10) // 6 // Easily set defaults n := getUserInput() -truthy.SetDefault(&n, 42) +defaults.SetFirst(&n, 42) ``` -## FAQs +### `truthy/is` +```go +// is.Truthy returns whether a comparable value +// is equal to the zero value for its type. +// +// is.TruthyAny returns the truthiness of any argument. +// If the value's type has a Bool() bool method, the method is called and returned. +// If the type has an IsZero() bool method, the opposite value is returned. +// Slices and maps are truthy if they have a length greater than zero. +// All other types are truthy if they are not their zero value. + +is.Truthy(0) // false +is.Truthy(1) // true -### Oh god +is.Truthy("") // false +is.Truthy(" ") // true -This is the correct reaction. +is.TruthySlice([]byte(``)) // false +is.TruthySlice([]byte(` `)) // true -### Isn't this just using reflection? Does it even really require generics? +is.TruthySlice([]int{}) // false +is.TruthySlice([]int{1, 2, 3}) // true -I tried to write a non-generic version of this package first, but you can’t reflect on an interface type. When you do `reflect.Value(x)`, you lose the fact that x was, e.g. an error, because `reflect.Value()` only takes `interface{}` and the conversion loses the interface type. You’d end up saying whether the underlying concrete type was empty or not, which is typically not what you want. To work around that, you could require that everything is passed as a pointer, e.g. `reflect.Value(&err)`, but `truthy.Value(&err)` sucks as an API. If you look at how `truthy.Value()` works, it accepts a value of type `T`, and then passes `*T` to `reflect.Value()` and calls `value.Elem()` to finally get the correct reflection type. So, on a technical level, you couldn’t quite make this API work without generics, although it could be close. However, `truthy.Filter()`, `truthy.SetDefault()`, `truthy.Any()`, and `truthy.All()` could be implemented with pure reflection, although the implementation would be a lot uglier. +var err error +is.Truthy(err) // false +is.Truthy(errors.New("hi")) // true +if is.Truthy(err) { + panic(err) +} -Then there’s `truthy.First()`. To be honest, `truthy.First()` is the only part of the package that I consider actually useful, and even that, I mostly expect it to be used for picking a string or default. Anyhow, it requires generics to avoid the cast back from interface type to the concrete type. -### Should I use this package? -Probably not. It's a little bit of a joke package, but the `truthy.First()` and `truthy.SetDefault()` functionality seem useful, especially for strings. Time will tell what best practices around the use of generics in Go turn out to be. +var p *int +is.Truthy(p) // false + +p = new(int) +// Truthy does not check the underlying truthy value for a pointer! +is.Truthy(p) // true +// for that, use TruthyPointer, +is.TruthyPointer(p) // false +// but beware of double pointers! +is.TruthyPointer(&p) // true +``` + +### `truthy/pointers` +```go +// Start with a null pointer +var strptr *string + +// pointers.Deref safely dereferences a nil pointer into its empty value +fmt.Println(pointers.Deref(strptr) == "") // prints true + +// pointers.Coalesce lets us specify a default value for a nil pointer +fmt.Println(pointers.Coalesce(strptr, "hello, world")) // prints "hello, world" + +// We can create a pointer to a string or other primitive type with pointers.New +newptr := pointers.New("meaning of life") // makes a pointer to a string, wow! + +// pointers.First returns the first pointer that isn't nil. +strptr = pointers.First(strptr, newptr) // returns newptr +``` diff --git a/coalesce_example_test.go b/coalesce_example_test.go deleted file mode 100644 index c1d89d9..0000000 --- a/coalesce_example_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package truthy_test - -import ( - "fmt" - - "github.com/carlmjohnson/truthy" -) - -func ExampleCoalesce() { - var np *int - fmt.Println(truthy.Coalesce(np, 1)) - np = new(int) - fmt.Println(truthy.Coalesce(np, 1)) - // Output: - // 1 - // 0 -} - -func ExampleDeref() { - var np *int - fmt.Println(truthy.Deref(np)) - one := 1 - np = &one - fmt.Println(truthy.Deref(np)) - // Output: - // 0 - // 1 -} diff --git a/cond.go b/cond.go deleted file mode 100644 index 82ff8af..0000000 --- a/cond.go +++ /dev/null @@ -1,10 +0,0 @@ -package truthy - -// Cond returns ifVal if cond is true, -// otherwise it returns elseVal. -func Cond[T any](cond bool, ifVal, elseVal T) T { - if cond { - return ifVal - } - return elseVal -} diff --git a/condition/condition.go b/condition/condition.go new file mode 100644 index 0000000..e4b4d95 --- /dev/null +++ b/condition/condition.go @@ -0,0 +1,10 @@ +package condition + +// Evaluate returns ifVal if cond is true, +// otherwise it returns elseVal. +func Evaluate[T any](cond bool, ifVal, elseVal T) T { + if cond { + return ifVal + } + return elseVal +} diff --git a/cond_example_test.go b/condition/condition_example_test.go similarity index 63% rename from cond_example_test.go rename to condition/condition_example_test.go index e364b94..8a4e96f 100644 --- a/cond_example_test.go +++ b/condition/condition_example_test.go @@ -1,17 +1,18 @@ -package truthy_test +package condition_test import ( "fmt" - "github.com/carlmjohnson/truthy" + "github.com/carlmjohnson/truthy/condition" + "github.com/carlmjohnson/truthy/is" ) -func ExampleCond_lazy() { +func ExampleEvaluate_lazy() { i := 1 // Cond cannot lazily evaluate its arguments, // but you can use a closure to fake it. - s := truthy.Cond( - truthy.Value(i), + s := condition.Evaluate( + is.Truthy(i), func() string { // do some calculation return "true" diff --git a/default.go b/default.go deleted file mode 100644 index 4d7978a..0000000 --- a/default.go +++ /dev/null @@ -1,37 +0,0 @@ -package truthy - -// First returns the first value in vs which is non-zero. -func First[T comparable](vs ...T) (t T) { - for _, v := range vs { - if v != *new(T) { - return v - } - } - return -} - -// FirstAny returns the first value in vs which is truthy. -func FirstAny[T any](vs ...T) (t T) { - for _, v := range vs { - if ValueAny(v) { - return v - } - } - return -} - -// SetDefault sets p to the first non-zero value in defaults -// if it is not already non-zero. -func SetDefault[T comparable](p *T, defaults ...T) { - if *p == *new(T) { - *p = First(defaults...) - } -} - -// SetDefaultAny sets p to the first truthy value in defaults -// if it is not already truthy. -func SetDefaultAny[T any](p *T, defaults ...T) { - if !ValueAny(*p) { - *p = FirstAny(defaults...) - } -} diff --git a/defaults/defaults.go b/defaults/defaults.go new file mode 100644 index 0000000..ee32363 --- /dev/null +++ b/defaults/defaults.go @@ -0,0 +1,39 @@ +package defaults + +import "github.com/carlmjohnson/truthy/is" + +// GetFirst returns the first value in vs which is non-zero. +func GetFirst[T comparable](vs ...T) (t T) { + for _, v := range vs { + if is.Truthy(v) { + return v + } + } + return +} + +// GetFirstAny returns the first value in vs which is truthy. +func GetFirstAny[T any](vs ...T) (t T) { + for _, v := range vs { + if is.TruthyAny(v) { + return v + } + } + return +} + +// SetFirst sets p to the first non-zero value in defaults +// if it is not already non-zero. +func SetFirst[T comparable](p *T, defaults ...T) { + if !is.TruthyPointer(p) { + *p = GetFirst(defaults...) + } +} + +// SetFirstAny sets p to the first truthy value in defaults +// if it is not already truthy. +func SetFirstAny[T any](p *T, defaults ...T) { + if !is.TruthyAny(*p) { + *p = GetFirstAny(defaults...) + } +} diff --git a/default_example_test.go b/defaults/defaults_example_test.go similarity index 68% rename from default_example_test.go rename to defaults/defaults_example_test.go index 57a4b0e..ae5b184 100644 --- a/default_example_test.go +++ b/defaults/defaults_example_test.go @@ -1,10 +1,10 @@ -package truthy_test +package defaults_test import ( "fmt" "time" - "github.com/carlmjohnson/truthy" + "github.com/carlmjohnson/truthy/defaults" ) type MyStruct struct { @@ -15,9 +15,9 @@ type MyStruct struct { func NewMyStruct(port int, host string, timeout time.Duration) *MyStruct { s := MyStruct{port, host, timeout} - truthy.SetDefault(&s.Port, 80) - truthy.SetDefault(&s.Host, "localhost") - truthy.SetDefault(&s.Timeout, 10*time.Second) + defaults.SetFirst(&s.Port, 80) + defaults.SetFirst(&s.Host, "localhost") + defaults.SetFirst(&s.Timeout, 10*time.Second) return &s } @@ -31,9 +31,9 @@ func ExampleSetDefault() { func MakeMyStruct(port int, host string, timeout time.Duration) *MyStruct { return &MyStruct{ - Port: truthy.First(port, 80), - Host: truthy.First(host, "localhost"), - Timeout: truthy.First(timeout, 10*time.Second), + Port: defaults.GetFirst(port, 80), + Host: defaults.GetFirst(host, "localhost"), + Timeout: defaults.GetFirst(timeout, 10*time.Second), } } diff --git a/value.go b/is/is.go similarity index 56% rename from value.go rename to is/is.go index 3bd25b1..5555bb2 100644 --- a/value.go +++ b/is/is.go @@ -1,10 +1,17 @@ -package truthy +package is import ( "reflect" ) -// ValueAny returns the truthy value of anything. +// Truthy returns the truthy value of comparable types. +// Values are truthy if they are not equal to the zero value for the type. +// Note that it does not evaluate the zero value for pointer types. +func Truthy[T comparable](v T) bool { + return v != *new(T) +} + +// TruthyAny returns the truthy value of anything. // If the value's type has a Bool() bool method, the method is called and returned. // If the type has an IsZero() bool method, the opposite value is returned. // Slices and maps are truthy if they have a length greater than zero. @@ -13,9 +20,9 @@ import ( // Note that the usual Go type system caveats apply around a nil pointer value not being a nil interface value. // // In benchmark testing, -// ValueAny is approximately 25 times slower than Value, +// TruthyAny is approximately 25 times slower than Value, // and 50 times slower than native Go comparisons. -func ValueAny[T any](v T) bool { +func TruthyAny[T any](v T) bool { switch m := any(v).(type) { case interface{ Bool() bool }: return m.Bool() @@ -25,36 +32,33 @@ func ValueAny[T any](v T) bool { return reflectValue(&v) } -func reflectValue(vp any) bool { - switch rv := reflect.ValueOf(vp).Elem(); rv.Kind() { - case reflect.Map, reflect.Slice: - return rv.Len() != 0 - default: - return !rv.IsZero() - } -} - -// ValueSlice returns true if the length of the slice is greater than 0. -// Note that it does not distinguish nil slices from empty slices. -func ValueSlice[T any, S ~[]T](v S) bool { - return len(v) > 0 -} - -// ValueMap returns true if the length of the map is greater than 0. +// TruthyMap returns true if the length of the map is greater than 0. // Note that it does not distinguish nil maps from empty maps. -func ValueMap[K comparable, V any, M ~map[K]V](v M) bool { +func TruthyMap[K comparable, V any, M ~map[K]V](v M) bool { return len(v) > 0 } -// Value returns the truthy value of comparable types. +// Get returns the truthy value of dereferenced pointers of comparable types. // Values are truthy if they are not equal to the zero value for the type. -func Value[T comparable](v T) bool { - return v != *new(T) +// Note that it will evaluate to true for double pointers. +func TruthyPointer[T comparable](v *T) bool { + if v == nil { + return false + } + return Truthy(*v) +} + +// TruthySlice returns true if the length of the slice is greater than 0. +// Note that it does not distinguish nil slices from empty slices. +func TruthySlice[T any, S ~[]T](v S) bool { + return len(v) > 0 } -// ValueDeref returns the truthy value of comparable types. -// Values are truthy if they are not equal to the -// zero value for the dereferenced type. -func ValueDeref[T comparable](p *T) bool { - return Value(Deref(p)) +func reflectValue(vp any) bool { + switch rv := reflect.ValueOf(vp).Elem(); rv.Kind() { + case reflect.Map, reflect.Slice: + return rv.Len() != 0 + default: + return !rv.IsZero() + } } diff --git a/value_bench_test.go b/is/is_bench_test.go similarity index 87% rename from value_bench_test.go rename to is/is_bench_test.go index 11907a9..c4dfdbe 100644 --- a/value_bench_test.go +++ b/is/is_bench_test.go @@ -1,10 +1,10 @@ -package truthy_test +package is_test import ( "errors" "testing" - "github.com/carlmjohnson/truthy" + "github.com/carlmjohnson/truthy/is" ) func BenchmarkValueAny_error(b *testing.B) { @@ -15,7 +15,7 @@ func BenchmarkValueAny_error(b *testing.B) { if fill { value = fillVal } - if truthy.ValueAny(value) != fill { + if is.TruthyAny(value) != fill { b.FailNow() } fill = !fill @@ -30,7 +30,7 @@ func BenchmarkValue_error(b *testing.B) { if fill { value = fillVal } - if truthy.Value(value) != fill { + if is.Truthy(value) != fill { b.FailNow() } fill = !fill @@ -60,7 +60,7 @@ func BenchmarkValueAny_string(b *testing.B) { if fill { value = fillVal } - if truthy.ValueAny(value) != fill { + if is.TruthyAny(value) != fill { b.FailNow() } fill = !fill @@ -75,7 +75,7 @@ func BenchmarkValue_string(b *testing.B) { if fill { value = fillVal } - if truthy.Value(value) != fill { + if is.Truthy(value) != fill { b.FailNow() } fill = !fill @@ -105,7 +105,7 @@ func BenchmarkValueAny_int(b *testing.B) { if fill { value = fillVal } - if truthy.ValueAny(value) != fill { + if is.TruthyAny(value) != fill { b.FailNow() } fill = !fill @@ -120,7 +120,7 @@ func BenchmarkValue_int(b *testing.B) { if fill { value = fillVal } - if truthy.Value(value) != fill { + if is.Truthy(value) != fill { b.FailNow() } fill = !fill diff --git a/is/is_example_test.go b/is/is_example_test.go new file mode 100644 index 0000000..a1dda3a --- /dev/null +++ b/is/is_example_test.go @@ -0,0 +1,78 @@ +package is_test + +import ( + "errors" + "fmt" + "time" + + "github.com/carlmjohnson/truthy/is" +) + +func ExampleTruthy() { + var err error + fmt.Println(is.Truthy(err)) + + err = errors.New("hi") + fmt.Println(is.Truthy(err)) + + var n int + fmt.Println(is.Truthy(n)) + + n = 1 + fmt.Println(is.Truthy(n)) + + var p *int + fmt.Println(is.Truthy(p)) + + p = new(int) + // Truthy does not check underlying pointer value! + fmt.Println(is.Truthy(p)) + // for that, use TruthyPointer, + fmt.Println(is.TruthyPointer(p)) + // but beware of double pointers! + fmt.Println(is.TruthyPointer(&p)) + + var s string + fmt.Println(is.Truthy(s)) + + s = " " + fmt.Println(is.Truthy(s)) + + var b []byte + fmt.Println(is.TruthySlice(b)) + + b = []byte(" ") + fmt.Println(is.TruthySlice(b)) + + m := map[string]string{} + fmt.Println(is.TruthyMap(m)) + + m["a"] = "b" + fmt.Println(is.TruthyMap(m)) + + var t time.Time + t = t.Local() + // t.IsZero() is still true although t is not the empty value + fmt.Println(is.TruthyAny(t)) + + t = t.Add(1 * time.Second) + fmt.Println(is.TruthyAny(t)) + + // Output: + // false + // true + // false + // true + // false + // true + // false + // true + // false + // true + // false + // true + // false + // true + // false + // true +} diff --git a/value_test.go b/is/is_for_any_test.go similarity index 89% rename from value_test.go rename to is/is_for_any_test.go index 6e4e24e..4f245ac 100644 --- a/value_test.go +++ b/is/is_for_any_test.go @@ -1,11 +1,11 @@ -package truthy_test +package is_test import ( "fmt" "testing" "time" - "github.com/carlmjohnson/truthy" + "github.com/carlmjohnson/truthy/is" ) type errT struct{} @@ -14,13 +14,13 @@ func (*errT) Error() string { return "" } func test[T any](t *testing.T, v T, ok bool) { t.Run(fmt.Sprintf("%T-%v-%v", v, v, ok), func(t *testing.T) { - if got := truthy.ValueAny(v); got != ok { + if got := is.TruthyAny(v); got != ok { t.Fatal() } }) } -func TestValueAny(t *testing.T) { +func TestValueForAny(t *testing.T) { var err error test(t, err, false) err = (*errT)(nil) diff --git a/coalesce.go b/pointers/deref.go similarity index 93% rename from coalesce.go rename to pointers/deref.go index f6d6c72..df5e103 100644 --- a/coalesce.go +++ b/pointers/deref.go @@ -1,4 +1,4 @@ -package truthy +package pointers // Coalesce returns *p if p is not nil, otherwise v. func Coalesce[T any](p *T, v T) T { diff --git a/pointers/deref_test.go b/pointers/deref_test.go new file mode 100644 index 0000000..67fb722 --- /dev/null +++ b/pointers/deref_test.go @@ -0,0 +1,28 @@ +package pointers_test + +import ( + "fmt" + + "github.com/carlmjohnson/truthy/pointers" +) + +func ExampleCoalesce() { + var np *int + fmt.Println(pointers.Coalesce(np, 1)) + np = new(int) + fmt.Println(pointers.Coalesce(np, 1)) + // Output: + // 1 + // 0 +} + +func ExampleDeref() { + var np *int + fmt.Println(pointers.Deref(np)) + one := 1 + np = &one + fmt.Println(pointers.Deref(np)) + // Output: + // 0 + // 1 +} diff --git a/pointers/first.go b/pointers/first.go new file mode 100644 index 0000000..9cb118b --- /dev/null +++ b/pointers/first.go @@ -0,0 +1,11 @@ +package pointers + +// GetFirst returns the first non-nil pointer it is passed. +func GetFirst[T any](ptrs ...*T) *T { + for _, ptr := range ptrs { + if ptr != nil { + return ptr + } + } + return nil +} diff --git a/pointers/first_example_test.go b/pointers/first_example_test.go new file mode 100644 index 0000000..b46205b --- /dev/null +++ b/pointers/first_example_test.go @@ -0,0 +1,21 @@ +package pointers_test + +import ( + "fmt" + + "github.com/carlmjohnson/truthy/pointers" +) + +func ExampleGetFirst() { + type config struct{ string } + userInput := func() *config { + return nil + } + someConfig := pointers.GetFirst( + userInput(), + &config{"default config"}, + ) + fmt.Println(someConfig) + // Output: + // &{default config} +} diff --git a/pointers/new.go b/pointers/new.go new file mode 100644 index 0000000..92beca0 --- /dev/null +++ b/pointers/new.go @@ -0,0 +1,6 @@ +package pointers + +// New allocates a new variable of a given value and returns a pointer to it. +func New[T any](value T) *T { + return &value +} diff --git a/pointers/new_example_test.go b/pointers/new_example_test.go new file mode 100644 index 0000000..ad18a2e --- /dev/null +++ b/pointers/new_example_test.go @@ -0,0 +1,32 @@ +package pointers_test + +import ( + "fmt" + + "github.com/carlmjohnson/truthy/pointers" +) + +func ExampleNew() { + strptr1 := pointers.New("meaning of life") + strptr2 := pointers.New("meaning of life") + fmt.Println(strptr1 != strptr2) + fmt.Println(*strptr1 == *strptr2) + + intp1 := pointers.New(42) + intp2 := pointers.New(42) + fmt.Println(intp1 != intp2) + fmt.Println(*intp1 == *intp2) + + type MyFloat float64 + fp := pointers.New[MyFloat](42) + fmt.Println(fp != nil) + fmt.Println(*fp == 42) + + // Output: + // true + // true + // true + // true + // true + // true +} diff --git a/value_example_test.go b/value_example_test.go deleted file mode 100644 index 3874c7e..0000000 --- a/value_example_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package truthy_test - -import ( - "errors" - "fmt" - "time" - - "github.com/carlmjohnson/truthy" -) - -func ExampleValue() { - var err error - fmt.Println(truthy.Value(err)) - - err = errors.New("hi") - fmt.Println(truthy.Value(err)) - - var n int - fmt.Println(truthy.Value(n)) - - n = 1 - fmt.Println(truthy.Value(n)) - - var p *int - fmt.Println(truthy.Value(p)) - - p = new(int) - // truthy does not check value underlying pointer! - fmt.Println(truthy.Value(p)) - - var s string - fmt.Println(truthy.Value(s)) - - s = " " - fmt.Println(truthy.Value(s)) - - var b []byte - fmt.Println(truthy.ValueSlice(b)) - - b = []byte(" ") - fmt.Println(truthy.ValueSlice(b)) - - m := map[string]string{} - fmt.Println(truthy.ValueMap(m)) - - m["a"] = "b" - fmt.Println(truthy.ValueMap(m)) - - var t time.Time - t = t.Local() - // t.IsZero() is still true although t is not the empty value - fmt.Println(truthy.ValueAny(t)) - - t = t.Add(1 * time.Second) - fmt.Println(truthy.ValueAny(t)) - - // Output: - // false - // true - // false - // true - // false - // true - // false - // true - // false - // true - // false - // true - // false - // true -} - -func ExampleValueDeref() { - var np *int - fmt.Println(truthy.ValueDeref(np)) - zero := 0 - np = &zero - fmt.Println(truthy.ValueDeref(np)) - one := 1 - np = &one - fmt.Println(truthy.ValueDeref(np)) - // Output: - // false - // false - // true -} From 8a03a2c8afe2a837056395f6b25e197df54f3f58 Mon Sep 17 00:00:00 2001 From: "jacob h. brown" Date: Mon, 8 Jan 2024 18:44:51 -0600 Subject: [PATCH 3/6] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 61bb93a..93f8713 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Truthiness](https://user-images.githubusercontent.com/222245/136619462-f2bc5858-067f-4277-a813-b95c64b3cdac.png) -truthy is a set of packages that use generics to create useful boolean tests of truthiness and also contain some nice helper functions +`truthy` leverages generics to create a useful collection of boolean tests of truthiness and helper functions ## Package Examples From 8f5e2eeef97449764487b91cf6bb754fb547824a Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Mon, 8 Jan 2024 19:04:19 -0600 Subject: [PATCH 4/6] Rename condition to ternary --- README.md | 40 +++++++++---------- condition/condition.go => ternary/ternary.go | 2 +- .../ternary_example_test.go | 6 +-- 3 files changed, 24 insertions(+), 24 deletions(-) rename condition/condition.go => ternary/ternary.go (90%) rename condition/condition_example_test.go => ternary/ternary_example_test.go (80%) diff --git a/README.md b/README.md index 93f8713..402574c 100644 --- a/README.md +++ b/README.md @@ -6,26 +6,6 @@ ## Package Examples -### `truthy/condition` -```go -// Ever wish Go had ? : ternary operators? -// Now it has a ternary function. -x := condition.Evaluate(is.Truthy(""), 1, 10) // x == 10 - -// condition.Evaluate cannot lazily evaluate its arguments, -// but you can use a closure to fake it. -s := condition.Evaluate(is.TruthySlice([]string{""}), - func() string { - // do some calculation - return "foo" - }, - func() string { - // do some calculation - return "bar" - })() -// s == "foo" -``` - ### `truthy/defaults` ```go // How about an equivalent of the nullish coalescing operator ?? @@ -101,3 +81,23 @@ newptr := pointers.New("meaning of life") // makes a pointer to a string, wow! // pointers.First returns the first pointer that isn't nil. strptr = pointers.First(strptr, newptr) // returns newptr ``` + +### `truthy/ternary` +```go +// Ever wish Go had ? : ternary operators? +// Now it has a ternary function. +x := ternary.Evaluate(is.Truthy(""), 1, 10) // x == 10 + +// ternary.Evaluate cannot lazily evaluate its arguments, +// but you can use a closure to fake it. +s := ternary.Evaluate(is.TruthySlice([]string{""}), + func() string { + // do some calculation + return "foo" + }, + func() string { + // do some calculation + return "bar" + })() +// s == "foo" +``` diff --git a/condition/condition.go b/ternary/ternary.go similarity index 90% rename from condition/condition.go rename to ternary/ternary.go index e4b4d95..d5b1c6f 100644 --- a/condition/condition.go +++ b/ternary/ternary.go @@ -1,4 +1,4 @@ -package condition +package ternary // Evaluate returns ifVal if cond is true, // otherwise it returns elseVal. diff --git a/condition/condition_example_test.go b/ternary/ternary_example_test.go similarity index 80% rename from condition/condition_example_test.go rename to ternary/ternary_example_test.go index 8a4e96f..98d2f11 100644 --- a/condition/condition_example_test.go +++ b/ternary/ternary_example_test.go @@ -1,17 +1,17 @@ -package condition_test +package ternary_test import ( "fmt" - "github.com/carlmjohnson/truthy/condition" "github.com/carlmjohnson/truthy/is" + "github.com/carlmjohnson/truthy/ternary" ) func ExampleEvaluate_lazy() { i := 1 // Cond cannot lazily evaluate its arguments, // but you can use a closure to fake it. - s := condition.Evaluate( + s := ternary.Evaluate( is.Truthy(i), func() string { // do some calculation From 06b8cf717f4d989592be026fc02bac8e5209cc16 Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Mon, 8 Jan 2024 19:08:50 -0600 Subject: [PATCH 5/6] Some cleanup --- defaults/defaults_example_test.go | 4 ++-- is/is.go | 6 +++--- is/is_bench_test.go | 12 ++++++------ is/is_for_any_test.go | 2 +- ternary/ternary_example_test.go | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/defaults/defaults_example_test.go b/defaults/defaults_example_test.go index ae5b184..06dbb90 100644 --- a/defaults/defaults_example_test.go +++ b/defaults/defaults_example_test.go @@ -21,7 +21,7 @@ func NewMyStruct(port int, host string, timeout time.Duration) *MyStruct { return &s } -func ExampleSetDefault() { +func ExampleSetFirst() { fmt.Println(NewMyStruct(0, "", 0)) fmt.Println(NewMyStruct(443, "example.com", 1*time.Minute)) // Output: @@ -37,7 +37,7 @@ func MakeMyStruct(port int, host string, timeout time.Duration) *MyStruct { } } -func ExampleFirst() { +func ExampleGetFirst() { fmt.Println(MakeMyStruct(0, "", 0)) fmt.Println(MakeMyStruct(443, "example.com", 1*time.Minute)) // Output: diff --git a/is/is.go b/is/is.go index 5555bb2..202e0a9 100644 --- a/is/is.go +++ b/is/is.go @@ -20,7 +20,7 @@ func Truthy[T comparable](v T) bool { // Note that the usual Go type system caveats apply around a nil pointer value not being a nil interface value. // // In benchmark testing, -// TruthyAny is approximately 25 times slower than Value, +// TruthyAny is approximately 25 times slower than Truthy, // and 50 times slower than native Go comparisons. func TruthyAny[T any](v T) bool { switch m := any(v).(type) { @@ -38,8 +38,8 @@ func TruthyMap[K comparable, V any, M ~map[K]V](v M) bool { return len(v) > 0 } -// Get returns the truthy value of dereferenced pointers of comparable types. -// Values are truthy if they are not equal to the zero value for the type. +// TruthyPointer returns the truthy value of dereferenced pointers of comparable types. +// Values are truthy if they are not equal to the zero value for the dereferenced type. // Note that it will evaluate to true for double pointers. func TruthyPointer[T comparable](v *T) bool { if v == nil { diff --git a/is/is_bench_test.go b/is/is_bench_test.go index c4dfdbe..c468af5 100644 --- a/is/is_bench_test.go +++ b/is/is_bench_test.go @@ -7,7 +7,7 @@ import ( "github.com/carlmjohnson/truthy/is" ) -func BenchmarkValueAny_error(b *testing.B) { +func BenchmarkTruthyAny_error(b *testing.B) { fillVal := errors.New("something") fill := false for i := 0; i < b.N; i++ { @@ -22,7 +22,7 @@ func BenchmarkValueAny_error(b *testing.B) { } } -func BenchmarkValue_error(b *testing.B) { +func BenchmarkTruthy_error(b *testing.B) { fillVal := errors.New("something") fill := false for i := 0; i < b.N; i++ { @@ -52,7 +52,7 @@ func Benchmark_error(b *testing.B) { } } -func BenchmarkValueAny_string(b *testing.B) { +func BenchmarkTruthyAny_string(b *testing.B) { fillVal := "something" fill := false for i := 0; i < b.N; i++ { @@ -67,7 +67,7 @@ func BenchmarkValueAny_string(b *testing.B) { } } -func BenchmarkValue_string(b *testing.B) { +func BenchmarkTruthy_string(b *testing.B) { fillVal := "something" fill := false for i := 0; i < b.N; i++ { @@ -97,7 +97,7 @@ func Benchmark_string(b *testing.B) { } } -func BenchmarkValueAny_int(b *testing.B) { +func BenchmarkTruthyAny_int(b *testing.B) { fillVal := 1 fill := false for i := 0; i < b.N; i++ { @@ -112,7 +112,7 @@ func BenchmarkValueAny_int(b *testing.B) { } } -func BenchmarkValue_int(b *testing.B) { +func BenchmarkTruthy_int(b *testing.B) { fillVal := 1 fill := false for i := 0; i < b.N; i++ { diff --git a/is/is_for_any_test.go b/is/is_for_any_test.go index 4f245ac..1f7d922 100644 --- a/is/is_for_any_test.go +++ b/is/is_for_any_test.go @@ -20,7 +20,7 @@ func test[T any](t *testing.T, v T, ok bool) { }) } -func TestValueForAny(t *testing.T) { +func TestTruthyAny(t *testing.T) { var err error test(t, err, false) err = (*errT)(nil) diff --git a/ternary/ternary_example_test.go b/ternary/ternary_example_test.go index 98d2f11..ee276e7 100644 --- a/ternary/ternary_example_test.go +++ b/ternary/ternary_example_test.go @@ -9,7 +9,7 @@ import ( func ExampleEvaluate_lazy() { i := 1 - // Cond cannot lazily evaluate its arguments, + // Evaluate cannot lazily evaluate its arguments, // but you can use a closure to fake it. s := ternary.Evaluate( is.Truthy(i), From e66db9d72ad3de562654c0c4a46df1ed265f469b Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Tue, 9 Jan 2024 08:27:00 -0600 Subject: [PATCH 6/6] bools and pointers --- README.md | 110 +++++++++--------- is/is.go => bools/bools.go | 29 ++--- .../bools_any_test.go | 8 +- .../bools_bench_test.go | 28 ++--- bools/bools_example_test.go | 78 +++++++++++++ {defaults => bools}/defaults.go | 12 +- {defaults => bools}/defaults_example_test.go | 16 +-- bools/int.go | 10 ++ bools/int_example_test.go | 19 +++ bools/ternary.go | 10 ++ {ternary => bools}/ternary_example_test.go | 13 +-- is/is_example_test.go | 78 ------------- ternary/ternary.go | 10 -- 13 files changed, 223 insertions(+), 198 deletions(-) rename is/is.go => bools/bools.go (63%) rename is/is_for_any_test.go => bools/bools_any_test.go (89%) rename is/is_bench_test.go => bools/bools_bench_test.go (75%) create mode 100644 bools/bools_example_test.go rename {defaults => bools}/defaults.go (81%) rename {defaults => bools}/defaults_example_test.go (68%) create mode 100644 bools/int.go create mode 100644 bools/int_example_test.go create mode 100644 bools/ternary.go rename {ternary => bools}/ternary_example_test.go (53%) delete mode 100644 is/is_example_test.go delete mode 100644 ternary/ternary.go diff --git a/README.md b/README.md index 402574c..37b00cc 100644 --- a/README.md +++ b/README.md @@ -6,91 +6,72 @@ ## Package Examples -### `truthy/defaults` +### `truthy/bools` ```go -// How about an equivalent of the nullish coalescing operator ?? -// as seen in C#, JavaScript, PHP, etc.: -var s string -defaults.GetFirst(s, "default") // "default" -s = "something" -defaults.GetFirst(s, "default") // "something" -defaults.GetFirst(0, 0*1, 1-1, 0x10-10) // 6 - -// Easily set defaults -n := getUserInput() -defaults.SetFirst(&n, 42) -``` - -### `truthy/is` -```go -// is.Truthy returns whether a comparable value +// bools.Comparable returns whether a comparable value // is equal to the zero value for its type. // -// is.TruthyAny returns the truthiness of any argument. +// bools.Any returns the truthiness of any argument. // If the value's type has a Bool() bool method, the method is called and returned. // If the type has an IsZero() bool method, the opposite value is returned. // Slices and maps are truthy if they have a length greater than zero. // All other types are truthy if they are not their zero value. -is.Truthy(0) // false -is.Truthy(1) // true +bools.Comparable(0) // false +bools.Comparable(1) // true -is.Truthy("") // false -is.Truthy(" ") // true +bools.Comparable("") // false +bools.Comparable(" ") // true -is.TruthySlice([]byte(``)) // false -is.TruthySlice([]byte(` `)) // true +bools.Map(map[string]string{}) // false +bools.Map(map[string]string{"truthy": "is awesome"}) // true -is.TruthySlice([]int{}) // false -is.TruthySlice([]int{1, 2, 3}) // true +bools.Slice([]int{}) // false +bools.Slice([]int{1, 2, 3}) // true var err error -is.Truthy(err) // false -is.Truthy(errors.New("hi")) // true -if is.Truthy(err) { +bools.Comparable(err) // false +bools.Comparable(errors.New("hi")) // true +if bools.Comparable(err) { panic(err) } var p *int -is.Truthy(p) // false +bools.Comparable(p) // false p = new(int) -// Truthy does not check the underlying truthy value for a pointer! -is.Truthy(p) // true -// for that, use TruthyPointer, -is.TruthyPointer(p) // false +// Comparable does not check the underlying truthy value for a pointer! +bools.Comparable(p) // true +// for that, use Pointer, +bools.Pointer(p) // false // but beware of double pointers! -is.TruthyPointer(&p) // true -``` +bools.Pointer(&p) // true -### `truthy/pointers` -```go -// Start with a null pointer -var strptr *string - -// pointers.Deref safely dereferences a nil pointer into its empty value -fmt.Println(pointers.Deref(strptr) == "") // prints true - -// pointers.Coalesce lets us specify a default value for a nil pointer -fmt.Println(pointers.Coalesce(strptr, "hello, world")) // prints "hello, world" +// How about an equivalent of the nullish coalescing operator ?? +// as seen in C#, JavaScript, PHP, etc.: +var s string +bools.GetFirst(s, "default") // "default" +s = "something" +bools.GetFirst(s, "default") // "something" +bools.GetFirst(0, 0*1, 1-1, 0x10-10) // 6 -// We can create a pointer to a string or other primitive type with pointers.New -newptr := pointers.New("meaning of life") // makes a pointer to a string, wow! +// Easily set defaults +n := getUserInput() +bools.SetFirst(&n, 42) -// pointers.First returns the first pointer that isn't nil. -strptr = pointers.First(strptr, newptr) // returns newptr -``` +// Want to be able to convert from Boolean to Int? +bools.Int(true) // 1 +bools.Int(10 > 100) // 0 +bools.Int(bools.Comparable("")) // 0 -### `truthy/ternary` -```go // Ever wish Go had ? : ternary operators? // Now it has a ternary function. -x := ternary.Evaluate(is.Truthy(""), 1, 10) // x == 10 +x := bools.Ternary(bools.Comparable(""), 1, 10) // x == 10 -// ternary.Evaluate cannot lazily evaluate its arguments, +// bools.Ternary cannot lazily evaluate its arguments, // but you can use a closure to fake it. -s := ternary.Evaluate(is.TruthySlice([]string{""}), +s := bools.Ternary(bools.Slice([]string{""}), func() string { // do some calculation return "foo" @@ -101,3 +82,20 @@ s := ternary.Evaluate(is.TruthySlice([]string{""}), })() // s == "foo" ``` + +### `truthy/pointers` +```go +// Start with a null pointer +var strptr *string + +// pointers.Deref safely dereferences a nil pointer into its empty value +fmt.Println(pointers.Deref(strptr) == "") // prints true + +// pointers.Coalesce lets us specify a default value for a nil pointer +fmt.Println(pointers.Coalesce(strptr, "hello, world")) // prints "hello, world" + +// We can create a pointer to a string or other primitive type with pointers.New +newptr := pointers.New("meaning of life") // makes a pointer to a string, wow! + +// pointers.First returns the first pointer that isn't nil. +strptr = pointers.First(strptr, newptr) // returns newptr diff --git a/is/is.go b/bools/bools.go similarity index 63% rename from is/is.go rename to bools/bools.go index 202e0a9..5205d9b 100644 --- a/is/is.go +++ b/bools/bools.go @@ -1,17 +1,18 @@ -package is +package bools import ( "reflect" ) -// Truthy returns the truthy value of comparable types. +// Comparable returns the truthy value of comparable types. // Values are truthy if they are not equal to the zero value for the type. -// Note that it does not evaluate the zero value for pointer types. -func Truthy[T comparable](v T) bool { +// Use the Pointer method to evaluate underlying pointer values, otherwise +// this will return true if the pointer is not nil. +func Comparable[T comparable](v T) bool { return v != *new(T) } -// TruthyAny returns the truthy value of anything. +// Any returns the truthy value of anything. // If the value's type has a Bool() bool method, the method is called and returned. // If the type has an IsZero() bool method, the opposite value is returned. // Slices and maps are truthy if they have a length greater than zero. @@ -20,9 +21,9 @@ func Truthy[T comparable](v T) bool { // Note that the usual Go type system caveats apply around a nil pointer value not being a nil interface value. // // In benchmark testing, -// TruthyAny is approximately 25 times slower than Truthy, +// Any is approximately 25 times slower than Comparable, // and 50 times slower than native Go comparisons. -func TruthyAny[T any](v T) bool { +func Any[T any](v T) bool { switch m := any(v).(type) { case interface{ Bool() bool }: return m.Bool() @@ -32,25 +33,25 @@ func TruthyAny[T any](v T) bool { return reflectValue(&v) } -// TruthyMap returns true if the length of the map is greater than 0. +// Map returns true if the length of the map is greater than 0. // Note that it does not distinguish nil maps from empty maps. -func TruthyMap[K comparable, V any, M ~map[K]V](v M) bool { +func Map[K comparable, V any, M ~map[K]V](v M) bool { return len(v) > 0 } -// TruthyPointer returns the truthy value of dereferenced pointers of comparable types. +// Pointer returns the truthy value of dereferenced pointers for comparable types. // Values are truthy if they are not equal to the zero value for the dereferenced type. // Note that it will evaluate to true for double pointers. -func TruthyPointer[T comparable](v *T) bool { +func Pointer[T comparable](v *T) bool { if v == nil { return false } - return Truthy(*v) + return Comparable(*v) } -// TruthySlice returns true if the length of the slice is greater than 0. +// Slice returns true if the length of the slice is greater than 0. // Note that it does not distinguish nil slices from empty slices. -func TruthySlice[T any, S ~[]T](v S) bool { +func Slice[T any, S ~[]T](v S) bool { return len(v) > 0 } diff --git a/is/is_for_any_test.go b/bools/bools_any_test.go similarity index 89% rename from is/is_for_any_test.go rename to bools/bools_any_test.go index 1f7d922..c0b8ed4 100644 --- a/is/is_for_any_test.go +++ b/bools/bools_any_test.go @@ -1,11 +1,11 @@ -package is_test +package bools_test import ( "fmt" "testing" "time" - "github.com/carlmjohnson/truthy/is" + "github.com/carlmjohnson/truthy/bools" ) type errT struct{} @@ -14,13 +14,13 @@ func (*errT) Error() string { return "" } func test[T any](t *testing.T, v T, ok bool) { t.Run(fmt.Sprintf("%T-%v-%v", v, v, ok), func(t *testing.T) { - if got := is.TruthyAny(v); got != ok { + if got := bools.Any(v); got != ok { t.Fatal() } }) } -func TestTruthyAny(t *testing.T) { +func TestAny(t *testing.T) { var err error test(t, err, false) err = (*errT)(nil) diff --git a/is/is_bench_test.go b/bools/bools_bench_test.go similarity index 75% rename from is/is_bench_test.go rename to bools/bools_bench_test.go index c468af5..7abd638 100644 --- a/is/is_bench_test.go +++ b/bools/bools_bench_test.go @@ -1,13 +1,13 @@ -package is_test +package bools_test import ( "errors" "testing" - "github.com/carlmjohnson/truthy/is" + "github.com/carlmjohnson/truthy/bools" ) -func BenchmarkTruthyAny_error(b *testing.B) { +func BenchmarkAny_error(b *testing.B) { fillVal := errors.New("something") fill := false for i := 0; i < b.N; i++ { @@ -15,14 +15,14 @@ func BenchmarkTruthyAny_error(b *testing.B) { if fill { value = fillVal } - if is.TruthyAny(value) != fill { + if bools.Any(value) != fill { b.FailNow() } fill = !fill } } -func BenchmarkTruthy_error(b *testing.B) { +func BenchmarkComparable_error(b *testing.B) { fillVal := errors.New("something") fill := false for i := 0; i < b.N; i++ { @@ -30,7 +30,7 @@ func BenchmarkTruthy_error(b *testing.B) { if fill { value = fillVal } - if is.Truthy(value) != fill { + if bools.Comparable(value) != fill { b.FailNow() } fill = !fill @@ -52,7 +52,7 @@ func Benchmark_error(b *testing.B) { } } -func BenchmarkTruthyAny_string(b *testing.B) { +func BenchmarkAny_string(b *testing.B) { fillVal := "something" fill := false for i := 0; i < b.N; i++ { @@ -60,14 +60,14 @@ func BenchmarkTruthyAny_string(b *testing.B) { if fill { value = fillVal } - if is.TruthyAny(value) != fill { + if bools.Any(value) != fill { b.FailNow() } fill = !fill } } -func BenchmarkTruthy_string(b *testing.B) { +func BenchmarkComparable_string(b *testing.B) { fillVal := "something" fill := false for i := 0; i < b.N; i++ { @@ -75,7 +75,7 @@ func BenchmarkTruthy_string(b *testing.B) { if fill { value = fillVal } - if is.Truthy(value) != fill { + if bools.Comparable(value) != fill { b.FailNow() } fill = !fill @@ -97,7 +97,7 @@ func Benchmark_string(b *testing.B) { } } -func BenchmarkTruthyAny_int(b *testing.B) { +func BenchmarkAny_int(b *testing.B) { fillVal := 1 fill := false for i := 0; i < b.N; i++ { @@ -105,14 +105,14 @@ func BenchmarkTruthyAny_int(b *testing.B) { if fill { value = fillVal } - if is.TruthyAny(value) != fill { + if bools.Any(value) != fill { b.FailNow() } fill = !fill } } -func BenchmarkTruthy_int(b *testing.B) { +func BenchmarkComparable_int(b *testing.B) { fillVal := 1 fill := false for i := 0; i < b.N; i++ { @@ -120,7 +120,7 @@ func BenchmarkTruthy_int(b *testing.B) { if fill { value = fillVal } - if is.Truthy(value) != fill { + if bools.Comparable(value) != fill { b.FailNow() } fill = !fill diff --git a/bools/bools_example_test.go b/bools/bools_example_test.go new file mode 100644 index 0000000..89d3e11 --- /dev/null +++ b/bools/bools_example_test.go @@ -0,0 +1,78 @@ +package bools_test + +import ( + "errors" + "fmt" + "time" + + "github.com/carlmjohnson/truthy/bools" +) + +func ExampleBools() { + var err error + fmt.Println(bools.Comparable(err)) + + err = errors.New("hi") + fmt.Println(bools.Comparable(err)) + + var n int + fmt.Println(bools.Comparable(n)) + + n = 1 + fmt.Println(bools.Comparable(n)) + + var p *int + fmt.Println(bools.Comparable(p)) + + p = new(int) + // Comparable does not check underlying pointer value! + fmt.Println(bools.Comparable(p)) + // for that, use Pointer, + fmt.Println(bools.Pointer(p)) + // but beware of double pointers! + fmt.Println(bools.Pointer(&p)) + + var s string + fmt.Println(bools.Comparable(s)) + + s = " " + fmt.Println(bools.Comparable(s)) + + var b []byte + fmt.Println(bools.Slice(b)) + + b = []byte(" ") + fmt.Println(bools.Slice(b)) + + m := map[string]string{} + fmt.Println(bools.Map(m)) + + m["a"] = "b" + fmt.Println(bools.Map(m)) + + var t time.Time + t = t.Local() + // t.IsZero() is still true although t is not the empty value + fmt.Println(bools.Any(t)) + + t = t.Add(1 * time.Second) + fmt.Println(bools.Any(t)) + + // Output: + // false + // true + // false + // true + // false + // true + // false + // true + // false + // true + // false + // true + // false + // true + // false + // true +} diff --git a/defaults/defaults.go b/bools/defaults.go similarity index 81% rename from defaults/defaults.go rename to bools/defaults.go index ee32363..89c50e4 100644 --- a/defaults/defaults.go +++ b/bools/defaults.go @@ -1,11 +1,9 @@ -package defaults - -import "github.com/carlmjohnson/truthy/is" +package bools // GetFirst returns the first value in vs which is non-zero. func GetFirst[T comparable](vs ...T) (t T) { for _, v := range vs { - if is.Truthy(v) { + if Comparable(v) { return v } } @@ -15,7 +13,7 @@ func GetFirst[T comparable](vs ...T) (t T) { // GetFirstAny returns the first value in vs which is truthy. func GetFirstAny[T any](vs ...T) (t T) { for _, v := range vs { - if is.TruthyAny(v) { + if Any(v) { return v } } @@ -25,7 +23,7 @@ func GetFirstAny[T any](vs ...T) (t T) { // SetFirst sets p to the first non-zero value in defaults // if it is not already non-zero. func SetFirst[T comparable](p *T, defaults ...T) { - if !is.TruthyPointer(p) { + if !Pointer(p) { *p = GetFirst(defaults...) } } @@ -33,7 +31,7 @@ func SetFirst[T comparable](p *T, defaults ...T) { // SetFirstAny sets p to the first truthy value in defaults // if it is not already truthy. func SetFirstAny[T any](p *T, defaults ...T) { - if !is.TruthyAny(*p) { + if !Any(*p) { *p = GetFirstAny(defaults...) } } diff --git a/defaults/defaults_example_test.go b/bools/defaults_example_test.go similarity index 68% rename from defaults/defaults_example_test.go rename to bools/defaults_example_test.go index 06dbb90..c1f8fb3 100644 --- a/defaults/defaults_example_test.go +++ b/bools/defaults_example_test.go @@ -1,10 +1,10 @@ -package defaults_test +package bools_test import ( "fmt" "time" - "github.com/carlmjohnson/truthy/defaults" + "github.com/carlmjohnson/truthy/bools" ) type MyStruct struct { @@ -15,9 +15,9 @@ type MyStruct struct { func NewMyStruct(port int, host string, timeout time.Duration) *MyStruct { s := MyStruct{port, host, timeout} - defaults.SetFirst(&s.Port, 80) - defaults.SetFirst(&s.Host, "localhost") - defaults.SetFirst(&s.Timeout, 10*time.Second) + bools.SetFirst(&s.Port, 80) + bools.SetFirst(&s.Host, "localhost") + bools.SetFirst(&s.Timeout, 10*time.Second) return &s } @@ -31,9 +31,9 @@ func ExampleSetFirst() { func MakeMyStruct(port int, host string, timeout time.Duration) *MyStruct { return &MyStruct{ - Port: defaults.GetFirst(port, 80), - Host: defaults.GetFirst(host, "localhost"), - Timeout: defaults.GetFirst(timeout, 10*time.Second), + Port: bools.GetFirst(port, 80), + Host: bools.GetFirst(host, "localhost"), + Timeout: bools.GetFirst(timeout, 10*time.Second), } } diff --git a/bools/int.go b/bools/int.go new file mode 100644 index 0000000..cb34480 --- /dev/null +++ b/bools/int.go @@ -0,0 +1,10 @@ +package bools + +// Int returns 1 if v is true, otherwise 0. +// Inspired by https://github.com/golang/go/issues/64825 +func Int[T bool](v T) int { + if v { + return 1 + } + return 0 +} diff --git a/bools/int_example_test.go b/bools/int_example_test.go new file mode 100644 index 0000000..e1178b7 --- /dev/null +++ b/bools/int_example_test.go @@ -0,0 +1,19 @@ +package bools_test + +import ( + "fmt" + + "github.com/carlmjohnson/truthy/bools" +) + +func ExampleInt() { + fmt.Println(bools.Int(true)) + fmt.Println(bools.Int(false)) + fmt.Println(bools.Int(bools.Comparable(""))) + fmt.Println(bools.Int('a' < 'b')) + // Output: + // 1 + // 0 + // 0 + // 1 +} diff --git a/bools/ternary.go b/bools/ternary.go new file mode 100644 index 0000000..1321c01 --- /dev/null +++ b/bools/ternary.go @@ -0,0 +1,10 @@ +package bools + +// Ternary returns ifVal if cond is true, +// otherwise it returns elseVal. +func Ternary[T any](cond bool, ifVal, elseVal T) T { + if cond { + return ifVal + } + return elseVal +} diff --git a/ternary/ternary_example_test.go b/bools/ternary_example_test.go similarity index 53% rename from ternary/ternary_example_test.go rename to bools/ternary_example_test.go index ee276e7..84490e3 100644 --- a/ternary/ternary_example_test.go +++ b/bools/ternary_example_test.go @@ -1,18 +1,17 @@ -package ternary_test +package bools_test import ( "fmt" - "github.com/carlmjohnson/truthy/is" - "github.com/carlmjohnson/truthy/ternary" + "github.com/carlmjohnson/truthy/bools" ) -func ExampleEvaluate_lazy() { +func ExampleTernary_lazy() { i := 1 - // Evaluate cannot lazily evaluate its arguments, + // Ternary cannot lazily evaluate its arguments, // but you can use a closure to fake it. - s := ternary.Evaluate( - is.Truthy(i), + s := bools.Ternary( + bools.Comparable(i), func() string { // do some calculation return "true" diff --git a/is/is_example_test.go b/is/is_example_test.go deleted file mode 100644 index a1dda3a..0000000 --- a/is/is_example_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package is_test - -import ( - "errors" - "fmt" - "time" - - "github.com/carlmjohnson/truthy/is" -) - -func ExampleTruthy() { - var err error - fmt.Println(is.Truthy(err)) - - err = errors.New("hi") - fmt.Println(is.Truthy(err)) - - var n int - fmt.Println(is.Truthy(n)) - - n = 1 - fmt.Println(is.Truthy(n)) - - var p *int - fmt.Println(is.Truthy(p)) - - p = new(int) - // Truthy does not check underlying pointer value! - fmt.Println(is.Truthy(p)) - // for that, use TruthyPointer, - fmt.Println(is.TruthyPointer(p)) - // but beware of double pointers! - fmt.Println(is.TruthyPointer(&p)) - - var s string - fmt.Println(is.Truthy(s)) - - s = " " - fmt.Println(is.Truthy(s)) - - var b []byte - fmt.Println(is.TruthySlice(b)) - - b = []byte(" ") - fmt.Println(is.TruthySlice(b)) - - m := map[string]string{} - fmt.Println(is.TruthyMap(m)) - - m["a"] = "b" - fmt.Println(is.TruthyMap(m)) - - var t time.Time - t = t.Local() - // t.IsZero() is still true although t is not the empty value - fmt.Println(is.TruthyAny(t)) - - t = t.Add(1 * time.Second) - fmt.Println(is.TruthyAny(t)) - - // Output: - // false - // true - // false - // true - // false - // true - // false - // true - // false - // true - // false - // true - // false - // true - // false - // true -} diff --git a/ternary/ternary.go b/ternary/ternary.go deleted file mode 100644 index d5b1c6f..0000000 --- a/ternary/ternary.go +++ /dev/null @@ -1,10 +0,0 @@ -package ternary - -// Evaluate returns ifVal if cond is true, -// otherwise it returns elseVal. -func Evaluate[T any](cond bool, ifVal, elseVal T) T { - if cond { - return ifVal - } - return elseVal -}