1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-08-10 22:31:32 +02:00

fix: add optics and consistent copyright

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2023-07-23 22:05:54 +02:00
parent 9e14cd1c00
commit 7476b70a23
377 changed files with 7987 additions and 4 deletions

View File

@@ -0,0 +1,27 @@
// Copyright (c) 2023 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 either
import (
ET "github.com/IBM/fp-go/either"
L "github.com/IBM/fp-go/optics/lens"
LG "github.com/IBM/fp-go/optics/lens/generic"
T "github.com/IBM/fp-go/optics/traversal/either"
)
func AsTraversal[E, S, A any]() func(L.Lens[S, A]) T.Traversal[E, S, A] {
return LG.AsTraversal[T.Traversal[E, S, A]](ET.MonadMap[E, A, S])
}

View File

@@ -0,0 +1,35 @@
// Copyright (c) 2023 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 generic
import (
L "github.com/IBM/fp-go/optics/lens"
)
// AsTraversal converts a lens to a traversal
func AsTraversal[R ~func(func(A) HKTA) func(S) HKTS, S, A, HKTS, HKTA any](
fmap func(HKTA, func(A) S) HKTS,
) func(L.Lens[S, A]) R {
return func(sa L.Lens[S, A]) R {
return func(f func(a A) HKTA) func(S) HKTS {
return func(s S) HKTS {
return fmap(f(sa.Get(s)), func(a A) S {
return sa.Set(a)(s)
})
}
}
}
}

44
optics/lens/iso/iso.go Normal file
View File

@@ -0,0 +1,44 @@
// Copyright (c) 2023 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 iso
import (
F "github.com/IBM/fp-go/function"
I "github.com/IBM/fp-go/optics/iso"
IL "github.com/IBM/fp-go/optics/iso/lens"
L "github.com/IBM/fp-go/optics/lens"
O "github.com/IBM/fp-go/option"
)
// FromNillable converts a nillable value to an option and back
func FromNillable[T any]() I.Iso[*T, O.Option[T]] {
return I.MakeIso(F.Flow2(
O.FromPredicate(F.IsNonNil[T]),
O.Map(F.Deref[T]),
),
O.Fold(F.Constant((*T)(nil)), F.Ref[T]),
)
}
// Compose converts a Lens to a property of `A` into a lens to a property of type `B`
// the transformation is done via an ISO
func Compose[S, A, B any](ab I.Iso[A, B]) func(sa L.Lens[S, A]) L.Lens[S, B] {
return F.Pipe2(
ab,
IL.IsoAsLens[A, B],
L.Compose[S, A, B],
)
}

View File

