-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding Seq.traverse & sequence functions #277
Merged
Merged
Changes from 6 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
3442e14
Added POC functions
1eyewonder 6875038
benchmark updates
1eyewonder d53d87f
Refactored functions & added unit tests
1eyewonder 5ae5f49
Added documentation
1eyewonder c9a1152
Added more benchmarks
1eyewonder a7753f0
Updated unit tests to fix transpiling issues
1eyewonder 31212f4
Updated Seq.fooM functions to have an early exit condition
1eyewonder d46f132
Updated benchmark functions to be more accurate of actual behavior
1eyewonder 7821136
Inlined base traverse functions
1eyewonder 3e401ae
Updated seq functions to seq expressions for improved performance per…
1eyewonder File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"name": "benchmarks", | ||
"type": "coreclr", | ||
"request": "launch", | ||
"program": "${workspaceFolder}/benchmarks/bin/Release/net7.0/benchmarks.exe", | ||
"args": [], | ||
"env": { | ||
"ASPNETCORE_ENVIRONMENT": "Development" | ||
}, | ||
"console": "integratedTerminal", | ||
"preLaunchTask": "build release", | ||
"cwd": "${workspaceFolder}/benchmarks/bin/Release/net7.0/" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,11 @@ | ||
{ | ||
"editor.inlayHints.enabled": "off", | ||
"FSharp.enableAdaptiveLspServer": true, | ||
"FSharp.enableMSBuildProjectGraph": true, | ||
"editor.formatOnSave": true, | ||
"FSharp.notifications.trace": false, | ||
"FSharp.notifications.traceNamespaces": [ | ||
"BoundModel.TypeCheck", | ||
"BackgroundCompiler." | ||
], | ||
"FSharp.fsac.conserveMemory": true, | ||
"FSharp.fsac.parallelReferenceResolution": false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"version": "2.0.0", | ||
"tasks": [ | ||
{ | ||
"label": "build release", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❤️ |
||
"type": "process", | ||
"command": "dotnet", | ||
"args": [ | ||
"build", | ||
"-c", | ||
"Release", | ||
"${workspaceFolder}/FsToolkit.ErrorHandling.sln", | ||
"/property:GenerateFullPaths=true", | ||
"/consoleloggerparameters:NoSummary" | ||
] | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
module SeqTests | ||
|
||
open BenchmarkDotNet.Attributes | ||
open BenchmarkDotNet.Order | ||
open BenchmarkDotNet.Mathematics | ||
open BenchmarkDotNet.Configs | ||
|
||
module sequenceResultMTests = | ||
|
||
module v1 = | ||
|
||
let sequenceResultM (xs: seq<Result<'t, 'e>>) : Result<'t seq, 'e> = | ||
let rec loop xs ts = | ||
match Seq.tryHead xs with | ||
| Some x -> | ||
x | ||
|> Result.bind (fun t -> loop (Seq.tail xs) (t :: ts)) | ||
| None -> | ||
Ok( | ||
List.rev ts | ||
|> List.toSeq | ||
) | ||
|
||
// Seq.cache prevents double evaluation in Seq.tail | ||
loop (Seq.cache xs) [] | ||
|
||
module v2 = | ||
|
||
let traverseResultM' state (f: 'okInput -> Result<'okOutput, 'error>) xs = | ||
let folder state x = | ||
match state, f x with | ||
| Error e, _ -> Error e | ||
| Ok oks, Ok ok -> | ||
Seq.singleton ok | ||
|> Seq.append oks | ||
|> Ok | ||
| Ok _, Error e -> Error e | ||
|
||
Seq.fold folder state xs | ||
|> Result.map Seq.rev | ||
|
||
let traverseResultM (f: 'okInput -> Result<'okOutput, 'error>) xs = | ||
traverseResultM' (Ok Seq.empty) f xs | ||
|
||
let sequenceResultM xs = traverseResultM id xs | ||
|
||
module v3 = | ||
|
||
let inline traverseResultM' | ||
state | ||
([<InlineIfLambda>] f: 'okInput -> Result<'okOutput, 'error>) | ||
xs | ||
= | ||
let folder state x = | ||
match state, f x with | ||
| Error e, _ -> Error e | ||
| Ok oks, Ok ok -> | ||
Seq.singleton ok | ||
|> Seq.append oks | ||
|> Ok | ||
| Ok _, Error e -> Error e | ||
|
||
Seq.fold folder state xs | ||
|> Result.map Seq.rev | ||
|
||
let traverseResultM (f: 'okInput -> Result<'okOutput, 'error>) xs = | ||
traverseResultM' (Ok Seq.empty) f xs | ||
|
||
let sequenceResultM xs = traverseResultM id xs | ||
|
||
module v4 = | ||
|
||
let traverseResultM' initialState (f: 'okInput -> Result<'okOutput, 'error>) xs = | ||
(initialState, 0) | ||
|> Seq.unfold (fun (state, i) -> | ||
xs | ||
|> Seq.tryItem i | ||
|> Option.bind (fun x -> | ||
match state, f x with | ||
| Error _, _ -> None | ||
| Ok oks, Ok ok -> | ||
let newState = | ||
Seq.singleton ok | ||
|> Seq.append oks | ||
|> Ok | ||
|
||
Some(newState, (newState, i + 1)) | ||
| Ok _, Error e -> Some(Error e, (Error e, i + 1)) | ||
) | ||
) | ||
|> Seq.last | ||
|
||
let traverseResultM f xs = traverseResultM' (Ok Seq.empty) f xs | ||
let sequenceResultM xs = traverseResultM id xs | ||
|
||
module v5 = | ||
|
||
let traverseResultM' state (f: 'okInput -> Result<'okOutput, 'error>) (xs: seq<'okInput>) = | ||
let mutable state = state | ||
|
||
let enumerator = xs.GetEnumerator() | ||
|
||
while enumerator.MoveNext() do | ||
match state, f enumerator.Current with | ||
| Error _, _ -> () | ||
| Ok oks, Ok ok -> state <- Ok(Seq.append oks (Seq.singleton ok)) | ||
| Ok _, Error e -> state <- Error e | ||
|
||
state | ||
|
||
let traverseResultM f xs = traverseResultM' (Ok Seq.empty) f xs | ||
let sequenceResultM xs = traverseResultM id xs | ||
|
||
module v6 = | ||
|
||
let inline traverseResultM' | ||
state | ||
([<InlineIfLambda>] f: 'okInput -> Result<'okOutput, 'error>) | ||
(xs: seq<'okInput>) | ||
= | ||
let mutable state = state | ||
|
||
let enumerator = xs.GetEnumerator() | ||
|
||
while enumerator.MoveNext() do | ||
match state, f enumerator.Current with | ||
| Error _, _ -> () | ||
| Ok oks, Ok ok -> state <- Ok(Seq.append oks (Seq.singleton ok)) | ||
| Ok _, Error e -> state <- Error e | ||
|
||
state | ||
|
||
let traverseResultM f xs = traverseResultM' (Ok Seq.empty) f xs | ||
let sequenceResultM xs = traverseResultM id xs | ||
|
||
|
||
[<MemoryDiagnoser>] | ||
[<Orderer(SummaryOrderPolicy.FastestToSlowest)>] | ||
[<RankColumn(NumeralSystem.Stars)>] | ||
[<MinColumn; MaxColumn; MedianColumn; MeanColumn; CategoriesColumn>] | ||
[<GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)>] | ||
type SeqBenchmarks() = | ||
|
||
member _.GetPartialOkSeq size = | ||
seq { | ||
for i in 1u .. size do | ||
if i = size / 2u then Error "error" else Ok i | ||
} | ||
|
||
member _.SmallSize = 1000u | ||
|
||
member _.LargeSize = 500_000u | ||
|
||
[<Benchmark(Baseline = true, Description = "original")>] | ||
[<BenchmarkCategory("Small")>] | ||
member this.original() = | ||
sequenceResultMTests.v1.sequenceResultM (this.GetPartialOkSeq this.SmallSize) | ||
|> ignore | ||
|
||
[<Benchmark(Description = "Seq.fold")>] | ||
[<BenchmarkCategory("Small")>] | ||
member this.seqFoldSmall() = | ||
sequenceResultMTests.v2.sequenceResultM (this.GetPartialOkSeq this.SmallSize) | ||
|> ignore | ||
|
||
[<Benchmark(Description = "inlined Seq.fold")>] | ||
[<BenchmarkCategory("Small")>] | ||
member this.inlineSeqFoldSmall() = | ||
sequenceResultMTests.v3.sequenceResultM (this.GetPartialOkSeq this.SmallSize) | ||
|> ignore | ||
|
||
[<Benchmark(Description = "Seq.unfold")>] | ||
[<BenchmarkCategory("Small")>] | ||
member this.seqUnfoldSmall() = | ||
sequenceResultMTests.v4.sequenceResultM (this.GetPartialOkSeq this.SmallSize) | ||
|> ignore | ||
|
||
[<Benchmark(Description = "GetEnumerator w/ mutability")>] | ||
[<BenchmarkCategory("Small")>] | ||
member this.getEnumeratorSmall() = | ||
sequenceResultMTests.v5.sequenceResultM (this.GetPartialOkSeq this.SmallSize) | ||
|> ignore | ||
|
||
[<Benchmark(Description = "inlined GetEnumerator w/ mutability")>] | ||
[<BenchmarkCategory("Small")>] | ||
member this.inlineGetEnumeratorSmall() = | ||
sequenceResultMTests.v6.sequenceResultM (this.GetPartialOkSeq this.SmallSize) | ||
|> ignore | ||
|
||
// made this baseline for this category since unfold and original were so unperformant for this size of data | ||
[<Benchmark(Baseline = true, Description = "Seq.fold")>] | ||
[<BenchmarkCategory("Large")>] | ||
member this.seqFoldLarge() = | ||
sequenceResultMTests.v2.sequenceResultM (this.GetPartialOkSeq this.LargeSize) | ||
|> ignore | ||
|
||
[<Benchmark(Description = "inlined Seq.fold")>] | ||
[<BenchmarkCategory("Large")>] | ||
member this.inlineSeqFoldLarge() = | ||
sequenceResultMTests.v3.sequenceResultM (this.GetPartialOkSeq this.LargeSize) | ||
|> ignore | ||
|
||
[<Benchmark(Description = "GetEnumerator w/ mutability")>] | ||
[<BenchmarkCategory("Large")>] | ||
member this.getEnumeratorLarge() = | ||
sequenceResultMTests.v5.sequenceResultM (this.GetPartialOkSeq this.LargeSize) | ||
|> ignore | ||
|
||
[<Benchmark(Description = "inlined GetEnumerator w/ mutability")>] | ||
[<BenchmarkCategory("Large")>] | ||
member this.inlineGetEnumeratorLarge() = | ||
sequenceResultMTests.v6.sequenceResultM (this.GetPartialOkSeq this.LargeSize) | ||
|> ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
## Seq.sequenceAsyncResultA | ||
|
||
Namespace: `FsToolkit.ErrorHandling` | ||
|
||
Function Signature: | ||
|
||
``` | ||
Async<Result<'a, 'b>> seq -> Async<Result<'a seq, 'b seq>> | ||
``` | ||
|
||
Note that `sequence` is the same as `traverse id`. See also [Seq.traverseAsyncResultA](traverseAsyncResultA.md). | ||
|
||
This is applicative, collecting all errors. | ||
|
||
This is the same as [sequenceResultA](sequenceResultA.md) except that it uses `Async<Result<_,_>>` instead of `Result<_,_>`. | ||
|
||
See also Scott Wlaschin's [Understanding traverse and sequence](https://fsharpforfunandprofit.com/posts/elevated-world-4/). | ||
|
||
## Examples | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
## Seq.sequenceAsyncResultM | ||
|
||
Namespace: `FsToolkit.ErrorHandling` | ||
|
||
Function Signature: | ||
|
||
``` | ||
Async<Result<'a, 'b>> seq -> Async<Result<'a seq, 'b>> | ||
``` | ||
|
||
Note that `sequence` is the same as `traverse id`. See also [Seq.traverseAsyncResultM](traverseAsyncResultM.md). | ||
|
||
This is monadic, stopping on the first error. | ||
|
||
This is the same as [sequenceResultM](sequenceResultM.md) except that it uses `Async<Result<_,_>>` instead of `Result<_,_>`. | ||
|
||
See also Scott Wlaschin's [Understanding traverse and sequence](https://fsharpforfunandprofit.com/posts/elevated-world-4/). | ||
|
||
## Examples |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is nice ❤️