-
Notifications
You must be signed in to change notification settings - Fork 21
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
Add 'try' versions of Set.minElement and Set.maxElement #803
Comments
Isn't the existing way to check for emptiness first? |
Good point - I'll edit my post. |
I think that for the same reasons we should also add |
Perhaps we could add a |
Or more generically we can add an
|
@abelbraaksma I'm not a fan of having to throw and catch an exception (even implicitly) to handle empty collections :/ @gusty Interesting idea. It's longer than an explicit set [42] |> fun s -> if Set.isEmpty s then None else Some (Set.maxElement s) Another possibility that would provide this functionality without adding too many functions: adding a function module Set =
val ifEmpty : ifEmpty:'u -> ifNotEmpty:(Set<'t> -> 'u) -> collection:Set<'t> -> 'u
// Usage:
set [42] |> Set.ifEmpty None (Set.maxElement >> Some) This also allows using something else than Option. set [42] |> Set.ifEmpty 0 Set.maxElement
set [42] |> Set.ifEmpty ValueNone (Set.maxElement >> ValueSome) |
@Tarmil, I'm not suggesting try/catching. That is slow if the common case can be that a statement throws and exception, and it clutters code. I try (pun intended) to stay away from it where possible. I meant something like this: module Set =
let tryNonEmpty s = if Set.isEmpty s then None else Some s
module List =
let tryNonEmpty l = if List.isEmpty l then None else Some l
.... Usage: set [42] |> Set.tryNonEmpty |> Option.map Set.maxElement
[42] |> List.tryNonEmpty |> Option.map (List.reduce (+)) I think it ends up rather concise and relatively readable.
@gusty, I actually have set [42] |> Option.whenFalse Set.isEmpty |> Option.map Set.maxElement
[42] |> Option.whenFalse List.isEmpty |> Option.map (List.reduce (+)) side thought: I have these functions in my default Option extension project for a while, but looking through my code, I didn't end up using them very often. As @dsyme often mentions, oftentimes, explicit branching and explicit |
@abelbraaksma OK I misunderstood, sorry. This is better indeed. I still slightly prefer |
@Tarmil, this is somewhat in the same league as the recently added
So perhaps we'd need both approaches ;). As another aside: it may be wiser to simply skip to a |
The point of my original proposal is that it is flexible enough to do both and more. // Setting the default value:
myList |> List.ifEmpty [1] id
// (an additional With variant would avoid allocating the list unless needed)
// Turning into an option:
myList |> List.ifEmpty None Some
// Call an aggregation or return a default value:
myList |> List.ifEmpty 0 List.min
// Call an aggregation and return an option:
myList |> List.ifEmpty None (List.min >> Some) |
Needing to check for emptiness violates the F# pattern of |
@Happypig375 Note that this is my alternative proposal if we don't want to add too many functions at once; I still think |
Actually, I think Also, they would force the same type, while your I think that both with my proposal and yours, they are not very flexible. @gusty's proposal, or For instance, I have many requirements for "more-than-one" situations in my own field, which would easily be covered with the predicate-based functions: // not necessarily the best way, but just to illustrate:
// this will run doSomeCalculations only when size of list is 2+,
// without having to iterate the list manually
myList
|> List.withPredicate
(List.tryTail >> Option.bind List.tryTail >> Option.isSome)
doSomeCalculations |
I understand your reasoning but I disagree. If we go that way, for consistency, we should add the List.average
List.averageBy
List.chunkBySize
List.exactlyOne
List.fold2
List.foldBack2
List.forall2
List.iter2
List.iteri2
List.map2
List.map3
List.mapi2
List.min
List.minBy
List.max
List.maxBy
List.permute
List.reduceBack
List.skip
List.split
List.splitInto
List.take
List.transpose
List.windowed
List.zip
List.zip3 To be fair, I'm a little surprised this list is so long, and I also noted that some on that list do not explicitly stated that they can raise an exception. But my point is simple: allow for some function (name TBD) that allows easy composition with the existing functions for either a common predicate (empty, equal lengths) or a generalized predicate. |
Yes, but that's mainly due to the verbose syntax of having to write repeatedly the name of the module Regarding the |
@abelbraaksma The problem with using a combinator to make a function safe is you still have to check carefully that you've combined the functions safely. I would prefer to have all of the I would go so far as to say I'd prefer to have the unsafe versions moved to an |
The BCL is full of types and methods that can throw exceptions. We will have to deal with them in F# anyways. |
@theprash, I agree, but simple defensive programming means you use Just using (for example) Totally safe code is still a task for the programmer to look out for. If all statements are safe, we'd be out of a job soon ;). Though it may be interesting to write a safe library of all core functions and use that as an alternative. Something like SafeF# ;). |
@abelbraaksma We already have Then we could optionally lint for the usage of the unsafe functions, giving people the chance to write code that forces them to consider all cases and use
The more cognitive load we can automate away from the programmer without significant cost the better. They will still have plenty of other things to think about. |
@theprash I know, I was responding to your comment that we shouldn't have unsafe functions to begin with, it put them in About the downside: only exponential growth of functions. It's already hard to weed through the list of your unsure what you need. But I'm not against the idea. Just that when I was proposing similar ideas (see
Indeed, though currently, F#Lint doesn't work out of the box in the VS IDE. This would certainly help new users, but now they're probably not using it, I'd argue it'd be a fine addition and better user experience if it were added to the standard F# installation by default. Overall I think we'd be much helped if we can create a list that's guaranteed 1+. It should be a list, so that you can use the same functions. Then Lint can understand the type and doesn't have to warn when using |
FSharpLint's new recently added rule[1] complains about function Seq.tail being a partial function, so we need an alternative before all try-prefixed versions get included in FSharp.Core[2]. This is not actually a tryTail function but at least most uses of Seq.tail use a Seq.head before it, so we can replace both calls to Seq.(try)head & Seq.tail with a single-call to this Seq.tryHeadTail which shouldn't be considered a partial function anymore (even if it calls Seq.tail underneath). Adding it in FSharpx.Collections would be a nice stopgap instead of having to include this function in every F# project that wants to enable this new FSharpLint rule. [1] fsprojects/FSharpLint#453 [2] fsharp/fslang-suggestions#803
FSharpLint's new recently added rule[1] complains about function Seq.tail being a partial function, so we need an alternative before all try-prefixed versions get included in FSharp.Core[2]. This is not actually a tryTail function but at least most uses of Seq.tail use a Seq.head before it, so we can replace both calls to Seq.(try)head & Seq.tail with a single-call to this Seq.tryHeadTail which shouldn't be considered a partial function anymore (even if it calls Seq.tail underneath). Adding it in FSharpx.Collections would be a nice stopgap instead of having to include this function in every F# project that wants to enable this new FSharpLint rule. [1] fsprojects/FSharpLint#453 [2] fsharp/fslang-suggestions#803
FSharpLint's new recently added rule[1] complains about function Seq.tail being a partial function, so we need an alternative before all try-prefixed versions get included in FSharp.Core[2]. This is not actually a tryTail function but at least most uses of Seq.tail use a Seq.head before it, so we can replace both calls to Seq.(try)head & Seq.tail with a single-call to this Seq.tryHeadTail which shouldn't be considered a partial function anymore (even if it calls Seq.tail underneath). Adding it in FSharpx.Collections would be a nice stopgap instead of having to include this function in every F# project that wants to enable this new FSharpLint rule. [1] fsprojects/FSharpLint#453 [2] fsharp/fslang-suggestions#803
FSharpLint's new recently added rule[1] complains about function Seq.tail being a partial function, so we need an alternative before all try-prefixed versions get included in FSharp.Core[2]. This is not actually a tryTail function but at least most uses of Seq.tail use a Seq.head before it, so we can replace both calls to Seq.(try)head & Seq.tail with a single-call to this Seq.tryHeadTail which shouldn't be considered a partial function anymore (even if it calls Seq.tail underneath). Adding it in FSharpx.Collections would be a nice stopgap instead of having to include this function in every F# project that wants to enable this new FSharpLint rule. [1] fsprojects/FSharpLint#453 [2] fsharp/fslang-suggestions#803
tryLast doesn't have the same goal as tail; let's rather suggest FSharpx.Collections.Seq.tryHeadTail extension for now, which has just been merged [1] and will be in 3.0.0 release (at least until we have a tryTail of some sort [2]). [1] fsprojects/FSharpx.Collections#176 [2] fsharp/fslang-suggestions#803
tryLast doesn't have the same goal as tail; let's rather suggest FSharpx.Collections.Seq.tryHeadTail extension for now, which has just been merged [1] and will be in 3.0.0 release (at least until we have a tryTail of some sort [2]). [1] fsprojects/FSharpx.Collections#176 [2] fsharp/fslang-suggestions#803
tryLast doesn't have the same goal as tail; let's rather suggest FSharpx.Collections.Seq.tryHeadTail extension for now, which has just been merged [1] and will be in 3.0.0 release (at least until we have a tryTail of some sort [2]). [1] fsprojects/FSharpx.Collections#176 [2] fsharp/fslang-suggestions#803
My feeling is that these are too corner case to add. Using minElement/maxElement is just really rare already, and use of the try versions would be vanishingly small |
I propose we add 'try' version of minElement and maxElement (and corresponding class methods) in Microsoft.FSharp.Collections.Set module.
The existing way of approaching this problem in F# is to first check if the set is empty. Alternatively wrap the current functions in a try .. with since they may throw an ArgumentException.
Pros and Cons
The advantages of making this adjustment to F# are readily available type safe options for retrieving set elements without exceptions. This is already idiomatic in other collection modules.
The disadvantages of making this adjustment to F# are enlarging the Set module and class.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): XS
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
The text was updated successfully, but these errors were encountered: