From 5c77044886a338fb409c17491f6636b11ef8b84e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 17 Apr 2026 04:06:43 +0000
Subject: [PATCH 1/2] feat(stdlib): add itertools module bindings
Add comprehensive F# bindings for Python's itertools module including:
- Infinite iterators: count, cycle, repeat
- Finite iterators: accumulate, chain, chainFromIterable, compress,
dropwhile, filterfalse, groupby, islice, pairwise, takewhile, zip_longest
- Combinatoric iterators: permutations, combinations,
combinationsWithReplacement, product
Also adds 20 tests in TestItertools.fs covering all major functions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
CHANGELOG.md | 1 +
src/Fable.Python.fsproj | 1 +
src/stdlib/Itertools.fs | 190 ++++++++++++++++++++++++++++++++++
test/Fable.Python.Test.fsproj | 1 +
test/TestItertools.fs | 121 ++++++++++++++++++++++
5 files changed, 314 insertions(+)
create mode 100644 src/stdlib/Itertools.fs
create mode 100644 test/TestItertools.fs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 749c832..f72e806 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file.
### ✨ Enhancements
+* Add `itertools` module bindings: `count`, `cycle`, `repeat`, `accumulate`, `chain`, `chainFromIterable`, `compress`, `dropwhile`, `filterfalse`, `groupby`, `islice`, `pairwise`, `takewhile`, `zip_longest`, `permutations`, `combinations`, `combinationsWithReplacement`, `product`
* Add missing `math` module constants: `pi`, `e`, `tau`, `inf`, `nan`
* Add missing `math` module functions: `sqrt`, `degrees`, `radians`, `trunc`, `hypot`, `fsum`, `isqrt`, `prod`, `perm`, `acosh`, `asinh`, `atanh`, `cosh`, `sinh`, `tanh`, `erf`, `erfc`, `gamma`, `lgamma`
* Fix `math.dist` signature to accept float arrays (for multi-dimensional distance)
diff --git a/src/Fable.Python.fsproj b/src/Fable.Python.fsproj
index e7eb9a7..1a155e9 100644
--- a/src/Fable.Python.fsproj
+++ b/src/Fable.Python.fsproj
@@ -24,6 +24,7 @@
+
diff --git a/src/stdlib/Itertools.fs b/src/stdlib/Itertools.fs
new file mode 100644
index 0000000..1c24898
--- /dev/null
+++ b/src/stdlib/Itertools.fs
@@ -0,0 +1,190 @@
+/// Type bindings for Python itertools module: https://docs.python.org/3/library/itertools.html
+module Fable.Python.Itertools
+
+open Fable.Core
+
+// fsharplint:disable MemberNames
+
+[]
+type IExports =
+ // ========================================================================
+ // Infinite iterators
+ // ========================================================================
+
+ /// Make an iterator that returns evenly spaced values starting with number start
+ /// See https://docs.python.org/3/library/itertools.html#itertools.count
+ []
+ abstract count: start: int -> seq
+
+ /// Make an iterator that returns evenly spaced values starting with number start,
+ /// incrementing by step
+ /// See https://docs.python.org/3/library/itertools.html#itertools.count
+ []
+ abstract count: start: int * step: int -> seq
+
+ /// Make an iterator that returns evenly spaced float values
+ /// See https://docs.python.org/3/library/itertools.html#itertools.count
+ []
+ abstract count: start: float -> seq
+
+ /// Make an iterator that returns evenly spaced float values starting with start,
+ /// incrementing by step
+ /// See https://docs.python.org/3/library/itertools.html#itertools.count
+ []
+ abstract count: start: float * step: float -> seq
+
+ /// Make an iterator returning elements from the iterable and saving a copy of each.
+ /// When the iterable is exhausted, return elements from the saved copy. Repeats indefinitely.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.cycle
+ abstract cycle: iterable: 'T seq -> seq<'T>
+
+ /// Make an iterator that returns object elem over and over again. Runs indefinitely.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.repeat
+ abstract repeat: elem: 'T -> seq<'T>
+
+ /// Make an iterator that returns object elem, times times.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.repeat
+ []
+ abstract repeat: elem: 'T * times: int -> seq<'T>
+
+ // ========================================================================
+ // Finite iterators
+ // ========================================================================
+
+ /// Make an iterator that returns accumulated sums
+ /// See https://docs.python.org/3/library/itertools.html#itertools.accumulate
+ abstract accumulate: iterable: 'T seq -> seq<'T>
+
+ /// Make an iterator that returns accumulated results of a binary function.
+ /// Uses a curried function: fun a b -> a + b (Fable wraps it to Python's two-arg call)
+ /// See https://docs.python.org/3/library/itertools.html#itertools.accumulate
+ []
+ abstract accumulate: iterable: 'T seq * func: ('T -> 'T -> 'T) -> seq<'T>
+
+ /// Make an iterator that returns accumulated results of a binary function,
+ /// with an initial value.
+ /// Uses a curried function: fun a b -> a + b (Fable wraps it to Python's two-arg call)
+ /// See https://docs.python.org/3/library/itertools.html#itertools.accumulate
+ []
+ abstract accumulate: iterable: 'T seq * func: ('T -> 'T -> 'T) * initial: 'T -> seq<'T>
+
+ /// Make an iterator that returns elements from the first iterable until it is exhausted,
+ /// then proceeds to the next iterable, until all of the iterables are exhausted.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.chain
+ abstract chain: a: 'T seq * b: 'T seq -> seq<'T>
+
+ /// Make an iterator that chains three iterables together.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.chain
+ abstract chain: a: 'T seq * b: 'T seq * c: 'T seq -> seq<'T>
+
+ /// Make an iterator that chains four iterables together.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.chain
+ abstract chain: a: 'T seq * b: 'T seq * c: 'T seq * d: 'T seq -> seq<'T>
+
+ /// Make an iterator that chains iterables from a single iterable of iterables.
+ /// Equivalent to Python's itertools.chain.from_iterable.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.chain.from_iterable
+ []
+ abstract chainFromIterable: iterable: 'T seq seq -> seq<'T>
+
+ /// Make an iterator that filters elements from data returning only those that have
+ /// a corresponding element in selectors that evaluates to True.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.compress
+ abstract compress: data: 'T seq * selectors: bool seq -> seq<'T>
+
+ /// Make an iterator that drops elements from the iterable as long as the predicate is true;
+ /// afterwards, returns every element.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.dropwhile
+ abstract dropwhile: predicate: ('T -> bool) * iterable: 'T seq -> seq<'T>
+
+ /// Make an iterator that filters elements from iterable returning only those for which
+ /// the predicate returns False.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.filterfalse
+ abstract filterfalse: predicate: ('T -> bool) * iterable: 'T seq -> seq<'T>
+
+ /// Make an iterator that returns consecutive keys and groups from the iterable
+ /// (using identity as the key function).
+ /// Note: The group iterators share the underlying iterable — consume each group before advancing.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.groupby
+ abstract groupby: iterable: 'T seq -> seq<'T * seq<'T>>
+
+ /// Make an iterator that returns consecutive keys and groups from the iterable.
+ /// The key is computed for each element; elements with equal consecutive keys are grouped.
+ /// Note: The group iterators share the underlying iterable — consume each group before advancing.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.groupby
+ abstract groupby: iterable: 'T seq * key: ('T -> 'K) -> seq<'K * seq<'T>>
+
+ /// Make an iterator that returns selected elements from the iterable. Stops at position stop.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.islice
+ []
+ abstract islice: iterable: 'T seq * stop: int -> seq<'T>
+
+ /// Make an iterator that returns selected elements from the iterable, starting at start.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.islice
+ []
+ abstract islice: iterable: 'T seq * start: int * stop: int -> seq<'T>
+
+ /// Make an iterator that returns selected elements from the iterable, with a step.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.islice
+ []
+ abstract islice: iterable: 'T seq * start: int * stop: int * step: int -> seq<'T>
+
+ /// Return successive overlapping pairs taken from the iterable.
+ /// Requires Python 3.10+.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.pairwise
+ abstract pairwise: iterable: 'T seq -> seq<'T * 'T>
+
+ /// Make an iterator that returns elements from the iterable as long as the predicate is true.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.takewhile
+ abstract takewhile: predicate: ('T -> bool) * iterable: 'T seq -> seq<'T>
+
+ /// Make an iterator that aggregates elements from each of the iterables.
+ /// If the iterables are of uneven length, missing values are filled with None (option).
+ /// See https://docs.python.org/3/library/itertools.html#itertools.zip_longest
+ abstract zip_longest: a: 'T seq * b: 'U seq -> seq<'T option * 'U option>
+
+ /// Make an iterator that aggregates elements from each of the iterables.
+ /// If the iterables are of uneven length, missing values are filled with fillvalue.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.zip_longest
+ []
+ abstract zip_longest: a: 'T seq * b: 'T seq * fillvalue: 'T -> seq<'T * 'T>
+
+ // ========================================================================
+ // Combinatoric iterators
+ // ========================================================================
+
+ /// Return successive r-length permutations of elements in the iterable.
+ /// All elements are used (no r limit).
+ /// See https://docs.python.org/3/library/itertools.html#itertools.permutations
+ []
+ abstract permutations: iterable: 'T seq -> seq<'T[]>
+
+ /// Return successive r-length permutations of elements in the iterable.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.permutations
+ []
+ abstract permutations: iterable: 'T seq * r: int -> seq<'T[]>
+
+ /// Return successive r-length combinations of elements in the iterable (no repeated elements).
+ /// See https://docs.python.org/3/library/itertools.html#itertools.combinations
+ []
+ abstract combinations: iterable: 'T seq * r: int -> seq<'T[]>
+
+ /// Return successive r-length combinations of elements in the iterable, allowing individual
+ /// elements to be repeated.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.combinations_with_replacement
+ []
+ abstract combinationsWithReplacement: iterable: 'T seq * r: int -> seq<'T[]>
+
+ /// Return the Cartesian product of the two input iterables.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.product
+ abstract product: a: 'T seq * b: 'U seq -> seq<'T * 'U>
+
+ /// Return the Cartesian product of the iterable with itself, repeated r times.
+ /// Each element is a list of r items.
+ /// See https://docs.python.org/3/library/itertools.html#itertools.product
+ []
+ abstract product: iterable: 'T seq * repeat: int -> seq>
+
+/// Functions creating iterators for efficient looping
+[]
+let itertools: IExports = nativeOnly
diff --git a/test/Fable.Python.Test.fsproj b/test/Fable.Python.Test.fsproj
index 188b590..7b0d3b9 100644
--- a/test/Fable.Python.Test.fsproj
+++ b/test/Fable.Python.Test.fsproj
@@ -17,6 +17,7 @@
+
diff --git a/test/TestItertools.fs b/test/TestItertools.fs
new file mode 100644
index 0000000..0be962d
--- /dev/null
+++ b/test/TestItertools.fs
@@ -0,0 +1,121 @@
+module Fable.Python.Tests.Itertools
+
+open Fable.Python.Testing
+open Fable.Python.Itertools
+
+[]
+let ``test count from start works`` () =
+ itertools.count 1
+ |> Seq.take 4
+ |> Seq.toList
+ |> equal [ 1; 2; 3; 4 ]
+
+[]
+let ``test count with step works`` () =
+ itertools.count (0, 2)
+ |> Seq.take 4
+ |> Seq.toList
+ |> equal [ 0; 2; 4; 6 ]
+
+[]
+let ``test cycle works`` () =
+ itertools.cycle [ 1; 2; 3 ]
+ |> Seq.take 7
+ |> Seq.toList
+ |> equal [ 1; 2; 3; 1; 2; 3; 1 ]
+
+[]
+let ``test repeat with times works`` () =
+ itertools.repeat ("x", 3)
+ |> Seq.toList
+ |> equal [ "x"; "x"; "x" ]
+
+[]
+let ``test accumulate works`` () =
+ itertools.accumulate [ 1; 2; 3; 4; 5 ]
+ |> Seq.toList
+ |> equal [ 1; 3; 6; 10; 15 ]
+
+[]
+let ``test accumulate with func works`` () =
+ itertools.accumulate ([ 1; 2; 3; 4 ], fun a b -> a * b)
+ |> Seq.toList
+ |> equal [ 1; 2; 6; 24 ]
+
+[]
+let ``test chain two sequences works`` () =
+ itertools.chain ([ 1; 2 ], [ 3; 4 ])
+ |> Seq.toList
+ |> equal [ 1; 2; 3; 4 ]
+
+[]
+let ``test chain three sequences works`` () =
+ itertools.chain ([ 1 ], [ 2; 3 ], [ 4; 5 ])
+ |> Seq.toList
+ |> equal [ 1; 2; 3; 4; 5 ]
+
+[]
+let ``test chainFromIterable works`` () =
+ itertools.chainFromIterable [ [ 1; 2 ]; [ 3; 4 ]; [ 5 ] ]
+ |> Seq.toList
+ |> equal [ 1; 2; 3; 4; 5 ]
+
+[]
+let ``test compress works`` () =
+ itertools.compress ([ 1; 2; 3; 4; 5 ], [ true; false; true; false; true ])
+ |> Seq.toList
+ |> equal [ 1; 3; 5 ]
+
+[]
+let ``test dropwhile works`` () =
+ itertools.dropwhile ((fun x -> x < 3), [ 1; 2; 3; 4; 5 ])
+ |> Seq.toList
+ |> equal [ 3; 4; 5 ]
+
+[]
+let ``test filterfalse works`` () =
+ itertools.filterfalse ((fun x -> x % 2 = 0), [ 1; 2; 3; 4; 5 ])
+ |> Seq.toList
+ |> equal [ 1; 3; 5 ]
+
+[]
+let ``test islice with stop works`` () =
+ itertools.islice ([ 1; 2; 3; 4; 5 ], 3)
+ |> Seq.toList
+ |> equal [ 1; 2; 3 ]
+
+[]
+let ``test islice with start and stop works`` () =
+ itertools.islice ([ 1; 2; 3; 4; 5 ], 1, 4)
+ |> Seq.toList
+ |> equal [ 2; 3; 4 ]
+
+[]
+let ``test pairwise works`` () =
+ itertools.pairwise [ 1; 2; 3; 4 ]
+ |> Seq.toList
+ |> equal [ (1, 2); (2, 3); (3, 4) ]
+
+[]
+let ``test takewhile works`` () =
+ itertools.takewhile ((fun x -> x < 4), [ 1; 2; 3; 4; 5 ])
+ |> Seq.toList
+ |> equal [ 1; 2; 3 ]
+
+[]
+let ``test combinations works`` () =
+ itertools.combinations ([ 1; 2; 3 ], 2)
+ |> Seq.toList
+ |> equal [ [| 1; 2 |]; [| 1; 3 |]; [| 2; 3 |] ]
+
+[]
+let ``test permutations with r works`` () =
+ itertools.permutations ([ 1; 2; 3 ], 2)
+ |> Seq.length
+ |> equal 6
+
+[]
+let ``test product two sequences works`` () =
+ itertools.product ([ 1; 2 ], [ "a"; "b" ])
+ |> Seq.toList
+ |> equal [ (1, "a"); (1, "b"); (2, "a"); (2, "b") ]
From 650dd10b6f6603b40d2daf743192e27c1cbd75d9 Mon Sep 17 00:00:00 2001
From: Dag Brattli
Date: Fri, 17 Apr 2026 06:57:36 +0200
Subject: [PATCH 2/2] fix(itertools): correct accumulate callable +
combinatoric return types
- accumulate with func: replace curried lambda bridge with System.Func<_,_,_>.
Fable uncurries F# binary functions to 2-arg Python callables, so the
previous lambda a,b: f(a)(b) wrapper failed at runtime. System.Func
compiles directly to a 2-arg Python callable and also unblocks overload
resolution (curried arg methods can't be overloaded).
- permutations / combinations / combinations_with_replacement / product
(repeat overload): change return type from seq<'T[]> / seq>
to seq<'T seq>. Python itertools yields tuples, which don't compare equal
to F# arrays; seq<'T seq> matches the tuple shape and lets callers pick
their own materialization.
- Revert CHANGELOG.md edit per AGENTS.md.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
CHANGELOG.md | 1 -
src/stdlib/Itertools.fs | 27 +++++++++++++--------------
test/TestItertools.fs | 3 ++-
3 files changed, 15 insertions(+), 16 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f72e806..749c832 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,7 +18,6 @@ All notable changes to this project will be documented in this file.
### ✨ Enhancements
-* Add `itertools` module bindings: `count`, `cycle`, `repeat`, `accumulate`, `chain`, `chainFromIterable`, `compress`, `dropwhile`, `filterfalse`, `groupby`, `islice`, `pairwise`, `takewhile`, `zip_longest`, `permutations`, `combinations`, `combinationsWithReplacement`, `product`
* Add missing `math` module constants: `pi`, `e`, `tau`, `inf`, `nan`
* Add missing `math` module functions: `sqrt`, `degrees`, `radians`, `trunc`, `hypot`, `fsum`, `isqrt`, `prod`, `perm`, `acosh`, `asinh`, `atanh`, `cosh`, `sinh`, `tanh`, `erf`, `erfc`, `gamma`, `lgamma`
* Fix `math.dist` signature to accept float arrays (for multi-dimensional distance)
diff --git a/src/stdlib/Itertools.fs b/src/stdlib/Itertools.fs
index 1c24898..bdb2aaf 100644
--- a/src/stdlib/Itertools.fs
+++ b/src/stdlib/Itertools.fs
@@ -56,17 +56,14 @@ type IExports =
abstract accumulate: iterable: 'T seq -> seq<'T>
/// Make an iterator that returns accumulated results of a binary function.
- /// Uses a curried function: fun a b -> a + b (Fable wraps it to Python's two-arg call)
/// See https://docs.python.org/3/library/itertools.html#itertools.accumulate
- []
- abstract accumulate: iterable: 'T seq * func: ('T -> 'T -> 'T) -> seq<'T>
+ abstract accumulate: iterable: 'T seq * func: System.Func<'T, 'T, 'T> -> seq<'T>
/// Make an iterator that returns accumulated results of a binary function,
/// with an initial value.
- /// Uses a curried function: fun a b -> a + b (Fable wraps it to Python's two-arg call)
/// See https://docs.python.org/3/library/itertools.html#itertools.accumulate
- []
- abstract accumulate: iterable: 'T seq * func: ('T -> 'T -> 'T) * initial: 'T -> seq<'T>
+ []
+ abstract accumulate: iterable: 'T seq * func: System.Func<'T, 'T, 'T> * initial: 'T -> seq<'T>
/// Make an iterator that returns elements from the first iterable until it is exhausted,
/// then proceeds to the next iterable, until all of the iterables are exhausted.
@@ -154,36 +151,38 @@ type IExports =
// ========================================================================
/// Return successive r-length permutations of elements in the iterable.
- /// All elements are used (no r limit).
+ /// All elements are used (no r limit). Each group is a Python tuple, exposed as seq<'T>.
/// See https://docs.python.org/3/library/itertools.html#itertools.permutations
[]
- abstract permutations: iterable: 'T seq -> seq<'T[]>
+ abstract permutations: iterable: 'T seq -> seq<'T seq>
/// Return successive r-length permutations of elements in the iterable.
+ /// Each group is a Python tuple, exposed as seq<'T>.
/// See https://docs.python.org/3/library/itertools.html#itertools.permutations
[]
- abstract permutations: iterable: 'T seq * r: int -> seq<'T[]>
+ abstract permutations: iterable: 'T seq * r: int -> seq<'T seq>
/// Return successive r-length combinations of elements in the iterable (no repeated elements).
+ /// Each group is a Python tuple, exposed as seq<'T>.
/// See https://docs.python.org/3/library/itertools.html#itertools.combinations
[]
- abstract combinations: iterable: 'T seq * r: int -> seq<'T[]>
+ abstract combinations: iterable: 'T seq * r: int -> seq<'T seq>
/// Return successive r-length combinations of elements in the iterable, allowing individual
- /// elements to be repeated.
+ /// elements to be repeated. Each group is a Python tuple, exposed as seq<'T>.
/// See https://docs.python.org/3/library/itertools.html#itertools.combinations_with_replacement
[]
- abstract combinationsWithReplacement: iterable: 'T seq * r: int -> seq<'T[]>
+ abstract combinationsWithReplacement: iterable: 'T seq * r: int -> seq<'T seq>
/// Return the Cartesian product of the two input iterables.
/// See https://docs.python.org/3/library/itertools.html#itertools.product
abstract product: a: 'T seq * b: 'U seq -> seq<'T * 'U>
/// Return the Cartesian product of the iterable with itself, repeated r times.
- /// Each element is a list of r items.
+ /// Each group is a Python tuple, exposed as seq<'T>.
/// See https://docs.python.org/3/library/itertools.html#itertools.product
[]
- abstract product: iterable: 'T seq * repeat: int -> seq>
+ abstract product: iterable: 'T seq * repeat: int -> seq<'T seq>
/// Functions creating iterators for efficient looping
[]
diff --git a/test/TestItertools.fs b/test/TestItertools.fs
index 0be962d..1f9a413 100644
--- a/test/TestItertools.fs
+++ b/test/TestItertools.fs
@@ -105,8 +105,9 @@ let ``test takewhile works`` () =
[]
let ``test combinations works`` () =
itertools.combinations ([ 1; 2; 3 ], 2)
+ |> Seq.map Seq.toList
|> Seq.toList
- |> equal [ [| 1; 2 |]; [| 1; 3 |]; [| 2; 3 |] ]
+ |> equal [ [ 1; 2 ]; [ 1; 3 ]; [ 2; 3 ] ]
[]
let ``test permutations with r works`` () =