Documentation
¶
Overview ¶
Package iso provides isomorphisms - bidirectional transformations between types without loss of information.
Overview ¶
An Isomorphism (Iso) is an optic that converts elements of type S into elements of type A and back again without any loss of information. Unlike lenses which focus on a part of a structure, isomorphisms represent a complete, reversible transformation between two types.
Isomorphisms are useful for:
- Converting between equivalent representations (e.g., Celsius ↔ Fahrenheit)
- Wrapping and unwrapping newtypes
- Encoding and decoding data formats
- Normalizing data structures
Mathematical Foundation ¶
An Iso[S, A] consists of two functions:
- Get: S → A (convert from S to A)
- ReverseGet: A → S (convert from A back to S)
Isomorphisms must satisfy the round-trip laws:
- ReverseGet(Get(s)) == s (for all s: S)
- Get(ReverseGet(a)) == a (for all a: A)
These laws ensure that the transformation is truly reversible with no information loss.
Basic Usage ¶
Creating an isomorphism between Celsius and Fahrenheit:
celsiusToFahrenheit := iso.MakeIso(
func(c float64) float64 { return c*9/5 + 32 },
func(f float64) float64 { return (f - 32) * 5 / 9 },
)
// Convert Celsius to Fahrenheit
fahrenheit := celsiusToFahrenheit.Get(20.0) // 68.0
// Convert back to Celsius
celsius := celsiusToFahrenheit.ReverseGet(68.0) // 20.0
Identity Isomorphism ¶
The identity isomorphism represents no transformation:
idIso := iso.Id[int]() value := idIso.Get(42) // 42 same := idIso.ReverseGet(42) // 42
Composing Isomorphisms ¶
Isomorphisms can be composed to create more complex transformations:
metersToKm := iso.MakeIso(
func(m float64) float64 { return m / 1000 },
func(km float64) float64 { return km * 1000 },
)
kmToMiles := iso.MakeIso(
func(km float64) float64 { return km * 0.621371 },
func(mi float64) float64 { return mi / 0.621371 },
)
// Compose: meters → kilometers → miles
metersToMiles := F.Pipe1(
metersToKm,
iso.Compose[float64](kmToMiles),
)
miles := metersToMiles.Get(5000) // ~3.11 miles
meters := metersToMiles.ReverseGet(3.11) // ~5000 meters
Reversing Isomorphisms ¶
Any isomorphism can be reversed to swap the direction:
fahrenheitToCelsius := iso.Reverse(celsiusToFahrenheit) celsius := fahrenheitToCelsius.Get(68.0) // 20.0 fahrenheit := fahrenheitToCelsius.ReverseGet(20.0) // 68.0
Modifying Through Isomorphisms ¶
Apply transformations in the target space and convert back:
type Meters float64
type Kilometers float64
mToKm := iso.MakeIso(
func(m Meters) Kilometers { return Kilometers(m / 1000) },
func(km Kilometers) Meters { return Meters(km * 1000) },
)
// Double the distance in kilometers, result in meters
doubled := iso.Modify[Meters](func(km Kilometers) Kilometers {
return km * 2
})(mToKm)(Meters(5000))
// Result: 10000 meters
Wrapping and Unwrapping ¶
Convenient functions for working with newtypes:
type UserId int
type User struct {
id UserId
}
userIdIso := iso.MakeIso(
func(id UserId) int { return int(id) },
func(i int) UserId { return UserId(i) },
)
// Unwrap (Get)
rawId := iso.Unwrap[int](UserId(42))(userIdIso) // 42
// Also available as: iso.To[int](UserId(42))(userIdIso)
// Wrap (ReverseGet)
userId := iso.Wrap[UserId](42)(userIdIso) // UserId(42)
// Also available as: iso.From[UserId](42)(userIdIso)
Bidirectional Mapping ¶
Transform both directions of an isomorphism:
type Celsius float64
type Kelvin float64
celsiusIso := iso.Id[Celsius]()
// Create isomorphism to Kelvin
celsiusToKelvin := F.Pipe1(
celsiusIso,
iso.IMap(
func(c Celsius) Kelvin { return Kelvin(c + 273.15) },
func(k Kelvin) Celsius { return Celsius(k - 273.15) },
),
)
kelvin := celsiusToKelvin.Get(Celsius(20)) // 293.15 K
celsius := celsiusToKelvin.ReverseGet(Kelvin(293.15)) // 20°C
Real-World Example: Data Encoding ¶
type JSON string
type User struct {
Name string
Age int
}
userJsonIso := iso.MakeIso(
func(u User) JSON {
data, _ := json.Marshal(u)
return JSON(data)
},
func(j JSON) User {
var u User
json.Unmarshal([]byte(j), &u)
return u
},
)
user := User{Name: "Alice", Age: 30}
// Encode to JSON
jsonData := userJsonIso.Get(user)
// `{"Name":"Alice","Age":30}`
// Decode from JSON
decoded := userJsonIso.ReverseGet(jsonData)
// User{Name: "Alice", Age: 30}
Real-World Example: Unit Conversions ¶
type Distance float64
type DistanceUnit int
const (
Meters DistanceUnit = iota
Kilometers
Miles
)
type MeasuredDistance struct {
Value Distance
Unit DistanceUnit
}
// Normalize all distances to meters
normalizeIso := iso.MakeIso(
func(md MeasuredDistance) Distance {
switch md.Unit {
case Kilometers:
return md.Value * 1000
case Miles:
return md.Value * 1609.34
default:
return md.Value
}
},
func(d Distance) MeasuredDistance {
return MeasuredDistance{Value: d, Unit: Meters}
},
)
// Convert 5 km to meters
meters := normalizeIso.Get(MeasuredDistance{
Value: 5,
Unit: Kilometers,
}) // 5000 meters
// Convert back (always to meters)
measured := normalizeIso.ReverseGet(5000)
// MeasuredDistance{Value: 5000, Unit: Meters}
Real-World Example: Newtype Pattern ¶
type Email string
type ValidatedEmail struct {
value Email
}
func validateEmail(s string) (ValidatedEmail, error) {
if !strings.Contains(s, "@") {
return ValidatedEmail{}, errors.New("invalid email")
}
return ValidatedEmail{value: Email(s)}, nil
}
// Note: This iso assumes validation has already occurred
emailIso := iso.MakeIso(
func(ve ValidatedEmail) Email { return ve.value },
func(e Email) ValidatedEmail { return ValidatedEmail{value: e} },
)
validated := ValidatedEmail{value: "user@example.com"}
// Extract raw email
raw := emailIso.Get(validated) // "user@example.com"
// Wrap back (assumes valid)
wrapped := emailIso.ReverseGet(Email("admin@example.com"))
Isomorphisms vs Lenses ¶
While both are optics, they serve different purposes:
**Isomorphisms:**
- Represent complete, reversible transformations
- No information loss
- Both directions are equally important
- Example: Celsius ↔ Fahrenheit
**Lenses:**
- Focus on a part of a larger structure
- Information loss when setting (other fields unchanged)
- Asymmetric (get vs set)
- Example: Person → Name
Performance Considerations ¶
Isomorphisms are lightweight and have minimal overhead:
- No allocations for the iso structure itself
- Performance depends on the Get and ReverseGet functions
- Composition creates new function closures but is still efficient
- Consider caching results if transformations are expensive
Type Safety ¶
Isomorphisms are fully type-safe:
- The compiler ensures Get and ReverseGet have compatible types
- Composition maintains type relationships
- No runtime type assertions needed
Function Reference ¶
Core Functions:
- MakeIso: Create an isomorphism from two functions
- Id: Create an identity isomorphism
- Compose: Compose two isomorphisms
- Reverse: Reverse the direction of an isomorphism
Transformation:
- Modify: Apply a transformation in the target space
- IMap: Bidirectionally map an isomorphism
Convenience Functions:
- Unwrap/To: Extract the target value (Get)
- Wrap/From: Wrap into the source value (ReverseGet)
Useful Iso Implementations ¶
The package provides several ready-to-use isomorphisms for common transformations:
**String and Byte Conversions:**
- UTF8String: []byte ↔ string (UTF-8 encoding)
- Lines: []string ↔ string (newline-separated text)
**Time Conversions:**
- UnixMilli: int64 ↔ time.Time (Unix millisecond timestamps)
**Numeric Operations:**
- Add[T]: T ↔ T (shift by constant addition)
- Sub[T]: T ↔ T (shift by constant subtraction)
**Collection Operations:**
- ReverseArray[A]: []A ↔ []A (reverse slice order, self-inverse)
- Head[A]: A ↔ NonEmptyArray[A] (singleton array conversion)
**Pair and Either Operations:**
- SwapPair[A, B]: Pair[A, B] ↔ Pair[B, A] (swap pair elements, self-inverse)
- SwapEither[E, A]: Either[E, A] ↔ Either[A, E] (swap Either types, self-inverse)
**Option Conversions (optics/iso/option):**
- FromZero[T]: T ↔ Option[T] (zero value ↔ None, non-zero ↔ Some)
**Lens Conversions (optics/iso/lens):**
- IsoAsLens: Convert Iso[S, A] to Lens[S, A]
- IsoAsLensRef: Convert Iso[*S, A] to Lens[*S, A]
Example usage of built-in isomorphisms:
// String/byte conversion
utf8 := UTF8String()
str := utf8.Get([]byte("hello")) // "hello"
// Time conversion
unixTime := UnixMilli()
t := unixTime.Get(1609459200000) // 2021-01-01 00:00:00 UTC
// Numeric shift
addTen := Add(10)
result := addTen.Get(5) // 15
// Array reversal
reverse := ReverseArray[int]()
reversed := reverse.Get([]int{1, 2, 3}) // []int{3, 2, 1}
// Pair swap
swap := SwapPair[string, int]()
swapped := swap.Get(pair.MakePair("a", 1)) // Pair[int, string](1, "a")
// Option conversion
optIso := option.FromZero[int]()
opt := optIso.Get(0) // None
opt = optIso.Get(42) // Some(42)
Related Packages ¶
- github.com/IBM/fp-go/v2/optics/lens: Lenses for focusing on parts of structures
- github.com/IBM/fp-go/v2/optics/prism: Prisms for sum types
- github.com/IBM/fp-go/v2/optics/optional: Optional optics
- github.com/IBM/fp-go/v2/function: Function composition utilities
- github.com/IBM/fp-go/v2/endomorphism: Endomorphisms (A → A functions)
Package iso provides isomorphisms - bidirectional transformations between types without loss of information.
Index ¶
- func Compose[S, A, B any](ab Iso[A, B]) func(Iso[S, A]) Iso[S, B]
- func From[S, A any](a A) func(Iso[S, A]) S
- func IMap[S, A, B any](ab func(A) B, ba func(B) A) func(Iso[S, A]) Iso[S, B]
- func Modify[S any, FCT ~func(A) A, A any](f FCT) func(Iso[S, A]) EM.Endomorphism[S]
- func To[A, S any](s S) func(Iso[S, A]) A
- func Unwrap[A, S any](s S) func(Iso[S, A]) A
- func Wrap[S, A any](a A) func(Iso[S, A]) S
- type Either
- type Iso
- func Add[T Number](n T) Iso[T, T]
- func Head[A any]() Iso[A, NonEmptyArray[A]]
- func Id[S any]() Iso[S, S]
- func Lines() Iso[[]string, string]
- func MakeIso[S, A any](get func(S) A, reverse func(A) S) Iso[S, A]
- func Reverse[S, A any](sa Iso[S, A]) Iso[A, S]
- func ReverseArray[A any]() Iso[[]A, []A]
- func Sub[T Number](n T) Iso[T, T]
- func SwapEither[E, A any]() Iso[Either[E, A], Either[A, E]]
- func SwapPair[A, B any]() Iso[Pair[A, B], Pair[B, A]]
- func UTF8String() Iso[[]byte, string]
- func UnixMilli() Iso[int64, time.Time]
- type NonEmptyArray
- type Number
- type Pair
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Compose ¶
Compose combines two isomorphisms to create a new isomorphism. Given Iso[S, A] and Iso[A, B], creates Iso[S, B]. The resulting isomorphism first applies the outer iso (S → A), then the inner iso (A → B).
Type Parameters:
- S: The outermost source type
- A: The intermediate type
- B: The innermost target type
Parameters:
- ab: The inner isomorphism (A → B)
Returns:
- A function that takes the outer isomorphism (S → A) and returns the composed isomorphism (S → B)
Example:
metersToKm := MakeIso(
func(m float64) float64 { return m / 1000 },
func(km float64) float64 { return km * 1000 },
)
kmToMiles := MakeIso(
func(km float64) float64 { return km * 0.621371 },
func(mi float64) float64 { return mi / 0.621371 },
)
// Compose: meters → kilometers → miles
metersToMiles := F.Pipe1(metersToKm, Compose[float64](kmToMiles))
miles := metersToMiles.Get(5000) // ~3.11 miles
meters := metersToMiles.ReverseGet(3.11) // ~5000 meters
func From ¶
From wraps a target value into a source value using an isomorphism. This is an alias for Wrap, provided for semantic clarity when the direction of conversion is important.
Type Parameters:
- S: The source type to convert from
- A: The target type
Parameters:
- a: The target value to convert
Returns:
- A function that takes an Iso[S, A] and returns the converted value of type S
Example:
type Email string
type ValidatedEmail struct{ value Email }
emailIso := MakeIso(
func(ve ValidatedEmail) Email { return ve.value },
func(e Email) ValidatedEmail { return ValidatedEmail{value: e} },
)
// Convert from Email
validated := From[ValidatedEmail](Email("admin@example.com"))(emailIso)
// ValidatedEmail{value: "admin@example.com"}
func IMap ¶
IMap bidirectionally maps the target type of an isomorphism. Given Iso[S, A] and functions A → B and B → A, creates Iso[S, B]. This allows you to transform both directions of an isomorphism.
Type Parameters:
- S: The source type (unchanged)
- A: The original target type
- B: The new target type
Parameters:
- ab: Function to map from A to B
- ba: Function to map from B to A (inverse of ab)
Returns:
- A function that transforms Iso[S, A] to Iso[S, B]
Example:
type Celsius float64
type Kelvin float64
celsiusIso := Id[Celsius]()
// Create isomorphism to Kelvin
celsiusToKelvin := F.Pipe1(
celsiusIso,
IMap(
func(c Celsius) Kelvin { return Kelvin(c + 273.15) },
func(k Kelvin) Celsius { return Celsius(k - 273.15) },
),
)
kelvin := celsiusToKelvin.Get(Celsius(20)) // 293.15 K
celsius := celsiusToKelvin.ReverseGet(Kelvin(293.15)) // 20°C
Note: The functions ab and ba must be inverses of each other to maintain the isomorphism laws.
func Modify ¶
func Modify[S any, FCT ~func(A) A, A any](f FCT) func(Iso[S, A]) EM.Endomorphism[S]
Modify creates a function that applies a transformation in the target space. It converts the source value to the target type, applies the transformation, then converts back to the source type.
Type Parameters:
- S: The source type
- FCT: The transformation function type (A → A)
- A: The target type
Parameters:
- f: The transformation function to apply in the target space
Returns:
- A function that takes an Iso[S, A] and returns an endomorphism (S → S)
Example:
type Meters float64
type Kilometers float64
mToKm := MakeIso(
func(m Meters) Kilometers { return Kilometers(m / 1000) },
func(km Kilometers) Meters { return Meters(km * 1000) },
)
// Double the distance in kilometers, result in meters
doubled := Modify[Meters](func(km Kilometers) Kilometers {
return km * 2
})(mToKm)(Meters(5000))
// Result: Meters(10000)
func To ¶
To extracts the target value from a source value using an isomorphism. This is an alias for Unwrap, provided for semantic clarity when the direction of conversion is important.
Type Parameters:
- A: The target type to convert to
- S: The source type
Parameters:
- s: The source value to convert
Returns:
- A function that takes an Iso[S, A] and returns the converted value of type A
Example:
type Email string
type ValidatedEmail struct{ value Email }
emailIso := MakeIso(
func(ve ValidatedEmail) Email { return ve.value },
func(e Email) ValidatedEmail { return ValidatedEmail{value: e} },
)
// Convert to Email
email := To[Email](ValidatedEmail{value: "user@example.com"})(emailIso)
// "user@example.com"
func Unwrap ¶
Unwrap extracts the target value from a source value using an isomorphism. This is a convenience function that applies the Get function of the isomorphism.
Type Parameters:
- A: The target type to extract
- S: The source type
Parameters:
- s: The source value to unwrap
Returns:
- A function that takes an Iso[S, A] and returns the unwrapped value of type A
Example:
type UserId int
userIdIso := MakeIso(
func(id UserId) int { return int(id) },
func(i int) UserId { return UserId(i) },
)
rawId := Unwrap[int](UserId(42))(userIdIso) // 42
Note: This function is also available as To for semantic clarity.
func Wrap ¶
Wrap wraps a target value into a source value using an isomorphism. This is a convenience function that applies the ReverseGet function of the isomorphism.
Type Parameters:
- S: The source type to wrap into
- A: The target type
Parameters:
- a: The target value to wrap
Returns:
- A function that takes an Iso[S, A] and returns the wrapped value of type S
Example:
type UserId int
userIdIso := MakeIso(
func(id UserId) int { return int(id) },
func(i int) UserId { return UserId(i) },
)
userId := Wrap[UserId](42)(userIdIso) // UserId(42)
Note: This function is also available as From for semantic clarity.
Types ¶
type Iso ¶
type Iso[S, A any] struct { // Get converts a value from the source type S to the target type A. Get func(s S) A // ReverseGet converts a value from the target type A back to the source type S. // This is the inverse of Get. ReverseGet func(a A) S }
Iso represents an isomorphism between types S and A. An isomorphism is a bidirectional transformation that converts between two types without any loss of information. It consists of two functions that are inverses of each other.
Type Parameters:
- S: The source type
- A: The target type
Fields:
- Get: Converts from S to A
- ReverseGet: Converts from A back to S
Laws: An Iso must satisfy the round-trip laws:
- ReverseGet(Get(s)) == s for all s: S
- Get(ReverseGet(a)) == a for all a: A
Example:
// Isomorphism between Celsius and Fahrenheit
tempIso := Iso[float64, float64]{
Get: func(c float64) float64 { return c*9/5 + 32 },
ReverseGet: func(f float64) float64 { return (f - 32) * 5 / 9 },
}
fahrenheit := tempIso.Get(20.0) // 68.0
celsius := tempIso.ReverseGet(68.0) // 20.0
func Add ¶
Add creates an isomorphism that adds a constant value to a number. This isomorphism provides bidirectional conversion by adding a value in one direction and subtracting it in the reverse direction, effectively shifting numbers by a constant offset.
Type Parameters:
- T: A numeric type (integer, float, or complex number)
Parameters:
- n: The constant value to add in the Get direction (and subtract in ReverseGet)
Returns:
- An Iso[T, T] where:
- Get: Adds n to the input value (x + n)
- ReverseGet: Subtracts n from the input value (x - n)
Behavior:
- Get direction: Shifts the value up by n
- ReverseGet direction: Shifts the value down by n (inverse operation)
Example:
// Create an isomorphism that adds 10 addTen := Add(10) // Add 10 to a value result := addTen.Get(5) // 15 // Subtract 10 from a value (reverse) original := addTen.ReverseGet(15) // 5 // Round-trip conversion value := 42 roundTrip := addTen.ReverseGet(addTen.Get(value)) // 42
Use cases:
- Converting between different numeric scales or offsets
- Temperature conversions (e.g., Celsius offset adjustments)
- Index transformations (e.g., 0-based to 1-based indexing)
- Coordinate system translations
- Time zone offset adjustments (when working with numeric timestamps)
Example with different numeric types:
// Integer addition intIso := Add(5) intResult := intIso.Get(10) // 15 // Float addition floatIso := Add(2.5) floatResult := floatIso.Get(7.5) // 10.0 // Complex number addition complexIso := Add(complex(1, 2)) complexResult := complexIso.Get(complex(3, 4)) // (4+6i)
Example with coordinate translation:
// Translate x-coordinates by 100 units translateX := Add(100) newX := translateX.Get(50) // 150 originalX := translateX.ReverseGet(150) // 50
Note: This isomorphism satisfies the round-trip laws:
- ReverseGet(Get(x)) == x (because (x + n) - n == x)
- Get(ReverseGet(x)) == x (because (x - n) + n == x)
func Head ¶
Head creates an isomorphism between a single element and a non-empty array containing that element. This isomorphism provides bidirectional conversion between a value and a singleton non-empty array, where the value becomes the head (first element) of the array.
Type Parameters:
- A: The type of the element
Returns:
- An Iso[A, NonEmptyArray[A]] where:
- Get: Wraps a single element into a non-empty array (singleton array)
- ReverseGet: Extracts the head (first element) from a non-empty array
Behavior:
- Get direction: Creates a non-empty array with the single element as its head
- ReverseGet direction: Extracts the first element from a non-empty array
- The non-empty array type guarantees at least one element exists
Example:
iso := Head[int]() // Wrap a value into a non-empty array value := 42 arr := iso.Get(value) // NonEmptyArray[int] with head=42 // Extract the head from a non-empty array head := iso.ReverseGet(arr) // 42 // Round-trip conversion original := 100 roundTrip := iso.ReverseGet(iso.Get(original)) // 100
Use cases:
- Converting between single values and non-empty collections
- Working with APIs that require non-empty arrays
- Ensuring type safety when a value must be in a non-empty context
- Composing with other optics that work on non-empty arrays
- Lifting single values into collection contexts
Example with strings:
iso := Head[string]() name := "Alice" arr := iso.Get(name) // NonEmptyArray[string] with head="Alice" extracted := iso.ReverseGet(arr) // "Alice"
Example with structs:
type User struct {
Name string
Age int
}
iso := Head[User]()
user := User{"Bob", 30}
arr := iso.Get(user) // NonEmptyArray[User] with head=User{"Bob", 30}
Example with composition:
// Lift a value into a non-empty array, then process it
iso := Head[int]()
value := 5
result := F.Pipe2(
value,
iso.Get, // Wrap in non-empty array
nonempty.Map(N.Mul(2)), // Map over array
)
// result: NonEmptyArray[int] with head=10
Example with lens usage:
// Use Head to focus on a single value as a non-empty array
type Config struct {
DefaultValue int
}
headIso := Head[int]()
config := Config{DefaultValue: 42}
// Convert default value to non-empty array for processing
arr := headIso.Get(config.DefaultValue)
Note: This isomorphism satisfies the round-trip laws:
- ReverseGet(Get(x)) == x (extracting head from singleton returns original)
- Get(ReverseGet(arr)) creates a singleton with the same head
Important: When using ReverseGet on a non-empty array with multiple elements, only the head (first element) is extracted. Other elements are discarded.
Example with multi-element array:
iso := Head[int]() // If you have a non-empty array with multiple elements arr := nonempty.From(1, 2, 3, 4, 5) head := iso.ReverseGet(arr) // 1 (only the head is extracted)
func Id ¶
Id returns an identity isomorphism that performs no transformation. Both Get and ReverseGet are the identity function.
Type Parameters:
- S: The type for both source and target
Returns:
- An Iso[S, S] where Get and ReverseGet are both identity functions
Example:
idIso := Id[int]() value := idIso.Get(42) // 42 same := idIso.ReverseGet(42) // 42
Use cases:
- As a starting point for isomorphism composition
- When you need an isomorphism but don't want to transform the value
- In generic code that requires an isomorphism parameter
func Lines ¶
Lines creates an isomorphism between a slice of strings and a single string with newline-separated lines. This is useful for working with multi-line text where you need to convert between a single string and individual lines.
Returns:
- An Iso[[]string, string] where:
- Get: Joins string slice with newline characters ("\n")
- ReverseGet: Splits string by newline characters into a slice
Behavior:
- Get direction: Joins each string in the slice with "\n" separator
- ReverseGet direction: Splits the string at each "\n" into a slice
Example:
iso := Lines()
// Convert lines to single string
lines := []string{"line1", "line2", "line3"}
text := iso.Get(lines) // "line1\nline2\nline3"
// Convert string to lines
text := "hello\nworld"
lines := iso.ReverseGet(text) // []string{"hello", "world"}
// Round-trip conversion
original := []string{"a", "b", "c"}
result := iso.ReverseGet(iso.Get(original)) // []string{"a", "b", "c"}
Use cases:
- Processing multi-line text files
- Converting between text editor representations (array of lines vs single string)
- Working with configuration files that have line-based structure
- Parsing or generating multi-line output
Note: Empty strings in the slice will result in consecutive newlines in the output. Splitting a string with trailing newlines will include an empty string at the end.
Example with edge cases:
iso := Lines()
lines := []string{"a", "", "b"}
text := iso.Get(lines) // "a\n\nb"
result := iso.ReverseGet(text) // []string{"a", "", "b"}
text := "a\nb\n"
lines := iso.ReverseGet(text) // []string{"a", "b", ""}
func MakeIso ¶
MakeIso constructs an isomorphism from two functions. The functions should be inverses of each other to satisfy the isomorphism laws.
Type Parameters:
- S: The source type
- A: The target type
Parameters:
- get: Function to convert from S to A
- reverse: Function to convert from A to S (inverse of get)
Returns:
- An Iso[S, A] that uses the provided functions
Example:
// Create an isomorphism between string and []byte
stringBytesIso := MakeIso(
func(s string) []byte { return []byte(s) },
func(b []byte) string { return string(b) },
)
bytes := stringBytesIso.Get("hello") // []byte("hello")
str := stringBytesIso.ReverseGet([]byte("hi")) // "hi"
func Reverse ¶
Reverse swaps the direction of an isomorphism. Given Iso[S, A], creates Iso[A, S] where Get and ReverseGet are swapped.
Type Parameters:
- S: The original source type (becomes target)
- A: The original target type (becomes source)
Parameters:
- sa: The isomorphism to reverse
Returns:
- An Iso[A, S] with Get and ReverseGet swapped
Example:
celsiusToFahrenheit := MakeIso(
func(c float64) float64 { return c*9/5 + 32 },
func(f float64) float64 { return (f - 32) * 5 / 9 },
)
// Reverse to get Fahrenheit to Celsius
fahrenheitToCelsius := Reverse(celsiusToFahrenheit)
celsius := fahrenheitToCelsius.Get(68.0) // 20.0
fahrenheit := fahrenheitToCelsius.ReverseGet(20.0) // 68.0
func ReverseArray ¶
ReverseArray creates an isomorphism that reverses the order of elements in a slice. This isomorphism is self-inverse, meaning applying it twice returns the original slice. It provides bidirectional conversion where both Get and ReverseGet perform the same reversal operation.
Type Parameters:
- A: The type of elements in the slice
Returns:
- An Iso[[]A, []A] where:
- Get: Reverses the slice order
- ReverseGet: Reverses the slice order (same as Get, since reverse is self-inverse)
Behavior:
- Get direction: Returns a new slice with elements in reverse order
- ReverseGet direction: Returns a new slice with elements in reverse order
- Both directions create new slices without modifying the original
- Self-inverse property: Get(Get(x)) == x and ReverseGet(ReverseGet(x)) == x
Example:
iso := ReverseArray[int]()
// Reverse a slice
numbers := []int{1, 2, 3, 4, 5}
reversed := iso.Get(numbers) // []int{5, 4, 3, 2, 1}
// Reverse back (using ReverseGet)
original := iso.ReverseGet(reversed) // []int{1, 2, 3, 4, 5}
// Round-trip conversion
data := []int{10, 20, 30}
roundTrip := iso.ReverseGet(iso.Get(data)) // []int{10, 20, 30}
Use cases:
- Processing data in reverse order within an isomorphism pipeline
- Implementing reversible transformations on collections
- Converting between forward and backward iteration orders
- Working with optics that need to reverse array order
- Composing with other isomorphisms for complex transformations
Example with strings:
iso := ReverseArray[string]()
words := []string{"hello", "world", "foo"}
reversed := iso.Get(words) // []string{"foo", "world", "hello"}
Example with composition:
// Reverse, then map, then reverse back
iso := ReverseArray[int]()
numbers := []int{1, 2, 3, 4, 5}
result := F.Pipe3(
numbers,
iso.Get, // Reverse
array.Map(N.Mul(2)), // Map
iso.ReverseGet, // Reverse back
)
// result: []int{2, 4, 6, 8, 10}
Example with lens composition:
// Use ReverseArray as part of a lens pipeline
type Container struct {
Items []int
}
// Create an isomorphism that reverses items in a container
reverseItems := ReverseArray[int]()
Note: This isomorphism is self-inverse, which means:
- Get and ReverseGet perform the same operation
- Applying the isomorphism twice returns the original value
- ReverseArray().Get == ReverseArray().ReverseGet
Performance:
- Time complexity: O(n) where n is the length of the slice
- Space complexity: O(n) for the new slice
- Both Get and ReverseGet have the same performance characteristics
func Sub ¶
Sub creates an isomorphism that subtracts a constant value from a number. This isomorphism provides bidirectional conversion by subtracting a value in one direction and adding it in the reverse direction, effectively shifting numbers by a constant offset in the opposite direction of Add.
Type Parameters:
- T: A numeric type (integer, float, or complex number)
Parameters:
- n: The constant value to subtract in the Get direction (and add in ReverseGet)
Returns:
- An Iso[T, T] where:
- Get: Subtracts n from the input value (x - n)
- ReverseGet: Adds n to the input value (x + n)
Behavior:
- Get direction: Shifts the value down by n
- ReverseGet direction: Shifts the value up by n (inverse operation)
Example:
// Create an isomorphism that subtracts 10 subTen := Sub(10) // Subtract 10 from a value result := subTen.Get(15) // 5 // Add 10 to a value (reverse) original := subTen.ReverseGet(5) // 15 // Round-trip conversion value := 42 roundTrip := subTen.ReverseGet(subTen.Get(value)) // 42
Use cases:
- Converting between different numeric scales or offsets
- Discount or reduction calculations
- Index transformations (e.g., 1-based to 0-based indexing)
- Coordinate system translations in the negative direction
- Time calculations (e.g., going back in time by a fixed amount)
Example with different numeric types:
// Integer subtraction intIso := Sub(5) intResult := intIso.Get(10) // 5 // Float subtraction floatIso := Sub(2.5) floatResult := floatIso.Get(10.0) // 7.5 // Complex number subtraction complexIso := Sub(complex(1, 2)) complexResult := complexIso.Get(complex(4, 6)) // (3+4i)
Example with discount calculation:
// Apply a discount of 10 units discount := Sub(10) discountedPrice := discount.Get(50) // 40 originalPrice := discount.ReverseGet(40) // 50
Relationship with Add: Sub(n) is equivalent to Add(-n). The following are equivalent:
sub5 := Sub(5) addNeg5 := Add(-5) // Both produce the same results: sub5.Get(10) // 5 addNeg5.Get(10) // 5
Note: This isomorphism satisfies the round-trip laws:
- ReverseGet(Get(x)) == x (because (x - n) + n == x)
- Get(ReverseGet(x)) == x (because (x + n) - n == x)
func SwapEither ¶
SwapEither creates an isomorphism that swaps the type parameters of an Either. This isomorphism provides bidirectional conversion between Either[E, A] and Either[A, E], effectively exchanging the Left and Right type positions while preserving which side the value is on.
Type Parameters:
- E: The type of the Left value in the source Either (becomes Right in target)
- A: The type of the Right value in the source Either (becomes Left in target)
Returns:
- An Iso[Either[E, A], Either[A, E]] where:
- Get: Swaps Either[E, A] to Either[A, E] (Left[E] becomes Right[E], Right[A] becomes Left[A])
- ReverseGet: Swaps Either[A, E] back to Either[E, A] (inverse operation)
Behavior:
- Get direction: Transforms Either[E, A] to Either[A, E]
- Left[E] becomes Right[E]
- Right[A] becomes Left[A]
- ReverseGet direction: Transforms Either[A, E] to Either[E, A] (inverse)
- Right[E] becomes Left[E]
- Left[A] becomes Right[A]
Example:
// Create a swap isomorphism for Either[string, int]
swapIso := SwapEither[string, int]()
// Swap a Left value
leftVal := either.Left[int]("error") // Either[string, int] with Left
swapped := swapIso.Get(leftVal) // Either[int, string] with Right("error")
// Swap a Right value
rightVal := either.Right[string](42) // Either[string, int] with Right
swapped2 := swapIso.Get(rightVal) // Either[int, string] with Left(42)
// Round-trip conversion
original := either.Left[int]("test")
roundTrip := swapIso.ReverseGet(swapIso.Get(original)) // Left("test")
Use cases:
- Converting between different Either conventions (error-left vs error-right)
- Adapting between APIs with different Either type parameter orders
- Normalizing error handling patterns
- Working with libraries that use opposite Either conventions
- Transforming result types to match expected signatures
Example with error handling:
// Convert from Either[Error, Value] to Either[Value, Error]
swapError := SwapEither[error, string]()
result := either.Right[error]("success") // Either[error, string]
swapped := swapError.Get(result) // Either[string, error] with Left("success")
Example with validation:
// Swap validation result types
swapValidation := SwapEither[[]string, User]()
// Valid user
valid := either.Right[[]string](User{Name: "Alice"})
swapped := swapValidation.Get(valid) // Either[User, []string] with Left(User)
// Invalid user
invalid := either.Left[User]([]string{"error1", "error2"})
swapped2 := swapValidation.Get(invalid) // Either[User, []string] with Right(errors)
Example demonstrating self-inverse property:
swapIso := SwapEither[string, int]()
value := either.Left[int]("error")
// Apply swap twice to get back to original
result := F.Pipe2(value, swapIso.Get, swapIso.ReverseGet) // Left("error")
Note: This isomorphism satisfies the round-trip laws:
- ReverseGet(Get(either)) == either (swapping twice returns to original)
- Get(ReverseGet(either)) == either (swapping twice returns to original)
Note: SwapEither is self-inverse, meaning applying it twice returns the original value. The swap operation preserves which side (Left/Right) the value is on, only changing the type parameter positions.
func SwapPair ¶
SwapPair creates an isomorphism that swaps the elements of a Pair. This isomorphism provides bidirectional conversion between Pair[A, B] and Pair[B, A], effectively exchanging the first and second elements of the pair.
Type Parameters:
- A: The type of the first element in the source pair (becomes second in target)
- B: The type of the second element in the source pair (becomes first in target)
Returns:
- An Iso[Pair[A, B], Pair[B, A]] where:
- Get: Swaps the pair elements from (A, B) to (B, A)
- ReverseGet: Swaps the pair elements from (B, A) back to (A, B)
Behavior:
- Get direction: Transforms Pair[A, B] to Pair[B, A]
- ReverseGet direction: Transforms Pair[B, A] to Pair[A, B] (inverse operation)
Example:
// Create a swap isomorphism for pairs of string and int
swapIso := SwapPair[string, int]()
// Swap a pair
original := pair.MakePair("hello", 42) // Pair[string, int]
swapped := swapIso.Get(original) // Pair[int, string] = (42, "hello")
// Swap back
restored := swapIso.ReverseGet(swapped) // Pair[string, int] = ("hello", 42)
// Round-trip conversion
p := pair.MakePair(1, "a")
roundTrip := swapIso.ReverseGet(swapIso.Get(p)) // (1, "a")
Use cases:
- Reordering pair elements for API compatibility
- Converting between different pair representations
- Normalizing data structures with swapped element order
- Working with functions that expect arguments in different order
- Adapting between coordinate systems (e.g., (x, y) to (y, x))
Example with coordinates:
// Swap between (x, y) and (y, x) coordinates swapCoords := SwapPair[float64, float64]() point := pair.MakePair(3.0, 4.0) // (x=3, y=4) swapped := swapCoords.Get(point) // (y=4, x=3)
Example with heterogeneous types:
// Swap between (name, age) and (age, name)
swapPerson := SwapPair[string, int]()
person := pair.MakePair("Alice", 30)
swapped := swapPerson.Get(person) // (30, "Alice")
Example with function composition:
// Use with other isomorphisms swapIso := SwapPair[int, string]() p := pair.MakePair(1, "test") // Apply swap twice to get back to original result := F.Pipe2(p, swapIso.Get, swapIso.ReverseGet) // (1, "test")
Note: This isomorphism satisfies the round-trip laws:
- ReverseGet(Get(pair)) == pair (swapping twice returns to original)
- Get(ReverseGet(pair)) == pair (swapping twice returns to original)
Note: SwapPair is self-inverse, meaning applying it twice returns the original value. This makes it particularly useful for symmetric transformations.
func UTF8String ¶
UTF8String creates an isomorphism between byte slices and UTF-8 strings. This isomorphism provides bidirectional conversion between []byte and string, treating the byte slice as UTF-8 encoded text.
Returns:
- An Iso[[]byte, string] where:
- Get: Converts []byte to string using UTF-8 encoding
- ReverseGet: Converts string to []byte using UTF-8 encoding
Behavior:
- Get direction: Interprets the byte slice as UTF-8 and returns the corresponding string
- ReverseGet direction: Encodes the string as UTF-8 bytes
Example:
iso := UTF8String()
// Convert bytes to string
str := iso.Get([]byte("hello")) // "hello"
// Convert string to bytes
bytes := iso.ReverseGet("world") // []byte("world")
// Round-trip conversion
original := []byte("test")
result := iso.ReverseGet(iso.Get(original)) // []byte("test")
Use cases:
- Converting between string and byte representations
- Working with APIs that use different text representations
- File I/O operations where you need to switch between strings and bytes
- Network protocols that work with byte streams
Note: This isomorphism assumes valid UTF-8 encoding. Invalid UTF-8 sequences in the byte slice will be handled according to Go's string conversion rules (typically replaced with the Unicode replacement character U+FFFD).
func UnixMilli ¶
UnixMilli creates an isomorphism between Unix millisecond timestamps and time.Time values. This isomorphism provides bidirectional conversion between int64 milliseconds since the Unix epoch (January 1, 1970 UTC) and Go's time.Time type.
Returns:
- An Iso[int64, time.Time] where:
- Get: Converts Unix milliseconds (int64) to time.Time
- ReverseGet: Converts time.Time to Unix milliseconds (int64)
Behavior:
- Get direction: Creates a time.Time from milliseconds since Unix epoch
- ReverseGet direction: Extracts milliseconds since Unix epoch from time.Time
Example:
iso := UnixMilli() // Convert milliseconds to time.Time millis := int64(1609459200000) // 2021-01-01 00:00:00 UTC t := iso.Get(millis) // Convert time.Time to milliseconds now := time.Now() millis := iso.ReverseGet(now) // Round-trip conversion original := int64(1234567890000) result := iso.ReverseGet(iso.Get(original)) // 1234567890000
Use cases:
- Working with APIs that use Unix millisecond timestamps (e.g., JavaScript Date.now())
- Database storage where timestamps are stored as integers
- JSON serialization/deserialization of timestamps
- Converting between different time representations in distributed systems
Precision notes:
- Millisecond precision is maintained in both directions
- Sub-millisecond precision in time.Time is lost when converting to int64
- The conversion is timezone-aware (time.Time includes location information)
Example with precision:
iso := UnixMilli() t := time.Date(2021, 1, 1, 12, 30, 45, 123456789, time.UTC) millis := iso.ReverseGet(t) // Nanoseconds are truncated to milliseconds restored := iso.Get(millis) // Nanoseconds will be 123000000
Note: This isomorphism uses UTC for the time.Time values. If you need to preserve timezone information, consider storing it separately or using a different representation.
func (Iso[S, T]) Format ¶
Format implements fmt.Formatter for Iso. Supports all standard format verbs:
- %s, %v, %+v: uses String() representation
- %#v: uses GoString() representation
- %q: quoted String() representation
- other verbs: uses String() representation
Example:
tempIso := iso.MakeIso(...)
fmt.Printf("%s", tempIso) // "Iso"
fmt.Printf("%v", tempIso) // "Iso"
fmt.Printf("%#v", tempIso) // "iso.Iso[Celsius, Fahrenheit]"
func (Iso[S, T]) GoString ¶
GoString implements fmt.GoStringer for Iso. Returns a Go-syntax representation of the Iso value.
Example:
tempIso := iso.MakeIso(...) tempIso.GoString() // "iso.Iso[Celsius, Fahrenheit]"
func (Iso[S, T]) LogValue ¶
LogValue implements slog.LogValuer for Iso. Returns a slog.Value that represents the Iso for structured logging. Logs the type information as a string value.
Example:
logger := slog.Default()
tempIso := iso.MakeIso(...)
logger.Info("using iso", "iso", tempIso)
// Logs: {"msg":"using iso","iso":"Iso"}
type NonEmptyArray ¶
type NonEmptyArray[A any] = nonempty.NonEmptyArray[A]
NonEmptyArray represents an array that is guaranteed to have at least one element.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package lens provides conversions from isomorphisms to lenses.
|
Package lens provides conversions from isomorphisms to lenses. |
|
Package option provides isomorphisms for working with Option types.
|
Package option provides isomorphisms for working with Option types. |