1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00
Files
fp-go/v2/function/function_test.go

499 lines
13 KiB
Go
Raw Normal View History

Implement v2 using type aliases (#141) * fix: initial checkin of v2 Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: slowly migrate IO Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: migrate MonadTraverseArray and TraverseArray Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: migrate traversal Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: complete migration of IO Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: migrate ioeither Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: refactorY Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: next step in migration Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: adjust IO generation code Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: get rid of more IO methods Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: get rid of more IO * fix: convert iooption Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: convert reader Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: convert a bit of reader Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: new build script Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: cleanup Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: reformat Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: simplify Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: some cleanup Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: adjust Pair to Haskell semantic Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: documentation and testcases Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: some performance optimizations Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: remove coverage Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> * fix: better doc Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> --------- Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-06 09:27:00 +01:00
// Copyright (c) 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package function
import (
"testing"
"github.com/stretchr/testify/assert"
)
// TestIdentity tests the Identity function
func TestIdentity(t *testing.T) {
t.Run("returns int unchanged", func(t *testing.T) {
assert.Equal(t, 42, Identity(42))
assert.Equal(t, 0, Identity(0))
assert.Equal(t, -10, Identity(-10))
})
t.Run("returns string unchanged", func(t *testing.T) {
assert.Equal(t, "hello", Identity("hello"))
assert.Equal(t, "", Identity(""))
})
t.Run("returns bool unchanged", func(t *testing.T) {
assert.True(t, Identity(true))
assert.False(t, Identity(false))
})
t.Run("returns struct unchanged", func(t *testing.T) {
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
assert.Equal(t, p, Identity(p))
})
}
// TestConstant tests the Constant function
func TestConstant(t *testing.T) {
t.Run("returns constant int", func(t *testing.T) {
getFortyTwo := Constant(42)
assert.Equal(t, 42, getFortyTwo())
assert.Equal(t, 42, getFortyTwo())
})
t.Run("returns constant string", func(t *testing.T) {
getMessage := Constant("Hello")
assert.Equal(t, "Hello", getMessage())
})
t.Run("returns constant bool", func(t *testing.T) {
getTrue := Constant(true)
assert.True(t, getTrue())
})
}
// TestConstant1 tests the Constant1 function
func TestConstant1(t *testing.T) {
t.Run("ignores input and returns constant", func(t *testing.T) {
alwaysZero := Constant1[string, int](0)
assert.Equal(t, 0, alwaysZero("anything"))
assert.Equal(t, 0, alwaysZero("something else"))
assert.Equal(t, 0, alwaysZero(""))
})
t.Run("works with different types", func(t *testing.T) {
defaultName := Constant1[int, string]("Unknown")
assert.Equal(t, "Unknown", defaultName(42))
assert.Equal(t, "Unknown", defaultName(0))
})
}
// TestConstant2 tests the Constant2 function
func TestConstant2(t *testing.T) {
t.Run("ignores both inputs and returns constant", func(t *testing.T) {
alwaysTrue := Constant2[int, string, bool](true)
assert.True(t, alwaysTrue(42, "test"))
assert.True(t, alwaysTrue(0, ""))
})
t.Run("works with different types", func(t *testing.T) {
alwaysPi := Constant2[string, bool, float64](3.14)
assert.Equal(t, 3.14, alwaysPi("test", true))
})
}
// TestIsNil tests the IsNil function
func TestIsNil(t *testing.T) {
t.Run("returns true for nil pointer", func(t *testing.T) {
var ptr *int
assert.True(t, IsNil(ptr))
var strPtr *string
assert.True(t, IsNil(strPtr))
})
t.Run("returns false for non-nil pointer", func(t *testing.T) {
value := 42
assert.False(t, IsNil(&value))
str := "hello"
assert.False(t, IsNil(&str))
})
}
// TestIsNonNil tests the IsNonNil function
func TestIsNonNil(t *testing.T) {
t.Run("returns false for nil pointer", func(t *testing.T) {
var ptr *int
assert.False(t, IsNonNil(ptr))
})
t.Run("returns true for non-nil pointer", func(t *testing.T) {
value := 42
assert.True(t, IsNonNil(&value))
str := "hello"
assert.True(t, IsNonNil(&str))
})
}
// TestSwap tests the Swap function
func TestSwap(t *testing.T) {
t.Run("swaps parameters of subtraction", func(t *testing.T) {
subtract := func(a, b int) int { return a - b }
swapped := Swap(subtract)
assert.Equal(t, 7, subtract(10, 3)) // 10 - 3
assert.Equal(t, -7, swapped(10, 3)) // 3 - 10
})
t.Run("swaps parameters of division", func(t *testing.T) {
divide := func(a, b float64) float64 { return a / b }
swapped := Swap(divide)
assert.Equal(t, 5.0, divide(10, 2)) // 10 / 2
assert.Equal(t, 0.2, swapped(10, 2)) // 2 / 10
})
t.Run("swaps parameters of string concatenation", func(t *testing.T) {
concat := func(a, b string) string { return a + b }
swapped := Swap(concat)
assert.Equal(t, "HelloWorld", concat("Hello", "World"))
assert.Equal(t, "WorldHello", swapped("Hello", "World"))
})
}
// TestFirst tests the First function
func TestFirst(t *testing.T) {
t.Run("returns first of two ints", func(t *testing.T) {
assert.Equal(t, 42, First(42, 100))
assert.Equal(t, 0, First(0, 1))
})
t.Run("returns first of two strings", func(t *testing.T) {
assert.Equal(t, "hello", First("hello", "world"))
})
t.Run("returns first of mixed types", func(t *testing.T) {
assert.Equal(t, 42, First(42, "hello"))
assert.True(t, First(true, 100))
})
}
// TestSecond tests the Second function
func TestSecond(t *testing.T) {
t.Run("returns second of two ints", func(t *testing.T) {
assert.Equal(t, 100, Second(42, 100))
assert.Equal(t, 1, Second(0, 1))
})
t.Run("returns second of two strings", func(t *testing.T) {
assert.Equal(t, "world", Second("hello", "world"))
})
t.Run("returns second of mixed types", func(t *testing.T) {
assert.Equal(t, "hello", Second(42, "hello"))
assert.Equal(t, 100, Second(true, 100))
})
}
// TestBind1st tests the Bind1st function
func TestBind1st(t *testing.T) {
t.Run("binds first parameter of multiplication", func(t *testing.T) {
multiply := func(a, b int) int { return a * b }
double := Bind1st(multiply, 2)
triple := Bind1st(multiply, 3)
assert.Equal(t, 10, double(5))
assert.Equal(t, 20, double(10))
assert.Equal(t, 15, triple(5))
})
t.Run("binds first parameter of division", func(t *testing.T) {
divide := func(a, b float64) float64 { return a / b }
divideBy10 := Bind1st(divide, 10.0)
assert.Equal(t, 5.0, divideBy10(2.0))
assert.Equal(t, 2.0, divideBy10(5.0))
})
t.Run("binds first parameter of string concatenation", func(t *testing.T) {
concat := func(a, b string) string { return a + b }
addHello := Bind1st(concat, "Hello ")
assert.Equal(t, "Hello World", addHello("World"))
assert.Equal(t, "Hello Go", addHello("Go"))
})
}
// TestBind2nd tests the Bind2nd function
func TestBind2nd(t *testing.T) {
t.Run("binds second parameter of multiplication", func(t *testing.T) {
multiply := func(a, b int) int { return a * b }
double := Bind2nd(multiply, 2)
triple := Bind2nd(multiply, 3)
assert.Equal(t, 10, double(5))
assert.Equal(t, 20, double(10))
assert.Equal(t, 15, triple(5))
})
t.Run("binds second parameter of division", func(t *testing.T) {
divide := func(a, b float64) float64 { return a / b }
halve := Bind2nd(divide, 2.0)
assert.Equal(t, 5.0, halve(10.0))
assert.Equal(t, 2.5, halve(5.0))
})
t.Run("binds second parameter of subtraction", func(t *testing.T) {
subtract := func(a, b int) int { return a - b }
decrementBy5 := Bind2nd(subtract, 5)
assert.Equal(t, 5, decrementBy5(10))
assert.Equal(t, 0, decrementBy5(5))
})
}
// TestSK tests the SK function
func TestSK(t *testing.T) {
t.Run("returns second argument ignoring first", func(t *testing.T) {
assert.Equal(t, "hello", SK(42, "hello"))
assert.Equal(t, 100, SK(true, 100))
assert.Equal(t, 3.14, SK("test", 3.14))
})
t.Run("behaves like Second", func(t *testing.T) {
// SK should be identical to Second
assert.Equal(t, Second(42, "hello"), SK(42, "hello"))
assert.Equal(t, Second(true, 100), SK(true, 100))
})
}
// TestTernary tests the Ternary function
func TestTernary(t *testing.T) {
t.Run("applies onTrue when predicate is true", func(t *testing.T) {
isPositive := func(n int) bool { return n > 0 }
double := func(n int) int { return n * 2 }
negate := func(n int) int { return -n }
transform := Ternary(isPositive, double, negate)
assert.Equal(t, 10, transform(5))
assert.Equal(t, 20, transform(10))
})
t.Run("applies onFalse when predicate is false", func(t *testing.T) {
isPositive := func(n int) bool { return n > 0 }
double := func(n int) int { return n * 2 }
negate := func(n int) int { return -n }
transform := Ternary(isPositive, double, negate)
assert.Equal(t, 3, transform(-3))
assert.Equal(t, 5, transform(-5))
assert.Equal(t, 0, transform(0))
})
t.Run("works with string classification", func(t *testing.T) {
isPositive := func(n int) bool { return n > 0 }
classify := Ternary(
isPositive,
Constant1[int, string]("positive"),
Constant1[int, string]("non-positive"),
)
assert.Equal(t, "positive", classify(5))
assert.Equal(t, "non-positive", classify(-3))
assert.Equal(t, "non-positive", classify(0))
})
}
// TestRef tests the Ref function
func TestRef(t *testing.T) {
t.Run("creates pointer to int", func(t *testing.T) {
value := 42
ptr := Ref(value)
assert.NotNil(t, ptr)
assert.Equal(t, 42, *ptr)
})
t.Run("creates pointer to string", func(t *testing.T) {
str := "hello"
ptr := Ref(str)
assert.NotNil(t, ptr)
assert.Equal(t, "hello", *ptr)
})
t.Run("creates pointer to struct", func(t *testing.T) {
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
ptr := Ref(p)
assert.NotNil(t, ptr)
assert.Equal(t, "Alice", ptr.Name)
assert.Equal(t, 30, ptr.Age)
})
}
// TestDeref tests the Deref function
func TestDeref(t *testing.T) {
t.Run("dereferences int pointer", func(t *testing.T) {
value := 42
ptr := &value
assert.Equal(t, 42, Deref(ptr))
})
t.Run("dereferences string pointer", func(t *testing.T) {
str := "hello"
ptr := &str
assert.Equal(t, "hello", Deref(ptr))
})
t.Run("round trip with Ref", func(t *testing.T) {
original := "test"
copy := Deref(Ref(original))
assert.Equal(t, original, copy)
})
}
// TestToAny tests the ToAny function
func TestToAny(t *testing.T) {
t.Run("converts int to any", func(t *testing.T) {
value := 42
anyValue := ToAny(value)
assert.Equal(t, any(42), anyValue)
})
t.Run("converts string to any", func(t *testing.T) {
str := "hello"
anyStr := ToAny(str)
assert.Equal(t, any("hello"), anyStr)
})
t.Run("converts bool to any", func(t *testing.T) {
b := true
anyBool := ToAny(b)
assert.Equal(t, any(true), anyBool)
})
}
// TestConstNil tests the ConstNil function
func TestConstNil(t *testing.T) {
t.Run("returns nil int pointer", func(t *testing.T) {
nilInt := ConstNil[int]()
assert.Nil(t, nilInt)
assert.True(t, IsNil(nilInt))
})
t.Run("returns nil string pointer", func(t *testing.T) {
nilString := ConstNil[string]()
assert.Nil(t, nilString)
assert.True(t, IsNil(nilString))
})
t.Run("returns nil struct pointer", func(t *testing.T) {
type Person struct {
Name string
}
nilPerson := ConstNil[Person]()
assert.Nil(t, nilPerson)
})
}
// TestConstTrue tests the ConstTrue constant
func TestConstTrue(t *testing.T) {
t.Run("always returns true", func(t *testing.T) {
assert.True(t, ConstTrue())
assert.True(t, ConstTrue())
})
}
// TestConstFalse tests the ConstFalse constant
func TestConstFalse(t *testing.T) {
t.Run("always returns false", func(t *testing.T) {
assert.False(t, ConstFalse())
assert.False(t, ConstFalse())
})
}
// TestSwitch tests the Switch function
func TestSwitch(t *testing.T) {
type Animal struct {
Type string
Name string
}
getType := func(a Animal) string { return a.Type }
handlers := map[string]func(Animal) string{
"dog": func(a Animal) string { return a.Name + " barks" },
"cat": func(a Animal) string { return a.Name + " meows" },
}
defaultHandler := func(a Animal) string {
return a.Name + " makes a sound"
}
makeSound := Switch(getType, handlers, defaultHandler)
t.Run("applies handler for dog", func(t *testing.T) {
dog := Animal{Type: "dog", Name: "Rex"}
assert.Equal(t, "Rex barks", makeSound(dog))
})
t.Run("applies handler for cat", func(t *testing.T) {
cat := Animal{Type: "cat", Name: "Whiskers"}
assert.Equal(t, "Whiskers meows", makeSound(cat))
})
t.Run("applies default handler for unknown type", func(t *testing.T) {
bird := Animal{Type: "bird", Name: "Tweety"}
assert.Equal(t, "Tweety makes a sound", makeSound(bird))
})
}
// TestPipeAndFlow tests basic Pipe and Flow functions
func TestPipeAndFlow(t *testing.T) {
t.Run("Pipe1 applies function", func(t *testing.T) {
double := func(n int) int { return n * 2 }
result := Pipe1(5, double)
assert.Equal(t, 10, result)
})
t.Run("Pipe3 composes functions left-to-right", func(t *testing.T) {
add1 := func(n int) int { return n + 1 }
double := func(n int) int { return n * 2 }
square := func(n int) int { return n * n }
// (5 + 1) * 2 = 12, then 12 * 12 = 144
result := Pipe3(5, add1, double, square)
assert.Equal(t, 144, result)
})
t.Run("Flow3 creates composed function", func(t *testing.T) {
add1 := func(n int) int { return n + 1 }
double := func(n int) int { return n * 2 }
square := func(n int) int { return n * n }
// Flow3 composes left-to-right like Pipe3
// Flow3(f1, f2, f3)(x) = f3(f2(f1(x)))
// So Flow3(add1, double, square)(5) = square(double(add1(5)))
// = square(double(6)) = square(12) = 144
composed := Flow3(add1, double, square)
result := composed(5)
assert.Equal(t, 144, result)
})
}
// TestCurry tests currying functions
func TestCurry(t *testing.T) {
t.Run("Curry2 curries binary function", func(t *testing.T) {
add := func(a, b int) int { return a + b }
curriedAdd := Curry2(add)
add5 := curriedAdd(5)
assert.Equal(t, 8, add5(3))
assert.Equal(t, 10, add5(5))
})
}