[RFC FS-1135] Random functions for collections #731
Replies: 6 comments 46 replies
-
FSharp.Core does not target .NET 6 or higher though. |
Beta Was this translation helpful? Give feedback.
-
Right now there is a main issue - how to call newly functions, right now we have two main options
Please vote for the prefixed version with 🚀, for the short version with 🎉 |
Beta Was this translation helpful? Give feedback.
-
Hey @Lanayx, I'm reviewing your proposal atm (and was hoping to un-stale this RFC, it is good work and should go in!). I was wondering if you've considered to use a function as argument, as opposed to a I know it would increase the surface area of the functions, and it would require an extra check (i.e., if the returned value is out of range). But considering we're in a functional language, it stands to reason that we allow users to use a function to provide the random values. Alternatively, |
Beta Was this translation helpful? Give feedback.
-
@Lanayx, putting this in a different discussion thread, @nkeenan38 pointed out the following point in an offline discussion:
My take on this is that these will fail for infinite sequences (similar to |
Beta Was this translation helpful? Give feedback.
-
@Lanayx, you raised a point about what should happen when the shuffling algorithm you provide is not really shuffling. I wasn't entirely sure wrt my own answer, so I figured, let's look at the source. You may or may not want to mention this in the RFC. It may help with adding corner cases. Here are two examples (remember that max is exclusive, min is inclusive): type MyRandom() =
inherit System.Random()
override this.Next(minv, maxv) =
if minv = maxv then minv
else maxv - 1
let testShuffle() =
let xs = [|1;2;3;4;5;6;7;8|]
MyRandom().Shuffle(xs)
xs
> testShuffle();;
val it: int array = [|8; 1; 2; 3; 4; 5; 6; 7|] type MyRandom() =
inherit System.Random()
override this.Next(minv, maxv) =minv
let testShuffle() =
let xs = [|1;2;3;4;5;6;7;8|]
MyRandom().Shuffle(xs)
xs
> testShuffle();;
val it: int array = [|1; 2; 3; 4; 5; 6; 7; 8|] Essentially, I'd suggest we just follow the .NET algorithm and translate where needed. I'm sure you've seen this before (for reference, here's the original implementation, not sure we pointed to it before: dotnet/runtime#78598): public void Shuffle<T>(Span<T> values)
{
int n = values.Length;
for (int i = 0; i < n - 1; i++)
{
int j = Next(i, n);
if (j != i)
{
T temp = values[i];
values[i] = values[j];
values[j] = temp;
}
}
} I don't think we can rely on |
Beta Was this translation helpful? Give feedback.
-
I wonder if there's anything to learn from OCaml's approach to this:
ProposalHere's a wild stab at taking some inspiration from that approach that winnows the set of new APIs to three per collection type while, I think, still being relatively ergonomic and discoverable. Script file with provisional implementation: https://gist.github.com/brianrourkeboll/9935b6aa35a2227610a4251b38404729 Proposed signaturerandom.fsinamespace Microsoft.FSharp.Core
open System
[<AutoOpen>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module internal ThreadSafeRandom =
type Random with
static member internal Shared : Random
[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Random =
val nextInt : unit -> int
val int : maxValue:int -> int
val intInRange : minValue:int -> maxValue:int -> int
val nextFloat : unit -> float
val bytes : count:int -> byte array
val fill : buffer:byte array -> unit array.fsimodule Array =
// …
val shuffle : randomizer:(int -> int) -> array:'T array -> 'T array
val choice : randomizer:(int -> int) -> array:'T array -> 'T
val choices : randomizer:(int -> int) -> count:int -> array:'T array -> 'T array
val sample : randomizer:(int -> int) -> count:int -> array:'T array -> 'T array Likewise for the Proposed implementationrandom.fsnamespace Microsoft.FSharp.Core
open System
open System.Runtime.CompilerServices
[<Sealed>]
type internal ThreadSafeRandom() =
inherit Random()
[<DefaultValue>]
[<ThreadStatic>]
static val mutable private random: Random
[<MethodImpl(MethodImplOptions.NoInlining)>]
static member private Create() =
ThreadSafeRandom.random <- Random()
ThreadSafeRandom.random
static member private LocalRandom =
match ThreadSafeRandom.random with
| null -> ThreadSafeRandom.Create()
| random -> random
override _.Next() = ThreadSafeRandom.LocalRandom.Next()
override _.Next maxValue = ThreadSafeRandom.LocalRandom.Next maxValue
override _.Next(minValue, maxValue) = ThreadSafeRandom.LocalRandom.Next(minValue, maxValue)
override _.NextDouble() = ThreadSafeRandom.LocalRandom.NextDouble()
override _.NextBytes(buffer: byte array) = ThreadSafeRandom.LocalRandom.NextBytes buffer
override _.Sample() = raise (NotSupportedException())
[<AutoOpen>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module internal ThreadSafeRandom =
// Avoid the static init check overhead on each access
// that would happen if this were a static member val in a class.
// See: https://github.com/dotnet/fsharp/issues/6454
let private shared = ThreadSafeRandom()
type Random with
static member internal Shared: Random = shared
[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Random =
let nextInt () = Random.Shared.Next()
let int maxValue = Random.Shared.Next maxValue
let intInRange minValue maxValue = Random.Shared.Next(minValue, maxValue)
let nextFloat () = Random.Shared.NextDouble()
let bytes count =
if count < 0 then invalidArgOutOfRange (nameof count) count "TODO" 0
let bytes = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked count
Random.Shared.NextBytes bytes
bytes
let fill (buffer: byte array) = Random.Shared.NextBytes buffer array.fsmodule Array =
// …
let inline shuffle ([<InlineIfLambda>] randomizer) (array: 'T array) =
checkNonNull (nameof array) array
let array = Unchecked.unbox<'T array> (array.Clone())
for i in array.Length - 1 .. -1 .. 1 do
let j = randomizer (i + 1)
if j < 0 || i < j then invalidArgOutOfRange (nameof randomizer) j "TODO" i
let temp = array[i]
array[i] <- array[j]
array[j] <- temp
array
// …Etc. Proposed usagelet array = [|1..100|]
let shuffled = array |> Array.shuffle Random.int
let choice = array |> Array.choice Random.int
let choices = array |> Array.choices Random.int 33
let sample = array |> Array.sample Random.int 33
Alternatives
|
Beta Was this translation helpful? Give feedback.
-
Let's discuss RFC FS-1135 as proposed in PR #732
Beta Was this translation helpful? Give feedback.
All reactions