@@ -0,0 +1,99 @@
// Copyright (c) 2023 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 iso
import (
"testing"
EQT "github.com/IBM/fp-go/eq/testing"
F "github.com/IBM/fp-go/function"
L "github.com/IBM/fp-go/optics/lens"
LT "github.com/IBM/fp-go/optics/lens/testing"
O "github.com/IBM/fp-go/option"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type (
Inner struct {
Value *int
Foo string
}
Outer struct {
inner Inner
}
)
func (outer Outer) GetInner() Inner {
return outer.inner
}
func (outer Outer) SetInner(inner Inner) Outer {
outer.inner = inner
return outer
}
func (inner Inner) GetValue() *int {
return inner.Value
}
func (inner Inner) SetValue(value *int) Inner {
inner.Value = value
return inner
}
func TestIso(t *testing.T) {
eqOptInt := O.Eq(EQT.Eq[int]())
eqOuter := EQT.Eq[Outer]()
emptyOuter := Outer{}
// iso
intIso := FromNillable[int]()
innerFromOuter := L.MakeLens((Outer).GetInner, (Outer).SetInner)
valueFromInner := L.MakeLens((Inner).GetValue, (Inner).SetValue)
optValueFromInner := F.Pipe1(
valueFromInner,
Compose[Inner](intIso),
)
optValueFromOuter := F.Pipe1(
innerFromOuter,
L.Compose[Outer](optValueFromInner),
)
// try some access
require.True(t, eqOptInt.Equals(optValueFromOuter.Get(emptyOuter), O.None[int]()))
updatedOuter := optValueFromOuter.Set(O.Some(1))(emptyOuter)
require.True(t, eqOptInt.Equals(optValueFromOuter.Get(updatedOuter), O.Some(1)))
secondOuter := optValueFromOuter.Set(O.None[int]())(updatedOuter)
require.True(t, eqOptInt.Equals(optValueFromOuter.Get(secondOuter), O.None[int]()))
// check if this obeys laws
laws := LT.AssertLaws(
t,
eqOptInt,
eqOuter,
)(optValueFromOuter)
assert.True(t, laws(emptyOuter, O.Some(2)))
}

325
optics/lens/lens.go Normal file
View File

@@ -0,0 +1,325 @@
// Copyright (c) 2023 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.
// Lens is an optic used to zoom inside a product.
package lens
import (
F "github.com/IBM/fp-go/function"
I "github.com/IBM/fp-go/identity"
O "github.com/IBM/fp-go/option"
)
type (
// Lens is a reference to a subpart of a data type
Lens[S, A any] struct {
Get func(s S) A
Set func(a A) func(S) S
}
)
// setCopy wraps a setter for a pointer into a setter that first creates a copy before
// modifying that copy
func setCopy[S, A any](setter func(*S, A) *S) func(s *S, a A) *S {
return func(s *S, a A) *S {
copy := *s
return setter(&copy, a)
}
}
// setCopyCurried wraps a setter for a pointer into a setter that first creates a copy before
// modifying that copy
func setCopyCurried[S, A any](setter func(A) func(*S) *S) func(a A) func(*S) *S {
return func(a A) func(*S) *S {
seta := setter(a)
return func(s *S) *S {
copy := *s
return seta(&copy)
}
}
}
// MakeLens creates a lens based on a getter and a setter function. Make sure that the setter creates a (shallow) copy of the
// data. This happens automatically if the data is passed by value. For pointers consider to use `MakeLensRef`
// and for other kinds of data structures that are copied by reference make sure the setter creates the copy.
func MakeLens[S, A any](get func(S) A, set func(S, A) S) Lens[S, A] {
return MakeLensCurried(get, F.Curry2(F.Swap(set)))
}
// MakeLensCurried creates a lens based on a getter and a setter function. Make sure that the setter creates a (shallow) copy of the
// data. This happens automatically if the data is passed by value. For pointers consider to use `MakeLensRef`
// and for other kinds of data structures that are copied by reference make sure the setter creates the copy.
func MakeLensCurried[S, A any](get func(S) A, set func(A) func(S) S) Lens[S, A] {
return Lens[S, A]{Get: get, Set: set}
}
// MakeLensRef creates a lens based on a getter and a setter function. The setter passed in does not have to create a shallow
// copy, the implementation wraps the setter into one that copies the pointer before modifying it
//
// Such a lens assumes that property A of S always exists
func MakeLensRef[S, A any](get func(*S) A, set func(*S, A) *S) Lens[*S, A] {
return MakeLens(get, setCopy(set))
}
// MakeLensRefCurried creates a lens based on a getter and a setter function. The setter passed in does not have to create a shallow
// copy, the implementation wraps the setter into one that copies the pointer before modifying it
//
// Such a lens assumes that property A of S always exists
func MakeLensRefCurried[S, A any](get func(*S) A, set func(A) func(*S) *S) Lens[*S, A] {
return MakeLensCurried(get, setCopyCurried(set))
}
// Id returns a lens implementing the identity operation
func id[S any](creator func(get func(S) S, set func(S, S) S) Lens[S, S]) Lens[S, S] {
return creator(F.Identity[S], F.Second[S, S])
}
// Id returns a lens implementing the identity operation
func Id[S any]() Lens[S, S] {
return id(MakeLens[S, S])
}
// IdRef returns a lens implementing the identity operation
func IdRef[S any]() Lens[*S, *S] {
return id(MakeLensRef[S, *S])
}
// Compose combines two lenses and allows to narrow down the focus to a sub-lens
func compose[S, A, B any](creator func(get func(S) B, set func(S, B) S) Lens[S, B], ab Lens[A, B]) func(Lens[S, A]) Lens[S, B] {
abget := ab.Get
abset := ab.Set
return func(sa Lens[S, A]) Lens[S, B] {
saget := sa.Get
saset := sa.Set
return creator(
F.Flow2(saget, abget),
func(s S, b B) S {
return saset(abset(b)(saget(s)))(s)
},
)
}
}
// Compose combines two lenses and allows to narrow down the focus to a sub-lens
func Compose[S, A, B any](ab Lens[A, B]) func(Lens[S, A]) Lens[S, B] {
return compose(MakeLens[S, B], ab)
}
// ComposeOption combines a `Lens` that returns an optional value with a `Lens` that returns a definite value
// the getter returns an `Option[B]` because the container `A` could already be an option
// if the setter is invoked with `Some[B]` then the value of `B` will be set, potentially on a default value of `A` if `A` did not exist
// if the setter is invoked with `None[B]` then the container `A` is reset to `None[A]` because this is the only way to remove `B`
func ComposeOption[S, A, B any](defaultA A) func(ab Lens[A, B]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
defa := F.Constant(defaultA)
return func(ab Lens[A, B]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
foldab := O.Fold(O.None[B], F.Flow2(ab.Get, O.Some[B]))
return func(sa Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
// set A on S
seta := F.Flow2(
O.Some[A],
sa.Set,
)
// remove A from S
unseta := F.Nullary2(
O.None[A],
sa.Set,
)
return MakeLens(
F.Flow2(sa.Get, foldab),
func(s S, ob O.Option[B]) S {
return F.Pipe2(
ob,
O.Fold(unseta, func(b B) func(S) S {
setbona := F.Flow2(
ab.Set(b),
seta,
)
return F.Pipe2(
s,
sa.Get,
O.Fold(
F.Nullary2(
defa,
setbona,
),
setbona,
),
)
}),
I.Ap[S, S](s),
)
},
)
}
}
}
// ComposeOptions combines a `Lens` that returns an optional value with a `Lens` that returns another optional value
// the getter returns `None[B]` if either `A` or `B` is `None`
// if the setter is called with `Some[B]` and `A` exists, 'A' is updated with `B`
// if the setter is called with `Some[B]` and `A` does not exist, the default of 'A' is updated with `B`
// if the setter is called with `None[B]` and `A` does not exist this is the identity operation on 'S'
// if the setter is called with `None[B]` and `A` does exist, 'B' is removed from 'A'
func ComposeOptions[S, A, B any](defaultA A) func(ab Lens[A, O.Option[B]]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
defa := F.Constant(defaultA)
noops := F.Constant(F.Identity[S])
noneb := O.None[B]()
return func(ab Lens[A, O.Option[B]]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
unsetb := ab.Set(noneb)
return func(sa Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
// sets an A onto S
seta := F.Flow2(
O.Some[A],
sa.Set,
)
return MakeLensCurried(
F.Flow2(
sa.Get,
O.Chain(ab.Get),
),
func(b O.Option[B]) func(S) S {
return func(s S) S {
return O.MonadFold(b, func() func(S) S {
return F.Pipe2(
s,
sa.Get,
O.Fold(noops, F.Flow2(unsetb, seta)),
)
}, func(b B) func(S) S {
// sets a B onto an A
setb := F.Flow2(
ab.Set(O.Some(b)),
seta,
)
return F.Pipe2(
s,
sa.Get,
O.Fold(F.Nullary2(defa, setb), setb),
)
})(s)
}
},
)
}
}
}
// Compose combines two lenses and allows to narrow down the focus to a sub-lens
func ComposeRef[S, A, B any](ab Lens[A, B]) func(Lens[*S, A]) Lens[*S, B] {
return compose(MakeLensRef[S, B], ab)
}
func modify[S, A any](f func(A) A, sa Lens[S, A], s S) S {
return sa.Set(f(sa.Get(s)))(s)
}
// Modify changes a property of a lens by invoking a transformation function
// if the transformed property has not changes, the method returns the original state
func Modify[S, A any](f func(A) A) func(Lens[S, A]) func(S) S {
return F.Curry3(modify[S, A])(f)
}
func IMap[E, A, B any](ab func(A) B, ba func(B) A) func(Lens[E, A]) Lens[E, B] {
return func(ea Lens[E, A]) Lens[E, B] {
return Lens[E, B]{Get: F.Flow2(ea.Get, ab), Set: F.Flow2(ba, ea.Set)}
}
}
// fromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional
// if the optional value is set then the nil value will be set instead
func fromPredicate[S, A any](creator func(get func(S) O.Option[A], set func(S, O.Option[A]) S) Lens[S, O.Option[A]], pred func(A) bool, nilValue A) func(sa Lens[S, A]) Lens[S, O.Option[A]] {
fromPred := O.FromPredicate(pred)
return func(sa Lens[S, A]) Lens[S, O.Option[A]] {
fold := O.Fold(F.Bind1of1(sa.Set)(nilValue), sa.Set)
return creator(F.Flow2(sa.Get, fromPred), func(s S, a O.Option[A]) S {
return F.Pipe2(
a,
fold,
I.Ap[S, S](s),
)
})
}
}
// FromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional
// if the optional value is set then the nil value will be set instead
func FromPredicate[S, A any](pred func(A) bool, nilValue A) func(sa Lens[S, A]) Lens[S, O.Option[A]] {
return fromPredicate(MakeLens[S, O.Option[A]], pred, nilValue)
}
// FromPredicateRef returns a `Lens` for a property accessibly as a getter and setter that can be optional
// if the optional value is set then the nil value will be set instead
func FromPredicateRef[S, A any](pred func(A) bool, nilValue A) func(sa Lens[*S, A]) Lens[*S, O.Option[A]] {
return fromPredicate(MakeLensRef[S, O.Option[A]], pred, nilValue)
}
// FromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional
// if the optional value is set then the `nil` value will be set instead
func FromNillable[S, A any](sa Lens[S, *A]) Lens[S, O.Option[*A]] {
return FromPredicate[S](F.IsNonNil[A], nil)(sa)
}
// FromNillableRef returns a `Lens` for a property accessibly as a getter and setter that can be optional
// if the optional value is set then the `nil` value will be set instead
func FromNillableRef[S, A any](sa Lens[*S, *A]) Lens[*S, O.Option[*A]] {
return FromPredicateRef[S](F.IsNonNil[A], nil)(sa)
}
// fromNullableProp returns a `Lens` from a property that may be optional. The getter returns a default value for these items
func fromNullableProp[S, A any](creator func(get func(S) A, set func(S, A) S) Lens[S, A], isNullable func(A) O.Option[A], defaultValue A) func(sa Lens[S, A]) Lens[S, A] {
return func(sa Lens[S, A]) Lens[S, A] {
return creator(F.Flow3(
sa.Get,
isNullable,
O.GetOrElse(F.Constant(defaultValue)),
), func(s S, a A) S {
return sa.Set(a)(s)
},
)
}
}
// FromNullableProp returns a `Lens` from a property that may be optional. The getter returns a default value for these items
func FromNullableProp[S, A any](isNullable func(A) O.Option[A], defaultValue A) func(sa Lens[S, A]) Lens[S, A] {
return fromNullableProp(MakeLens[S, A], isNullable, defaultValue)
}
// FromNullablePropRef returns a `Lens` from a property that may be optional. The getter returns a default value for these items
func FromNullablePropRef[S, A any](isNullable func(A) O.Option[A], defaultValue A) func(sa Lens[*S, A]) Lens[*S, A] {
return fromNullableProp(MakeLensRef[S, A], isNullable, defaultValue)
}
// fromFromOption returns a `Lens` from an option property. The getter returns a default value the setter will always set the some option
func fromOption[S, A any](creator func(get func(S) A, set func(S, A) S) Lens[S, A], defaultValue A) func(sa Lens[S, O.Option[A]]) Lens[S, A] {
return func(sa Lens[S, O.Option[A]]) Lens[S, A] {
return creator(F.Flow2(
sa.Get,
O.GetOrElse(F.Constant(defaultValue)),
), func(s S, a A) S {
return sa.Set(O.Some(a))(s)
},
)
}
}
// FromFromOption returns a `Lens` from an option property. The getter returns a default value the setter will always set the some option
func FromOption[S, A any](defaultValue A) func(sa Lens[S, O.Option[A]]) Lens[S, A] {
return fromOption(MakeLens[S, A], defaultValue)
}
// FromFromOptionRef returns a `Lens` from an option property. The getter returns a default value the setter will always set the some option
func FromOptionRef[S, A any](defaultValue A) func(sa Lens[*S, O.Option[A]]) Lens[*S, A] {
return fromOption(MakeLensRef[S, A], defaultValue)
}

250
optics/lens/lens_test.go Normal file
View File

@@ -0,0 +1,250 @@
// Copyright (c) 2023 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 lens
import (
"testing"
F "github.com/IBM/fp-go/function"
O "github.com/IBM/fp-go/option"
"github.com/stretchr/testify/assert"
)
type (
Street struct {
num int
name string
}
Address struct {
city string
street *Street
}
Inner struct {
Value int
Foo string
}
InnerOpt struct {
Value *int
Foo *string
}
Outer struct {
inner *Inner
}
OuterOpt struct {
inner *InnerOpt
}
)
func (outer Outer) GetInner() *Inner {
return outer.inner
}
func (outer Outer) SetInner(inner *Inner) Outer {
outer.inner = inner
return outer
}
func (outer OuterOpt) GetInnerOpt() *InnerOpt {
return outer.inner
}
func (outer OuterOpt) SetInnerOpt(inner *InnerOpt) OuterOpt {
outer.inner = inner
return outer
}
func (inner *Inner) GetValue() int {
return inner.Value
}
func (inner *Inner) SetValue(value int) *Inner {
inner.Value = value
return inner
}
func (inner *InnerOpt) GetValue() *int {
return inner.Value
}
func (inner *InnerOpt) SetValue(value *int) *InnerOpt {
inner.Value = value
return inner
}
func (street *Street) GetName() string {
return street.name
}
func (street *Street) SetName(name string) *Street {
street.name = name
return street
}
func (addr *Address) GetStreet() *Street {
return addr.street
}
func (addr *Address) SetStreet(s *Street) *Address {
addr.street = s
return addr
}
var (
streetLens = MakeLensRef((*Street).GetName, (*Street).SetName)
addrLens = MakeLensRef((*Address).GetStreet, (*Address).SetStreet)
sampleStreet = Street{num: 220, name: "Schönaicherstr"}
sampleAddress = Address{city: "Böblingen", street: &sampleStreet}
)
func TestLens(t *testing.T) {
// read the value
assert.Equal(t, sampleStreet.name, streetLens.Get(&sampleStreet))
// new street
newName := "Böblingerstr"
// update
old := sampleStreet
updated := streetLens.Set(newName)(&sampleStreet)
assert.Equal(t, old, sampleStreet)
// validate the new name
assert.Equal(t, newName, streetLens.Get(updated))
}
func TestAddressCompose(t *testing.T) {
// compose
streetName := Compose[*Address](streetLens)(addrLens)
assert.Equal(t, sampleStreet.name, streetName.Get(&sampleAddress))
// new street
newName := "Böblingerstr"
updated := streetName.Set(newName)(&sampleAddress)
// check that we have not modified the original
assert.Equal(t, sampleStreet.name, streetName.Get(&sampleAddress))
assert.Equal(t, newName, streetName.Get(updated))
}
func TestIMap(t *testing.T) {
type S struct {
a int
}
sa := F.Pipe1(
Id[S](),
IMap[S](
func(s S) int { return s.a },
func(a int) S { return S{a} },
),
)
assert.Equal(t, 1, sa.Get(S{1}))
assert.Equal(t, S{2}, sa.Set(2)(S{1}))
}
func TestPassByValue(t *testing.T) {
testLens := MakeLens(func(s Street) string { return s.name }, func(s Street, value string) Street {
s.name = value
return s
})
s1 := Street{1, "value1"}
s2 := testLens.Set("value2")(s1)
assert.Equal(t, "value1", s1.name)
assert.Equal(t, "value2", s2.name)
}
func TestFromNullableProp(t *testing.T) {
// default inner object
defaultInner := &Inner{
Value: 0,
Foo: "foo",
}
// access to the value
value := MakeLensRef((*Inner).GetValue, (*Inner).SetValue)
// access to inner
inner := FromNullableProp[Outer](O.FromNillable[Inner], defaultInner)(MakeLens(Outer.GetInner, Outer.SetInner))
// compose
lens := F.Pipe1(
inner,
Compose[Outer](value),
)
outer1 := Outer{inner: &Inner{Value: 1, Foo: "a"}}
// the checks
assert.Equal(t, Outer{inner: &Inner{Value: 1, Foo: "foo"}}, lens.Set(1)(Outer{}))
assert.Equal(t, 0, lens.Get(Outer{}))
assert.Equal(t, Outer{inner: &Inner{Value: 1, Foo: "foo"}}, lens.Set(1)(Outer{inner: &Inner{Value: 2, Foo: "foo"}}))
assert.Equal(t, 1, lens.Get(Outer{inner: &Inner{Value: 1, Foo: "foo"}}))
assert.Equal(t, outer1, Modify[Outer](F.Identity[int])(lens)(outer1))
}
func TestComposeOption(t *testing.T) {
// default inner object
defaultInner := &Inner{
Value: 0,
Foo: "foo",
}
// access to the value
value := MakeLensRef((*Inner).GetValue, (*Inner).SetValue)
// access to inner
inner := FromNillable(MakeLens(Outer.GetInner, Outer.SetInner))
// compose lenses
lens := F.Pipe1(
inner,
ComposeOption[Outer, *Inner, int](defaultInner)(value),
)
outer1 := Outer{inner: &Inner{Value: 1, Foo: "a"}}
// the checks
assert.Equal(t, Outer{inner: &Inner{Value: 1, Foo: "foo"}}, lens.Set(O.Some(1))(Outer{}))
assert.Equal(t, O.None[int](), lens.Get(Outer{}))
assert.Equal(t, Outer{inner: &Inner{Value: 1, Foo: "foo"}}, lens.Set(O.Some(1))(Outer{inner: &Inner{Value: 2, Foo: "foo"}}))
assert.Equal(t, O.Some(1), lens.Get(Outer{inner: &Inner{Value: 1, Foo: "foo"}}))
assert.Equal(t, outer1, Modify[Outer](F.Identity[O.Option[int]])(lens)(outer1))
}
func TestComposeOptions(t *testing.T) {
// default inner object
defaultValue1 := 1
defaultFoo1 := "foo1"
defaultInner := &InnerOpt{
Value: &defaultValue1,
Foo: &defaultFoo1,
}
// access to the value
value := FromNillable(MakeLensRef((*InnerOpt).GetValue, (*InnerOpt).SetValue))
// access to inner
inner := FromNillable(MakeLens(OuterOpt.GetInnerOpt, OuterOpt.SetInnerOpt))
// compose lenses
lens := F.Pipe1(
inner,
ComposeOptions[OuterOpt, *InnerOpt, *int](defaultInner)(value),
)
// additional settings
defaultValue2 := 2
defaultFoo2 := "foo2"
outer1 := OuterOpt{inner: &InnerOpt{Value: &defaultValue2, Foo: &defaultFoo2}}
// the checks
assert.Equal(t, OuterOpt{inner: &InnerOpt{Value: &defaultValue1, Foo: &defaultFoo1}}, lens.Set(O.Some(&defaultValue1))(OuterOpt{}))
assert.Equal(t, O.None[*int](), lens.Get(OuterOpt{}))
assert.Equal(t, OuterOpt{inner: &InnerOpt{Value: &defaultValue1, Foo: &defaultFoo2}}, lens.Set(O.Some(&defaultValue1))(OuterOpt{inner: &InnerOpt{Value: &defaultValue2, Foo: &defaultFoo2}}))
assert.Equal(t, O.Some(&defaultValue1), lens.Get(OuterOpt{inner: &InnerOpt{Value: &defaultValue1, Foo: &defaultFoo1}}))
assert.Equal(t, outer1, Modify[OuterOpt](F.Identity[O.Option[*int]])(lens)(outer1))
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) 2023 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 option
import (
L "github.com/IBM/fp-go/optics/lens"
LG "github.com/IBM/fp-go/optics/lens/generic"
T "github.com/IBM/fp-go/optics/traversal/option"
O "github.com/IBM/fp-go/option"
)
func AsTraversal[S, A any]() func(L.Lens[S, A]) T.Traversal[S, A] {
return LG.AsTraversal[T.Traversal[S, A]](O.MonadMap[A, S])
}

View File

@@ -0,0 +1,34 @@
// Copyright (c) 2023 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 optional
import (
F "github.com/IBM/fp-go/function"
L "github.com/IBM/fp-go/optics/lens"
OPT "github.com/IBM/fp-go/optics/optional"
O "github.com/IBM/fp-go/option"
)
func lensAsOptional[S, A any](creator func(get func(S) O.Option[A], set func(S, A) S) OPT.Optional[S, A], sa L.Lens[S, A]) OPT.Optional[S, A] {
return creator(F.Flow2(sa.Get, O.Some[A]), func(s S, a A) S {
return sa.Set(a)(s)
})
}
// LensAsOptional converts a Lens into an Optional
func LensAsOptional[S, A any](sa L.Lens[S, A]) OPT.Optional[S, A] {
return lensAsOptional(OPT.MakeOptional[S, A], sa)
}

View File

@@ -0,0 +1,49 @@
// Copyright (c) 2023 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 generic
import (
F "github.com/IBM/fp-go/function"
I "github.com/IBM/fp-go/identity"
L "github.com/IBM/fp-go/optics/lens"
O "github.com/IBM/fp-go/option"
RR "github.com/IBM/fp-go/record/generic"
)
// AtRecord returns a lens that focusses on a value in a record
func AtRecord[M ~map[K]V, K comparable, V any](key K) L.Lens[M, O.Option[V]] {
addKey := F.Bind1of2(RR.UpsertAt[M, K, V])(key)
delKey := F.Bind1of1(RR.DeleteAt[M, K, V])(key)
fold := O.Fold(
delKey,
addKey,
)
return L.MakeLens(
RR.Lookup[M](key),
func(m M, v O.Option[V]) M {
return F.Pipe2(
v,
fold,
I.Ap[M, M](m),
)
},
)
}
// AtKey returns a `Lens` focused on a required key of a `ReadonlyRecord`
func AtKey[M ~map[K]V, S any, K comparable, V any](key K) func(sa L.Lens[S, M]) L.Lens[S, O.Option[V]] {
return L.Compose[S](AtRecord[M](key))
}

View File

@@ -0,0 +1,32 @@
// Copyright (c) 2023 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 record
import (
L "github.com/IBM/fp-go/optics/lens"
G "github.com/IBM/fp-go/optics/lens/record/generic"
O "github.com/IBM/fp-go/option"
)
// AtRecord returns a lens that focusses on a value in a record
func AtRecord[K comparable, V any](key K) L.Lens[map[K]V, O.Option[V]] {
return G.AtRecord[map[K]V](key)
}
// AtKey returns a `Lens` focused on a required key of a `ReadonlyRecord`
func AtKey[S any, K comparable, V any](key K) func(sa L.Lens[S, map[K]V]) L.Lens[S, O.Option[V]] {
return G.AtKey[map[K]V, S](key)
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2023 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 record
import (
"testing"
F "github.com/IBM/fp-go/function"
L "github.com/IBM/fp-go/optics/lens"
O "github.com/IBM/fp-go/option"
"github.com/stretchr/testify/assert"
)
type (
S = map[string]int
)
func TestAtKey(t *testing.T) {
sa := F.Pipe1(
L.Id[S](),
AtKey[S, string, int]("a"),
)
assert.Equal(t, O.Some(1), sa.Get(S{"a": 1}))
assert.Equal(t, S{"a": 2, "b": 2}, sa.Set(O.Some(2))(S{"a": 1, "b": 2}))
assert.Equal(t, S{"a": 1, "b": 2}, sa.Set(O.Some(1))(S{"b": 2}))
assert.Equal(t, S{"b": 2}, sa.Set(O.None[int]())(S{"a": 1, "b": 2}))
}

View File

@@ -0,0 +1,80 @@
// Copyright (c) 2023 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 testing
import (
"testing"
E "github.com/IBM/fp-go/eq"
L "github.com/IBM/fp-go/optics/lens"
"github.com/stretchr/testify/assert"
)
// LensGet tests the law:
// get(set(a)(s)) = a
func LensGet[S, A any](
t *testing.T,
eqa E.Eq[A],
) func(l L.Lens[S, A]) func(s S, a A) bool {
return func(l L.Lens[S, A]) func(s S, a A) bool {
return func(s S, a A) bool {
return assert.True(t, eqa.Equals(l.Get(l.Set(a)(s)), a), "Lens get(set(a)(s)) = a")
}
}
}
// LensSet tests the laws:
// set(get(s))(s) = s
// set(a)(set(a)(s)) = set(a)(s)
func LensSet[S, A any](
t *testing.T,
eqs E.Eq[S],
) func(l L.Lens[S, A]) func(s S, a A) bool {
return func(l L.Lens[S, A]) func(s S, a A) bool {
return func(s S, a A) bool {
return assert.True(t, eqs.Equals(l.Set(l.Get(s))(s), s), "Lens set(get(s))(s) = s") && assert.True(t, eqs.Equals(l.Set(a)(l.Set(a)(s)), l.Set(a)(s)), "Lens set(a)(set(a)(s)) = set(a)(s)")
}
}
}
// AssertLaws tests the lens laws
//
// get(set(a)(s)) = a
// set(get(s))(s) = s
// set(a)(set(a)(s)) = set(a)(s)
func AssertLaws[S, A any](
t *testing.T,
eqa E.Eq[A],
eqs E.Eq[S],
) func(l L.Lens[S, A]) func(s S, a A) bool {
lenGet := LensGet[S](t, eqa)
lenSet := LensSet[S, A](t, eqs)
return func(l L.Lens[S, A]) func(s S, a A) bool {
get := lenGet(l)
set := lenSet(l)
return func(s S, a A) bool {
return get(s, a) && set(s, a)
}
}
}

View File

@@ -0,0 +1,265 @@
// Copyright (c) 2023 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 testing
import (
"testing"
EQT "github.com/IBM/fp-go/eq/testing"
F "github.com/IBM/fp-go/function"
I "github.com/IBM/fp-go/identity"
L "github.com/IBM/fp-go/optics/lens"
LI "github.com/IBM/fp-go/optics/lens/iso"
O "github.com/IBM/fp-go/option"
"github.com/stretchr/testify/assert"
)
type (
Street struct {
num int
name string
}
Address struct {
city string
street *Street
}
Inner struct {
Value int
Foo string
}
InnerOpt struct {
Value *int
Foo *string
}
Outer struct {
inner *Inner
}
OuterOpt struct {
inner *InnerOpt
}
)
func (outer *OuterOpt) GetInner() *InnerOpt {
return outer.inner
}
func (outer *OuterOpt) SetInner(inner *InnerOpt) *OuterOpt {
outer.inner = inner
return outer
}
func (inner *InnerOpt) GetValue() *int {
return inner.Value
}
func (inner *InnerOpt) SetValue(value *int) *InnerOpt {
inner.Value = value
return inner
}
func (outer *Outer) GetInner() *Inner {
return outer.inner
}
func (outer *Outer) SetInner(inner *Inner) *Outer {
outer.inner = inner
return outer
}
func (inner *Inner) GetValue() int {
return inner.Value
}
func (inner *Inner) SetValue(value int) *Inner {
inner.Value = value
return inner
}
func (street *Street) GetName() string {
return street.name
}
func (street *Street) SetName(name string) *Street {
street.name = name
return street
}
func (addr *Address) GetStreet() *Street {
return addr.street
}
func (addr *Address) SetStreet(s *Street) *Address {
addr.street = s
return addr
}
var (
streetLens = L.MakeLensRef((*Street).GetName, (*Street).SetName)
addrLens = L.MakeLensRef((*Address).GetStreet, (*Address).SetStreet)
outerLens = L.FromNillableRef(L.MakeLensRef((*Outer).GetInner, (*Outer).SetInner))
valueLens = L.MakeLensRef((*Inner).GetValue, (*Inner).SetValue)
outerOptLens = L.FromNillableRef(L.MakeLensRef((*OuterOpt).GetInner, (*OuterOpt).SetInner))
valueOptLens = L.MakeLensRef((*InnerOpt).GetValue, (*InnerOpt).SetValue)
sampleStreet = Street{num: 220, name: "Schönaicherstr"}
sampleAddress = Address{city: "Böblingen", street: &sampleStreet}
sampleStreet2 = Street{num: 220, name: "Neue Str"}
defaultInner = Inner{
Value: -1,
Foo: "foo",
}
emptyOuter = Outer{}
defaultInnerOpt = InnerOpt{
Value: &defaultInner.Value,
Foo: &defaultInner.Foo,
}
emptyOuterOpt = OuterOpt{}
)
func TestStreetLensLaws(t *testing.T) {
// some comparison
eqs := EQT.Eq[*Street]()
eqa := EQT.Eq[string]()
laws := AssertLaws(
t,
eqa,
eqs,
)(streetLens)
cpy := sampleStreet
assert.True(t, laws(&sampleStreet, "Neue Str."))
assert.Equal(t, cpy, sampleStreet)
}
func TestAddrLensLaws(t *testing.T) {
// some comparison
eqs := EQT.Eq[*Address]()
eqa := EQT.Eq[*Street]()
laws := AssertLaws(
t,
eqa,
eqs,
)(addrLens)
cpyAddr := sampleAddress
cpyStreet := sampleStreet2
assert.True(t, laws(&sampleAddress, &sampleStreet2))
assert.Equal(t, cpyAddr, sampleAddress)
assert.Equal(t, cpyStreet, sampleStreet2)
}
func TestCompose(t *testing.T) {
// some comparison
eqs := EQT.Eq[*Address]()
eqa := EQT.Eq[string]()
streetName := L.Compose[*Address](streetLens)(addrLens)
laws := AssertLaws(
t,
eqa,
eqs,
)(streetName)
cpyAddr := sampleAddress
cpyStreet := sampleStreet
assert.True(t, laws(&sampleAddress, "Neue Str."))
assert.Equal(t, cpyAddr, sampleAddress)
assert.Equal(t, cpyStreet, sampleStreet)
}
func TestOuterLensLaws(t *testing.T) {
// some equal predicates
eqValue := EQT.Eq[int]()
eqOptValue := O.Eq(eqValue)
// lens to access a value from outer
valueFromOuter := L.ComposeOption[*Outer, *Inner, int](&defaultInner)(valueLens)(outerLens)
// try to access the value, this should get an option
assert.True(t, eqOptValue.Equals(valueFromOuter.Get(&emptyOuter), O.None[int]()))
// update the object
withValue := valueFromOuter.Set(O.Some(1))(&emptyOuter)
assert.True(t, eqOptValue.Equals(valueFromOuter.Get(&emptyOuter), O.None[int]()))
assert.True(t, eqOptValue.Equals(valueFromOuter.Get(withValue), O.Some(1)))
// updating with none should remove the inner
nextValue := valueFromOuter.Set(O.None[int]())(withValue)
assert.True(t, eqOptValue.Equals(valueFromOuter.Get(nextValue), O.None[int]()))
// check if this meets the laws
eqOuter := EQT.Eq[*Outer]()
laws := AssertLaws(
t,
eqOptValue,
eqOuter,
)(valueFromOuter)
assert.True(t, laws(&emptyOuter, O.Some(2)))
assert.True(t, laws(&emptyOuter, O.None[int]()))
assert.True(t, laws(withValue, O.Some(2)))
assert.True(t, laws(withValue, O.None[int]()))
}
func TestOuterOptLensLaws(t *testing.T) {
// some equal predicates
eqValue := EQT.Eq[int]()
eqOptValue := O.Eq(eqValue)
intIso := LI.FromNillable[int]()
// lens to access a value from outer
valueFromOuter := F.Pipe3(
valueOptLens,
LI.Compose[*InnerOpt](intIso),
L.ComposeOptions[*OuterOpt, *InnerOpt, int](&defaultInnerOpt),
I.Ap[L.Lens[*OuterOpt, O.Option[int]]](outerOptLens),
)
// try to access the value, this should get an option
assert.True(t, eqOptValue.Equals(valueFromOuter.Get(&emptyOuterOpt), O.None[int]()))
// update the object
withValue := valueFromOuter.Set(O.Some(1))(&emptyOuterOpt)
assert.True(t, eqOptValue.Equals(valueFromOuter.Get(&emptyOuterOpt), O.None[int]()))
assert.True(t, eqOptValue.Equals(valueFromOuter.Get(withValue), O.Some(1)))
// updating with none should remove the inner
nextValue := valueFromOuter.Set(O.None[int]())(withValue)
assert.True(t, eqOptValue.Equals(valueFromOuter.Get(nextValue), O.None[int]()))
// check if this meets the laws
eqOuter := EQT.Eq[*OuterOpt]()
laws := AssertLaws(
t,
eqOptValue,
eqOuter,
)(valueFromOuter)
assert.True(t, laws(&emptyOuterOpt, O.Some(2)))
assert.True(t, laws(&emptyOuterOpt, O.None[int]()))
assert.True(t, laws(withValue, O.Some(2)))
assert.True(t, laws(withValue, O.None[int]()))
}