From 3966021520053831b3cbfd06de0522415c376091 Mon Sep 17 00:00:00 2001 From: Charlie Morley Date: Thu, 11 Jul 2019 13:48:48 -0500 Subject: [PATCH] Issues #33, #34: *[.NonEmpty].init and .replicate, and some basic Numbers members --- SafetyFirst.Specs/ArraySpec.fs | 18 ++++ SafetyFirst.Specs/FSeqSpec.fs | 31 +++++- SafetyFirst.Specs/Generators.fs | 23 +++++ SafetyFirst.Specs/ListSpec.fs | 19 ++++ SafetyFirst.Specs/SafetyFirst.Specs.fsproj | 2 + SafetyFirst.Specs/SeqSpec.fs | 110 +++++++++++++++++---- SafetyFirst.Specs/StringSpec.fs | 16 +++ SafetyFirst/Array.fs | 56 ++++++++++- SafetyFirst/ErrorTypes.fs | 2 + SafetyFirst/FiniteSeqModule.fs | 90 +++++++++++++++++ SafetyFirst/InfiniteSeq.fs | 7 +- SafetyFirst/LazyList.fs | 32 ++++-- SafetyFirst/List.fs | 54 ++++++++++ SafetyFirst/Numbers.fs | 50 +++++++++- SafetyFirst/Seq.fs | 74 ++++++++++++++ SafetyFirst/String.fs | 13 +++ 16 files changed, 559 insertions(+), 38 deletions(-) create mode 100644 SafetyFirst.Specs/Generators.fs create mode 100644 SafetyFirst.Specs/StringSpec.fs diff --git a/SafetyFirst.Specs/ArraySpec.fs b/SafetyFirst.Specs/ArraySpec.fs index 6703ad8..81bab3f 100644 --- a/SafetyFirst.Specs/ArraySpec.fs +++ b/SafetyFirst.Specs/ArraySpec.fs @@ -5,12 +5,18 @@ open FsCheck open SafetyFirst open SafetyFirst.Specs.SeqSpec +open SafetyFirst.Numbers + +let upcastNE = Array.NonEmpty.toArray let averageFloats' (xs:float[]) = Array.average' xs let averageFloats (xs:float[]) = Array.average xs let averageByFloats' (projection:_ -> float) xs = Array.averageBy' projection xs let averageByFloats (projection:_ -> float) xs = Array.averageBy projection xs +let initByInts' = (fun count initializer -> Array.init' count (NaturalInt.value >> initializer)) +let initByIntsNonEmpty = (fun count initializer -> Array.NonEmpty.initN count (NaturalInt.value >> initializer)) + [] let ``Safe Array functions error whenever unsafe versions throw for all random inputs`` () = errorsWheneverThrows1 averageFloats' averageFloats @@ -25,6 +31,7 @@ let ``Safe Array functions error whenever unsafe versions throw for all random i errorsWheneverThrows4 Array.foldBack2' Array.foldBack2 errorsWheneverThrows3 Array.forall2' Array.forall2 errorsWheneverThrows1 Array.head' Array.head + errorsWheneverThrows2 initByInts' Array.init errorsWheneverThrows2 Array.item' Array.item errorsWheneverThrows1 Array.last' Array.last errorsWheneverThrows3 Array.map2' Array.map2 @@ -37,6 +44,7 @@ let ``Safe Array functions error whenever unsafe versions throw for all random i errorsWheneverThrows2 Array.pick' Array.pick errorsWheneverThrows2 Array.reduce' Array.reduce errorsWheneverThrows2 Array.reduceBack' Array.reduceBack + errorsWheneverThrows2 Array.replicate' Array.replicate errorsWheneverThrows2 Array.skip' Array.skip errorsWheneverThrows2 Array.splitAt' Array.splitAt errorsWheneverThrows2 Array.splitInto' Array.splitInto @@ -63,6 +71,7 @@ let ``Safe Array functions always produce the same output as unsafe versions for alwaysProduceSameOutput4 Array.foldBack2' Array.foldBack2 alwaysProduceSameOutput3 Array.forall2' Array.forall2 alwaysProduceSameOutput1 Array.head' Array.head + alwaysProduceSameOutput2 initByInts' Array.init alwaysProduceSameOutput2 Array.item' Array.item alwaysProduceSameOutput1 Array.last' Array.last alwaysProduceSameOutput3 Array.map2' Array.map2 @@ -75,6 +84,7 @@ let ``Safe Array functions always produce the same output as unsafe versions for alwaysProduceSameOutput2 Array.pick' Array.pick alwaysProduceSameOutput2 Array.reduce' Array.reduce alwaysProduceSameOutput2 Array.reduceBack' Array.reduceBack + alwaysProduceSameOutput2 Array.replicate' Array.replicate alwaysProduceSameOutput2 Array.skip' Array.skip alwaysProduceSameOutput2 Array.splitAt' Array.splitAt alwaysProduceSameOutput2 Array.splitInto' Array.splitInto @@ -86,4 +96,12 @@ let ``Safe Array functions always produce the same output as unsafe versions for alwaysProduceSameOutput2 Array.zip' Array.zip alwaysProduceSameOutput3 Array.zip3' Array.zip3 +module SafeByType = + open SeqSpec.SafeByType + + [] + let ``Array functions that are safe by type behave like the base functions`` () = + let (>>>) f g = (fun a b -> f a b |> g) + alwaysProduceSameOutput2 (initByIntsNonEmpty >>> upcastNE) (Array.init << PositiveInt.value) + alwaysProduceSameOutput2 (Array.NonEmpty.replicateN >>> upcastNE) (Array.replicate << PositiveInt.value) diff --git a/SafetyFirst.Specs/FSeqSpec.fs b/SafetyFirst.Specs/FSeqSpec.fs index 5a0af03..8fac0c2 100644 --- a/SafetyFirst.Specs/FSeqSpec.fs +++ b/SafetyFirst.Specs/FSeqSpec.fs @@ -4,6 +4,7 @@ open NUnit.Framework open Swensen.Unquote open SafetyFirst +open SafetyFirst.Numbers [] let ``can construct an FSeq with the fseq function`` () = @@ -294,6 +295,11 @@ module Splitting = module SafeFunctions = open SeqSpec + let upcastNE = FSeq.NonEmpty.toSeq + + let initByInts' = (fun count initializer -> FSeq.init' count (NaturalInt.value >> initializer)) + let initByIntsNonEmpty = (fun count initializer -> FSeq.NonEmpty.initN count (NaturalInt.value >> initializer)) + let averageFloats' (xs:float fseq) = FSeq.average' xs let averageFloats (xs:float seq) = Seq.average xs let averageByFloats' (projection:_ -> float) xs = FSeq.averageBy' projection xs @@ -321,6 +327,7 @@ module SafeFunctions = errorsWheneverThrowsForFSeq2 averageByFloats' averageByFloats errorsWheneverThrowsForFSeq2 FSeq.findBack' Seq.findBack errorsWheneverThrowsForFSeq2 FSeq.findIndexBack' Seq.findIndexBack + errorsWheneverThrows2 initByInts' Seq.init errorsWheneverThrowsForFSeq1 FSeq.last' Seq.last errorsWheneverThrowsForFSeq1 FSeq.max' Seq.max errorsWheneverThrowsForFSeq2 FSeq.maxBy' Seq.maxBy @@ -328,6 +335,7 @@ module SafeFunctions = errorsWheneverThrowsForFSeq2 FSeq.minBy' Seq.minBy errorsWheneverThrowsForFSeq2 FSeq.reduce' Seq.reduce errorsWheneverThrowsForFSeq2 FSeq.reduceBack' Seq.reduceBack + errorsWheneverThrows2 FSeq.replicate' Seq.replicate errorsWheneverThrowsForFSeq2 FSeq.splitInto' Seq.splitInto errorsWheneverThrowsForSeq1 fseqTransposeComparable seqTransposeComparable @@ -340,16 +348,25 @@ module SafeFunctions = (fun a xs -> safeVersion a (fseq xs)) (fun a xs -> unsafeVersion a (List.toSeq xs)) + let alwaysProduceSameFSeqOutput1 safeVersion unsafeVersion = + alwaysProduceSameOutput1 (safeVersion >> Result.map FSeq.toSeq) unsafeVersion + + let alwaysProduceSameFSeqOutput2 safeVersion unsafeVersion = + alwaysProduceSameOutput2 + (fun a b -> safeVersion a b |> Result.map FSeq.toSeq) + unsafeVersion + let fseqSplitIntoComparable n xs = FSeq.splitInto' n xs |> Result.map (FSeq.map FSeq.toArray >> FSeq.toSeq) - + [] let ``Safe FSeq functions always produce the same output as unsafe versions for all random inputs`` () = alwaysProduceSameOutputForFSeq1 averageFloats' averageFloats alwaysProduceSameOutputForFSeq2 averageByFloats' averageByFloats alwaysProduceSameOutputForFSeq2 FSeq.findBack' Seq.findBack alwaysProduceSameOutputForFSeq2 FSeq.findIndexBack' Seq.findIndexBack + alwaysProduceSameFSeqOutput2 initByInts' Seq.init alwaysProduceSameOutputForFSeq1 FSeq.last' Seq.last alwaysProduceSameOutputForFSeq1 FSeq.max' Seq.max alwaysProduceSameOutputForFSeq2 FSeq.maxBy' Seq.maxBy @@ -357,8 +374,16 @@ module SafeFunctions = alwaysProduceSameOutputForFSeq2 FSeq.minBy' Seq.minBy alwaysProduceSameOutputForFSeq2 FSeq.reduce' Seq.reduce alwaysProduceSameOutputForFSeq2 FSeq.reduceBack' Seq.reduceBack + alwaysProduceSameFSeqOutput2 FSeq.replicate' Seq.replicate alwaysProduceSameOutputForFSeq2 fseqSplitIntoComparable Seq.splitInto alwaysProduceSameOutputForSeq1 fseqTransposeComparable seqTransposeComparable - - + module SafeByType = + open SeqSpec.SafeByType + + [] + let ``FSeq functions that are safe by type behave like the base functions`` () = + let (>>>) f g = (fun a b -> f a b |> g) + + alwaysProduceSameOutput2 (initByIntsNonEmpty >>> upcastNE) (Seq.init << PositiveInt.value) + alwaysProduceSameOutput2 (FSeq.NonEmpty.replicateN >>> upcastNE) (Seq.replicate << PositiveInt.value) \ No newline at end of file diff --git a/SafetyFirst.Specs/Generators.fs b/SafetyFirst.Specs/Generators.fs new file mode 100644 index 0000000..5205488 --- /dev/null +++ b/SafetyFirst.Specs/Generators.fs @@ -0,0 +1,23 @@ +module SafetyFirst.Specs.Generators + +open FsCheck +open SafetyFirst.Numbers + +type Numbers = + static member private gen_NaturalInt = gen { + let! x = Arb.generate + return NaturalInt.assume (abs x) + } + static member NaturalIntGen() = { new Arbitrary() with override this.Generator = Numbers.gen_NaturalInt } + + static member private gen_PositiveInt = gen { + let! t = Numbers.gen_NaturalInt + return t.Increment + } + static member PositiveIntGen() = { new Arbitrary() with override this.Generator = Numbers.gen_PositiveInt } + + static member private gen_NegativeInt = gen { + let! p = Numbers.gen_PositiveInt + return p.Opposite + } + static member NegativeIntGen() = { new Arbitrary() with override this.Generator = Numbers.gen_NegativeInt } \ No newline at end of file diff --git a/SafetyFirst.Specs/ListSpec.fs b/SafetyFirst.Specs/ListSpec.fs index 85e76a7..d9fc6ef 100644 --- a/SafetyFirst.Specs/ListSpec.fs +++ b/SafetyFirst.Specs/ListSpec.fs @@ -5,12 +5,18 @@ open FsCheck open SafetyFirst open SafetyFirst.Specs.SeqSpec +open SafetyFirst.Numbers + +let upcastNE = List.NonEmpty.toList let averageFloats' (xs:float list) = List.average' xs let averageFloats (xs:float list) = List.average xs let averageByFloats' (projection:_ -> float) xs = List.averageBy' projection xs let averageByFloats (projection:_ -> float) xs = List.averageBy projection xs +let initByInts' = (fun count initializer -> List.init' count (NaturalInt.value >> initializer)) +let initByIntsNonEmpty = (fun count initializer -> List.NonEmpty.initN count (NaturalInt.value >> initializer)) + let arrayTransposeComparable xs = Array.transpose xs |> Seq.map (Array.toList) |> Seq.toList @@ -33,6 +39,7 @@ let ``Safe List functions error whenever unsafe versions throw for all random in errorsWheneverThrows4 List.foldBack2' List.foldBack2 errorsWheneverThrows3 List.forall2' List.forall2 errorsWheneverThrows1 List.head' List.head + errorsWheneverThrows2 initByInts' List.init errorsWheneverThrows2 List.item' List.item errorsWheneverThrows1 List.last' List.last errorsWheneverThrows3 List.map2' List.map2 @@ -45,6 +52,7 @@ let ``Safe List functions error whenever unsafe versions throw for all random in errorsWheneverThrows2 List.pick' List.pick errorsWheneverThrows2 List.reduce' List.reduce errorsWheneverThrows2 List.reduceBack' List.reduceBack + errorsWheneverThrows2 List.replicate' List.replicate errorsWheneverThrows2 List.skip' List.skip errorsWheneverThrows2 List.splitAt' List.splitAt errorsWheneverThrows2 List.splitInto' List.splitInto @@ -70,6 +78,7 @@ let ``Safe List functions always produce the same output as unsafe versions for alwaysProduceSameOutput4 List.foldBack2' List.foldBack2 alwaysProduceSameOutput3 List.forall2' List.forall2 alwaysProduceSameOutput1 List.head' List.head + alwaysProduceSameOutput2 initByInts' List.init alwaysProduceSameOutput2 List.item' List.item alwaysProduceSameOutput1 List.last' List.last alwaysProduceSameOutput3 List.map2' List.map2 @@ -82,6 +91,7 @@ let ``Safe List functions always produce the same output as unsafe versions for alwaysProduceSameOutput2 List.pick' List.pick alwaysProduceSameOutput2 List.reduce' List.reduce alwaysProduceSameOutput2 List.reduceBack' List.reduceBack + alwaysProduceSameOutput2 List.replicate' List.replicate alwaysProduceSameOutput2 List.skip' List.skip alwaysProduceSameOutput2 List.splitAt' List.splitAt alwaysProduceSameOutput2 List.splitInto' List.splitInto @@ -92,3 +102,12 @@ let ``Safe List functions always produce the same output as unsafe versions for alwaysProduceSameOutput2 List.zip' List.zip alwaysProduceSameOutput3 List.zip3' List.zip3 +module SafeByType = + open SeqSpec.SafeByType + + [] + let ``List functions that are safe by type behave like the base functions`` () = + let (>>>) f g = (fun a b -> f a b |> g) + + alwaysProduceSameOutput2 (initByIntsNonEmpty >>> upcastNE) (List.init << PositiveInt.value) + alwaysProduceSameOutput2 (List.NonEmpty.replicateN >>> upcastNE) (List.replicate << PositiveInt.value) \ No newline at end of file diff --git a/SafetyFirst.Specs/SafetyFirst.Specs.fsproj b/SafetyFirst.Specs/SafetyFirst.Specs.fsproj index e8598c5..474a300 100644 --- a/SafetyFirst.Specs/SafetyFirst.Specs.fsproj +++ b/SafetyFirst.Specs/SafetyFirst.Specs.fsproj @@ -10,6 +10,7 @@ + @@ -23,6 +24,7 @@ + diff --git a/SafetyFirst.Specs/SeqSpec.fs b/SafetyFirst.Specs/SeqSpec.fs index 68cfc68..d840f5b 100644 --- a/SafetyFirst.Specs/SeqSpec.fs +++ b/SafetyFirst.Specs/SeqSpec.fs @@ -7,6 +7,18 @@ open Swensen.Unquote open SafetyFirst open SafetyFirst.Numbers +let check prop = + Arb.register() |> ignore + Check.QuickThrowOnFailure prop + +let upcastNE = Seq.NonEmpty.toSeq + +let initByInts' = (fun count initializer -> Seq.init' count (NaturalInt.value >> initializer)) + +[] +module SimplifiedFunctions = + let initByIntsNonEmpty = (fun count initializer -> Seq.NonEmpty.initN count (NaturalInt.value >> initializer)) + let errorsAndThrowsOrNeither safeVersion unsafeVersion = let throws = try @@ -28,25 +40,25 @@ let errorsWheneverThrows1 safeVersion unsafeVersion = let prop inputs = errorsAndThrowsOrNeither (safeVersion inputs) (lazy (unsafeVersion inputs)) - Check.QuickThrowOnFailure prop + check prop let errorsWheneverThrows2 safeVersion unsafeVersion = let prop input1 input2 = errorsAndThrowsOrNeither (safeVersion input1 input2) (lazy (unsafeVersion input1 input2)) - Check.QuickThrowOnFailure prop + check prop let errorsWheneverThrows3 safeVersion unsafeVersion = let prop input1 input2 input3 = errorsAndThrowsOrNeither (safeVersion input1 input2 input3) (lazy (unsafeVersion input1 input2 input3)) - Check.QuickThrowOnFailure prop + check prop let errorsWheneverThrows4 safeVersion unsafeVersion = let prop input1 input2 input3 input4 = errorsAndThrowsOrNeither (safeVersion input1 input2 input3 input4) (lazy (unsafeVersion input1 input2 input3 input4)) - Check.QuickThrowOnFailure prop + check prop let errorsWheneverThrowsForSeq1 safeVersion unsafeVersion = errorsWheneverThrows1 (List.toSeq >> safeVersion) (List.toSeq >> unsafeVersion) @@ -63,8 +75,10 @@ let ``Safe Seq functions error whenever unsafe versions throw for all random inp errorsWheneverThrowsForSeq2 Seq.find' Seq.find errorsWheneverThrowsForSeq2 Seq.findIndex' Seq.findIndex errorsWheneverThrowsForSeq1 Seq.head' Seq.head + errorsWheneverThrows2 initByInts' Seq.init errorsWheneverThrowsForSeq2 Seq.item' Seq.item errorsWheneverThrowsForSeq2 Seq.pick' Seq.pick + errorsWheneverThrows2 Seq.replicate' Seq.replicate errorsWheneverThrowsForSeq2 Seq.skip' Seq.skip errorsWheneverThrowsForSeq1 Seq.tail' Seq.tail errorsWheneverThrowsForSeq2 Seq.take' Seq.take @@ -80,13 +94,15 @@ let (|Float|_|) x = | :? float as a -> Some a | _ -> None +let areSameOutput = + function + | (Seq xs, Seq ys) -> Seq.toList xs = Seq.toList ys + | (Float x, Float y) -> x.Equals y + | (x, y) -> x = y + let safeAndUnsafeVersionProduceSameOutput safeVersion unsafeVersion = match safeVersion with - | Ok x -> - match (x, Lazy.force unsafeVersion) with - | (Seq xs, Seq ys) -> Seq.toList xs = Seq.toList ys - | (Float x, Float y) -> x.Equals y - | _ -> Lazy.force unsafeVersion = x + | Ok x -> areSameOutput (x, Lazy.force unsafeVersion) | _ -> true @@ -94,25 +110,25 @@ let alwaysProduceSameOutput1 safeVersion unsafeVersion = let prop inputs = safeAndUnsafeVersionProduceSameOutput (safeVersion inputs) (lazy (unsafeVersion inputs)) - Check.QuickThrowOnFailure prop + check prop let alwaysProduceSameOutput2 safeVersion unsafeVersion = let prop input1 input2 = safeAndUnsafeVersionProduceSameOutput (safeVersion input1 input2) (lazy (unsafeVersion input1 input2)) - Check.QuickThrowOnFailure prop + check prop let alwaysProduceSameOutput3 safeVersion unsafeVersion = let prop input1 input2 input3 = safeAndUnsafeVersionProduceSameOutput (safeVersion input1 input2 input3) (lazy (unsafeVersion input1 input2 input3)) - Check.QuickThrowOnFailure prop + check prop let alwaysProduceSameOutput4 safeVersion unsafeVersion = let prop input1 input2 input3 input4 = safeAndUnsafeVersionProduceSameOutput (safeVersion input1 input2 input3 input4) (lazy (unsafeVersion input1 input2 input3 input4)) - Check.QuickThrowOnFailure prop + check prop let alwaysProduceSameOutputForSeq1 safeVersion unsafeVersion = alwaysProduceSameOutput1 (List.toSeq >> safeVersion) (List.toSeq >> unsafeVersion) @@ -122,7 +138,6 @@ let alwaysProduceSameOutputForSeq2 safeVersion unsafeVersion = (fun a xs -> safeVersion a (List.toSeq xs)) (fun a xs -> unsafeVersion a (List.toSeq xs)) - [] let ``Safe Seq functions always produce the same output as unsafe versions for all random inputs`` () = alwaysProduceSameOutputForSeq2 Seq.chunkBySize' Seq.chunkBySize @@ -130,13 +145,71 @@ let ``Safe Seq functions always produce the same output as unsafe versions for a alwaysProduceSameOutputForSeq2 Seq.find' Seq.find alwaysProduceSameOutputForSeq2 Seq.findIndex' Seq.findIndex alwaysProduceSameOutputForSeq1 Seq.head' Seq.head + alwaysProduceSameOutput2 initByInts' Seq.init alwaysProduceSameOutputForSeq2 Seq.item' Seq.item alwaysProduceSameOutputForSeq2 Seq.pick' Seq.pick + alwaysProduceSameOutput2 Seq.replicate' Seq.replicate alwaysProduceSameOutputForSeq2 Seq.skip' Seq.skip alwaysProduceSameOutputForSeq1 Seq.tail' Seq.tail alwaysProduceSameOutputForSeq2 Seq.take' Seq.take alwaysProduceSameOutputForSeq2 Seq.windowed' Seq.windowed +module SafeByType = + let alwaysProduceSameOutput1 safeVersion baseVersion = + let prop inputs = + areSameOutput (safeVersion inputs, baseVersion inputs) + + check prop + + let alwaysProduceSameOutput2 safeVersion baseVersion = + let prop input1 input2 = + areSameOutput (safeVersion input1 input2, baseVersion input1 input2) + + check prop + + let alwaysProduceSameOutput3 safeVersion baseVersion = + let prop input1 input2 input3 = + areSameOutput (safeVersion input1 input2 input3, baseVersion input1 input2 input3) + + check prop + + let alwaysProduceSameOutput4 safeVersion baseVersion = + let prop input1 input2 input3 input4 = + areSameOutput (safeVersion input1 input2 input3 input4, baseVersion input1 input2 input3 input4) + + check prop + + [] + let ``Seq functions that are safe by type behave like the base functions`` () = + let (>>>) f g = (fun a b -> f a b |> g) + + alwaysProduceSameOutput2 (initByIntsNonEmpty >>> upcastNE) (Seq.init << PositiveInt.value) + alwaysProduceSameOutput2 (Seq.NonEmpty.replicateN >>> upcastNE) (Seq.replicate << PositiveInt.value) + +module Infinite = + let alwaysProduceSameInfiniteOutput actualVersion expectedVersion = + let sufficientlyInfinite = 20 + + let prop initialization = + let makeComparable s = s |> Seq.truncate sufficientlyInfinite |> Seq.toList + + (actualVersion initialization |> makeComparable) + = (expectedVersion initialization |> makeComparable) + + Check.QuickThrowOnFailure prop + + [] + let ``Infinite Seq initializers behave like Seq initInfinite`` () = + let ``InfiniteSeq.init`` = (fun initializer -> InfiniteSeq.init (NaturalInt.value >> initializer)) + let ``Seq.NonEmpty.initInfinitely`` = (fun initializer -> Seq.NonEmpty.initInfinitely (NaturalInt.value >> initializer)) + + let ``Seq.replicateInfinitely`` initial = Seq.initInfinite (fun _ -> initial) + + alwaysProduceSameInfiniteOutput ``InfiniteSeq.init`` Seq.initInfinite + alwaysProduceSameInfiniteOutput InfiniteSeq.replicate ``Seq.replicateInfinitely`` + alwaysProduceSameInfiniteOutput ``Seq.NonEmpty.initInfinitely`` Seq.initInfinite + alwaysProduceSameInfiniteOutput Seq.NonEmpty.replicateInfinitely ``Seq.replicateInfinitely`` + module Splitting = let ofNonEmpty (xs:seq<#seq<_>>) = Seq.toList <| Seq.map Seq.toList xs @@ -195,22 +268,21 @@ module Splitting = let alwaysFalse (_:int) = false test <@ - InfiniteSeq.split (fun i -> i % 20 = 0) (InfiniteSeq.init id) |> InfiniteSeq.take 3 |> ofNonEmpty + InfiniteSeq.split (fun i -> i % 20 = 0) (InfiniteSeq.init NaturalInt.value) |> InfiniteSeq.take 3 |> ofNonEmpty = [[0]; [1 .. 20]; [21 .. 40]] && - InfiniteSeq.splitPairwise (fun left right -> left % 20 = 0) (InfiniteSeq.init id) |> InfiniteSeq.take 3 |> ofNonEmpty + InfiniteSeq.splitPairwise (fun left right -> left % 20 = 0) (InfiniteSeq.init NaturalInt.value) |> InfiniteSeq.take 3 |> ofNonEmpty = [[0]; [1 .. 20]; [21 .. 40]] && - InfiniteSeq.split alwaysFalse (InfiniteSeq.init id) |> InfiniteSeq.head |> InfiniteSeq.take 40 |> FSeq.toList + InfiniteSeq.split alwaysFalse (InfiniteSeq.init NaturalInt.value) |> InfiniteSeq.head |> InfiniteSeq.take 40 |> FSeq.toList = [0 .. 39] && - InfiniteSeq.splitPairwise (fun left right -> false) (InfiniteSeq.init id) |> InfiniteSeq.head |> InfiniteSeq.take 40 |> FSeq.toList + InfiniteSeq.splitPairwise (fun left right -> false) (InfiniteSeq.init NaturalInt.value) |> InfiniteSeq.head |> InfiniteSeq.take 40 |> FSeq.toList = [0 .. 39] @> - diff --git a/SafetyFirst.Specs/StringSpec.fs b/SafetyFirst.Specs/StringSpec.fs new file mode 100644 index 0000000..eed53fd --- /dev/null +++ b/SafetyFirst.Specs/StringSpec.fs @@ -0,0 +1,16 @@ +module SafetyFirst.Specs.StringSpec + +open NUnit.Framework +open FsCheck + +open SafetyFirst +open SafetyFirst.Numbers + +open SafetyFirst.Specs.SeqSpec.SafeByType + +let initByInts = (fun count initializer -> String.initN count (NaturalInt.value >> initializer)) + +[] +let ``String functions that are safe by type behave like the base functions`` () = + alwaysProduceSameOutput2 initByInts (String.init << NaturalInt.value) + alwaysProduceSameOutput2 String.replicateN (String.replicate << NaturalInt.value) \ No newline at end of file diff --git a/SafetyFirst/Array.fs b/SafetyFirst/Array.fs index 69effa4..cab2cde 100644 --- a/SafetyFirst/Array.fs +++ b/SafetyFirst/Array.fs @@ -28,7 +28,7 @@ let inline averageBySafe selector xs = if Array.isEmpty xs then Error <| avgErr () else Ok <| Array.averageBy selector xs - + /// /// Returns the average of the results generated by applying the function to each element /// of the array. @@ -205,6 +205,27 @@ let headSafe xs = /// let inline head' xs = headSafe xs +/// +/// Creates an array given the dimension and a generator function to compute the elements. +/// Same as Array.init, but restricts count to a NaturalInt, and provides NaturalInt indices to initializer. +/// +let initN (count:NaturalInt) initializer = Array.init count.Value (initializer << (NaturalInt.verify >> Option.unless "F# core assumption failed: Array.init called an initializer with a negative index.")) + +/// +/// Creates an array given the dimension and a generator function to compute the elements. +/// Returns a NegativeInput Error when count is not natural. +/// +let initSafe count initializer = + match count with + | NonNatural _ -> Error <| initErr count + | Natural count -> Ok <| initN count initializer + +/// +/// Creates an array given the dimension and a generator function to compute the elements. +/// Returns a NegativeInput Error when count is not natural. +/// +let inline init' count initializer = initSafe count initializer + /// /// Computes the element at the specified index in the collection. /// Returns an IndexOutOfRange Error if the index is negative or exceeds the size of the collection. @@ -435,6 +456,27 @@ let reduceBackSafe reduction xs = /// let inline reduceBack' reduction xs = reduceBackSafe reduction xs +/// +/// Creates an array by replicating the given initial value. +/// Same as Array.replicate, but restricts count to a NaturalInt. +/// +let replicateN (count:NaturalInt) initial = Array.replicate count.Value initial + +/// +/// Creates an array by replicating the given initial value. +/// Returns a NegativeInput Error when count is not natural. +/// +let replicateSafe count initial = + match count with + | NonNatural _ -> Error <| replicateErr count + | Natural count -> Ok <| replicateN count initial + +/// +/// Creates an array by replicating the given initial value. +/// Returns a NegativeInput Error when count is not natural. +/// +let inline replicate' count initial = replicateSafe count initial + /// /// Returns an array that skips N elements of the underlying array and then yields the /// remaining elements of the array. @@ -751,6 +793,12 @@ module NonEmpty = /// let indexed (NonEmpty xs : NonEmptyArray<_>) : NonEmptyArray<_> = NonEmpty (Array.indexed xs) + /// + /// Creates an array given the dimension and a generator function to compute the elements. + /// Same as Array.init, but restricts count to a PositiveInt, and provides NaturalInt indices to initializer. + /// + let initN (count:PositiveInt) initializer = create (initializer NaturalInt.zero) (initN count.Decrement (NaturalInt.increment >> PositiveInt.asNatural >> initializer)) + /// /// Builds a new collection whose elements are the results of applying the given function /// to each of the elements of the collection. The given function will be applied @@ -941,6 +989,12 @@ module NonEmpty = /// let pairwise (NonEmpty xs : NonEmptyArray<_>) = Array.pairwise xs + /// + /// Creates an array by replicating the given initial value. + /// Same as Array.replicate, but restricts count to a PositiveInt. + /// + let replicateN (count:PositiveInt) initial = create initial (replicateN count.Decrement initial) + /// /// Returns a new sequence with the elements in reverse order. /// diff --git a/SafetyFirst/ErrorTypes.fs b/SafetyFirst/ErrorTypes.fs index 4fc6375..a9bb844 100644 --- a/SafetyFirst/ErrorTypes.fs +++ b/SafetyFirst/ErrorTypes.fs @@ -47,6 +47,7 @@ module internal ErrorTypes = let fold2Err length1 length2 = differingLengthsErr "fold2" length1 length2 let forall2Err length1 length2 = differingLengthsErr "forall2" length1 length2 let headErr = SeqIsEmpty "Cannot get the first element (head) of an empty sequence" + let initErr n = NegativeInput <| sprintf "Cannot initialize a collection of length %i; the count must be positive" n let iter2Err length1 length2 = differingLengthsErr "iter2" length1 length2 let iteri2Err length1 length2 = differingLengthsErr "iteri2" length1 length2 let lazyIndexTooLargeErr index = IndexOutOfRange <| sprintf "Cannot access the element at index %i in the input sequence because the sequence doesn't have enough elements" index @@ -61,6 +62,7 @@ module internal ErrorTypes = let minErr = SeqIsEmpty "Cannot produce a minimum element from an empty sequence" let pickErr = NoMatchingElement "Cannot find an element in the input collection matching the input predicate" let reduceErr = SeqIsEmpty "Cannot reduce an empty sequence" + let replicateErr n = NegativeInput <| sprintf "Cannot create a collection by replicating an element %i times; the count must be positive" n let lazySkipErr n = NotEnoughElements <| sprintf "Cannot skip %i elements of the input collection, since it does not contain enough elements" n let skipErr n length = NotEnoughElements <| sprintf "Cannot skip %i elements of the input collection, since it only contains %i elements" n length let splitIntoErr n = NegativeInput <| sprintf "Cannot splitInto %i chunks; the count must be positive" n diff --git a/SafetyFirst/FiniteSeqModule.fs b/SafetyFirst/FiniteSeqModule.fs index cdd10a2..59ae330 100644 --- a/SafetyFirst/FiniteSeqModule.fs +++ b/SafetyFirst/FiniteSeqModule.fs @@ -267,6 +267,27 @@ module FiniteSeq = /// let indexed (xs : FiniteSeq<_>) : FiniteSeq<_> = fseq (Seq.indexed xs) + /// + /// Generates a new sequence which, when iterated, will return successive elements by calling the given function, up to the given count. + /// Same as Seq.init, but restricts count to a NaturalInt, and provides NaturalInt indices to initializer. + /// + let initN count initializer = fseq (LazyList.initN count initializer) + + /// + /// Generates a new sequence which, when iterated, will return successive elements by calling the given function, up to the given count. + /// Returns a NegativeInput Error when count is not natural. + /// + let initSafe count initializer = + match count with + | NonNatural _ -> Error <| initErr count + | Natural count -> Ok <| initN count initializer + + /// + /// Generates a new sequence which, when iterated, will return successive elements by calling the given function, up to the given count. + /// Returns a NegativeInput Error when count is not natural. + /// + let inline init' count initializer = initSafe count initializer + /// /// Returns true if the sequence contains no elements, false otherwise. /// @@ -501,6 +522,27 @@ module FiniteSeq = /// let inline reduceBack' f xs = reduceBackSafe f xs + /// + /// Generates a new sequence which, when iterated, will return the given value for every element, up to the given count. + /// Same as Seq.replicate, but restricts count to a NaturalInt. + /// + let replicateN (count:NaturalInt) initial = fseq (LazyList.replicateN count initial) + + /// + /// Generates a new sequence which, when iterated, will return the given value for every element, up to the given count. + /// Returns a NegativeInput Error when count is not natural. + /// + let replicateSafe count initial = + match count with + | NonNatural _ -> Error <| replicateErr count + | Natural count -> Ok <| replicateN count initial + + /// + /// Generates a new sequence which, when iterated, will return the given value for every element, up to the given count. + /// Returns a NegativeInput Error when count is not natural. + /// + let inline replicate' count initial = replicateSafe count initial + /// /// Returns a new sequence with the elements in reverse order. /// @@ -1063,6 +1105,24 @@ module FSeq = /// exception of the first element which is only returned as the predecessor of the second element. /// let inline pairwise xs : _ fseq = FiniteSeq.pairwise xs + + /// + /// Generates a new sequence which, when iterated, will return the given value for every element, up to the given count. + /// Same as Seq.replicate, but restricts count to a NaturalInt. + /// + let inline replicateN count initial = FiniteSeq.replicateN count initial + + /// + /// Generates a new sequence which, when iterated, will return the given value for every element, up to the given count. + /// Returns a NegativeInput Error when count is not natural. + /// + let inline replicateSafe count initial = FiniteSeq.replicateSafe count initial + + /// + /// Generates a new sequence which, when iterated, will return the given value for every element, up to the given count. + /// Returns a NegativeInput Error when count is not natural. + /// + let inline replicate' count initial = replicateSafe count initial /// /// Returns a new sequence with the elements in reverse order. @@ -1237,6 +1297,24 @@ module FSeq = /// let inline indexed (xs : _ fseq) : _ fseq = FiniteSeq.indexed xs + /// + /// Generates a new sequence which, when iterated, will return successive elements by calling the given function, up to the given count. + /// Same as Seq.init, but restricts count to a NaturalInt, and provides NaturalInt indices to initializer. + /// + let inline initN count initializer = FiniteSeq.initN count initializer + + /// + /// Generates a new sequence which, when iterated, will return successive elements by calling the given function, up to the given count. + /// Returns a NegativeInput Error when count is not natural. + /// + let inline initSafe count initializer = FiniteSeq.initSafe count initializer + + /// + /// Generates a new sequence which, when iterated, will return successive elements by calling the given function, up to the given count. + /// Returns a NegativeInput Error when count is not natural. + /// + let inline init' count initializer = initSafe count initializer + /// /// Applies the given function to each element of the collection. /// @@ -1792,6 +1870,12 @@ module FSeq = /// let indexed (NonEmptyFSeq xs) : NonEmptyFSeq<_> = NonEmpty (indexed xs) + /// + /// Generates a new sequence which, when iterated, will return successive elements by calling the given function, up to the given count. + /// Same as Seq.init, but restricts count to a PositiveInt, and provides NaturalInt indices to initializer. + /// + let initN (count:PositiveInt) initializer = create (initializer NaturalInt.zero) (initN count.Decrement (NaturalInt.increment >> PositiveInt.asNatural >> initializer)) + /// /// Asserts that xs is not empty, creating a NonEmpty FSeq. /// Returns a SeqIsEmpty Error if xs is empty. @@ -1813,6 +1897,12 @@ module FSeq = /// let pairwise (NonEmptyFSeq xs) = pairwise xs + /// + /// Generates a new sequence which, when iterated, will return the given value for every element, up to the given count. + /// Same as Seq.replicate, but restricts count to a PositiveInt. + /// + let replicateN (count:PositiveInt) initial = create initial (replicateN count.Decrement initial) + /// /// Returns a new sequence with the elements in reverse order. /// diff --git a/SafetyFirst/InfiniteSeq.fs b/SafetyFirst/InfiniteSeq.fs index ca30449..055b3a9 100644 --- a/SafetyFirst/InfiniteSeq.fs +++ b/SafetyFirst/InfiniteSeq.fs @@ -36,7 +36,7 @@ module InfiniteSeq = /// regenerate the elements. The function is passed the index of the item being /// generated. /// - let init transform = InfiniteSeq (Seq.initInfinite transform) + let init transform = InfiniteSeq (Seq.initInfinite (transform << (NaturalInt.verify >> Option.unless "F# core assumption failed: Seq.initInfinite called an initializer with a negative index."))) /// /// Computes the element at the specified index in the collection. @@ -64,6 +64,11 @@ module InfiniteSeq = /// let chunksOf chunkSize (InfiniteSeq xs) = InfiniteSeq <| Seq.chunksOf chunkSize xs + /// + /// Generates a new sequence which, when iterated, will return the given value for every element. + /// + let replicate initial = Seq.initInfinite (fun _ -> initial) + /// /// Returns the first N elements of the sequence. /// diff --git a/SafetyFirst/LazyList.fs b/SafetyFirst/LazyList.fs index 4fe655e..9a316b6 100644 --- a/SafetyFirst/LazyList.fs +++ b/SafetyFirst/LazyList.fs @@ -1,8 +1,8 @@ namespace SafetyFirst.FSharpxCopy.Collections -//THIS FILE LIFTED STRAIGHT FROM https://github.com/fsprojects/FSharpx.Collections/blob/311c74433fd616611554f38f182bf860c24b91ee/src/FSharpx.Collections/LazyList.fs +//THIS FILE (partially) LIFTED STRAIGHT FROM https://github.com/fsprojects/FSharpx.Collections/blob/311c74433fd616611554f38f182bf860c24b91ee/src/FSharpx.Collections/LazyList.fs //FSharpx.Collections is forcing the use of .NET Framework instead of .NET Standard -//once FSharpx.Collections v2.0 is released, we can delete this file and reference FSharpx.Collections instead +//once FSharpx.Collections v2.0 is released, we can remove much of the contents of this file and reference FSharpx.Collections instead open System open System.Collections.Generic @@ -126,9 +126,23 @@ module LazyList = | CellCons(a,b) -> consc a (append b l2) let delayed f = lzy(fun () -> (getCell (f()))) - let repeat x = - let rec s = cons x (delayed (fun () -> s)) in s + let initInfinitely (initializer:NaturalInt -> 'a) = + let rec s i () = consc (initializer i) (lzy (s i.Increment.AsNatural)) + lzy (s NaturalInt.zero) + + let initN count (initializer:NaturalInt -> 'a) = + let rec s i () = if i = count then CellEmpty else consc (initializer i) (lzy (s i.Increment.AsNatural)) + lzy (s NaturalInt.zero) + + let replicateInfinitely initial = + let rec s () = consc initial (lzy s) + lzy s + + let replicateN count initial = + let rec s n () = match n with | ZeroNatural -> CellEmpty | PositiveNatural count -> consc initial (lzy (s count.Decrement)) + lzy (s count) + let rec map f s = lzy(fun () -> match getCell s with @@ -279,12 +293,12 @@ module LazyList = let rev r = revAux r empty - let rec drop (NaturalInt n) xs = - match n - 1 with - | Natural nm1 when n > 0 -> //n > 0 is redundant, just clarifies + let rec drop (n:NaturalInt) xs = + match n with + | PositiveNatural p -> match xs with - | Cons(x, xs') -> drop nm1 xs' + | Cons(x, xs') -> drop p.Decrement xs' | _ -> EmptyValue<'T>.Value - | neg -> + | ZeroNatural -> xs diff --git a/SafetyFirst/List.fs b/SafetyFirst/List.fs index 1dedf65..abc8f42 100644 --- a/SafetyFirst/List.fs +++ b/SafetyFirst/List.fs @@ -211,6 +211,27 @@ let headSafe xs = /// let inline head' xs = headSafe xs +/// +/// Creates a list by calling the given generator on each index. +/// Same as List.init, but restricts count to a NaturalInt, and provides NaturalInt indices to initializer. +/// +let initN (count:NaturalInt) initializer = List.init count.Value (initializer << (NaturalInt.verify >> Option.unless "F# core assumption failed: List.init called an initializer with a negative index.")) + +/// +/// Creates a list by calling the given generator on each index. +/// Returns a NegativeInput Error when count is not natural. +/// +let initSafe count initializer = + match count with + | NonNatural _ -> Error <| initErr count + | Natural count -> Ok <| initN count initializer + +/// +/// Creates a list by calling the given generator on each index. +/// Returns a NegativeInput Error when count is not natural. +/// +let inline init' count initializer = initSafe count initializer + /// /// Computes the element at the specified index in the collection. /// Returns an IndexOutOfRange Error if the index is negative or exceeds the size of the collection. @@ -442,6 +463,27 @@ let reduceBackSafe reduction xs = /// let inline reduceBack' reduction xs = reduceBackSafe reduction xs +/// +/// Creates a list of a specified length with every element set to the given value. +/// Same as List.replicate, but restricts count to a NaturalInt. +/// +let replicateN (count:NaturalInt) initial = List.replicate count.Value initial + +/// +/// Creates a list of a specified length with every element set to the given value. +/// Returns a NegativeInput Error when count is not natural. +/// +let replicateSafe count initial = + match count with + | NonNatural _ -> Error <| replicateErr count + | Natural count -> Ok <| replicateN count initial + +/// +/// Creates a list of a specified length with every element set to the given value. +/// Returns a NegativeInput Error when count is not natural. +/// +let inline replicate' count initial = replicateSafe count initial + /// /// Returns a list that skips N elements of the underlying list and then yields the /// remaining elements of the list. @@ -647,6 +689,12 @@ module NonEmpty = /// let head (NonEmpty xs : NonEmptyList<_>) = List.head xs + /// + /// Creates a list by calling the given generator on each index. + /// Same as List.init, but restricts count to a PositiveInt, and provides NaturalInt indices to initializer. + /// + let initN (count:PositiveInt) initializer = create (initializer NaturalInt.zero) (initN count.Decrement (NaturalInt.increment >> PositiveInt.asNatural >> initializer)) + /// /// Returns the last element of the list. /// @@ -921,6 +969,12 @@ module NonEmpty = /// let pairwise (NonEmpty xs : NonEmptyList<_>) = List.pairwise xs + /// + /// Creates a list of a specified length with every element set to the given value. + /// Same as List.replicate, but restricts count to a PositiveInt. + /// + let replicateN (count:PositiveInt) initial = create initial (replicateN count.Decrement initial) + /// /// Returns a new sequence with the elements in reverse order. /// diff --git a/SafetyFirst/Numbers.fs b/SafetyFirst/Numbers.fs index 982e348..c34af26 100644 --- a/SafetyFirst/Numbers.fs +++ b/SafetyFirst/Numbers.fs @@ -4,16 +4,52 @@ namespace SafetyFirst.Numbers /// An integer >= 0 /// [] -type NaturalInt = private NaturalInt of int +type NaturalInt = + private | NaturalInt of int -[] -type NegativeInt = private NegativeInt of int + static member zero = NaturalInt 0 + + member this.Value = let (NaturalInt t) = this in t + static member value (t:NaturalInt) = t.Value + static member op_Implicit (t:NaturalInt) = t.Value + + member this.Increment = let (NaturalInt t) = this in PositiveInt (t + 1) + static member increment (t:NaturalInt) = t.Increment + +and [] NegativeInt = + private | NegativeInt of int + + member this.Value = let (NegativeInt n) = this in n + static member value (n:NegativeInt) = n.Value + static member op_Implicit (n:NegativeInt) = n.Value + + member this.Decrement = let (NegativeInt n) = this in NegativeInt (n - 1) + static member decrement (n:NegativeInt) = n.Decrement + + member this.Opposite = let (NegativeInt n) = this in PositiveInt -n + static member opposite (n:NegativeInt) = n.Opposite /// /// An integer > 0. If you want to include 0, use NaturalInt /// -[] -type PositiveInt = private PositiveInt of int +and [] PositiveInt = + private | PositiveInt of int + + member this.Value = let (PositiveInt p) = this in p + static member value (p:PositiveInt) = p.Value + static member op_Implicit (p:PositiveInt) = p.Value + + member this.AsNatural = let (PositiveInt p) = this in NaturalInt p + static member asNatural (p:PositiveInt) = p.AsNatural + + member this.Increment = let (PositiveInt p) = this in PositiveInt (p + 1) + static member increment (p:PositiveInt) = p.Increment + + member this.Decrement = let (PositiveInt p) = this in NaturalInt (p - 1) + static member decrement (p:PositiveInt) = p.Decrement + + member this.Opposite = let (PositiveInt p) = this in NegativeInt -p + static member opposite (p:PositiveInt) = p.Opposite [] module NaturalIntMatchers = @@ -30,6 +66,10 @@ module NaturalIntMatchers = | i when i < 0 -> Negative (NegativeInt i) | _ -> Zero + let (|PositiveNatural|ZeroNatural|) = function + | (NaturalInt i) when i > 0 -> PositiveNatural (PositiveInt i) + | _ -> ZeroNatural + module NaturalInt = let verify = function | Natural i -> Some i diff --git a/SafetyFirst/Seq.fs b/SafetyFirst/Seq.fs index 657b22c..3fd1c5b 100644 --- a/SafetyFirst/Seq.fs +++ b/SafetyFirst/Seq.fs @@ -86,6 +86,27 @@ let headSafe xs = /// let inline head' xs = headSafe xs +/// +/// Generates a new sequence which, when iterated, will return successive elements by calling the given function, up to the given count. +/// Same as Seq.init, but restricts count to a NaturalInt, and provides NaturalInt indices to initializer. +/// +let initN (count:NaturalInt) initializer = Seq.init count.Value (initializer << (NaturalInt.verify >> Option.unless "F# core assumption failed: Seq.init called an initializer with a negative index.")) + +/// +/// Generates a new sequence which, when iterated, will return successive elements by calling the given function, up to the given count. +/// Returns a NegativeInput Error when count is not natural. +/// +let initSafe count initializer = + match count with + | NonNatural _ -> Error <| initErr count + | Natural count -> Ok <| initN count initializer + +/// +/// Generates a new sequence which, when iterated, will return successive elements by calling the given function, up to the given count. +/// Returns a NegativeInput Error when count is not natural. +/// +let inline init' count initializer = initSafe count initializer + /// /// Computes the element at the specified index in the collection. /// Returns an IndexOutOfRange Error if the index is negative or exceeds the size of the collection. @@ -121,6 +142,27 @@ let pickSafe chooser xs = /// let inline pick' chooser xs = pickSafe chooser xs +/// +/// Generates a new sequence which, when iterated, will return the given value for every element, up to the given count. +/// Same as Seq.replicate, but restricts count to a NaturalInt. +/// +let replicateN (count:NaturalInt) initial = Seq.replicate count.Value initial + +/// +/// Generates a new sequence which, when iterated, will return the given value for every element, up to the given count. +/// Returns a NegativeInput Error when count is not natural. +/// +let replicateSafe count initial = + match count with + | NonNatural _ -> Error <| replicateErr count + | Natural count -> Ok <| replicateN count initial + +/// +/// Generates a new sequence which, when iterated, will return the given value for every element, up to the given count. +/// Returns a NegativeInput Error when count is not natural. +/// +let inline replicate' count initial = replicateSafe count initial + /// /// Returns a sequence that skips N elements of the underlying sequence and then yields the /// remaining elements of the sequence. @@ -260,6 +302,27 @@ module NonEmpty = /// let indexed (NonEmpty xs) : NonEmptySeq<_> = NonEmpty (Seq.indexed xs) + /// + /// Generates a new sequence which, when iterated, will return successive elements by calling the given function. + /// Same as Seq.initInfinite, but provides NaturalInt indices to initializer. + /// + let initInfinitely initializer = + create + (initializer NaturalInt.zero) + (Seq.initInfinite ( + NaturalInt.verify + >> Option.unless "F# core assumption failed: Seq.initInfinite called an initializer with a negative index." + >> NaturalInt.increment + >> PositiveInt.asNatural + >> initializer + )) + + /// + /// Generates a new sequence which, when iterated, will return successive elements by calling the given function, up to the given count. + /// Same as Seq.init, but restricts count to a PositiveInt, and provides NaturalInt indices to initializer. + /// + let initN (count:PositiveInt) initializer = create (initializer NaturalInt.zero) (initN count.Decrement (NaturalInt.increment >> PositiveInt.asNatural >> initializer)) + /// /// Builds a new collection whose elements are the results of applying the given function /// to each of the elements of the collection. The given function will be applied @@ -352,6 +415,17 @@ module NonEmpty = /// let pairwise (NonEmpty xs) = Seq.pairwise xs + /// + /// Generates a new sequence which, when iterated, will return the given value for every element. + /// + let replicateInfinitely initial = create initial (Seq.initInfinite (fun _ -> initial)) + + /// + /// Generates a new sequence which, when iterated, will return the given value for every element, up to the given count. + /// Same as Seq.replicate, but restricts count to a PositiveInt. + /// + let replicateN (count:PositiveInt) initial = create initial (replicateN count.Decrement initial) + /// /// Like fold, but computes on-demand and returns the sequence of intermediary and final results. /// diff --git a/SafetyFirst/String.fs b/SafetyFirst/String.fs index d1b8670..697413c 100644 --- a/SafetyFirst/String.fs +++ b/SafetyFirst/String.fs @@ -1,6 +1,19 @@ module SafetyFirst.String open System +open SafetyFirst.Numbers + +/// +/// Alternate version of String.init with specific, safe types. +/// Creates a new string whose characters are the results of applying a specified function to each index and concatenating the resulting strings. +/// +let initN (count:NaturalInt) initializer = String.init count.Value (initializer << (NaturalInt.verify >> Option.unless "F# core assumption failed: String.init called an initializer with a negative index.")) + +/// +/// Alternate version of String.replicate with specific, safe types. +/// Generates a new sequence which, when iterated, will return the given value for every element, up to the given count. +/// +let replicateN (count:NaturalInt) initial = String.replicate count.Value initial /// /// Splits a string into substrings based on the strings in a sequence.