5

Additions to immutable collections (List, Map etc.) · Issue #1047 · fsharp/fslan...

 3 years ago
source link: https://github.com/fsharp/fslang-suggestions/issues/1047
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Comments

Copy link

Task lists! Give feedback

Collaborator

dsyme commented 27 days ago

edited
  1. Add functions to List, Array, Seq, Map to improve them as workhorse functional collections
  2. Add Keys property to Map
  3. Add members to List, Map, Set to align their design more with the patterns used by ImmutableCollections.

I propose we add the following:
Here's what I've concluded:

module List =
    /// Return a new list with the item at a given index removed
    /// If the index is outside the range of the list then it is ignored.
    val removeAt: index: int -> source: 'T list -> 'T list

    /// Return a new list with the number of items starting at a given index removed.
    /// If index is outside 0..source.Length-1 then it is ignored.
    val removeManyAt: index: int -> count: int -> source: 'T list -> 'T list

    /// Return a new list with the item at a given index set to the new value. If 
    /// index is outside 0..source.Length-1 an exception is raised.
    val updateAt: index: int -> value: 'T -> source: 'T list -> 'T list

    /// Return a new list with the items starting at a given index set to the new values.
    /// If index is outside 0..source.Length-values.Length an exception is raised.
    val updateManyAt: index: int -> values:seq< 'T> -> source: 'T list -> 'T list

    /// Return a new list with a new item inserted before the given index.The index may be source.Length to
    /// include extra elements at the end of the list. If 
    /// index is outside 0..source.Length an exception is raised.
    val insertAt: index: int -> value: 'T -> source: 'T list -> 'T list

    /// Return a new list with new items inserted before the given index. The index may be source.Length to
    /// include extra elements at the end of the list. 
    /// If index is outside 0..source.Length  an exception is raised.
    val insertManyAt: index: int -> values: seq<'T> -> source: 'T list -> 'T list

module Array =
    /// Return a new array with the item at a given index removed
    /// If the index is outside the range of the array then it is ignored.
    val removeAt: index: int -> source: 'T[] -> 'T[]

    /// Return a new array with the number of items starting at a given index removed.
    /// If an implied item index is outside the range of the array then it is ignored.
    val removeManyAt: index: int -> count: int -> source: 'T[] -> 'T[]

    /// Return a new array with the item at a given index set to the new value. If 
    /// index is outside 0..source.Length-1 an exception is raised.
    val updateAt: index: int -> value: 'T -> source: 'T[] -> 'T[]

    /// Return a new array with the items starting at a given index set to the new values. 
    /// If index is outside 0..source.Length-values.Length an exception is raised.
    val updateManyAt: index: int -> values:seq< 'T> -> source: 'T[] -> 'T[]

    /// Return a new array with a new item inserted before the given index. The index may be source.Length to
    /// include extra elements at the end of the array. If 
    /// index is outside 0..source.Length than source.Length an exception is raised.
    val insertAt: index: int -> value: 'T -> source: 'T[] -> 'T[]

    /// Return a new array with new items inserted before the given index. The index may be source.Length to
    /// include extra elements at the end of the array.   If index is outside 0..source.Length an exception is raised.
    val insertManyAt: index: int -> values: seq<'T> -> source: 'T[] -> 'T[]

module Seq =
    /// Return a new ssequence which, when iterated, will have the item at a given index removed
    /// If the index is outside the range of valid indexes for the sequence then it is ignored.
    val removeAt: index: int -> source: seq<'T> -> seq<'T>

    /// Return a new sequence which, when iterated, will have the given count of items starting at a given index removed.
    /// If any implied item index is outside the range of valid indexes for the sequence then it is ignored.
    val removeManyAt: index: int -> count: int -> source: seq<'T> -> seq<'T>

    /// Return a new sequence which, when iterated, will return the given item for the given index, and
    /// otherwise return the items from source. If index is below zero an exception is raised immediately.
    /// If the index is beyond the range on the source sequence the update is ignored.
    val updateAt: index: int -> value: 'T -> source: seq<'T> -> seq<'T>

    /// Return a new sequence which, when iterated, will return the given items for count items
    /// starting at the index.   If index is outside 0..source.Length-values.Length  an exception is raised.
    val updateManyAt: index: int -> values:seq< 'T> -> source: seq<'T> -> seq<'T>

    /// Return a new sequence which, when iterated, includes a new item inserted before the given index. The index may be source.Length to
    /// include extra elements at the end of the sequence. If 
    /// index is outside 0..source.Length than source.Length an exception is raised.
    val insertAt: index: int -> value: 'T -> source: seq<'T> -> seq<'T>

    /// Return a new sequence which, when iterated, will include additional items given by values, starting
    /// at the given index. The index may be source.Length to
    /// include extra elements at the end of the sequence. 
    /// If index is outside 0..source.Length an exception is raised.
    val insertManyAt: index: int -> values: seq<'T> -> source: seq<'T> -> seq<'T>

I changed to use ManyAt etc. since Range should best be reserved to take Range type. In retrospect I think mapAt and mapManyAt are overkill for this PR, they don't make the cut for FSharp.Core. We'd have to ask "why not collectAt, filterAt, chooseAt etc., I've no good answer. I will mark this as approved with mapAt and mapManyAt removed

  • Note that for Seq the operations potentially involve managing state as the sequence
    unfolds to remap indexes appropriately.

  • Note that for Array the operations would not mutate, they would be treating Array as a functional collection.

For Map we have some choices since there are existing functions that do the above.
We could consider including functions with the right names for consistency, however insertItem makes no sense

    /// Same as map.Add
    /// Set an item at a given index. 
    val setItem: key: 'Key -> value: 'Value -> source: source: Map<'Key, ;Value> -> Map<'Key, ;Value>

    /// Remove an item at a given index
    val removeItem: key: 'Key -> source: Map<'Key, ;Value> -> Map<'Key, ;Value>

    /// Modify the item at the given index. 
    val mapItem: key: 'Key -> modifier: ('Value -> 'Value)  -> source: Map<'Key, ;Value> -> Map<'Key, ;Value>

For Map we should also add:

    val keys: source: Map<'Key, ;Value> -> ICollection<'Key>

    type Map<'Key, ;Value> with
        member Keys: ICollection<'Key>

Separately we should also consider adding members to align the design with with ImmutableDictionary as much as possible. THis could be a separate RFC.

The existing way of approaching this problem in F# is adding the above as post-hoc extensions.

Pros and Cons

The advantages of making this adjustment to F# are it makes programming with functional collections simpler

The disadvantages of making this adjustment to F# are more things in FSharp.Core.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): M

Related suggestions: (put links to related suggestions here)

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • I or my company would be willing to help implement and/or test this

For Readers

If you would like to see this issue implemented, please click the +1 emoji on this issue. These counts are used to generally order the suggestions by engagement.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK