diff --git a/src/Fable.Python.fsproj b/src/Fable.Python.fsproj index 1a155e9..4920133 100644 --- a/src/Fable.Python.fsproj +++ b/src/Fable.Python.fsproj @@ -25,6 +25,7 @@ + diff --git a/src/stdlib/Functools.fs b/src/stdlib/Functools.fs new file mode 100644 index 0000000..f8b8db0 --- /dev/null +++ b/src/stdlib/Functools.fs @@ -0,0 +1,44 @@ +/// Type bindings for Python functools module: https://docs.python.org/3/library/functools.html +module Fable.Python.Functools + +open Fable.Core + +// fsharplint:disable MemberNames + +[] +type IExports = + // ======================================================================== + // Higher-order functions + // ======================================================================== + + /// Apply a function of two arguments cumulatively to the items of an iterable, + /// reducing it to a single value (fold-left without a seed). + /// See https://docs.python.org/3/library/functools.html#functools.reduce + abstract reduce: func: System.Func<'T, 'T, 'T> * iterable: 'T seq -> 'T + + /// Apply a function of two arguments cumulatively to the items of an iterable, + /// starting with the initializer as the seed value (fold-left with a seed). + /// See https://docs.python.org/3/library/functools.html#functools.reduce + [] + abstract reduce: func: System.Func<'State, 'T, 'State> * iterable: 'T seq * initializer: 'State -> 'State + + // ======================================================================== + // Caching decorators + // ======================================================================== + + /// Wrap func with an LRU (least-recently-used) cache of at most maxsize entries. + /// Returns a memoised callable with the same signature as func. + /// Requires Python 3.8+. + /// See https://docs.python.org/3/library/functools.html#functools.lru_cache + [] + abstract lruCache: maxsize: int * func: ('T -> 'R) -> ('T -> 'R) + + /// Wrap func with an unbounded cache (equivalent to lru_cache(maxsize=None)). + /// Requires Python 3.9+. + /// See https://docs.python.org/3/library/functools.html#functools.cache + [] + abstract cache: func: ('T -> 'R) -> ('T -> 'R) + +/// Higher-order functions and operations on callable objects +[] +let functools: IExports = nativeOnly diff --git a/test/Fable.Python.Test.fsproj b/test/Fable.Python.Test.fsproj index 3c439a7..f37dca6 100644 --- a/test/Fable.Python.Test.fsproj +++ b/test/Fable.Python.Test.fsproj @@ -18,6 +18,7 @@ + diff --git a/test/TestFunctools.fs b/test/TestFunctools.fs new file mode 100644 index 0000000..a0f9bb1 --- /dev/null +++ b/test/TestFunctools.fs @@ -0,0 +1,48 @@ +module Fable.Python.Tests.Functools + +open Fable.Python.Testing +open Fable.Python.Functools + +[] +let ``test reduce sum works`` () = + functools.reduce ((fun a b -> a + b), [ 1; 2; 3; 4; 5 ]) + |> equal 15 + +[] +let ``test reduce product works`` () = + functools.reduce ((fun a b -> a * b), [ 1; 2; 3; 4; 5 ]) + |> equal 120 + +[] +let ``test reduce with initializer works`` () = + functools.reduce ((fun acc x -> acc + x), [ 1; 2; 3 ], 10) + |> equal 16 + +[] +let ``test reduce string fold with initializer works`` () = + functools.reduce ((fun acc s -> acc + s), [ "b"; "c"; "d" ], "a") + |> equal "abcd" + +[] +let ``test lruCache memoises results`` () = + let callCount = ResizeArray() + let expensive (x: int) = + callCount.Add x + x * x + let cached = functools.lruCache (128, expensive) + cached 5 |> equal 25 + cached 5 |> equal 25 + cached 3 |> equal 9 + callCount.Count |> equal 2 + +[] +let ``test cache memoises results`` () = + let callCount = ResizeArray() + let expensive (x: int) = + callCount.Add x + x * 2 + let cached = functools.cache expensive + cached 7 |> equal 14 + cached 7 |> equal 14 + cached 4 |> equal 8 + callCount.Count |> equal 2