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:
4
optics/README.md
Normal file
4
optics/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Optics
|
||||
|
||||
Refer to [Introduction to optics: lenses and prisms](https://medium.com/@gcanti/introduction-to-optics-lenses-and-prisms-3230e73bfcfe) for an introduction about functional optics.
|
||||
|
105
optics/iso/iso.go
Normal file
105
optics/iso/iso.go
Normal file
@@ -0,0 +1,105 @@
|
||||
// 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.
|
||||
|
||||
// Iso is an optic which converts elements of type `S` into elements of type `A` without loss.
|
||||
package iso
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/function"
|
||||
)
|
||||
|
||||
type Iso[S, A any] struct {
|
||||
Get func(s S) A
|
||||
ReverseGet func(a A) S
|
||||
}
|
||||
|
||||
func MakeIso[S, A any](get func(S) A, reverse func(A) S) Iso[S, A] {
|
||||
return Iso[S, A]{Get: get, ReverseGet: reverse}
|
||||
}
|
||||
|
||||
// Id returns an iso implementing the identity operation
|
||||
func Id[S any]() Iso[S, S] {
|
||||
return MakeIso(F.Identity[S], F.Identity[S])
|
||||
}
|
||||
|
||||
// Compose combines an ISO with another ISO
|
||||
func Compose[S, A, B any](ab Iso[A, B]) func(Iso[S, A]) Iso[S, B] {
|
||||
return func(sa Iso[S, A]) Iso[S, B] {
|
||||
return MakeIso(
|
||||
F.Flow2(sa.Get, ab.Get),
|
||||
F.Flow2(ab.ReverseGet, sa.ReverseGet),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse changes the order of parameters for an iso
|
||||
func Reverse[S, A any](sa Iso[S, A]) Iso[A, S] {
|
||||
return MakeIso(
|
||||
sa.ReverseGet,
|
||||
sa.Get,
|
||||
)
|
||||
}
|
||||
|
||||
func modify[S, A any](f func(A) A, sa Iso[S, A], s S) S {
|
||||
return F.Pipe3(
|
||||
s,
|
||||
sa.Get,
|
||||
f,
|
||||
sa.ReverseGet,
|
||||
)
|
||||
}
|
||||
|
||||
// Modify applies a transformation
|
||||
func Modify[S, A any](f func(A) A) func(Iso[S, A]) func(S) S {
|
||||
return F.Curry3(modify[S, A])(f)
|
||||
}
|
||||
|
||||
// Wrap wraps the value
|
||||
func Unwrap[S, A any](s S) func(Iso[S, A]) A {
|
||||
return func(sa Iso[S, A]) A {
|
||||
return sa.Get(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap unwraps the value
|
||||
func Wrap[S, A any](a A) func(Iso[S, A]) S {
|
||||
return func(sa Iso[S, A]) S {
|
||||
return sa.ReverseGet(a)
|
||||
}
|
||||
}
|
||||
|
||||
// From wraps the value
|
||||
func To[S, A any](s S) func(Iso[S, A]) A {
|
||||
return Unwrap[S, A](s)
|
||||
}
|
||||
|
||||
// To unwraps the value
|
||||
func From[S, A any](a A) func(Iso[S, A]) S {
|
||||
return Wrap[S](a)
|
||||
}
|
||||
|
||||
func imap[S, A, B any](sa Iso[S, A], ab func(A) B, ba func(B) A) Iso[S, B] {
|
||||
return MakeIso(
|
||||
F.Flow2(sa.Get, ab),
|
||||
F.Flow2(ba, sa.ReverseGet),
|
||||
)
|
||||
}
|
||||
|
||||
// IMap implements a bidirectional mapping of the transform
|
||||
func IMap[S, A, B any](ab func(A) B, ba func(B) A) func(Iso[S, A]) Iso[S, B] {
|
||||
return func(sa Iso[S, A]) Iso[S, B] {
|
||||
return imap(sa, ab, ba)
|
||||
}
|
||||
}
|
79
optics/iso/iso_test.go
Normal file
79
optics/iso/iso_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// 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"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
mToKm = MakeIso(
|
||||
func(m float32) float32 {
|
||||
return m / 1000
|
||||
},
|
||||
func(km float32) float32 {
|
||||
return km * 1000
|
||||
},
|
||||
)
|
||||
|
||||
kmToMile = MakeIso(
|
||||
func(km float32) float32 {
|
||||
return km * 0.621371
|
||||
},
|
||||
func(mile float32) float32 {
|
||||
return mile / 0.621371
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
assert.Equal(t, mToKm.Get(100), float32(0.1))
|
||||
assert.Equal(t, Unwrap[float32, float32](float32(100))(mToKm), float32(0.1))
|
||||
assert.Equal(t, To[float32, float32](float32(100))(mToKm), float32(0.1))
|
||||
}
|
||||
|
||||
func TestReverseGet(t *testing.T) {
|
||||
assert.Equal(t, mToKm.ReverseGet(1.2), float32(1200))
|
||||
assert.Equal(t, Wrap[float32](float32(1.2))(mToKm), float32(1200))
|
||||
assert.Equal(t, From[float32](float32(1.2))(mToKm), float32(1200))
|
||||
}
|
||||
|
||||
func TestModify(t *testing.T) {
|
||||
|
||||
double := func(x float32) float32 {
|
||||
return x * 2
|
||||
}
|
||||
|
||||
assert.Equal(t, float32(2000), Modify[float32](double)(mToKm)(float32(1000)))
|
||||
}
|
||||
|
||||
func TestReverse(t *testing.T) {
|
||||
|
||||
double := func(x float32) float32 {
|
||||
return x * 2
|
||||
}
|
||||
|
||||
assert.Equal(t, float32(4000), Modify[float32](double)(Reverse(mToKm))(float32(2000)))
|
||||
}
|
||||
|
||||
func TestCompose(t *testing.T) {
|
||||
comp := Compose[float32](mToKm)(kmToMile)
|
||||
|
||||
assert.InDelta(t, 0.93, comp.Get(1500), 0.01)
|
||||
assert.InDelta(t, 1609.34, comp.ReverseGet(1), 0.01)
|
||||
}
|
32
optics/iso/lens/lens.go
Normal file
32
optics/iso/lens/lens.go
Normal 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 lens
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/function"
|
||||
I "github.com/IBM/fp-go/optics/iso"
|
||||
L "github.com/IBM/fp-go/optics/lens"
|
||||
)
|
||||
|
||||
// IsoAsLens converts an `Iso` to a `Lens`
|
||||
func IsoAsLens[S, A any](sa I.Iso[S, A]) L.Lens[S, A] {
|
||||
return L.MakeLensCurried(sa.Get, F.Flow2(sa.ReverseGet, F.Constant1[S, S]))
|
||||
}
|
||||
|
||||
// IsoAsLensRef converts an `Iso` to a `Lens`
|
||||
func IsoAsLensRef[S, A any](sa I.Iso[*S, A]) L.Lens[*S, A] {
|
||||
return L.MakeLensRefCurried(sa.Get, F.Flow2(sa.ReverseGet, F.Constant1[*S, *S]))
|
||||
}
|
27
optics/lens/either/either.go
Normal file
27
optics/lens/either/either.go
Normal 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])
|
||||
}
|
35
optics/lens/generic/lens.go
Normal file
35
optics/lens/generic/lens.go
Normal 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
44
optics/lens/iso/iso.go
Normal 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],
|
||||
)
|
||||
}
|
99
optics/lens/iso/iso_test.go
Normal file
99
optics/lens/iso/iso_test.go
Normal 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
325
optics/lens/lens.go
Normal 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(©, 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(©)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
250
optics/lens/lens_test.go
Normal 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))
|
||||
}
|
27
optics/lens/option/option.go
Normal file
27
optics/lens/option/option.go
Normal 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])
|
||||
}
|
34
optics/lens/optional/optional.go
Normal file
34
optics/lens/optional/optional.go
Normal 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)
|
||||
}
|
49
optics/lens/record/generic/record.go
Normal file
49
optics/lens/record/generic/record.go
Normal 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))
|
||||
}
|
32
optics/lens/record/record.go
Normal file
32
optics/lens/record/record.go
Normal 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)
|
||||
}
|
41
optics/lens/record/record_test.go
Normal file
41
optics/lens/record/record_test.go
Normal 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}))
|
||||
}
|
80
optics/lens/testing/laws.go
Normal file
80
optics/lens/testing/laws.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
265
optics/lens/testing/laws_test.go
Normal file
265
optics/lens/testing/laws_test.go
Normal 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]()))
|
||||
}
|
32
optics/optional/lens/lens.go
Normal file
32
optics/optional/lens/lens.go
Normal 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 lens
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/function"
|
||||
L "github.com/IBM/fp-go/optics/lens"
|
||||
LO "github.com/IBM/fp-go/optics/lens/optional"
|
||||
OPT "github.com/IBM/fp-go/optics/optional"
|
||||
)
|
||||
|
||||
// Compose composes a lens with an optional
|
||||
func Compose[S, A, B any](ab L.Lens[A, B]) func(sa OPT.Optional[S, A]) OPT.Optional[S, B] {
|
||||
return F.Pipe2(
|
||||
ab,
|
||||
LO.LensAsOptional[A, B],
|
||||
OPT.Compose[S, A, B],
|
||||
)
|
||||
}
|
78
optics/optional/lens/lens_test.go
Normal file
78
optics/optional/lens/lens_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// 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"
|
||||
L "github.com/IBM/fp-go/optics/lens"
|
||||
OPT "github.com/IBM/fp-go/optics/optional"
|
||||
OPTP "github.com/IBM/fp-go/optics/optional/prism"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type Inner struct {
|
||||
A int
|
||||
}
|
||||
|
||||
type State = O.Option[*Inner]
|
||||
|
||||
func (inner *Inner) getA() int {
|
||||
return inner.A
|
||||
}
|
||||
|
||||
func (inner *Inner) setA(A int) *Inner {
|
||||
inner.A = A
|
||||
return inner
|
||||
}
|
||||
|
||||
func TestCompose(t *testing.T) {
|
||||
|
||||
inner1 := Inner{1}
|
||||
|
||||
lensa := L.MakeLensRef((*Inner).getA, (*Inner).setA)
|
||||
|
||||
sa := F.Pipe1(
|
||||
OPT.Id[State](),
|
||||
OPTP.Some[State, *Inner],
|
||||
)
|
||||
ab := F.Pipe1(
|
||||
L.IdRef[Inner](),
|
||||
L.ComposeRef[Inner](lensa),
|
||||
)
|
||||
sb := F.Pipe1(
|
||||
sa,
|
||||
Compose[State](ab),
|
||||
)
|
||||
// check get access
|
||||
assert.Equal(t, O.None[int](), sb.GetOption(O.None[*Inner]()))
|
||||
assert.Equal(t, O.Of(1), sb.GetOption(O.Of(&inner1)))
|
||||
|
||||
// check set access
|
||||
res := F.Pipe1(
|
||||
sb.Set(2)(O.Of(&inner1)),
|
||||
O.Map(func(i *Inner) int {
|
||||
return i.A
|
||||
}),
|
||||
)
|
||||
assert.Equal(t, O.Of(2), res)
|
||||
assert.Equal(t, 1, inner1.A)
|
||||
|
||||
assert.Equal(t, O.None[*Inner](), sb.Set(2)(O.None[*Inner]()))
|
||||
|
||||
}
|
190
optics/optional/optional.go
Normal file
190
optics/optional/optional.go
Normal file
@@ -0,0 +1,190 @@
|
||||
// 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.
|
||||
|
||||
// Optional is an optic used to zoom inside a product. Unlike the `Lens`, the element that the `Optional` focuses
|
||||
// on may not exist.
|
||||
package optional
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/function"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
)
|
||||
|
||||
// Optional is an optional reference to a subpart of a data type
|
||||
type Optional[S, A any] struct {
|
||||
GetOption func(s S) O.Option[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(©, a)
|
||||
}
|
||||
}
|
||||
|
||||
// MakeOptional creates an Optional 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 `MakeOptionalRef`
|
||||
// and for other kinds of data structures that are copied by reference make sure the setter creates the copy.
|
||||
func MakeOptional[S, A any](get func(S) O.Option[A], set func(S, A) S) Optional[S, A] {
|
||||
return Optional[S, A]{GetOption: get, Set: F.Curry2(F.Swap(set))}
|
||||
}
|
||||
|
||||
// MakeOptionalRef creates an Optional 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
|
||||
func MakeOptionalRef[S, A any](get func(*S) O.Option[A], set func(*S, A) *S) Optional[*S, A] {
|
||||
return MakeOptional(get, setCopy(set))
|
||||
}
|
||||
|
||||
// Id returns am optional implementing the identity operation
|
||||
func id[S any](creator func(get func(S) O.Option[S], set func(S, S) S) Optional[S, S]) Optional[S, S] {
|
||||
return creator(O.Some[S], F.Second[S, S])
|
||||
}
|
||||
|
||||
// Id returns am optional implementing the identity operation
|
||||
func Id[S any]() Optional[S, S] {
|
||||
return id(MakeOptional[S, S])
|
||||
}
|
||||
|
||||
// Id returns am optional implementing the identity operation
|
||||
func IdRef[S any]() Optional[*S, *S] {
|
||||
return id(MakeOptionalRef[S, *S])
|
||||
}
|
||||
|
||||
func optionalModifyOption[S, A any](f func(A) A, optional Optional[S, A], s S) O.Option[S] {
|
||||
return F.Pipe1(
|
||||
optional.GetOption(s),
|
||||
O.Map(func(a A) S {
|
||||
return optional.Set(f(a))(s)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func optionalModify[S, A any](f func(A) A, optional Optional[S, A], s S) S {
|
||||
return F.Pipe1(
|
||||
optionalModifyOption(f, optional, s),
|
||||
O.GetOrElse(F.Constant(s)),
|
||||
)
|
||||
}
|
||||
|
||||
// Compose combines two Optional and allows to narrow down the focus to a sub-Optional
|
||||
func compose[S, A, B any](creator func(get func(S) O.Option[B], set func(S, B) S) Optional[S, B], ab Optional[A, B]) func(Optional[S, A]) Optional[S, B] {
|
||||
abget := ab.GetOption
|
||||
abset := ab.Set
|
||||
return func(sa Optional[S, A]) Optional[S, B] {
|
||||
saget := sa.GetOption
|
||||
return creator(
|
||||
F.Flow2(saget, O.Chain(abget)),
|
||||
func(s S, b B) S {
|
||||
return optionalModify(abset(b), sa, s)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Compose combines two Optional and allows to narrow down the focus to a sub-Optional
|
||||
func Compose[S, A, B any](ab Optional[A, B]) func(Optional[S, A]) Optional[S, B] {
|
||||
return compose(MakeOptional[S, B], ab)
|
||||
}
|
||||
|
||||
// ComposeRef combines two Optional and allows to narrow down the focus to a sub-Optional
|
||||
func ComposeRef[S, A, B any](ab Optional[A, B]) func(Optional[*S, A]) Optional[*S, B] {
|
||||
return compose(MakeOptionalRef[S, B], ab)
|
||||
}
|
||||
|
||||
// fromPredicate implements the function generically for both the ref and the direct case
|
||||
func fromPredicate[S, A any](creator func(get func(S) O.Option[A], set func(S, A) S) Optional[S, A], pred func(A) bool) func(func(S) A, func(S, A) S) Optional[S, A] {
|
||||
fromPred := O.FromPredicate(pred)
|
||||
return func(get func(S) A, set func(S, A) S) Optional[S, A] {
|
||||
return creator(
|
||||
F.Flow2(get, fromPred),
|
||||
func(s S, a A) S {
|
||||
return F.Pipe3(
|
||||
s,
|
||||
get,
|
||||
fromPred,
|
||||
O.Fold(F.Constant(s), F.Bind1st(set, s)),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// FromPredicate creates an optional from getter and setter functions. It checks
|
||||
// for optional values and the correct update procedure
|
||||
func FromPredicate[S, A any](pred func(A) bool) func(func(S) A, func(S, A) S) Optional[S, A] {
|
||||
return fromPredicate(MakeOptional[S, A], pred)
|
||||
}
|
||||
|
||||
// FromPredicate creates an optional from getter and setter functions. It checks
|
||||
// for optional values and the correct update procedure
|
||||
func FromPredicateRef[S, A any](pred func(A) bool) func(func(*S) A, func(*S, A) *S) Optional[*S, A] {
|
||||
return fromPredicate(MakeOptionalRef[S, A], pred)
|
||||
}
|
||||
|
||||
func imap[S, A, B any](sa Optional[S, A], ab func(A) B, ba func(B) A) Optional[S, B] {
|
||||
return MakeOptional(
|
||||
F.Flow2(sa.GetOption, O.Map(ab)),
|
||||
func(s S, b B) S {
|
||||
return sa.Set(ba(b))(s)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// IMap implements a bidirectional mapping of the transform
|
||||
func IMap[S, A, B any](ab func(A) B, ba func(B) A) func(Optional[S, A]) Optional[S, B] {
|
||||
return func(sa Optional[S, A]) Optional[S, B] {
|
||||
return imap(sa, ab, ba)
|
||||
}
|
||||
}
|
||||
|
||||
func ModifyOption[S, A any](f func(A) A) func(Optional[S, A]) func(S) O.Option[S] {
|
||||
return func(o Optional[S, A]) func(S) O.Option[S] {
|
||||
return func(s S) O.Option[S] {
|
||||
return optionalModifyOption(f, o, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SetOption[S, A any](a A) func(Optional[S, A]) func(S) O.Option[S] {
|
||||
return ModifyOption[S](F.Constant1[A](a))
|
||||
}
|
||||
|
||||
func ichain[S, A, B any](sa Optional[S, A], ab func(A) O.Option[B], ba func(B) O.Option[A]) Optional[S, B] {
|
||||
return MakeOptional(
|
||||
F.Flow2(sa.GetOption, O.Chain(ab)),
|
||||
func(s S, b B) S {
|
||||
return O.MonadFold(ba(b), F.Constant(F.Identity[S]), sa.Set)(s)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// IChain implements a bidirectional mapping of the transform if the transform can produce optionals (e.g. in case of type mappings)
|
||||
func IChain[S, A, B any](ab func(A) O.Option[B], ba func(B) O.Option[A]) func(Optional[S, A]) Optional[S, B] {
|
||||
return func(sa Optional[S, A]) Optional[S, B] {
|
||||
return ichain(sa, ab, ba)
|
||||
}
|
||||
}
|
||||
|
||||
// IChainAny implements a bidirectional mapping to and from any
|
||||
func IChainAny[S, A any]() func(Optional[S, any]) Optional[S, A] {
|
||||
fromAny := O.ToType[A]
|
||||
toAny := O.ToAny[A]
|
||||
return func(sa Optional[S, any]) Optional[S, A] {
|
||||
return ichain(sa, fromAny, toAny)
|
||||
}
|
||||
}
|
64
optics/optional/optional_test.go
Normal file
64
optics/optional/optional_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// 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 (
|
||||
"testing"
|
||||
|
||||
F "github.com/IBM/fp-go/function"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type (
|
||||
Phone struct {
|
||||
number string
|
||||
}
|
||||
|
||||
Employment struct {
|
||||
phone *Phone
|
||||
}
|
||||
|
||||
Info struct {
|
||||
employment *Employment
|
||||
}
|
||||
|
||||
Response struct {
|
||||
info *Info
|
||||
}
|
||||
)
|
||||
|
||||
func (response *Response) GetInfo() *Info {
|
||||
return response.info
|
||||
}
|
||||
|
||||
func (response *Response) SetInfo(info *Info) *Response {
|
||||
response.info = info
|
||||
return response
|
||||
}
|
||||
|
||||
var (
|
||||
responseOptional = FromPredicateRef[Response](F.IsNonNil[Info])((*Response).GetInfo, (*Response).SetInfo)
|
||||
|
||||
sampleResponse = Response{info: &Info{}}
|
||||
sampleEmptyResponse = Response{}
|
||||
)
|
||||
|
||||
func TestOptional(t *testing.T) {
|
||||
assert.Equal(t, O.Of(sampleResponse.info), responseOptional.GetOption(&sampleResponse))
|
||||
assert.Equal(t, O.None[*Info](), responseOptional.GetOption(&sampleEmptyResponse))
|
||||
}
|
42
optics/optional/prism/prism.go
Normal file
42
optics/optional/prism/prism.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// 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 prism
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/function"
|
||||
OPT "github.com/IBM/fp-go/optics/optional"
|
||||
P "github.com/IBM/fp-go/optics/prism"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
)
|
||||
|
||||
// PrismAsOptional converts a prism into an optional
|
||||
func PrismAsOptional[S, A any](sa P.Prism[S, A]) OPT.Optional[S, A] {
|
||||
return OPT.MakeOptional(
|
||||
sa.GetOption,
|
||||
func(s S, a A) S {
|
||||
return P.Set[S](a)(sa)(s)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func PrismSome[A any]() P.Prism[O.Option[A], A] {
|
||||
return P.MakePrism(F.Identity[O.Option[A]], O.Some[A])
|
||||
}
|
||||
|
||||
// Some returns a `Optional` from a `Optional` focused on the `Some` of a `Option` type.
|
||||
func Some[S, A any](soa OPT.Optional[S, O.Option[A]]) OPT.Optional[S, A] {
|
||||
return OPT.Compose[S](PrismAsOptional(PrismSome[A]()))(soa)
|
||||
}
|
37
optics/optional/record/generic/generic.go
Normal file
37
optics/optional/record/generic/generic.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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 (
|
||||
OP "github.com/IBM/fp-go/optics/optional"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
RR "github.com/IBM/fp-go/record/generic"
|
||||
)
|
||||
|
||||
func setter[M ~map[K]V, K comparable, V any](key K) func(M, V) M {
|
||||
return func(dst M, value V) M {
|
||||
return RR.UpsertAt[M](key, value)(dst)
|
||||
}
|
||||
}
|
||||
|
||||
func getter[M ~map[K]V, K comparable, V any](key K) func(M) O.Option[V] {
|
||||
return RR.Lookup[M](key)
|
||||
}
|
||||
|
||||
// AtKey returns a Optional that gets and sets properties of a map
|
||||
func AtKey[M ~map[K]V, K comparable, V any](key K) OP.Optional[M, V] {
|
||||
return OP.MakeOptional(getter[M](key), setter[M](key))
|
||||
}
|
26
optics/optional/record/record.go
Normal file
26
optics/optional/record/record.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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 (
|
||||
OP "github.com/IBM/fp-go/optics/optional"
|
||||
G "github.com/IBM/fp-go/optics/optional/record/generic"
|
||||
)
|
||||
|
||||
// FromProperty returns a Optional that gets and sets properties of a map
|
||||
func AtKey[K comparable, V any](key K) OP.Optional[map[K]V, V] {
|
||||
return G.AtKey[map[K]V](key)
|
||||
}
|
100
optics/optional/record/record_test.go
Normal file
100
optics/optional/record/record_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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"
|
||||
OP "github.com/IBM/fp-go/optics/optional"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
ON "github.com/IBM/fp-go/option/number"
|
||||
RR "github.com/IBM/fp-go/record"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type (
|
||||
GenericMap = map[string]any
|
||||
)
|
||||
|
||||
func TestOptionalRecord(t *testing.T) {
|
||||
// sample record
|
||||
r := RR.Singleton("key", "value")
|
||||
|
||||
// extract values
|
||||
optKey := AtKey[string, string]("key")
|
||||
optKey1 := AtKey[string, string]("key1")
|
||||
|
||||
// check if we can get the key
|
||||
assert.Equal(t, O.Of("value"), optKey.GetOption(r))
|
||||
assert.Equal(t, O.None[string](), optKey1.GetOption(r))
|
||||
|
||||
// check if we can set a value
|
||||
r1 := optKey1.Set("value1")(r)
|
||||
|
||||
// check if we can get the key
|
||||
assert.Equal(t, O.Of("value"), optKey.GetOption(r))
|
||||
assert.Equal(t, O.None[string](), optKey1.GetOption(r))
|
||||
// check if we can get the key
|
||||
assert.Equal(t, O.Of("value"), optKey.GetOption(r1))
|
||||
assert.Equal(t, O.Of("value1"), optKey1.GetOption(r1))
|
||||
}
|
||||
|
||||
func TestOptionalWithType(t *testing.T) {
|
||||
// sample record
|
||||
r := RR.Singleton("key", "1")
|
||||
// convert between string and int
|
||||
// writes a key
|
||||
optStringKey := AtKey[string, string]("key")
|
||||
optIntKey := F.Pipe1(
|
||||
optStringKey,
|
||||
OP.IChain[map[string]string](ON.Atoi, ON.Itoa),
|
||||
)
|
||||
// test the scenarions
|
||||
assert.Equal(t, O.Of("1"), optStringKey.GetOption(r))
|
||||
assert.Equal(t, O.Of(1), optIntKey.GetOption(r))
|
||||
// modify
|
||||
r1 := optIntKey.Set(2)(r)
|
||||
assert.Equal(t, O.Of("2"), optStringKey.GetOption(r1))
|
||||
assert.Equal(t, O.Of(2), optIntKey.GetOption(r1))
|
||||
}
|
||||
|
||||
// func TestNestedRecord(t *testing.T) {
|
||||
// // some sample data
|
||||
// x := GenericMap{
|
||||
// "a": GenericMap{
|
||||
// "b": "1",
|
||||
// },
|
||||
// }
|
||||
// // accessor for first level
|
||||
// optA := F.Pipe1(
|
||||
// AtKey[string, any]("a"),
|
||||
// OP.IChainAny[GenericMap, GenericMap](),
|
||||
// )
|
||||
// optB := F.Pipe2(
|
||||
// AtKey[string, any]("b"),
|
||||
// OP.IChainAny[GenericMap, string](),
|
||||
// OP.IChain[GenericMap](ON.Atoi, ON.Itoa),
|
||||
// )
|
||||
// // go directly to b
|
||||
// optAB := F.Pipe1(
|
||||
// optA,
|
||||
// OP.Compose[GenericMap](optB),
|
||||
// )
|
||||
// // access the value of b
|
||||
// assert.Equal(t, O.Of(1), optAB.GetOption(x))
|
||||
// }
|
117
optics/prism/prism.go
Normal file
117
optics/prism/prism.go
Normal file
@@ -0,0 +1,117 @@
|
||||
// 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.
|
||||
|
||||
// Prism is an optic used to select part of a sum type.
|
||||
package prism
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/function"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
)
|
||||
|
||||
type (
|
||||
// Prism is an optic used to select part of a sum type.
|
||||
Prism[S, A any] interface {
|
||||
GetOption(s S) O.Option[A]
|
||||
ReverseGet(a A) S
|
||||
}
|
||||
|
||||
prismImpl[S, A any] struct {
|
||||
get func(S) O.Option[A]
|
||||
rev func(A) S
|
||||
}
|
||||
)
|
||||
|
||||
func (prism prismImpl[S, A]) GetOption(s S) O.Option[A] {
|
||||
return prism.get(s)
|
||||
}
|
||||
|
||||
func (prism prismImpl[S, A]) ReverseGet(a A) S {
|
||||
return prism.rev(a)
|
||||
}
|
||||
|
||||
func MakePrism[S, A any](get func(S) O.Option[A], rev func(A) S) Prism[S, A] {
|
||||
return prismImpl[S, A]{get, rev}
|
||||
}
|
||||
|
||||
// Id returns a prism implementing the identity operation
|
||||
func Id[S any]() Prism[S, S] {
|
||||
return MakePrism(O.Some[S], F.Identity[S])
|
||||
}
|
||||
|
||||
func FromPredicate[S any](pred func(S) bool) Prism[S, S] {
|
||||
return MakePrism(O.FromPredicate(pred), F.Identity[S])
|
||||
}
|
||||
|
||||
// Compose composes a `Prism` with a `Prism`.
|
||||
func Compose[S, A, B any](ab Prism[A, B]) func(Prism[S, A]) Prism[S, B] {
|
||||
return func(sa Prism[S, A]) Prism[S, B] {
|
||||
return MakePrism(F.Flow2(
|
||||
sa.GetOption,
|
||||
O.Chain(ab.GetOption),
|
||||
), F.Flow2(
|
||||
ab.ReverseGet,
|
||||
sa.ReverseGet,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
func prismModifyOption[S, A any](f func(A) A, sa Prism[S, A], s S) O.Option[S] {
|
||||
return F.Pipe2(
|
||||
s,
|
||||
sa.GetOption,
|
||||
O.Map(F.Flow2(
|
||||
f,
|
||||
sa.ReverseGet,
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
func prismModify[S, A any](f func(A) A, sa Prism[S, A], s S) S {
|
||||
return F.Pipe1(
|
||||
prismModifyOption(f, sa, s),
|
||||
O.GetOrElse(F.Constant(s)),
|
||||
)
|
||||
}
|
||||
|
||||
func prismSet[S, A any](a A) func(Prism[S, A]) func(S) S {
|
||||
return F.Curry3(prismModify[S, A])(F.Constant1[A](a))
|
||||
}
|
||||
|
||||
func Set[S, A any](a A) func(Prism[S, A]) func(S) S {
|
||||
return F.Curry3(prismModify[S, A])(F.Constant1[A](a))
|
||||
}
|
||||
|
||||
func prismSome[A any]() Prism[O.Option[A], A] {
|
||||
return MakePrism(F.Identity[O.Option[A]], O.Some[A])
|
||||
}
|
||||
|
||||
// Some returns a `Prism` from a `Prism` focused on the `Some` of a `Option` type.
|
||||
func Some[S, A any](soa Prism[S, O.Option[A]]) Prism[S, A] {
|
||||
return Compose[S](prismSome[A]())(soa)
|
||||
}
|
||||
|
||||
func imap[S, A, B any](sa Prism[S, A], ab func(A) B, ba func(B) A) Prism[S, B] {
|
||||
return MakePrism(
|
||||
F.Flow2(sa.GetOption, O.Map(ab)),
|
||||
F.Flow2(ba, sa.ReverseGet),
|
||||
)
|
||||
}
|
||||
|
||||
func IMap[S, A, B any](ab func(A) B, ba func(B) A) func(Prism[S, A]) Prism[S, B] {
|
||||
return func(sa Prism[S, A]) Prism[S, B] {
|
||||
return imap(sa, ab, ba)
|
||||
}
|
||||
}
|
31
optics/prism/prism_test.go
Normal file
31
optics/prism/prism_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// 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 prism
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
F "github.com/IBM/fp-go/function"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSome(t *testing.T) {
|
||||
somePrism := MakePrism(F.Identity[O.Option[int]], O.Some[int])
|
||||
|
||||
assert.Equal(t, O.Some(1), somePrism.GetOption(O.Some(1)))
|
||||
|
||||
}
|
46
optics/prism/traversal.go
Normal file
46
optics/prism/traversal.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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 prism
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/function"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
)
|
||||
|
||||
// AsTraversal converts a prism to a traversal
|
||||
func AsTraversal[R ~func(func(A) HKTA) func(S) HKTS, S, A, HKTS, HKTA any](
|
||||
fof func(S) HKTS,
|
||||
fmap func(HKTA, func(A) S) HKTS,
|
||||
) func(Prism[S, A]) R {
|
||||
return func(sa Prism[S, A]) R {
|
||||
return func(f func(a A) HKTA) func(S) HKTS {
|
||||
return func(s S) HKTS {
|
||||
return F.Pipe2(
|
||||
s,
|
||||
sa.GetOption,
|
||||
O.Fold(
|
||||
F.Nullary2(F.Constant(s), fof),
|
||||
func(a A) HKTS {
|
||||
return fmap(f(a), func(a A) S {
|
||||
return prismModify(F.Constant1[A](a), sa, s)
|
||||
})
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
optics/traversal/array/array.go
Normal file
16
optics/traversal/array/array.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// 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 array
|
28
optics/traversal/array/const/traversal.go
Normal file
28
optics/traversal/array/const/traversal.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// 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 identity
|
||||
|
||||
import (
|
||||
C "github.com/IBM/fp-go/constant"
|
||||
M "github.com/IBM/fp-go/monoid"
|
||||
AR "github.com/IBM/fp-go/optics/traversal/array/generic/const"
|
||||
G "github.com/IBM/fp-go/optics/traversal/generic"
|
||||
)
|
||||
|
||||
// FromArray returns a traversal from an array for the identity monad
|
||||
func FromArray[E, A any](m M.Monoid[E]) G.Traversal[[]A, A, C.Const[E, []A], C.Const[E, A]] {
|
||||
return AR.FromArray[[]A, E, A](m)
|
||||
}
|
32
optics/traversal/array/generic/const/traversal.go
Normal file
32
optics/traversal/array/generic/const/traversal.go
Normal 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 generic
|
||||
|
||||
import (
|
||||
C "github.com/IBM/fp-go/constant"
|
||||
M "github.com/IBM/fp-go/monoid"
|
||||
AR "github.com/IBM/fp-go/optics/traversal/array/generic"
|
||||
G "github.com/IBM/fp-go/optics/traversal/generic"
|
||||
)
|
||||
|
||||
// FromArray returns a traversal from an array for the const monad
|
||||
func FromArray[GA ~[]A, E, A any](m M.Monoid[E]) G.Traversal[GA, A, C.Const[E, GA], C.Const[E, A]] {
|
||||
return AR.FromArray[GA, GA, A, A, C.Const[E, A], C.Const[E, func(A) GA], C.Const[E, GA]](
|
||||
C.Of[E, GA](m),
|
||||
C.Map[E, GA, func(A) GA],
|
||||
C.Ap[E, A, GA](m),
|
||||
)
|
||||
}
|
31
optics/traversal/array/generic/identity/traversal.go
Normal file
31
optics/traversal/array/generic/identity/traversal.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// 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 (
|
||||
I "github.com/IBM/fp-go/identity"
|
||||
AR "github.com/IBM/fp-go/optics/traversal/array/generic"
|
||||
G "github.com/IBM/fp-go/optics/traversal/generic"
|
||||
)
|
||||
|
||||
// FromArray returns a traversal from an array for the identity monad
|
||||
func FromArray[GA ~[]A, A any]() G.Traversal[GA, A, GA, A] {
|
||||
return AR.FromArray[GA, GA, A, A, A, func(A) GA, GA](
|
||||
I.Of[GA],
|
||||
I.Map[GA, func(A) GA],
|
||||
I.Ap[GA, A],
|
||||
)
|
||||
}
|
34
optics/traversal/array/generic/traversal.go
Normal file
34
optics/traversal/array/generic/traversal.go
Normal 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 generic
|
||||
|
||||
import (
|
||||
AR "github.com/IBM/fp-go/internal/array"
|
||||
G "github.com/IBM/fp-go/optics/traversal/generic"
|
||||
)
|
||||
|
||||
// FromArray returns a traversal from an array
|
||||
func FromArray[GA ~[]A, GB ~[]B, A, B, HKTB, HKTAB, HKTRB any](
|
||||
fof func(GB) HKTRB,
|
||||
fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB,
|
||||
fap func(HKTB) func(HKTAB) HKTRB,
|
||||
) G.Traversal[GA, A, HKTRB, HKTB] {
|
||||
return func(f func(A) HKTB) func(s GA) HKTRB {
|
||||
return func(s GA) HKTRB {
|
||||
return AR.MonadTraverse(fof, fmap, fap, s, f)
|
||||
}
|
||||
}
|
||||
}
|
26
optics/traversal/array/identity/traversal.go
Normal file
26
optics/traversal/array/identity/traversal.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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 identity
|
||||
|
||||
import (
|
||||
AR "github.com/IBM/fp-go/optics/traversal/array/generic/identity"
|
||||
G "github.com/IBM/fp-go/optics/traversal/generic"
|
||||
)
|
||||
|
||||
// FromArray returns a traversal from an array for the identity monad
|
||||
func FromArray[A any]() G.Traversal[[]A, A, []A, A] {
|
||||
return AR.FromArray[[]A, A]()
|
||||
}
|
34
optics/traversal/either/traversal.go
Normal file
34
optics/traversal/either/traversal.go
Normal 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 either
|
||||
|
||||
import (
|
||||
ET "github.com/IBM/fp-go/either"
|
||||
T "github.com/IBM/fp-go/optics/traversal/generic"
|
||||
)
|
||||
|
||||
type (
|
||||
Traversal[E, S, A any] T.Traversal[S, A, ET.Either[E, S], ET.Either[E, A]]
|
||||
)
|
||||
|
||||
func Compose[
|
||||
E, S, A, B any](ab Traversal[E, A, B]) func(Traversal[E, S, A]) Traversal[E, S, B] {
|
||||
return T.Compose[
|
||||
Traversal[E, A, B],
|
||||
Traversal[E, S, A],
|
||||
Traversal[E, S, B],
|
||||
](ab)
|
||||
}
|
73
optics/traversal/generic/traversal.go
Normal file
73
optics/traversal/generic/traversal.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// 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 (
|
||||
AR "github.com/IBM/fp-go/array/generic"
|
||||
C "github.com/IBM/fp-go/constant"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
)
|
||||
|
||||
type (
|
||||
Traversal[S, A, HKTS, HKTA any] func(func(A) HKTA) func(S) HKTS
|
||||
)
|
||||
|
||||
func Compose[
|
||||
TAB ~func(func(B) HKTB) func(A) HKTA,
|
||||
TSA ~func(func(A) HKTA) func(S) HKTS,
|
||||
TSB ~func(func(B) HKTB) func(S) HKTS,
|
||||
S, A, B, HKTS, HKTA, HKTB any](ab TAB) func(TSA) TSB {
|
||||
return func(sa TSA) TSB {
|
||||
return F.Flow2(ab, sa)
|
||||
}
|
||||
}
|
||||
|
||||
func FromTraversable[
|
||||
TAB ~func(func(A) HKTFA) func(HKTTA) HKTAA,
|
||||
A,
|
||||
HKTTA,
|
||||
HKTFA,
|
||||
HKTAA any](
|
||||
traverseF func(HKTTA, func(A) HKTFA) HKTAA,
|
||||
) TAB {
|
||||
return F.Bind1st(F.Bind2nd[HKTTA, func(A) HKTFA, HKTAA], traverseF)
|
||||
}
|
||||
|
||||
// FoldMap maps each target to a `Monoid` and combines the result
|
||||
func FoldMap[M, S, A any](f func(A) M) func(sa Traversal[S, A, C.Const[M, S], C.Const[M, A]]) func(S) M {
|
||||
return func(sa Traversal[S, A, C.Const[M, S], C.Const[M, A]]) func(S) M {
|
||||
return F.Flow2(
|
||||
F.Pipe1(
|
||||
F.Flow2(f, C.Make[M, A]),
|
||||
sa,
|
||||
),
|
||||
C.Unwrap[M, S],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Fold maps each target to a `Monoid` and combines the result
|
||||
func Fold[S, A any](sa Traversal[S, A, C.Const[A, S], C.Const[A, A]]) func(S) A {
|
||||
return FoldMap[A, S, A](F.Identity[A])(sa)
|
||||
}
|
||||
|
||||
// GetAll gets all the targets of a traversal
|
||||
func GetAll[GA ~[]A, S, A any](s S) func(sa Traversal[S, A, C.Const[GA, S], C.Const[GA, A]]) GA {
|
||||
fmap := FoldMap[GA, S, A](AR.Of[GA, A])
|
||||
return func(sa Traversal[S, A, C.Const[GA, S], C.Const[GA, A]]) GA {
|
||||
return fmap(sa)(s)
|
||||
}
|
||||
}
|
34
optics/traversal/option/traversal.go
Normal file
34
optics/traversal/option/traversal.go
Normal 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 option
|
||||
|
||||
import (
|
||||
T "github.com/IBM/fp-go/optics/traversal/generic"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
)
|
||||
|
||||
type (
|
||||
Traversal[S, A any] T.Traversal[S, A, O.Option[S], O.Option[A]]
|
||||
)
|
||||
|
||||
func Compose[
|
||||
S, A, B any](ab Traversal[A, B]) func(Traversal[S, A]) Traversal[S, B] {
|
||||
return T.Compose[
|
||||
Traversal[A, B],
|
||||
Traversal[S, A],
|
||||
Traversal[S, B],
|
||||
](ab)
|
||||
}
|
28
optics/traversal/record/const/traversal.go
Normal file
28
optics/traversal/record/const/traversal.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// 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 (
|
||||
C "github.com/IBM/fp-go/constant"
|
||||
M "github.com/IBM/fp-go/monoid"
|
||||
G "github.com/IBM/fp-go/optics/traversal/generic"
|
||||
RR "github.com/IBM/fp-go/optics/traversal/record/generic/const"
|
||||
)
|
||||
|
||||
// FromRecord returns a traversal from an array for the const monad
|
||||
func FromRecord[E, K comparable, A any](m M.Monoid[E]) G.Traversal[map[K]A, A, C.Const[E, map[K]A], C.Const[E, A]] {
|
||||
return RR.FromRecord[map[K]A, E, K, A](m)
|
||||
}
|
32
optics/traversal/record/generic/const/traversal.go
Normal file
32
optics/traversal/record/generic/const/traversal.go
Normal 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 generic
|
||||
|
||||
import (
|
||||
C "github.com/IBM/fp-go/constant"
|
||||
M "github.com/IBM/fp-go/monoid"
|
||||
G "github.com/IBM/fp-go/optics/traversal/generic"
|
||||
RR "github.com/IBM/fp-go/optics/traversal/record/generic"
|
||||
)
|
||||
|
||||
// FromRecord returns a traversal from an array for the const monad
|
||||
func FromRecord[MA ~map[K]A, E, K comparable, A any](m M.Monoid[E]) G.Traversal[MA, A, C.Const[E, MA], C.Const[E, A]] {
|
||||
return RR.FromRecord[MA, MA, K, A, A, C.Const[E, A], C.Const[E, func(A) MA], C.Const[E, MA]](
|
||||
C.Of[E, MA](m),
|
||||
C.Map[E, MA, func(A) MA],
|
||||
C.Ap[E, A, MA](m),
|
||||
)
|
||||
}
|
31
optics/traversal/record/generic/identity/traversal.go
Normal file
31
optics/traversal/record/generic/identity/traversal.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// 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 (
|
||||
I "github.com/IBM/fp-go/identity"
|
||||
G "github.com/IBM/fp-go/optics/traversal/generic"
|
||||
RR "github.com/IBM/fp-go/optics/traversal/record/generic"
|
||||
)
|
||||
|
||||
// FromRecord returns a traversal from a record for the identity monad
|
||||
func FromRecord[MA ~map[K]A, K comparable, A any]() G.Traversal[MA, A, MA, A] {
|
||||
return RR.FromRecord[MA, MA, K, A, A, A, func(A) MA, MA](
|
||||
I.Of[MA],
|
||||
I.Map[MA, func(A) MA],
|
||||
I.Ap[MA, A],
|
||||
)
|
||||
}
|
34
optics/traversal/record/generic/traversal.go
Normal file
34
optics/traversal/record/generic/traversal.go
Normal 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 generic
|
||||
|
||||
import (
|
||||
R "github.com/IBM/fp-go/internal/record"
|
||||
G "github.com/IBM/fp-go/optics/traversal/generic"
|
||||
)
|
||||
|
||||
// FromRecord returns a traversal from a record
|
||||
func FromRecord[MA ~map[K]A, MB ~map[K]B, K comparable, A, B, HKTB, HKTAB, HKTRB any](
|
||||
fof func(MB) HKTRB,
|
||||
fmap func(func(MB) func(B) MB) func(HKTRB) HKTAB,
|
||||
fap func(HKTB) func(HKTAB) HKTRB,
|
||||
) G.Traversal[MA, A, HKTRB, HKTB] {
|
||||
return func(f func(A) HKTB) func(s MA) HKTRB {
|
||||
return func(s MA) HKTRB {
|
||||
return R.MonadTraverse(fof, fmap, fap, s, f)
|
||||
}
|
||||
}
|
||||
}
|
26
optics/traversal/record/identity/traversal.go
Normal file
26
optics/traversal/record/identity/traversal.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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 identity
|
||||
|
||||
import (
|
||||
G "github.com/IBM/fp-go/optics/traversal/generic"
|
||||
RR "github.com/IBM/fp-go/optics/traversal/record/generic/identity"
|
||||
)
|
||||
|
||||
// FromRecord returns a traversal from an array for the identity monad
|
||||
func FromRecord[K comparable, A any]() G.Traversal[map[K]A, A, map[K]A, A] {
|
||||
return RR.FromRecord[map[K]A, K, A]()
|
||||
}
|
16
optics/traversal/record/traversal.go
Normal file
16
optics/traversal/record/traversal.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// 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
|
66
optics/traversal/traversal.go
Normal file
66
optics/traversal/traversal.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// 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 traversal
|
||||
|
||||
import (
|
||||
C "github.com/IBM/fp-go/constant"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
G "github.com/IBM/fp-go/optics/traversal/generic"
|
||||
)
|
||||
|
||||
// Id is the identity constructor of a traversal
|
||||
func Id[S, A any]() G.Traversal[S, S, A, A] {
|
||||
return F.Identity[func(S) A]
|
||||
}
|
||||
|
||||
// Modify applies a transformation function to a traversal
|
||||
func Modify[S, A any](f func(A) A) func(sa G.Traversal[S, A, S, A]) func(S) S {
|
||||
return func(sa G.Traversal[S, A, S, A]) func(S) S {
|
||||
return sa(f)
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets a constant value for all values of the traversal
|
||||
func Set[S, A any](a A) func(sa G.Traversal[S, A, S, A]) func(S) S {
|
||||
return Modify[S, A](F.Constant1[A](a))
|
||||
}
|
||||
|
||||
// FoldMap maps each target to a `Monoid` and combines the result
|
||||
func FoldMap[M, S, A any](f func(A) M) func(sa G.Traversal[S, A, C.Const[M, S], C.Const[M, A]]) func(S) M {
|
||||
return G.FoldMap[M, S, A](f)
|
||||
}
|
||||
|
||||
// Fold maps each target to a `Monoid` and combines the result
|
||||
func Fold[S, A any](sa G.Traversal[S, A, C.Const[A, S], C.Const[A, A]]) func(S) A {
|
||||
return G.Fold[S, A](sa)
|
||||
}
|
||||
|
||||
// GetAll gets all the targets of a traversal
|
||||
func GetAll[S, A any](s S) func(sa G.Traversal[S, A, C.Const[[]A, S], C.Const[[]A, A]]) []A {
|
||||
return G.GetAll[[]A, S, A](s)
|
||||
}
|
||||
|
||||
// Compose composes two traversables
|
||||
func Compose[
|
||||
S, A, B, HKTS, HKTA, HKTB any](ab G.Traversal[A, B, HKTA, HKTB]) func(sa G.Traversal[S, A, HKTS, HKTA]) G.Traversal[S, B, HKTS, HKTB] {
|
||||
return G.Compose[
|
||||
G.Traversal[A, B, HKTA, HKTB],
|
||||
G.Traversal[S, A, HKTS, HKTA],
|
||||
G.Traversal[S, B, HKTS, HKTB],
|
||||
S, A, B,
|
||||
HKTS, HKTA, HKTB,
|
||||
](ab)
|
||||
}
|
79
optics/traversal/traversal_test.go
Normal file
79
optics/traversal/traversal_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// 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 traversal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
AR "github.com/IBM/fp-go/array"
|
||||
C "github.com/IBM/fp-go/constant"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
"github.com/IBM/fp-go/internal/utils"
|
||||
N "github.com/IBM/fp-go/number"
|
||||
AT "github.com/IBM/fp-go/optics/traversal/array/const"
|
||||
AI "github.com/IBM/fp-go/optics/traversal/array/identity"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetAll(t *testing.T) {
|
||||
|
||||
as := AR.From(1, 2, 3)
|
||||
|
||||
tr := AT.FromArray[[]int, int](AR.Monoid[int]())
|
||||
|
||||
sa := F.Pipe1(
|
||||
Id[[]int, C.Const[[]int, []int]](),
|
||||
Compose[[]int, []int, int, C.Const[[]int, []int]](tr),
|
||||
)
|
||||
|
||||
getall := GetAll[[]int, int](as)(sa)
|
||||
|
||||
assert.Equal(t, AR.From(1, 2, 3), getall)
|
||||
}
|
||||
|
||||
func TestFold(t *testing.T) {
|
||||
|
||||
monoidSum := N.MonoidSum[int]()
|
||||
|
||||
as := AR.From(1, 2, 3)
|
||||
|
||||
tr := AT.FromArray[int, int](monoidSum)
|
||||
|
||||
sa := F.Pipe1(
|
||||
Id[[]int, C.Const[int, []int]](),
|
||||
Compose[[]int, []int, int, C.Const[int, []int]](tr),
|
||||
)
|
||||
|
||||
folded := Fold[[]int, int](sa)(as)
|
||||
|
||||
assert.Equal(t, 6, folded)
|
||||
}
|
||||
|
||||
func TestTraverse(t *testing.T) {
|
||||
|
||||
as := AR.From(1, 2, 3)
|
||||
|
||||
tr := AI.FromArray[int]()
|
||||
|
||||
sa := F.Pipe1(
|
||||
Id[[]int, []int](),
|
||||
Compose[[]int, []int, int, []int, []int, int](tr),
|
||||
)
|
||||
|
||||
res := sa(utils.Double)(as)
|
||||
|
||||
assert.Equal(t, AR.From(2, 4, 6), res)
|
||||
}
|
Reference in New Issue
Block a user