Skip to content

Commit 9d7316e

Browse files
xperiandriclaude
andcommitted
feat: FlatList.Parallel module with the vocabulary of the FSharp.Core Array.Parallel module (#27)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent 2785dd1 commit 9d7316e

13 files changed

Lines changed: 2387 additions & 0 deletions

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626
change the builder return it. `replace` and `replaceWith` leave the builder as it is when no element equals the old
2727
value, as `ImmutableArray<'T>.Builder.Replace` does, where `FlatList.replace` throws `ArgumentException`.
2828
* `flatList { }` builds a flat list.
29+
* `FlatList.Parallel` has every function of the FSharp.Core `Array.Parallel` module except the in-place sorts, with its
30+
semantics and exceptions. Its `tryFind`, `tryFindIndex` and `tryPick` return `voption`, `choose` and `tryPick` take
31+
functions that return `voption`, and `groupBy`, `partition`, `partitionWith` and `zip` return struct tuples; the
32+
`Parallel` modules of the `Option` and `Tuple` namespaces repeat them with options and reference tuples.
2933

3034
### Changed
3135

‎src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<Compile Include="Helper\Raise.fs" />
3333
<Compile Include="Helper\ArrayChecks.fs" />
3434
<Compile Include="Helper\Arrays.fs" />
35+
<Compile Include="Helper\ParallelArrays.fs" />
3536
<Compile Include="FlatList\FlatListTypes.fs" />
3637
<Compile Include="FlatList\FlatList.fsi" />
3738
<Compile Include="FlatList\FlatList.fs" />

‎src/FSharp.Collections.Immutable/FlatList/FlatList.fs‎

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,3 +1006,201 @@ let isDefault (array : FlatList<'T>) = array.IsDefault
10061006

10071007
[<CompiledName "IsDefaultOrEmpty">]
10081008
let isDefaultOrEmpty (array : FlatList<'T>) = array.IsDefaultOrEmpty
1009+
1010+
// Most functions unwrap the underlying array without a copy, call the Array.Parallel function, and wrap its fresh result
1011+
// without a copy (ADR 0001, D11), so that they have the semantics and the exceptions of Array.Parallel. Where the
1012+
// Array.Parallel function takes or returns options or builds a tuple per element or per group (choose, tryFind,
1013+
// tryFindIndex, tryPick, zip, groupBy), an algorithm of ParallelArrays with value options and struct tuples runs instead.
1014+
// Array.Parallel sorts a copy of its input, so the shared array is never changed.
1015+
[<RequireQualifiedAccess>]
1016+
module Parallel =
1017+
1018+
// ---- Creation ----
1019+
1020+
[<CompiledName "Initialize">]
1021+
let init count (initializer : int -> 'T) = Array.Parallel.init count initializer |> wrapArray
1022+
1023+
// ---- Search ----
1024+
1025+
[<CompiledName "Exists">]
1026+
let exists predicate (array : FlatList<'T>) = Array.Parallel.exists predicate (unwrapArray (nameof array) array)
1027+
1028+
[<CompiledName "ForAll">]
1029+
let forall predicate (array : FlatList<'T>) = Array.Parallel.forall predicate (unwrapArray (nameof array) array)
1030+
1031+
[<CompiledName "TryFind">]
1032+
let tryFind predicate (array : FlatList<'T>) =
1033+
let source = unwrapArray (nameof array) array
1034+
1035+
match ParallelArrays.tryFindIndex predicate source with
1036+
| ValueSome index -> ValueSome source[index]
1037+
| ValueNone -> ValueNone
1038+
1039+
[<CompiledName "TryFindIndex">]
1040+
let tryFindIndex predicate (array : FlatList<'T>) = ParallelArrays.tryFindIndex predicate (unwrapArray (nameof array) array)
1041+
1042+
[<CompiledName "TryPick">]
1043+
let tryPick (chooser : 'T -> 'U voption) (array : FlatList<'T>) =
1044+
ParallelArrays.tryPick chooser (unwrapArray (nameof array) array)
1045+
1046+
// ---- Transformation ----
1047+
1048+
[<CompiledName "Choose">]
1049+
let choose (chooser : 'T -> 'U voption) (array : FlatList<'T>) =
1050+
ParallelArrays.choose chooser (unwrapArray (nameof array) array)
1051+
|> wrapArray
1052+
1053+
[<CompiledName "Collect">]
1054+
let collect (mapping : 'T -> FlatList<'U>) (array : FlatList<'T>) =
1055+
// As in Array.Parallel.collect, the mapping runs in parallel, and the results are copied in their order into one
1056+
// array of their total length. Array.Parallel.collect dereferences a null result; a default flat list from the
1057+
// mapping throws ArgumentNullException instead, as in the sequential collect (ADR 0001, D6).
1058+
let results = Array.Parallel.map mapping (unwrapArray (nameof array) array)
1059+
let mutable length = 0
1060+
1061+
for result in results do
1062+
checkNotDefault (nameof mapping) result
1063+
length <- length + result.Length
1064+
1065+
let builder = ImmutableArray.CreateBuilder<'U> length
1066+
1067+
for result in results do
1068+
builder.AddRange result
1069+
1070+
builder.MoveToImmutable ()
1071+
1072+
[<CompiledName "Filter">]
1073+
let filter predicate (array : FlatList<'T>) =
1074+
Array.Parallel.filter predicate (unwrapArray (nameof array) array)
1075+
|> wrapArray
1076+
1077+
[<CompiledName "Map">]
1078+
let map (mapping : 'T -> 'U) (array : FlatList<'T>) =
1079+
Array.Parallel.map mapping (unwrapArray (nameof array) array)
1080+
|> wrapArray
1081+
1082+
[<CompiledName "MapIndexed">]
1083+
let mapi (mapping : int -> 'T -> 'U) (array : FlatList<'T>) =
1084+
Array.Parallel.mapi mapping (unwrapArray (nameof array) array)
1085+
|> wrapArray
1086+
1087+
[<CompiledName "Zip">]
1088+
let zip (array1 : FlatList<'T1>) (array2 : FlatList<'T2>) =
1089+
let source1 = unwrapArray (nameof array1) array1
1090+
let source2 = unwrapArray (nameof array2) array2
1091+
ArrayChecks.sameLengths (nameof array1) source1 (nameof array2) source2
1092+
ParallelArrays.zip structPair source1 source2 |> wrapArray
1093+
1094+
// ---- Folding ----
1095+
1096+
// The inline functions can use public members only, so they check and unwrap the array themselves
1097+
[<CompiledName "Average">]
1098+
let inline average (array : FlatList< ^T >) : ^T =
1099+
if array.IsDefault then
1100+
nullArg (nameof array)
1101+
1102+
Array.Parallel.average (
1103+
ImmutableCollectionsMarshal.AsArray array
1104+
|> Unchecked.nonNull
1105+
)
1106+
1107+
[<CompiledName "AverageBy">]
1108+
let inline averageBy ([<InlineIfLambda>] projection : 'T -> ^U) (array : FlatList<'T>) : ^U =
1109+
if array.IsDefault then
1110+
nullArg (nameof array)
1111+
1112+
Array.Parallel.averageBy
1113+
projection
1114+
(ImmutableCollectionsMarshal.AsArray array
1115+
|> Unchecked.nonNull)
1116+
1117+
[<CompiledName "Iterate">]
1118+
let iter action (array : FlatList<'T>) = Array.Parallel.iter action (unwrapArray (nameof array) array)
1119+
1120+
[<CompiledName "IterateIndexed">]
1121+
let iteri action (array : FlatList<'T>) = Array.Parallel.iteri action (unwrapArray (nameof array) array)
1122+
1123+
[<CompiledName "Max">]
1124+
let max (array : FlatList<'T>) = Array.Parallel.max (unwrapArray (nameof array) array)
1125+
1126+
[<CompiledName "MaxBy">]
1127+
let maxBy (projection : 'T -> 'U) (array : FlatList<'T>) = Array.Parallel.maxBy projection (unwrapArray (nameof array) array)
1128+
1129+
[<CompiledName "Min">]
1130+
let min (array : FlatList<'T>) = Array.Parallel.min (unwrapArray (nameof array) array)
1131+
1132+
[<CompiledName "MinBy">]
1133+
let minBy (projection : 'T -> 'U) (array : FlatList<'T>) = Array.Parallel.minBy projection (unwrapArray (nameof array) array)
1134+
1135+
[<CompiledName "Reduce">]
1136+
let reduce reduction (array : FlatList<'T>) = Array.Parallel.reduce reduction (unwrapArray (nameof array) array)
1137+
1138+
[<CompiledName "ReduceBy">]
1139+
let reduceBy (projection : 'T -> 'U) reduction (array : FlatList<'T>) =
1140+
Array.Parallel.reduceBy projection reduction (unwrapArray (nameof array) array)
1141+
1142+
// Array.Parallel.sum dereferences a null array without a check; a default flat list throws ArgumentNullException,
1143+
// which its documentation promises
1144+
[<CompiledName "Sum">]
1145+
let inline sum (array : FlatList< ^T >) : ^T =
1146+
if array.IsDefault then
1147+
nullArg (nameof array)
1148+
1149+
Array.Parallel.sum (
1150+
ImmutableCollectionsMarshal.AsArray array
1151+
|> Unchecked.nonNull
1152+
)
1153+
1154+
[<CompiledName "SumBy">]
1155+
let inline sumBy ([<InlineIfLambda>] projection : 'T -> ^U) (array : FlatList<'T>) : ^U =
1156+
if array.IsDefault then
1157+
nullArg (nameof array)
1158+
1159+
Array.Parallel.sumBy
1160+
projection
1161+
(ImmutableCollectionsMarshal.AsArray array
1162+
|> Unchecked.nonNull)
1163+
1164+
// ---- Partitioning and grouping ----
1165+
1166+
[<CompiledName "GroupBy">]
1167+
let groupBy (projection : 'T -> 'Key) (array : FlatList<'T>) =
1168+
ParallelArrays.groupBy (fun key items -> struct (key, wrapArray items)) projection (unwrapArray (nameof array) array)
1169+
|> wrapArray
1170+
1171+
[<CompiledName "Partition">]
1172+
let partition predicate (array : FlatList<'T>) =
1173+
let accepted, rejected = Array.Parallel.partition predicate (unwrapArray (nameof array) array)
1174+
struct (wrapArray accepted, wrapArray rejected)
1175+
1176+
[<CompiledName "PartitionWith">]
1177+
let partitionWith (partitioner : 'T -> Choice<'T1, 'T2>) (array : FlatList<'T>) =
1178+
let first, second = Array.Parallel.partitionWith partitioner (unwrapArray (nameof array) array)
1179+
struct (wrapArray first, wrapArray second)
1180+
1181+
// ---- Sorting ----
1182+
1183+
[<CompiledName "Sort">]
1184+
let sort (array : FlatList<'T>) =
1185+
Array.Parallel.sort (unwrapArray (nameof array) array)
1186+
|> wrapArray
1187+
1188+
[<CompiledName "SortBy">]
1189+
let sortBy (projection : 'T -> 'Key) (array : FlatList<'T>) =
1190+
Array.Parallel.sortBy projection (unwrapArray (nameof array) array)
1191+
|> wrapArray
1192+
1193+
[<CompiledName "SortByDescending">]
1194+
let sortByDescending (projection : 'T -> 'Key) (array : FlatList<'T>) =
1195+
Array.Parallel.sortByDescending projection (unwrapArray (nameof array) array)
1196+
|> wrapArray
1197+
1198+
[<CompiledName "SortDescending">]
1199+
let sortDescending (array : FlatList<'T>) =
1200+
Array.Parallel.sortDescending (unwrapArray (nameof array) array)
1201+
|> wrapArray
1202+
1203+
[<CompiledName "SortWith">]
1204+
let sortWith comparer (array : FlatList<'T>) =
1205+
Array.Parallel.sortWith comparer (unwrapArray (nameof array) array)
1206+
|> wrapArray

0 commit comments

Comments
 (0)