mirror of
https://github.com/IBM/fp-go.git
synced 2025-08-10 22:31:32 +02:00
fix: implement simple cache for pure functions
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
30
function/cache.go
Normal file
30
function/cache.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// 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 function
|
||||||
|
|
||||||
|
import (
|
||||||
|
G "github.com/IBM/fp-go/function/generic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache converts a unary function into a unary function that caches the value depending on the parameter
|
||||||
|
func Cache[K comparable, T any](f func(K) T) func(K) T {
|
||||||
|
return G.Cache(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContramapCache converts a unary function into a unary function that caches the value depending on the parameter
|
||||||
|
func ContramapCache[A any, K comparable, T any](kf func(A) K) func(func(A) T) func(A) T {
|
||||||
|
return G.ContramapCache[func(A) T](kf)
|
||||||
|
}
|
50
function/cache_test.go
Normal file
50
function/cache_test.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// 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 function
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCache(t *testing.T) {
|
||||||
|
var count int
|
||||||
|
|
||||||
|
withSideEffect := func(n int) int {
|
||||||
|
count++
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
cached := Cache(withSideEffect)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, count)
|
||||||
|
|
||||||
|
assert.Equal(t, 10, cached(10))
|
||||||
|
assert.Equal(t, 1, count)
|
||||||
|
|
||||||
|
assert.Equal(t, 10, cached(10))
|
||||||
|
assert.Equal(t, 1, count)
|
||||||
|
|
||||||
|
assert.Equal(t, 20, cached(20))
|
||||||
|
assert.Equal(t, 2, count)
|
||||||
|
|
||||||
|
assert.Equal(t, 20, cached(20))
|
||||||
|
assert.Equal(t, 2, count)
|
||||||
|
|
||||||
|
assert.Equal(t, 10, cached(10))
|
||||||
|
assert.Equal(t, 2, count)
|
||||||
|
}
|
65
function/generic/cache.go
Normal file
65
function/generic/cache.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// 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 (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
L "github.com/IBM/fp-go/internal/lazy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache converts a unary function into a unary function that caches the value depending on the parameter
|
||||||
|
func Cache[F ~func(K) T, K comparable, T any](f F) F {
|
||||||
|
return ContramapCache[F](func(k K) K { return k })(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContramapCache converts a unary function into a unary function that caches the value depending on the parameter
|
||||||
|
func ContramapCache[F ~func(A) T, KF func(A) K, A any, K comparable, T any](kf KF) func(F) F {
|
||||||
|
return CacheCallback[F](kf, getOrCreate[K, T]())
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOrCreate is a naive implementation of a cache, without bounds
|
||||||
|
func getOrCreate[K comparable, T any]() func(K, func() func() T) func() T {
|
||||||
|
cache := make(map[K]func() T)
|
||||||
|
var l sync.Mutex
|
||||||
|
|
||||||
|
return func(k K, cb func() func() T) func() T {
|
||||||
|
// only lock to access a lazy accessor to the value
|
||||||
|
l.Lock()
|
||||||
|
existing, ok := cache[k]
|
||||||
|
if !ok {
|
||||||
|
existing = cb()
|
||||||
|
cache[k] = existing
|
||||||
|
}
|
||||||
|
l.Unlock()
|
||||||
|
// compute the value outside of the lock
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheCallback converts a unary function into a unary function that caches the value depending on the parameter
|
||||||
|
func CacheCallback[F ~func(A) T, KF func(A) K, C ~func(K, func() func() T) func() T, A any, K comparable, T any](kf KF, getOrCreate C) func(F) F {
|
||||||
|
return func(f F) F {
|
||||||
|
return func(a A) T {
|
||||||
|
// cache entry
|
||||||
|
return getOrCreate(kf(a), func() func() T {
|
||||||
|
return L.Memoize[func() T](func() T {
|
||||||
|
return f(a)
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
internal/lazy/memoize.go
Normal file
34
internal/lazy/memoize.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 lazy
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// Memoize computes the value of the provided IO monad lazily but exactly once
|
||||||
|
func Memoize[GA ~func() A, A any](ma GA) GA {
|
||||||
|
// synchronization primitives
|
||||||
|
var once sync.Once
|
||||||
|
var result A
|
||||||
|
// callback
|
||||||
|
gen := func() {
|
||||||
|
result = ma()
|
||||||
|
}
|
||||||
|
// returns our memoized wrapper
|
||||||
|
return func() A {
|
||||||
|
once.Do(gen)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@@ -16,11 +16,11 @@
|
|||||||
package generic
|
package generic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
F "github.com/IBM/fp-go/function"
|
F "github.com/IBM/fp-go/function"
|
||||||
C "github.com/IBM/fp-go/internal/chain"
|
C "github.com/IBM/fp-go/internal/chain"
|
||||||
|
L "github.com/IBM/fp-go/internal/lazy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// type IO[A any] = func() A
|
// type IO[A any] = func() A
|
||||||
@@ -119,18 +119,7 @@ func Flatten[GA ~func() A, GAA ~func() GA, A any](mma GAA) GA {
|
|||||||
|
|
||||||
// Memoize computes the value of the provided IO monad lazily but exactly once
|
// Memoize computes the value of the provided IO monad lazily but exactly once
|
||||||
func Memoize[GA ~func() A, A any](ma GA) GA {
|
func Memoize[GA ~func() A, A any](ma GA) GA {
|
||||||
// synchronization primitives
|
return L.Memoize[GA, A](ma)
|
||||||
var once sync.Once
|
|
||||||
var result A
|
|
||||||
// callback
|
|
||||||
gen := func() {
|
|
||||||
result = ma()
|
|
||||||
}
|
|
||||||
// returns our memoized wrapper
|
|
||||||
return func() A {
|
|
||||||
once.Do(gen)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay creates an operation that passes in the value after some delay
|
// Delay creates an operation that passes in the value after some delay
|
||||||
|
@@ -83,7 +83,7 @@ func Flatten[A any](mma Lazy[Lazy[A]]) Lazy[A] {
|
|||||||
return G.Flatten(mma)
|
return G.Flatten(mma)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Memoize computes the value of the provided IO monad lazily but exactly once
|
// Memoize computes the value of the provided [Lazy] monad lazily but exactly once
|
||||||
func Memoize[A any](ma Lazy[A]) Lazy[A] {
|
func Memoize[A any](ma Lazy[A]) Lazy[A] {
|
||||||
return G.Memoize(ma)
|
return G.Memoize(ma)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user