Skip to content

Implement TaskSeq.insertAt, updateAt, removeAt, insertManyAt, removeManyAt #236

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

Merged
merged 6 commits into from
Mar 16, 2024

Conversation

abelbraaksma
Copy link
Member

@abelbraaksma abelbraaksma commented Mar 16, 2024

Part of the push for good coverage of surface area functions, see #208 for an overview. This implements insertAt, updateAt, removeAt, insertManyAt, removeManyAt and maxByAsync.

We won't be implementing updateManyAt unless there's a strong use case. We skip it for the same reasons it was skipped in FSharp.Core for Seq: fsharp/fslang-suggestions#1047 (comment).

Each of these behave exactly like their Seq counterparts:

  • raises on null input
  • raises for negative indices
  • raises when the index turns out to be out of bounds

As before, the xml doc blibs have been taken from seq.fs and modified a bit for readability and applicability to TaskSeq.

The signatures are as follows:

static member insertAt: index: int -> value: 'T -> source: TaskSeq<'T> -> TaskSeq<'T>
static member insertManyAt: index: int -> values: TaskSeq<'T> -> source: TaskSeq<'T> -> TaskSeq<'T>

static member removeAt: index: int -> source: TaskSeq<'T> -> TaskSeq<'T>
static member removeManyAt: index: int -> count: int -> source: TaskSeq<'T> -> TaskSeq<'T>

static member updateAt: index: int -> value: 'T -> source: TaskSeq<'T> -> TaskSeq<'T>

TODO list:

  • implement TaskSeq.insertAt
  • Implement TaskSeq.insertManyAt
  • Implement TaskSeq.removeAt
  • Implement TaskSeq.removeManyAt
  • Implement TaskSeq.updateAt

@abelbraaksma abelbraaksma force-pushed the implement-insertat-removeat-updateat branch from c57f5e7 to 391f438 Compare March 16, 2024 02:03
@abelbraaksma
Copy link
Member Author

This is ready. Now all that's left is forall and the release notes, and then we can publish the new version finally.

@abelbraaksma abelbraaksma self-assigned this Mar 16, 2024
@abelbraaksma abelbraaksma added topic: surface area Adds functions to the public surface area feature request New feature or enhancement request labels Mar 16, 2024
@abelbraaksma abelbraaksma merged commit cedbb32 into main Mar 16, 2024
@abelbraaksma abelbraaksma deleted the implement-insertat-removeat-updateat branch March 16, 2024 13:11
Copy link
Member

@bartelink bartelink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drinking in the completeness of your tests always just fills me with self loathing

Luckily that pain can fuel nit picking and general devil's advocate "just asking questions" routine.

In other words, please take as little or as much of this as you wish, especially given the review is long after the work...

@@ -1275,3 +1276,65 @@ type TaskSeq =
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence is null.</exception>
static member foldAsync:
folder: ('State -> 'T -> #Task<'State>) -> state: 'State -> source: TaskSeq<'T> -> Task<'State>

/// <summary>
/// Return a new task sequence with a new item inserted before the given index.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ye olde yields vs returns thing triggering in my head
Yes, all functions return things
the <returns> expresses it in that way and it's clear.
But in general mapping a sequence that's lazy to another thing that's still lazy
Thoughts? If there is prior art art/or it makes any sense to you I might do a walk to see if such language makes sense to apply across the board for all functions that return some mapped/altered rendition of some input?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a new item -> an additional item?
or
a specified item
/ value?

let ``TaskSeq-updateAt(0) will execute side effects at start of sequence`` () =
// NOTE: while not strictly necessary, this mirrors behavior of Seq.updateAt

let mutable x = 42 // for this test, the potential mutation should not actually occur
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not?

/// Return a new task sequence with a new item inserted before the given index.
/// </summary>
///
/// <param name="index">The index where the item should be inserted.</param>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where -> at which ?

/// <param name="source">The input task sequence.</param>
/// <returns>The result task sequence.</returns>
/// <exception cref="T:ArgumentNullException">Thrown when the input task sequence is null.</exception>
/// <exception cref="T:ArgumentException">Thrown when index is below 0 or greater than source length.</exception>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

below is atypical, unless it aligns with something I'd say less than?

you use the term negative elsewhere and that feels best?

static member removeAt: index: int -> source: TaskSeq<'T> -> TaskSeq<'T>

/// <summary>
/// Return a new task sequence with the number of items starting at a given index removed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return a new task sequence with the number of items starting at a given index removed

Yields a new task sequence omitting the specified number of items at the given index

  • the removed is already in the name ? and it does not convey that there never was a materialized thing that things are being chopped out of; they're kinda just skipped. Maybe that's obvious, but I'm thinking of people that are hearing index and at index and thinking mentally of arrays. I guess I don't have beginner cred here so I don't know; i.e. it's just a suggestions/thought

Am also thinking that the term position might make sense somewhere, but you use index consistenly and I think its a good term

also instead of omitting, maybe dropping?

@@ -49,6 +49,11 @@ type internal InitAction<'T, 'TaskT when 'TaskT :> Task<'T>> =
| InitAction of init_item: (int -> 'T)
| InitActionAsync of async_init_item: (int -> 'TaskT)

[<Struct>]
type internal ManyOrOne<'T> =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not base case first in the naming?

let inline raiseCannotBeNegative name = invalidArg name "The value must be non-negative."

let inline raiseOutOfBounds name =
invalidArg name "The value or index must be within the bounds of the task sequence."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems very wooly and prob should be just 2 helpers with specific messages?
if I say "value or" and didnt know anything I'd wonder what that is about, while index is pretty clear
Also should it be ArgumentOutOfRangeException?

@@ -57,7 +62,10 @@ module internal TaskSeqInternal =

let inline raiseEmptySeq () = invalidArg "source" "The input task sequence was empty."

let inline raiseCannotBeNegative name = invalidArg name "The value must be non-negative"
let inline raiseCannotBeNegative name = invalidArg name "The value must be non-negative."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be ArgumentOutOfRangeException?
I'm fine with a generic invalidArg with a message tho; just being a devil's advocate


for item in source do
if i = index then
match valueOrValues with
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name suggests one or many, but you handle the cases in many or one order ;P

| One value -> yield value

if i < index then
raiseOutOfBounds (nameof index)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is a dynamic case, like this, is argument exception appropriate?
should it not be invalidOperationExceptions? IndexOutOfRangeExpcetions?

I know I suggested that ArgumentOutOfRangeException might be better too, but it's kinda wrong for the same reasons no?

Only raising questions; I assume this is derived from what Seq does/decided...

@abelbraaksma abelbraaksma added this to the v0.4.0 milestone Mar 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature or enhancement request topic: surface area Adds functions to the public surface area
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants