Skip to content

Commit 0db2183

Browse files
github-actions[bot]Copilotdbrattliclaude
authored
feat(stdlib): add functools module bindings (#260)
* feat(stdlib): add functools module bindings (reduce, lruCache, cache) Add Fable.Python.Functools module binding Python's functools stdlib: - reduce: fold-left with and without seed value (2 overloads) - lruCache: LRU-memoised wrapper with configurable maxsize - cache: unbounded memoised wrapper (Python 3.9+, lru_cache(maxsize=None)) Includes 6 tests covering all bindings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(stdlib): revert CHANGELOG edit and align reduce callback with Itertools - Revert CHANGELOG.md (AGENTS.md forbids PR edits to it). - Use System.Func<_,_,_> for reduce's binary callback to match the accumulate convention in Itertools and ensure Fable uncurries the callable before Python invokes it with two positional arguments. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Dag Brattli <dag@brattli.net> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3309786 commit 0db2183

4 files changed

Lines changed: 94 additions & 0 deletions

File tree

src/Fable.Python.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<Compile Include="stdlib/Os.fs" />
2626
<Compile Include="stdlib/Heapq.fs" />
2727
<Compile Include="stdlib/Itertools.fs" />
28+
<Compile Include="stdlib/Functools.fs" />
2829
<Compile Include="stdlib/Queue.fs" />
2930
<Compile Include="stdlib/String.fs" />
3031
<Compile Include="stdlib/Sys.fs" />

src/stdlib/Functools.fs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/// Type bindings for Python functools module: https://docs.python.org/3/library/functools.html
2+
module Fable.Python.Functools
3+
4+
open Fable.Core
5+
6+
// fsharplint:disable MemberNames
7+
8+
[<Erase>]
9+
type IExports =
10+
// ========================================================================
11+
// Higher-order functions
12+
// ========================================================================
13+
14+
/// Apply a function of two arguments cumulatively to the items of an iterable,
15+
/// reducing it to a single value (fold-left without a seed).
16+
/// See https://docs.python.org/3/library/functools.html#functools.reduce
17+
abstract reduce: func: System.Func<'T, 'T, 'T> * iterable: 'T seq -> 'T
18+
19+
/// Apply a function of two arguments cumulatively to the items of an iterable,
20+
/// starting with the initializer as the seed value (fold-left with a seed).
21+
/// See https://docs.python.org/3/library/functools.html#functools.reduce
22+
[<Emit("$0.reduce($1, $2, $3)")>]
23+
abstract reduce: func: System.Func<'State, 'T, 'State> * iterable: 'T seq * initializer: 'State -> 'State
24+
25+
// ========================================================================
26+
// Caching decorators
27+
// ========================================================================
28+
29+
/// Wrap func with an LRU (least-recently-used) cache of at most maxsize entries.
30+
/// Returns a memoised callable with the same signature as func.
31+
/// Requires Python 3.8+.
32+
/// See https://docs.python.org/3/library/functools.html#functools.lru_cache
33+
[<Emit("$0.lru_cache(maxsize=int($1))($2)")>]
34+
abstract lruCache: maxsize: int * func: ('T -> 'R) -> ('T -> 'R)
35+
36+
/// Wrap func with an unbounded cache (equivalent to lru_cache(maxsize=None)).
37+
/// Requires Python 3.9+.
38+
/// See https://docs.python.org/3/library/functools.html#functools.cache
39+
[<Emit("$0.cache($1)")>]
40+
abstract cache: func: ('T -> 'R) -> ('T -> 'R)
41+
42+
/// Higher-order functions and operations on callable objects
43+
[<ImportAll("functools")>]
44+
let functools: IExports = nativeOnly

test/Fable.Python.Test.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<Compile Include="TestBuiltinsAttr.fs" />
1919
<Compile Include="TestHeapq.fs" />
2020
<Compile Include="TestItertools.fs" />
21+
<Compile Include="TestFunctools.fs" />
2122
<Compile Include="TestQueue.fs" />
2223
<Compile Include="TestThreading.fs" />
2324
<Compile Include="TestTraceback.fs" />

test/TestFunctools.fs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
module Fable.Python.Tests.Functools
2+
3+
open Fable.Python.Testing
4+
open Fable.Python.Functools
5+
6+
[<Fact>]
7+
let ``test reduce sum works`` () =
8+
functools.reduce ((fun a b -> a + b), [ 1; 2; 3; 4; 5 ])
9+
|> equal 15
10+
11+
[<Fact>]
12+
let ``test reduce product works`` () =
13+
functools.reduce ((fun a b -> a * b), [ 1; 2; 3; 4; 5 ])
14+
|> equal 120
15+
16+
[<Fact>]
17+
let ``test reduce with initializer works`` () =
18+
functools.reduce ((fun acc x -> acc + x), [ 1; 2; 3 ], 10)
19+
|> equal 16
20+
21+
[<Fact>]
22+
let ``test reduce string fold with initializer works`` () =
23+
functools.reduce ((fun acc s -> acc + s), [ "b"; "c"; "d" ], "a")
24+
|> equal "abcd"
25+
26+
[<Fact>]
27+
let ``test lruCache memoises results`` () =
28+
let callCount = ResizeArray<int>()
29+
let expensive (x: int) =
30+
callCount.Add x
31+
x * x
32+
let cached = functools.lruCache (128, expensive)
33+
cached 5 |> equal 25
34+
cached 5 |> equal 25
35+
cached 3 |> equal 9
36+
callCount.Count |> equal 2
37+
38+
[<Fact>]
39+
let ``test cache memoises results`` () =
40+
let callCount = ResizeArray<int>()
41+
let expensive (x: int) =
42+
callCount.Add x
43+
x * 2
44+
let cached = functools.cache expensive
45+
cached 7 |> equal 14
46+
cached 7 |> equal 14
47+
cached 4 |> equal 8
48+
callCount.Count |> equal 2

0 commit comments

Comments
 (0)