diff --git a/unison-src/transcripts/pretty-print-libraries.md b/unison-src/transcripts/pretty-print-libraries.md new file mode 100644 index 0000000000..639f13a9fd --- /dev/null +++ b/unison-src/transcripts/pretty-print-libraries.md @@ -0,0 +1,13 @@ +This transcript is to detect changes in the pretty-printer for a few major public libraries. + +We clone releases and not dev branches to avoid external changes, and also to reduce the time needed to clone the libraries. + +```ucm +scratch/main> clone @unison/base/releases/3.19.0 +@unison/base/releases/3.19.0> edit.namespace +``` + +```ucm +scratch/main> clone @unison/http/releases/3.3.2 +@unison/http/releases/3.3.2> edit.namespace +``` diff --git a/unison-src/transcripts/pretty-print-libraries.output.md b/unison-src/transcripts/pretty-print-libraries.output.md new file mode 100644 index 0000000000..f78faa9916 --- /dev/null +++ b/unison-src/transcripts/pretty-print-libraries.output.md @@ -0,0 +1,87504 @@ +This transcript is to detect changes in the pretty-printer for a few major public libraries. + +We clone releases and not dev branches to avoid external changes, and also to reduce the time needed to clone the libraries. + +``` ucm +scratch/main> clone @unison/base/releases/3.19.0 + + Downloaded 13100 entities. + + Cloned @unison/base/releases/3.19.0. + +@unison/base/releases/3.19.0> edit.namespace + + ☝️ + + I added 6902 definitions to the top of scratch.u + + You can edit them there, then run `update` to replace the + definitions currently in this namespace. + +``` +```` unison:added-by-ucm scratch.u +structural ability abilities.Abort where abort : {abilities.Abort} a + +structural ability abilities.Ask a where ask : {abilities.Ask a} a + +ability abilities.Clock where + elapsed : {abilities.Clock} Duration + now : {abilities.Clock} Instant + +structural ability abilities.Each where + lazily : '{Stream a} () ->{abilities.Each} a + +structural ability abilities.Exception where + raise : Failure ->{abilities.Exception} x + +type abilities.Exception.Generic + = + +ability abilities.Label where + label : Text -> a ->{abilities.Label} () + popScope : {abilities.Label} () + pushScope : Text ->{abilities.Label} () + +ability abilities.Random where + split! : {abilities.Random} (∀ g a. '{g, abilities.Random} a ->{g} a) + nat! : {abilities.Random} Nat + bytes : Nat ->{abilities.Random} Bytes + +structural type abilities.Random.RNG + = RNG (∀ g a. '{Random, g} a ->{g} a) + +-- abilities.Request is built-in. + +structural ability abilities.Store a where + put : a ->{abilities.Store a} () + get : {abilities.Store a} a + +structural ability abilities.Throw e where throw : e ->{abilities.Throw e} a + +ability abilities.Wait where wait : Duration ->{abilities.Wait} () + +-- Any is built-in. + +-- Boolean is built-in. + +-- Bytes is built-in. + +structural type Bytes.base32Hex.Hex32Piece + = Single Nat Nat Bytes + | Double Nat Nat Nat Bytes + +-- Char is built-in. + +-- Char.Class is built-in. + +type crypto.CryptoFailure + = + +type crypto.Ed25519.PrivateKey + = PrivateKey Bytes + +type crypto.Ed25519.PublicKey + = PublicKey Bytes + +type crypto.Ed25519.Signature + = Signature Bytes + +-- crypto.HashAlgorithm is built-in. + +type crypto.Rsa.PrivateKey + = PrivateKey Bytes + +type crypto.Rsa.PublicKey + = PublicKey Bytes + +type crypto.Rsa.Signature + = Signature Bytes + +structural type data.Array a + = Arr Nat Nat (data.Array.Raw a) + +type data.Array.ArrayFailure + = + +-- data.Array.Raw is built-in. + +structural type data.Bag a + = internal.MkBag (Map a Nat) + +structural type data.ByteArray + = BArr Nat Nat data.ByteArray.Raw + +-- data.ByteArray.Raw is built-in. + +structural type data.deprecated.Heap k v + = Heap Nat k v [data.deprecated.Heap k v] + +structural type data.deprecated.Weighted a + = Weight Nat ('data.deprecated.Weighted a) + | Fail + | Yield a (data.deprecated.Weighted a) + +type data.Graph v + = AdjLists (data.Array.Raw [Nat]) (data.Array.Raw v) + +type data.Graph.SCC v + = Acyclic v + | Cyclic [v] + +structural type data.Id a + = Id a + +-- data.List is built-in. + +structural type data.List.Nonempty a + = Nonempty a [a] + +type data.Map k v + = internal.Bin Nat k v (data.Map k v) (data.Map k v) + | internal.Tip + +type data.Map.internal.MaxView k v + = MaxView k v (Map k v) + +type data.Map.internal.MinView k v + = MinView k v (Map k v) + +type data.Map.Nonempty k v + = Bin Nat k v (Map k v) (Map k v) + +type data.NatBag + = NatBag (NatMap Nat) + +type data.NatBag.Nonempty + = NatBag.Nonempty (NatMap.Nonempty Nat) + +type data.NatMap a + = NatMap (Optional (NatMap.Nonempty a)) + +type data.NatMap.Nonempty a + = NatMap.Nonempty.Tip Nat a + | NatMap.Nonempty.Bin + Nat Nat Nat (data.NatMap.Nonempty a) (data.NatMap.Nonempty a) + +type data.NatSet + = NatSet (Optional NatSet.Nonempty) + +type data.NatSet.Nonempty + = Tip Nat Nat + | Bin Nat Nat Nat data.NatSet.Nonempty data.NatSet.Nonempty + +structural type data.OneOrBoth a b + = Both a b + | That b + | This a + +structural type data.SeqView a b + = VElem a b + | VEmpty + +structural type data.Set a + = internal.Set (Map a ()) + +structural type data.Set.Nonempty a + = Set (Map.Nonempty a ()) + +structural ability data.Stream e where emit : e ->{data.Stream e} () + +ability data.Stream.collate.test.Counter where + comma! : {data.Stream.collate.test.Counter} () + nat! : {data.Stream.collate.test.Counter} () + +structural type data.Trie k v + = Trie (Optional v) (Map k (data.Trie k v)) + +structural type data.Tuple a b + = Cons a b + +type Doc + = Folded Boolean Doc Doc + | Tooltip Doc Doc + | NamedLink Doc Doc + | Callout (Optional Doc) Doc + | NumberedList Nat [Doc] + | Special SpecialForm + | Code Doc + | Bold Doc + | Italic Doc + | Strikethrough Doc + | Blockquote Doc + | Aside Doc + | Group Doc + | Section Doc [Doc] + | Image Doc Doc (Optional Doc) + | Blankline + | Linebreak + | SectionBreak + | Table [[Doc]] + | Word Text + | Paragraph [Doc] + | BulletedList [Doc] + | Join [Doc] + | UntitledSection [Doc] + | Column [Doc] + | CodeBlock Text Doc + | Style Text Doc + | Anchor Text Doc + +type Doc.Deprecated + = Blob Text + | Link Link + | Source Link + | Signature Link.Term + | Evaluate Link.Term + | Join [Doc.Deprecated] + +type Doc.EmbedSvg + = EmbedSvg Text + +type Doc.FrontMatter + = FrontMatter [(Text, Text)] + +type Doc.LaTeXInline + = LaTeXInline Text + +type Doc.MediaSource + = { sourceUrl : Text, mimeType : Optional Text } + +type Doc.SpecialForm + = Example Nat Doc.Term + | ExampleBlock Nat Doc.Term + | Signature [Doc.Term] + | Link (Either Type Doc.Term) + | Embed Any + | EmbedInline Any + | Source [(Either Type Doc.Term, [Doc.Term])] + | FoldedSource [(Either Type Doc.Term, [Doc.Term])] + | SignatureInline Doc.Term + | Eval Doc.Term + | EvalInline Doc.Term + +type Doc.Term + = Term Any + +type Doc.Video + = { sources : [MediaSource], config : [(Text, Text)] } + +structural type Either a b + = Right b + | Left a + +-- Float is built-in. + +type GUID + = GUID Bytes + +-- Int is built-in. + +-- IO is built-in. + +-- IO.concurrent.MVar is built-in. + +-- IO.concurrent.Promise is built-in. + +-- IO.concurrent.STM is built-in. + +type IO.concurrent.STM.STMFailure + = + +type IO.concurrent.STM.TMap a + = TMap (TVar (Optional a)) [TVar (IO.concurrent.STM.TMap.impl.F a)] + +type IO.concurrent.STM.TMap.impl.F a + = One Bytes a + | Many (IO.concurrent.STM.TMap a) + | Empty + +type IO.concurrent.STM.TQueue a + = TQueue (TVar [a]) (TVar Nat) + +-- IO.concurrent.ThreadId is built-in. + +type IO.concurrent.ThreadKilledFailure + = + +structural type IO.concurrent.TMVar a + = TMVar (TVar (Optional a)) + +-- IO.concurrent.TVar is built-in. + +type IO.deprecated.EpochTime + = EpochTime Nat + +type IO.Failure + = Failure Type Text Any + +type IO.Failure.ArithmeticFailure + = + +type IO.Failure.MiscFailure + = + +type IO.Failure.RuntimeFailure + = + +type IO.FilePath + = FilePath Text + +type IO.FilePath.FileMode + = Read + | Write + | Append + | ReadWrite + +-- IO.Handle is built-in. + +type IO.Handle.BufferMode + = NoBuffering + | LineBuffering + | BlockBuffering + | SizedBlockBuffering Nat + +type IO.Handle.SeekMode + = AbsoluteSeek + | RelativeSeek + | SeekFromEnd + +type IO.Handle.Std + = StdIn + | StdOut + | StdErr + +type IO.IOError + = AlreadyExists + | NoSuchThing + | ResourceBusy + | ResourceExhausted + | EOF + | IllegalOperation + | PermissionDenied + | UserError + +type IO.IOFailure + = + +type IO.net.Connection + = Connection + (Bytes ->{IO, Exception} ()) + ('{IO, Exception} Bytes) + ('{IO, Exception} ()) + +type IO.net.HostName + = HostName Text + +type IO.net.Port + = Port Text + +-- IO.net.Socket is built-in. + +type IO.net.Socket.BoundServerSocket + = BoundServerSocket Socket + +type IO.net.Socket.ListeningServerSocket + = ListeningServerSocket Socket + +type IO.net.Socket.UnboundServerSocket + = UnboundServerSocket Socket + +-- IO.net.Tls is built-in. + +-- IO.net.Tls.Cipher is built-in. + +-- IO.net.Tls.ClientConfig is built-in. + +-- IO.net.Tls.PrivateKey is built-in. + +-- IO.net.Tls.ServerConfig is built-in. + +-- IO.net.Tls.SignedCert is built-in. + +type IO.net.Tls.TlsFailure + = + +type IO.net.Tls.TlsSocket + = TlsSocket Tls + +-- IO.net.Tls.Version is built-in. + +type IO.net.URI + = URI Scheme (Optional Authority) Path RawQuery Fragment + +type IO.net.URI.Authority + = { userInfo : Optional UserInfo, host : HostName, port : Optional Port } + +type IO.net.URI.Fragment + = Fragment Text + +type IO.net.URI.Method + = GET + | HEAD + | POST + | PUT + | DELETE + | CONNECT + | TRACE + | PATCH + | OPTIONS + +type IO.net.URI.ParseError + = + +type IO.net.URI.Path + = { segments : [Text] } + +type IO.net.URI.Query + = Query (Map Text [Text]) + +type IO.net.URI.RawQuery + = RawQuery Text + +type IO.net.URI.Scheme + = Scheme Text + +type IO.net.URI.UserInfo + = UserInfo Text + +-- IO.Process is built-in. + +type IPattern n a + = IPattern (Pattern a) + +type IPattern.And l r + = + +type IPattern.Capture + = + +type math.ArithmeticException + = DividedByZero + | Overflow + | Underflow + | NotANumber + | NegativeInfinityNotAllowed + | PositiveInfinityNotAllowed + +type math.Natural + = internal.Natural (List.Nonempty Nat) + +type metadata.Author + = Author GUID Text + +type metadata.CopyrightHolder + = CopyrightHolder GUID Text + +type metadata.IsPropagated + = IsPropagated + +type metadata.IsTest + = IsTest + +type metadata.License + = License [CopyrightHolder] [Year] LicenseType + +type metadata.LicenseType + = LicenseType Doc + +type metadata.Year + = Year Nat + +structural type mutable.Array g a + = MArr Nat Nat (mutable.Array.Raw g a) + +-- mutable.Array.Raw is built-in. + +structural type mutable.ByteArray g + = MBArr Nat Nat (mutable.ByteArray.Raw g) + +-- mutable.ByteArray.Raw is built-in. + +-- mutable.Ref is built-in. + +-- mutable.Ref.Ticket is built-in. + +-- mutable.Scope is built-in. + +-- Nat is built-in. + +structural type Optional a + = Some a + | None + +type Ordering + = Less + | Equal + | Greater + +-- Pattern is built-in. + +structural type Pretty txt + = Pretty (Annotated () txt) + +type Pretty.Annotated w txt + = Table w [[Pretty.Annotated w txt]] + | Append w [Pretty.Annotated w txt] + | OrElse w (Pretty.Annotated w txt) (Pretty.Annotated w txt) + | Indent + w + (Pretty.Annotated w txt) + (Pretty.Annotated w txt) + (Pretty.Annotated w txt) + | Group w (Pretty.Annotated w txt) + | Wrap w (Pretty.Annotated w txt) + | Empty + | Lit w txt + +-- reflection.Code is built-in. + +type reflection.Link + = Term Link.Term + | Type Type + +-- reflection.Link.Term is built-in. + +-- reflection.Link.Type is built-in. + +type reflection.RewriteCase a b + = RewriteCase a b + +type reflection.Rewrites a + = Rewrites a + +type reflection.RewriteSignature a b + = RewriteSignature (a -> b -> ()) + +type reflection.RewriteTerm a b + = RewriteTerm a b + +-- reflection.Value is built-in. + +type system.ANSI.Color + = Black + | Red + | Green + | Yellow + | Blue + | Magenta + | Cyan + | White + | BrightBlack + | BrightRed + | BrightGreen + | BrightYellow + | BrightBlue + | BrightMagenta + | BrightCyan + | BrightWhite + +type system.ConsoleText + = Bold system.ConsoleText + | Underline system.ConsoleText + | Invert system.ConsoleText + | Plain Text + | Foreground Color system.ConsoleText + | Background Color system.ConsoleText + +structural type test.deprecated.Domain a + = Large (Weighted a) + | Small [a] + +structural ability test.deprecated.Gen where + sample : Weighted a ->{test.deprecated.Gen} a + +type test.deprecated.internals.v1.Test.Labels + = Labels [Text] + +structural type test.deprecated.internals.v1.Test.Report + = Report (Trie Text Status) + +structural type test.deprecated.internals.v1.Test.Status + = Expected Success + | Unexpected Success + | Failed + | Pending + +structural type test.deprecated.internals.v1.Test.Success + = Passed Nat + | Proved + +structural type test.deprecated.Test + = Test (Labels -> Report) + +type test.Result + = Fail Text + | Ok Text + +type test.TestFailure + = + +-- Text is built-in. + +-- time.Clock.internals.TimeSpec is built-in. + +type time.DayOfWeek + = Sat + | Sun + | Mon + | Tue + | Wed + | Thu + | Fri + +type time.Duration + = internal.Duration Int Nat + +type time.Instant + = internal.Instant Int Nat + +type time.LocalDate + = { year : Int, month : Nat, day : Nat } + +type time.LocalDateTime + = { date : LocalDate, time : LocalTime } + +type time.LocalTime + = { hour : Nat, minute : Nat, second : Nat, nanosecond : Nat } + +structural type time.OffsetDateTime + = OffsetDateTime UTCOffset LocalDateTime + +type time.OffsetTime + = { offset : UTCOffset, time : LocalTime } + +type time.TimeZone + = { offset : UTCOffset, summerOnly : Boolean, name : Text } + +type time.UTCOffset + = UTCOffset Int + +structural type Unit + = Unit + +structural type Void + = + +abilities.Abort.abortWhen : Boolean ->{Abort} () +abilities.Abort.abortWhen b = if b then abort else () + +abilities.Abort.abortWhen.doc : Doc +abilities.Abort.abortWhen.doc = + {{ Abort if the given {type Boolean} is ``true``, otherwise do nothing. }} + +abilities.Abort.doc : Doc +abilities.Abort.doc = + use Nat / + {{ + The {type Abort} ability is used to terminate a computation without returning + a value or an error. This is useful for partial functions, where the function + is not defined for the given argument and either the caller doesn't need to + know why or it's obvious why the function is not defined. + + 📚 Guide: + [Handling errors with abilities](https://www.unison-lang.org/learn/fundamentals/abilities/error-handling/) + + # Aborting a computation + + {type Abort}'s only request constructor is {abort}. Here's an example of a + partial function that uses it: + + @typecheck ``` + divBy : Nat -> Nat ->{Abort} Nat + divBy a b = match b with + 0 -> abort + n -> a / b + ``` + + Abort if a condition is ``true``: + + @signature{abortWhen} + + # Relationship to {type Optional} + + The {type Abort} ability is similar to {type Optional}, in that both allow + a computation to fail without returning a value. There is a tradeoff + between the two approaches: + + * {type Abort} is an ability, while {type Optional} is a data type. This + means {type Optional} values can be passed around, used in data + structures, and as the return type of the constructors of abilities, + while {type Abort} can only be used in computations and functions. + * {type Abort} is more efficient, because it doesn't require allocating a + an {type Optional} value. + * {type Abort} readily composes with other abilities, while {type Optional} + does not. For example, you can use both {type Abort} and {type Random} to + combine partiality with randomness, but doing so with {type Optional} can + be awkward. + * Partial functions that use {type Abort} compose with other functions + normally, while partial functions that use {type Optional} must be + composed using {Optional.map}, {Optional.flatMap}, or explicit pattern + matching. + + However, {type Optional} and {type Abort} can be readily converted between + each other, so you can use whichever is more convenient for a given + situation. + + # Handling {type Abort} + + Throw a runtime error on {abort}: + + @signature{Abort.toBug} + + Fall back to the first computation if the second computation aborts: + + @signatures{toDefault, toDefault!} + + Fall back to the given value if the computation aborts: + + @signatures{toDefaultValue, toDefaultValue!} + + Raise a {type Failure} of a particular type if the computation aborts: + + @signature{Abort.toException} + + Raise a {type Generic} {type Failure} if the computation aborts: + + @signature{Abort.toGenericException} + + Return {None} if the computation aborts: + + @signatures{Abort.toOptional, toOptional!} + }} + +test> abilities.Abort.tests.ex1 = check ((toOptional! do abort) === None) + +test> abilities.Abort.tests.ex2 = + use Nat + + check ((toOptional! do 1 + abort) === None) + +test> abilities.Abort.tests.ex3 = + use Nat + + check ((toOptional! do Optional.toAbort (Some 1) + 1) === Some 2) + +abilities.Abort.toBug : '{g, Abort} a ->{g} a +abilities.Abort.toBug x = + handle x() + with cases + { abort -> _ } -> bug "Aborted unexpectedly." + { a } -> a + +abilities.Abort.toBug.doc : Doc +abilities.Abort.toBug.doc = + use Abort toBug + {{ + Takes a computation that may use the {type Abort} ability, and turns it into + a computation that crashes whenever the original would have called {abort}. + + # Examples + + ``` + toBug do if true then 42 else abort + ``` + + ``` + toBug do abort + ``` + }} + +abilities.Abort.toDefault : '{g} a -> '{g, Abort} a -> '{g} a +abilities.Abort.toDefault default thunk = do toDefault! default thunk + +abilities.Abort.toDefault.doc : Doc +abilities.Abort.toDefault.doc = + {{ + `` toDefault default thunk `` returns a delayed computation of + ``toDefault! default thunk``. + }} + +abilities.Abort.toDefault! : '{g} a -> '{g, Abort} a ->{g} a +abilities.Abort.toDefault! default thunk = + handle thunk() with toDefault!.handler default + +abilities.Abort.toDefault!.doc : Doc +abilities.Abort.toDefault!.doc = + {{ + `` toDefault! default thunk `` handles a {type Abort} computation `thunk` by + returning the result of the computation or, if the computation calls + ``abort``, returning the result of a delayed `default` computation. + }} + +abilities.Abort.toDefault!.handler : '{g} a -> Request {Abort} a ->{g} a +abilities.Abort.toDefault!.handler default = cases + { a } -> a + { abort -> _ } -> default() + +abilities.Abort.toDefaultValue : a -> '{g, Abort} a -> '{g} a +abilities.Abort.toDefaultValue default thunk = do toDefaultValue! default thunk + +abilities.Abort.toDefaultValue.doc : Doc +abilities.Abort.toDefaultValue.doc = + {{ + `` toDefaultValue default thunk `` returns a delayed computation of + ``toDefaultValue! default thunk``. + }} + +abilities.Abort.toDefaultValue! : a -> '{g, Abort} a ->{g} a +abilities.Abort.toDefaultValue! default thunk = + handle thunk() with toDefaultValue!.handler default + +abilities.Abort.toDefaultValue!.doc : Doc +abilities.Abort.toDefaultValue!.doc = + {{ + `` toDefaultValue! default thunk `` handles a {type Abort} computation + `thunk` by returning the result of the computation or, if the computation + calls ``abort``, returning the default value `default`. + }} + +abilities.Abort.toDefaultValue!.handler : a -> Request {Abort} a -> a +abilities.Abort.toDefaultValue!.handler default = cases + { a } -> a + { abort -> _ } -> default + +abilities.Abort.toException : '{g, Abort} a -> Type -> Text ->{g, Exception} a +abilities.Abort.toException a t msg = + toDefault! (do Exception.raise (Failure t msg (Any ()))) a + +abilities.Abort.toException.doc : Doc +abilities.Abort.toException.doc = + {{ + Takes a computation that may {abort}, and turns it into a computation that + will raise an {type Exception} with the given {type Failure} type and message + instead of aborting. + + # Example + + ``` + catch do + Abort.toException (do abort) (typeLink Generic) "Something went wrong" + ``` + }} + +test> abilities.Abort.toException.doesn'tThrowOnNoAbort = + check + (isRight (catch do Abort.toException (do ()) (typeLink Generic) "oops")) + +test> abilities.Abort.toException.throwsOnAbort = + check + (isLeft (catch do Abort.toException (do abort) (typeLink Generic) "oops")) + +abilities.Abort.toGenericException : Text -> e -> '{Abort} a ->{Exception} a +abilities.Abort.toGenericException msg e = + toDefault! do Exception.raise (failure msg e) + +abilities.Abort.toGenericException.doc : Doc +abilities.Abort.toGenericException.doc = + use Abort toGenericException + {{ + Converts a computation that may call {abort} to one that throws a + {type Generic} {type Exception} instead. + + # Example + + ``` + catch do + toGenericException "oops" "payload" do + abort + "result" + ``` + + ``` + catch do toGenericException "oops" "payload" do "result" + ``` + }} + +test> abilities.Abort.toGenericException.doesn'tThrowOnNoAbort = + check (isRight (catch do Abort.toGenericException "oops" () do ())) + +test> abilities.Abort.toGenericException.throwsOnAbort = + check (isLeft (catch do Abort.toGenericException "oops" () do abort)) + +abilities.Abort.toOptional : '{g, Abort} a -> '{g} Optional a +abilities.Abort.toOptional thunk = do toOptional! thunk + +abilities.Abort.toOptional.doc : Doc +abilities.Abort.toOptional.doc = + {{ + `` Abort.toOptional a `` returns a delayed computation of `` toOptional! a `` + }} + +abilities.Abort.toOptional! : '{g, Abort} a ->{g} Optional a +abilities.Abort.toOptional! thunk = toDefaultValue! None do Some thunk() + +abilities.Abort.toOptional!.doc : Doc +abilities.Abort.toOptional!.doc = + {{ + `` toOptional! `` runs a {type Abort} computation, returning `` Some `` of + the result if the computation succeeds, and `` None `` if it aborts. + }} + +abilities.Abort.toThrow : e -> '{g, Abort} a ->{g, Throw e} a +abilities.Abort.toThrow e c = + handle c() + with cases + { a } -> a + { abort -> _ } -> throw e + +abilities.Abort.toThrow.doc : Doc +abilities.Abort.toThrow.doc = + {{ + Takes a computation that may {type Abort} and executes it, throwing the given + error in {type Throw} if it calls {abort}. + + # Example + + ``` + toEither do Abort.toThrow "aborted" do abort + ``` + }} + +abilities.Ask.ask.doc : Doc +abilities.Ask.ask.doc = + use Nat + + {{ + The sole request constructor of the {type Ask} ability. It asks for a value + of a particular type, to be provided by the nearest enclosing handler. + + # Examples + + ``` + provide 2 do ask + ask + ``` + + ``` + Stream.toList (pipe (Stream.range 0 10) do forever do emit (ask + ask)) + ``` + }} + +abilities.Ask.doc : Doc +abilities.Ask.doc = + use Nat + + {{ + {type Ask} is the ability to read a value of type `a` provided by the handler + by calling {ask}. + + # Handlers + + ## Providing the same value each time + + The function {provide} is a handler for {type Ask} that provides the + given value wherever {ask} is called in the body of its second argument. + + @signature{provide} + + ### Example + + ``` + provide 42 do ask + ask + ``` + + ## Varying the value provided + + The function {provide'} is a handler like {provide}, but instead of + always providing the same value, can use an ability to provide a + different value each time. + + @signature{provide'} + + ### Example + + ``` + splitmix 1 do provide' (do Random.natIn 1 7) do fill' 8 do ask + ``` + + ## Using with other abilities + + {toStore} allows you to use an {type Ask} computation in the context of + a stateful {type Store} computation. Each call to {ask} will be treated + as a call to {Store.get}. + + @signature{toStore} + + # Applications + + {type Ask} is used by the {type Stream} ability's {pipe} function to model + transducers between streams: + + @signature{pipe!} + }} + +abilities.Ask.map : (a ->{g1} b) -> '{g2, Ask b} r -> '{g1, g2, Ask a} r +abilities.Ask.map f = delay (Ask.map! f) + +abilities.Ask.map.doc : Doc +abilities.Ask.map.doc = + {{ + `` Ask.map transformation op `` returns a delayed computation of {Ask.map!} + `transformation op`. + }} + +abilities.Ask.map! : (a ->{g1} b) -> '{g2, Ask b} r ->{g1, g2, Ask a} r +abilities.Ask.map! f = + go = cases + { r } -> r + { ask -> k } -> + a = ask + handle k (f a) with go + thunk -> (handle thunk() with go) + +abilities.Ask.map!.doc : Doc +abilities.Ask.map!.doc = + use map! example + {{ + `` Ask.map! transformation op `` runs `op` with a modified {type Ask} + environment. Each time that `op` calls {ask}, `transformation` will be + applied to the value before it is provided to `op`. + + # Examples + + @source{example} + + ``` + example + ``` + }} + +abilities.Ask.map!.example : Text +abilities.Ask.map!.example = + use Text ++ + indentation : '{Ask Nat} Text + indentation = do + indentationLevel = ask + Text.repeat indentationLevel " " + indent : '{g, Ask Nat} r ->{g, Ask Nat} r + indent = Ask.map! (indentationLevel -> Nat.increment indentationLevel) + line : Text ->{Ask Nat, Stream Text} () + line t = emit (indentation() ++ t) + lines : '{Ask Nat, Stream Text} () + lines = do + line "no indentation" + line "still no indentation" + indent do + line "one level of indentation" + line "still one level of indentation" + indent do + line "two levels of indentation" + line "still two levels of indentation" + line "back to one level of indentation" + line "back to no indentation" + provide 0 do Stream.toList lines |> Text.join "\n" + +test> abilities.Ask.map!.test = test.verify do + expected = + """ + no indentation + still no indentation + one level of indentation + still one level of indentation + two levels of indentation + still two levels of indentation + back to one level of indentation + back to no indentation + """ + actual = map!.example + ensureEqual expected actual + +abilities.Ask.provide : a -> '{g, Ask a} r ->{g} r +abilities.Ask.provide a asker = handle asker() with provide.handler a + +abilities.Ask.provide.doc : Doc +abilities.Ask.provide.doc = + use Nat + + {{ + A handler for the {type Ask} ability that provides the given value wherever + {ask} is called in the given computation. + + # Example + + ``` + provide 42 do ask + ask + ``` + }} + +abilities.Ask.provide.handler : a -> Request {Ask a} r -> r +abilities.Ask.provide.handler a = + h = cases + { r } -> r + { ask -> resume } -> handle resume a with h + h + +abilities.Ask.provide' : '{g} a -> '{g, Ask a} r ->{g} r +abilities.Ask.provide' currentValue thunk = + handle thunk() with provide'.handler currentValue + +abilities.Ask.provide'.doc : Doc +abilities.Ask.provide'.doc = + {{ + `` provide' currentValue f `` runs `f`, evaluating `currentValue` __each__ + time that {ask} is invoked. This differs from {provide}, which provides the + __same__ value each time that {ask} is called. + + # Examples + + If `currentValue` doesn't perform any effects, then the {ask} result will + be the same each time. + + ``` + provide' (do "hi") do fill' 3 do ask + ``` + + If `currentValue` performs an effect, then the {ask} result can vary. + + ``` + results : '{Ask Int} [Int] + results = do fill' 3 do ask + (do provide' Random.int results) |> lcg 42 + ``` + }} + +abilities.Ask.provide'.handler : '{g} a -> Request {Ask a} r ->{g} r +abilities.Ask.provide'.handler currentValue = + h = cases + { r } -> r + { ask -> resume } -> handle resume currentValue() with h + h + +test> abilities.Ask.provide'.tests = test.verify do + ensureEqual (provide' (do "hi") do (ask, ask, ask)) ("hi", "hi", "hi") + Scope.run do + n = Scope.ref 0 + incrementAndGet = do + Ref.modify n Nat.increment + Ref.read n + ensureEqual (provide' incrementAndGet do (ask, ask, ask)) (1, 2, 3) + +test> abilities.Ask.tests.ex1 = + use Nat + + check ((provide 10 do 1 + ask + ask) === 21) + +abilities.Ask.toStore : '{g, Ask a} r ->{g, Store a} r +abilities.Ask.toStore f = handle f() with toStore.handler + +abilities.Ask.toStore.doc : Doc +abilities.Ask.toStore.doc = + {{ Convert each {ask} in the provided computation into {Store.get}. }} + +abilities.Ask.toStore.handler : Request {Ask a} r ->{Store a} r +abilities.Ask.toStore.handler = + h = cases + { r } -> r + { ask -> resume } -> handle resume Store.get with h + h + +test> abilities.Ask.toStore.tests = test.verify do + inc = do Store.modify Nat.increment + program = do + x = ask + inc() + y = ask + inc() + z = ask + (x, y, z) + ensureEqual (withInitialValue 0 do toStore program) (0, 1, 2) + +abilities.Clock.doc : Doc +abilities.Clock.doc = + use Clock now + use Duration second + use Instant + + {{ + The {type Clock} ability is an abstract interface for querying the current + time and the time elapsed since a fixed point in the past. The {now} + operation returns the current time as an {type Instant}. The {elapsed} + operation returns the time elapsed on the system clock since a fixed point in + the past as a {type Duration}. + + The {type Clock} ability is useful for writing code that needs to be aware of + the current time or the time elapsed since a fixed point in the past, without + having to depend on the system clock directly. + + The Base library provides an implementation of the {type Clock} ability that + uses the system clock: {runClock}. You can use this implementation to run + computations that use the {type Clock} ability, in the {type IO} ability. + + # Example + + @typecheck ``` + runClock do now + elapsed + ``` + + # Example handler implementation + + The following is an example handler implementation for the {type Clock} + ability that uses an abstrack clock. Whenever the {now} or {elapsed} + operations are called, the handler advances the clock by one second. + + ``` + testClock startTime p = + handle p() + with cases + { now -> k } -> testClock (startTime + second) do k startTime + { elapsed -> k } -> + testClock (startTime + second) do + k (Instant.timeSinceEpoch startTime) + { a } -> a + testClock epoch do Instant.toRFC2822 (now + elapsed) + ``` + }} + +abilities.Clock.runClock : (a ->{g, Clock} b) -> a ->{g, IO, Exception} b +abilities.Clock.runClock f a = + h : '{g, IO, Exception, Clock} b ->{g, IO, Exception} b + h x = + handle x() + with cases + { Clock.now -> k } -> h do k realtime() + { elapsed -> k } -> h do k Clock.monotonic() + { a } -> a + h do f a + +abilities.Clock.runClock.doc : Doc +abilities.Clock.runClock.doc = + use Clock monotonic now + {{ + `` runClock f `` runs the computation `f` that uses the {type Clock} ability. + The {type Clock} ability provides two operations: {now} and {elapsed}. In + this implementation, the {now} operation returns the current civic time as an + {type Instant}, by calling {realtime}. The {elapsed} operation returns the + time elapsed on the system clock since a fixed point in the past, by calling + {monotonic}. + + # See also + + * {type Clock} - The ability type. + * {realtime} - Returns the current civic time in the {type IO} ability. + * {monotonic} - Returns the time elapsed on the system clock since a fixed + point in the past in the {type IO} ability. + }} + +abilities.Each.allowThrow : '{Throw e, Stream a} () ->{Each, Throw e} a +abilities.Each.allowThrow s = + x = lazily do catchWith (emit << Left) (Stream.map Right s) + match x with + Left e -> throw e + Right a -> a + +abilities.Each.allowThrow.doc : Doc +abilities.Each.allowThrow.doc = + {{ + Converts a computation that may {type Throw} in a {type Stream}, to a + computation that may {type Throw} in {type Each}. + + # Example + + ``` + Each.toList do + catchWith (do 0) do + allowThrow do + emit 1 + throw "oops" + emit 2 + ``` + }} + +abilities.Each.append : '{g, Each} a -> '{g, Each} a ->{g, Each} a +abilities.Each.append a b = + x = each [a, b] + x() + +abilities.Each.append.doc : Doc +abilities.Each.append.doc = + use Each append + {{ + Concatenates two {type Each} computations. The computation `` append x y `` + first produces all the results of `x` and then all the results of `y`. + + # Example + + ``` + Each.toList do append (do each [1, 2, 3]) do each [4, 5, 6] + ``` + + # See also + + * {Each.interleave} - interleaves two computations in a fair manner. + }} + +abilities.Each.count : '{g, Each} a ->{g} Nat +abilities.Each.count a = + use Nat + + step n _ = n + 1 + Each.toStream a |> Stream.fold step 0 + +abilities.Each.count.doc : Doc +abilities.Each.count.doc = + use Each count + {{ + Counts the number of elements produced by the given {type Each} computation, + without producing an intermediate {type List}. + + Examples: + + ``` + count do each [1, 2, 3] + ``` + + ``` + count do each [] + ``` + + A computation that doesn't use nondeterminism at all has a count of `1`: + + ``` + count do 99 + ``` + }} + +test> abilities.Each.count.tests = test.verify do + N = Each.range 0 25 + ns = Nat.range 0 N + c1 = Each.count do each ns + c2 = (Each.toList do each ns) |> List.size + ensureEqual c1 c2 + +abilities.Each.doc : Doc +abilities.Each.doc = + use Each range toList + use Nat * < + {{ + A nondeterminism ability that produces output lazily. It can be used for + nested loops, mimicking + [list comprehensions](https://en.wikipedia.org/wiki/List_comprehension) in + languages like Haskell or Python, and can also be used for logic programming + in the style of Prolog. + + # Basic usage + + ``` + toList do + a = range 0 5 + b = each [1, 2, 3] + guard (a < b) + (a, b) + ``` + + In the above example, `a` gets each value in the range `0` to `5` (not + including `5`), and `b` gets each value from the list ``[1, 2, 3]``. The + {guard} removes all pairs of `a` and `b` that don't satisfy the condition. + + Note that we don't need to declare a new binding for every use of + nondeterminism. For instance: + + ``` + toList do each [1, 2, 3, 4] * 100 + ``` + + This example multiplies each of the values in the list by 100. + + # Iteration + + The {type Each} ability can be used to iterate over data structures, such + as lists, streams, etc. + + Iterate over a {type List}: + + @signature{each} + + Iterate over a {type Stream}: + + @signature{lazily} + + Iterate over a {type Stream} that may throw exceptions: + + @signature{allowThrow} + + Iterate over a {type Text}: + + @signature{eachChar} + + Iterate over a range of numbers: + + @signature{range} + + Repeat the rest of the computation a number of times: + + @signature{Each.repeat} + + Iterate over the captures of a {type Pattern}: + + @signature{eachCapture} + + Get the value from an {type Optional}, or fail if it's {None}: + + @signature{optionally} + + Get the {Left} value from an {type Either}, or fail if it's {Right}: + + @signature{Each.left} + + Get the {Right} value from an {type Either}, or fail if it's {Left}: + + @signature{Each.right} + + Get the value from a computation that uses {type Abort}, or fail if it + aborts: + + @signature{fromAbort} + + Get the value from a computation that uses {type Exception}, or fail if it + raises an exception: + + @signature{fromException} + + Get the value from a computation that uses {type Throw}, or fail if it + throws: + + @signature{fromThrow} + + # Filtering and failure + + Fail the current branch unconditionally (i.e. produce no values): + + @signature{Each.fail} + + Fail if a {type Boolean} condition is not satisfied: + + @signature{guard} + + Fail if a predicate returns ``false``: + + @signature{Each.filter} + + Succeed if a given computation fails, and fail if it succeeds: + + @signature{Each.negate} + + # Extracting values + + Get a {type List} of all the results: + + @signature{toList} + + Get a {type Stream} of all the results: + + @signature{Each.toStream} + + Get the first result, if any: + + @signature{observe} + + Split a computation into its first result, if any, and the rest of the + computation: + + @signature{Each.split} + + Run a computation just for its effects, ignoring all results: + + @signature{Each.run} + + Count the number of elements emitted: + + @signature{Each.count} + + # Combining computations + + Append the results of one computation to another: + + @signature{Each.append} + + Interleave the results of one computation with another: + + @signature{Each.interleave} + + Interleave a computation with another that may depend on the outputs of the + first computation: + + @signature{interleaveMap} + + Proceed differently depending on whether one computation succeeds or fails: + + @signature{ifThenElse} + + # Pruning + + Stop a computation after it produces one value: + + @signature{once} + + Stop a computation after it produces a specific number of values: + + @signature{limit} + + # Logic programming + + The {type Each} ability can be used for logic programming in the style of + Prolog. + + Here is a worked example of a logic program that finds all people in a list + who are not ancestors of a given person: + + @source{notJimsAncestors'} + }} + +abilities.Each.each : [a] ->{Each} a +abilities.Each.each as = lazily (Stream.fromList as) + +abilities.Each.each.doc : Doc +abilities.Each.each.doc = + {{ + `` each xs `` produces each of the elements of `xs`. + + ``` + Each.toList do each [1, 2, 3, 4] + ``` + }} + +abilities.Each.eachCapture : Pattern t -> t ->{Each} t +abilities.Each.eachCapture p t = + (ts, _) = optionally (Pattern.run p t) + each ts + +abilities.Each.eachCapture.doc : Doc +abilities.Each.eachCapture.doc = + use Pattern capture + {{ + `` eachCapture p t `` produces each capture made by running the pattern `p` + on the input `t`. + + # Example + + ``` + Each.toList do + eachCapture + (sepMany + (some (patterns.char (Class.not Class.letter))) + (capture (some patterns.letter))) + "Hello123, this-is*an_example#of some_text!" + ``` + + # See also + + * {Pattern.run} + * {capture} + }} + +abilities.Each.eachChar : Text ->{Each} Char +abilities.Each.eachChar t = lazily do Text.toStream t + +abilities.Each.eachChar.doc : Doc +abilities.Each.eachChar.doc = + {{ + Produces each character of the given {type Text}. + + # Example + + ``` + Each.toList do + c = eachChar "abcde" + (c, Char.toNat c) + ``` + + # See also + + * {type Each} + * {each} + * {Each.fail} + }} + +abilities.Each.fail : '{Each} a +abilities.Each.fail _ = lazily do () + +abilities.Each.fail.doc : Doc +abilities.Each.fail.doc = + use Each fail + {{ + `` fail `` aborts the current branch of this computation. + + For example, in: + + ``` + Each.toList do + fail() + each (Nat.range 0 100000) + ``` + + the `` each (List.range 0 100000) `` will never actually be computed, because + the current branch is aborted by the call to ``fail()``. + + Also see {guard} if you want to conditionally abort the current branch. + }} + +abilities.Each.filter : (a ->{e} Boolean) -> a ->{e, Each} a +abilities.Each.filter p a = if p a then a else Each.fail() + +abilities.Each.filter.doc : Doc +abilities.Each.filter.doc = + {{ + Aborts the current branch of the computation if the given predicate returns + `` false `` for the given value. + + # Example + + ``` + Each.toList do Each.range 0 10 |> Each.filter Nat.isEven + ``` + }} + +abilities.Each.fromAbort : '{g, Abort} a ->{g, Each} a +abilities.Each.fromAbort c = + h = cases + { abort -> _ } -> lazily do () + { a } -> lazily do emit a + handle c() with h + +abilities.Each.fromAbort.doc : Doc +abilities.Each.fromAbort.doc = + use Bag counts + use Each toList + use Map getOrAbort + use Nat + + use Text toBag + {{ + Given a computation `x` that may use the {type Abort} ability, `` fromAbort x + `` returns the value of `x`, or fails if the computation `x` calls {abort}. + + # Examples + + ``` + m = toBag "abracadabra" |> counts + toList do + a = fromAbort do getOrAbort ?a m + b = fromAbort do getOrAbort ?b m + a + b + ``` + + ``` + m = toBag "abracadabra" |> counts + toList do + z = fromAbort do getOrAbort ?z m + c = fromAbort do getOrAbort ?c m + c + z + ``` + + # See also + + * {each} + * {Each.fail} + }} + +abilities.Each.fromException : '{g, Exception} a ->{g, Each} a +abilities.Each.fromException c = + h = cases + { Exception.raise _ -> _ } -> lazily do () + { a } -> lazily do emit a + handle c() with h + +abilities.Each.fromException.doc : Doc +abilities.Each.fromException.doc = + use Nat + + use data.Array read + {{ + Given a computation `x` that may throw an {type Exception}, `` + fromException x `` returns the value of `x`, or fails if the computation `x` + calls {Exception.raise}. + + # Example + + In this example, {fromException} suppresses exceptions from {read} when the + index is out of bounds: + + ``` + Scope.run do + Each.toList do + x = Array.fromList [1, 2, 3] + i = Each.range 0 10 + fromException do read x i + 1 + ``` + + # See also + + * {each} + * {Each.fail} + }} + +abilities.Each.fromThrow : '{g, Throw e} a ->{g, Each} a +abilities.Each.fromThrow c = + h = cases + { throw _ -> _ } -> lazily do () + { a } -> lazily do emit a + handle c() with h + +abilities.Each.fromThrow.doc : Doc +abilities.Each.fromThrow.doc = + use Bag counts + use Each toList + use Map getOrThrow + use Nat + + use Text toBag + {{ + Given a computation `x` that may {type Throw}, `` fromThrow x `` returns the + value of `x`, or fails if the computation `x` calls {throw}. + + # Examples + + ``` + m = toBag "abracadabra" |> counts + toList do + a = fromThrow do getOrThrow "no a" ?a m + b = fromThrow do getOrThrow "no b" ?b m + a + b + ``` + + ``` + m = toBag "abracadabra" |> counts + toList do + z = fromThrow do getOrThrow "no z" ?z m + c = fromThrow do getOrThrow "no c" ?c m + c + z + ``` + + # See also + + * {each} + * {Each.fail} + }} + +abilities.Each.guard : Boolean ->{Each} () +abilities.Each.guard = cases + true -> lazily do emit() + false -> lazily do () + +abilities.Each.guard.doc : Doc +abilities.Each.guard.doc = + use Each range + use Nat + == + {{ + `` guard b `` aborts the current branch of the computation if `b` is + ``false``. + + This is useful for adding filters to a list comprehension: + + ``` + Each.toList do + x = range 0 10 + y = range 0 10 + guard (x + x == y) + (x, y) + ``` + }} + +abilities.Each.ifThenElse : + '{g, Each} a -> (a ->{g, Each} b) -> '{g, Each} b ->{g, Each} b +abilities.Each.ifThenElse cond onValue onFail = match Each.split cond with + None -> onFail() + Some (a, rest) -> Each.append (do onValue a) do onValue rest() + +abilities.Each.ifThenElse.doc : Doc +abilities.Each.ifThenElse.doc = + use Nat range + {{ + `` ifThenElse c th el `` evaluates the computation `c`, and on each success + proceeds with `th`. If `c` fails, then it proceeds with `el`. + + # Example + + This example finds even integers in the first argument and multiplies them + by 100. If it finds none, it returns the odd integers in the second + argument. + + ``` + numbers xs ys = + use Nat * + first = do + x = each xs + guard (Nat.isEven x) + x + second = do + y = each ys + guard (Nat.isOdd y) + y + ifThenElse first ((*) 100) second + Each.toList do numbers (range 1 10) (range 21 30) + ``` + + # Motivation + + The normal `if/then/else` conditional, and the {guard} and {when} + functions, can be used within an {type Each} computation to restrict it to + only succeed if some condition is met. + + But we sometimes want to proceed one way on success and another way on + failure, as in the above example. + + The normal conditional is not adequate for this, as it doesn't allow us to + detect the case when a computation fails. + + In the example below, we want to find everyone who is __not__ a particular + person's ancestor. This is difficult to express with {guard}, {when}, or + the `if/then/else` control construct, but it's straightforward with the + {ifThenElse} function: + + @source{notJimsAncestors} + + Here {ifThenElse} checks if the `ancestorOf` computation succeeds for the + current person. If it does, then we fail the computation, which means that + the current person is not included in the results. If `ancestorOf` doesn't + succeed, then we return a success, which means the current person is + included in the results. + + # See also + + * {guard} for a simpler way to restrict a computation to only succeed when + some condition is met. + + * {Each.negate} uses {ifThenElse} internally to invert the success/failure + of a computation. + * {when} performs an action when a condition is `` true `` or succeeds + without performing that action when it's ``false``. + }} + +abilities.Each.ifThenElse.examples.jimsAncestors : [Text] +abilities.Each.ifThenElse.examples.jimsAncestors = + use Text == + relations = + [ ("John", "Jim") + , ("John", "Anna") + , ("Jim", "Bob") + , ("Jim", "Alice") + , ("Anna", "Eve") + , ("Anna", "Fred") + ] + people = ["John", "Anna", "Bob", "Alice", "Eve", "Fred"] + parents : Text ->{Each} Text + parents person = + (child, parent) = each relations + guard (child == person) + parent + ancestorOf : Text -> Text ->{Each} () + ancestorOf descendant ancestor = + parent = parents descendant + if parent == ancestor then () else ancestorOf parent ancestor + ancestors : Text ->{Each} Text + ancestors person = + p = each people + ancestorOf person p + p + Each.toList do ancestors "Jim" + +abilities.Each.ifThenElse.examples.notJimsAncestors : [Text] +abilities.Each.ifThenElse.examples.notJimsAncestors = + use Text == + relations = + [ ("John", "Jim") + , ("John", "Anna") + , ("Jim", "Bob") + , ("Jim", "Alice") + , ("Anna", "Eve") + , ("Anna", "Fred") + ] + people = ["John", "Anna", "Bob", "Alice", "Eve", "Fred"] + parents : Text ->{Each} Text + parents person = + (child, parent) = each relations + guard (child == person) + parent + ancestorOf : Text -> Text ->{Each} () + ancestorOf descendant ancestor = + parent = parents descendant + if parent == ancestor then () else ancestorOf parent ancestor + ancestors : Text ->{Each} Text + ancestors person = + p = each people + ancestorOf person p + p + notAncestorOf : Text -> Text ->{Each} () + notAncestorOf descendant ancestor = + ifThenElse (do ancestorOf descendant ancestor) Each.fail do () + notAncestors : Text ->{Each} Text + notAncestors person = + p = each people + notAncestorOf person p + p + Each.toList do notAncestors "Jim" + +abilities.Each.ifThenElse.examples.notJimsAncestors' : [Text] +abilities.Each.ifThenElse.examples.notJimsAncestors' = + use Text == + relations = + [ ("John", "Jim") + , ("John", "Anna") + , ("Jim", "Bob") + , ("Jim", "Alice") + , ("Anna", "Eve") + , ("Anna", "Fred") + ] + people = ["John", "Anna", "Bob", "Alice", "Eve", "Fred"] + parents : Text ->{Each} Text + parents person = + (child, parent) = each relations + guard (child == person) + parent + ancestorOf : Text -> Text ->{Each} () + ancestorOf descendant ancestor = + use Text != + parent = parents descendant + when (parent != ancestor) do ancestorOf parent ancestor + notAncestors : Text ->{Each} Text + notAncestors person = + p = each people + Each.negate do ancestorOf person p + p + Each.toList do notAncestors "Jim" + +abilities.Each.interleave : '{g, Each} a -> '{g, Each} a ->{g, Each} a +abilities.Each.interleave a b = match Each.split a with + None -> b() + Some (a, rest) -> Each.append (do a) do abilities.Each.interleave b rest + +abilities.Each.interleave.doc : Doc +abilities.Each.interleave.doc = + use Each interleave + {{ + Fair interleaving disjunction of two {type Each} computations. The + computation `` interleave x y `` produces the first result of `x`, then the + first result of `y`, then the second result of `x`, then the second result of + `y`, and so on. + + If either side fails, the computation proceeds with the other side. + + # Example + + ``` + Each.toList do interleave (do each [1, 2, 3]) do each [4, 5, 6, 7, 8] + ``` + + # See also + + * {Each.append} - concatenates two computations. + * {interleaveMap} - interleaves the results of applying a nondeterministic + function to all results of a computation. + }} + +abilities.Each.interleaveMap : (a ->{g, Each} b) -> '{g, Each} a ->{g, Each} b +abilities.Each.interleaveMap f m = + match Each.split m with + None -> Each.fail() + Some (a, rest) -> + Each.interleave (do f a) do abilities.Each.interleaveMap f rest + +abilities.Each.interleaveMap.doc : Doc +abilities.Each.interleaveMap.doc = + use Each append fail toList + use Nat + isEven + {{ + Fair interleaving conjunction of {type Each} computations. The computation `` + interleaveMap f xs `` produces the first result of `f x` for each `x` result + of `xs`, then the second result of `f x` for each result of `xs`, and so on. + + If `xs` fails, the computation fails. If `f x` fails for any result `x` from + `xs`, the computation proceeds with the other results. + + Thus, this works like a fair equivalent of {List.flatMap}. + + # Examples + + In this example, the inner calls to {interleaveMap} are interleaved by the + outer call. + + ``` + toList do + (do each [1, 2, 3]) + |> interleaveMap + (x -> (do each [?a, ?b]) |> interleaveMap (y -> (x, y))) + ``` + + Compare with the following normal conjunction, where each result of `y` is + considered before backtracking to the next result of `x`: + + ``` + toList do + x = each [1, 2, 3] + y = each [?a, ?b] + (x, y) + ``` + + The unfairness of such conjunction can lead to a computation diverging, as + in this example: + + @typecheck ``` + odds : '{Each} Nat + odds = do + append (do 1) do + a = odds() + a + 2 + observe do + n = each [0, 1] + x = odds() + n + if isEven x then x else fail() + ``` + + This diverges, even though there's an infinite number of answers that + satisfy the condition. The problem is that `!odds + 1` never gets a chance + to produce any results since `!odds + 0` produces infinite answers that + fail the condition. + + We can fix this with fair conjunction via {interleaveMap}: + + ``` + odds : '{Each} Nat + odds = do + append (do 1) do + a = odds() + a + 2 + observe do + ns = do each [0, 1] + x = interleaveMap (n -> odds() + n) ns + if isEven x then x else fail() + ``` + + # See also + + * {Each.interleave} - fair disjunction of two computations + * {append} - unfair disjunction + }} + +abilities.Each.lazily.doc : Doc +abilities.Each.lazily.doc = + {{ + `` lazily s `` generates the values from the {type Stream} `s`. + + ``` + Each.toList do lazily (Stream.fromList ["a", "b", "c"]) + ``` + }} + +abilities.Each.left : Either a b ->{Each} a +abilities.Each.left = cases + Left a -> lazily do emit a + Right _ -> Each.fail() + +abilities.Each.left.doc : Doc +abilities.Each.left.doc = + use Each left toList + use Nat + + {{ + Given an {type Either} value `x`, `` left x `` returns the value from `x` if + it is {Left}, or fails if the value is {Right}. + + # Examples + + ``` + toList do + x = left (Left 1) + y = left (Right 2) + x + y + ``` + + ``` + toList do + x = left (Left 1) + y = left (Left 2) + x + y + ``` + + # See also + + * {Each.right} + * {each} + * {Each.fail} + }} + +abilities.Each.limit : Nat -> '{g, Each} a ->{g, Each} a +abilities.Each.limit n a = + use Each fail + use Nat - > + go n a = match Each.split a with + None -> fail() + Some (a, rest) | n > 0 -> Each.append (do a) do go (n - 1) rest + Some (a, _) -> fail() + go n a + +abilities.Each.limit.doc : Doc +abilities.Each.limit.doc = + {{ + `` limit n c `` selects `n` results from the computation `c`, pruning all + other branches. + + # Example + + ``` + Each.toList do limit 3 do each [1, 2, 3, 4, 5] + ``` + + # See also + + * {once} - to get just one result. + }} + +abilities.Each.negate : '{g, Each} a ->{g, Each} () +abilities.Each.negate c = ifThenElse (do once c) (do Each.fail()) do () + +abilities.Each.negate.doc : Doc +abilities.Each.negate.doc = + use Each negate + use Nat + == > + {{ + Generalized negation for the {type Each} ability, implementing "negation as + failure". The computation `` negate c `` succeeds by producing `` () `` when + `c` fails (i.e. produces no results) and fails if `c` produces any results. + + # Example + + This example finds odd prime numbers, by eliminating numbers that are + divisible by any other number: + + ``` + odds : '{Each} Nat + odds = do + Each.append (do 1) do + a = odds() + a + 2 + Each.toList do + limit 10 do + n = odds() + guard (n > 1) + negate do + d = Each.range 2 n + guard (Nat.mod n d == 0) + n + ``` + + # See also + + * {ifThenElse} - a more general function for handling success and failure + of a computation. + }} + +abilities.Each.observe : '{g, Each} a ->{g} Optional a +abilities.Each.observe c = + h = cases + { emit a -> _ } -> Some a + { _ } -> None + handle Each.toStream c () with h + +abilities.Each.observe.doc : Doc +abilities.Each.observe.doc = + {{ + Observes the first result of a {type Each} computation, returning {None} if + the computation fails. + + # Examples + + ``` + observe do each [1, 2, 3] + ``` + + ``` + observe do each [] + ``` + + # See also + + * {once} - Limits an {type Each} computation to its first result. + }} + +abilities.Each.once : '{g, Each} a ->{g, Each} a +abilities.Each.once a = match Each.split a with + None -> Each.fail() + Some (a, rest) -> each [a] + +abilities.Each.once.doc : Doc +abilities.Each.once.doc = + use Each toList + use List +: + use Nat <= + {{ + Selects a single result from the given computation, pruning all other + branches. + + # Examples + + {once} can be used to get the first result of a computation: + + ``` + toList do once do each [1, 2, 3] + ``` + + A more involved example is the following, which sorts a list by generating + all permutations and testing them: + + ``` + insert : a -> [a] ->{Each} [a] + insert e = cases + [] -> [e] + l@(h +: t) -> + Each.append (do e +: l) do + t' = insert e t + h +: t' + permutations : [a] ->{Each} [a] + permutations = cases + [] -> [] + h +: t -> + tt = permutations t + insert h tt + sorted = cases + e1 +: (e2 +: r) -> e1 <= e2 && sorted (e2 +: r) + _ -> true + bogosort xs = once do + p = permutations xs + if sorted p then p else Each.fail() + toList do bogosort [5, 0, 3, 4, 0, 1] + ``` + + Since the list to be sorted may have duplicates, there may be several + permutations of the list that are all sorted. But since the difference + between them is not observable, we use {once} to stop as soon as we see one + sorted permutation. + + # See also + + * {limit} - to get the first `n` results instead of just the first one. + * {observe} - to run a computation until it produces one result. + }} + +abilities.Each.optionally : Optional a ->{Each} a +abilities.Each.optionally = cases + None -> Each.fail() + Some a -> lazily do emit a + +abilities.Each.optionally.doc : Doc +abilities.Each.optionally.doc = + use Nat + + {{ + Given an {type Optional} value `x`, `` optionally x `` returns the value from + `x` if it is {Some}, or fails if the value is {None}. + + `` optionally x `` is equivalent to ``each (Optional.toList x)``. + + # Examples + + ``` + Each.toList do + x = optionally (Some 1) + y = optionally (Some 2) + x + y + ``` + + ``` + Each.toList do + x = optionally (Some 1) + y = optionally None + x + y + ``` + + # See also + + * {each} + * {Each.fail} + }} + +abilities.Each.range : Nat -> Nat ->{Each} Nat +abilities.Each.range start stopExclusive = + lazily (Stream.range start stopExclusive) + +abilities.Each.range.doc : Doc +abilities.Each.range.doc = + use Each range toList + {{ + `` range i j `` generates the values from `i` up to but not including `j`. + + ``` + toList do range 2 8 + ``` + + ``` + toList do range 200 0 + ``` + }} + +abilities.Each.rangeClosed : Nat -> Nat ->{Each} Nat +abilities.Each.rangeClosed start stopInclusive = + lazily (Stream.rangeClosed start stopInclusive) + +abilities.Each.rangeClosed.doc : Doc +abilities.Each.rangeClosed.doc = + use Each rangeClosed + {{ + `` rangeClosed start end `` generates the values from `start` to `end` + inclusive, using the {type Each} ability. + + # Examples + + ``` + Each.toList do rangeClosed 1 5 + ``` + }} + +abilities.Each.repeat : Nat ->{Each} () +abilities.Each.repeat n = + lazily (Stream.range 0 n) + () + +abilities.Each.repeat.doc : Doc +abilities.Each.repeat.doc = + use Each repeat + {{ + `` repeat n `` repeats `` () `` `n` times. It can be used to + + ``` + Each.toList do + repeat 4 + "hi" + ``` + }} + +abilities.Each.repeatForever : '{Each} () +abilities.Each.repeatForever = do lazily do forever do emit() + +abilities.Each.repeatForever.doc : Doc +abilities.Each.repeatForever.doc = + {{ + Repeats the continuation of the program forever. This is useful for + generating infinite sequences of values with {type Each}. Use with {limit} to + generate a finite number of values from an infinite sequence. + + # Example + + We can generate an infinite sequence of random numbers and then take the + first 10 values: + + ``` + splitmix 1 do + Each.toList do + limit 10 do + repeatForever() + Random.nat! + ``` + }} + +abilities.Each.right : Either a b ->{Each} b +abilities.Each.right = cases + Left _ -> Each.fail() + Right b -> lazily do emit b + +abilities.Each.right.doc : Doc +abilities.Each.right.doc = + use Each right toList + use Nat + + {{ + Given an {type Either} value `x`, `` right x `` returns the value from `x` if + it is {Right}, or fails if the value is {Left}. + + # Examples + + ``` + toList do + x = right (Right 1) + y = right (Left "oops") + x + y + ``` + + ``` + toList do + x = right (Right 1) + y = right (Right 2) + x + y + ``` + + # See also + + * {Each.left} + * {each} + * {Each.fail} + }} + +abilities.Each.run : '{g, Each} a ->{g} () +abilities.Each.run g = + handle g() + with cases + { a } -> () + { lazily s -> k } -> + Stream.foreach ignore (Stream.flatMap (a -> toStream! do k a) s) + +abilities.Each.run.doc : Doc +abilities.Each.run.doc = + use Each run + use Nat + + use Store get + {{ + `` run e `` ignores the elements of the {type Each} `e` and executes it just + for its effects. + + # Example + + ``` + withInitialValue 0 do + run do + x = each [1, 2, 3] + Store.put (get + x) + get + ``` + }} + +abilities.Each.split : '{g, Each} a ->{g} Optional (a, '{g, Each} a) +abilities.Each.split c = + use Stream uncons + h : + '{Stream ('{Each, g} a)} () + -> Request {Each} a + ->{g} Optional (a, '{g, Each} a) + h jq = cases + { a } -> + match uncons jq with + Left _ -> Some (a, Each.fail) + _ -> + Some + (a, do + x = lazily jq + x()) + { lazily s -> k } -> + match (uncons s, uncons jq) with + (Left _, Left _) -> None + (Left _, Right (j, jq)) -> handle j() with h jq + (Right (v, vs), _) -> + conts = Stream.map (x -> do k x) vs + handle k v + with + h do + conts() + jq() + handle c() with h do () + +abilities.Each.split.doc : Doc +abilities.Each.split.doc = + use Each split + use Int > + {{ + Probes a non-deterministic computation that uses the {type Each} ability, + returning the first result and a computation that generates the rest of the + results, or {None} if there are no results. + + # Examples + + In this code, `numbers` is a non-deterministic computation that generates + some integers. We use the {split} function to find the first positive + number, or return `` +0 `` if there isn't one: + + ``` + numbers : '{Each} Int + numbers = do each [-1, +0, +1, +2, +3, -2] + firstPositive : '{Each} Int -> Int + firstPositive numbers = match split numbers with + None -> +0 + Some (x, moreNumbers) -> if x > +0 then x else firstPositive moreNumbers + firstPositive numbers + ``` + + In this example, we use {split} to interleave the results of one + computation with another: + + @source{Each.interleave} + }} + +abilities.Each.toList : '{g, Each} a ->{g} [a] +abilities.Each.toList a = Stream.toList do toStream! a + +abilities.Each.toList.doc : Doc +abilities.Each.toList.doc = + {{ + `` Each.toList e `` converts `e` back to a {type List}. + + Also see {Each.toStream} and {toStream!}. + }} + +abilities.Each.toOptional : '{g, Each} a ->{g} Optional a +abilities.Each.toOptional e = + Optional.map at1 (Either.toOptional (Stream.uncons (Each.toStream e))) + +abilities.Each.toOptional.doc : Doc +abilities.Each.toOptional.doc = + use Each toOptional + {{ + Return the first value yielded by an {type Each} computation, if any, or + {None} if the computation yields no values. + + # Examples + + ``` + toOptional do each [1, 2, 3] + ``` + + ``` + toOptional do each [] + ``` + }} + +abilities.Each.toStream : '{g, Each} a -> '{g, Stream a} () +abilities.Each.toStream e _ = toStream! e + +abilities.Each.toStream.doc : Doc +abilities.Each.toStream.doc = + use Each toStream + {{ + `` toStream e `` converts `e` to an unevaluated {type Stream}. + + ``` + (toStream do each [1, 2, 3]) |> Stream.toList + ``` + }} + +abilities.Each.toStream! : '{g, Each} a ->{g, Stream a} () +abilities.Each.toStream! a = + handle a() + with cases + { a } -> emit a + { lazily s -> k } -> flatMap! (a -> abilities.Each.toStream! do k a) s + +abilities.Each.toStream!.doc : Doc +abilities.Each.toStream!.doc = + {{ + `` toStream! e `` converts `e` to a forced {type Stream}. + + ``` + Stream.toList do toStream! do each [1, 2, 3] + ``` + }} + +abilities.Exception.abortOnException : '{g, Exception} a ->{g, Abort} a +abilities.Exception.abortOnException action = match catch action with + Left e -> abort + Right a -> a + +abilities.Exception.abortOnException.doc : Doc +abilities.Exception.abortOnException.doc = + {{ + Runs an action, and if it throws an exception, calls {abort}. + + # Examples + + ``` + toOptional! do abortOnException do Exception.raise (failure "oops" ()) + ``` + + ``` + toOptional! do abortOnException do 1 + ``` + + # See also + + * {catch} for a version of this that returns the {type Failure} instead of + aborting. + }} + +abilities.Exception.bracket : + '{g, Exception} r + -> (r ->{g, Exception} ()) + -> (r ->{g, Exception} a) + ->{g, Exception} a +abilities.Exception.bracket provision final run = + r = provision() + finally (do final r) do run r + +abilities.Exception.bracket.doc : Doc +abilities.Exception.bracket.doc = + use Exception raise + use Nat + + {{ + {bracket} is used to provision a resource, perform some action on it, and + release it, even if the action throws an {type Exception}. + + `` bracket make onComplete op `` + + The first argument, `make` is used for provisioning the resource. If `make` + is successful, the argument `op` is executed. Finally, `onComplete`, is + executed. + + `onComplete` will be executed even if an exception is raised when evaluating + `op`. + + {{ + docCallout + (Some {{ 🚨 }}) + {{ + It is possible that `onComplete` never runs, if `op` or `make` use an + ability that can terminate the computation, such as {type Abort} or + {type Throw}. Using {bracket} with such computations may lead to resource + leaks. + + It's best to convert those abilities to {type Exception} before calling + {bracket}, for example with {Abort.toException} and {Throw.toException}. + }} }} + + # Examples + + Some use cases for {bracket} include opening and closing a {type Socket} + for receiving data, or opening and closing a file to read from it without + leaving file handles open. + + In the example below `onComplete` will be run, even after the function + using the resource raises an exception. The result of {bracket} is the + raised exception. + + ``` + catch do + mockRead : Text ->{Exception} Text + mockRead n = + if n === "BadInput" then raise (failure "Oh No" ()) else "Success" + mockCloseFile : Text ->{Exception} () + mockCloseFile n = () + mockOpenFile : '{Exception} Text + mockOpenFile = do "BadInput" + bracket mockOpenFile mockCloseFile mockRead + ``` + + If making the resource fails, the error will be immediately returned + + ``` + catch do + op : '{Exception} Nat + op = do raise (failure "Exception in body" ()) + onComplete : '{Exception} () + onComplete = do () + make : '{Exception} () + make = do raise (failure "Trouble making the resource" ()) + bracket make onComplete op + ``` + + If the desired operation to run succeeds, the result of it will be returned + and the `onComplete` block will be executed. + + ``` + catch do + op : Nat ->{Exception} Nat + op n = if n === 0 then raise (failure "Oh No" ()) else n + 1 + onComplete : Nat ->{Exception} () + onComplete n = () + make : '{Exception} Nat + make = do 5 + bracket make onComplete op + ``` + }} + +abilities.Exception.catch : '{g, Exception} a ->{g} Either Failure a +abilities.Exception.catch ex = + handle ex() + with cases + { a } -> Right a + { Exception.raise f -> _ } -> Left f + +abilities.Exception.catch.doc : Doc +abilities.Exception.catch.doc = + {{ + {catch} tries to run an operation which potentially raises an exception. It + catches all exceptions, returning an {type Either} representing the failure + or success of executing the {type Exception} ability. + + # Examples + + ``` + catch do Exception.raise (failure "number too big!" 42) + ``` + + ``` + catch do "No exception raised" + ``` + }} + +abilities.Exception.catchMany : + [Type] -> '{g, Exception} a ->{g, Exception} Either Failure a +abilities.Exception.catchMany ls ex = + match catch ex with + Left f@(Failure t _ _)| Boolean.not (List.contains t ls) -> + Exception.raise f + r -> r + +abilities.Exception.catchMany.doc : Doc +abilities.Exception.catchMany.doc = + use Exception raise + {{ + Tries to run code which potentially raises an {type Exception}, transforming + it to an {type Either). If an exception is raised and contained in the target + list of types, {catchMany} returns a {Left} otherwise it reraises the error. + + # Examples + + {catchMany} wraps an exception found in the target list of exceptions in a + {Left}. + + ``` + unsafeRun! do + catchMany [typeLink TlsFailure] do + raise (Failure (typeLink TlsFailure) "Oh no" (Any ())) + ``` + + {catchMany} should reraise the exception when it is not contained in the + supplied list. We re-catch the raised exception using {catch} for + rendering. + + ``` + catch do + opFailure : '{Exception} a + opFailure = do raise (Failure (typeLink Generic) "Oh no" (Any ())) + catchMany [typeLink TlsFailure, typeLink IOFailure] opFailure + ``` + }} + +abilities.Exception.catchOnly : + Type -> '{g, Exception} a ->{g, Exception} Either Failure a +abilities.Exception.catchOnly tpe ex = match catch ex with + Left f@(Failure t _ _) | Boolean.not (t === tpe) -> Exception.raise f + r -> r + +abilities.Exception.catchOnly.doc : Doc +abilities.Exception.catchOnly.doc = + use Exception raise + {{ + Tries to run code which potentially raises an {type Exception}, transforming + it to an {type Either}. If an exception is raised, {catchOnly} catches only + the given failure type {Type} as a {Left}, otherwise it re-raises the + exception. + + # Examples + + {catchOnly} should wrap the type {type TlsFailure} in a {Left} when an + {type Exception} of that type is raised. + + ``` + unsafeRun! do + catchOnly (typeLink TlsFailure) do + raise (Failure (typeLink TlsFailure) "Oh no" (Any ())) + ``` + + Here's an example with no type match below. We're re-catching the raised + exception using {catch} for rendering. + + ``` + catch do + catchOnly (typeLink Generic) do + raise + (Failure + (typeLink TlsFailure) "Exception type did not match" (Any ())) + ``` + }} + +abilities.Exception.doc : Doc +abilities.Exception.doc = + use doc div + {{ + {type Exception} is an ability used to represent when an exceptional state + has occurred. Its sole request constructor, {Exception.raise}, expects a + value of type {type Failure} to capture information about the error. + + {catch} is a handler which translates the {type Exception} into a value of + type {type Either}. + + @source{div} + + ``` + catch do div 1 0 + ``` + + # Common functions + + @signatures{reraise, bracket, finally, catchOnly, unsafeRun!} + + # See also + + * [Failure]({type Failure}) + * [Generic.failure]({failure}) + * [Either.toException]({reraise}) + }} + +abilities.Exception.doc.div : Nat -> Nat ->{Exception} Nat +abilities.Exception.doc.div a b = + use Nat / == + if b == 0 then Exception.raise (failure "cannot divide by zero" b) else a / b + +abilities.Exception.failOnError : Text -> Either e a ->{Exception} a +abilities.Exception.failOnError msg = cases + Left e -> Exception.raise (failure msg e) + Right a -> a + +abilities.Exception.failOnError.doc : Doc +abilities.Exception.failOnError.doc = + {{ + Fails with a {type Generic} {type Exception} containing the given {type Text} + message if the given {type Either} is a {Left} value. + + # Example + + ``` + catch do failOnError "oops" (Left "something went wrong") + ``` + + ``` + catch do failOnError "oops" (Right 1) + ``` + }} + +abilities.Exception.finally : + '{g, Exception} () -> '{g, Exception} a ->{g, Exception} a +abilities.Exception.finally end ex = + r = catch ex + end() + reraise r + +abilities.Exception.finally.doc : Doc +abilities.Exception.finally.doc = + use Nat + + {{ + `` finally onComplete op `` calls a computation, `op`, which may raise an + {type Exception}. After `op` is executed, `onComplete` is run, even if an + {type Exception} is raised when evaluating `op`. + + {{ + docCallout + (Some {{ 🚨 }}) + {{ + It is possible that `onComplete` never runs, if `op` makes use of an + ability that can terminate the computation, such as {type Abort} or + {type Throw}. Using {finally} with such computations may lead to resource + leaks. + + It's best to convert those abilities to {type Exception} before calling + {finally}, for example with {Abort.toException} and {Throw.toException}. + }} }} + + # Examples + + An exeption is generated during the call to `op` + + ``` + catch do + op : Nat -> '{Exception} Nat + op n = + if n === 0 then + do Exception.raise (failure "Exception during operation" ()) + else do n + 1 + onComplete = do () + finally onComplete (op 0) + ``` + + When no exception is raised: + + ``` + onComplete = do () + unsafeRun! do finally onComplete do "NoExceptionRaised" + ``` + }} + +abilities.Exception.Generic.doc : Doc +abilities.Exception.Generic.doc = + use Exception raise + {{ + A generic {type Failure} type that can be used to raise an {type Exception} + with a custom message. + + # Example + + ``` + catch do + raise + (Failure + (typeLink Generic) + "Something went wrong" + (Any "This is what went wrong")) + ``` + + ``` + catch do raise (failure "Something went wrong" ()) + ``` + }} + +abilities.Exception.Generic.failure : Text -> a -> Failure +abilities.Exception.Generic.failure msg a = + Failure (typeLink Generic) msg (Any a) + +abilities.Exception.Generic.failure.doc : Doc +abilities.Exception.Generic.failure.doc = + {{ + Creates a {type Generic} {type Failure} with the given {type Text} message + and payload. + + # Example + + ``` + catch do Exception.raise (failure "oops" "payload") + ``` + }} + +abilities.Exception.hush : '{g, Exception} t ->{g} Optional t +abilities.Exception.hush e = match catch e with + Left _ -> None + Right a -> Some a + +abilities.Exception.hush.doc : Doc +abilities.Exception.hush.doc = + {{ + Returns {None} if the given {type Exception} computation raises a { type + Failure } and {Some} of the result otherwise. + + # Examples + + ``` + hush do ArrayFailure.raise "oops" () + ``` + + ``` + hush do "hello" + ``` + }} + +abilities.Exception.onException : + (Failure ->{e} ()) -> '{g, Exception} a ->{e, g, Exception} a +abilities.Exception.onException f g = match catch g with + Left e -> + f e + Exception.raise e + Right x -> x + +abilities.Exception.onException.doc : Doc +abilities.Exception.onException.doc = + use Store put + use Text ++ + {{ + `` onException handler p `` runs `p`, and calls `handler` if an exception is + raised. The {type Failure} is passed to `handler`, and the exception is + reraised afterwards. + + # Example + + This example uses the {type Store} ability. On exception, it appends a + value to the {type Store} and reraises the exception: + + ``` + withInitialValue "" do + e = catch do + onException (_ -> Store.modify (x -> x ++ "💔")) do + put "💚" + Exception.raise (failure "❗️" "🪦") + put "💛" + (e, Store.get) + ``` + }} + +abilities.Exception.orElse : '{f} a -> '{g, Exception} a ->{f, g} a +abilities.Exception.orElse f g = match catch g with + Left _ -> f() + Right r -> r + +abilities.Exception.orElse.doc : Doc +abilities.Exception.orElse.doc = + {{ + If the second argument raises an exception when forced, this forces the first + argument and returns its result. Otherwise, returns the result of the second + argument. + + # Example + + ``` + Exception.orElse (do 42) do raiseGeneric "Not found" () + ``` + + # See also + + * {catch} - to catch an exception. + }} + +abilities.Exception.raise.doc : Doc +abilities.Exception.raise.doc = + {{ + Raises an {type Exception} with the given {type Failure}. + + # Example + + ``` + catch do Exception.raise (failure "oops" ()) + ``` + }} + +abilities.Exception.raiseFailure : Type -> Text -> a ->{Exception} x +abilities.Exception.raiseFailure typ msg payload = + Exception.raise (Failure typ msg (Any payload)) + +abilities.Exception.raiseFailure.doc : Doc +abilities.Exception.raiseFailure.doc = + use Nat / == + {{ + Raises a {type Failure} with the given type, message, and payload, in the + {type Exception} ability. + + # Example + + ``` + safeDiv x y = + if y == 0 then + Exception.raiseFailure + (typeLink ArithmeticException) "Divide by zero" (x, y) + else x / y + catch do safeDiv 1 0 + ``` + }} + +abilities.Exception.raiseGeneric : Text -> a ->{Exception} b +abilities.Exception.raiseGeneric msg a = + Exception.raiseFailure (typeLink Generic) msg a + +abilities.Exception.raiseGeneric.doc : Doc +abilities.Exception.raiseGeneric.doc = + {{ + Raises a {type Generic} exception with the given message and value. + + # Example + + ``` + catch do raiseGeneric "Not found" () + ``` + }} + +abilities.Exception.reraise.doc : Doc +abilities.Exception.reraise.doc = + {{ + Converts `` Left e `` to ``Exception.raise e``. + + # Examples + + ``` + e = Left (failure "msg" 0) + (catch do reraise e) === e + ``` + + ``` + r = Right "ok" + (catch do reraise r) === r + ``` + }} + +abilities.Exception.unsafeRun! : '{g, Exception} a ->{g} a +abilities.Exception.unsafeRun! e = + h : Request {Exception} a -> a + h = cases + { Exception.raise fail -> _ } -> bug fail + { a } -> a + handle e() with h + +abilities.Exception.unsafeRun!.doc : Doc +abilities.Exception.unsafeRun!.doc = + {{ + {unsafeRun!} is a handler which, if given an argument which raises an + exception, will stop the program with a call to the {bug} builtin. + + It is an unsafe computation, meaning it will stop the runtime of your program + if an exception is raised, but it can be useful in circumstances where + referential transparency is not material. + + # Examples + + The following example will call the {bug} builtin: + + ``` + unsafeRun! do Exception.raise (failure "A failure occurred" ()) + ``` + + If no exception is raised, the result of evaluating the expression is + returned. + + ``` + maybeRaiseException : '{Exception} Text + maybeRaiseException _ = "No Exception raised in the body of this function" + unsafeRun! maybeRaiseException + ``` + + # See also + + {catch} is another {type Exception} ability handler which transforms the + raised exception into an {type Either}. + }} + +abilities.force : '{e} a ->{e} a +abilities.force op = op() + +abilities.force.doc : Doc +abilities.force.doc = + {{ + `` force op `` forces a delayed computation `op``; it is equivalent to `!op`, + but {force} is a first-class value (unlike `!`). + }} + +abilities.forever : '{e} a ->{e} b +abilities.forever op = + ignore op() + abilities.forever op + +abilities.forever.doc : Doc +abilities.forever.doc = + {{ + `` forever op `` performs an effectful computation `op` repeatedly forever, + never returning. + }} + +abilities.forever' : '{g} a -> '{g} b +abilities.forever' a = do forever a + +abilities.forever'.doc : Doc +abilities.forever'.doc = + {{ + `` forever' op `` returns a delayed computation that when forced will run + `!op` repeatedly, forever. + }} + +abilities.Label.doc : Doc +abilities.Label.doc = + use Nat + + use Random natIn + {{ + Provides the ability to add labels to a test to help identify the values or + conditions that caused the test to fail. + + # Label scopes + + Labels can be nested within scopes. The scope of a label is the innermost + scope that contains it. When a label is added, it is added to the current + scope. + + To create a new scope, use {labeled} with a {type Text} argument. + + @signature{labeled} + + The label is added to the current scope, and any labels added within the + scope are added to the same scope. When the scope ends, the labels are + removed. For example: + + @typecheck ``` + test.verify do + labeled "This will randomly fail" do + x = natIn 0 10 + y = natIn 0 100 + label "x" x + label "y" y + ensureEqual (x + y) 100 + ``` + + In this example, the labels "x" and "y" are added to the scope "This will + randomly fail". If the test fails, the error message will include the + labels and their values, and the error message that caused the test to + fail, along with the scope in which the test failed: + + ``` raw + 🚫 FAILED + This will randomly fail: + x: 3 + y: 67 + elements not equal + (70, 100) + ``` + + # Labeled values + + The {label} function takes a {type Text} and a value of any type. The + {type Text} is the label, and the value is the information to be associated + with the label. + + @signature{label} + + The test failure report will only ever show the last value associated with + a label in a given scope (including the top-level, unnamed scope). This + avoids cluttering the report with information irrelevant to the failure. + }} + +abilities.Label.formatLabels : [(Text, Map Text Text)] -> Text +abilities.Label.formatLabels labels = + use Text ++ == + go ind acc = cases + rest :+ (label, m) -> + go + (ind ++ " ") + (Map.foldLeftWithKey + (a k v -> a ++ ind ++ " " ++ k ++ ": " ++ v ++ "\n") + (acc ++ ind ++ label ++ (if label == "" then "" else ":\n")) + m) + rest + [] -> acc + go "" "" labels + +abilities.Label.formatLabels.doc : Doc +abilities.Label.formatLabels.doc = + {{ + Formats a list of labels and their values as {type Text}. The labels are + indented according to their scope. + + # Example + + ``` + formatLabels [("test", Map.fromList [("x", "10"), ("y", "20")])] + ``` + }} + +abilities.Label.getLabels : '{g, Label} a ->{g} [(Text, Map Text Text)] +abilities.Label.getLabels a = at1 (Label.run a) + +abilities.Label.getLabels.doc : Doc +abilities.Label.getLabels.doc = + {{ + Gets the labels from a {type Label} computation. The labels are returned as a + list of pairs of the scope names and the labels in each scope. + }} + +abilities.Label.label.doc : Doc +abilities.Label.label.doc = + use Nat + + use Random natIn + {{ + Provides the ability to add labels to a test to help identify the values or + conditions that caused the test to fail. + + # Label scopes + + Labels can be nested within scopes. The scope of a label is the innermost + scope that contains it. When a label is added, it is added to the current + scope. + + To create a new scope, use {labeled} with a {type Text} argument. + + @signature{labeled} + + The label is added to the current scope, and any labels added within the + scope are added to the same scope. When the scope ends, the labels are + removed. For example: + + @typecheck ``` + verifyAndIgnore do + labeled "This will randomly fail" do + x = natIn 0 10 + y = natIn 0 100 + label "x" x + label "y" y + ensureEqual (x + y) 100 + ``` + + In this example, the labels "x" and "y" are added to the scope "This will + randomly fail". If the test fails, the error message will include the + labels and their values, and the error message that caused the test to + fail, along with the scope in which the test failed: + + ``` raw + 🚫 FAILED + This will randomly fail: + x: 3 + y: 67 + elements not equal + (70, 100) + ``` + + # Labeled values + + The {label} function takes a {type Text} and a value of any type. The + {type Text} is the label, and the value is the information to be associated + with the label. + + @signature{label} + + The test failure report will only ever show the last value associated with + a label in a given scope (including the top-level, unnamed scope). This + avoids cluttering the report with information irrelevant to the failure. + }} + +abilities.Label.labeled : Text -> '{g} a ->{g, Label} a +abilities.Label.labeled l a = + pushScope l + x = a() + popScope + x + +abilities.Label.labeled.doc : Doc +abilities.Label.labeled.doc = + use Nat + + use Random natIn + {{ + Wraps a computation in a new {type Label} scope with the given name. The + labels added within the scope are added to the same scope. When the scope + ends, the labels are removed. + + @typecheck ``` + test.verify do + labeled "This will randomly fail" do + x = natIn 0 10 + y = natIn 0 100 + label "x" x + label "y" y + ensureEqual (x + y) 100 + ``` + + In this example, the labels "x" and "y" are added to the scope "This will + randomly fail". If the test fails, the error message will include the labels + and their values, and the error message that caused the test to fail, along + with the scope in which the test failed: + + ``` raw + 🚫 FAILED + This will randomly fail: + x: 3 + y: 67 + elements not equal + (70, 100) + ``` + }} + +abilities.Label.run : '{g, Label} r ->{g} ([(Text, Map Text Text)], r) +abilities.Label.run x = + use List +: + use Map empty + h = cases + (scope, labels) +: scopes -> + cases + { label l v -> k } -> + f : Request Label r -> ([(Text, Map Text Text)], r) + f = h ((scope, Map.insert l (toDebugText v) labels) +: scopes) + handle k() with f + { pushScope l -> k } -> + handle k() with h ((l, empty) +: ((scope, labels) +: scopes)) + { popScope -> k } -> + match scopes with + [] -> handle k() with h [("", empty)] + s +: ss -> handle k() with h scopes + { x } -> ((scope, labels) +: scopes, x) + _ -> bug "Label.run: no scopes" + handle x() with h [("", empty)] + +abilities.Label.run.doc : Doc +abilities.Label.run.doc = + {{ + Runs a computation with labels. The labels are collected as the computation + runs, and the result is a pair of the collected labels and the result of the + computation. + }} + +abilities.Label.runToText : '{g, Label} a ->{g} (Text, a) +abilities.Label.runToText a = first formatLabels (Label.run a) + +abilities.Label.runToText.doc : Doc +abilities.Label.runToText.doc = + {{ + Runs a {type Label} computation and returns the labels as a formatted + {type Text} string along with the result of the computation. + }} + +abilities.Label.toText : '{g, Label} a ->{g} Text +abilities.Label.toText a = at1 (runToText a) + +abilities.Label.toText.doc : Doc +abilities.Label.toText.doc = + {{ + Runs a {type Label} computation and returns the labels as a formatted + {type Text} string, dropping the result of the computation. + }} + +abilities.Random.boolean : '{Random} Boolean +abilities.Random.boolean _ = Universal.gteq (Nat.popCount Random.nat()) 32 + +abilities.Random.boolean.doc : Doc +abilities.Random.boolean.doc = + use Random boolean + {{ + Generates a random {type Boolean}. + + # Examples + + ``` + lcg 3 boolean + ``` + + ``` + lcg 93 do boolean() || boolean() + ``` + + ``` + lcg 30 do List.replicate 7 boolean + ``` + }} + +abilities.Random.bytes.base32Hex : '{Random} Bytes +abilities.Random.bytes.base32Hex _ = Text.toUtf8 text.base32Hex() + +abilities.Random.bytes.base32Hex.doc : Doc +abilities.Random.bytes.base32Hex.doc = + {{ + Produces a byte string with the ASCII/UTF-8 bytes of a base 32 hex encoding + of another bytes value. + }} + +abilities.Random.bytes.doc : Doc +abilities.Random.bytes.doc = + use Random bytes + {{ + `` bytes n `` produces random {type Bytes} of length `n`. + + # Examples + + ``` + lcg 3290 do bytes 24 + ``` + + ``` + lcg 19 do bytes 0 + ``` + }} + +abilities.Random.char.ascii.control : '{Random} Char +abilities.Random.char.ascii.control = do interval ?\0 ? + +abilities.Random.char.ascii.control.doc : Doc +abilities.Random.char.ascii.control.doc = + {{ + A random character in the ASCII control character range. + + # Example + + ``` + splitmix 42 do Text.toUtf8 (ofChars ascii.control 4) + ``` + }} + +test> abilities.Random.char.ascii.control.tests = test.verify do + Each.run do + _ = Each.repeat 100 + isControl ascii.control() + +abilities.Random.char.ascii.lower : '{Random} Char +abilities.Random.char.ascii.lower = do interval ?a ?z + +abilities.Random.char.ascii.lower.doc : Doc +abilities.Random.char.ascii.lower.doc = + {{ + A random character in the ASCII lowercase character range, `a` to `z`. + + # Example + + ``` + splitmix 42 do ofChars ascii.lower 4 + ``` + }} + +test> abilities.Random.char.ascii.lower.tests = test.verify do + Each.run do + _ = Each.repeat 100 + isLower ascii.lower() + +abilities.Random.char.ascii.printable : '{Random} Char +abilities.Random.char.ascii.printable = do interval ?\s ?‰ + +abilities.Random.char.ascii.printable.doc : Doc +abilities.Random.char.ascii.printable.doc = + use ascii printable + {{ + A random character in the ASCII printable character range. + + # Examples + + ``` + printable |> splitmix 42 + ``` + + ``` + (do ofChars printable 4) |> splitmix 42 + ``` + }} + +test> abilities.Random.char.ascii.printable.tests = test.verify do + Each.run do + _ = Each.repeat 100 + isPrint ascii.printable() + +abilities.Random.char.ascii.upper : '{Random} Char +abilities.Random.char.ascii.upper = do interval ?A ?Z + +abilities.Random.char.ascii.upper.doc : Doc +abilities.Random.char.ascii.upper.doc = + {{ + A random character in the ASCII uppercase character range, `A` to `Z`. + + # Example + + ``` + splitmix 42 do ofChars ascii.upper 4 + ``` + }} + +test> abilities.Random.char.ascii.upper.tests = test.verify do + Each.run do + _ = Each.repeat 100 + isUpper ascii.upper() + +abilities.Random.char.base32Hex : '{Random} Char +abilities.Random.char.base32Hex _ = + Random.either (do interval ?0 ?9) do interval ?a ?v + +abilities.Random.char.base32Hex.doc : Doc +abilities.Random.char.base32Hex.doc = + use char base32Hex + {{ + Produces a valid base32 hex character. + + # Examples + + ``` + lcg 4096 base32Hex + ``` + + ``` + lcg 2517 base32Hex + ``` + }} + +abilities.Random.char.digit : '{Random} Char +abilities.Random.char.digit _ = interval ?0 ?9 + +abilities.Random.char.digit.doc : Doc +abilities.Random.char.digit.doc = + {{ Produces a random decimal digit character }} + +abilities.Random.char.hex : '{Random} Char +abilities.Random.char.hex _ = + Random.either (do interval ?0 ?9) do interval ?a ?f + +abilities.Random.char.hex.doc : Doc +abilities.Random.char.hex.doc = + {{ Produces a random hexadecimal digit character }} + +abilities.Random.char.interval : Char -> Char ->{Random} Char +abilities.Random.char.interval lo hi = + use Char toNat + use Nat + + fromNat.impl (Random.natIn (toNat lo) (toNat hi + 1)) + +abilities.Random.char.interval.doc : Doc +abilities.Random.char.interval.doc = + {{ + Produces a random character in the closed interval between two given + characters. + + # Examples + + ``` + lcg 4096 do interval ?a ?z + ``` + + ``` + lcg 2517 do interval ?a ?z + ``` + }} + +abilities.Random.char.unicode : '{Random} Char +abilities.Random.char.unicode = + range min maxInclusive = + use Nat - + maxExclusive = Nat.increment maxInclusive + (maxExclusive - min, do fromNat.impl (Random.natIn min maxExclusive)) + Random.weighted [range 0 55295, range 57344 65533, range 65536 1114111] + +abilities.Random.char.unicode.doc : Doc +abilities.Random.char.unicode.doc = + {{ + A random valid Unicode character. + + # Example + + ``` + splitmix 42 do ofChars unicode 8 + ``` + }} + +abilities.Random.doc : Doc +abilities.Random.doc = + use List replicate + use Nat * range + use Random boolean natIn run + {{ + An ability for generating pseudorandom values. + + # Sample usage + + ``` + splitmix 1 do replicate 5 boolean + ``` + + This example uses the {splitmix} algorithm, initialized from an arbitrarily + chosen seed value, ``1``. This provides random values to the {boolean} + generator, and {replicate} uses that generator 5 times to produce a list of + 5 random booleans. + + If you'd rather not select a seed and you're in a context where you're able + to use {type IO}, you can use {run} instead of {splitmix}. This will use + the system's secure random number generator to seed the {splitmix} + algorithm, and then use that to generate values: + + @typecheck ``` + run do replicate 5 boolean + ``` + + # Generating random values + + Generate random {type Bytes} of a given length: + + @signature{Random.bytes} + + Generate a random {type Boolean}: + + @signature{boolean} + + ## Random numbers + + Generate a random {type Nat} value in the range 0 to {maxNat}: + + @signature{Random.nat} + + Generate a random {type Int} value in the range {minInt} to {maxInt}: + + @signature{Random.int} + + Generate a random {type Nat} in a given range: + + @signature{natIn} + + Generate a random {type Int} in a given range: + + @signature{intIn} + + Generate a uniformly distributed {type Float} from `` 0.0 `` up to but + not including `` 1.0 `` : + + @signature{Random.float} + + ## Text and characters + + Generate a random [Base32Hex](https://en.wikipedia.org/wiki/Base32) + string: + + @signature{text.base32Hex} + + Generate a random [Base32Hex](https://en.wikipedia.org/wiki/Base32) + character: + + @signature{char.base32Hex} + + Generate a random decimal digit: + + @signature{char.digit} + + Generate a random hexadecimal digit: + + @signature{hex} + + Generate a random {type Char} in a given range: + + @signature{interval} + + # Combinators on generators + + Generate a random value from a list of possible values: + + @signature{Random.oneOf} + + Generate a {type List} of values of a given length from a generator: + + @signature{Random.listOf} + + Generate a value from one of two generators with equal probability: + + @signature{Random.either} + + Shuffle a {type List} of values: + + @signature{shuffle} + + # Splitting the generator + + Create a new supply of {type Random} values from an existing one: + + @signature{Random.split} + + This is useful for concurrent use: each thread can be given its own supply + of random numbers. + + # Handlers + + {splitmix} is a prototypical handler of this ability that uses the + [SplitMix](https://gee.cs.oswego.edu/dl/papers/oopsla14.pdf) algorithm to + generate pseudorandom values. It is seeded with a value provided by the + user. The generator is not cryptographically secure, but it's fast and has + good statistical properties. + + ## Examples + + {{ docEval (_ -> splitmix 853322303 do replicate 10 do natIn 0 100) }} + + {{ docEval (_ -> splitmix 1 do shuffle (range 1 11)) }} + + {run} is a handler that uses {type IO} to generate the initial seed + value, and then uses {splitmix} to generate values. This is useful when + you have access to the {type IO} ability and don't want to choose a seed + value yourself. + + {lcg} is a simpler handler of this ability that uses a linear + congruential generator to generate pseudorandom values. It is seeded + with a value provided by the user. The generator is not + cryptographically secure and prone to predictability in the output, but + it's very simple. + + ## Examples + + {{ docEval (_ -> lcg 853322303 do replicate 10 boolean) }} + + {{ docEval (_ -> lcg 1 do List.map (i -> natIn 0 i * 100) (range 1 11)) + }} + }} + +abilities.Random.either : '{Random} r -> '{Random} r ->{Random} r +abilities.Random.either l r = if Random.boolean() then l() else r() + +abilities.Random.either.doc : Doc +abilities.Random.either.doc = + use Random either + {{ + Randomly chooses between one of two other random generators. + + # Examples + + ``` + lcg 2048 do either (do 0) do 1 + ``` + + ``` + lcg 2517 do either (do 0) do 1 + ``` + }} + +abilities.Random.exponential : Float ->{Random} Float +abilities.Random.exponential lambda = + use Float - / + Float.negate (log (1.0 - Random.float())) / lambda + +abilities.Random.exponential.doc : Doc +abilities.Random.exponential.doc = + use Float == + use List replicate + {{ + `` exponential lambda `` generates an + [exponentially distributed](https://en.wikipedia.org/wiki/Exponential_distribution) + random {type Float} using `lambda` as the rate parameter. + + Most values drawn from the exponential distribution will be close to 0, + dropping off exponentially with distance from 0. The higher the "rate" + parameter, `lambda`, the quicker this dropoff. + + For instance, in ``exponential 0.5``, the mean value will be + ``1.0 / 0.5 == 2.0``: + + ``` + splitmix 1 do replicate 10 do exponential 0.5 + ``` + + In ``exponential 2.0``, the mean value will be ``1.0 Float./ 2.0``: + + ``` + splitmix 1 do replicate 10 do exponential 2.0 + ``` + }} + +abilities.Random.float : '{Random} Float +abilities.Random.float _ = + use Float * + Float.fromNat (Nat.shiftRight Random.nat! 11) * ulp 0.5 + +abilities.Random.float.doc : Doc +abilities.Random.float.doc = + {{ + Generate a uniformly distributed random {type Float} from 0 up to but not + including 1. + + ``` + splitmix 1 do List.replicate 10 Random.float + ``` + }} + +abilities.Random.functionOf : '{g, Random} b ->{Random} (∀ a. a ->{g} b) +abilities.Random.functionOf valueGen = + rngSeed = Random.nat! + input -> + let + inputSeed = + hash Blake2b_256 input |> decodeNat64be + |> getOrBug "Blake2b_256 returned fewer than 64 bits" + |> at1 + seed = Nat.xor rngSeed inputSeed + splitmix seed valueGen + +abilities.Random.functionOf.doc : Doc +abilities.Random.functionOf.doc = + use GUID new toBase16 + use Text ++ + {{ + `` functionOf valueGen `` generates a function that returns random but + deterministic values. The pool of possible return values is determined by + `valueGen`. + + The {type Random} effect occurs when the __function__ is generated as opposed + to when an input is applied to the function. Therefore the output of the + function is deterministic with respect a given input argument (see examples + below). + + # Examples + + ``` + (userGUID1, userGUID2) = splitmix 42 do (functionOf new, functionOf new) + [ "Grace GUID 1: " ++ toBase16 (userGUID1 "Grace") + , "Grace GUID 2: " ++ toBase16 (userGUID2 "Grace") + , "Grace GUID 1 (again): " ++ toBase16 (userGUID1 "Grace") + , "Ada GUID 1: " ++ toBase16 (userGUID1 "Ada") + , "Ada GUID 2: " ++ toBase16 (userGUID2 "Ada") + , "Alan GUID 1: " ++ toBase16 (userGUID1 "Alan") + , "Alan GUID 2: " ++ toBase16 (userGUID2 "Alan") + ] + ``` + }} + +abilities.Random.impl.bytesFromNats : + '{g, Random} Nat -> Nat ->{g, Random} Bytes +abilities.Random.impl.bytesFromNats nat n = + use Bytes ++ + go acc = + if Universal.gteq (Bytes.size acc) n then Bytes.take n acc + else go (acc ++ toBytesLittleEndian nat()) + go Bytes.empty + +abilities.Random.impl.bytesFromNats.doc : Doc +abilities.Random.impl.bytesFromNats.doc = + {{ + A default implementation of {Random.bytes} in terms of {Random.nat}. + + This is useful for generators that don't have a more direct way of producing + random bytes. See usage in {lcg.handler}. + }} + +abilities.Random.int : '{Random} Int +abilities.Random.int = do Nat.toInt Random.nat! + +abilities.Random.int.doc : Doc +abilities.Random.int.doc = + {{ + Generates a random {type Int}. + + # Example + + ``` + splitmix 0 Random.int + ``` + }} + +abilities.Random.intIn : Int -> Int ->{Random} Int +abilities.Random.intIn start stopExclusive = + use Int >= abs toRepresentation + use Nat mod + if Universal.lt start stopExclusive then + startPositive = start >= +0 + stopPositive = stopExclusive >= +0 + range = + if startPositive then toRepresentation (stopExclusive Int.- start) + else + if stopPositive then toRepresentation stopExclusive Nat.+ abs start + else abs start Nat.- abs stopExclusive + maxAcceptableValue = maxNat Nat.- mod maxNat range + loop = + do + use Int + + use Nat - < > toInt + rngOutput = Random.nat() + if rngOutput > maxAcceptableValue then loop() + else + n = mod rngOutput range + if n < 4294967296 then start + toInt n + else start + +4294967296 + toInt (n - 4294967296) + loop() + else bug ("Random.intIn start must be < stop", start, stopExclusive) + +abilities.Random.intIn.doc : Doc +abilities.Random.intIn.doc = + {{ + Generates a random {type Int} in the given range, exclusive of the upper + bound. The upper bound must be greater than the lower bound. + + Every value in the range has an equal chance of being generated. + + # Example + + ``` + splitmix 1 do intIn +0 +10 + ``` + }} + +test> abilities.Random.intIn.tests.ex1 : [Result] +abilities.Random.intIn.tests.ex1 = + use List all replicate + check + (lcg 99 do + ok start stop n = Universal.gteq n start && Universal.lt n stop + all (ok -100 +100) (replicate 100 do intIn -100 +100) + && all (ok -10 +10) (replicate 100 do intIn -10 +10) + && all (ok -1000 +1000) (replicate 100 do intIn -1000 +1000) + && all (ok -9999 +9999) (replicate 100 do intIn -9999 +9999)) + +test> abilities.Random.intIn.tests.fairness = + use Float <= fromNat + use Nat toInt + check + (splitmix 99 do + bins = 10 + samples = 100000 + ignore "21.666 is the critical value for Chi-Squared at 1% significance" + critical = 21.666 + bag = + Bag.fromList + (List.replicate samples do + intIn (Int.negate (toInt (bins Nat./ 2))) (toInt (bins Nat./ 2))) + expected = fromNat samples Float./ fromNat bins + chiSquared = + Float.sum + (List.map + (cases + (gen, observed) -> + Float.pow (fromNat observed Float.- expected) 2.0 + Float./ expected) + (Bag.occurrenceList bag)) + freedom = bins Nat.- 1 + chiSquared <= critical) + +abilities.Random.lcg : Nat -> '{g, Random} t ->{g} t +abilities.Random.lcg seed r = handle r() with lcg.handler seed + +abilities.Random.lcg.doc : Doc +abilities.Random.lcg.doc = + use List replicate + use Random natIn + {{ + `` lcg seed r `` provides random numbers to `r` using a + [linear congruential random number generator](https://en.wikipedia.org/wiki/Linear_congruential_generator). + + The `seed` parameter can be any {type Nat}. Using different seeds will + produce different streams of random numbers. + + # Examples + + ``` + lcg 0 do replicate 10 do natIn 1 100 + ``` + + ``` + lcg 939394837 do replicate 10 do natIn 1 100 + ``` + + # Implementation notes + + This implementation uses the MMIX choice of parameters for `modulus`, `a`, + and `c` + [discussed here](https://en.wikipedia.org/wiki/Linear_congruential_generator). + + Other generators can be patterned after {lcg.handler}. + }} + +abilities.Random.lcg.handler : Nat -> Request {Random} a -> a +abilities.Random.lcg.handler = + use Nat * + shiftRight + c = 11 + a = 25214903917 + mask48 = 281474976710655 + update seed = Nat.and (a * seed + c) mask48 + go : Nat -> Request {Random} x -> x + go seed = cases + { a } -> a + { Random.nat! -> resume } -> + seed1 = update seed + part1 = shiftRight seed1 16 + seed2 = update seed1 + part2 = shiftRight seed2 16 + full64 = Nat.or (Nat.shiftLeft part1 32) part2 + handle resume full64 with go seed2 + { Random.bytes n -> resume } -> + handle resume (bytesFromNats Random.nat n) with go seed + { split! -> resume } -> + seed' = finish (Murmur.add seed initialSeed) + seed'' = update seed' + handle resume (r -> (handle r() with go seed')) with go seed'' + go + +abilities.Random.lcg.handler.doc : Doc +abilities.Random.lcg.handler.doc = + {{ + Implementation detail of {lcg}. + + Other {type Random} handlers can be patterned after this. You can use + different static parameters (for `a` and/or `c`), state (expressed as handler + parameter(s) to the `go` function), and/or a different update rule. + }} + +abilities.Random.listOf : '{g, Random} a -> '{g, Random} Nat ->{g, Random} [a] +abilities.Random.listOf gen n = Each.toList do + Each.repeat n() + gen() + +abilities.Random.listOf.doc : Doc +abilities.Random.listOf.doc = + use Random listOf natIn + use fromNat impl + {{ + `` listOf gen n `` returns a list of random values generated by the generator + `gen`, where the length of the list is generated by the generator `n`. + + # Examples + + ``` + lcg 1 do listOf (do natIn 1 10) do 10 + ``` + + ``` + lcg 1 do + listOf + (do + fromCharList + (listOf (do interval (impl 127813) (impl 127832)) do natIn 0 7)) + do 5 + ``` + }} + +abilities.Random.mapOf : + '{g1, Random} k + -> '{g2, Random} v + -> '{g1, g2, Random} Nat + ->{g1, g2, Random} Map k v +abilities.Random.mapOf genKey genValue = + (Random.listOf do (genKey(), genValue())) >> Map.fromList + +abilities.Random.mapOf.doc : Doc +abilities.Random.mapOf.doc = + use Map toList + use Random mapOf + {{ + `` mapOf keyGen valueGen n `` returns a {type Map} with entries generated by + the provided key and value generators. The number of entries in the map is + generated by the generator `n`. + + # Examples + + ``` + splitmix 42 do + (mapOf (do intIn -9 +9) Random.float do Random.natIn 0 10) |> toList + ``` + + ``` + splitmix 42 do (mapOf text.base32Hex Random.boolean do 4) |> toList + ``` + }} + +abilities.Random.nat : '{Random} Nat +abilities.Random.nat = do Random.nat! + +abilities.Random.nat.doc : Doc +abilities.Random.nat.doc = + {{ Generate a random {type Nat} between `` 0 `` and {maxNat} inclusive. }} + +abilities.Random.nat.natsWithSum : Nat -> Nat ->{Random} [Nat] +abilities.Random.nat.natsWithSum sum count = + use List :+ + use Nat - == + if count == 0 then + if sum == 0 then [] + else bug "count must be greater than 0 if sum is greater than 0" + else + upperExclusive = if sum === maxNat then maxNat else Nat.increment sum + intervals = cases + (acc, prev), current -> + interval = current - prev + (acc :+ interval, current) + (fill' (Nat.decrement count) do Random.natIn 0 upperExclusive) |> List.sort + |> List.foldLeft intervals ([], 0) + |> (cases (acc, last) -> acc :+ (sum - last)) + +abilities.Random.nat.natsWithSum.doc : Doc +abilities.Random.nat.natsWithSum.doc = + {{ + `` natsWithSum sum count `` returns a list of `count` uniformly-distributed + natural numbers whose sum is `sum`. + + If `count` is 0, `sum` must be 0 or else this will result in a runtime {bug}. + + # Examples + + ``` + splitmix 13 do natsWithSum 20 4 + ``` + + ``` + splitmix 42 do natsWithSum 0 4 + ``` + + ``` + splitmix 42 do natsWithSum maxNat 2 + ``` + + # Implementation details + + Below is a description of how {natsWithSum} will generate 4 numbers that + sum to `20` in the example of ``natsWithSum 20 4``: + + * Generate `count - 1` (3) uniformly-distributed natural numbers between 0 + and `sum` (20). In this example we generate ``[8, 10, 16]``. + * Sort the numbers. They happened to be sorted in this example! + * Calculate the difference between each number and the previous number. + This interval size becomes the number in the output. Use 0 as the + starting point and `sum` (20) as the ending point. ``[8, 2, 6, 4]``. + * Done! `` [8, 2, 6, 4] `` are 4 numbers that sum to `20`. + + ``` raw + |--------|--|------|----| + generated numbers: 0 8 10 16 20 + \______/ \/ \____/ \__/ + intervals: 8 2 6 4 + ``` + }} + +test> abilities.Random.nat.natsWithSum.tests = test.verify do + use Each repeat run + use Random natIn + check sum count = + use Nat == + nats = natsWithSum sum count + ensuring do List.size nats == count + ensuring do Nat.sum nats == sum + run do + repeat 10 + sum = 0 + count = natIn 0 101 + check sum count + run do + repeat 100 + sum = Random.nat! + count = natIn 1 101 + check sum count + +abilities.Random.natIn : Nat -> Nat ->{Random} Nat +abilities.Random.natIn start stopExclusive = + use Nat - mod + if Universal.lt start stopExclusive then + range = stopExclusive - start + maxAcceptableValue = maxNat - mod maxNat range + loop = + do + use Nat + > + rngOutput = Random.nat() + if rngOutput > maxAcceptableValue then loop() + else start + mod rngOutput range + loop() + else bug ("Random.natIn start must be < stop", start, stopExclusive) + +abilities.Random.natIn.doc : Doc +abilities.Random.natIn.doc = + use Random natIn + {{ + `` natIn i j `` generates a {type Nat} between `i` and `j`, not including + `j`. + + If `j` is less than or equal to `i`, throws an error. + + # Examples + + ``` + lcg 384 do List.replicate 5 do natIn 5 15 + ``` + + Here, the upper bound is less than the lower bound, resulting in an error: + + ``` + lcg 37 do natIn 8 3 + ``` + }} + +test> abilities.Random.natIn.tests.ex1 : [Result] +abilities.Random.natIn.tests.ex1 = + use List all replicate + use Random natIn + check + (lcg 99 do + ok start stop n = Universal.gteq n start && Universal.lt n stop + all (ok 0 100) (replicate 100 do natIn 0 100) + && all (ok 1 10) (replicate 100 do natIn 1 10) + && all (ok 100 1000) (replicate 100 do natIn 100 1000) + && all (ok 1000 9999) (replicate 100 do natIn 1000 9999)) + +test> abilities.Random.natIn.tests.fairness = + use Float / <= fromNat + check + (splitmix 99 do + bins = 10 + samples = 100000 + ignore "21.666 is the critical value for Chi-Squared at 1% significance" + critical = 21.666 + bag = Bag.fromList (List.replicate samples do Random.natIn 0 bins) + expected = fromNat samples / fromNat bins + chiSquared = + Float.sum + (List.map + (cases + (gen, observed) -> + Float.pow (fromNat observed Float.- expected) 2.0 / expected) + (Bag.occurrenceList bag)) + freedom = bins Nat.- 1 + chiSquared <= critical) + +abilities.Random.oneOf : [a] ->{Random} a +abilities.Random.oneOf l = + i = Random.natIn 0 (List.size l) + match List.at i l with + None -> bug "Random.oneOf: impossible" + Some x -> x + +abilities.Random.oneOf.doc : Doc +abilities.Random.oneOf.doc = + use Random oneOf + {{ + Picks a random value from a list. Assumes that the list is not empty, so an + empty list will cause a runtime exception. + + # Examples + + ``` + lcg 4096 do oneOf [0, 3, 5, 7] + ``` + + ``` + lcg 2517 do oneOf [?x, ?y, ?z] + ``` + + ``` + lcg 128 do oneOf [char.digit, hex] () + ``` + + # See also + + {oneOfNonempty} ensures the list is not empty using the type system. + }} + +abilities.Random.oneOfNonempty : List.Nonempty a ->{Random} a +abilities.Random.oneOfNonempty as = + n = Random.natIn 0 (List.Nonempty.size as) + Abort.toBug do List.at! n (List.Nonempty.toList as) + +abilities.Random.oneOfNonempty.doc : Doc +abilities.Random.oneOfNonempty.doc = + {{ + Picks a random value from a {type List.Nonempty}. + + # Examples + + ``` + lcg 4096 do oneOfNonempty (0 +| [3, 5, 7]) + ``` + + ``` + lcg 2517 do oneOfNonempty (?x +| [?y, ?z]) + ``` + + ``` + lcg 128 do oneOfNonempty (char.digit +| [hex]) () + ``` + + # See also + + {Random.oneOf} takes a {type List} and assumes it is not empty. + }} + +abilities.Random.optional : '{g, Random} a ->{g} '{g, Random} Optional a +abilities.Random.optional gen = + Random.weighted [(9, gen >> Some), (1, do None)] + +abilities.Random.optional.doc : Doc +abilities.Random.optional.doc = + use Random optional + {{ + `` optional gen `` returns a generator that will sometimes product {None} and + sometimes produce {Some} values wrapping a value generated by the provided + `gen`. + + # Example + + ``` + optionGen = optional do Random.natIn 0 100 + splitmix 17 do Random.listOf optionGen do 20 + ``` + + # Related + + See {optional!} for a variant whose result is not delayed. + }} + +abilities.Random.optional! : '{g, Random} a ->{g, Random} Optional a +abilities.Random.optional! = Random.optional >> force + +abilities.Random.optional!.doc : Doc +abilities.Random.optional!.doc = + {{ + A variant of {Random.optional} that eagerly returns a result. + + # Example + + ``` + splitmix 17 do Random.listOf (do optional! do Random.natIn 0 100) do 20 + ``` + }} + +abilities.Random.RNG.doc : Doc +abilities.Random.RNG.doc = + {{ + A random number generator as a value, rather than as a handler, useful for + passing random number generators to functions or stashing them in data + structures. + + @signatures{fromSplitmix, fromLcg, RNG.split, RNG.run} + }} + +abilities.Random.RNG.fromLcg : Nat -> RNG +abilities.Random.RNG.fromLcg seed = RNG (lcg seed) + +abilities.Random.RNG.fromLcg.doc : Doc +abilities.Random.RNG.fromLcg.doc = + {{ Creates an {type RNG} from a call to {lcg}. }} + +abilities.Random.RNG.fromSplitmix : Nat -> RNG +abilities.Random.RNG.fromSplitmix seed = RNG (splitmix seed) + +abilities.Random.RNG.fromSplitmix.doc : Doc +abilities.Random.RNG.fromSplitmix.doc = + {{ Creates an {type RNG} from a call to {splitmix}. }} + +abilities.Random.RNG.run : RNG -> '{g, Random} a ->{g} a +abilities.Random.RNG.run = cases RNG h -> h + +abilities.Random.RNG.run.doc : Doc +abilities.Random.RNG.run.doc = + use RNG run + {{ + `` run r c `` uses `r` to handle requests for randomness in `c`. + + ``` + run (fromSplitmix 0) do List.replicate 5 do Random.natIn 0 10 + ``` + }} + +abilities.Random.RNG.split : RNG -> (RNG, RNG) +abilities.Random.RNG.split = cases RNG h -> h do (RNG split!, RNG split!) + +abilities.Random.RNG.split.doc : Doc +abilities.Random.RNG.split.doc = + use RNG run split + {{ + `` split r `` returns two independent generators by splitting `r`. + + ``` + p = split (fromSplitmix 0) + c = do List.replicate 5 do Random.natIn 0 10 + (run (at1 p) c, run (at2 p) c) + ``` + }} + +abilities.Random.run : '{g, Random} a ->{g, IO} a +abilities.Random.run r = splitmix randomNat() r + +abilities.Random.run.doc : Doc +abilities.Random.run.doc = + {{ + Runs a computation that uses {type Random}, generating a random seed using + the system's secure random number generator. + + Requires the {type IO} ability, so it can only be used where I/O is allowed. + + This generator is not cryptographically secure, as it uses the + [SplitMix](https://gee.cs.oswego.edu/dl/papers/oopsla14.pdf) algorithm to + generate pseudorandom values. + + # Example + + This example generates 10 random numbers between 0 and 100 (not including + 100): + + @typecheck ``` + Random.run do List.replicate 10 do Random.natIn 0 100 + ``` + }} + +abilities.Random.shuffle : [a] ->{Random} [a] +abilities.Random.shuffle as = Scope.run do + use List at map + use Nat + - + use Ref read write + refs = map Scope.ref as + swap i j = match (at i refs, at j refs) with + (Some r1, Some r2) -> + tmp = read r1 + write r1 (read r2) + write r2 tmp + _ -> bug ("out of bounds", i, j) + go = cases + 0 -> () + i -> + j = Random.natIn 0 (i + 1) + swap i j + go (i - 1) + go (List.size refs - 1) + map read refs + +abilities.Random.shuffle.doc : Doc +abilities.Random.shuffle.doc = + use Nat range + {{ + `` shuffle xs `` randomly shuffles the list `xs`. All permutations of the + list are equally likely. + + ``` + splitmix 0 do shuffle (range 0 10) + ``` + + ``` + splitmix 1 do shuffle (range 0 10) + ``` + + ``` + splitmix 2 do shuffle [] + ``` + + ``` + splitmix 3 do shuffle [1] + ``` + + # Implementation notes + + Uses a + [Fischer-Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle), + using local mutable state to do the swapping of elements. + }} + +test> abilities.Random.shuffle.tests = test.verify do + use Set fromList toList + Each.repeat 25 + as = Nat.range 0 (Random.natIn 0 20) + as2 = shuffle as + ensureEqual (toList (fromList as)) (toList (fromList as2)) + +abilities.Random.split : '{Random} ('{g, Random} t ->{g} t) +abilities.Random.split = do split! + +abilities.Random.split.doc : Doc +abilities.Random.split.doc = + use List replicate + use Random natIn split + {{ + `` split() `` creates a new supply of random numbers uncorrelated from the + current supply. + + This is useful for concurrent use: a forked thread can be given its own + supply of random numbers. + + # Examples + + ``` + example = do + rng = split() + t1 = rng do replicate 5 do natIn 5 15 + (natIn 5 15, t1) + lcg 94859830948 example + ``` + + The result of {split} also acts as a checkpoint and can be used to produce + the same random numbers multiple times. + + ``` + example = do + rng = split() + run1 = rng do replicate 4 do natIn 1 100 + run2 = rng do replicate 4 do natIn 1 100 + (run1, run2) + lcg 74 example + ``` + }} + +abilities.Random.splitmix : Nat -> '{g, Random} t ->{g} t +abilities.Random.splitmix seed r = + use Nat + + seed0 = mix64 seed + gamma0 = mixGamma (goldenGamma + seed) + handle r() with splitmix.handler seed0 gamma0 + +abilities.Random.splitmix.doc : Doc +abilities.Random.splitmix.doc = + use Random natIn + {{ + `` splitmix seed r `` provides random numbers to `r`. + + The `seed` parameter can be any {type Nat}. Using different seeds will + produce different streams of random numbers. + + # Examples + + ``` + splitmix 0 do List.replicate 10 do natIn 1 100 + ``` + + ``` + example = do + x = natIn 1 100 + y = (natIn 1 100, natIn 0 10) + (x, y) + splitmix 3849 example + ``` + + # References + + Based on + [Fast Splittable Pseudorandom Number Generators](http://gee.cs.oswego.edu/dl/papers/oopsla14.pdf). + + # Credits + + This implementation borrows heavily from the + [Haskell splitmix package](https://hackage.haskell.org/package/splitmix), + which is {bsd3}-licensed. + }} + +abilities.Random.splitmix.handler : Nat -> Nat -> Request {Random} a2 -> a2 +abilities.Random.splitmix.handler seed gamma = + use Nat + + use Random nat! + go : ∀ a2. Nat -> Nat -> Request {Random} a2 -> a2 + go seed gamma = cases + { a } -> a + { split! -> k } -> + seed' = seed + gamma + seed'' = seed' + gamma + handle k (c -> (handle c() with go seed'' gamma)) + with go (mix64 seed') (mixGamma seed'') + { nat! -> k } -> + seed' = seed + gamma + handle k (mix64 seed') with go seed' gamma + { Random.bytes n -> k } -> + handle k (bytesFromNats (do nat!) n) with go seed gamma + go seed gamma + +abilities.Random.splitmix.impl.goldenGamma : Nat +abilities.Random.splitmix.impl.goldenGamma = 11400714819323198485 + +abilities.Random.splitmix.impl.mix64 : Nat -> Nat +abilities.Random.splitmix.impl.mix64 z0 = + z1 = shiftXorMultiply 33 18397679294719823053 z0 + z2 = shiftXorMultiply 33 14181476777654086739 z1 + z3 = shiftXor 33 z2 + z3 + +abilities.Random.splitmix.impl.mix64variant13 : Nat -> Nat +abilities.Random.splitmix.impl.mix64variant13 z0 = + z1 = shiftXorMultiply 30 13787848793156543929 z0 + z2 = shiftXorMultiply 27 10723151780598845931 z1 + z3 = shiftXor 31 z2 + z3 + +abilities.Random.splitmix.impl.mixGamma : Nat -> Nat +abilities.Random.splitmix.impl.mixGamma z0 = + use Nat >= xor + z1 = Nat.or (mix64variant13 z0) 1 + n = Nat.popCount (xor z1 (Nat.shiftRight z1 1)) + if n >= 24 then z1 else xor z1 12297829382473034410 + +abilities.Random.splitmix.impl.shiftXor : Nat -> Nat -> Nat +abilities.Random.splitmix.impl.shiftXor n w = Nat.xor w (Nat.shiftRight w n) + +abilities.Random.splitmix.impl.shiftXorMultiply : Nat -> Nat -> Nat -> Nat +abilities.Random.splitmix.impl.shiftXorMultiply n k w = + use Nat * + shiftXor n w * k + +abilities.Random.splits : + (a ->{g1} Nat) + -> (Nat -> a ->{g2} (a, a)) + -> Nat + -> a + -> '{g1, g2, Random, Stream a} () +abilities.Random.splits size splitAt = + go remaining count = match splitAt count remaining with + (chunk, remaining) -> + emit chunk + remaining + chunkCount original -> + do + natsWithSum (size original) chunkCount |> List.foldLeft go original + |> ignore + +abilities.Random.splits.bytes : Nat -> Bytes -> '{Random, Stream Bytes} () +abilities.Random.splits.bytes = Random.splits Bytes.size Bytes.splitAt + +abilities.Random.splits.bytes.doc : Doc +abilities.Random.splits.bytes.doc = + use fromList impl + {{ + `` splits.bytes chunkCount bytes `` splits `bytes` into `chunkCount` chunks + of uniformly-distributed size. + + # Examples + + ``` + splits.bytes 3 0xsc0decafe |> toDelayedList |> splitmix 41 + ``` + + ``` + splits.bytes 3 0xs |> toDelayedList |> splitmix 42 + ``` + }} + +test> abilities.Random.splits.bytes.tests = test.verify do + use Bytes empty + use Each repeat run + use Random natIn + check bytes chunkCount = + use Bytes ++ + use Nat == + chunks = splits.bytes chunkCount bytes |> Stream.toList + ensuring do List.size chunks == chunkCount + ensuring do List.foldLeft (++) empty chunks === bytes + run do + repeat 10 + bytes = empty + chunkCount = natIn 0 101 + check bytes chunkCount + run do + repeat 100 + bytes = Random.bytes (natIn 0 100) + chunkCount = natIn 1 101 + check bytes chunkCount + +abilities.Random.splits.doc : Doc +abilities.Random.splits.doc = + {{ + `` Random.splits size splitAt chunkCount original `` splits `original` into + `chunkCount` chunks of uniformly-distributed size. + + `size` takes the input and returns its total size. For splitting {type Bytes} + this would be {Bytes.size}. + + `splitAt index value` splits `value` into two parts at `index`. For splitting + {type Bytes} this would be {Bytes.splitAt}. + + `chunkCount` is the number of chunks to split `original` into. + + `original` is the value to split. + + # Examples + + @source{splits.bytes} + + @source{splits.text} + + @source{list} + }} + +abilities.Random.splits.list : Nat -> [a] -> '{Random, Stream [a]} () +abilities.Random.splits.list = Random.splits List.size List.splitAt + +abilities.Random.splits.list.doc : Doc +abilities.Random.splits.list.doc = + {{ + `` splits.list chunkCount list `` splits `list` into `chunkCount` chunks of + uniformly-distributed size. + + # Examples + + ``` + splits.list 3 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |> toDelayedList + |> splitmix 42 + ``` + + ``` + splits.list 3 [] |> toDelayedList |> splitmix 42 + ``` + }} + +test> abilities.Random.splits.list.tests = test.verify do + use Each repeat run + use List empty + use Random natIn + check list chunkCount = + use List ++ + use Nat == + chunks = splits.list chunkCount list |> Stream.toList + ensuring do List.size chunks == chunkCount + ensuring do List.foldLeft (++) empty chunks === list + run do + repeat 10 + list = empty + chunkCount = natIn 0 101 + check list chunkCount + run do + repeat 100 + list = fill' (natIn 0 101) Random.nat + chunkCount = natIn 1 101 + check list chunkCount + +abilities.Random.splits.text : Nat -> Text -> '{Random, Stream Text} () +abilities.Random.splits.text = Random.splits Text.size Text.splitAt + +abilities.Random.splits.text.doc : Doc +abilities.Random.splits.text.doc = + {{ + `` splits.text chunkCount text `` splits `text` into `chunkCount` chunks of + uniformly-distributed size. + + # Examples + + ``` + splits.text 3 "Hello, world!" |> toDelayedList |> splitmix 42 + ``` + + ``` + splits.text 3 "" |> toDelayedList |> splitmix 42 + ``` + }} + +abilities.Random.text.base32Hex : '{Random} Text +abilities.Random.text.base32Hex _ = + use List :+ replicate + use Nat * + use Random natIn oneOf + use Text ++ + use char base32Hex + b8 : '{Random} [Char] + b8 _ = [base32Hex(), b32h (4 * natIn 0 8)] + b16 : '{Random} [Char] + b16 _ = replicate 3 base32Hex :+ oneOf [?0, ?g] + b24 : '{Random} [Char] + b24 _ = replicate 4 base32Hex :+ b32h (2 * natIn 0 16) + b32 : '{Random} [Char] + b32 _ = replicate 6 base32Hex :+ oneOf [?0, ?8, ?g, ?o] + b40 : '{Random} [Char] + b40 _ = replicate 8 base32Hex + ck : '{Random} Text + ck _ = fromCharList b40() + finish : '{Random} Text + finish _ = + use Nat - + t = fromCharList (oneOf [b8, b16, b24, b32, b40] ()) + k = 8 - Text.size t + t ++ Text.repeat k "=" + n = natIn 0 8 + Text.join "" (replicate n ck) ++ finish() + +abilities.Random.text.base32Hex.doc : Doc +abilities.Random.text.base32Hex.doc = + use text base32Hex + {{ + Produces a valid base 32 hex string that encodes a byte aligned {type Bytes} + value. + + # Examples + + ``` + lcg 4096 base32Hex + ``` + + ``` + lcg 2517 base32Hex + ``` + }} + +abilities.Random.text.ofChars : '{Random} Char -> Nat ->{Random} Text +abilities.Random.text.ofChars chars length = + fromCharList + (Each.toList do + _ = Each.range 0 length + chars()) + +abilities.Random.text.ofChars.doc : Doc +abilities.Random.text.ofChars.doc = + {{ + Generates random {type Text} of the given length, containing characters + generated by the given {type Char} generator. + + # Example + + ``` + splitmix 2 do ofChars (do interval ?a ?z) 20 + ``` + }} + +abilities.Random.weighted : [(Nat, '{g, Random} a)] ->{g} '{g, Random} a +abilities.Random.weighted weightedGen = + fromStream (Stream.fromList weightedGen) + +abilities.Random.weighted.doc : Doc +abilities.Random.weighted.doc = + use Random weighted + {{ + Creates a random generator that is a weighted sampling of the provided + generators. The weights are provided as a list of pairs, where the first + element of each pair is the weight and the second element is the generator. + + # Examples + + In the following example, the generator will produce a random character + that is either `a` or `b`. The probability of `a` is 1/4 and the + probability of `b` is 3/4. + + ``` + splitmix 42 do ofChars (weighted [(1, do ?a), (3, do ?b)]) 12 + ``` + + In the following example, the generator will produce a random character + that has a 2/5 chance of being lowercase, a 2/5 chance of being uppercase, + and a 1/5 chance of being a digit. + + ``` + splitmix 42 do + ofChars + (weighted [(2, ascii.lower), (2, ascii.upper), (1, char.digit)]) 20 + ``` + + # See also + + * {fromStream} for a variant of this that takes a {type Stream} of + generators instead of a {type List}. + }} + +abilities.Random.weighted.fromStream : + '{g, Stream (Nat, '{g, Random} a)} r ->{g} '{g, Random} a +abilities.Random.weighted.fromStream weights = + use Nat + + go : + (Nat, NatMap ('{g, Random} a)) + -> (Nat, '{g, Random} a) + -> (Nat, NatMap ('{g, Random} a)) + go = cases + (prevTotal, m), (weight, gen) -> + total = prevTotal + weight + (total, NatMap.insert total gen m) + let + (total, m) = Stream.fold go (0, NatMap.empty) weights + endExclusive = Nat.increment total + do + n = Random.natIn 1 endExclusive + NatMap.getAtLeast n m + |> getOrBug + "a weighted random sampling requires at least one non-zero weight" + |> at2 + |> force + +abilities.Random.weighted.fromStream.doc : Doc +abilities.Random.weighted.fromStream.doc = + {{ + A variant of {Random.weighted} that takes a {type Stream} of generators + instead of a {type List}. + }} + +abilities.repeat : Nat -> '{e} a ->{e} () +abilities.repeat n op = + use Nat - + if n === 0 then () + else + ignore op() + abilities.repeat (n - 1) op + +abilities.repeat.doc : Doc +abilities.repeat.doc = + use abilities repeat + {{ + `` repeat n op `` repeats an effectful computation `op`, `n` times. + + # Examples + + ``` + withInitialValue 0 do + repeat 5 do Store.modify Nat.increment + Store.get + ``` + + ``` + Each.toList do + repeat 2 do Each.range 0 3 + "x" + ``` + }} + +abilities.Request.doc : Doc +abilities.Request.doc = + {{ + A {type Request} is the type of object that is passed to a handler when an + ability's constructor is called. It contains the constructor, the arguments + to the constructor, and the continuation to be called when the request is + handled. + + {type Request} is parameterized on the ability type and the return type of + the continuation. For a {type Request} of type `Request e r` the ability type + is `e`, and if the constructor requested has type `x ->{e} y`, then the + continuation has type `y ->{e} r`. + + 📚 Guide: + [Writing ability handlers](https://www.unison-lang.org/learn/fundamentals/abilities/writing-abilities/) + + # Example + + ``` + h : Request {Ask Text} Nat -> Nat + h = cases + { ask -> k } -> handle k "foo" with h + { a } -> a + handle Text.size ask with h + ``` + }} + +abilities.Store.accumulateLeft : (a ->{g, Store s} b) -> s -> [a] ->{g} [b] +abilities.Store.accumulateLeft f s xs = withInitialValue s do List.map f xs + +abilities.Store.accumulateLeft.doc : Doc +abilities.Store.accumulateLeft.doc = + use Nat + + {{ + `` accumulateLeft f s xs `` applies `f` to every element of `xs`, + accumulating effects in left-to-right order. The value `s` is the initial + state of the {type Store}. The final state of the {type Store} is discarded. + + # Example + + ``` + accumulateLeft + (x -> let + n = Store.get + Store.put (n + 1) + (n, x)) 0 ["a", "b", "c"] + ``` + }} + +abilities.Store.accumulateRight : (a ->{g, Store s} b) -> s -> [a] ->{g} [b] +abilities.Store.accumulateRight f s xs = + withInitialValue s do List.mapRight f xs + +abilities.Store.accumulateRight.doc : Doc +abilities.Store.accumulateRight.doc = + use Nat + + {{ + `` accumulateRight f s xs `` applies `f` to every element of `xs`, + accumulating effects in right-to-left order. The value `s` is the initial + state of the {type Store}. The final state of the {type Store} is discarded. + + # Example + + ``` + accumulateRight + (x -> let + n = Store.get + Store.put (n + 1) + (n, x)) 0 ["a", "b", "c"] + ``` + }} + +abilities.Store.doc : Doc +abilities.Store.doc = + use stack add pop push + {{ + {type Store} represents the ability to store a value with {Store.put} and to + retrieve the stored value with {Store.get}. + + See {withInitialValue} for a polymorphic handler that stores a value in + memory. + + {local!} allows you to focus on some attribute of the stored value using a + getter and setter for that attribute. + + {Store.modify} applies a function to the stored value. + + {unfold!} generates a list of values by iterating a state machine implemented + as a {type Store} computation. + + # Example : a simple stack machine + + If we can {type Store} a stack, we can push onto the stack: + + @source{push} + + We can pop an element off the stack: + + @source{pop} + + We can add the top two elements from the stack and push the result: + + @source{binop} + + @source{add} + + We can run the machine starting with an empty stack: + + @source{runStack} + + And now we can use our stack machine to add some numbers: + + ``` + runStack do + push +10 + push +20 + add() + push +100 + add() + pop() + ``` + }} + +abilities.Store.examples.stack.add : '{Abort, Store [Int]} () +abilities.Store.examples.stack.add = do binop (Int.+) + +abilities.Store.examples.stack.binop : + (a ->{e} a ->{g} a) ->{e, g, Abort, Store [a]} () +abilities.Store.examples.stack.binop f = + use stack pop + x = pop() + y = pop() + stack.push (f x y) + +abilities.Store.examples.stack.pop : '{Abort, Store [a]} a +abilities.Store.examples.stack.pop = do + use Optional toAbort + stack = Store.get + Store.put (toAbort (List.tail stack)) + toAbort (List.head stack) + +abilities.Store.examples.stack.push : a ->{Store [a]} () +abilities.Store.examples.stack.push n = + use List +: + Store.put (n +: Store.get) + +abilities.Store.examples.stack.runStack : '{Abort, Store [Int]} a -> Optional a +abilities.Store.examples.stack.runStack p = + Abort.toOptional (do withInitialValue [] p) () + +abilities.Store.get.doc : Doc +abilities.Store.get.doc = + use Store get + {{ + The `` get `` operation for the {type Store} ability retrieves the stored + state. + + # Example + + ``` + withInitialValue 1 do get + ``` + }} + +abilities.Store.get.examples.ex1 : Nat +abilities.Store.get.examples.ex1 = withInitialValue 1 do Store.get + +abilities.Store.getOrUpdate : k -> '{Store (Map k v)} v ->{Store (Map k v)} v +abilities.Store.getOrUpdate k ifEmpty = match Map.get k Store.get with + Some v -> v + None -> + v = ifEmpty() + Store.modify (Map.insert k v) + v + +abilities.Store.getOrUpdate.doc : Doc +abilities.Store.getOrUpdate.doc = + {{ + `` getOrUpdate k ifEmpty `` returns the value under the key 'k' in the stored + {type Map} if it exists, otherwise it returns the result of the computation + 'ifEmpty' and updates the {type Map} so that it contains that result under + the key 'k'. + + # Example + + We can use {getOrUpdate} to implement a cache that memoizes the result of a + function. Here the {fibonacci} function re-uses previous results to avoid + unnecessary recursion, by storing the results in a {type Map}. + + @source{fibonacci} + + ``` + toDecimalText (withInitialValue Map.empty do fibonacci 200) + ``` + }} + +abilities.Store.getOrUpdate.examples.fibonacci : + Nat ->{Store (Map Nat Natural)} Natural +abilities.Store.getOrUpdate.examples.fibonacci = cases + n + | n Nat.< 2 -> Natural.fromNat n + | otherwise -> + use Nat - + use Natural + + use abilities.Store.getOrUpdate.examples fibonacci + n2 = getOrUpdate (n - 2) do fibonacci (n - 2) + n1 = getOrUpdate (n - 1) do fibonacci (n - 1) + n2 + n1 + +abilities.Store.local : + (a ->{g} b) -> (a ->{g} b ->{g} a) -> '{g, Store b} v -> '{g, Store a} v +abilities.Store.local getter setter thunk _ = local! getter setter thunk + +abilities.Store.local.deprecated : a -> '{g, Store a} v ->{g, Store a} v +abilities.Store.local.deprecated a op = + use Store put + old = Store.get + put a + res = op() + put old + res + +abilities.Store.local.deprecated.examples.ex1 : (Nat, Nat, Nat) +abilities.Store.local.deprecated.examples.ex1 = withInitialValue 1 do + use Store get + before = get + during = local.deprecated 2 do get + after = get + (before, during, after) + +test> abilities.Store.local.deprecated.test = + deprecated.run (expect ((1, 2, 1) === deprecated.examples.ex1)) + +abilities.Store.local.doc : Doc +abilities.Store.local.doc = + {{ + `` local getter setter thunk `` returns a delayed computation of + ``local! getter setter thunk``. + }} + +abilities.Store.local! : + (a ->{g} b) -> (a ->{g} b ->{g} a) -> '{g, Store b} v ->{g, Store a} v +abilities.Store.local! getter setter thunk = + use Store get + op : '{g, Store b} (v, b) + op _ = (thunk(), get) + let + (v, b) = withInitialValue (getter get) op + Store.modify (a -> setter a b) + v + +abilities.Store.local!.doc : Doc +abilities.Store.local!.doc = + {{ + `` local! getter setter thunk `` provides {type Store} access to __part__ of + a state, determined by `getter` and `setter` functions. Specifically, given + some state type `a`, a partial state type `b`, a `getter` function for + computing a `b` from an `a`, a `setter` function for updating an `a` with a + new `b` value, and a `{type Store} b` computation `thunk`, `` + local! getter setter thunk `` evaluates `thunk` in a context where the `{type + Store} a` ability is provided by translating operations on partial states of + type `b` into operations on the complete state of type `a`. + }} + +abilities.Store.local!.examples.ex1 : (Text, Nat) +abilities.Store.local!.examples.ex1 = withInitialValue ("a", 1) do + use Nat + + use Store get + ignore {{ use {local!} to modify the second element of the state pair. }} + local! at2 (p v -> (at1 p, v)) do + n = get + Store.put (n + 1) + get + +test> abilities.Store.local!.tests.ex1 = + deprecated.run (expect (assertEquals local!.examples.ex1 ("a", 2))) + +abilities.Store.modify : (a ->{g} a) ->{g, Store a} () +abilities.Store.modify f = Store.put (f Store.get) + +abilities.Store.modify.doc : Doc +abilities.Store.modify.doc = + use Store get modify + {{ + {modify} applies a function to the stored value (returned by {get}) and + stores the result (using {Store.put}). + + # Example + + ``` + withInitialValue 3 do + modify Nat.increment + get + ``` + }} + +abilities.Store.modify.examples.increment : Nat +abilities.Store.modify.examples.increment = withInitialValue 3 do + use Nat + + Store.modify (x -> x + 1) + Store.get + +test> abilities.Store.modify.test = + deprecated.run (expect (4 === examples.increment)) + +abilities.Store.put.doc : Doc +abilities.Store.put.doc = + use Store put + {{ + The {put} operation for the {type Store} ability sets the stored state to the + given value. + + # Example + + ``` + withInitialValue 10 do + put 15 + Store.get + ``` + }} + +abilities.Store.put.examples.ex1 : Nat +abilities.Store.put.examples.ex1 = withInitialValue 10 do + Store.put 15 + Store.get + +abilities.Store.unfold : s -> '{g, Abort, Store s} r -> '{g} [r] +abilities.Store.unfold init thunk = do unfold! init thunk + +abilities.Store.unfold.doc : Doc +abilities.Store.unfold.doc = + use Nat < + use Store get + {{ + Starting with the given initial state, repeatedly runs the given {type Store} + action and returns a list of the results. The action is run until it calls + {abort}. + + # Example + + ``` + Store.unfold + 0 (do + if get < 10 then Store.modify Nat.increment else abort + get) () + ``` + + # See also + + {unfold!} is a strict version of this function. + }} + +abilities.Store.unfold! : s -> '{g, Abort, Store s} r ->{g} [r] +abilities.Store.unfold! initialState computeNext = + use List :+ + loop lst = toDefaultValue! lst do loop (lst :+ computeNext()) + withInitialValue initialState do loop [] + +abilities.Store.unfold!.doc : Doc +abilities.Store.unfold!.doc = + use Nat * > + {{ + Generate a list of values using a {type Store s} computation. The first + argument is the initial state for the Store computation, the second is the + stateful computation used to generate the values for the list. At each step, + the second argument is called to generate a new value to be added to the + output list. The computation should call {abort} to stop generating values. + + Examples: + + ``` + unfold! 1 do + n = Store.get + Store.put (n * 2) + if n > 1024 then abort else n + ``` + + Also see {List.iterate} for an example of a function defined using unfold! + }} + +test> abilities.Store.unfold!.tests.ex1 = + go : '{Abort, Store Nat} Nat + go = do + use Nat + < + n = Store.get + Store.put (n + 1) + if n < 10 then n else abort + ns : [Nat] + ns = unfold! 1 go + check (ns === [1, 2, 3, 4, 5, 6, 7, 8, 9]) + +abilities.Store.withInitialValue : a -> '{g, Store a} v ->{g} v +abilities.Store.withInitialValue init thunk = + handle thunk() with withInitialValue.handler init + +abilities.Store.withInitialValue.doc : Doc +abilities.Store.withInitialValue.doc = + use Nat + + use Store get put + {{ + `` withInitialValue init thunk `` provides the {type Store} ability to the + delayed computation `thunk`. Initially, {get} operations will return the + given initial value `init`. After a {put} operation, {get} will return the + value `v` stored by the most recent `` put v `` operation. + + # Example + + ``` + withInitialValue 1 do + put (get + 2) + put (get + 3) + get + ``` + }} + +abilities.Store.withInitialValue.handler : a -> Request (Store a) v -> v +abilities.Store.withInitialValue.handler init = cases + { v } -> v + { Store.get -> k } -> + handle k init with abilities.Store.withInitialValue.handler init + { Store.put v -> k } -> + handle k() with abilities.Store.withInitialValue.handler v + +abilities.Store.withInitialValue.laws.law1 : v ->{Store v} Test +abilities.Store.withInitialValue.laws.law1 v = + expect + (v === let + Store.put v + Store.get) + +test> abilities.Store.withInitialValue.tests.law1 = + runs 20 do + use List +: + vs = gen.listOf natInOrder () + Test.tests + (withInitialValue 0 do expect (Store.get === 0) +: List.map laws.law1 vs) + +abilities.Throw.catchWith : (e ->{g1} a) -> '{g2, Throw e} a ->{g1, g2} a +abilities.Throw.catchWith handleError thunk = + handle thunk() + with cases + { a } -> a + { throw e -> _ } -> handleError e + +abilities.Throw.catchWith.doc : Doc +abilities.Throw.catchWith.doc = + use Float / == + {{ + Catch thrown values and handle them with the provided handler function. + + Examples: + + ``` + divideOrThrow : Float -> Float ->{Throw Text} Float + divideOrThrow numerator denominator = + if denominator == 0.0 then throw "division by zero" + else numerator / denominator + catchWith Left do Right (divideOrThrow 3.0 0.0) + ``` + }} + +abilities.Throw.doc : Doc +abilities.Throw.doc = + use Nat / + {{ + The {type Throw} ability will stop a computation and pass a value of a + **specific type** to the enclosing handler. It is parameterized on the type + of the value that is thrown. It's similar to {type Abort}, but can provide + some additional information about why the computation stopped. {type Throw} + is useful for partial functions, where the function is not defined for the + given argument and we want to provide a reason for the failure. + + 📚 Guide: + [Handling errors with abilities](https://www.unison-lang.org/learn/fundamentals/abilities/error-handling/) + + # Throwing an error + + {type Throw}'s only request constructor is {throw}. Here's an example of a + partial function that uses it: + + @typecheck ``` + divBy : Nat -> Nat ->{Throw Text} Nat + divBy a b = match b with + 0 -> throw "divide by zero" + n -> a / b + ``` + + # Relationship to {type Either} + + The {type Throw} ability is similar to {type Either}, in that both allow a + computation to fail with an error. There is a tradeoff between the two + approaches: + + * {type Throw} is an ability, while {type Either} is a data type. This + means {type Either} values can be passed around, used in data structures, + and as the return type of the constructors of abilities, while + {type Throw} can only be used in computations and functions. + * {type Throw} is more efficient, because it doesn't require allocating a + an {type Either} value. + * {type Throw} readily composes with other abilities, while {type Either} + does not. For example, you can use both {type Throw} and {type Random} to + combine partiality with randomness, but doing so with {type Either} can + be awkward. + * Partial functions that use {type Throw} compose with other functions + normally, while partial functions that use {type Either} must be composed + using {Either.mapRight}, {Either.flatMapRight}, or explicit pattern + matching. + + # Handling {type Throw} + + Catch the error with a function that returns a value of the same type as + the computation: + + @signature{catchWith} + + Crash the program with a runtime error if the computation throws: + + @signature{Throw.toBug} + + Convert a {type Throw} to an {type Either}: + + @signature{toEither} + + Convert a {type Throw} to an {type Exception} by catching the error with a + function that returns a {type Failure}: + + @signature{Throw.toException} + + For a computation that throws the same type as it returns, return the + thrown value if the computation throws: + + @signatures{Throw.unwrap, unwrap!} + }} + +test> abilities.Throw.tests.ex1 = check ((toEither do throw 42) === Left 42) + +test> abilities.Throw.tests.ex2 = + use Nat + + check ((toEither do 1 + throw 42) === Left 42) + +test> abilities.Throw.tests.ex3 = + use Nat + + check ((toEither do Either.toThrow (Right 1) + 42) === Right 43) + +abilities.Throw.toBug : '{g, Throw e} o ->{g} o +abilities.Throw.toBug p = + handle p() + with cases + { throw e -> k } -> bug e + { a } -> a + +abilities.Throw.toBug.doc : Doc +abilities.Throw.toBug.doc = + {{ + A handler for the {type Throw} ability that crashes the program with a call + to {bug} whenever {throw} is called in the given computation. + + # Example + + ``` + Throw.toBug do throw 42 + ``` + }} + +abilities.Throw.toEither : '{g, Throw e} a ->{g} Either e a +abilities.Throw.toEither a = handle a() with toEither.handler + +abilities.Throw.toEither.doc : Doc +abilities.Throw.toEither.doc = + {{ + Converts a computation that may {type Throw}, to an {type Either} that + returns any {type Throw}n value in the {Left} case, and the result of the + computation in the {Right} case. + + # Example + + ``` + toEither do throw "oops" + ``` + + ``` + toEither do 1 + ``` + }} + +abilities.Throw.toEither.handler : Request {Throw e} a -> Either e a +abilities.Throw.toEither.handler = cases + { a } -> Right a + { throw e -> _ } -> Left e + +abilities.Throw.toException : + (e ->{g1} Failure) -> '{g2, Throw e} a ->{g1, g2, Exception} a +abilities.Throw.toException convert = catchWith (Exception.raise << convert) + +abilities.Throw.toException.doc : Doc +abilities.Throw.toException.doc = + use Float / == + {{ + Convert a computation that can {type Throw} an error into one that can raise + an {type Exception}. + + Example: + + ``` + divideOrThrow : Float -> Float ->{Throw Text} Float + divideOrThrow numerator denominator = + if denominator == 0.0 then throw "division by zero" + else numerator / denominator + catch do + Throw.toException (e -> failure e do 3.0 / 0.0) do divideOrThrow 3.0 0.0 + ``` + }} + +abilities.Throw.toOptional : '{g, Throw e} a ->{g} Optional a +abilities.Throw.toOptional c = + h = cases + { a } -> Some a + { throw _ -> _ } -> None + handle c() with h + +abilities.Throw.toOptional.doc : Doc +abilities.Throw.toOptional.doc = + use Throw toOptional + {{ + Converts a {type Throw} effect to an {type Optional} value. + + # Examples + + ``` + toOptional do throw "error" + ``` + + ``` + toOptional do "hello" + ``` + }} + +abilities.Throw.unwrap : '{g, Throw a} a -> '{g} a +abilities.Throw.unwrap thunk = do unwrap! thunk + +abilities.Throw.unwrap.doc : Doc +abilities.Throw.unwrap.doc = + {{ Similar to {unwrap!} but does not eagerly evaluate the result. }} + +abilities.Throw.unwrap! : '{g, Throw a} a ->{g} a +abilities.Throw.unwrap! thunk = + handle thunk() + with cases + { a } -> a + { throw a -> _ } -> a + +abilities.Throw.unwrap!.doc : Doc +abilities.Throw.unwrap!.doc = + {{ + {unwrap!} takes a delayed computation that can either return __or__ throw a + value of type `a` and returns the `a` value that was either returned or + thrown. + + This can be used to achieve the same behavior as an early `return` statement + in an imperative language, using {throw} instead of `return`. For example, + consider the following {sumUntil} function. It will sum all of the numbers in + a {type List}, but if the sum reaches the provided maximum value, it will + stop iterating through the list and immediately return the maximum value. + + @source{sumUntil} + + ``` + sumUntil 10 (Nat.range 1 1000) + ``` + + ``` + sumUntil 10 [1, 2] + ``` + }} + +abilities.Throw.unwrap!.docs.sumUntil : Nat -> [Nat] -> Nat +abilities.Throw.unwrap!.docs.sumUntil max values = + go : Nat -> Nat ->{Throw Nat} Nat + go accumulator currentValue = + use Nat + >= + sum = accumulator + currentValue + if sum >= max then throw max else sum + unwrap! do List.foldLeft go 0 values + +abilities.Wait.doc : Doc +abilities.Wait.doc = + use Wait wait + {{ + The {type Wait} ability is an abstract interface for waiting for a specified + {type Duration}. The {wait} operation should block the current thread for + that duration, though the exact behavior is left up to the implementation. + + The Base library provides an implementation of the {type Wait} ability that + uses the {type IO} ability: {runWait}. You can use this implementation to run + computations that use the {type Wait} ability where the {type IO} ability is + available. + + The {type Wait} ability is useful for writing code that needs to wait or + sleep for some duration without having to depend on a specific implementation + of thread blocking. + + # Example + + @typecheck ``` + runWait do wait Duration.second + ``` + + # Example handler implementation + + The following is an example handler implementation for the {type Wait} + ability that simply increments a total wait time whenever the {wait} + operation is called. + + ``` + testWait : '{Wait} a -> (a, Duration) + testWait p = + use Duration + + h totalWait p = + handle p() + with cases + { wait d -> k } -> h (totalWait + d) k + { a } -> (a, totalWait) + h Duration.zero p + Tuple.second + Duration.toText (testWait do + wait (minutes +4) + wait (seconds +3) + wait (minutes +2) + wait (hours +1) + "Done") + ``` + }} + +abilities.Wait.runWait : (a ->{g, Wait} b) -> a ->{g, IO, Exception} b +abilities.Wait.runWait f a = + h : '{g, IO, Exception, Wait} b ->{g, IO, Exception} b + h x = + handle x() + with cases + { Wait.wait d -> k } -> h do k (sleep d) + { a } -> a + h do f a + +abilities.Wait.runWait.doc : Doc +abilities.Wait.runWait.doc = + use Wait wait + {{ + `` runWait f `` runs the computation `f` that uses the {type Wait} ability. + The {type Wait} ability provides one operation: {wait}. In this + implementation, the {wait} operation blocks the current thread for the + specified {type Duration}, by calling {sleep}. + + # See also + + * {type Wait} - The ability type. + * {sleep} - Blocks the current thread for the specified duration in the + {type IO} ability. + }} + +-- builtin Any.Any : a -> Any + +Any.doc : Doc +Any.doc = + {{ + The {type Any} type is an + [existential type](http://en.wikipedia.org/wiki/Existential_type), which + means that values of type {type Any} could be any type. It is used to + represent values of unknown type or where the type is not important. + + # Usage in the Base library + + The {type Any} type is used internally: + + * In the {type Doc} type. Documentation often needs to refer to Unison + code, where the type of the code is not important. For example, + {type Doc.Term} wraps a Unison expression of type {type Any}. + * In the {type Failure} type. The {type Failure} type is used with the + {type Exception} ability to allow the programmer to include arbitrary + data in a {type Failure}. + + # Construction + + The {type Any} type has one constructor. It can take a value of any type: + + @signature{Any} + + # Extraction + + The value of type {type Any} can be extracted in an unsafe way: + + @signature{unsafeExtract} + + This operation is __unsafe__ because it can fail at runtime if the value is + not of the expected type. It is up to you to ensure that the value is of + the expected type. + + # Example + + ``` + unsafeExtract (Any 42) + ``` + }} + +-- builtin Any.unsafeExtract : Any -> a + +Any.unsafeExtract.doc : Doc +Any.unsafeExtract.doc = + use Nat + + {{ + Unsafely cast an {type Any} to any value. Use with care! + + It's recommended that you provide a type annotation on the result of this + function, to make it clear what type you're casting to (and to produce a type + error if the surrounding context isn't expecting this type). + + ``` + (unsafeExtract (Any "yay!")) : Text + ``` + + This function won't give you a type error if you cast the value inside to the + wrong type, but you will get a runtime error if you try to use the value in a + way that doesn't match its type. For instance: + + ``` + unsafeExtract (Any "nay!") + 42 + ``` + }} + +(Boolean.!=) : Boolean -> Boolean -> Boolean +x Boolean.!= y = (x || y) && Boolean.not (x && y) + +(Boolean.<) : Boolean -> Boolean -> Boolean +a Boolean.< b = Boolean.and (Boolean.not a) b + +(Boolean.<=) : Boolean -> Boolean -> Boolean +a Boolean.<= b = Boolean.not a || b + +(Boolean.==) : Boolean -> Boolean -> Boolean +a Boolean.== b = + use Boolean != + Boolean.not (a != b) + +(Boolean.>) : Boolean -> Boolean -> Boolean +a Boolean.> b = Boolean.and a (Boolean.not b) + +(Boolean.>=) : Boolean -> Boolean -> Boolean +a Boolean.>= b = implies b a + +Boolean.and : Boolean -> Boolean -> Boolean +Boolean.and x y = x && y + +Boolean.and.doc : Doc +Boolean.and.doc = + {{ + {type Boolean} conjunction. Returns `` true `` only if both inputs are + ``true``, otherwise ``false``. Note that this function cannot short-circuit, + as it's strict in both arguments. For a short-circuiting version, use the + built-in syntax {{ docExample 2 do a b -> a && b }}. + + # Truth table + + {{ binaryTruthTable "and" Boolean.and }} + }} + +Boolean.doc : Doc +Boolean.doc = + use Boolean != and not or + {{ + The {type Boolean} type is built in to Unison. Expressions of type + {type Boolean} evaluate to either `` true `` or ``false``. This type can be + used to represent any choice between two possibilities (on/off, yes/no, + pass/fail). + + For example, the result of a comparison is `` true `` if the comparison + succeeds, and `` false `` if it fails: + + ``` + 1 Nat.< 2 + ``` + + ``` + 1 Nat.> 2 + ``` + + # Conditional statements + + A conditional expression has the form + {{ docExample 3 do c t f -> (if c then t else f) }}, where `c` is a + __condition__, an expression of type {type Boolean}, and `t` and `f` are + expressions of any type, but `t` and `f` must have the same type. If `c` + evaluates to ``true``, then the result of the whole expression is whatever + `t` evaluates to. If `c` evaluates to ``false``, then the result of the + whole expression is whatever `f` evaluates to. + + Evaluation of conditional expressions is __non-strict__. The evaluation + semantics of {{ docExample 3 do c t f -> (if c then t else f) }} are: + + * The condition `c` is always evaluated. + * If `c` evaluates to ``true``, the expression `t` is evaluated and `f` + remains unevaluated. The whole expression reduces to the value of `t`. + * If `c` evaluates to ``false``, the expression `f` is evaluated and `t` + remains unevaluated. The whole expression reduces to the value of `f` + + # Boolean conjunction and disjunction + + A __Boolean conjunction expression__ is a {type Boolean} expression of the + form {{ docExample 2 do a b -> a && b }} where `a` and `b` are + {type Boolean} expressions. Note that {{ docExample 2 do a b -> a && b }} + is not a function call, but built-in syntax. + + The evaluation semantics of {{ docExample 2 do a b -> a && b }} are + equivalent to {{ docExample 2 do a b -> (if a then b else false) }}. + + A Boolean disjunction expression is a Boolean expression of the form {{ + docExample 2 do a b -> a || b }} where `a` and `b` are Boolean expressions. + Note that {{ docExample 2 do a b -> a || b }} is not a function call, but + built-in syntax. + + The evaluation semantics of {{ docExample 2 do a b -> a || b }} are + equivalent to {{ docExample 2 do a b -> (if a then true else b) }}. + + # Boolean functions + + The base libraries provide a number of operations on {type Boolean} values: + + ## Negation (not) + + `` not a `` returns the opposite of `a`. If `a` is ``true``, then `` + not a `` is ``false``, and vice versa. + + ## Exclusive-or + + `` a != b `` is `` true `` if either `a` or `b` are ``true``, but not + both. Put another way, it's `` true `` only if `a` and `b` are not the + same. This is the complement of ``iff``. + + ## Not-and + + The complement of ``and``. `` nand a b `` is `` false `` ony if both `a` + and `b` are ``true``. + + ## Not-or + + The complement of ``or``. `` nor a b `` is `` true `` if neither `a` nor + `b` are ``true``. + + ## Equivalence + + The complement of ``(!=)``. `` iff a b `` is `` true `` if `a` and `b` + are the same. + + ## Implication + + `` implies a b `` is `` true `` unless `a` is `` true `` and `b` is + ``false``. This is the same as logical implication. + + `` given a b `` is `` true `` unless `a` is `` false `` and `b` is + ``true``. This is the converse of logical implication. + + ## Inhibition (not-implies) + + `` a Boolean.> b `` is `` true `` only if `a` is `` true `` but `b` is + ``false``. Equivalent to {{ docExample 2 do a b -> not (implies a b) }}. + + `` a Boolean.< b `` is `` true `` only if `a` is `` false `` but `b` is + ``true``. Equivalent to {{ docExample 3 do a b -> not (implies b a) }}. + + ## Conjunction (as a function) + + `` and a b `` is `` true `` only if both `a` and `b` are ``true``. It's + equivalent to {{ docExample 2 do a b -> a && b }}, except `` and `` is + an ordinary function (rather than built-in syntax), and doesn't + short-circuit. + + ## Disjunction (as a function) + + `` or a b `` is `` true `` if either `a` or `b` are ``true``. It's + equivalent to {{ docExample 2 do a b -> a || b }}, except `` or `` is an + ordinary function (rather than built-in syntax), and doesn't + short-circuit. + }} + +Boolean.docs.binaryTruthTable : + Text -> (Boolean ->{g} Boolean ->{g1} Boolean) ->{g, g1} Doc +Boolean.docs.binaryTruthTable name f = + use List ++ map + table = + List.flatMap (x -> map (y -> [x, y, f x y]) [false, true]) [false, true] + body = map (map (column -> {{ {{ docExample 0 (_ -> column) }} }})) table + {{ + {{ + docTable + ([[{{ `x` }}, {{ `y` }}, docCode {{ {{ (docWord name) }} x y }}]] ++ body) + }} + }} + +Boolean.docs.binaryTruthTable.doc : Doc +Boolean.docs.binaryTruthTable.doc = + use Boolean != + {{ + Generates a {type Doc} that shows the truth table for the given binary + Boolean function. The {type Text} argument is a name to show for the + function. + + # Examples + + ``` + binaryTruthTable "and" Boolean.and + ``` + + ``` + binaryTruthTable "or" Boolean.or + ``` + + ``` + binaryTruthTable "xor" (!=) + ``` + }} + +Boolean.docs.unaryTruthTable : Text -> (Boolean -> Boolean) -> Doc +Boolean.docs.unaryTruthTable name f = + {{ + {{ + docTable + [ [{{ `x` }}, docCode (docWord (name Text.++ " ") Doc.++ {{ x }})] + , [{{ `` false `` }}, {{ @eval{ f false } }}] + , [{{ `` true `` }}, {{ @eval{ f true } }}] + ] }} + }} + +Boolean.docs.unaryTruthTable.doc : Doc +Boolean.docs.unaryTruthTable.doc = + {{ + Generates a {type Doc} that shows the truth table for the given unary Boolean + function. The {type Text} argument is a name to show for the function. + + # Example + + ``` + unaryTruthTable "not" Boolean.not + ``` + }} + +Boolean.eq.doc : Doc +Boolean.eq.doc = + use Boolean not + {{ + {type Boolean} equivalence. Returns `` true `` only if both inputs are the + same, otherwise returns ``false``. Note that this function cannot + short-circuit, as it's strict in both arguments. For a short-circuiting + version, use the built-in syntax + {{ docExample 2 do a b -> a && b || not a && not b }}. + + # Truth table + + {{ binaryTruthTable "iff" Boolean.eq }} + }} + +Boolean.given.doc : Doc +Boolean.given.doc = + {{ + Inverse {type Boolean} implication. An expression `` given a b `` Returns `` + false `` only if `a` is `` false `` and `b` is ``true``, otherwise returns + ``true``. Note that this function cannot short-circuit, as it's strict in + both arguments. For a short-circuiting version, use the built-in syntax + {{ docExample 2 do a b -> a || Boolean.not b }}. + + # Truth table + + {{ binaryTruthTable "given" given }} + }} + +test> Boolean.given.test = + deprecated.run + (Test.tests + [ check' (given false false === true) + , check' (given false true === false) + , check' (given true false === true) + , check' (given true true === true) + ]) + +Boolean.gt.doc : Doc +Boolean.gt.doc = + use Boolean < + {{ + {type Boolean} inhibition. An expression `` Boolean.gt a b `` Returns `` true + `` only if `a` is `` true `` and `b` is ``false``, otherwise returns + ``false``. Note that this function cannot short-circuit, as it's strict in + both arguments. For a short-circuiting version, use the built-in syntax + {{ docExample 2 do a b -> a && Boolean.not b }}. + + # Truth table + + {{ binaryTruthTable "gt" (<) }} + }} + +Boolean.implies.doc : Doc +Boolean.implies.doc = + {{ + {type Boolean} implication. An expression `` implies a b `` Returns `` false + `` only if `a` is `` true `` and `b` is ``false``, otherwise returns + ``true``. Note that this function cannot short-circuit, as it's strict in + both arguments. For a short-circuiting version, use the built-in syntax + {{ docExample 2 do a b -> Boolean.not a || b }}. + + # Truth table + + {{ binaryTruthTable "implies" implies }} + }} + +test> Boolean.implies.test = + deprecated.run + (Test.tests + [ check' (implies false false === true) + , check' (implies false true === true) + , check' (implies true false === false) + , check' (implies true true === true) + ]) + +Boolean.lt.doc : Doc +Boolean.lt.doc = + use Boolean lt + {{ + {type Boolean} inhibition. An expression `` lt a b `` Returns `` true `` only + if `a` is `` false `` and `b` is ``true``, otherwise returns ``false``. Note + that this function cannot short-circuit, as it's strict in both arguments. + For a short-circuiting version, use the built-in syntax + {{ docExample 2 do a b -> Boolean.not a && b }}. + + # Truth table + + {{ binaryTruthTable "lt" lt }} + }} + +Boolean.nand : Boolean -> Boolean -> Boolean +Boolean.nand x y = Boolean.not (Boolean.and x y) + +Boolean.nand.doc : Doc +Boolean.nand.doc = + {{ + Negated {type Boolean} conjunction. Returns `` false `` only if both inputs + are ``true``, otherwise returns ``true``. Note that this function cannot + short-circuit, as it's strict in both arguments. For a short-circuiting + version, use the built-in syntax + {{ docExample 2 do a b -> Boolean.not (a && b) }}. + + # Truth table + + {{ binaryTruthTable "nand" nand }} + }} + +Boolean.nor : Boolean -> Boolean -> Boolean +Boolean.nor x y = Boolean.not (Boolean.or x y) + +Boolean.nor.doc : Doc +Boolean.nor.doc = + {{ + Negated {type Boolean} disjunction. Returns `` true `` only if both inputs + are ``false``, otherwise returns ``false``. Note that this function cannot + short-circuit, as it's strict in both arguments. For a short-circuiting + version, use the built-in syntax + {{ docExample 2 do a b -> Boolean.not (a || b) }}. + + # Truth table + + {{ binaryTruthTable "nor" nor }} + }} + +-- builtin Boolean.not : Boolean -> Boolean + +Boolean.not.doc : Doc +Boolean.not.doc = + {{ + {type Boolean} negation. Returns `` true `` if the input is ``false``, and + vice versa. + }} + +Boolean.or : Boolean -> Boolean -> Boolean +Boolean.or x y = x || y + +Boolean.or.doc : Doc +Boolean.or.doc = + {{ + {type Boolean} disjunction. Returns `` false `` only if both inputs are + ``false``, otherwise ``true``. Note that this function cannot short-circuit, + as it's strict in both arguments. For a short-circuiting version, use the + built-in syntax {{ docExample 2 do a b -> a || b }}. + + # Truth table + + {{ binaryTruthTable "or" Boolean.or }} + }} + +Boolean.toNat : Boolean -> Nat +Boolean.toNat b = if b then 1 else 0 + +Boolean.toNat.doc : Doc +Boolean.toNat.doc = + use Boolean toNat + {{ + Converts a {type Boolean} into either `` 0 `` (if it's ``false``) or `` 1 `` + (if it's ``true``). + + ``` + toNat true + ``` + + ``` + toNat false + ``` + }} + +Boolean.toText : Boolean -> Text +Boolean.toText = cases + true -> "true" + false -> "false" + +Boolean.toText.doc : Doc +Boolean.toText.doc = + use Boolean toText + {{ + Renders a {type Boolean} into {type Text}. + + # Examples + + ``` + toText true + ``` + + ``` + toText false + ``` + }} + +Boolean.until : (a ->{e} Boolean) -> '{e} a ->{e} a +Boolean.until pred op = + r = op() + if pred r then r else Boolean.until pred op + +Boolean.until.doc : Doc +Boolean.until.doc = + {{ + `` Boolean.until predicate op `` repeatedly runs `op`, returning the first + result for which the `predicate` is ``true``. + + Does not terminate if the `predicate` never becomes ``true``. + }} + +test> Boolean.until.test = + use Boolean until + use Store get + use withInitialValue handler + go n _ = + use Nat + + new = get + 1 + Store.put new + new === n + t3 = handle expect (until id (go 3) && get === 3) with handler 0 + t1 = handle expect (until id (go 1) && get === 1) with handler 0 + deprecated.run (Test.both t1 t3) + +Boolean.when : Boolean -> '{e} () ->{e} () +Boolean.when b x = if b then x() else () + +Boolean.when.doc : Doc +Boolean.when.doc = + {{ + `` when b x `` forces `x` and performs its effects if the {type Boolean} `b` + is ``true``, and otherwise does nothing. + + # Examples + + ``` + toOptional! do when true do abort + ``` + + ``` + toOptional! do when false do abort + ``` + }} + +Boolean.while : (a ->{e} Boolean) -> '{e} a ->{e} a +Boolean.while pred op = Boolean.until (Boolean.not << pred) op + +Boolean.while.doc : Doc +Boolean.while.doc = + {{ + `` while predicate op `` repeatedly runs `op`, returning the first result for + which the `predicate` is ``false``. + + Does not terminate if the `predicate` never becomes ``false``. + }} + +test> Boolean.while.test = + use Boolean not + use Store get + use withInitialValue handler + go n _ = + use Nat + + new = get + 1 + Store.put new + Universal.lt new n + t3 = handle expect (not (while id (go 3)) && get === 3) with handler 0 + t1 = handle expect (not (while id (go 1)) && get === 1) with handler 0 + deprecated.run (Test.both t1 t3) + +Boolean.xor.doc : Doc +Boolean.xor.doc = + {{ + {type Boolean} exclusive-or. Returns `` false `` only if the inputs are the + same, otherwise ``true``. + + # Truth table + + {{ binaryTruthTable "xor" Boolean.xor }} + }} + +test> Boolean.xor.test = + use Boolean xor + deprecated.run + (Test.tests + [ check' (xor false false === false) + , check' (xor false true === true) + , check' (xor true false === true) + , check' (xor true true === false) + ]) + +-- builtin bug : a -> b + +bug.doc : Doc +bug.doc = + {{ + The {bug} function forces a program to halt with a specific value. Often used + for runtime invariants. + + This can be any value: + + @typecheck ``` + bug "halt the program" + ``` + + @typecheck ``` + bug 42 + ``` + + **Example:** + + ``` ucm + >bug (Some ?👋) + ``` + + The value will be printed out as the program halts: + + ``` ucm + 💔💥 + + I've encountered a call to builtin.bug with the following value: + + Some ?👋 + + I'm sorry this message doesn't have more detail about the location of the failure. My makers + plan to fix this in a future release. 😢 + ``` + + [Learn more about bug](https://www.unison-lang.org/learn/fundamentals/control-flow/exception-handling/bug/) + }} + +bug.impossible : Request Exception a -> a +bug.impossible = cases + { x } -> x + { Exception.raise f -> _ } -> bug f + +bug.impossible.doc : Doc +bug.impossible.doc = {{ Handler for exceptions that shouldn't happen. }} + +-- builtin Bytes.++ : Bytes -> Bytes -> Bytes + +Bytes.++.doc : Doc +Bytes.++.doc = + use Bytes ++ + use fromList impl + {{ + Append two {type Bytes} values. + + # Examples + + ``` + 0xs ++ 0xs + ``` + + ``` + 0xs01 ++ 0xs02 + ``` + + ``` + 0xsfeed ++ 0xsface + ``` + }} + +(Bytes.==) : Bytes -> Bytes -> Boolean +(Bytes.==) = (===) + +-- builtin Bytes.at : Nat -> Bytes -> Optional Nat + +Bytes.at.doc : Doc +Bytes.at.doc = + use Bytes at + use Optional flatten + use fromList impl + {{ + Returns the byte at the given index in the {type Bytes} value, as a + {type Nat}. + + # Examples + + ``` + flatten (hush do at 0 0xs08090a) + ``` + + ``` + flatten (hush do at 1 0xs08090a) + ``` + + ``` + flatten (hush do at 3 0xs08090a) + ``` + }} + +Bytes.at! : Nat -> Bytes ->{Abort} Nat +Bytes.at! n bs = Optional.toAbort (Bytes.at n bs) + +Bytes.at!.doc : Doc +Bytes.at!.doc = + use Bytes at! + {{ + `` at! i bs `` returns the `i`-th (0-based) byte in `bs` and calls {abort} if + the index is out of bounds. + + # Examples + + ``` + toOptional! do at! 9 Bytes.empty + ``` + + ``` + toOptional! do at! 1 0xs1122334455 + ``` + }} + +Bytes.base32Hex : Bytes -> Text +Bytes.base32Hex = + use Text ++ + go acc bs = + match grab bs with + None -> acc + Some (Double m n pad bs) -> + go (acc ++ encodeChunk m 0 ++ encodeChunk n pad) bs + Some (Single n pad bs) -> go (acc ++ encodeChunk n pad) bs + go "" + +Bytes.base32Hex.b32h : Nat -> Char +Bytes.base32Hex.b32h n = + use Nat + < + use fromNat impl + if n < 10 then impl (n + 48) else impl (n + 87) + +Bytes.base32Hex.b32h.doc : Doc +Bytes.base32Hex.b32h.doc = + {{ Translates 5 bits to is base 32 hex ASCII encoding. }} + +Bytes.base32Hex.doc : Doc +Bytes.base32Hex.doc = + {{ Encodes a bytes value as the textual base 32 hex encoding. }} + +Bytes.base32Hex.encodeChunk : Nat -> Nat -> Text +Bytes.base32Hex.encodeChunk n pad = + use List +: + use Nat - + use Text ++ + go acc m = cases + 0 -> fromCharList acc + i -> + c = b32h (Nat.and m 31) + n = Nat.shiftRight m 5 + go (c +: acc) n (i - 1) + go [] n (8 - pad) ++ Text.repeat pad "=" + +Bytes.base32Hex.encodeChunk.doc : Doc +Bytes.base32Hex.encodeChunk.doc = + {{ + Encodes a single {type Nat} of up to 40 bits as base 32 hex. The second + argument specifies how much padding should be used instead of bits from the + first argument. + }} + +Bytes.base32Hex.grab : Bytes -> Optional Hex32Piece +Bytes.base32Hex.grab bs = match decodeNat64be bs with + Some (n, bs) -> + use Nat or shiftLeft + m = Nat.shiftRight n 24 + k = Nat.and n 16777215 + match decodeNat16be bs with + Some (n, bs) -> Some <| Double m (or (shiftLeft k 16) n) 0 bs + None -> + match Bytes.at 0 bs with + Some n -> + r = shiftLeft (or n (shiftLeft k 8)) 3 + Some <| Double m r 1 (Bytes.drop 1 bs) + None -> Some (Double m (shiftLeft k 1) 3 bs) + None -> grab1 bs + +Bytes.base32Hex.grab.doc : Doc +Bytes.base32Hex.grab.doc = + {{ Extracts the next portion of a {type Bytes} for encoding. }} + +Bytes.base32Hex.grab1 : Bytes -> Optional Hex32Piece +Bytes.base32Hex.grab1 bs = + match decodeNat32be bs with + Some (n, bs) -> + match Bytes.at 0 bs with + None -> Some <| Single (Nat.shiftLeft n 3) 1 bs + Some k -> + Some <| Single (Nat.or k (Nat.shiftLeft n 8)) 0 (Bytes.drop 1 bs) + None -> + match decodeNat16be bs with + Some (n, bs) -> + match Bytes.at 0 bs with + None -> Some <| Single (Nat.shiftLeft n 4) 4 bs + Some k -> + use Nat shiftLeft + m = shiftLeft (Nat.or k (shiftLeft n 8)) 1 + Some <| Single m 3 (Bytes.drop 1 bs) + None -> + match Bytes.at 0 bs with + Some n -> Some <| Single (Nat.shiftLeft n 2) 6 (Bytes.drop 1 bs) + None -> None + +Bytes.base32Hex.grab1.doc : Doc +Bytes.base32Hex.grab1.doc = + {{ Extracts up to 40 bits of a {type Bytes} for encoding. }} + +Bytes.constantTimeEqual : Bytes -> Bytes -> Boolean +Bytes.constantTimeEqual b1 b2 = + use Bytes at! size + use Nat != == >= + use Optional toAbort + if size b1 != size b2 then false + else + go acc b1 b2 = match size b1 with + n + | n >= 8 -> + match decodeNat64be b1 |> toAbort with + (hd1, b1) -> + match decodeNat64be b2 |> toAbort with + (hd2, b2) -> + hdEq = hd1 == hd2 + go (hdEq && acc) b1 b2 + | n >= 4 -> + match decodeNat32be b1 |> toAbort with + (hd1, b1) -> + match decodeNat32be b2 |> toAbort with + (hd2, b2) -> + hdEq = hd1 == hd2 + go (hdEq && acc) b1 b2 + 0 -> acc + 1 -> + hdEq = at! 0 b1 == at! 0 b2 + hdEq && acc + n -> + match decodeNat16be b1 |> toAbort with + (hd1, b1) -> + match decodeNat16be b2 |> toAbort with + (hd2, b2) -> + hdEq = hd1 == hd2 + go (hdEq && acc) b1 b2 + toDefault! (do false) do go true b1 b2 + +Bytes.constantTimeEqual.doc : Doc +Bytes.constantTimeEqual.doc = + use fromList impl + {{ + Like {===} but examines every byte of both inputs even if an earlier byte + doesn't match. Used to avoid timing attacks when (say) verifying an {hmac}. + + ``` + constantTimeEqual 0xsaabbccdd 0xsaabbccdd + ``` + + Note: if the two inputs aren't of the same {Bytes.size}, returns `false` + immediately. + }} + +test> Bytes.constantTimeEqual.tests = test.verify do + use Bytes ++ + Each.repeat 100 + n = Random.natIn 10 50 + b1 = Random.bytes n + ensureEqual b1 b1 + ensure (constantTimeEqual b1 b1) + ensure (Boolean.not (constantTimeEqual b1 (Bytes.drop 1 b1 ++ 0xs00))) + +-- builtin Bytes.decodeNat16be : Bytes -> Optional (Nat, Bytes) + +Bytes.decodeNat16be.doc : Doc +Bytes.decodeNat16be.doc = + use fromList impl + {{ + Decodes a {type Nat} from the first two bytes of a {type Bytes} value in + big-endian order. Returns a pair containing the decoded {type Nat} and the + remaining {type Bytes} value, or {None} if the {type Bytes} value is shorter + than two bytes. The returned {type Nat} value is in the range `` 0 `` to + ``65535``. + + # Examples + + ``` + decodeNat16be 0xs0102 + ``` + + ``` + decodeNat16be 0xs0102030405060708 + ``` + + ``` + decodeNat16be 0xs + ``` + + ``` + decodeNat16be 0xs01 + ``` + + # See also + + * {decodeNat16le} + * {decodeNat32be} + * {decodeNat32le} + * {decodeNat64be} + * {decodeNat64le} + }} + +-- builtin Bytes.decodeNat16le : Bytes -> Optional (Nat, Bytes) + +Bytes.decodeNat16le.doc : Doc +Bytes.decodeNat16le.doc = + use fromList impl + {{ + Decodes a {type Nat} from the first two bytes of a {type Bytes} value in + little-endian order. Returns a pair containing the decoded {type Nat} and the + remaining {type Bytes} value, or {None} if the {type Bytes} value is shorter + than two bytes. The returned {type Nat} value is in the range `` 0 `` to + ``65535``. + + # Examples + + ``` + decodeNat16le 0xs0102 + ``` + + ``` + decodeNat16le 0xs0102030405060708 + ``` + + ``` + decodeNat16le 0xs + ``` + + ``` + decodeNat16le 0xs01 + ``` + + # See also + + * {decodeNat16be} + * {decodeNat32be} + * {decodeNat32le} + * {decodeNat64be} + * {decodeNat64le} + }} + +Bytes.decodeNat16sbe : Nat -> Bytes -> ([Nat], Bytes) +Bytes.decodeNat16sbe n bs = + use List :+ + use Nat - == + go n bs acc = + if n == 0 then (acc, bs) + else + match decodeNat16be bs with + None -> (acc, bs) + Some (w, rest) -> go (n - 1) rest (acc :+ w) + go n bs [] + +Bytes.decodeNat16sbe.doc : Doc +Bytes.decodeNat16sbe.doc = + use fromList impl + {{ + Decodes **at most** a given number of 16-bit unsigned integers in big-endian + order from the given {type Bytes} and returns them as a list of {type Nat} + values together with the rest of the {type Bytes}. + + If the {type Bytes} value doesn't contain enough bytes to decode the given + number of integers, the returned list will contain fewer integers than + requested. + + # Examples + + ``` + decodeNat16sbe 2 0xs01020304 + ``` + + ``` + decodeNat16sbe 2 0xs010203 + ``` + }} + +-- builtin Bytes.decodeNat32be : Bytes -> Optional (Nat, Bytes) + +Bytes.decodeNat32be.doc : Doc +Bytes.decodeNat32be.doc = + use fromList impl + {{ + Decodes a {type Nat} from the first four bytes of a {type Bytes} value in + big-endian order. Returns a pair containing the decoded {type Nat} and the + remaining {type Bytes} value, or {None} if the {type Bytes} value is shorter + than four bytes. The returned {type Nat} value is in the range `` 0 `` to + ``4294967295``. + + # Examples + + ``` + decodeNat32be 0xs01020304 + ``` + + ``` + decodeNat32be 0xs0102030405060708 + ``` + + ``` + decodeNat32be 0xs + ``` + + ``` + decodeNat32be 0xs01 + ``` + + # See also + + * {decodeNat16be} + * {decodeNat16le} + * {decodeNat32le} + * {decodeNat64be} + * {decodeNat64le} + }} + +-- builtin Bytes.decodeNat32le : Bytes -> Optional (Nat, Bytes) + +Bytes.decodeNat32le.doc : Doc +Bytes.decodeNat32le.doc = + use fromList impl + {{ + Decodes a {type Nat} from the first four bytes of a {type Bytes} value in + little-endian order. Returns a pair containing the decoded {type Nat} and the + remaining {type Bytes} value, or {None} if the {type Bytes} value is shorter + than four bytes. The returned {type Nat} value is in the range `` 0 `` to + ``4294967295``. + + # Examples + + ``` + decodeNat32le 0xs01020304 + ``` + + ``` + decodeNat32le 0xs0102030405060708 + ``` + + ``` + decodeNat32le 0xs + ``` + + ``` + decodeNat32le 0xs01 + ``` + + # See also + + * {decodeNat16be} + * {decodeNat16le} + * {decodeNat32be} + * {decodeNat64be} + * {decodeNat64le} + }} + +Bytes.decodeNat32sbe : Nat -> Bytes -> ([Nat], Bytes) +Bytes.decodeNat32sbe n bs = + use List :+ + use Nat - == + go n bs acc = + if n == 0 then (acc, bs) + else + match decodeNat32be bs with + None -> (acc, bs) + Some (w, rest) -> go (n - 1) rest (acc :+ w) + go n bs [] + +Bytes.decodeNat32sbe.doc : Doc +Bytes.decodeNat32sbe.doc = + use fromList impl + {{ + Decodes **at most** a given number of 32-bit unsigned integers in big-endian + order from the given {type Bytes} and returns them as a list of {type Nat} + values together with the rest of the {type Bytes}. + + If the {type Bytes} value doesn't contain enough bytes to decode the given + number of integers, the returned list will contain fewer integers than + requested. + + # Examples + + ``` + decodeNat32sbe 2 0xs0102030405060708 + ``` + + ``` + decodeNat32sbe 2 0xs010203040506 + ``` + }} + +-- builtin Bytes.decodeNat64be : Bytes -> Optional (Nat, Bytes) + +Bytes.decodeNat64be.doc : Doc +Bytes.decodeNat64be.doc = + use fromList impl + {{ + Decodes a {type Nat} from the first eight bytes of a {type Bytes} value in + big-endian order. Returns a pair containing the decoded {type Nat} and the + remaining {type Bytes} value, or {None} if the {type Bytes} value is shorter + than eight bytes. The returned {type Nat} value is in the range `` 0 `` to + ``18446744073709551615``. + + # Examples + + ``` + decodeNat64be 0xs0102030405060708 + ``` + + ``` + decodeNat64be 0xs0102030405060708090a0b0c0d0e0f + ``` + + ``` + decodeNat64be 0xs + ``` + + ``` + decodeNat64be 0xs01 + ``` + + # See also + + * {decodeNat16be} + * {decodeNat16le} + * {decodeNat32be} + * {decodeNat32le} + * {decodeNat64le} + }} + +-- builtin Bytes.decodeNat64le : Bytes -> Optional (Nat, Bytes) + +Bytes.decodeNat64le.doc : Doc +Bytes.decodeNat64le.doc = + use fromList impl + {{ + Decodes a {type Nat} from the first eight bytes of a {type Bytes} value in + little-endian order. Returns a pair containing the decoded {type Nat} and the + remaining {type Bytes} value, or {None} if the {type Bytes} value is shorter + than eight bytes. The returned {type Nat} value is in the range `` 0 `` to + ``18446744073709551615``. + + # Examples + + ``` + decodeNat64le 0xs0102030405060708 + ``` + + ``` + decodeNat64le 0xs0102030405060708090a0b0c0d0e0f + ``` + + ``` + decodeNat64le 0xs + ``` + + ``` + decodeNat64le 0xs01 + ``` + + # See also + + * {decodeNat16be} + * {decodeNat16le} + * {decodeNat32be} + * {decodeNat32le} + * {decodeNat64be} + }} + +Bytes.decodeNat64sbe : Nat -> Bytes -> ([Nat], Bytes) +Bytes.decodeNat64sbe n bs = + use List :+ + use Nat - == + go n bs acc = + if n == 0 then (acc, bs) + else + match decodeNat64be bs with + None -> (acc, bs) + Some (w, rest) -> go (n - 1) rest (acc :+ w) + go n bs [] + +Bytes.decodeNat64sbe.doc : Doc +Bytes.decodeNat64sbe.doc = + use fromList impl + {{ + Decodes **at most** a given number of 64-bit unsigned integers in big-endian + order from the given {type Bytes} and returns them as a list of {type Nat} + values together with the rest of the {type Bytes}. + + If the {type Bytes} value doesn't contain enough bytes to decode the given + number of integers, the returned list will contain fewer integers than + requested. + + # Examples + + ``` + decodeNat64sbe 2 0xs0102030405060708090a0b0c0d0e0f10 + ``` + + ``` + decodeNat64sbe 2 0xs0102030405060708090a0b0c0d0e + ``` + }} + +Bytes.doc : Doc +Bytes.doc = + use Bytes ++ + use Text toUtf8 + use fromList impl + {{ + {type Bytes} is a type of in-memory data represented as strings of bytes. + + # Constructing {type Bytes} + + A **byte literal** is an even number of hexadecimal digits preceded by + `0xs`, such as ``0xsdeadbeef``. + + The empty {type Bytes} is `` 0xs `` or {Bytes.empty}. + + Convert a hexadecimal {type Text} value to {type Bytes}: + + ``` + catch do Bytes.fromHex "deadbeef" + ``` + + Convert a {type Text} value to {type Bytes} using UTF-8 encoding: + + ``` + toUtf8 "Hello, world!" + ``` + + Convert a {type List} of {type Nat} values to {type Bytes}. Raises an + {type Exception} if any of the {type Nat} values are greater than 255: + + ``` + catch do Bytes.fromList [222, 173, 190, 175] + ``` + + # Converting {type Bytes} to other types + + Convert {type Bytes} to a {type Text} value using UTF-8 encoding: + + ``` + catch do fromUtf8 0xsf09f8c88e2ad90 + ``` + + Convert {type Bytes} to a {type List} of {type Nat} values: + + ``` + Bytes.toList 0xsf09f8c88e2ad90 + ``` + + Convert {type Bytes} to a {type Text} value using hexadecimal encoding: + + ``` + Bytes.toHex 0xsf09f8c88e2ad90 + ``` + + # Manipulating {type Bytes} + + Append two {type Bytes} values: + + ``` + 0xsf09f8c88 ++ 0xse2ad90 + ``` + + Take a prefix of a {type Bytes} value: + + ``` + Bytes.take 3 0xsf09f8c88e2ad90 + ``` + + Drop a prefix of a {type Bytes} value: + + ``` + Bytes.drop 3 0xsf09f8c88e2ad90 + ``` + + # Bitwise operations + + Truncate a {type Bytes} value to a given number of bits: + + ``` + truncateLeft 12 0xsf09f8c88e2ad90 + ``` + + ``` + truncateRight 12 0xsf09f8c88e2ad90 + ``` + + Shift a {type Bytes} value to the **left** by a number of bits: + + ``` + Bytes.shiftLeft 4 0xsf09f8c88e2ad90 + ``` + + Shift a {type Bytes} value to the **right** by a number of bits: + + ``` + Bytes.shiftRight 4 0xsf09f8c88e2ad90 + ``` + + # Querying and indexing + + Get the byte at a given index: + + ``` + Bytes.at 3 0xsf09f8c88e2ad90 + ``` + + Get the length of a {type Bytes} value: + + ``` + Bytes.size 0xsf09f8c88e2ad90 + ``` + + Check if a {type Bytes} value is empty: + + ``` + Bytes.isEmpty 0xsf09f8c88e2ad90 + ``` + + Find a substring of {type Bytes} in a {type Bytes} value: + + ``` + Bytes.indexOf 0xsbeef 0xscafebeef + ``` + + # Basic binary encoding and decoding + + Encode a {type Nat} value as {type Bytes} in big-endian order: + + ``` + encodeNat64be 2301017106344378350 + ``` + + And in little-endian order: + + ``` + encodeNat64le 2301017106344378350 + ``` + + Decode just the first 32 bits of a {type Bytes} value as a {type Nat} in + big-endian order: + + ``` + decodeNat32be 0xs1feedaddedc0ffee + ``` + + And in little-endian order: + + ``` + decodeNat32le 0xs1feedaddedc0ffee + ``` + + There are also {decodeNat16be} and {decodeNat16le} for the first 16 bits. + + # Serializing and deserializing values + + Any Unison value can be turned into {type Bytes} via the {type Value} type: + + ``` + Value.serialize (value ["a", "b", "c"]) + ``` + + If you know the type of a previously serialized {type Value}, you can + deserialize it into a Unison value: + + @typecheck ``` + catch do + bytes = 0xs00000003030103030001610300016203000163 + v = Value.deserialize bytes + load v + ``` + + # Encodings, hashing, and compression + + Functions that encode data into various formats usually operate on + {type Bytes}. + + Encode a {type Bytes} value as + [Base64](https://en.wikipedia.org/wiki/Base64): + + ``` + toBase64 0xsf09f8c88e2ad90 + ``` + + Decode a [Base64](https://en.wikipedia.org/wiki/Base64)-encoded value to + {type Bytes}: + + ``` + catch do fromBase64 (toUtf8 "8J+YgQ==") + ``` + + Encode a {type Bytes} value as + [Base32Hex](https://tools.ietf.org/html/rfc4648#section-6): + + ``` + catch do fromUtf8 (toBase32Hex 0xsf09f8c88e2ad90) + ``` + + Decode a [Base32Hex](https://tools.ietf.org/html/rfc4648#section-6)-encoded + value to {type Bytes}: + + ``` + fromBase32Hex (toUtf8 "f09f8c88e2ad90") + ``` + + Compute the SHA2-256 hash of a {type Bytes} value: + + ``` + hash Sha2_256 0xsf09f8c88e2ad90 + ``` + + Compress a {type Bytes} value using the + [gzip](https://www.ietf.org/rfc/rfc1952.txt) format: + + ``` + gzip.compress 0xsdeadbeef + ``` + + Decompress a {type Bytes} value compressed with + [gzip](https://www.ietf.org/rfc/rfc1952.txt): + + ``` + catch do + gzip.decompress 0xs1f8b0800000000000013bbb776df7b005aa39c7c04000000 + ``` + }} + +-- builtin Bytes.drop : Nat -> Bytes -> Bytes + +Bytes.drop.doc : Doc +Bytes.drop.doc = + use Bytes drop + use fromList impl + {{ + `` drop n b `` returns the {type Bytes} `b` with the first `n` bytes removed. + + If `n` is greater than or equal to the length of `b`, the result is + {Bytes.empty}. + + # Examples + + ``` + drop 0 0xsfeedface + ``` + + ``` + drop 1 0xsfeedface + ``` + + ``` + drop 10 0xsfeedface + ``` + }} + +-- builtin Bytes.empty : Bytes + +Bytes.empty.doc : Doc +Bytes.empty.doc = {{ The empty {type Bytes} value, ``0xs``. }} + +-- builtin Bytes.encodeNat16be : Nat -> Bytes + +Bytes.encodeNat16be.doc : Doc +Bytes.encodeNat16be.doc = + {{ + Encodes the lower-order 16 bits of a {type Nat} value to {type Bytes} in + big-endian order. + + # Examples + + ``` + encodeNat16be 258 + ``` + + ``` + encodeNat16be 16 + ``` + + ``` + encodeNat16be 16777216 + ``` + + # See also + + * {encodeNat16le} + * {encodeNat32be} + * {encodeNat32le} + * {encodeNat64be} + * {encodeNat64le} + }} + +-- builtin Bytes.encodeNat16le : Nat -> Bytes + +Bytes.encodeNat16le.doc : Doc +Bytes.encodeNat16le.doc = + {{ + Encodes the lower-order 16 bits of a {type Nat} value to {type Bytes} in + little-endian order. + + # Examples + + ``` + encodeNat16le 258 + ``` + + ``` + encodeNat16le 16 + ``` + + ``` + encodeNat16le 16777216 + ``` + + # See also + + * {encodeNat16be} + * {encodeNat32be} + * {encodeNat32le} + * {encodeNat64be} + * {encodeNat64le} + }} + +-- builtin Bytes.encodeNat32be : Nat -> Bytes + +Bytes.encodeNat32be.doc : Doc +Bytes.encodeNat32be.doc = + {{ + Encodes the lower-order 32 bits of a {type Nat} value to {type Bytes} in + big-endian order. + + # Examples + + ``` + encodeNat32be 16909060 + ``` + + ``` + encodeNat32be 16 + ``` + + ``` + encodeNat32be 72623859790382856 + ``` + + # See also + + * {encodeNat16be} + * {encodeNat16le} + * {encodeNat32le} + * {encodeNat64be} + * {encodeNat64le} + }} + +-- builtin Bytes.encodeNat32le : Nat -> Bytes + +Bytes.encodeNat32le.doc : Doc +Bytes.encodeNat32le.doc = + {{ + Encodes the lower-order 32 bits of a {type Nat} value to {type Bytes} in + little-endian order. + + # Examples + + ``` + encodeNat32le 16909060 + ``` + + ``` + encodeNat32le 16 + ``` + + ``` + encodeNat32le 72623859790382856 + ``` + + # See also + + * {encodeNat16be} + * {encodeNat16le} + * {encodeNat32be} + * {encodeNat64be} + * {encodeNat64le} + }} + +-- builtin Bytes.encodeNat64be : Nat -> Bytes + +Bytes.encodeNat64be.doc : Doc +Bytes.encodeNat64be.doc = + {{ + Encodes the 64 bits of a {type Nat} value to {type Bytes} in big-endian + order. + + # Examples + + ``` + encodeNat64be 72623859790382856 + ``` + + ``` + encodeNat64be 16 + ``` + + ``` + encodeNat64be 579005069656919567 + ``` + + # See also + + * {encodeNat16be} + * {encodeNat16le} + * {encodeNat32be} + * {encodeNat32le} + * {encodeNat64le} + }} + +-- builtin Bytes.encodeNat64le : Nat -> Bytes + +Bytes.encodeNat64le.doc : Doc +Bytes.encodeNat64le.doc = + {{ + Encodes the 64 bits of a {type Nat} value to {type Bytes} in little-endian + order. + + # Examples + + ``` + encodeNat64le 72623859790382856 + ``` + + ``` + encodeNat64le 16 + ``` + + ``` + encodeNat64le 579005069656919567 + ``` + + # See also + + * {encodeNat16be} + * {encodeNat16le} + * {encodeNat32be} + * {encodeNat32le} + * {encodeNat64be} + }} + +-- builtin Bytes.flatten : Bytes -> Bytes + +Bytes.flatten.doc : Doc +Bytes.flatten.doc = + {{ + Internally, {type Bytes} values are represented as a + [rope](https://en.wikipedia.org/wiki/Rope_%28data_structure%29) of byte + arrays. {Bytes.flatten} flattens a {type Bytes} value into a single + contiguous byte array by copying the bytes into a new array. This conserves + memory and improves performance for later operations on the {type Bytes} + value, at the cost of a potentially expensive copy operation. + }} + +Bytes.fromBase16 : Bytes ->{Exception} Bytes +Bytes.fromBase16 v = raiseMessage v (fromBase16.impl v) + +Bytes.fromBase16.doc : Doc +Bytes.fromBase16.doc = + {{ + Decodes a [hexadecimal-encoded](https://en.wikipedia.org/wiki/Hexadecimal) + {type Bytes} value into the {type Bytes} it encodes. + + # Example + + ``` + catch do fromBase16 (Text.toUtf8 "48656C6C6F20576F726C6421") + ``` + }} + +-- builtin Bytes.fromBase16.impl : Bytes -> Either Text Bytes + +Bytes.fromBase16.impl.doc : Doc +Bytes.fromBase16.impl.doc = + use Text toUtf8 + use fromBase16 impl + {{ + Transforms a {type Bytes} from a + [hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal) string in the UTF-8 + encoding to the {type Bytes} those characters represent. Returns {Left} with + an error message {type Text} if the input is not a valid hexadecimal string + or is not an even number of characters long. + + # Example + + ``` + impl (toUtf8 "68656c6c6f20776f726c64") + ``` + + ``` + impl (toUtf8 "Hello, World!") + ``` + + ``` + impl (toUtf8 "Hello, World") + ``` + }} + +Bytes.fromBase32 : Bytes ->{Exception} Bytes +Bytes.fromBase32 v = raiseMessage v (fromBase32.impl v) + +Bytes.fromBase32.doc : Doc +Bytes.fromBase32.doc = + {{ + Decodes a [base32-encoded](https://en.wikipedia.org/wiki/Base32) {type Bytes} + value into the {type Bytes} it encodes. + + # Example + + ``` + catch do fromBase32 (Text.toUtf8 "JBSWY3DPEBLW64TMMQQQ====") + ``` + }} + +-- builtin Bytes.fromBase32.impl : Bytes -> Either Text Bytes + +Bytes.fromBase32.impl.doc : Doc +Bytes.fromBase32.impl.doc = + use Text toUtf8 + use fromBase32 impl + {{ + Transforms {type Bytes} from their representation in the + [RFC 4648 base32 alphabet](https://en.wikipedia.org/wiki/Base32#RFC_4648_Base32_alphabet) + to the {type Bytes} those characters represent. Returns {Left} with an error + message {type Text} if the input is not a valid base32 string or is not a + multiple of 8 characters long. + + # Example + + ``` + catch do + fromUtf8 (raiseMessage () (impl (toUtf8 "JBSWY3DPEBLW64TMMQ======"))) + ``` + + ``` + impl (toUtf8 "Hello, World!") + ``` + + ``` + impl (toUtf8 "Goodbye!") + ``` + }} + +Bytes.fromBase32Hex : Bytes -> Either Text Bytes +Bytes.fromBase32Hex bs = + use Bytes ++ flatten + use Nat + - == + go bcc acc i j = match Bytes.at i bs with + None -> Right <| flatten (bcc ++ finalize acc j) + Some c + | c == 61 -> Right <| flatten (bcc ++ finalize acc j) + | otherwise -> + match h32b c with + Left err -> Left err + Right n -> + match fromBase32Hex.push acc n j with + Left acc -> go bcc acc (i + 1) (j + 5) + Right (bs, acc) -> go (bcc ++ bs) acc (i + 1) (j + 5 - 64) + go Bytes.empty 0 0 0 + +Bytes.fromBase32Hex.alignment : Nat -> Nat +Bytes.fromBase32Hex.alignment = clearBit 0 << clearBit 1 << clearBit 2 + +Bytes.fromBase32Hex.alignment.doc : Doc +Bytes.fromBase32Hex.alignment.doc = + {{ Rounds an encoded bits number down to an even number of bytes. }} + +Bytes.fromBase32Hex.doc : Doc +Bytes.fromBase32Hex.doc = + {{ + Decodes a base 32 hex encoded byte string given a valid ASCII byte string as + input. + + Note: this assumes that the input represents a {type Bytes} value, where + there is an integral number of bytes. Base 32 hex strings represent multiples + of 5 bits, so for strings that do not represent a multiple of 40 bits, some + information will be discarded under the assumption that it is just padding. + }} + +Bytes.fromBase32Hex.finalize : Nat -> Nat -> Bytes +Bytes.fromBase32Hex.finalize acc j = + use Nat - + acc' = Nat.shiftLeft acc (64 - j) + ali = Nat.shiftRight j 3 + Bytes.take ali (encodeNat64be acc') + +Bytes.fromBase32Hex.finalize.doc : Doc +Bytes.fromBase32Hex.finalize.doc = + {{ + Gets a final byte string from a {type Nat} accumulator and a count of its + stored bits. + }} + +Bytes.fromBase32Hex.h32b : Nat -> Either Text Nat +Bytes.fromBase32Hex.h32b = cases + n + | 48 Nat.<= n && n Nat.< 58 -> Right (n Nat.- 48) + | 65 Nat.<= n && n Nat.< 87 -> Right (n Nat.- 55) + | 97 Nat.<= n && n Nat.< 119 -> Right (n Nat.- 87) + | otherwise -> + Left ("h32b: unrecognized byte: " Text.++ Nat.toText n) + +Bytes.fromBase32Hex.h32b.doc : Doc +Bytes.fromBase32Hex.h32b.doc = + {{ Translates a base 32 hex digit back to its 5-bit meaning. }} + +Bytes.fromBase32Hex.push : Nat -> Nat -> Nat -> Either Nat (Bytes, Nat) +Bytes.fromBase32Hex.push acc new j = + use Nat - < or shiftLeft + if j < 60 then Left <| or (shiftLeft acc 5) new + else + k = 64 - j + k' = 5 - k + out = or (shiftLeft acc k) (Nat.shiftRight new k') + mask = setBit k' 0 - 1 + Right (encodeNat64be out, Nat.and new mask) + +Bytes.fromBase32Hex.push.doc : Doc +Bytes.fromBase32Hex.push.doc = + {{ + Adds bits to a {type Nat} accumulator for decoding base 32 hex. + + * The first argument is the accumulator. + * The second argument is the 5 new bytes. + * The third argument is the number of bits already in the accumulator. + + The result is the new accumulator, with a byte string if the new bytes won't + fit in the natural. + }} + +test> Bytes.fromBase32Hex.tests.inverse = test.verify do + Each.repeat 100 + n = Random.natIn 0 10 + bs = Random.bytes n + err = test.raiseFailure "fromBase32Hex failed" + ensure <| Either.fold err ((===) bs) (fromBase32Hex (toBase32Hex bs)) + +Bytes.fromBase64 : Bytes ->{Exception} Bytes +Bytes.fromBase64 v = raiseMessage v (fromBase64.impl v) + +Bytes.fromBase64.doc : Doc +Bytes.fromBase64.doc = + {{ + Decodes a [base64-encoded](https://en.wikipedia.org/wiki/Base64) {type Bytes} + value into the {type Bytes} it encodes. + + # Example + + ``` + catch do fromBase64 (Text.toUtf8 "SGVsbG8gV29ybGQh") + ``` + }} + +-- builtin Bytes.fromBase64.impl : Bytes -> Either Text Bytes + +Bytes.fromBase64.impl.doc : Doc +Bytes.fromBase64.impl.doc = + use Text toUtf8 + use fromBase64 impl + {{ + Transforms a {type Bytes} from a + [base64](https://en.wikipedia.org/wiki/Base64) string in the UTF-8 encoding + to the {type Bytes} those characters represent. Returns {Left} with an error + message {type Text} if the input is not a valid base64 string or is not a + multiple of 4 characters long. + + # Example + + ``` + catch do fromUtf8 (raiseMessage () (impl (toUtf8 "aGVsbG8gd29ybGQ="))) + ``` + + ``` + impl (toUtf8 "Hello, World!") + ``` + + ``` + impl (toUtf8 "Goodbye!") + ``` + }} + +-- builtin Bytes.fromBase64UrlUnpadded : Bytes -> Either Text Bytes + +Bytes.fromBase64UrlUnpadded.doc : Doc +Bytes.fromBase64UrlUnpadded.doc = + use Text toUtf8 + {{ + Decodes [base64url](https://tools.ietf.org/html/rfc4648#section-5)-encoded + {type Text} into {type Bytes}. The input must be a multiple of 4 bytes long, + and must not contain any padding characters. Returns {Left} with an error + message if the input is invalid, or {Right} with the decoded bytes otherwise. + + # Examples + + ``` + catch do + fromUtf8 + (raiseMessage () (fromBase64UrlUnpadded (toUtf8 "aGVsbG8gd29ybGQ"))) + ``` + + ``` + fromBase64UrlUnpadded (toUtf8 "aGVsbG8gd29ybGQ=") + ``` + }} + +Bytes.fromHex : Text ->{Exception} Bytes +Bytes.fromHex = fromBase16 << Text.toUtf8 + +Bytes.fromHex.doc : Doc +Bytes.fromHex.doc = + {{ + Decodes a hexadecimal string into a byte array. + + # Example + + ``` + catch do Bytes.fromHex "deadbeef" + ``` + }} + +Bytes.fromHex.impl : Text -> Either Text Bytes +Bytes.fromHex.impl = fromBase16.impl << Text.toUtf8 + +Bytes.fromList : [Nat] ->{Exception} Bytes +Bytes.fromList nats = + if List.all (Nat.inRange 0 256) nats then fromList.impl nats + else Exception.raise (failure "Bytes.fromList: value out of range" nats) + +Bytes.fromList.doc : Doc +Bytes.fromList.doc = + use Bytes fromList + {{ + Creates a {type Bytes} from a {type List} of {type Nat} values in the range 0 + to 255 (inclusive). Throws an error if the list contains any values outside + this range. + + # Example + + ``` + catch do fromList [192, 168, 0, 1] + ``` + + ``` + catch do fromList [256] + ``` + }} + +-- builtin Bytes.fromList.impl : [Nat] -> Bytes + +-- builtin Bytes.gzip.compress : Bytes -> Bytes + +Bytes.gzip.compress.doc : Doc +Bytes.gzip.compress.doc = + use fromList impl + use gzip compress + {{ + Compresses a {type Bytes} value using the + [gzip](https://tools.ietf.org/html/rfc1952) format. + + # Examples + + ``` + compress 0xs4b696c726f79207761732068657265 + ``` + + ``` + compress 0xs + ``` + + # See also + + * {gzip.decompress} + }} + +Bytes.gzip.decompress : Bytes ->{Exception} Bytes +Bytes.gzip.decompress bs = match gzip.decompress.impl bs with + Left e -> raiseGeneric e bs + Right bs' -> bs' + +Bytes.gzip.decompress.doc : Doc +Bytes.gzip.decompress.doc = + {{ + Decompresses the given [gzip](https://en.wikipedia.org/wiki/Gzip) compressed + {type Bytes}. + + # Example + + ``` + catch do gzip.decompress (gzip.compress 0xsfeedfacecafebeef) + ``` + }} + +-- builtin Bytes.gzip.decompress.impl : Bytes -> Either Text Bytes + +Bytes.gzip.decompress.impl.doc : Doc +Bytes.gzip.decompress.impl.doc = + {{ + Decompresses the given [gzip](https://en.wikipedia.org/wiki/Gzip) compressed + {type Bytes}. + + # Example + + ``` + gzip.decompress.impl (gzip.compress 0xsfeedfacecafebeef) + ``` + }} + +-- builtin Bytes.indexOf : Bytes -> Bytes -> Optional Nat + +Bytes.indexOf.doc : Doc +Bytes.indexOf.doc = + {{ + `` Bytes.indexOf needle haystack `` returns the index of the first occurrence + of 'needle' in 'haystack'. + }} + +test> Bytes.indexOf.tests.infix = + test.verify do + use Bytes ++ indexOf + use Nat == + use Random bytes natIn + Each.repeat 100 + szHaystackLeft = natIn 0 100 + szNeedle = natIn 0 100 + szHaystackRight = natIn 0 100 + haystackLeft = bytes szHaystackLeft + needle = bytes szNeedle + haystackRight = bytes szHaystackRight + haystack = haystackLeft ++ needle ++ haystackRight + ix = indexOf needle haystack + if szNeedle == 0 then ensure (ix === Some 0) + else ensuring do indexOf needle haystack === Some szHaystackLeft + +Bytes.isEmpty : Bytes -> Boolean +Bytes.isEmpty bs = + use Nat == + Bytes.size bs == 0 + +Bytes.isEmpty.doc : Doc +Bytes.isEmpty.doc = + use Bytes isEmpty + use fromList impl + {{ + Returns `` true `` if the {type Bytes} is empty. + + # Examples + + ``` + isEmpty 0xs + ``` + + ``` + isEmpty Bytes.empty + ``` + + ``` + isEmpty 0xs2390 + ``` + }} + +Bytes.shiftLeft : Nat -> Bytes -> Bytes +Bytes.shiftLeft n bs = + nats = Bytes.toList bs + f b = + use Nat - + carry = Store.get + Store.put (Nat.shiftRight b (8 - n)) + dropBits 56 (Nat.or (Nat.shiftLeft b n) carry) + fromList.impl (accumulateRight f 0 nats) + +Bytes.shiftLeft.doc : Doc +Bytes.shiftLeft.doc = + use Bytes shiftLeft + use fromList impl + {{ + `` shiftLeft n bs `` shifts all the {type Bytes} `bs` left by `n` bits, where + `n` is at most ``8``. Passing `n` larger than `` 8 `` will have the same + effect as passing ``8``. + + # Examples + + ``` + shiftLeft 2 0xs010203 + ``` + + ``` + shiftLeft 4 0xsdeadbeef + ``` + + ``` + shiftLeft 100 0xsfeedface + ``` + }} + +Bytes.shiftRight : Nat -> Bytes -> Bytes +Bytes.shiftRight n bs = + nats = Bytes.toList bs + f b = + use Nat - + carry = Store.get + Store.put (Nat.and b (dropBits (64 - n) b)) + Nat.or (Nat.shiftRight b n) (dropBits 56 (Nat.shiftLeft carry (8 - n))) + fromList.impl (accumulateLeft f 0 nats) + +Bytes.shiftRight.doc : Doc +Bytes.shiftRight.doc = + use Bytes shiftRight + use fromList impl + {{ + `` shiftRight n bs `` shifts all the {type Bytes} `bs` right by `n` bits, + where `n` is at most ``8``. Passing `n` larger than `` 8 `` will have the + same effect as passing ``8``. + + # Examples + + ``` + shiftRight 2 0xs010203 + ``` + + ``` + shiftRight 4 0xsdeadbeef + ``` + + ``` + shiftRight 100 0xsfeedface + ``` + }} + +-- builtin Bytes.size : Bytes -> Nat + +Bytes.size.doc : Doc +Bytes.size.doc = + use Bytes size + use fromList impl + {{ + Returns the number of bytes in the given {type Bytes}. + + # Examples + + ``` + size 0xsfeedface + ``` + + ``` + size 0xs + ``` + }} + +Bytes.splitAt : Nat -> Bytes -> (Bytes, Bytes) +Bytes.splitAt index bytes = (Bytes.take index bytes, Bytes.drop index bytes) + +Bytes.splitAt.doc : Doc +Bytes.splitAt.doc = + use Bytes splitAt + use fromList impl + {{ + `` splitAt index bytes `` splits the provided bytes into two pieces at the + given index. The length of the first piece will be the given index, and the + length of the second piece will be the length of the original text minus the + given index. + + If the index is out of bounds, the first piece will be the original bytes and + the second piece will be empty. + + # Examples + + {{ + docCallout + (Some {{ ℹ️ }}) + {{ + {type Bytes} are printed in hexadecimal format, so the two characters `c` + and `0` combined in `` 0xsc0 `` represents a single byte. + }} }} + + ``` + splitAt 1 0xsc0decafe + ``` + + ``` + splitAt 3 0xsc0decafe + ``` + + ``` + splitAt 10 0xsc0decafe + ``` + + ``` + splitAt 0 0xsc0decafe + ``` + }} + +test> Bytes.splitAt.tests = + test.verify do + (index, expected) = + each + [ (1, (0xsc0, 0xsdecafe)) + , (3, (0xsc0deca, 0xsfe)) + , (10, (0xsc0decafe, 0xs)) + , (0, (0xs, 0xsc0decafe)) + ] + ensuring do Bytes.splitAt index 0xsc0decafe === expected + +-- builtin Bytes.take : Nat -> Bytes -> Bytes + +Bytes.take.doc : Doc +Bytes.take.doc = + use Bytes take + use fromList impl + {{ + `` take n b `` returns the first `n` bytes of `b`. + + If `n` is greater than or equal to the length of `b`, the result is `b`. + + # Examples + + ``` + take 0 0xsfeedface + ``` + + ``` + take 2 0xsfeedface + ``` + + ``` + take 10 0xsfeedface + ``` + }} + +-- builtin Bytes.toBase16 : Bytes -> Bytes + +Bytes.toBase16.doc : Doc +Bytes.toBase16.doc = + use Bytes toBase16 + use fromList impl + {{ + Transforms {type Bytes} to their hexadecimal representation in the ASCII + character set. See also {toBase16.text} if you'd like to convert directly to + {type Text}. + + # Example + + {{ docEval (_ -> toBase16 0xsfeedfacecafebeef) }} + + {{ docEval (_ -> catch do fromUtf8 (toBase16 0xsfeedfacecafebeef)) }} + }} + +-- builtin Bytes.toBase32 : Bytes -> Bytes + +Bytes.toBase32.doc : Doc +Bytes.toBase32.doc = + use fromList impl + {{ + Transforms {type Bytes} to their representation in the + [RFC 4648 base32 alphabet](https://en.wikipedia.org/wiki/Base32#RFC_4648_Base32_alphabet). + + # Example + + ``` + toBase32 0xsfeedfacecafebeef + ``` + + ``` + catch do fromUtf8 (toBase32 0xsfeedfacecafebeef) + ``` + }} + +Bytes.toBase32Hex : Bytes -> Bytes +Bytes.toBase32Hex = Text.toUtf8 << Bytes.base32Hex + +Bytes.toBase32Hex.doc : Doc +Bytes.toBase32Hex.doc = + {{ + Encodes {type Bytes} using the + [base32hex](https://tools.ietf.org/html/rfc4648#section-6) encoding. + + The output is the {type Bytes} representing the ASCII characters of the + encoding which uses the characters `0-9` and `A-V`. Use {toBase32Hex.text} if + you'd like to instead produce {type Text}. + + # Example + + {{ docEval (_ -> toBase32Hex 0xs14fb9c03d97e) }} + + # See also + + * {fromBase32Hex} + * {toBase32} + * {fromBase32.impl} + * {toBase64} + * {fromBase64.impl} + * {Bytes.toBase16} + * {fromBase16.impl} + }} + +test> Bytes.toBase32Hex.tests.inverse = test.verify do + Each.repeat 100 + bs = bytes.base32Hex() + err = test.raiseFailure "fromBase32Hex failed" + ensure (bs === Either.fold err toBase32Hex (fromBase32Hex bs)) + +Bytes.toBase32Hex.text : Bytes -> Text +Bytes.toBase32Hex.text bs = match catch do toBase32Hex bs |> fromUtf8 with + Left e -> bug "bug in Bytes.toBase32Hex, it somehow produced invalid utf8" + Right a -> a + +Bytes.toBase32Hex.text.doc : Doc +Bytes.toBase32Hex.text.doc = + {{ + Like {toBase32Hex}, but converts the result to {type Text}. + + ``` + toBase32Hex.text 0xs2a3b9082340202394802 + ``` + }} + +-- builtin Bytes.toBase64 : Bytes -> Bytes + +Bytes.toBase64.doc : Doc +Bytes.toBase64.doc = + use fromList impl + {{ + Transforms {type Bytes} to their representation in the + [RFC 4648 base64 alphabet](https://en.wikipedia.org/wiki/Base64). + + # Example + + ``` + toBase64 0xsfeedfacecafebeef + ``` + + ``` + catch do fromUtf8 (toBase64 0xsfeedfacecafebeef) + ``` + }} + +-- builtin Bytes.toBase64UrlUnpadded : Bytes -> Bytes + +Bytes.toBase64UrlUnpadded.doc : Doc +Bytes.toBase64UrlUnpadded.doc = + {{ + Converts a {type Bytes} to a {type Text} containing the Base64 URL encoding + of the bytes, without any padding. + + # Examples + + ``` + toBase64UrlUnpadded 0xs01020304 + ``` + + ``` + toBase64UrlUnpadded (Text.toUtf8 "Hello, World!") + ``` + }} + +Bytes.toHex : Bytes -> Text +Bytes.toHex bs = unsafeRun! do Bytes.toBase16 bs |> fromUtf8 + +Bytes.toHex.deprecated : Bytes -> Text +Bytes.toHex.deprecated bs = + Text.join "" (List.somes (List.map (Nat.toTextBase 16) (Bytes.toList bs))) + +Bytes.toHex.doc : Doc +Bytes.toHex.doc = + {{ + Converts {type Bytes} to its hexadecimal representation in {type Text}. + + # Example + + ``` + Bytes.toHex (Text.toUtf8 "Hello") + ``` + }} + +-- builtin Bytes.toList : Bytes -> [Nat] + +Bytes.toList.doc : Doc +Bytes.toList.doc = + {{ + Convert a {type Bytes} value to a {type List} of {type Nat} values in the + range 0 to 255. + + # Example + + ``` + Bytes.toList 0xs4564646965206c69766573 + ``` + }} + +Bytes.toNat64sbe : Bytes -> ([Nat], Bytes) +Bytes.toNat64sbe bs = + use List :+ + go ns bs = match decodeNat64be bs with + None -> (ns, bs) + Some (n, bs) -> go (ns :+ n) bs + go [] bs + +Bytes.toNat64sbe.doc : Doc +Bytes.toNat64sbe.doc = + use fromList impl + {{ + Decodes a list of {type Nat}s in + [big-endian](https://en.wikipedia.org/wiki/Endianness) order from + {type Bytes}. If the {type Bytes} do not contain a complete {type Nat} at the + end, returns the {type Nat}s decoded so far and the remaining {type Bytes}. + + # Examples + + ``` + toNat64sbe 0xs0000000000000001 + ``` + + ``` + toNat64sbe 0xs00000000000000010000000000000002 + ``` + + ``` + toNat64sbe 0xscafe + ``` + + ``` + toNat64sbe 0xs + ``` + }} + +Bytes.truncateLeft : Nat -> Bytes -> Bytes +Bytes.truncateLeft n = + use Nat + / + f b = + use Nat - == >= + use Store put + bits = Store.get + if bits >= 8 then + put (bits - 8) + b + else + if bits == 0 then 0 + else + put 0 + Nat.and b (Nat.shiftLeft 255 (8 - bits)) + Bytes.take ((n + 7) / 8) << fromList.impl << accumulateLeft f n + << Bytes.toList + +Bytes.truncateLeft.doc : Doc +Bytes.truncateLeft.doc = + use fromList impl + {{ + `` truncateLeft n bs `` truncates the {type Bytes} `bs` to `n` bits, counting + from the left. + + # Examples + + ``` + truncateLeft 1 0xsdeadbeef + ``` + + ``` + truncateLeft 4 0xsdeadbeef + ``` + + ``` + truncateLeft 17 0xsdeadbeef + ``` + }} + +Bytes.truncateRight : Nat -> Bytes -> Bytes +Bytes.truncateRight n bs = + use Nat + - / + f w = + use Nat == >= + use Store put + bits = Store.get + if bits >= 8 then + put (bits - 8) + w + else + if bits == 0 then + put 0 + 0 + else + put 0 + dropBits (64 - bits) w + Bytes.toList bs |> accumulateRight f n |> fromList.impl + |> Bytes.drop (Bytes.size bs - (n + 7) / 8) + +Bytes.truncateRight.doc : Doc +Bytes.truncateRight.doc = + use fromList impl + {{ + `` truncateRight n bs `` truncates the {type Bytes} `bs` to `n` bits, + counting from the right. + + # Examples + + ``` + truncateRight 1 0xsdeadbeef + ``` + + ``` + truncateRight 4 0xsdeadbeef + ``` + + ``` + truncateRight 19 0xsdeadbeef + ``` + }} + +-- builtin Bytes.zlib.compress : Bytes -> Bytes + +Bytes.zlib.compress.doc : Doc +Bytes.zlib.compress.doc = + use zlib compress + {{ + Compresses a {type Bytes} value using the + [zlib](https://tools.ietf.org/html/rfc1950) format. + + # Examples + + ``` + compress 0xs4b696c726f79207761732068657265 + ``` + + ``` + compress 0xs + ``` + + # See also + + * {zlib.decompress.impl} + }} + +Bytes.zlib.decompress : Bytes ->{Exception} Bytes +Bytes.zlib.decompress bs = match zlib.decompress.impl bs with + Left e -> raiseGeneric e bs + Right bs' -> bs' + +Bytes.zlib.decompress.doc : Doc +Bytes.zlib.decompress.doc = + {{ + Decompresses the given [zlib](https://en.wikipedia.org/wiki/Zlib) compressed + {type Bytes}. + + # Example + + ``` + catch do zlib.decompress (zlib.compress 0xsfeedfacecafebeef) + ``` + }} + +-- builtin Bytes.zlib.decompress.impl : Bytes -> Either Text Bytes + +Bytes.zlib.decompress.impl.doc : Doc +Bytes.zlib.decompress.impl.doc = + {{ + Decompresses the given [zlib](https://en.wikipedia.org/wiki/Zlib) compressed + {type Bytes}. + + # Example + + ``` + zlib.decompress.impl (zlib.compress 0xsfeedfacecafebeef) + ``` + }} + +CHANGELOG : [(LocalDate, Doc)] +CHANGELOG = + use Stream takeWhile + use authors stew + use patterns hexDigit + [ (LocalDate +2022 7 15, {{ M4 release }}) + , (LocalDate +2022 7 19, {{ Added {Map.unions} }}) + , (LocalDate +2022 7 20, {{ Added {onException} }}) + , (LocalDate +2022 7 29, {{ Added {Nat.fromInt} }}) + , (LocalDate +2022 9 13, {{ {runarorama} added {flipped.deprecated} }}) + , ( LocalDate +2022 9 17 + , {{ + {runarorama} added {List.flatMap} and renamed old version to + {List.flatMapRight} + }} + ) + , ( LocalDate +2022 9 20 + , {{ + {runarorama} added {Stream.repeat} and {Stream.unfold} + }} + ) + , ( LocalDate +2022 9 21 + , {{ + {runarorama} renamed `List.foldb` to `List.foldMap`, and added + {MVar.modify}, {tryModify}, and {Random.listOf}. + }} + ) + , (LocalDate +2022 9 21, {{ {stew} added {hexDigit} and {asciiLetter} }}) + , ( LocalDate +2022 9 21 + , {{ + {runarorama} added {drain}, {takeWhile}, {takeWhile!}. + }} + ) + , ( LocalDate +2022 9 22 + , {{ + {runarorama} fixed {Stream.take}, {takeWhile}, added tests + }} + ) + , (LocalDate +2022 9 22, {{ {runarorama} added {ensuring} and docs }}) + , ( LocalDate +2022 9 22 + , {{ + {runarorama} changed the return type of {ensureWith} to {type Unit} + }} + ) + , (LocalDate +2022 9 22, {{ {stew} added {hexDigit} and {asciiLetter} }}) + , ( LocalDate +2022 9 22 + , {{ + {runarorama} moved around a lot of names in the `io` namespace + }} + ) + , ( LocalDate +2022 9 22 + , {{ + {runarorama} removed buffer size argument from {Socket.receive} and + renamed old version to {receiveAtMost} + }} + ) + , ( LocalDate +2022 9 23 + , {{ + {runarorama} added {type BoundServerSocket} and friends. Cleaned up + {type Socket} and {type Tls} API. + }} + ) + , ( LocalDate +2022 9 24 + , {{ + {dolio} added more + [array operations](https://github.com/unisonweb/base/issues/109) + }} + ) + , ( LocalDate +2022 9 24 + , {{ + {runarorama} merged `io` into `IO` namespace. Deleted `io` namespace. + }} + ) + , ( LocalDate +2022 9 28 + , {{ + {runarorama} moved `Test.Scope` to `Test.Labels` + }} + ) + , ( LocalDate +2022 9 28 + , {{ + {runarorama} added {type Map.Nonempty} and friends + }} + ) + , ( LocalDate +2022 10 24 + , {{ + {stew} added {isInfinity}, {isNegativeInfinity}, {isNaN} + }} + ) + , ( LocalDate +2022 11 15 + , {{ + {runarorama} added {twosComplement}, {reverseBits}, and {Text.indexOf} + }} + ) + , ( LocalDate +2022 11 18 + , {{ + {runarorama} added {Nat.isPrefixOf} and {Nat.isSuffixOf} + }} + ) + , ( LocalDate +2022 11 18 + , {{ + {runarorama} added {type NatSet} and {type NatMap} + }} + ) + , ( LocalDate +2022 11 23 + , {{ + {runarorama} added `addDuration` to {type OffsetDateTime}, + {type LocalDateTime}, and friends. Fixed an off-by-one error in + {fromInstant}. + }} + ) + , ( LocalDate +2022 12 5 + , {{ + {runarorama} added {break}, {words}, and {splitOnNewline}. + }} + ) + , ( LocalDate +2022 12 6 + , {{ + {runarorama} moved `scanr` and `scanl` to `scanLeft` and `scanRight` + }} + ) + , ( LocalDate +2023 1 30 + , {{ + {runarorama} moved `console.getLine` to `console.readLine` + }} + ) + , ( LocalDate +2023 1 30 + , {{ + {runarorama} added {type Process} builtin and supporting functions. + }} + ) + , ( LocalDate +2023 2 16 + , {{ + {runarorama} added {type Class} builtin and supporting functions. + }} + ) + , ( LocalDate +2023 5 4 + , {{ + {runarorama} added {Optional.toException}, {Pattern.oneOf}, and ISO 8601 + formatting/parsing of time/calendar types. + }} + ) + , ( LocalDate +2023 5 15 + , {{ + {runarorama} added {Each.append}, {ifThenElse}, {Each.interleave}, + {interleaveMap}, {limit}, {Each.negate}, {observe}, {once}, {Each.split} + }} + ) + , ( LocalDate +2023 5 15 + , {{ + {runarorama} replaced {Bytes.indexOf} with builtin + }} + ) + , ( LocalDate +2023 6 21 + , {{ + {runarorama} added {type TimeZone}, {getTimeZone}, {currentTimeZone}, and + friends. + }} + ) + ] + +(Char.!=) : Char -> Char -> Boolean +a Char.!= b = + use Char == + Boolean.not (a == b) + +Char.!=.doc : Doc +Char.!=.doc = + use Char != + {{ + Returns `` true `` if the two characters are not equal, and `` false `` + otherwise. + + # Examples + + ``` + ?a != ?a + ``` + + ``` + ?a != ?b + ``` + }} + +(Char.<) : Char -> Char -> Boolean +a Char.< b = + use Char toNat + use Nat < + toNat a < toNat b + +Char.<.doc : Doc +Char.<.doc = + use Char < + {{ + Returns `` true `` if the Unicode code point of the first character is less + than the Unicode code point of the second character, and `` false `` + otherwise. + + # Examples + + ``` + ?a < ?b + ``` + + ``` + ?b < ?a + ``` + + ``` + ?a < ?a + ``` + }} + +(Char.<=) : Char -> Char -> Boolean +a Char.<= b = + use Char toNat + use Nat <= + toNat a <= toNat b + +Char.<=.doc : Doc +Char.<=.doc = + use Char <= + {{ + Returns `` true `` if the Unicode code point of the first character is less + than or equal to the Unicode code point of the second character, and `` false + `` otherwise. + + # Examples + + ``` + ?a <= ?b + ``` + + ``` + ?b <= ?a + ``` + + ``` + ?a <= ?a + ``` + }} + +(Char.==) : Char -> Char -> Boolean +a Char.== b = + use Char toNat + use Nat == + toNat a == toNat b + +Char.==.doc : Doc +Char.==.doc = + use Char == + {{ + Returns `` true `` if the two characters are equal, and `` false `` + otherwise. + + # Examples + + ``` + ?a == ?a + ``` + + ``` + ?a == ?b + ``` + }} + +(Char.>) : Char -> Char -> Boolean +a Char.> b = + use Char toNat + use Nat > + toNat a > toNat b + +Char.>.doc : Doc +Char.>.doc = + use Char > + {{ + Returns `` true `` if the Unicode code point of the first character is + greater than the Unicode code point of the second character, and `` false `` + otherwise. + + # Examples + + ``` + ?a > ?b + ``` + + ``` + ?b > ?a + ``` + + ``` + ?a > ?a + ``` + }} + +(Char.>=) : Char -> Char -> Boolean +a Char.>= b = + use Char toNat + use Nat >= + toNat a >= toNat b + +Char.>=.doc : Doc +Char.>=.doc = + use Char >= + {{ + Returns `` true `` if the Unicode code point of the first character is + greater than or equal to the Unicode code point of the second character, and + `` false `` otherwise. + + # Examples + + ``` + ?a >= ?b + ``` + + ``` + ?b >= ?a + ``` + + ``` + ?a >= ?a + ``` + }} + +Char.ascii.fromBase36Digit : Nat -> Optional Char +Char.ascii.fromBase36Digit n = + use Char toNat + use Nat + - < > + use fromNat impl + if n > 36 then None + else + if n < 10 then Some (impl (toNat ?0 + n)) + else Some (impl (toNat ?A + n - 10)) + +Char.ascii.fromBase36Digit.doc : Doc +Char.ascii.fromBase36Digit.doc = + {{ + Turns a {type Nat} into a {type Char} representing it as a base-36 digit. If + the input is in the range 0-9, then this returns the respective numeric + character. If the input is in the range 10-35, then this returns a character + in the range A (for 10) through Z (for 35). If the input is higher than 35, + this returns ``None``. + + # Examples + + ``` + fromBase36Digit 2 + ``` + + ``` + fromBase36Digit 10 + ``` + + ``` + fromBase36Digit 256 + ``` + }} + +Char.ascii.isAlphaNum : Char -> Boolean +Char.ascii.isAlphaNum c = isLetter c || isDigit c + +Char.ascii.isAlphaNum.doc : Doc +Char.ascii.isAlphaNum.doc = + {{ + Returns whether its argument is an ascii alphanumeric character (letter or + digit). + }} + +Char.ascii.isAscii : Char -> Boolean +Char.ascii.isAscii c = Universal.lt (Char.toNat c) 128 + +Char.ascii.isAscii.doc : Doc +Char.ascii.isAscii.doc = + {{ Returns whether its argument is an ascii character. }} + +Char.ascii.isBlank : Char -> Boolean +Char.ascii.isBlank = cases + ?\s -> true + ?\t -> true + _ -> false + +Char.ascii.isBlank.doc : Doc +Char.ascii.isBlank.doc = {{ Returns whether its argument is a space or tab. }} + +Char.ascii.isControl : Char -> Boolean +Char.ascii.isControl c = + use Char toNat + use Nat < + toNat c < toNat ?\s + +Char.ascii.isControl.doc : Doc +Char.ascii.isControl.doc = + {{ Returns whether its argument is an ascii control character. }} + +Char.ascii.isDigit : Char -> Boolean +Char.ascii.isDigit = Char.inRange ?0 ?9 + +Char.ascii.isDigit.doc : Doc +Char.ascii.isDigit.doc = {{ Returns whether its argument is an ascii digit }} + +Char.ascii.isGraph : Char -> Boolean +Char.ascii.isGraph c = if c === ?\s then false else isPrint c + +Char.ascii.isGraph.doc : Doc +Char.ascii.isGraph.doc = + {{ + Returns whether its argument is an ascii graphical character. + + i.e. any printable character except space. + }} + +Char.ascii.isHexDigit : Char -> Boolean +Char.ascii.isHexDigit c = + use Char inRange + isDigit c || inRange ?a ?f c || inRange ?A ?F c + +Char.ascii.isHexDigit.doc : Doc +Char.ascii.isHexDigit.doc = + {{ Returns whether its argument is a hexidecimal digit. }} + +Char.ascii.isLetter : Char -> Boolean +Char.ascii.isLetter c = isLower c || isUpper c + +Char.ascii.isLetter.doc : Doc +Char.ascii.isLetter.doc = {{ Returns whether its argument is an ascii letter }} + +Char.ascii.isLower : Char -> Boolean +Char.ascii.isLower = Char.inRange ?a ?z + +Char.ascii.isLower.doc : Doc +Char.ascii.isLower.doc = + {{ Returns whether its argument is a lowercase ascii letter. }} + +Char.ascii.isPrint : Char -> Boolean +Char.ascii.isPrint = Char.inRange ?\s ?~ + +Char.ascii.isPrint.doc : Doc +Char.ascii.isPrint.doc = + {{ Returns whether its argument is an ascii printable character }} + +Char.ascii.isPunct : Char -> Boolean +Char.ascii.isPunct c = if c === ?\s || isAlphaNum c then false else isPrint c + +Char.ascii.isPunct.doc : Doc +Char.ascii.isPunct.doc = + {{ Returns whether its argument is an ascii punctuation mark. }} + +Char.ascii.isSpace : Char -> Boolean +Char.ascii.isSpace = cases + ?\s -> true + ?\f -> true + ?\n -> true + ?\r -> true + ?\t -> true + ?\v -> true + _ -> false + +Char.ascii.isSpace.doc : Doc +Char.ascii.isSpace.doc = + {{ Returns whether its argument is an ascii whitespace character. }} + +Char.ascii.isUpper : Char -> Boolean +Char.ascii.isUpper = Char.inRange ?A ?Z + +Char.ascii.isUpper.doc : Doc +Char.ascii.isUpper.doc = + {{ Returns whether its argument is an uppercase ascii letter. }} + +Char.ascii.lowerUpperDiff : Int +Char.ascii.lowerUpperDiff = + use Char toNat + use Int - + use Nat toInt + toInt (toNat ?a) - toInt (toNat ?A) + +Char.ascii.lowerUpperDiff.doc : Doc +Char.ascii.lowerUpperDiff.doc = + {{ + The numeric difference between a lowercase ascii character and its uppercase + equivalent. + }} + +Char.ascii.README : Doc +Char.ascii.README = {{ Utilities for working with ASCII characters. }} + +test> Char.ascii.tests.propUpperLower = + go _ = + use ascii toLower toUpper + c = fromNat.impl natInOrder() + if isUpper c then expect (toUpper (toLower c) === c) + else + if isLower c then expect (toLower (toUpper c) === c) + else expect (toUpper c === c && toLower c === c) + runs 128 go + +Char.ascii.tests.propUpperLower.doc : Doc +Char.ascii.tests.propUpperLower.doc = + {{ Test the relationship between toUpper and toLower. }} + +test> Char.ascii.tests.toLower = check (ascii.toLower ?A === ?a) + +test> Char.ascii.tests.toUpper = check (ascii.toUpper ?a === ?A) + +Char.ascii.toBase36Digit : Char -> Optional Nat +Char.ascii.toBase36Digit c = + use Char toNat + use Nat + - + digitAlphaGap = subtractToInt (toNat ?a) (toNat ?9) + if isDigit c then Some (toNat c - toNat ?0) + else + if isLower c then Some (toNat c - toNat ?a + 10) + else if isUpper c then Some (toNat c - toNat ?A + 10) else None + +Char.ascii.toBase36Digit.doc : Doc +Char.ascii.toBase36Digit.doc = + {{ + Turns an alphanumeric character into a digit in base-36. + + * If the caracter is numeric (see {isDigit}), then its numeric value is + returned. + * If the character is a Latin letter in the ranges `` ?A `` through `` ?Z `` + or `` ?a `` through ``?z``, then what's returned is 10 plus the character's + position in the latin alphabet. + * If the character is not an alphanumeric Latin character, then {None} is + returned. + + # Examples + + ``` + toBase36Digit ?0 + ``` + + ``` + toBase36Digit ?9 + ``` + + ``` + toBase36Digit ?a + ``` + + ``` + toBase36Digit ?Z + ``` + }} + +Char.ascii.toLower : Char -> Char +Char.ascii.toLower c = + use Int + + if isUpper c then + fromNat.impl (truncate0 (Nat.toInt (Char.toNat c) + lowerUpperDiff)) + else c + +Char.ascii.toLower.doc : Doc +Char.ascii.toLower.doc = + {{ + Converts its argument to lowercase. + + Does nothing if the argument is not an ascii letter. + }} + +Char.ascii.toUpper : Char -> Char +Char.ascii.toUpper c = + use Int - + if isLower c then + fromNat.impl (truncate0 (Nat.toInt (Char.toNat c) - lowerUpperDiff)) + else c + +Char.ascii.toUpper.doc : Doc +Char.ascii.toUpper.doc = + {{ + Converts its argument to uppercase. + + Does nothing if the argument is not an ascii letter. + }} + +-- builtin Char.Class.+ : Char.Class -> Char.Class -> Char.Class + +Char.Class.+.doc : Doc +Char.Class.+.doc = + use Class + letter number + {{ + Creates a character class that contains a character if it is in either of the + given character classes. + + # Examples + + ``` + is (letter + number) ?a + ``` + + ``` + is (letter + number) ?. + ``` + + ``` + Pattern.run + (Pattern.capture (many (patterns.char (letter + whitespace)))) + " \t\n abc123" + ``` + + # See also + + * {type Class} for more information on character classes. + * {Class.and} for the logical AND of two character classes. + * {Class.not} for the logical NOT of a character class. + }} + +(Char.Class.-) : Class -> Class -> Class +x Char.Class.- y = Class.and x (Class.not y) + +Char.Class.-.doc : Doc +Char.Class.-.doc = + use Class - letter + {{ + Subtracts one {type Class} from another. The resulting {type Class} will + match any {type Char} that is in the first {type Class} but not in the + second. + + # Examples + + Check if a character is a letter but not a hex digit: + + ``` + List.map (is (letter - Class.hexDigit)) (toCharList "rad") + ``` + + Capture letters up until the first occurrence of `c`. + + ``` + Pattern.run + (Pattern.capture (many (patterns.char (letter - fromChar ?c)))) "abcd" + ``` + }} + +-- builtin Char.Class.alphanumeric : Char.Class + +Char.Class.alphanumeric.doc : Doc +Char.Class.alphanumeric.doc = + use Class alphanumeric + {{ + Matches alphanumeric characters. This is equivalent to + ``Class.and Char.Class.letter Char.Class.number``. + + # Examples + + ``` + is alphanumeric ?a + ``` + + ``` + Pattern.run + (Pattern.capture (many (patterns.char alphanumeric))) "abc123*xyz" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +-- builtin Char.Class.and : Char.Class -> Char.Class -> Char.Class + +Char.Class.and.doc : Doc +Char.Class.and.doc = + use Class + and printable + {{ + Creates a character class that contains a character if it is in both of the + given character classes. + + # Examples + + ``` + is (and printable whitespace) ?\s + ``` + + ``` + is (and printable whitespace) ?\n + ``` + + ``` + Pattern.run + (Pattern.capture (many (patterns.char (and printable whitespace)))) + " \t\n abc123" + ``` + + # See also + + * {type Class} for more information on character classes. + * {+} for the logical OR of two character classes. + * {Class.not} for the logical NOT of a character class. + }} + +-- builtin Char.Class.any : Char.Class + +Char.Class.any.doc : Doc +Char.Class.any.doc = + use Class any + {{ + Matches any character. + + # Examples + + ``` + is any ?a + ``` + + ``` + Pattern.run (Pattern.capture (many (patterns.char any))) "abc123" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +-- builtin Char.Class.anyOf : [Char] -> Char.Class + +Char.Class.anyOf.doc : Doc +Char.Class.anyOf.doc = + {{ + Creates a character class that contains a character if it is in the given + list of characters. + + # Examples + + ``` + is (anyOf (toCharList "abc")) ?a + ``` + + ``` + is (anyOf (toCharList "abc")) ?d + ``` + + ``` + Pattern.run + (Pattern.capture (many (patterns.char (anyOf (toCharList "abc"))))) + "abc123" + ``` + + # See also + + * {type Class} for more information on character classes. + * {in} for a version of this that takes {type Text} instead of a list. + }} + +Char.Class.ascii : Class +Char.Class.ascii = Class.range ?\0 ? + +Char.Class.ascii.doc : Doc +Char.Class.ascii.doc = + use Class ascii + use Pattern capture run + use patterns char + {{ + Matches ASCII characters. The ASCII character set is a subset of the Unicode + character set. It contains the first 128 characters of Unicode. + + # Examples + + ``` + is ascii ?a + ``` + + ``` + is ascii ?€ + ``` + + ``` + run (capture (many (char ascii))) "abc123" + ``` + + ``` + run (capture (many (char ascii))) "123 €" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +-- builtin Char.Class.control : Char.Class + +Char.Class.control.doc : Doc +Char.Class.control.doc = + use Class control + use Pattern capture run + use patterns char + {{ + Matches control characters. Control characters are non-printable characters + that are used to control the display of text. + + # Examples + + ``` + is control ?\0 + ``` + + ``` + is control ?a + ``` + + ``` + run (capture (many (char control))) "abc123" + ``` + + ``` + run (capture (many (char control))) "\nabc\n123\n" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +Char.Class.digit : Class +Char.Class.digit = Class.range ?0 ?9 + +Char.Class.digit.doc : Doc +Char.Class.digit.doc = + use Class digit + {{ + Matches decimal digits. This is equivalent to ``Class.range ?0 ?9``. + + # Examples + + ``` + is digit ?0 + ``` + + ``` + is digit ?a + ``` + + ``` + Pattern.run (Pattern.capture (many (patterns.char digit))) "123abc" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +Char.Class.doc : Doc +Char.Class.doc = + use Class + + use patterns char + {{ + A {type Class} is a set of characters. It can be used to construct a + {type Pattern} that matches a single {type Char} in a {type Text} using + {char}, or a sequence of characters using {many} and + {{ docLink (docEmbedTermLink do some) }}. + + # Provided classes + + The following classes are provided: + + * {Class.alphanumeric} - alphanumeric characters. + * {Class.any} - any character. + * {Class.ascii} - ASCII characters. + * {Class.control} - control characters. + * {Class.digit} - decimal digits. + * {Class.hexDigit} - hexadecimal digits. + * {Class.letter} - letters in any script. + * {Class.lower} - lowercase letters in any script. + * {mark} - marks such as accents and diacritics. + * {Class.number} - numbers in any script. + * {Class.printable} - printable characters including space. + * {Class.punctuation} - punctuation characters such as `!` and `?`. + * {separator} - spaces and paragraph separators. + * {symbol} - symbols such as `$` and `%`. + * {Class.upper} - uppercase letters in any script. + * {visible} - printable characters excluding space. + * {whitespace} - whitespace characters such as spaces, tabs, and newlines. + * {word} - word characters (alphanumeric or connecting punctuation). + + # Class combinators + + You can construct a new class from existing classes using the following + combinators. + + Take the intersection of two classes, matching a character that is in both + classes: + + @signature{Class.and} + + Union two classes, matching a character that is in either class: + + @signature{+} + + Negate a class, matching a character that is not in the class: + + @signature{Class.not} + + You can also construct a class from a character range: + + @signature{Class.range} + + Or from a {type List} of characters: + + @signature{anyOf} + + Or from a {type Text}: + + @signature{in} + + # Testing for membership + + Test if a character is in the given class: + + @signature{is} + + # Constructing a pattern from a class + + Construct a {type Pattern} that matches a single character in the given + class: + + @signature{char} + + # See also + + * {many} and {{ docLink (docEmbedTermLink do some) }} to match a sequence + of characters. + * [Unicode Character Classes](https://www.compart.com/en/unicode/category) + }} + +Char.Class.fromChar : Char -> Class +Char.Class.fromChar c = Class.range c c + +Char.Class.fromChar.doc : Doc +Char.Class.fromChar.doc = + {{ + Creates a character class containing only the given character. + + # Examples + + ``` + Pattern.run (patterns.char (fromChar ?a)) "abc" + ``` + + ``` + is (fromChar ?a) ?a + ``` + + ``` + is (fromChar ?a) ?b + ``` + }} + +Char.Class.hexDigit : Class +Char.Class.hexDigit = + use Class + range + Class.digit + range ?a ?f + range ?A ?F + +Char.Class.hexDigit.doc : Doc +Char.Class.hexDigit.doc = + use Class + hexDigit range + {{ + Matches hexadecimal digits. This is equivalent to + {{ + docExample + 2 + (_ Char.Class.range1 Char.Class.or1 -> + range ?0 ?9 + range ?a ?f + range ?A ?F) + }}. + + # Examples + + ``` + is hexDigit ?0 + ``` + + ``` + is hexDigit ?a + ``` + + ``` + is hexDigit ?A + ``` + + ``` + is hexDigit ?g + ``` + + ``` + Pattern.run (Pattern.capture (many (patterns.char hexDigit))) "123abcdefgh" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +Char.Class.in : Text -> Class +Char.Class.in = anyOf << toCharList + +Char.Class.in.doc : Doc +Char.Class.in.doc = + {{ + Creates a character class that contains a character if it is in the given + {type Text}. + + # Examples + + ``` + is (in "abc") ?a + ``` + + ``` + is (in "abc") ?d + ``` + + ``` + Pattern.run + (Pattern.capture (many (patterns.char (in "abcr")))) "abracadabra" + ``` + + # See also + + * {type Class} for more information on character classes. + * {anyOf} for a version of this that takes a list instead of {type Text}. + }} + +-- builtin Char.Class.is : Char.Class -> Char -> Boolean + +Char.Class.is.doc : Doc +Char.Class.is.doc = + use Class letter + {{ + Determines if a character is in a character class. + + # Examples + + ``` + is letter ?a + ``` + + ``` + is letter ?. + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +-- builtin Char.Class.letter : Char.Class + +Char.Class.letter.doc : Doc +Char.Class.letter.doc = + use Class letter + {{ + Matches letters in any script. + + # Examples + + ``` + is letter ?a + ``` + + ``` + is letter ?€ + ``` + + ``` + is letter ?ð + ``` + + ``` + Pattern.run (Pattern.capture (many (patterns.char letter))) "abc123" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +-- builtin Char.Class.lower : Char.Class + +Char.Class.lower.doc : Doc +Char.Class.lower.doc = + use Class lower + {{ + Matches lowercase letters in any script. + + # Examples + + ``` + is lower ?a + ``` + + ``` + is lower ?A + ``` + + ``` + is lower ?ð + ``` + + ``` + Pattern.run (Pattern.capture (many (patterns.char lower))) "abcABC" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +-- builtin Char.Class.mark : Char.Class + +Char.Class.mark.doc : Doc +Char.Class.mark.doc = + {{ + Matches non-spacing, spacing, and enclosing marks. These are characters that + are used to modify the appearance of the preceding character. + + # Examples + + ``` + is mark ?a + ``` + + ``` + is mark ?̀ + ``` + + If the above example looks strange, it is because the character `?\768` + modifies the appearance of the preceding question mark that Unison uses to + denote a {type Char} literal. The character `?\768` is a combining grave + accent, which is a non-spacing mark. + + # See also + + * {type Class} for more information on character classes. + * [Unicode Character Classes](https://www.compart.com/en/unicode/category) + }} + +-- builtin Char.Class.not : Char.Class -> Char.Class + +Char.Class.not.doc : Doc +Char.Class.not.doc = + use Class + letter not + {{ + Creates a character class that contains a character if it is not in the given + character class. + + # Examples + + ``` + is (not letter) ?a + ``` + + ``` + is (not letter) ?. + ``` + + ``` + Pattern.run (Pattern.capture (many (patterns.char (not letter)))) "123abc" + ``` + + # See also + + * {type Class} for more information on character classes. + * {Class.and} for the logical AND of two character classes. + * {+} for the logical OR of two character classes. + }} + +-- builtin Char.Class.number : Char.Class + +Char.Class.number.doc : Doc +Char.Class.number.doc = + use Class number + {{ + Matches numbers in any script. + + # Examples + + ``` + is number ?0 + ``` + + ``` + is number ?a + ``` + + ``` + is number ?€ + ``` + + ``` + Pattern.run (Pattern.capture (many (patterns.char number))) "123abc" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +-- builtin Char.Class.printable : Char.Class + +Char.Class.printable.doc : Doc +Char.Class.printable.doc = + use Class printable + use Pattern capture run + use patterns char + {{ + Matches printable characters. This is equivalent to + ``Class.not Char.Class.control``. + + # Examples + + ``` + is printable ?a + ``` + + ``` + is printable ?\0 + ``` + + ``` + run (capture (many (char printable))) "abc123" + ``` + + ``` + run (capture (many (char printable))) "abc\n123" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +-- builtin Char.Class.punctuation : Char.Class + +Char.Class.punctuation.doc : Doc +Char.Class.punctuation.doc = + use Class punctuation + {{ + Matches punctuation characters. + + This class includes the following Unicode categories: + + * Connector Punctuation (Pc) (e.g. ``?_``) + * Dash Punctuation (Pd) (e.g. ``?-``) + * Open Punctuation (Ps) (e.g. ``?(``) + * Close Punctuation (Pe) (e.g. ``?)``) + * Initial Punctuation (Pi) (e.g. ``?«``) + * Final Punctuation (Pf) (e.g. ``?»``) + * Other Punctuation (Po) (e.g. ``?¿``) + + # Examples + + ``` + is punctuation ?. + ``` + + ``` + is punctuation ?a + ``` + + ``` + Pattern.run + (Pattern.capture (many (patterns.char punctuation))) ".,(abc123)" + ``` + + # See also + + * {type Class} for more information on character classes. + * [Unicode Character Classes](https://www.compart.com/en/unicode/category) + }} + +-- builtin Char.Class.range : Char -> Char -> Char.Class + +Char.Class.range.doc : Doc +Char.Class.range.doc = + use Class range + {{ + Creates a character class that contains a character if it is in the given + range of characters. + + # Examples + + ``` + is (range ?a ?z) ?a + ``` + + ``` + is (range ?a ?z) ?A + ``` + + ``` + Pattern.run (Pattern.capture (many (patterns.char (range ?a ?z)))) "abc123" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +-- builtin Char.Class.separator : Char.Class + +Char.Class.separator.doc : Doc +Char.Class.separator.doc = + {{ + Matches separator characters including spaces, no-break spaces, quads, and + Unicode paragraph separators. + + # Examples + + ``` + is separator ?\n + ``` + + ``` + is separator ?a + ``` + + ``` + Pattern.run + (Pattern.capture (many (patterns.char separator))) " \8233 abc123" + ``` + + # See also + + * {type Class} for more information on character classes. + * [Unicode Character Classes](https://www.compart.com/en/unicode/category) + }} + +-- builtin Char.Class.symbol : Char.Class + +Char.Class.symbol.doc : Doc +Char.Class.symbol.doc = + use Class + + use Pattern capture run + use patterns char + {{ + Matches symbols. These are characters that are used to represent mathematical + or logical concepts, currency, etc. + + # Examples + + ``` + is symbol ?+ + ``` + + ``` + is symbol ?a + ``` + + The `+` character is a mathematical symbol, but the `/` (forward slash) is + not: + + ``` + run (capture (many (char symbol))) "+/-10" + ``` + + However, the `∕` (division) character is a symbol: + + ``` + run (capture (many (char (Class.number + symbol)))) "1∕10" + ``` + + # See also + + * {type Class} for more information on character classes. + * [Unicode Character Classes](https://www.compart.com/en/unicode/category) + }} + +-- builtin Char.Class.upper : Char.Class + +Char.Class.upper.doc : Doc +Char.Class.upper.doc = + use Class upper + {{ + Matches uppercase letters in any script. + + # Examples + + ``` + is upper ?a + ``` + + ``` + is upper ?A + ``` + + ``` + Pattern.run (Pattern.capture (many (patterns.char upper))) "ABCabc" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +Char.Class.visible : Class +Char.Class.visible = Class.and Class.printable (Class.not whitespace) + +Char.Class.visible.doc : Doc +Char.Class.visible.doc = + {{ + Matches visible characters, which are printable characters that are not + whitespace. + + This is equivalent to + ``Class.and Char.Class.printable (Char.Class.not Char.Class.whitespace)``. + + # Examples + + ``` + is visible ?a + ``` + + ``` + is visible ?\s + ``` + + ``` + Pattern.run (Pattern.capture (many (patterns.char visible))) "abc 123" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +-- builtin Char.Class.whitespace : Char.Class + +Char.Class.whitespace.doc : Doc +Char.Class.whitespace.doc = + {{ + Matches whitespace characters. These are characters that are used to separate + text into words, sentences, and paragraphs. + + This class includes the unicode Space Separator (Zs) category, as well as + tabs, newlines, carriage returns, form feeds, and vertical tabs. + + # Examples + + ``` + is whitespace ?\s + ``` + + ``` + is whitespace ?a + ``` + + ``` + Pattern.run + (Pattern.capture (many (patterns.char whitespace))) " \t\n abc123" + ``` + + # See also + + * {type Class} for more information on character classes. + * [Unicode Character Classes](https://www.compart.com/en/unicode/category) + }} + +Char.Class.word : Class +Char.Class.word = + use Class + + Class.alphanumeric + anyOf [?_, ?‿, ?⁀, ?⁔, ?︳, ?︴, ?﹍, ?﹎, ?﹏, ?_] + +Char.Class.word.doc : Doc +Char.Class.word.doc = + {{ + Matches word characters. These are characters that are used to form words. + + This includes the `` Class.alphanumeric `` class, as well as the following + characters: ``[?_, ?‿, ?⁀, ?⁔, ?︳, ?︴, ?﹍, ?﹎, ?﹏, ?_]``. + + # Examples + + ``` + is word ?a + ``` + + ``` + is word ?. + ``` + + ``` + Pattern.run (Pattern.capture (many (patterns.char word))) "abc_123.ABC" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +Char.doc : Doc +Char.doc = + use Char < <= == > >= fromNat inRange toLowercase toNat toText toUppercase + use Class letter + {{ + {type Char} is the type of individual Unicode characters (more precisely, + single code points) like ``?A``, ``?b``, ``?🌈``, and ``?⭐``. + + # Construction + + A **character literal** is a single character preceded by a question mark, + like `` ?A `` or ``?🌈``. It has type {type Char}. + + Use + [escape sequences](https://www.unison-lang.org/learn/language-reference/escape-sequences/) + to construct characters that are not printable Unicode characters. For + example, `` ?\n `` is a newline character. + + You can also get a {type Char} from its code point as a {type Nat}, using + {fromNat}: + + ``` + fromNat 65 + ``` + + ``` + fromNat 128170 + ``` + + You can get a {type Char} representing a digit in base-36 (ASCII characters + `` ?0 `` - `` ?9 `` and `` ?A `` to ``?Z``) from the {type Nat} value of + that digit, using {fromBase36Digit}: + + ``` + fromBase36Digit 2 + ``` + + ``` + fromBase36Digit 15 + ``` + + ``` + fromBase36Digit 37 + ``` + + Convert a {type Text} to its list of characters using {toCharList}: + + ``` + toCharList "Hello, world!" + ``` + + ## Comparing characters + + You can compare characters using the usual comparison operators: {<}, + {<=}, {==}, {>=}, and {>}. These operators compare the code points of + the characters. + + A character is less than another character if its code point is less + than the other character's code point. For example, `` ?A `` is less + than ``?B``, and `` ?🌈 `` is less than ``?⭐``. + + Check if a {type Char} is between two other {type Char}s using + {inRange}: + + ``` + inRange ?A ?Z ?B + ``` + + ``` + inRange ?A ?Z ?a + ``` + + ## Convertring to other types + + You can convert a {type Char} to its code point as a {type Nat} using + {toNat}: + + ``` + toNat ?A + ``` + + ``` + toNat ?🌈 + ``` + + You can convert a {type Char} to a single-character {type Text} using + {toText}: + + ``` + toText ?A + ``` + + ## Character classes + + {type Class} provides a number of functions for checking if a + {type Char} is in a particular class of characters and for creating new + character classes. + + For example, one way to check if a {type Char} is a letter is to use + {letter}: + + ``` + is letter ?A + ``` + + Check if a {type Char} is a digit or numeral symbol: + + ``` + is Class.number ?0 + ``` + + You can also use {isDigit} to check if a {type Char} is a digit between + 0 and 9: + + ``` + isDigit ?4 + ``` + + Check if a {type Char} is a whitespace: + + ``` + is whitespace ?\s + ``` + + See {type Class} for more information on character classes. + + ## Upper- and lowercase + + You can transform a lowercase {type Char} to uppercase using + {toUppercase}: + + ``` + toUppercase ?a + ``` + + You can transform an uppercase {type Char} to lowercase using + {toLowercase}: + + ``` + toLowercase ?A + ``` + }} + +Char.fromNat : Nat -> Optional Char +Char.fromNat n = + use Nat > + if n > 1114111 then None else Some (fromNat.impl n) + +Char.fromNat.doc : Doc +Char.fromNat.doc = + {{ + Convert a {type Nat} value to a {type Char} value. If the {type Nat} is not + in the range `` 0 `` to `` 1114111 `` (inclusive), the result is {None}. + + # Example + + ``` + Char.fromNat 128512 + ``` + }} + +-- builtin Char.fromNat.impl : Nat -> Char + +Char.inRange : Char -> Char -> Char -> Boolean +Char.inRange lo hi c = + use Char toNat + use Universal lteq + code = toNat c + lteq (toNat lo) code && lteq code (toNat hi) + +Char.inRange.doc : Doc +Char.inRange.doc = + {{ + `` Char.inRange lo hi char `` returns true if and only if `char` is lexically + between `lo` and `hi` (inclusively). + }} + +Char.isWhitespace : Char -> Boolean +Char.isWhitespace = is whitespace + +Char.isWhitespace.doc : Doc +Char.isWhitespace.doc = + {{ + Tests if a {type Char} is a whitespace character. Returns `` true `` if the + character is a space ``?s``, a tab ``?t``, a carriage return ``?r``, a new + line ``?\n``, a vertical tab (Unicode `0xb`), or any character from the + ["Space Separator"](https://www.compart.com/en/unicode/category/Zs) Unicode + category. + + # Deprecation notice + + This function is deprecated. Use the `` whitespace `` character class + instead: + + ``` + is whitespace ?\n + ``` + }} + +Char.range : Char -> Char -> [Char] +Char.range from to = + use Char toNat + Nat.range (toNat from) (toNat to) + |> List.flatMap (Char.fromNat >> Optional.toList) + +Char.range.doc : Doc +Char.range.doc = + use Char range + {{ + Returns a list of characters in the range from the first character to the + second character, exclusive of the second character. The characters are in + ascending order. + + If the first character is greater than the second character, the result is an + empty list. + + # Examples + + ``` + range ?a ?e + ``` + + ``` + range ?e ?a + ``` + }} + +Char.rangeClosed : Char -> Char -> [Char] +Char.rangeClosed from to = + use Char toNat + Nat.rangeClosed (toNat from) (toNat to) + |> List.flatMap (Char.fromNat >> Optional.toList) + +Char.rangeClosed.doc : Doc +Char.rangeClosed.doc = + use Char rangeClosed + {{ + Returns a list of characters in the range from the first character to the + second character, inclusive of the second character. The characters are in + ascending order. + + If the first character is greater than the second character, the result is an + empty list. + + # Examples + + ``` + rangeClosed ?a ?e + ``` + + ``` + rangeClosed ?e ?a + ``` + }} + +Char.toLowercase : Char -> Char +Char.toLowercase = + Char.toText >> Text.toLowercase >> toCharList >> List.head + >> getOrBug "Char.toLowercase" + +Char.toLowercase.doc : Doc +Char.toLowercase.doc = + use Char toLowercase + {{ + Convert a {type Char} to lowercase. If the character is not uppercase, it is + returned unchanged. + + # Examples + + ``` + toLowercase ?A + ``` + + ``` + toLowercase ?Λ + ``` + + ``` + toLowercase ?a + ``` + + ``` + toLowercase ?🌈 + ``` + }} + +-- builtin Char.toNat : Char -> Nat + +Char.toNat.doc : Doc +Char.toNat.doc = + use Char toNat + {{ + Convert a {type Char} to a {type Nat} representing the Unicode code point of + the character. + + # Examples + + ``` + toNat ?a + ``` + + ``` + toNat ?👋 + ``` + }} + +-- builtin Char.toText : Char -> Text + +Char.toText.doc : Doc +Char.toText.doc = + use Char toText + {{ + Convert a {type Char} to a one-character {type Text}. + + # Examples + + ``` + toText ?a + ``` + + ``` + toText ?👋 + ``` + }} + +Char.toUppercase : Char -> Char +Char.toUppercase = + Char.toText >> Text.toUppercase >> toCharList >> List.head + >> getOrBug "Char.toUppercase" + +Char.toUppercase.doc : Doc +Char.toUppercase.doc = + use Char toUppercase + {{ + Convert a {type Char} to uppercase. If the character is not lowercase, it is + returned unchanged. + + # Examples + + ``` + toUppercase ?a + ``` + + ``` + toUppercase ?λ + ``` + + ``` + toUppercase ?A + ``` + + ``` + toUppercase ?🌈 + ``` + }} + +Char.toUtf8 : Char -> Bytes +Char.toUtf8 = Text.toUtf8 << Char.toText + +Char.toUtf8.doc : Doc +Char.toUtf8.doc = + use Char toUtf8 + {{ + Converts a {type Char} to its UTF-8 representation as {type Bytes}. + + # Examples + + ``` + toUtf8 ?a + ``` + + ``` + toUtf8 ?📌 + ``` + }} + +crypto.Ed25519.PrivateKey.doc : Doc +crypto.Ed25519.PrivateKey.doc = + {{ + A private key for the Ed25519 signature scheme. + + # Example + + ``` + catch do + Ed25519.PrivateKey.PrivateKey + (fromBase64 + (Text.toUtf8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")) + ``` + }} + +crypto.Ed25519.PublicKey.doc : Doc +crypto.Ed25519.PublicKey.doc = + {{ + A public key for the Ed25519 signature scheme. + + # Example + + ``` + catch do + Ed25519.PublicKey.PublicKey + (fromBase64 + (Text.toUtf8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")) + ``` + }} + +crypto.Ed25519.PublicKey.toBytes : Ed25519.PublicKey -> Bytes +crypto.Ed25519.PublicKey.toBytes = cases Ed25519.PublicKey.PublicKey p -> p + +crypto.Ed25519.PublicKey.toBytes.doc : Doc +crypto.Ed25519.PublicKey.toBytes.doc = + {{ + Converts a {type Ed25519.PublicKey} to {type Bytes}. + + # Example + + ``` + catch do + PublicKey.toBytes + (Ed25519.PublicKey.PublicKey + (fromBase64 + (Text.toUtf8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="))) + ``` + }} + +crypto.Ed25519.sign : + Ed25519.PrivateKey + -> Ed25519.PublicKey + -> Bytes + ->{Exception} Ed25519.Signature +crypto.Ed25519.sign = cases + Ed25519.PrivateKey.PrivateKey p, Ed25519.PublicKey.PublicKey q, msg -> + Ed25519.Signature.Signature + (Either.toException (Ed25519.sign.impl p q msg)) + +crypto.Ed25519.sign.doc : Doc +crypto.Ed25519.sign.doc = + use fromList impl + {{ + Signs a message with an Ed25519 key pair and returns the signature. + + # Example + + ``` + catch do + private = + Ed25519.PrivateKey.PrivateKey + 0xs1498b5467a63dffa2dc9d9e069caf075d16fc33fdd4c3b01bfadae6433767d93 + public = + Ed25519.PublicKey.PublicKey + 0xsb7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde + Ed25519.sign private public (Text.toUtf8 "Hello, world!") + ``` + }} + +-- builtin crypto.Ed25519.sign.impl : +-- Bytes -> Bytes -> Bytes -> Either Failure Bytes + +crypto.Ed25519.Signature.doc : Doc +crypto.Ed25519.Signature.doc = + {{ + A signature for the Ed25519 signature scheme. + + # Example + + ``` + catch do + Ed25519.Signature.Signature + (fromBase64 + (Text.toUtf8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")) + ``` + }} + +crypto.Ed25519.verify : + Ed25519.PublicKey -> Bytes -> Ed25519.Signature ->{Exception} Boolean +crypto.Ed25519.verify = cases + Ed25519.PublicKey.PublicKey p, msg, Ed25519.Signature.Signature s -> + Either.toException (Ed25519.verify.impl p msg s) + +crypto.Ed25519.verify.doc : Doc +crypto.Ed25519.verify.doc = + use Ed25519 verify + use Ed25519.PublicKey PublicKey + use Ed25519.Signature Signature + use Text toUtf8 + use fromList impl + {{ + Verifies an Ed25519 signature on a message. + + # Examples + + ``` + catch do + signature = + Signature + 0xsb3f9407deccf1e46cdddef8d10f22084a4807322499e753cab13d65974216b9c252cb82375992cfb9231c2ba2d564860f07ccfd9630e891b6aa363d9568bec0b + public = + PublicKey + 0xsb7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde + verify public (toUtf8 "Hello, world!") signature + ``` + + ``` + catch do + signature = + Signature + 0xsb3f9407deccf1e46cdddef8d10f22084a4807322499e753cab13d65974216b9c252cb82375992cfb9231c2ba2d564860f07ccfd9630e891b6aa363d9568bec0b + public = + PublicKey + 0xsb7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde + verify public (toUtf8 "Bogus message") signature + ``` + }} + +-- builtin crypto.Ed25519.verify.impl : +-- Bytes -> Bytes -> Bytes -> Either Failure Boolean + +-- builtin crypto.hash : crypto.HashAlgorithm -> a -> Bytes + +crypto.hash.doc : Doc +crypto.hash.doc = + use Nat + + {{ + `` hash algo a `` hashes any value `a` using + {{ docExample 1 do algo -> (algo : HashAlgorithm) }}. + + ``` + hash Sha3_256 ("all are hashable!", [1, 2, 3, 4]) + ``` + + ``` + hash Blake2b_512 [Left "including functions!", Right do 1 + 1] + ``` + + Also see @inlineSignature{hashBytes} for hashing a {type Bytes} value. + + {{ + docCallout + (Some {{ ℹ️ }}) + {{ + While you can also pass a {type Bytes} to {hash}, the hash produced by this + function mixes in information about the type of the input value (and this + is necessary to ensure that values of different types don't coicidentally + hash the same). If you need the results to agree with some other system + that is just expecting a hash of nothing but the bytes, use {hashBytes}. + }} }} + }} + +-- builtin crypto.HashAlgorithm.Blake2b_256 : crypto.HashAlgorithm + +crypto.HashAlgorithm.Blake2b_256.doc : Doc +crypto.HashAlgorithm.Blake2b_256.doc = + {{ + The + [BLAKE2b 256-bit](https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2) + hash algorithm. + + # Example + + ``` + hash Blake2b_256 (Text.toUtf8 "hello") + ``` + }} + +-- builtin crypto.HashAlgorithm.Blake2b_512 : crypto.HashAlgorithm + +crypto.HashAlgorithm.Blake2b_512.doc : Doc +crypto.HashAlgorithm.Blake2b_512.doc = + {{ + The + [BLAKE2b 512-bit](https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2) + hash algorithm. + + # Example + + ``` + hash Blake2b_512 (Text.toUtf8 "hello") + ``` + }} + +-- builtin crypto.HashAlgorithm.Blake2s_256 : crypto.HashAlgorithm + +crypto.HashAlgorithm.Blake2s_256.doc : Doc +crypto.HashAlgorithm.Blake2s_256.doc = + {{ + The + [BLAKE2s 256-bit](https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2) + hash algorithm. + + # Example + + ``` + hash Blake2s_256 (Text.toUtf8 "hello") + ``` + }} + +crypto.HashAlgorithm.doc : Doc +crypto.HashAlgorithm.doc = + {{ + A hashing algorithm. Passed as a parameter to functions like {hash} or + {hashBytes}. + + __Instances:__ + + * {Sha1}: See [SHA-1](https://en.wikipedia.org/wiki/SHA-1) + * {Sha2_256}: See [SHA-256](https://en.wikipedia.org/wiki/SHA-2), 256-bit + output + * {Sha2_512}: See [SHA-512](https://en.wikipedia.org/wiki/SHA-2), 512-bit + output + * {Sha3_256}: See [SHA3-256](https://en.wikipedia.org/wiki/SHA-3), 256-bit + output + * {Sha3_512}: See [SHA3-512](https://en.wikipedia.org/wiki/SHA-3), 512-bit + output + * {Blake2s_256}: See + [BLAKE2s-256]({{ + docWord "https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2" + }}), 256-bit output + * {Blake2b_256}: See + [BLAKE2b-256]({{ + docWord "https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2" + }}), 256-bit output + * {Blake2b_512}: See + [BLAKE2b-512]({{ + docWord "https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2" + }}), 512-bit output + + You can also use `find : HashAlgorithm` to find instances. + }} + +-- builtin crypto.HashAlgorithm.Md5 : crypto.HashAlgorithm + +crypto.HashAlgorithm.Md5.doc : Doc +crypto.HashAlgorithm.Md5.doc = + {{ + The [Md5](https://en.wikipedia.org/wiki/MD5) message digest algorithm. + + # Example + + ``` + hash Md5 (Text.toUtf8 "hello") + ``` + }} + +-- builtin crypto.HashAlgorithm.Sha1 : crypto.HashAlgorithm + +crypto.HashAlgorithm.Sha1.doc : Doc +crypto.HashAlgorithm.Sha1.doc = + {{ The [SHA1](https://en.wikipedia.org/wiki/SHA-1) hash algorithm. }} + +-- builtin crypto.HashAlgorithm.Sha2_256 : crypto.HashAlgorithm + +crypto.HashAlgorithm.Sha2_256.doc : Doc +crypto.HashAlgorithm.Sha2_256.doc = + {{ + The [SHA-2 256-bit](https://en.wikipedia.org/wiki/SHA-2) hash algorithm. + + # Example + + ``` + hash Sha2_256 (Text.toUtf8 "hello") + ``` + }} + +-- builtin crypto.HashAlgorithm.Sha2_512 : crypto.HashAlgorithm + +crypto.HashAlgorithm.Sha2_512.doc : Doc +crypto.HashAlgorithm.Sha2_512.doc = + {{ + The [SHA-2 512-bit](https://en.wikipedia.org/wiki/SHA-2) hash algorithm. + + # Example + + ``` + hash Sha2_512 (Text.toUtf8 "hello") + ``` + }} + +-- builtin crypto.HashAlgorithm.Sha3_256 : crypto.HashAlgorithm + +crypto.HashAlgorithm.Sha3_256.doc : Doc +crypto.HashAlgorithm.Sha3_256.doc = + {{ + The [SHA-3 256-bit](https://en.wikipedia.org/wiki/SHA-3) hash algorithm. + + # Example + + ``` + hash Sha3_256 (Text.toUtf8 "hello") + ``` + }} + +-- builtin crypto.HashAlgorithm.Sha3_512 : crypto.HashAlgorithm + +crypto.HashAlgorithm.Sha3_512.doc : Doc +crypto.HashAlgorithm.Sha3_512.doc = + {{ + The [SHA-3 512-bit](https://en.wikipedia.org/wiki/SHA-3) hash algorithm. + + # Example + + ``` + hash Sha3_512 (Text.toUtf8 "hello") + ``` + }} + +-- builtin crypto.hashBytes : crypto.HashAlgorithm -> Bytes -> Bytes + +crypto.hashBytes.doc : Doc +crypto.hashBytes.doc = + use fromList impl + {{ + `` hashBytes algo bs `` hashes bytes using + {{ docExample 1 do algo -> (algo : HashAlgorithm) }}. + + ``` + hashBytes Sha2_256 0xs2a89d92b + ``` + + ``` + hashBytes Blake2b_256 0xs2a89d92b + ``` + + Also see @inlineSignature{hash} for hashing an arbitrary value. + }} + +-- builtin crypto.hmac : crypto.HashAlgorithm -> Bytes -> a -> Bytes + +crypto.hmac.doc : Doc +crypto.hmac.doc = + use Text toUtf8 + {{ + Compute the hash-based message authentication code (HMAC) of an arbitrary + value using a secret key and a cryptographic hash function. The result is a + {type Bytes} value. + + `` hmac alg key msg `` uses the cryptographic hash function `alg` to compute + the HMAC of `msg` using `key` as the secret key. + + Available hash functions include: + + * {Sha1} + * {Sha2_256} + * {Sha2_512} + * {Sha3_256} + * {Sha3_512} + * {Blake2b_256} + * {Blake2b_512} + * {Blake2s_256} + + {{ + docCallout + (Some {{ ‼️ }}) + {{ + This function hashes a Unison-specific byte encoding of the message. If you + want fine control over how the message is encoded, first convert it to + {type Bytes} and then use {hmacBytes} instead. See the documentation of + {hmacBytes} for details. + }} }} + + # Examples + + ``` + hmac Sha1 (toUtf8 "big secret") "hello world" + ``` + + ``` + hmac Sha2_256 (toUtf8 "big secret") "hello world" + ``` + }} + +crypto.hmac.verify : HashAlgorithm -> Bytes -> a -> Bytes -> Boolean +crypto.hmac.verify algo key plaintext digest = + expected = hmac algo key plaintext + constantTimeEqual expected digest + +crypto.hmac.verify.doc : Doc +crypto.hmac.verify.doc = + {{ + Verify the signature of an HMAC digest created with {hmac}. + + Usage: + + ``` + hmacKey = Text.toUtf8 "MY SECRET HMAC KEY" + value = ["Some arbitrary unison object!"] + algo = Sha3_256 + digest = hmac algo hmacKey value + isValid = hmac.verify algo hmacKey value digest + isValid + ``` + }} + +test> crypto.hmac.verify.test.happy = test.verify do + labeled "Test verify correctly verifies identical values" do + hmacKey = Text.toUtf8 "MY SECRET HMAC KEY" + plainText = Random.bytes 50 + algo = Sha3_256 + digest = hmac algo hmacKey plainText + isValid = hmac.verify algo hmacKey plainText digest + ensure isValid + +test> crypto.hmac.verify.test.sad = + test.verify do + labeled + "Test verify correctly returns false when verifying incorrect payloads" + do + use Random bytes + hmacKey = Text.toUtf8 "MY SECRET HMAC KEY" + plainText = bytes 50 + badPlainText = once do + repeatForever() + badPlainText = bytes 50 + guard (badPlainText !== plainText) + algo = Sha3_256 + digest = hmac algo hmacKey plainText + isValid = hmac.verify algo hmacKey plainText digest + ensure isValid + +crypto.hmac.verifyBytes : HashAlgorithm -> Bytes -> Bytes -> Bytes -> Boolean +crypto.hmac.verifyBytes algo key plaintext digest = + expected = hmacBytes algo key plaintext + constantTimeEqual expected digest + +crypto.hmac.verifyBytes.doc : Doc +crypto.hmac.verifyBytes.doc = + use Text toUtf8 + {{ + Verify the signature of an HMAC digest created with {hmacBytes}. + + Usage: + + ``` + hmacKey = toUtf8 "MY SECRET HMAC KEY" + plainText = toUtf8 "Hello, world!" + algo = Sha3_256 + digest = hmacBytes algo hmacKey plainText + isValid = verifyBytes algo hmacKey plainText digest + isValid + ``` + }} + +test> crypto.hmac.verifyBytes.test.happy = test.verify do + labeled "Test verifyBytes correctly verifies identical values" do + hmacKey = Text.toUtf8 "MY SECRET HMAC KEY" + plainText = Random.bytes 50 + algo = Sha3_256 + digest = hmacBytes algo hmacKey plainText + isValid = verifyBytes algo hmacKey plainText digest + ensure isValid + +test> crypto.hmac.verifyBytes.test.sad = + test.verify do + labeled + "Test verifyBytes correctly returns false when verifying incorrect payloads" + do + use Random bytes + hmacKey = Text.toUtf8 "MY SECRET HMAC KEY" + plainText = bytes 50 + badPlainText = once do + repeatForever() + badPlainText = bytes 50 + guard (badPlainText !== plainText) + algo = Sha3_256 + digest = hmacBytes algo hmacKey plainText + isValid = verifyBytes algo hmacKey plainText digest + ensure isValid + +-- builtin crypto.hmacBytes : crypto.HashAlgorithm -> Bytes -> Bytes -> Bytes + +crypto.hmacBytes.doc : Doc +crypto.hmacBytes.doc = + use Text toUtf8 + {{ + Compute the hash-based message authentication code (HMAC) of a {type Bytes} + value using a secret key and a cryptographic hash function. The result is a + {type Bytes} value. + + `` hmacBytes alg key msg `` uses the cryptographic hash function `alg` to + compute the HMAC of `msg` using `key` as the secret key. + + Available hash functions include: + + * {Sha1} + * {Sha2_256} + * {Sha2_512} + * {Sha3_256} + * {Sha3_512} + * {Blake2b_256} + * {Blake2b_512} + * {Blake2s_256} + + # Examples + + ``` + hmacBytes + Sha1 + (toUtf8 "big secret") + (toUtf8 "The quick brown fox jumps over the lazy dog") + ``` + + ``` + hmacBytes + Sha2_256 + (toUtf8 "big secret") + (toUtf8 "The quick brown fox jumps over the lazy dog.") + ``` + }} + +crypto.Rsa.PrivateKey.doc : Doc +crypto.Rsa.PrivateKey.doc = + {{ + A private key for the RSA signature scheme. + + # Example + + ``` + catch do + Rsa.PrivateKey.PrivateKey + (fromBase64 + (Text.toUtf8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")) + ``` + }} + +crypto.Rsa.PublicKey.doc : Doc +crypto.Rsa.PublicKey.doc = + {{ + A public key for the RSA signature scheme. + + # Example + + ``` + catch do + Rsa.PublicKey.PublicKey + (fromBase64 + (Text.toUtf8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")) + ``` + }} + +crypto.Rsa.sign : Rsa.PrivateKey -> Bytes ->{Exception} Rsa.Signature +crypto.Rsa.sign = cases + Rsa.PrivateKey.PrivateKey p, msg -> + Rsa.Signature.Signature (Either.toException (Rsa.sign.impl p msg)) + +crypto.Rsa.sign.doc : Doc +crypto.Rsa.sign.doc = + use Rsa sign verify + use Rsa.PrivateKey PrivateKey + use Rsa.PublicKey PublicKey + use Text toUtf8 + use fromList impl + {{ + Signs a message with an RSA private key and returns the signature. + + # Example + + The private and public keys for these examples were generated with: + + * `openssl genrsa -out private_key.pem 1024` to generate the key pair + * `openssl rsa -in private_key.pem -outform DER | xxd -p` to encode the + private key + * `openssl rsa -in private_key.pem -outform DER -pubout | xxd -p` to encode + the public key + + ``` + catch do + private = + PrivateKey + 0xs30820276020100300d06092a864886f70d0101010500048202603082025c02010002818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab291502030100010281807cdc23a4fc3619d93f8293b728af848d0c0fdd603269d5bd7b99f760a9c22065d08693dbdcddf1f5863306133d694819e04d789aef4e95343b601507b8d9eac4492e6d7031b035c5d84eceaa9686b292712632d33b3303af84314d7920bc3d45f90d7818fc2587b129196d378ee4ed3e6b8d9010d504bb6470ff53e7c5fb17a1024100d67cbcf113d24325fcef12a778dc47c7060055290b68287649ef092558daccb61c4e7bc290740b75a29d4356dcbd66d18b0860dbff394cc8ff3d94d57617adbd024100c765d8261dd3d8e0d3caf11ab7b212eed181354215687ca6387283e4f0be16e79c8f298be0a70c7734dea78ea65128517d693cabfa4c0ff5328f2abb85d2023902403ca41dc347285e65c22251b2d9bfe5e7463217e1b7e0e5f7b3a58a7f6da4c6d60220ca6ad2ee8c42e10bf77afa83ee2af6551315800e52404db1ba7fb398b43d02410084877d85c0177933ddb12a554eb8edfa8b872c85d2c2d2ee8be019280696e19469ab81bab5c371f69d4e4be1f54b45d7fbda017870f1333e0eafb78051ee8689024061f694c12e934c44b7734f62d1b2a3d3624a4980e1b8e066d78dbabd2436654fbb9d9701425900daaafa1e031310e8a580520bb9e1c1288c669fce252bad1e65 + public = + PublicKey + 0xs30819f300d06092a864886f70d010101050003818d0030818902818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab29150203010001 + signature = sign private (toUtf8 "Hello, world!") + verify public (toUtf8 "Hello, world!") signature + ``` + + ``` + catch do + private = + PrivateKey + 0xs30820276020100300d06092a864886f70d0101010500048202603082025c02010002818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab291502030100010281807cdc23a4fc3619d93f8293b728af848d0c0fdd603269d5bd7b99f760a9c22065d08693dbdcddf1f5863306133d694819e04d789aef4e95343b601507b8d9eac4492e6d7031b035c5d84eceaa9686b292712632d33b3303af84314d7920bc3d45f90d7818fc2587b129196d378ee4ed3e6b8d9010d504bb6470ff53e7c5fb17a1024100d67cbcf113d24325fcef12a778dc47c7060055290b68287649ef092558daccb61c4e7bc290740b75a29d4356dcbd66d18b0860dbff394cc8ff3d94d57617adbd024100c765d8261dd3d8e0d3caf11ab7b212eed181354215687ca6387283e4f0be16e79c8f298be0a70c7734dea78ea65128517d693cabfa4c0ff5328f2abb85d2023902403ca41dc347285e65c22251b2d9bfe5e7463217e1b7e0e5f7b3a58a7f6da4c6d60220ca6ad2ee8c42e10bf77afa83ee2af6551315800e52404db1ba7fb398b43d02410084877d85c0177933ddb12a554eb8edfa8b872c85d2c2d2ee8be019280696e19469ab81bab5c371f69d4e4be1f54b45d7fbda017870f1333e0eafb78051ee8689024061f694c12e934c44b7734f62d1b2a3d3624a4980e1b8e066d78dbabd2436654fbb9d9701425900daaafa1e031310e8a580520bb9e1c1288c669fce252bad1e65 + public = + PublicKey + 0xs30819f300d06092a864886f70d010101050003818d0030818902818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab29150203010001 + signature = sign private (toUtf8 "Hello, world!") + verify public (toUtf8 "Bogus message") signature + ``` + + See also {verify}. + }} + +-- builtin crypto.Rsa.sign.impl : Bytes -> Bytes -> Either Failure Bytes + +test> crypto.Rsa.sign.test = + test.verify do + use fromList impl + actual = + catch do + private = + Rsa.PrivateKey.PrivateKey + 0xs30820276020100300d06092a864886f70d0101010500048202603082025c02010002818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab291502030100010281807cdc23a4fc3619d93f8293b728af848d0c0fdd603269d5bd7b99f760a9c22065d08693dbdcddf1f5863306133d694819e04d789aef4e95343b601507b8d9eac4492e6d7031b035c5d84eceaa9686b292712632d33b3303af84314d7920bc3d45f90d7818fc2587b129196d378ee4ed3e6b8d9010d504bb6470ff53e7c5fb17a1024100d67cbcf113d24325fcef12a778dc47c7060055290b68287649ef092558daccb61c4e7bc290740b75a29d4356dcbd66d18b0860dbff394cc8ff3d94d57617adbd024100c765d8261dd3d8e0d3caf11ab7b212eed181354215687ca6387283e4f0be16e79c8f298be0a70c7734dea78ea65128517d693cabfa4c0ff5328f2abb85d2023902403ca41dc347285e65c22251b2d9bfe5e7463217e1b7e0e5f7b3a58a7f6da4c6d60220ca6ad2ee8c42e10bf77afa83ee2af6551315800e52404db1ba7fb398b43d02410084877d85c0177933ddb12a554eb8edfa8b872c85d2c2d2ee8be019280696e19469ab81bab5c371f69d4e4be1f54b45d7fbda017870f1333e0eafb78051ee8689024061f694c12e934c44b7734f62d1b2a3d3624a4980e1b8e066d78dbabd2436654fbb9d9701425900daaafa1e031310e8a580520bb9e1c1288c669fce252bad1e65 + Rsa.sign private (Text.toUtf8 "Hello, world!") + expected = + Rsa.Signature.Signature + 0xs1c0cc9384fa0053b2cc2d2e9fe98b9a21ffc90a4996610934c56b101f64145f030d480cf33a9caee0cf1f1472979eda955dada655d7ae96c98183987ba7fd154f08dc281cccca3e6deb99b40d2e161ab65eba2236fa8004dba7002103b5dad5d678f832e20e552800d8722d0c34731266440b4a4ae77991c039a918d15fb8249 + ensureEqual actual (Right expected) + +crypto.Rsa.Signature.doc : Doc +crypto.Rsa.Signature.doc = + {{ + A signature for the Ed25519 signature scheme. + + # Example + + ``` + catch do + Rsa.Signature.Signature + (fromBase64 + (Text.toUtf8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")) + ``` + }} + +crypto.Rsa.verify : + Rsa.PublicKey -> Bytes -> Rsa.Signature ->{Exception} Boolean +crypto.Rsa.verify = cases + Rsa.PublicKey.PublicKey p, msg, Rsa.Signature.Signature s -> + Either.toException (Rsa.verify.impl p msg s) + +crypto.Rsa.verify.doc : Doc +crypto.Rsa.verify.doc = + use Rsa sign verify + use Rsa.PrivateKey PrivateKey + use Rsa.PublicKey PublicKey + use Text toUtf8 + use fromList impl + {{ + Verifies an RSA signature on a message. + + # Examples + + The private and public keys for these examples were generated with: + + * `openssl genrsa -out private_key.pem 1024` to generate the key pair + * `openssl rsa -in private_key.pem -outform DER | xxd -p` to encode the + private key + * `openssl rsa -in private_key.pem -outform DER -pubout | xxd -p` to encode + the public key + + ``` + catch do + private = + PrivateKey + 0xs30820276020100300d06092a864886f70d0101010500048202603082025c02010002818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab291502030100010281807cdc23a4fc3619d93f8293b728af848d0c0fdd603269d5bd7b99f760a9c22065d08693dbdcddf1f5863306133d694819e04d789aef4e95343b601507b8d9eac4492e6d7031b035c5d84eceaa9686b292712632d33b3303af84314d7920bc3d45f90d7818fc2587b129196d378ee4ed3e6b8d9010d504bb6470ff53e7c5fb17a1024100d67cbcf113d24325fcef12a778dc47c7060055290b68287649ef092558daccb61c4e7bc290740b75a29d4356dcbd66d18b0860dbff394cc8ff3d94d57617adbd024100c765d8261dd3d8e0d3caf11ab7b212eed181354215687ca6387283e4f0be16e79c8f298be0a70c7734dea78ea65128517d693cabfa4c0ff5328f2abb85d2023902403ca41dc347285e65c22251b2d9bfe5e7463217e1b7e0e5f7b3a58a7f6da4c6d60220ca6ad2ee8c42e10bf77afa83ee2af6551315800e52404db1ba7fb398b43d02410084877d85c0177933ddb12a554eb8edfa8b872c85d2c2d2ee8be019280696e19469ab81bab5c371f69d4e4be1f54b45d7fbda017870f1333e0eafb78051ee8689024061f694c12e934c44b7734f62d1b2a3d3624a4980e1b8e066d78dbabd2436654fbb9d9701425900daaafa1e031310e8a580520bb9e1c1288c669fce252bad1e65 + public = + PublicKey + 0xs30819f300d06092a864886f70d010101050003818d0030818902818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab29150203010001 + signature = sign private (toUtf8 "Hello, world!") + verify public (toUtf8 "Hello, world!") signature + ``` + + ``` + catch do + private = + PrivateKey + 0xs30820276020100300d06092a864886f70d0101010500048202603082025c02010002818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab291502030100010281807cdc23a4fc3619d93f8293b728af848d0c0fdd603269d5bd7b99f760a9c22065d08693dbdcddf1f5863306133d694819e04d789aef4e95343b601507b8d9eac4492e6d7031b035c5d84eceaa9686b292712632d33b3303af84314d7920bc3d45f90d7818fc2587b129196d378ee4ed3e6b8d9010d504bb6470ff53e7c5fb17a1024100d67cbcf113d24325fcef12a778dc47c7060055290b68287649ef092558daccb61c4e7bc290740b75a29d4356dcbd66d18b0860dbff394cc8ff3d94d57617adbd024100c765d8261dd3d8e0d3caf11ab7b212eed181354215687ca6387283e4f0be16e79c8f298be0a70c7734dea78ea65128517d693cabfa4c0ff5328f2abb85d2023902403ca41dc347285e65c22251b2d9bfe5e7463217e1b7e0e5f7b3a58a7f6da4c6d60220ca6ad2ee8c42e10bf77afa83ee2af6551315800e52404db1ba7fb398b43d02410084877d85c0177933ddb12a554eb8edfa8b872c85d2c2d2ee8be019280696e19469ab81bab5c371f69d4e4be1f54b45d7fbda017870f1333e0eafb78051ee8689024061f694c12e934c44b7734f62d1b2a3d3624a4980e1b8e066d78dbabd2436654fbb9d9701425900daaafa1e031310e8a580520bb9e1c1288c669fce252bad1e65 + public = + PublicKey + 0xs30819f300d06092a864886f70d010101050003818d0030818902818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab29150203010001 + signature = sign private (toUtf8 "Hello, world!") + verify public (toUtf8 "Bogus message") signature + ``` + + See also {sign}. + }} + +-- builtin crypto.Rsa.verify.impl : +-- Bytes -> Bytes -> Bytes -> Either Failure Boolean + +test> crypto.Rsa.verify.test = + test.verify do + use Text toUtf8 + use fromList impl + result = + catch do + private = + Rsa.PrivateKey.PrivateKey + 0xs30820276020100300d06092a864886f70d0101010500048202603082025c02010002818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab291502030100010281807cdc23a4fc3619d93f8293b728af848d0c0fdd603269d5bd7b99f760a9c22065d08693dbdcddf1f5863306133d694819e04d789aef4e95343b601507b8d9eac4492e6d7031b035c5d84eceaa9686b292712632d33b3303af84314d7920bc3d45f90d7818fc2587b129196d378ee4ed3e6b8d9010d504bb6470ff53e7c5fb17a1024100d67cbcf113d24325fcef12a778dc47c7060055290b68287649ef092558daccb61c4e7bc290740b75a29d4356dcbd66d18b0860dbff394cc8ff3d94d57617adbd024100c765d8261dd3d8e0d3caf11ab7b212eed181354215687ca6387283e4f0be16e79c8f298be0a70c7734dea78ea65128517d693cabfa4c0ff5328f2abb85d2023902403ca41dc347285e65c22251b2d9bfe5e7463217e1b7e0e5f7b3a58a7f6da4c6d60220ca6ad2ee8c42e10bf77afa83ee2af6551315800e52404db1ba7fb398b43d02410084877d85c0177933ddb12a554eb8edfa8b872c85d2c2d2ee8be019280696e19469ab81bab5c371f69d4e4be1f54b45d7fbda017870f1333e0eafb78051ee8689024061f694c12e934c44b7734f62d1b2a3d3624a4980e1b8e066d78dbabd2436654fbb9d9701425900daaafa1e031310e8a580520bb9e1c1288c669fce252bad1e65 + public = + Rsa.PublicKey.PublicKey + 0xs30819f300d06092a864886f70d010101050003818d0030818902818100a7104b2f20725896076e629ccedbcd6907b16694c6e3d8768b5e0e685670b49616e796c588e5aafb92ef986c1a42c021fed0bdc99212c969cdab98087a0ee4c2f4acd4b6049a87a96afc45668329a3cf21a86fb13b488bbe9fefa1cd5a459014f0d0101378e9661e11b73acf54c8a91141ac90309e7fb6ed69b4e63230ab29150203010001 + signature = Rsa.sign private (toUtf8 "Hello, world!") + Rsa.verify public (toUtf8 "Hello, world!") signature + ensureEqual result (Right true) + +data.Array.append : data.Array a -> data.Array a -> data.Array a +data.Array.append arr1 arr2 = unsafeRun! do + Scope.run do + (Arr off1 len1 raw1) = arr1 + (Arr off2 len2 raw2) = arr2 + use Nat + + use data.Array.Raw copyTo! + m = Scope.Raw.array (len1 + len2) + copyTo! m 0 raw1 off1 len1 + copyTo! m len1 raw2 off2 len2 + Arr 0 (len1 + len2) (Array.Raw.freeze! m) + +data.Array.append.doc : Doc +data.Array.append.doc = + use Array fromList + {{ + Appends two arrays. This is an O(n) operation. The original arrays are not + modified. + + # Examples + + ``` + Array.toList (Array.append (fromList [1, 2, 3]) (fromList [4, 5, 6])) + ``` + + # See also + + * {Array.cons} for a version of this that prepends an element to an array. + * {Array.snoc} for a version of this that appends an element to an array. + }} + +data.Array.Arr.doc : Doc +data.Array.Arr.doc = + {{ + Constructs an immutable {type data.Array} from its internal representation. + + `` Arr off len arr `` represents an array with `len` elements from the + underlying {type data.Array.Raw} array, starting at offset `off`. + + # Example + + @source{Array.fromList} + }} + +data.Array.ArrayFailure.doc : Doc +data.Array.ArrayFailure.doc = + {{ + A type of {type Failure} raised by {type data.Array} operations, for example + when an index is out of bounds. + + # Example + + ``` + catch do Array.take 10 (data.Array.of 5 0) + ``` + }} + +data.Array.ArrayFailure.raise : Text -> a ->{Exception} b +data.Array.ArrayFailure.raise msg payload = + fail = Failure (typeLink ArrayFailure) msg (Any payload) + Exception.raise fail + +data.Array.ArrayFailure.raise.doc : Doc +data.Array.ArrayFailure.raise.doc = + {{ Raises an exception using the {type ArrayFailure} type tag }} + +data.Array.at : Nat -> data.Array a -> Optional a +data.Array.at i arr = hush do data.Array.read arr i + +data.Array.at.doc : Doc +data.Array.at.doc = + use Array at fromList + {{ + Gets the element at the given index, if it exists, otherwise returns + ``None``. + + # Examples + + ``` + at 0 (fromList [1, 2, 3]) + ``` + + ``` + at 3 (fromList [1, 2, 3]) + ``` + + # See also + + * {Array.at!} for a version of this that aborts if the index is out of + bounds. + * {Array.unsafeAt} simply crashes if the index is out of bounds. + * {data.Array.read} for a version that throws an {type Exception} if the + index is out of bounds. + }} + +data.Array.at! : Nat -> data.Array a ->{Abort} a +data.Array.at! i arr = abortOnException do data.Array.read arr i + +data.Array.at!.doc : Doc +data.Array.at!.doc = + use Array at! fromList + {{ + Gets the element at the given index, calling {abort} if it does not exist. + + # Examples + + ``` + toOptional! do at! 0 (fromList [1, 2, 3]) + ``` + + ``` + toOptional! do at! 3 (fromList [1, 2, 3]) + ``` + + # See also + + * {Array.at} for a version of this that returns `` None `` if the index is + out of bounds. + * {Array.unsafeAt} simply crashes if the index is out of bounds. + * {data.Array.read} for a version that throws an {type Exception} if the + index is out of bounds. + }} + +data.Array.cons : a -> data.Array a -> data.Array a +data.Array.cons x arr = unsafeRun! do + Scope.run do + (Arr off len raw) = arr + use Nat + + m = Scope.Raw.array (len + 1) + data.Array.Raw.copyTo! m 1 raw off len + Raw.write m 0 x + Arr 0 (len + 1) (Array.Raw.freeze! m) + +data.Array.cons.doc : Doc +data.Array.cons.doc = + {{ + Prepends an element to an array. This is an O(n) operation. The original + array is not modified. + + # Example + + ``` + Array.toList (Array.cons 0 (Array.fromList [1, 2, 3])) + ``` + + # See also + + * {Array.snoc} for a version of this that appends an element to an array. + * {Array.append} for a version of this that appends two arrays. + }} + +data.Array.doc : Doc +data.Array.doc = + use Array toList + {{ + {type data.Array} is the type of fixed size, flat arrays. The array stores + references to the elements rather than the elements themselves, i.e. the + elements are __boxed__. Boxing is less efficient for fixed size types like + {type Nat} or {type Int}, so for those you can use {type data.ByteArray} + which stores the elements directly (unboxed) at the cost of a less ergonomic + interface. + + The values in a {type data.Array} also store an offset and length relative to + the underlying array, allowing for fast slicing operations. + + The {type data.Array} is immutable (no destructive updates), and homogeneous + (all elements have the same type). + + # Constructing arrays + + The most common way to construct an {type data.Array} is to create a + mutable array, fill it with values, and then freeze it: + + ``` + catch do + toList + (Scope.run do + a = mutable.Array.of 0 10 + initialize = do + n = Each.range 0 10 + Array.write a n (Nat.popCount n) + Each.run initialize + Array.freeze a) + ``` + + You can also directly construct an {type data.Array} from a list of values: + + ``` + data.Array.size (Array.fromList [1, 2, 3]) + ``` + + You can construct an array of a certain size where all values are the same: + + ``` + toList (data.Array.of "boing" 5) + ``` + }} + +data.Array.drop : Nat -> data.Array a ->{Exception} data.Array a +data.Array.drop n = cases + Arr off len arr| n Nat.<= len -> Arr (off Nat.+ n) (len Nat.- n) arr + _ -> ArrayFailure.raise "data.Array.drop: not enough elements" n + +data.Array.drop.doc : Doc +data.Array.drop.doc = + {{ + Returns a new {type data.Array} with some elements removed from the front. + + This operation is fast, replacing only the offset and length, and keeping the + underlying storage. The potential downside is that more memory may be used + than necessary. + + ``` + catch do Array.toList (Array.drop 2 (Array.fromList [1, 2, 3, 4, 5])) + ``` + }} + +data.Array.empty : data.Array a +data.Array.empty = Scope.run do Arr 0 0 (Array.Raw.freeze! (Scope.Raw.array 0)) + +data.Array.empty.doc : Doc +data.Array.empty.doc = + {{ + The empty array. + + # Example + + ``` + Array.toList Array.empty + ``` + + # See also + + * {Array.singleton} to create an array with a single element. + * {Array.fromList} to create an array from a {type List}. + }} + +data.Array.fill : Nat -> a -> data.Array a +data.Array.fill = flip data.Array.of + +data.Array.fill.doc : Doc +data.Array.fill.doc = + use data.Array fill + {{ + `` fill sz x `` creates a new {type data.Array} of size `sz`, filled with the + value `x`. + + # Example + + ``` + Array.toList (fill 4 0) + ``` + + # See also + + * {data.Array.of} for a version of this that takes the arguments in the + opposite order. + }} + +data.Array.find : (a ->{g} Boolean) -> data.Array a ->{g} Optional a +data.Array.find p arr = + Array.firstIndexOf p arr |> Optional.flatMap (flip Array.at arr) + +data.Array.find.doc : Doc +data.Array.find.doc = + use Array find + use Nat == + {{ + `` find p arr `` returns the first element of the array `arr` that satisfies + the predicate `p`, or {None} if no such element exists. + + # Example + + ``` + find ((==) 3) (Array.fromList [1, 2, 3, 4, 5]) + ``` + + # See also + + * {Array.findLast} - returns the last element that satisfies the predicate. + * {Array.firstIndexOf} - returns the index of the first element that + satisfies the predicate. + * {Array.lastIndexOf} - returns the index of the last element that + satisfies the predicate. + }} + +test> data.Array.find.tests.consistentWithList = test.verify do + use Nat == + zeroToTen = do Random.natIn 0 10 + list = Random.listOf zeroToTen zeroToTen + arr = Array.fromList list + p = zeroToTen() + ensureEqual (Array.find ((==) p) arr) (List.find ((==) p) list) + +data.Array.findLast : (a ->{g} Boolean) -> data.Array a ->{g} Optional a +data.Array.findLast p arr = + Array.lastIndexOf p arr |> Optional.flatMap (flip Array.at arr) + +data.Array.findLast.doc : Doc +data.Array.findLast.doc = + {{ + `` Array.findLast p arr `` returns the last element of the array `arr` that + satisfies the predicate `p`, or {None} if no such element exists. + }} + +data.Array.firstIndexOf : (a ->{g} Boolean) -> data.Array a ->{g} Optional Nat +data.Array.firstIndexOf p arr = + use Nat + < + sz = data.Array.size arr + go : Nat -> Optional Nat + go ix = + if ix < sz then + match Array.at ix arr with + Some x -> if p x then Some ix else go (ix + 1) + None -> None + else None + go 0 + +data.Array.firstIndexOf.doc : Doc +data.Array.firstIndexOf.doc = + use Array firstIndexOf lastIndexOf + use Nat == + {{ + `` firstIndexOf p arr `` returns the index of the first element of the array + `arr` that satisfies the predicate `p`, or {None} if no such element exists. + + # Example + + ``` + firstIndexOf ((==) 3) (Array.fromList [1, 2, 3, 4, 5]) + ``` + + # See also + + * {lastIndexOf} - returns the index of the last element that satisfies the + predicate. + * {Array.find} - returns the element itself, not just the index. + * {Array.findLast} - returns the last element that satisfies the predicate. + * {lastIndexOf} - returns the index of the last element that satisfies the + predicate. + }} + +test> data.Array.firstIndexOf.tests.finds = test.verify do + use Array firstIndexOf + use List ++ :+ size + use Nat == + use Random listOf natIn + el = do natIn 1 10 + sz = do natIn 0 10 + list1 = listOf el sz + list2 = listOf el sz + arr = Array.fromList (list1 :+ 0 ++ list2) + ensureEqual (firstIndexOf ((==) 0) arr) (Some (size list1)) + ensureEqual (Array.lastIndexOf ((==) 0) arr) (Some (size list1)) + ensureEqual (firstIndexOf ((==) 11) arr) None + +data.Array.foldLeft : (a ->{g} b ->{h} a) -> a -> data.Array b ->{g, h} a +data.Array.foldLeft f acc arr = unsafeRun! do + Scope.run do + (Arr off len raw) = arr + use Nat + >= + go n acc = + if n >= len then acc + else + x = data.Array.Raw.read raw (off + n) + acc' = f acc x + go (n + 1) acc' + go 0 acc + +data.Array.foldLeft.doc : Doc +data.Array.foldLeft.doc = + use Array fromList + use List +: + use Nat + + {{ + Folds the elements in an array from left to right, using the given binary + operator and initial accumulator. + + # Examples + + ``` + Array.foldLeft (+) 0 (fromList [1, 2, 3]) + ``` + + ``` + Array.foldLeft (flip (+:)) [] (fromList [1, 2, 3]) + ``` + + ``` + Array.foldLeft (+) 0 (fromList []) + ``` + + # See also + + * {Array.foldRight} to fold from right to left. + * {List.foldLeft} for the {type List} version of this. + }} + +data.Array.foldRight : (a ->{g} b ->{h} b) -> b -> data.Array a ->{g, h} b +data.Array.foldRight f acc arr = unsafeRun! do + Scope.run do + (Arr off len raw) = arr + use Nat + - == + go n acc = + if n == 0 then acc + else + x = data.Array.Raw.read raw (off + n - 1) + acc' = f x acc + go (n - 1) acc' + go len acc + +data.Array.foldRight.doc : Doc +data.Array.foldRight.doc = + use Array fromList + use List +: + use Nat + + {{ + Folds the elements in an array from right to left, using the given binary + operator and initial accumulator. + + # Examples + + ``` + Array.foldRight (+) 0 (fromList [1, 2, 3]) + ``` + + ``` + Array.foldRight (x acc -> x +: acc) [] (fromList [1, 2, 3]) + ``` + + ``` + Array.foldRight (+) 0 (fromList []) + ``` + + # See also + + * {Array.foldLeft} to fold from left to right. + * {List.foldRight} for the {type List} version of this. + }} + +data.Array.fromList : [a] -> data.Array a +data.Array.fromList l = Scope.run do + use Nat + + sz = List.size l + dst = Scope.Raw.array sz + go i = cases + [] -> () + x +: xs -> + Raw.write dst i x + go (i + 1) xs + handle go 0 l with impossible + Arr 0 sz (Array.Raw.freeze! dst) + +data.Array.fromList.doc : Doc +data.Array.fromList.doc = + {{ + Creates a new array containing the values of the list. + + ``` + catch do + a = Array.fromList [?x, ?y] + data.Array.read a 0 + ``` + }} + +data.Array.isEmpty : data.Array a -> Boolean +data.Array.isEmpty = cases Arr _ len _ -> len Nat.== 0 + +data.Array.isEmpty.doc : Doc +data.Array.isEmpty.doc = + use Array fromList isEmpty + {{ + Checks if an array is empty. + + # Examples + + ``` + isEmpty (fromList []) + ``` + + ``` + isEmpty (fromList [1, 2, 3]) + ``` + + # See also + + * {data.Array.size} to get the size of an array. + }} + +data.Array.lastIndexOf : (a ->{g} Boolean) -> data.Array a ->{g} Optional Nat +data.Array.lastIndexOf p arr = + sz = data.Array.size arr + go : Nat -> Optional Nat + go ix = + use Nat - > + ix' = ix - 1 + if ix > 0 then + match Array.at ix' arr with + Some x -> if p x then Some ix' else go ix' + None -> None + else None + go sz + +data.Array.lastIndexOf.doc : Doc +data.Array.lastIndexOf.doc = + use Array lastIndexOf + use Nat == + {{ + `` lastIndexOf p arr `` returns the index of the last element of the array + `arr` that satisfies the predicate `p`, or {None} if no such element exists. + + # Example + + ``` + lastIndexOf ((==) 3) (Array.fromList [1, 2, 3, 4, 5]) + ``` + }} + +data.Array.map : (a ->{g} b) -> data.Array a ->{g} data.Array b +data.Array.map f arr = + use Nat + < + sz = data.Array.size arr + e = catch do + Scope.run do + mbarr = MArr 0 sz (Scope.Raw.array sz) + go n = when (n < sz) do + Array.write mbarr n (f (data.Array.read arr n)) + go (n + 1) + go 0 + Array.freeze mbarr + match e with + Left err -> bug ("immutable Array.map threw an exception", e) + Right arr -> arr + +data.Array.map.doc : Doc +data.Array.map.doc = + use Array fromList map toList + use Nat toText + {{ + `` map f arr `` applies the function `f` to each element of the array `arr` + and returns the resulting array. + + # Examples + + ``` + toList (map toText (fromList [1, 2, 3])) + ``` + + ``` + toList (map toText (fromList [])) + ``` + }} + +data.Array.new! : + a + -> Nat + -> (∀ s. mutable.Array {Scope s} a ->{g, Exception, Scope s} ()) + ->{g} data.Array a +data.Array.new! default size init = Scope.run do + ma = Scope.arrayOf default size + handle init ma + with cases + { Exception.raise fail -> _ } -> bug fail + { r } -> () + Array.freeze! ma + +data.Array.new!.doc : Doc +data.Array.new!.doc = + use Array new! toList + {{ + Creates an array filled with a default value, passes it to the provided + initialization function which can mutate the array, then calls + {Array.freeze!} to produce an immutable {type data.Array}. + + ``` + new! "🌸" 5 const() |> toList + ``` + + ``` + new! "🌻" 8 (arr -> Array.write arr 2 "🌹") |> toList + ``` + }} + +test> data.Array.new!.tests = + test.verify do + use Array new! toList + ensure ((new! "hi" 5 const() |> toList) === List.fill 5 "hi") + ensure + ((new! "hi" 5 (arr -> Array.write arr 0 "bye") |> toList) + === ["bye", "hi", "hi", "hi", "hi"]) + +data.Array.of : x -> Nat -> data.Array x +data.Array.of x size = Scope.run do Array.freeze (Scope.arrayOf x size) + +data.Array.of.doc : Doc +data.Array.of.doc = + {{ + `` data.Array.of x sz `` creates a new {type data.Array} of size `sz`, filled + with the value `x`. + }} + +data.Array.randomChoice : data.Array a ->{Exception, Random} a +data.Array.randomChoice array = + randomIndex = Random.natIn 0 (data.Array.size array) + Array.at randomIndex array + |> Optional.toException + "data.Array.randomChoice: empty data.Array" (typeLink data.Array) + +data.Array.randomChoice.doc : Doc +data.Array.randomChoice.doc = + use Array fromList randomChoice + {{ + Picks a random element from the given {type data.Array}. Assumes that the + array is not empty, so an empty array will raise an {type Exception}. + + # Examples + + ``` + catch do lcg 4096 do randomChoice (fromList [0, 3, 5, 7]) + ``` + + ``` + catch do lcg 2510 do randomChoice (fromList [?x, ?y, ?z]) + ``` + + ``` + catch do lcg 128 do randomChoice (fromList [char.digit, hex]) () + ``` + }} + +test> data.Array.randomChoice.test = test.verify do + list = List.range 0 10 + set = Set.fromList list + array = Array.fromList list + Each.repeat 1000 + e = Array.randomChoice array + ensure (Set.contains e set) + +-- builtin data.Array.Raw.copyTo! : +-- mutable.Array.Raw g a +-- -> Nat +-- -> data.Array.Raw a +-- -> Nat +-- -> Nat +-- ->{g, Exception} () + +data.Array.Raw.copyTo!.doc : Doc +data.Array.Raw.copyTo!.doc = + {{ + `copyTo! dst doff src soff len` copies `len` values from `src` to `dst`. + `doff` and `soff` are starting point offsets into the corresponding arrays. + + Note that the destination array/offset comes first. + }} + +data.Array.Raw.doc : Doc +data.Array.Raw.doc = + use Array fromList + {{ + The type {type data.Array.Raw} represents a raw immutable array of + [boxed](https://https://en.wikipedia.org/wiki/Boxing_%28computer_science%29) + values. It's a low-level type that refers to a region of memory managed by + the runtime. + + You should in general avoid using this type directly, and instead use the + higher-level {type data.Array} type. + + The main use case for {type data.Array.Raw} is to serve as the underlying + representation for {type data.Array}: + + @source{type data.Array} + + # Constructing raw arrays + + There is no way to construct a {type data.Array.Raw} directly. Instead, you + must first construct a mutable array using {type mutable.Array.Raw}, and + then freeze it: + + @signatures{Array.Raw.freeze, Array.Raw.freeze!} + + ## Example + + {fromList} constructs a {type data.Array} from a list of {type Nat}, by + first constructing a {type mutable.Array.Raw}, copying the list into it, + and then freezing it into a {type data.Array.Raw}. The + {type data.Array.Raw} is then used as the internal representation of the + {type data.Array}. + + @source{fromList} + + # Using raw arrays + + Read an element from a {type data.Array.Raw}: + + @signature{data.Array.Raw.read} + + Get the number of elements in a {type data.Array.Raw}: + + @signature{data.Array.Raw.size} + + Copy a {type data.Array.Raw} into a {type mutable.Array.Raw}: + + @signature{data.Array.Raw.copyTo!} + }} + +data.Array.Raw.fromList : [a] -> data.Array.Raw a +data.Array.Raw.fromList l = Scope.run do + use Nat + + sz = List.size l + dst = Scope.Raw.array sz + go i = cases + [] -> () + x +: xs -> + Raw.write dst i x + go (i + 1) xs + handle go 0 l + with cases + { r } -> () + { Exception.raise _ -> _ } -> () + Array.Raw.freeze! dst + +-- builtin data.Array.Raw.read : data.Array.Raw a -> Nat ->{Exception} a + +data.Array.Raw.read.doc : Doc +data.Array.Raw.read.doc = + {{ + Reads an element from a {type data.Array.Raw} at a given index. + + # Example + + ``` + catch do + Scope.run do + arr = Array.Raw.freeze! (Scope.Raw.arrayOf ?🌱 10) + data.Array.Raw.read arr 0 + ``` + }} + +-- builtin data.Array.Raw.size : data.Array.Raw a -> Nat + +data.Array.Raw.size.doc : Doc +data.Array.Raw.size.doc = + {{ + Gets the size of a {type data.Array.Raw}. + + # Example + + ``` + catch do + Scope.run do + arr = Array.Raw.freeze! (Scope.Raw.arrayOf ?🌱 10) + data.Array.Raw.size arr + ``` + }} + +data.Array.read : data.Array a -> Nat ->{Exception} a +data.Array.read arr i = + match arr with + Arr off len src| i Nat.< len -> data.Array.Raw.read src (off Nat.+ i) + _ -> ArrayFailure.raise "data.Array.read: index out of bounds" i + +data.Array.read.doc : Doc +data.Array.read.doc = + {{ + Returns the value at the specified, 0-based position in the array. + + An exception is thrown if the index is out of bounds. + + ``` + catch do data.Array.read (Array.fromList [1, 2]) 0 + ``` + }} + +data.Array.singleton : a -> data.Array a +data.Array.singleton a = data.Array.of a 1 + +data.Array.singleton.doc : Doc +data.Array.singleton.doc = + use Array singleton toList + {{ + Creates an array with a single element. + + # Examples + + ``` + toList (singleton 1) + ``` + + ``` + toList (singleton "hello") + ``` + + # See also + + * {data.Array.of} to fill an array with multiple references to the same + value. + * {Array.fromList} to create an array from a {type List}. + }} + +data.Array.size : data.Array a -> Nat +data.Array.size = cases Arr _ length _ -> length + +data.Array.size.doc : Doc +data.Array.size.doc = {{ Gets the number of elements in the array. }} + +data.Array.slice : Nat -> Nat -> data.Array a ->{Exception} data.Array a +data.Array.slice o l = cases + Arr off len arr| o Nat.+ l Nat.<= len -> Arr (off Nat.+ o) l arr + _ -> ArrayFailure.raise "data.Array.slice: not enough elements" (o Nat.+ l) + +data.Array.slice.doc : Doc +data.Array.slice.doc = + use Array slice + {{ + {slice} returns a new {type data.Array} representing a portion of the + original. + + The first argument specifies how many values to drop from the beginning. The + second argument specifies how many values to retain. The original array must + have a length at least as long as the offset plus the new length. + + This operation is fast, replacing only the offset and length, and keeping the + underlying storage. The potential downside is that more memory may be used + than necessary. + + ``` + catch do Array.toList (slice 1 2 (Array.fromList [1, 2, 3, 4, 5])) + ``` + }} + +data.Array.snoc : data.Array a -> a -> data.Array a +data.Array.snoc arr x = unsafeRun! do + Scope.run do + (Arr off len raw) = arr + use Nat + + m = Scope.Raw.array (len + 1) + data.Array.Raw.copyTo! m 0 raw off len + Raw.write m len x + Arr 0 (len + 1) (Array.Raw.freeze! m) + +data.Array.snoc.doc : Doc +data.Array.snoc.doc = + {{ + Appends an element to an array. This is an O(n) operation. The original array + is not modified. + + # Examples + + ``` + Array.toList (Array.snoc (Array.fromList [1, 2, 3]) 4) + ``` + + # See also + + * {Array.cons} for a version of this that prepends an element to an array. + * {Array.append} for a version of this that appends two arrays. + }} + +data.Array.take : Nat -> data.Array t ->{Exception} data.Array t +data.Array.take n = Array.slice 0 n + +data.Array.take.doc : Doc +data.Array.take.doc = + use Array take + {{ + `` take n arr `` returns the first `n` elements of the array `arr`, as an + immutable {type data.Array}. + + This does not copy the array, but merely creates a new view on the same + underlying array. + + Raises an {type ArrayFailure} if `n` is larger than ``data.Array.size arr``. + + # Example + + ``` + catch do Array.toList (take 2 (Array.fromList [?a, ?b, ?c, ?d])) + ``` + }} + +data.Array.toList : data.Array a -> [a] +data.Array.toList = cases + Arr off len arr -> + use List :+ + use Nat + >= + max = off + len + go acc i = + if i >= max then acc else go (acc :+ data.Array.Raw.read arr i) (i + 1) + handle go [] off with impossible + +data.Array.toList.doc : Doc +data.Array.toList.doc = + {{ + Gets a list corresponding to the contents of an array + + ``` + Array.toList (Array.fromList ["hi", "bye"]) + ``` + }} + +data.Array.unsafeAt : Nat -> data.Array a -> a +data.Array.unsafeAt i arr = unsafeRun! do data.Array.read arr i + +data.Array.unsafeAt.doc : Doc +data.Array.unsafeAt.doc = + use Array fromList unsafeAt + {{ + Gets the element at the given index, crashing by calling {bug} if it does not + exist. + + # Examples + + ``` + unsafeAt 0 (fromList [1, 2, 3]) + ``` + + ``` + unsafeAt 3 (fromList [1, 2, 3]) + ``` + + # See also + + * {Array.at} for a version of this that returns `` None `` if the index is + out of bounds. + * {Array.at!} for a version of this that aborts if the index is out of + bounds. + * {data.Array.read} for a version that throws an {type Exception} if the + index is out of bounds. + }} + +(data.Bag.*) : Bag a -> Bag b -> Bag (a, b) +(data.Bag.*) = Bag.convolve Tuple.pair + +data.Bag.*.doc : Doc +data.Bag.*.doc = + use Bag fromText + {{ + The expression `` bx Bag.* by `` is the {type Bag} of all pairs of elements + `(x,y)` where `x` comes from the {type Bag} `bx` and `y` comes from the + {type Bag} `by`. The number of times `(x,y)` occurs in the result is + ``x Nat.* y``. + + # Example + + ``` + use Bag * + bx = fromText "🎩👕👕👖👖👖" + by = fromText "🚗🚕🚕🚙🚙🚙" + Bag.occurrenceList (bx * by) + ``` + }} + +(data.Bag.+) : Bag a -> Bag a -> Bag a +(data.Bag.+) = cases + MkBag b1 -> cases MkBag b2 -> MkBag (Map.unionWith (Nat.+) b1 b2) + +data.Bag.+.doc : Doc +data.Bag.+.doc = + use Bag + fromText + {{ + `` b1 + b2 `` (or equivalently ``b1 + b2``) constructs a new {type Bag} with + elements from `b1` and `b2`. The number of times each element occurs in the + new {type Bag} is the sum of the number of times it occurs in `b1` and the + number of times it occurs in `b2`. + + # Example + + ``` + a = fromText "🍆🌶🍏🍏" + b = fromText "🍏🌶🌶🍏🍏" + Bag.occurrenceList (a + b) + ``` + }} + +(data.Bag.==) : Bag a -> Bag a -> Boolean +b1 data.Bag.== b2 = + use Bag counts + use Map == + counts b1 == counts b2 + +data.Bag.add : k -> Bag k -> Bag k +data.Bag.add k b = + use Bag + + b + Bag.singleton k + +data.Bag.add.doc : Doc +data.Bag.add.doc = + use Bag add + {{ + `` add k b `` adds one occurrence of the element `k` to the {type Bag} `b`. + The rule is that if `k` occurs `n` times in the {type Bag} `b` then it will + occur `n + 1` times in ``add k b``. + + # Example + + ``` + a = Bag.fromText "🍎🍌🍓" + Bag.toText (add ?🍏 a) + ``` + }} + +test> data.Bag.add.tests.adds = runs 100 do + use Char ascii + b = bagOf ascii () + c = ascii() + expect (Bag.contains (Bag.add c b) c) + +test> data.Bag.addAll.tests.homomorphism = runs 100 do + use Bag count + use Char ascii + use Nat + + a = bagOf ascii () + b = bagOf ascii () + c = Bag.occurrenceList (Bag.addAll a b) + expect (List.all (cases (e, n) -> n === count e a + count e b) c) + +data.Bag.addN : Nat -> k -> Bag k -> Bag k +data.Bag.addN n k b = + use Bag + + b + Bag.scale n (Bag.singleton k) + +data.Bag.addN.doc : Doc +data.Bag.addN.doc = + use Bag addN + {{ + `` addN n k b `` adds `n` occurrences of the element `k` to the {type Bag} + `b`. + + # Example + + ``` + a = Bag.fromText "👻🎃💀" + b = addN 3 ?🎃 a + Bag.toText b + ``` + }} + +test> data.Bag.addN.test = runs 100 do + use Char ascii + b = bagOf ascii () + c = ascii() + n = natInOrder() + expect (Universal.gteq (Bag.count c (Bag.addN n c b)) n) + +data.Bag.all : (a ->{g} Boolean) -> Bag a ->{g} Boolean +data.Bag.all p = Set.all p << Bag.toSet + +data.Bag.all.doc : Doc +data.Bag.all.doc = + use Bag all + {{ + Check if all elements in the {type Bag} match a query. + + The expression `` all p b `` returns `` true `` if the function `p` returns + `true` for all elements in the {type Bag} `b`, or if `b` is empty. + Equivalently, it returns `` false `` only if `p` returns `` false `` for any + element in `b`. + + # Examples + + ``` + a = Bag.fromList ["birthday", "party", "celebration"] + all (Nat.isOdd << Text.size) a + ``` + + ``` + all Nat.isEven Bag.empty + ``` + }} + +test> data.Bag.all.test = runs 100 do + bs = gen.listOf gen.boolean () + p = yesNo() + expect (Bag.all p (Bag.fromList bs) === List.all p bs) + +data.Bag.any : (a ->{g} Boolean) -> Bag a ->{g} Boolean +data.Bag.any p = Set.any p << Bag.toSet + +data.Bag.any.doc : Doc +data.Bag.any.doc = + use Bag any + use Nat isOdd + {{ + Check if any elements in the {type Bag} match a query. + + The expression `` any p b `` returns `` true `` if the function `p` returns + `` true `` for at least one element in the {type Bag} `b`. Equivalently, it + returns `` false `` only if `p` returns `` false `` for all elements in `b`, + or if `b` is empty. + + # Examples + + ``` + a = Bag.fromList ["apple", "pie", "cheese"] + any (isOdd << Text.size) a + ``` + + ``` + any isOdd Bag.empty + ``` + }} + +test> data.Bag.any.test = runs 100 do + bs = gen.listOf gen.boolean () + p = yesNo() + expect (Bag.any p (Bag.fromList bs) === List.any p bs) + +data.Bag.contains : Bag a -> a -> Boolean +data.Bag.contains = flip elementOf + +data.Bag.contains.doc : Doc +data.Bag.contains.doc = + use Bag contains + {{ + `` contains b k `` is `` true `` if the value `k` is an element of the bag + `b`, and `` false `` otherwise. + + # Examples + + ``` + b = Bag.fromText "🎉🎊🎂" + contains b ?🎉 + ``` + + ``` + contains Bag.empty ?🎂 + ``` + }} + +test> data.Bag.contains.test = + runs 100 do + use gen listOf + printable = listOf Char.asciiPrintable () + chars = listOf Char.ascii () + bag = Bag.fromList printable + expect + (List.all (c -> Bag.contains bag c === List.contains c printable) chars) + +data.Bag.convolve : (i ->{g} a ->{g} t) -> Bag i -> Bag a ->{g} Bag t +data.Bag.convolve f ba bb = + use Bag flatMap + flatMap (a -> flatMap (b -> Bag.singleton (f a b)) bb) ba + +data.Bag.convolve.doc : Doc +data.Bag.convolve.doc = + use Bag convolve fromText + {{ + `` convolve f xs ys `` applies the function `f` to every pair of elements + `(x,y)` where `x` comes from the {type Bag} `xs` and `y` comes from the + {type Bag} `ys`. This is called the __convolution__ of `xs` and `ys` with the + function `f`. + + # Example + + ``` + bx = fromText "🎁🎈🎈" + by = fromText "🍏🍏🍇" + Bag.occurrenceList (convolve (x y -> fromCharList [x, y]) bx by) + ``` + }} + +test> data.Bag.convolve.tests.associative = runs 100 do + use Bag == convolve + use gen boolean + bx = bagOf boolean () + by = bagOf boolean () + bz = bagOf boolean () + g = logic() + h = logic() + f x y z = g x (h y z) + left = convolve id (convolve (x y z -> f x y z) bx by) bz + right = convolve (flip id) bx (convolve (y z x -> f x y z) by bz) + expect (left == right) + +test> data.Bag.convolve.tests.natural = runs 100 do + use Bag == convolve map + use gen boolean + bx = bagOf boolean () + by = bagOf boolean () + f = yesNo() + g = yesNo() + o = logic() + left = convolve (x y -> o (f x) (g y)) bx by + right = convolve o (map f bx) (map g by) + expect (left == right) + +test> data.Bag.convolve.tests.unit = runs 100 do + use Bag == convolve singleton + m = bagOf natInOrder () + k = natInOrder() + right = convolve (const id) (singleton k) m + left = convolve const m (singleton k) + expect (left == right && left == m) + +data.Bag.count : a -> Bag a -> Nat +data.Bag.count a = cases + MkBag counts -> Optional.getOrElse 0 (Map.get a counts) + +data.Bag.count.doc : Doc +data.Bag.count.doc = + use Bag count + {{ + `` count e b `` returns the number of times the element `e` occurs in the + {type Bag} `b`. + + # Example + + ``` + count ?🥕 (Bag.fromText "🥒🥕🥕🥕🥔") + ``` + }} + +test> data.Bag.count.tests.counts = runs 100 do + use Bag count + use Nat + + use Text ascii + m = bagOf ascii () + k = ascii() + n = natInOrder() + c = count k m + expect (count k (Bag.addN n k m) === c + n) + +data.Bag.counts : Bag a -> Map a Nat +data.Bag.counts b = + (MkBag m) = Bag.internal.normalize b + m + +data.Bag.counts.doc : Doc +data.Bag.counts.doc = + use Bag counts + {{ + `` counts b `` returns a {type Map} where the keys are the elements of the + {type Bag} `b` and the value under each key is the number of times that + element occurs in `b`. + + # Example + + ``` + b = Bag.fromText "abracadabra" + Map.toList (counts b) + ``` + }} + +data.Bag.difference : Bag a -> Bag a -> Bag a +data.Bag.difference b1 = cases + MkBag b2 -> + List.foldRight (cases (a, n), b -> Bag.removeN n a b) b1 (Map.toList b2) + +data.Bag.difference.doc : Doc +data.Bag.difference.doc = + use Bag difference fromText + use Nat - + {{ + `` difference b1 b2 `` removes elements from the {type Bag} `b1` to the + extent that they exist in the {type Bag} `b2`. If an element occurs `n` times + in `b1` and `m` times in `b2`, it will occur `` n - m `` times in the result. + + # Example + + ``` + b1 = fromText "🧣🧥🧤🧤" + b2 = fromText "🧤🧥🧥👕" + Bag.toList (difference b1 b2) + ``` + }} + +test> data.Bag.difference.tests.homomorphism = runs 100 do + use Bag count + use Char ascii + use Nat - + a = bagOf ascii () + b = bagOf ascii () + d = Bag.occurrenceList (Bag.difference a b) + expect (List.all (cases (e, n) -> n === count e a - count e b) d) + +data.Bag.doc : Doc +data.Bag.doc = + use Bag * + contains count counts fromMap fromOccurrenceList fromText occurrenceList scale subbag superbag toText + {{ + {type Bag} is the type of __multisets__, or bags. + + A {type Bag} is like a {type Set}, except that any given element can occur + more than once. In that sense it's more like a list, but unlike a list the + elements of a {type Bag} occur in no particular order. + + The elements of a {type Bag} can be of any type, but they must all be of the + same type. The {type Bag} type is parameterized by the type of its elements. + + # Constructing bags + + {Bag.empty} is the empty bag. + + `` Bag.singleton k `` is the bag with a single value `k` in it. + + `` Bag.fromList xs `` constructs a bag from a list `xs`. + + {fromMap} constructs a bag given a map from keys to counts: + + ``` + m = Map.fromList [(?🥒, 0), (?🥕, 3), (?🥑, 2), (?🌽, 1)] + toText (fromMap m) + ``` + + {fromOccurrenceList} constructs a bag from the list representation of such + a map: + + ``` + occurrences = [(?🍕, 1), (?🍟, 3), (?🍗, 2), (?🍔, 0)] + toText (fromOccurrenceList occurrences) + ``` + + {fromText} contructs a bag containing the characters of some {type Text}: + + ``` + b = fromText "🍎🍌🍇🍎🍌🍎" + occurrenceList b + ``` + + # Adding and removing elements + + `` Bag.add k b `` adds the single element `k` to the {type Bag} `b`. + + `` Bag.addN n k b `` adds `n` instances of `k` to the {type Bag} `b`. + + `` Bag.remove k b `` removes one `k` from the {type Bag} `b`. + + `` Bag.removeN n k b `` removes `n` occurrences of `k` from the {type Bag} + `b`. + + `` Bag.removeAll k b `` removes all occurrences of `k` from the {type Bag} + `b`. + + `` removeWhere p b `` removes elements from `b` where the condition `p` is + ``true``. + + `` Bag.filter p b `` removes elements from `b` where the condition `p` is + ``false``. + + # Querying and counting elements + + `` contains b k `` is `` true `` if `k` occurs at least once in the + {type Bag} `b`. + + {elementOf} is {contains} with its arguments flipped. + + `` count k b `` counts the number of occurrences of `k` in the {type Bag} + `b`. + + {Bag.isEmpty} checks if a {type Bag} is empty. + + {Bag.size} gets the number of elements in a {type Bag}. + + # Combining bags + + {+} adds the elements of one {type Bag} to another. + + `` Bag.convolve f xs ys `` collects all results `f x y` where `x` is from + the {type Bag} `xs` and `y` is from `ys`. + + {Bag.difference} removes the elements of one {type Bag} from another. + + `` Bag.intersect xs ys `` finds the largest {subbag} of both `xs` and `ys`. + + `` xs * ys `` is the {type Bag} of all pairs of elements `(x,y)` where `x` + is from the {type Bag} `xs` and `y` is from `ys`. + + `` Bag.union xs ys `` finds the smallest {superbag} of both `xs` and `ys`. + + # Comparing bags + + `` Bag.from b1 b2 `` returns `` true `` if every element in `b1` occurs at + least once in `b2`. + + `` subbag b1 b2 `` returns `` true `` if no element occurs more times in + `b1` than in `b2`. + + {superbag} is {subbag} with its arguments flipped. + + # Conversions to other structures + + `` counts b `` returns a {type Map} associating each distinct element of + `b` with its number of occurrences in `b`. + + {occurrenceList} is the same as {counts}, but returns a {type List}. + + `` Bag.toList b `` returns a {type List} of all the elements of `b`, + including duplicates. + + `` Bag.toSet b `` returns the {type Set} of distinct elements in `b`. + + `` toText b `` returns the {type Text} containing all the elements of `b` + as long as they are {type Char}s. + + # Traversal and iteration + + `` traverse f b `` iterates over the elements of `b`, applying the + effectful function `f` to each element, collecting the results in a new + {type Bag}. + + `` Bag.map f b `` applies the function `f` to every element of `b`. + + `` Bag.flatMap f b `` applies the bag-valued function `f` to every element + of `b`, collecting all results into one {type Bag}. + + {Bag.foldLeft} folds a {type Bag} with a binary function, accumulating in + the left argument. + + {Bag.foldRight} folds a {type Bag} with a binary function, accumulating in + the right argument. + + `` Bag.modify f b `` uses `f` to modify every distinct element of `b` as + well as its number of occurrences. + + # Operations on element counts + + `` scale n b `` multiplies all occurrences in `b` by `n`: + + ``` + b = fromText "🍊🍐🍐🍑🍑🍑" + toText (scale 2 b) + ``` + + `` mapCounts f b `` is a new {type Bag} where every element `x` of `b` + occurs {{ docExample 3 do f x b -> f (count x b) }} times: + + ``` + b = fromText "🌲🌲🌳🌳🌳🌴" + toText (mapCounts Nat.decrement b) + ``` + }} + +data.Bag.elementOf : a -> Bag a -> Boolean +data.Bag.elementOf a b = Universal.gt (Bag.count a b) 0 + +data.Bag.elementOf.doc : Doc +data.Bag.elementOf.doc = + {{ + `` elementOf k b `` is `` true `` if the value `k` is an element of the bag + `b`, and `false` otherwise. + + # Examples + + ``` + b = Bag.fromText "abracadabra" + elementOf ?a b + ``` + + ``` + elementOf 0 Bag.empty + ``` + }} + +test> data.Bag.elementOf.test = runs 100 do + use gen listOf + printable = listOf Char.asciiPrintable () + chars = listOf Char.ascii () + bag = Bag.fromList printable + expect (List.all (c -> elementOf c bag === List.contains c printable) chars) + +data.Bag.empty : Bag a +data.Bag.empty = MkBag Map.empty + +data.Bag.empty.doc : Doc +data.Bag.empty.doc = {{ The empty {type Bag}. }} + +test> data.Bag.empty.test = runs 2 do + k = gen.boolean() + expect (Bag.count k Bag.empty === 0) + +data.Bag.equals.doc : Doc +data.Bag.equals.doc = + use Bag count + use Nat == + {{ + Checks if two {type Bag}s are equal. Any two {type Bag}s `b1` and `b2` are + equal when for every value `k`, ``count k b1 == count k b2``. + + Note that {===} does not have the correct behaviour for {type Bag}, as two + equal {type Bag}s may have different internal structure. + }} + +test> data.Bag.equals.tests.reflexive = + runs 100 do laws.reflexive (bagOf natInOrder) Bag.equals + +test> data.Bag.equals.tests.symmetric = + runs 100 do laws.commutative (bagOf natInOrder) Bag.equals + +test> data.Bag.equals.tests.transitive = + runs 100 do laws.transitive (bagOf natInOrder) Bag.equals + +data.Bag.filter : (i ->{e} Boolean) -> Bag i ->{e} Bag i +data.Bag.filter p = Bag.modify cases (a, n) -> if p a then (a, n) else (a, 0) + +data.Bag.filter.doc : Doc +data.Bag.filter.doc = + use Bag filter + {{ + ``filter p b``returns a new {type Bag} with just the elements from `b` for + which the function `p` returns `true`. + + # Example + + ``` + b = Bag.fromText "abracadabra" + Bag.toText (filter (Char.inRange ?b ?d) b) + ``` + }} + +data.Bag.flatMap : (a ->{e} Bag b) -> Bag a ->{e} Bag b +data.Bag.flatMap f = + use Bag + + k z = cases (e, n) -> z + Bag.scale n (f e) + List.foldLeft k Bag.empty << Bag.occurrenceList + +data.Bag.flatMap.doc : Doc +data.Bag.flatMap.doc = + use Bag flatMap + {{ + `` flatMap f b `` applies the function `f` to every element of the {type Bag} + `b`. The function `f` returns a whole {type Bag} each time, and {flatMap} + collects all their elements into one {type Bag}. + + # Example + + This collects into one {type Bag} all the individual characters from a + {type Bag} of {type Text}: + + ``` + b = Bag.fromList ["site", "real", "live"] + Bag.toText (flatMap (n -> Bag.fromText n) b) + ``` + }} + +test> data.Bag.flatMap.tests.associative = runs 100 do + use Bag == flatMap + x = bagOf (bagOf (bagOf natInOrder)) () + left = flatMap id (flatMap id x) + right = flatMap id (flatMap (Bag.singleton << flatMap id) x) + expect (left == right) + +test> data.Bag.flatMap.tests.unit = runs 100 do + use Bag == flatMap singleton + x = bagOf natInOrder () + n = natInOrder() + expect (flatMap singleton x == x && flatMap (const x) (singleton n) == x) + +data.Bag.foldLeft : (b ->{g} a ->{g} b) -> b -> Bag a ->{g} b +data.Bag.foldLeft f z = List.foldLeft f z << Bag.toList + +data.Bag.foldLeft.doc : Doc +data.Bag.foldLeft.doc = + use Bag foldLeft + use Nat + + {{ + `` foldLeft f z b `` iterates over the elements of the {type Bag} `b`, + applying the binary operator `f` to each element and the result so far. The + result starts as the value `z`, which is also returned in case the {type Bag} + is empty. + + # Example + + Add up all the sizes of elements in a bag: + + ``` + b = Bag.fromList ["red", "blue", "green"] + foldLeft (z s -> z + Text.size s) 0 b + ``` + }} + +test> data.Bag.foldLeft.tests.asList = runs 100 do + use Nat + + x = gen.listOf natInOrder () + expect (List.foldLeft (+) 0 x === Bag.foldLeft (+) 0 (Bag.fromList x)) + +data.Bag.foldRight : (a ->{g} b ->{g} b) -> b -> Bag a ->{g} b +data.Bag.foldRight f z = List.foldRight f z << Bag.toList + +data.Bag.foldRight.doc : Doc +data.Bag.foldRight.doc = + use Bag foldRight + use Nat + + {{ + `` foldRight f z b `` iterates over the elements of the {type Bag} `b`, + applying the binary operator `f` to each element and the result so far. The + result starts as the value `z`, which is also returned in case the {type Bag} + is empty. + + # Example + + Add up all the sizes of elements in a bag: + + ``` + b = Bag.fromList ["red", "blue", "green"] + foldRight (s z -> Text.size s + z) 0 b + ``` + }} + +test> data.Bag.foldRight.tests.asList = runs 100 do + use Nat + + x = gen.listOf natInOrder () + expect (List.foldRight (+) 0 x === Bag.foldRight (+) 0 (Bag.fromList x)) + +data.Bag.from : Bag a -> Bag a -> Boolean +data.Bag.from b1 b2 = + use Bag toSet + Set.subset (toSet b1) (toSet b2) + +data.Bag.from.doc : Doc +data.Bag.from.doc = + use Bag from fromText + {{ + `` from b1 b2 `` returns `` true `` if every element in `b1` occurs at least + once in `b2`. + + # Example + + ``` + redSuits = fromText "♥♦" + h1 = fromText "♠♠♠♠♥♥" + h2 = fromText "♥♥" + (from h1 redSuits, from h2 redSuits) + ``` + }} + +test> data.Bag.from.tests.subset = runs 100 do + use Bag toSet + use Char ascii + a = bagOf ascii () + b = bagOf ascii () + expect (Bag.from a b === Set.subset (toSet a) (toSet b)) + +data.Bag.fromList : [k] -> Bag k +data.Bag.fromList = List.foldRight Bag.add Bag.empty + +data.Bag.fromList.doc : Doc +data.Bag.fromList.doc = + use Bag fromList + {{ + `` fromList ks `` creates a new {type Bag} from the {type List} `ks`. The + resulting {type Bag} contains exactly as many occurrences of each element as + there are in the {type List} `ks`. + + # Example + + ``` + Bag.occurrenceList (fromList ["apple", "banana", "cherry", "apple"]) + ``` + }} + +test> data.Bag.fromList.tests.roundtrip = runs 100 do + use Heap sort + x = sort (gen.listOf natInOrder ()) + expect (sort (Bag.toList (Bag.fromList x)) === x) + +data.Bag.fromMap : Map k Nat -> Bag k +data.Bag.fromMap m = MkBag m + +data.Bag.fromMap.doc : Doc +data.Bag.fromMap.doc = + {{ + Constructs a new {type Bag} from a {type Map} that associates elements to be + put in the {type Bag} with the number of times they should appear. + + # Example + + ``` + m = Map.fromList [(?🥒, 0), (?🥕, 3), (?🥑, 2), (?🌽, 1)] + Bag.toText (Bag.fromMap m) + ``` + }} + +test> data.Bag.fromMap.tests.roundTrip = runs 100 do + use Map == + m = tests.mapOf natInOrder nonzeroNat () + expect (Bag.counts (Bag.fromMap m) == m) + +data.Bag.fromOccurrenceList : [(a, Nat)] -> Bag a +data.Bag.fromOccurrenceList = MkBag << Map.fromList + +data.Bag.fromOccurrenceList.doc : Doc +data.Bag.fromOccurrenceList.doc = + {{ + Constructs a {type Bag} from a {type List} of elements paired with the number + of times they should occur in the {type Bag}. + + # Example + + ``` + occurrences = [(?🍕, 1), (?🍟, 3), (?🍗, 2), (?🍔, 0)] + Bag.toText (Bag.fromOccurrenceList occurrences) + ``` + }} + +test> data.Bag.fromOccurrenceList.tests.roundTrip = + runs 100 do + use Heap sort + m = + List.filter + (cases (_, v) -> Universal.gt v 0) + (distinctBy at1 (sort (gen.listOf (pairOf natInOrder natInOrder) ()))) + expect + (assertEquals (sort (Bag.occurrenceList (Bag.fromOccurrenceList m))) m) + +data.Bag.fromText.doc : Doc +data.Bag.fromText.doc = + use Bag fromText + {{ + `` fromText t `` is the {type Bag} of characters in the {type Text} `t`. + + # Example + + ``` + b = fromText "🍎🍌🍇🍎🍌🍎" + Bag.occurrenceList b + ``` + }} + +test> data.Bag.fromText.tests.anagram = runs 100 do + use Heap sort + s = Text.ascii() + expect (sort (Bag.toList (Bag.fromText s)) === sort (toCharList s)) + +data.Bag.internal.compare : + (Nat ->{e} Nat ->{e} Boolean) -> Bag a -> Bag a ->{e} Boolean +data.Bag.internal.compare k b1 b2 = + use Bag count + Bag.foldLeft (b e -> b && k (count e b1) (count e b2)) true (Bag.union b1 b2) + +data.Bag.internal.compare.doc : Doc +data.Bag.internal.compare.doc = + use Bag count + {{ + A utility function for comparisons of bags. `` internal.compare k b1 b2 `` + returns `` true `` if for all elements `e` of the {type Bag} `b1`, {{ + docExample 4 do k b1 b2 e -> k (count e b1) (count e b2) }} returns ``true``. + Otherwise ``false``. + }} + +data.Bag.internal.normalize : Bag a -> Bag a +data.Bag.internal.normalize = cases + MkBag b -> + Map.foldLeftWithKey + (m k n -> (if Universal.lt n 1 then m else Bag.addN n k m)) Bag.empty b + +data.Bag.intersect : Bag a -> Bag a -> Bag a +data.Bag.intersect = cases + MkBag b1 -> cases MkBag b2 -> MkBag (Map.intersectWith Universal.min b1 b2) + +data.Bag.intersect.doc : Doc +data.Bag.intersect.doc = + use Bag fromText intersect + {{ + `` intersect b1 b2 `` constructs the intersection of `b1` and `b2`, which is + the largest {type Bag} that is a {Bag.subbag} of both `b1` and `b2`. This is + a new {type Bag} with elements from `b1` and `b2` where the number of times + each element occurs in the new {type Bag} is either the number of times it + occurs in `b1` or the number of times it occurs in `b2`, whichever is less. + + ``` + a = fromText "🐱🐭🐭🐶🐶" + b = fromText "🐶🐭🐶🐶🐹" + Bag.toText (intersect a b) + ``` + }} + +test> data.Bag.intersect.tests.commutative = runs 100 do + use Bag == intersect + x = bagOf natInOrder () + y = bagOf natInOrder () + expect (intersect x y == intersect y x) + +test> data.Bag.intersect.tests.distributive = runs 100 do + use Bag == intersect union + x = bagOf natInOrder () + y = bagOf natInOrder () + z = bagOf natInOrder () + expect (intersect x (union y z) == union (intersect x y) (intersect x z)) + +test> data.Bag.intersect.tests.homomorphism = + runs 100 do + use Bag count + use Char ascii + a = bagOf ascii () + b = bagOf ascii () + c = Bag.occurrenceList (Bag.intersect a b) + expect + (List.all (cases (e, n) -> n === Universal.min (count e a) (count e b)) c) + +test> data.Bag.intersect.tests.idempotent = runs 100 do + use Bag == + x = bagOf natInOrder () + expect (Bag.intersect x x == x) + +test> data.Bag.intersect.tests.zero = runs 100 do + use Bag == empty + x = bagOf natInOrder () + expect (Bag.intersect x empty == empty) + +data.Bag.isEmpty : Bag a -> Boolean +data.Bag.isEmpty b = List.all (cases (_, n) -> n === 0) (Bag.occurrenceList b) + +data.Bag.isEmpty.doc : Doc +data.Bag.isEmpty.doc = + use Bag isEmpty + {{ + `` isEmpty b `` is `` true `` if the {type Bag} `b` has no elements, and `` + false `` otherwise. + + # Examples + + ``` + isEmpty Bag.empty + ``` + + ``` + isEmpty (Bag.singleton 42) + ``` + }} + +test> data.Bag.isEmpty.test = runs 100 do + use Bag isEmpty + k = natInOrder() + m = Bag.add k (bagOf natInOrder ()) + expect (isEmpty Bag.empty && Boolean.not (isEmpty m)) + +data.Bag.map : (a -> b) -> Bag a -> Bag b +data.Bag.map f = cases MkBag b -> MkBag (Map.mapKeysWith (Nat.+) f b) + +data.Bag.map.doc : Doc +data.Bag.map.doc = + use Bag map + {{ + `` map f b `` applies the function `f` to every element of the {type Bag} + `b`, constructing a new {type Bag} of the results. The function is not + allowed to use abilities. For a version of this that allows abilities, see + {traverse}. + + # Example + + ``` + b = Bag.fromList [2, 3, 1, 1] + Bag.occurrenceList (map Nat.isEven b) + ``` + }} + +test> data.Bag.map.tests.functor = runs 100 do + use Bag == + m = bagOf natInOrder () + expect (Bag.map id m == m) + +test> data.Bag.map.tests.surjection = runs 100 do + use Bag size + use Nat == + b = bagOf natInOrder () + c = Bag.map Nat.isEven b + expect (size b == size c) + +data.Bag.mapCounts : (Nat ->{e} Nat) -> Bag a ->{e} Bag a +data.Bag.mapCounts f = cases MkBag b -> MkBag (Map.map f b) + +data.Bag.mapCounts.doc : Doc +data.Bag.mapCounts.doc = + {{ + `` mapCounts f b `` constructs a new {type Bag} with the same elements as + `b`, but with the number of times any given element occurs modified by the + function `f`. + + # Example + + ``` + b = Bag.fromText "🍊🍊🍋🍋🍋🍌" + Bag.toText (mapCounts Nat.increment b) + ``` + }} + +test> data.Bag.mapCounts.tests.functor = runs 100 do + use Bag == + m = bagOf natInOrder () + expect (mapCounts id m == m) + +data.Bag.modify : ((a, Nat) ->{e} (b, Nat)) -> Bag a ->{e} Bag b +data.Bag.modify f = + Bag.fromOccurrenceList << List.flatMap + (p -> let + q@(b, n) = f p + if Universal.lt n 1 then [] else [q]) << Bag.occurrenceList + +data.Bag.modify.doc : Doc +data.Bag.modify.doc = + use Bag modify + use Nat - + {{ + `` modify f b `` passes each unique element in the {type Bag} `b`, together + with its count in `b`, to the function `f` which returns a modified element + and a new count. Can be used to modify elements, their counts, or both. + + # Example + + ``` + b = Bag.fromText "abracadabra" + Bag.toText (modify (cases (k, c) -> (ascii.toUpper k, c - 1)) b) + ``` + }} + +test> data.Bag.modify.tests.double = runs 100 do + use Bag + == + use Nat * + m = bagOf Text.ascii () + expect (Bag.modify (cases (k, n) -> (k, n * 2)) m == m + m) + +test> data.Bag.modify.tests.functor = runs 100 do + use Bag == + m = bagOf natInOrder () + expect (Bag.modify id m == m) + +test> data.Bag.modify.tests.zero = runs 100 do + use Bag == + m = bagOf Text.ascii () + expect (Bag.modify (cases (k, n) -> (k, 0)) m == Bag.empty) + +data.Bag.none : (a ->{g} Boolean) -> Bag a ->{g} Boolean +data.Bag.none p = Boolean.not << Bag.any p + +data.Bag.none.doc : Doc +data.Bag.none.doc = + use Bag fromList none + use Nat isEven + {{ + Check if no elements in the {type Bag} match a query. + + The expression `` none p b `` returns `` true `` if the function `p` returns + `` false `` for all elements in the {type Bag} `b`, or if `b` is empty. + Equivalently, it returns `` false `` only if `p` returns `` true `` for at + least one element in `b`. + + # Example + + ``` + none isEven (fromList [1, 2, 3]) + ``` + + ``` + none isEven (fromList [1, 3, 13]) + ``` + + ``` + none Nat.isOdd (fromList []) + ``` + }} + +test> data.Bag.none.test = runs 100 do + bs = gen.listOf gen.boolean () + p = yesNo() + expect (Bag.none p (Bag.fromList bs) === List.none p bs) + +data.Bag.nth : Nat -> Bag a -> Optional a +data.Bag.nth index bag = + use Nat + - < + loop : Nat -> Map a Nat -> Nat -> (Optional a, Nat) + loop index map visited = + match map with + internal.Tip -> (None, visited) + internal.Bin sz key value l r -> + match loop index l visited with + (Some k, v) -> (Some k, v) + (None, v) -> + if index - v < value then (Some key, v + value) + else loop index r (v + value) + loop index (Bag.counts bag) 0 |> at1 + +data.Bag.nth.doc : Doc +data.Bag.nth.doc = + use Bag nth + {{ + {{ docExample 2 do i b -> nth i b }} returns the `i`-th smallest element in + `b`, where `i`=0 is the smallest element (according to {Universal.ordering}). + + Is the same as {{ docExample 2 do i as -> List.at i (Bag.toList as) }} but + doesn't require instantiating the intermediate {type List}. + + ``` + b = Bag.fromList [3, 1, 2, 3, 4, 5, 1, 1] + List.map (i -> nth i b) (List.range 0 (Bag.size b)) + ``` + }} + +test> data.Bag.nth.tests = + test.verify do + use Random natIn + Each.repeat 100 + s = (List.replicate (natIn 0 20) do natIn 0 10) |> Bag.fromList + ensure + (List.somes (List.map (i -> Bag.nth i s) (List.range 0 (Bag.size s))) + === Bag.toList s) + +data.Bag.occurrenceList : Bag a -> [(a, Nat)] +data.Bag.occurrenceList = Map.toList << Bag.counts + +data.Bag.occurrenceList.doc : Doc +data.Bag.occurrenceList.doc = + {{ + Returns a list of all the elements of a {type Bag}, paired with the number of + times that element occurs. + + # Example + + ``` + Bag.occurrenceList (Bag.fromText "🍕🍕🍟🍔🍔🍔") + ``` + }} + +test> data.Bag.product.tests.spec = + runs 100 do + use Bag count occurrenceList + use List all + use Nat * + x = bagOf natInOrder () + y = bagOf natInOrder () + p = Bag.product x y + left = + all (cases ((a, b), n) -> n === count a x * count b y) (occurrenceList p) + right = + all + (cases ((a, m), (b, n)) -> count (a, b) p === m * n) + (List.flatMap + (a -> List.map (b -> (a, b)) (occurrenceList y)) (occurrenceList x)) + expect (left && right) + +data.Bag.randomChoice : Bag a ->{Exception, Random} a +data.Bag.randomChoice bag = + randomIndex = Random.natIn 0 (Bag.size bag) + Bag.nth randomIndex bag + |> Optional.toException "Bag.randomChoice: empty Bag" (typeLink Bag) + +data.Bag.randomChoice.doc : Doc +data.Bag.randomChoice.doc = + use Bag fromList randomChoice + {{ + Picks a random element from the given {type Bag}. Assumes that the bag is not + empty, so an empty bag will raise an {type Exception}. + + # Examples + + ``` + catch do lcg 4096 do randomChoice (fromList [0, 3, 5, 7]) + ``` + + ``` + catch do lcg 2510 do randomChoice (fromList [?x, ?y, ?z]) + ``` + + ``` + catch do lcg 128 do randomChoice (fromList [char.digit, hex]) () + ``` + }} + +test> data.Bag.randomChoice.test = test.verify do + list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 1, 4, 6, 6] + set = Set.fromList list + bag = Bag.fromList list + Each.repeat 1000 + e = Bag.randomChoice bag + ensure (Set.contains e set) + +data.Bag.remove : k -> Bag k -> Bag k +data.Bag.remove k b = Bag.removeN 1 k b + +data.Bag.remove.doc : Doc +data.Bag.remove.doc = + use Bag remove + {{ + `` remove k b `` removes one occurrence of the element `k` from the + {type Bag} `b` if `k` occurs in the {type Bag} `b`. Otherwise it does + nothing. + + # Example + + ``` + Bag.toText (remove ?🎅 (Bag.fromText "🎄🎅🎅🎁🎁🎁")) + ``` + }} + +test> data.Bag.remove.tests.removes = runs 100 do + use Bag count + use Nat - + b = bagOf natInOrder () + k = natInOrder() + p = count k (Bag.remove k b) === count k b - 1 + expect p + +data.Bag.removeAll : k -> Bag k -> Bag k +data.Bag.removeAll k = cases MkBag b -> MkBag (Map.delete k b) + +data.Bag.removeAll.doc : Doc +data.Bag.removeAll.doc = + use Bag removeAll + {{ + `` removeAll k b `` removes all occurrences of the element `k` from the + {type Bag} `b` if `k` occurs in the {type Bag} `b`. Otherwise it does + nothing. + + # Example + + ``` + Bag.toText (removeAll ?🎅 (Bag.fromText "🎄🎅🎅🎁🎁🎁")) + ``` + }} + +test> data.Bag.removeAll.tests.removes = runs 100 do + b = bagOf natInOrder () + k = natInOrder() + p = Bag.count k (Bag.removeAll k b) === 0 + expect p + +data.Bag.removeN : Nat -> k -> Bag k -> Bag k +data.Bag.removeN n k b = + use Nat - + c = Bag.count k b + MkBag match b with + MkBag b -> + if Universal.lteq c n then Map.delete k b else Map.insert k (c - n) b + +data.Bag.removeN.doc : Doc +data.Bag.removeN.doc = + use Bag fromText removeN toText + {{ + `` removeN n k b `` removes `n` occurrences of the element `k` from the + {type Bag} `b` if `k` occurs in it. If the {type Bag} has fewer occurrences + of `k`, then {removeN} removes them all. If `k` doesn't occur in the + {type Bag}, it does nothing. + + # Example + + ``` + toText (removeN 2 ?🎁 (fromText "🎄🎅🎅🎁🎁🎁")) + ``` + + ``` + toText (removeN 9 ?🎁 (fromText "🎄🎅🎅🎁🎁🎁")) + ``` + }} + +test> data.Bag.removeN.tests.removes = runs 100 do + use Bag count + use Nat - + b = bagOf natInOrder () + k = natInOrder() + n = natInOrder() + p = count k (Bag.removeN n k b) === count k b - n + expect p + +data.Bag.removeWhere : (a ->{e} Boolean) -> Bag a ->{e} Bag a +data.Bag.removeWhere p = Bag.filter (Boolean.not << p) + +data.Bag.removeWhere.doc : Doc +data.Bag.removeWhere.doc = + {{ + `` removeWhere p b `` removes the elements from the {type Bag} `b` for which + the function `p` returns ``true``. + + # Example + + ``` + b = Bag.fromText "A stitch in time saves nine." + Bag.occurrenceList + (removeWhere (c -> Boolean.not (isLetter c)) (Bag.map ascii.toLower b)) + ``` + }} + +test> data.Bag.removeWhere.tests.removes = runs 100 do + b = bagOf gen.boolean () + p = yesNo() + q = removeWhere p b + expect (Bag.none p q) + +data.Bag.scale : Nat -> Bag a -> Bag a +data.Bag.scale n = + use Nat * + mapCounts (x -> x * n) + +data.Bag.scale.doc : Doc +data.Bag.scale.doc = + use Bag fromText scale toText + {{ + `` scale k b `` constructs a new {type Bag} with the same elements as `b`, + but where the number of times each element occurs is multiplied by `k`. + + # Example + + ``` + toText (scale 2 (fromText "🍰🍩🍩🍬🍬🍬")) + ``` + + ``` + toText (scale 0 (fromText "🍰🍩🍬")) + ``` + }} + +test> data.Bag.scale.tests.scales = runs 10 do + use Bag count + use Nat * + n = natInOrder() + b = bagOf natInOrder () + s = Bag.scale n b + expect (Bag.all (k -> count k s === n * count k b) b) + +data.Bag.similarity : Bag a -> Bag a -> Float +data.Bag.similarity a b = + use Bag size + use Float + - / > fromNat + i = fromNat (size (Bag.intersect a b)) + s = fromNat (size a) + fromNat (size b) - i + if s > 0.0 then i / s else 1.0 + +data.Bag.similarity.doc : Doc +data.Bag.similarity.doc = + use Bag empty fromText similarity singleton + {{ + Measures the similarity of two {type Bag}s. This is the number of elements + they have in common, as a proportion of the total number of elements in their + union — a number between `` 0.0 `` (no common elements) and `` 1.0 `` (the + {type Bag}s are identical). + + # Examples + + ``` + similarity (fromText "barbara ara") (fromText "bara rabarbara") + ``` + + ``` + similarity (singleton 1) (singleton 2) + ``` + + ``` + similarity empty empty + ``` + }} + +data.Bag.singleton : k -> Bag k +data.Bag.singleton k = MkBag (Map.singleton k 1) + +data.Bag.singleton.doc : Doc +data.Bag.singleton.doc = + use Bag singleton + {{ + `` singleton k `` constructs a {type Bag} with a single element `k` occurring + exactly once. + + # Example + + ``` + Bag.toList (singleton 42) + ``` + }} + +test> data.Bag.singleton.tests.one = runs 100 do + k = natInOrder() + s = Bag.singleton k + expect (Bag.size s === 1 && Bag.all (x -> x === k) s) + +data.Bag.size : Bag a -> Nat +data.Bag.size = cases MkBag b -> List.foldLeft (Nat.+) 0 (Map.values b) + +data.Bag.size.doc : Doc +data.Bag.size.doc = + use Bag size + {{ + `` size b `` is the number of elements in the {type Bag} `b`, including + duplicate elements. + + # Example + + ``` + size (Bag.fromText "relentlessly") + ``` + }} + +test> data.Bag.size.test = runs 100 do + a = gen.listOf Char.ascii () + b = Bag.fromList a + expect (List.size a === Bag.size b) + +data.Bag.subbag : Bag a -> Bag a -> Boolean +data.Bag.subbag = internal.compare Universal.lteq + +data.Bag.subbag.doc : Doc +data.Bag.subbag.doc = + use Bag fromText subbag + {{ + `` subbag b1 b2 `` returns `` true `` if no element occurs more times in the + {type Bag} `b1` than in the {type Bag} `b2`. + + # Example + + ``` + b1 = fromText "abracadabra" + b2 = fromText "abcd" + (subbag b2 b1, subbag b1 b2) + ``` + }} + +test> data.Bag.subbag.tests.homomorphism = runs 100 do + use Bag count + k = natInOrder() + a = bagOf natInOrder () + b = bagOf natInOrder () + expect (implies (Bag.subbag a b) (Universal.lteq (count k a) (count k b))) + +data.Bag.superbag : Bag a -> Bag a -> Boolean +data.Bag.superbag = flip Bag.subbag + +data.Bag.superbag.doc : Doc +data.Bag.superbag.doc = + use Bag fromText superbag + {{ + `` superbag b1 b2 `` returns `true` if no element occurs more times in the + {type Bag} `b2` than in the {type Bag} `b1`. + + # Example + + ``` + b1 = fromText "abracadabra" + b2 = fromText "abcd" + (superbag b2 b1, superbag b1 b2) + ``` + }} + +test> data.Bag.superbag.tests.homomorphism = runs 100 do + use Bag count + k = natInOrder() + a = bagOf natInOrder () + b = bagOf natInOrder () + expect (implies (Bag.superbag a b) (Universal.gteq (count k a) (count k b))) + +data.Bag.tests.bagOf : '{Gen} a -> '{Gen} Bag a +data.Bag.tests.bagOf a = + do + Bag.fromOccurrenceList + (Map.toList (tests.mapOf a (do natInOrder() Nat.+ 1) ())) + +data.Bag.tests.bagOf.doc : Doc +data.Bag.tests.bagOf.doc = + {{ + Given a generator of elements, constructs a generator of {type Bag}s with + elements of that type. + + # Example + + ``` + List.map Bag.toList (deprecated.sample 10 (bagOf gen.int)) + ``` + }} + +data.Bag.toList : Bag a -> [a] +data.Bag.toList = + use List ++ + k z = cases (e, n) -> z ++ (List.replicate n do e) + List.foldLeft k [] << Bag.occurrenceList + +data.Bag.toList.doc : Doc +data.Bag.toList.doc = + use Bag toList + {{ + `` toList b `` returns the list of all the elements of the {type Bag} `b`, + including duplicates. + + # Example + + ``` + toList (Bag.fromText "🥒🥕🥔🥕") + ``` + }} + +test> data.Bag.toList.tests.roundtrip = runs 100 do + use Bag == + b = bagOf natInOrder () + l = Bag.toList b + expect (Bag.fromList l == b) + +data.Bag.toSet : Bag a -> Set a +data.Bag.toSet = internal.Set << Map.map const() << Bag.counts + +data.Bag.toSet.doc : Doc +data.Bag.toSet.doc = + use Bag toSet + {{ + `` toSet b `` returns the {type Set} of elements in the {type Bag} `b`. + + # Example + + ``` + Set.toList (toSet (Bag.fromText "🥒🥕🥔🥕")) + ``` + }} + +test> data.Bag.toSet.tests.roundtrip = + runs 100 do + b = bagOf natInOrder () + s = Bag.toSet b + expect + (Set.all (a -> Bag.contains b a) s && Bag.all (a -> Set.contains a s) b) + +data.Bag.toText : Bag Char -> Text +data.Bag.toText = fromCharList << Bag.toList + +data.Bag.toText.doc : Doc +data.Bag.toText.doc = + use Bag toText + {{ + `` toText b `` takes a {type Bag} of characters of type {type Char} and + returns {type Text} with those characters in an unspecified order. + + # Example + + ``` + b = Bag.fromText "👗👠👗🎩👠👠" + toText b + ``` + }} + +test> data.Bag.toText.tests.roundtrip = runs 100 do + use Bag == + b = bagOf Char.ascii () + t = Bag.toText b + expect (Bag.fromText t == b) + +data.Bag.traverse : (a ->{e} b) -> Bag a ->{e} Bag b +data.Bag.traverse f = Bag.flatMap (Bag.singleton << f) + +data.Bag.traverse.doc : Doc +data.Bag.traverse.doc = + {{ + `` traverse f b `` iterates over the elements of the {type Bag} `b`, applying + the function `f` to each element, collecting the results in a new {type Bag}. + + # Example + + ``` + b = Bag.fromList [5, 2, 4, 4, 2, 6] + randoms = do traverse (x -> Random.natIn 0 x) b + Bag.toList (lcg 3735928559 randoms) + ``` + }} + +test> data.Bag.traverse.tests.functor = runs 100 do + use Bag == + b = bagOf natInOrder () + expect (traverse id b == b) + +data.Bag.union : Bag a -> Bag a -> Bag a +data.Bag.union = cases + MkBag b1 -> cases MkBag b2 -> MkBag (Map.unionWith Universal.max b1 b2) + +data.Bag.union.doc : Doc +data.Bag.union.doc = + use Bag fromText union + {{ + `` union b1 b2 `` constructs the union of `b1` and `b2`, which is the + smallest {type Bag} that is a {Bag.superbag} of both `b1` and `b2`. This is a + new {type Bag} with elements from `b1` and `b2` where the number of times + each element occurs in the new {type Bag} is either the number of times it + occurs in `b1` or the number of times it occurs in `b2`, whichever is + greater. + + # Example + + ``` + b1 = fromText "🥒🥕🥔🥕" + b2 = fromText "🥒🥕🥔🥑🥒" + Bag.toText (union b1 b2) + ``` + }} + +test> data.Bag.union.tests.commutative = runs 100 do + use Bag == union + x = bagOf natInOrder () + y = bagOf natInOrder () + expect (union x y == union y x) + +test> data.Bag.union.tests.distributive = runs 100 do + use Bag == intersect union + x = bagOf natInOrder () + y = bagOf natInOrder () + z = bagOf natInOrder () + expect (union x (intersect y z) == intersect (union x y) (union x z)) + +test> data.Bag.union.tests.homomorphism = + runs 100 do + use Bag count + use Char ascii + a = bagOf ascii () + b = bagOf ascii () + c = Bag.occurrenceList (Bag.union a b) + expect + (List.all (cases (e, n) -> n === Universal.max (count e a) (count e b)) c) + +test> data.Bag.union.tests.idempotent = runs 100 do + use Bag == + x = bagOf natInOrder () + expect (Bag.union x x == x) + +test> data.Bag.union.tests.unit = runs 100 do + use Bag == + x = bagOf natInOrder () + expect (Bag.union x Bag.empty == x) + +(data.ByteArray.++) : data.ByteArray -> data.ByteArray -> data.ByteArray +(data.ByteArray.++) = cases + BArr o1 l1 a1, BArr o2 l2 a2 -> + use Nat + + use data.ByteArray.Raw copyTo! + sz = l1 + l2 + ByteArray.new! sz cases + MBArr _ _ ma -> + copyTo! ma 0 a1 o1 l1 + copyTo! ma l1 a2 o2 l2 + +data.ByteArray.++.doc : Doc +data.ByteArray.++.doc = + use ByteArray ++ fromList + {{ + Joins together two byte arrays. This requires copying both arrays to a new + array, so it has a cost proportional to the combined lengths of both arrays. + + ``` + fromList [1, 2, 3] ++ fromList [4, 5, 6] + ``` + }} + +data.ByteArray.append.doc : Doc +data.ByteArray.append.doc = + use ByteArray fromBytes + use fromList impl + {{ + Constructs a new {type data.ByteArray} with the contents of both the given + {type data.ByteArray}s, in order. + + # Example + + ``` + ByteArray.toList + (ByteArray.append (fromBytes 0xs01020304) (fromBytes 0xs05060708)) + ``` + }} + +test> data.ByteArray.append.test = test.verify do + use ByteArray fromList + use List ++ replicate + use Random natIn + Each.repeat 100 + l1 = replicate (natIn 0 20) do natIn 0 255 + l2 = replicate (natIn 0 20) do natIn 0 255 + l3 = l1 ++ l2 + a1 : data.ByteArray + a1 = fromList l1 + a2 = fromList l2 + ensureEqual (fromList l3) (ByteArray.append a1 a2) + +data.ByteArray.BArr.doc : Doc +data.ByteArray.BArr.doc = + {{ + The constructor of {type data.ByteArray} values. You should in general not + use this constructor directly. Instead, use {ByteArray.fromBytes}, + {ByteArray.fromList}, or {ByteArray.freeze!}. + + Takes an offset and a length, and a {type data.ByteArray.Raw} that contains + the bytes. The offset and length are used to specify a slice of the raw array + that the {type data.ByteArray} value represents. + }} + +data.ByteArray.base32Hex : data.ByteArray -> Text +data.ByteArray.base32Hex = cases + BArr off len arr -> handle Raw.base32Hex arr off len with impossible + +data.ByteArray.base32Hex.doc : Doc +data.ByteArray.base32Hex.doc = + {{ + Encodes an array using the base 32 hex encoding. + + ``` + ByteArray.base32Hex (ByteArray.fromList [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + ``` + }} + +test> data.ByteArray.base32Hex.test = test.verify do + use Text == + _ = Each.repeat 100 + tx = text.base32Hex() + match fromBase32Hex (Text.toUtf8 tx) with + Right bs -> + ba = ByteArray.fromBytes bs + ensure (tx == ByteArray.base32Hex ba) + Left _ -> bug "falied to decode base32Hex" + +data.ByteArray.doc : Doc +data.ByteArray.doc = + use ByteArray ++ toList + {{ + The type {type data.ByteArray} represents a raw immutable array of + [unboxed](https://en.wikipedia.org/wiki/Boxing_%28computer_science%29) bytes. + + # Constructing byte arrays + + Copy {type Bytes} into to a new {type data.ByteArray}: + + @signature{ByteArray.fromBytes} + + Copy a {type List} of {type Nat} into a new {type data.ByteArray}. The + {type Nat} values must be in the range 0 to 255 (inclusive). + + @signature{ByteArray.fromList} + + Freeze a {type mutable.ByteArray} into a {type data.ByteArray}: + + @signature{ByteArray.freeze!} + + You can also construct a {type data.ByteArray} fom a function that mutates + a {type mutable.ByteArray} using `data.ByteArray.new!`. The function will + be passed a {type mutable.ByteArray} of the requested size: + + ``` + toList + (ByteArray.new! + 3 (arr -> let + use mutable.ByteArray write8 + write8 arr 0 42 + write8 arr 1 43 + write8 arr 2 44)) + ``` + + The low-level {BArr} constructor can be used to construct a + {type data.ByteArray} from its internal representation: + + @source{type data.ByteArray} + + # Querying and indexing byte arrays + + Read a single byte at a given index: + + @signature{data.ByteArray.read8} + + Read two bytes in [big-endian](https://en.wikipedia.org/wiki/Endianness) + order at a given index: + + @signature{ByteArray.read16be} + + Read four bytes in [big-endian](https://en.wikipedia.org/wiki/Endianness) + order at a given index: + + @signature{ByteArray.read32be} + + Read eight bytes in [big-endian](https://en.wikipedia.org/wiki/Endianness) + order at a given index: + + @signature{ByteArray.read64be} + + Get the number of bytes in a {type data.ByteArray}: + + @signature{data.ByteArray.size} + + # Writing to byte arrays + + Write a single byte to a {type data.ByteArray}. Note that this will create + a new {type data.ByteArray} with the updated value and leave the original + {type data.ByteArray} unchanged: + + @signature{data.ByteArray.write8} + + # Combining byte arrays + + Concatenate two {type data.ByteArray}s: + + @signature{++} + + Concatenate a list of {type data.ByteArray}s: + + @signature{ByteArray.join} + + # Slicing byte arrays + + Get a prefix of a {type data.ByteArray}: + + @signature{ByteArray.take} + + Drop a prefix of a {type data.ByteArray}: + + @signature{ByteArray.drop} + + Get a slice of a {type data.ByteArray}: + + @signature{ByteArray.slice} + + # Converting to other types + + Convert a {type data.ByteArray} to a {type List} of {type Nat}: + + @signature{toList} + + Convert a {type data.ByteArray} to {type Text} using the + [base32hex](https://en.wikipedia.org/wiki/Base32#base32hex) encoding: + + @signature{ByteArray.base32Hex} + }} + +data.ByteArray.drop : Nat -> data.ByteArray -> data.ByteArray +data.ByteArray.drop n = cases + ba@(BArr o l a) + | n Nat.== 0 -> ba + | n Nat.>= l -> BArr (o Nat.+ l) 0 a + | otherwise -> BArr (o Nat.+ n) (l Nat.- n) a + +data.ByteArray.drop.doc : Doc +data.ByteArray.drop.doc = + {{ + Returns a new {type data.ByteArray} representing the portion of the original + with an initial segment removed. + + This operation is fast, but shares the underlying storage with the original + array. The potential downside is that more memory may be used than is + necessary. + }} + +test> data.ByteArray.drop.test = test.verify do + use ByteArray fromList toList + use Random natIn + Each.repeat 100 + l = List.replicate (natIn 0 30) do natIn 0 255 + a = fromList l + n = natIn 0 60 + ensuring do toList (fromList (List.drop n l)) === toList (ByteArray.drop n a) + +data.ByteArray.find : + (Nat ->{g} Boolean) -> data.ByteArray ->{g, Exception} Optional Nat +data.ByteArray.find p arr = + ByteArray.firstIndexOf (p << data.ByteArray.read8 arr) arr + +data.ByteArray.find.doc : Doc +data.ByteArray.find.doc = + use ByteArray find + use Nat == + {{ + `` find p arr `` returns the index of the first element of the byte array + `arr` that satisfies the predicate `p`, or {None} if no such element exists. + + # Example + + ``` + catch do find ((==) 3) (ByteArray.fromList [1, 2, 3, 4, 5]) + ``` + }} + +data.ByteArray.findLast : + (Nat ->{g} Boolean) -> data.ByteArray ->{g, Exception} Optional Nat +data.ByteArray.findLast p arr = + ByteArray.lastIndexOf (p << data.ByteArray.read8 arr) arr + +data.ByteArray.findLast.doc : Doc +data.ByteArray.findLast.doc = + use ByteArray findLast + use Nat == + {{ + `` findLast p arr `` returns the index of the last element of the byte array + `arr` that satisfies the predicate `p`, or {None} if no such element exists. + + # Example + + ``` + catch do findLast ((==) 3) (ByteArray.fromList [1, 2, 3, 4, 5]) + ``` + }} + +data.ByteArray.firstIndexOf : + (Nat ->{g} Boolean) -> data.ByteArray ->{g, Exception} Optional Nat +data.ByteArray.firstIndexOf p arr = + go : Nat -> Optional Nat + go ix = + use Nat + < + sz = data.ByteArray.size arr + if ix < sz then + if p (data.ByteArray.read8 arr ix) then Some ix else go (ix + 1) + else None + go 0 + +data.ByteArray.firstIndexOf.doc : Doc +data.ByteArray.firstIndexOf.doc = + use ByteArray firstIndexOf + use Nat == + {{ + `` firstIndexOf p arr `` returns the index of the first element of the byte + array `arr` that satisfies the predicate `p`, or {None} if no such element + exists. + + # Example + + ``` + catch do firstIndexOf ((==) 3) (ByteArray.fromList [1, 2, 3, 4, 5]) + ``` + }} + +data.ByteArray.fromBytes : Bytes -> data.ByteArray +data.ByteArray.fromBytes bs = BArr 0 (Bytes.size bs) (Raw.fromBytes bs) + +data.ByteArray.fromBytes.doc : Doc +data.ByteArray.fromBytes.doc = + {{ + Creates a {type data.ByteArray} from the contents of a {type Bytes}. + + ``` + ByteArray.toList (ByteArray.fromBytes 0xsffff) + ``` + }} + +data.ByteArray.fromList : [Nat] -> data.ByteArray +data.ByteArray.fromList l = Scope.run do + use Nat + + sz = List.size l + dst = Scope.Raw.byteArray sz + go i = cases + [] -> () + n +: ns -> + Raw.write8 dst i n + go (i + 1) ns + handle go 0 l with impossible + BArr 0 sz (ByteArray.Raw.freeze! dst) + +data.ByteArray.fromList.doc : Doc +data.ByteArray.fromList.doc = + {{ + Creates a new array containing the bytes in the list. + + ``` + ByteArray.toList (ByteArray.fromList [1, 2, 3]) + ``` + }} + +data.ByteArray.join : [data.ByteArray] -> data.ByteArray +data.ByteArray.join l = + use List foldLeft + use Nat + + f0 : mutable.ByteArray.Raw g -> Nat -> data.ByteArray ->{g, Exception} Nat + f0 ma mo = cases + BArr o l a -> + data.ByteArray.Raw.copyTo! ma mo a o l + mo + l + f : mutable.ByteArray g -> Nat -> data.ByteArray ->{g, Exception} Nat + f = cases MBArr _ _ ma -> f0 ma + sz = foldLeft (n a -> n + data.ByteArray.size a) 0 l + ByteArray.new! + sz (ma -> let + _ = foldLeft (f ma) 0 l + ()) + +data.ByteArray.join.doc : Doc +data.ByteArray.join.doc = + use ByteArray fromList + {{ + Joins several [byte arrays]({type data.ByteArray}) into a single array. Each + of the input arrays is copied to the output, so no storage is shared. + + ``` + ByteArray.join [fromList [1, 2], fromList [3], fromList [4, 5, 6]] + ``` + }} + +test> data.ByteArray.join.test = test.verify do + use ByteArray fromList + use List replicate + use Random natIn + Each.repeat 100 + by = do natIn 0 255 + ls = replicate (natIn 0 5) do replicate (natIn 0 20) by + as = List.map fromList ls + ensureEqual (fromList (List.join ls)) (ByteArray.join as) + +data.ByteArray.lastIndexOf : + (Nat ->{g} Boolean) -> data.ByteArray ->{g, Exception} Optional Nat +data.ByteArray.lastIndexOf p arr = + sz = data.ByteArray.size arr + go : Nat -> Optional Nat + go ix = + use Nat - > + ix' = ix - 1 + if ix > 0 then + if p (data.ByteArray.read8 arr ix') then Some ix' else go ix' + else None + go sz + +data.ByteArray.lastIndexOf.doc : Doc +data.ByteArray.lastIndexOf.doc = + use ByteArray lastIndexOf + use Nat == + {{ + `` lastIndexOf p arr `` returns the index of the last element of the byte + array `arr` that satisfies the predicate `p`, or {None} if no such element + exists. + + # Example + + ``` + catch do lastIndexOf ((==) 3) (ByteArray.fromList [1, 2, 3, 4, 5]) + ``` + }} + +data.ByteArray.new! : + Nat + -> (∀ s. mutable.ByteArray (Scope s) ->{g, Exception, Scope s} ()) + ->{g} data.ByteArray +data.ByteArray.new! n k = + ba = Raw.new! n (ma -> k (MBArr 0 n ma)) + BArr 0 n ba + +data.ByteArray.new!.doc : Doc +data.ByteArray.new!.doc = + use mutable.ByteArray write8 + {{ + Given a size and a function that initializes a + [mutable byte array]({type mutable.ByteArray}), creates an immutable + {type data.ByteArray} that is initialized in the way specified by the + function. + + # Example + + ``` + ByteArray.toList + (ByteArray.new! + 5 (arr -> let + write8 arr 0 1 + write8 arr 1 2 + write8 arr 2 3 + write8 arr 3 4 + write8 arr 4 5)) + ``` + }} + +data.ByteArray.randomChoice : data.ByteArray ->{Exception, Random} Nat +data.ByteArray.randomChoice byteArray = + randomIndex = Random.natIn 0 (data.ByteArray.size byteArray) + match catch do data.ByteArray.read8 byteArray randomIndex with + Right v -> v + Left _ -> + Exception.raise + (Failure + (typeLink data.ByteArray) + "data.ByteArray.randomChoice: index out of bounds" + (Any byteArray)) + +data.ByteArray.randomChoice.doc : Doc +data.ByteArray.randomChoice.doc = + use ByteArray fromList randomChoice + {{ + Picks a random byte (as a {type Nat}) from the given {type data.ByteArray}. + Assumes that the {type data.ByteArray} is not empty, so an empty + {type data.ByteArray} will raise an {type Exception}. + + # Examples + + ``` + catch do lcg 4096 do randomChoice (fromList [0, 3, 5, 7]) + ``` + + ``` + catch do lcg 2510 do randomChoice (fromList [0, 3, 5, 7]) + ``` + }} + +test> data.ByteArray.randomChoice.test = test.verify do + list = List.range 0 10 + set = Set.fromList list + byteArray = ByteArray.fromList list + Each.repeat 1000 + e = ByteArray.randomChoice byteArray + ensure (Set.contains e set) + +data.ByteArray.Raw.base32Hex : data.ByteArray.Raw -> Nat -> Nat -> Text +data.ByteArray.Raw.base32Hex arr off0 len = + use Nat + <= shiftLeft + readBytes off = cases + 0 -> (0, 8) + 1 -> (shiftLeft (data.ByteArray.Raw.read8 arr off) 2, 6) + 2 -> (shiftLeft (data.ByteArray.Raw.read16be arr off) 4, 4) + 3 -> (shiftLeft (data.ByteArray.Raw.read24be arr off) 1, 3) + 4 -> (shiftLeft (data.ByteArray.Raw.read32be arr off) 3, 1) + _ -> (data.ByteArray.Raw.read40be arr off, 0) + go acc off = + use Nat - > + use Text ++ + csz = len - off + match readBytes off csz with + (_, 8) -> acc + (n, p) + | p > 0 -> acc ++ encodeChunk n p + | otherwise -> go (acc ++ encodeChunk n p) (off + 5) + if off0 + len <= data.ByteArray.Raw.size arr then + handle go "" off0 with impossible + else "" + +data.ByteArray.Raw.base32Hex.doc : Doc +data.ByteArray.Raw.base32Hex.doc = + {{ + Encodes a portion of a byte array using the base 32 hex encoding. + + The first argument is an offset. The second is the number of bytes to encode. + + ``` + (BArr off len arr) = ByteArray.fromList [0, 10, 15] + Raw.base32Hex arr off len + ``` + }} + +-- builtin data.ByteArray.Raw.copyTo! : +-- mutable.ByteArray.Raw g +-- -> Nat +-- -> data.ByteArray.Raw +-- -> Nat +-- -> Nat +-- ->{g, Exception} () + +data.ByteArray.Raw.copyTo!.doc : Doc +data.ByteArray.Raw.copyTo!.doc = + {{ + `` data.ByteArray.Raw.copyTo! dst doff src soff len `` copies `len` bytes + from `src` to `dst`. `doff` and `soff` are byte offsets into the + corresponding arrays. + + Note that the destination array/offset comes first. + }} + +data.ByteArray.Raw.doc : Doc +data.ByteArray.Raw.doc = + use Raw toBytes + {{ + {type data.ByteArray.Raw} is the type of raw, immutable, unboxed arrays of + bytes. It's a low-level type that refers to a region of memory managed by the + runtime. + + It is similar to {type mutable.ByteArray.Raw}, but cannot be modified in + place. It's also similar to {type data.Array.Raw}, but is unboxed and thus + more efficient. + + The main use case for {type data.ByteArray.Raw} is to serve as the internal + representation of {type data.ByteArray}, and you should generally use that + type instead. A {type data.ByteArray} is implemented as a slice of an + underlying {type data.ByteArray.Raw}. + + @source{type data.ByteArray} + + # Constructing raw byte arrays + + Construct a {type data.ByteArray.Raw} from {type Bytes}: + + @signature{Raw.fromBytes} + + Freeze a portion of a {type mutable.ByteArray.Raw}. The mutable array can + still be safely modified after this operation: + + @signature{ByteArray.Raw.freeze} + + Freeze a {type mutable.ByteArray.Raw} in place. The mutable array can no + longer be modified after this operation: + + @signature{ByteArray.Raw.freeze!} + + You can also construct a {type data.ByteArray.Raw} from a function that + takes a {type mutable.ByteArray.Raw} and fills it with data: + + ``` + toBytes + (Scope.run do + Raw.new! + 10 (buf -> let + Raw.write64be buf 0 1311768467294899695 + Raw.write16be buf 8 4660)) + ``` + + # Using raw byte arrays + + Get the size of a {type data.ByteArray.Raw}: + + @signature{data.ByteArray.Raw.size} + + Read a byte from a {type data.ByteArray.Raw} at a given index: + + @signature{data.ByteArray.Raw.read8} + + Read two bytes from a {type data.ByteArray.Raw}, in big-endian order, at a + given index: + + @signature{data.ByteArray.Raw.read16be} + + Read three bytes from a {type data.ByteArray.Raw}, in big-endian order, at + a given index: + + @signature{data.ByteArray.Raw.read24be} + + Read four bytes from a {type data.ByteArray.Raw}, in big-endian order, at a + given index: + + @signature{data.ByteArray.Raw.read32be} + + Read five bytes from a {type data.ByteArray.Raw}, in big-endian order, at a + given index: + + @signature{data.ByteArray.Raw.read40be} + + Read eight bytes from a {type data.ByteArray.Raw}, in big-endian order, at + a given index: + + @signature{data.ByteArray.Raw.read64be} + + Copy a {type data.ByteArray.Raw} into a {type mutable.ByteArray.Raw}: + + @signature{data.ByteArray.Raw.copyTo!} + + # Converting raw byte arrays to other types + + Convert a {type data.ByteArray.Raw} to a {type Bytes}: + + @signature{toBytes} + + Convert a {type data.ByteArray.Raw} to a {type Text} in + [base32hex](https://tools.ietf.org/html/rfc4648#section-6): + + @signature{Raw.base32Hex} + + Convert a {type data.ByteArray.Raw} to a {type List} of {type Nat} in the + range 0 to 255: + + @signature{toByteList} + }} + +data.ByteArray.Raw.fromBytes : Bytes -> data.ByteArray.Raw +data.ByteArray.Raw.fromBytes bs = Scope.run do + use Nat + + sz = Bytes.size bs + arr = Scope.Raw.byteArray sz + fill i = match Bytes.at i bs with + Some b -> + Raw.write8 arr i b + fill (i + 1) + None -> () + handle fill 0 + with cases + { _ } -> () + { Exception.raise _ -> _ } -> () + ByteArray.Raw.freeze! arr + +data.ByteArray.Raw.fromBytes.doc : Doc +data.ByteArray.Raw.fromBytes.doc = + {{ + Creates a {type data.ByteArray.Raw} from the contents of a {type Bytes}. + }} + +data.ByteArray.Raw.new! : + Nat + -> (∀ s. mutable.ByteArray.Raw (Scope s) ->{g, Exception, Scope s} ()) + ->{g} data.ByteArray.Raw +data.ByteArray.Raw.new! n k = Scope.run do + ma = Scope.Raw.byteArray n + handle k ma + with cases + { Exception.raise fail -> _ } -> bug fail + { a } -> a + ByteArray.Raw.freeze! ma + +data.ByteArray.Raw.new!.doc : Doc +data.ByteArray.Raw.new!.doc = + use Raw write8 + {{ + Given a size and a function that initializes a + [mutable byte array]({type mutable.ByteArray.Raw}), creates an immutable + {type data.ByteArray.Raw} that is initialized in the way specified by the + function. + + # Example + + ``` + Raw.toBytes + (Raw.new! + 5 (arr -> let + write8 arr 0 1 + write8 arr 1 2 + write8 arr 2 3 + write8 arr 3 4 + write8 arr 4 5)) + ``` + }} + +-- builtin data.ByteArray.Raw.read16be : +-- data.ByteArray.Raw -> Nat ->{Exception} Nat + +data.ByteArray.Raw.read16be.doc : Doc +data.ByteArray.Raw.read16be.doc = + {{ + Reads a 16-bit big-endian unsigned integer from a {type data.ByteArray.Raw} + at the given offset. Raises an {type Exception} if the offset is out of + bounds. + }} + +-- builtin data.ByteArray.Raw.read24be : +-- data.ByteArray.Raw -> Nat ->{Exception} Nat + +data.ByteArray.Raw.read24be.doc : Doc +data.ByteArray.Raw.read24be.doc = + {{ + `read24be arr ix` yields a 24-bit big endian value starting at __byte__ + offset `ix` in the array. + }} + +-- builtin data.ByteArray.Raw.read32be : +-- data.ByteArray.Raw -> Nat ->{Exception} Nat + +data.ByteArray.Raw.read32be.doc : Doc +data.ByteArray.Raw.read32be.doc = + {{ + Reads a 32-bit big-endian unsigned integer from a {type data.ByteArray.Raw} + at the given offset. Raises an {type Exception} if the offset is out of + bounds. + }} + +-- builtin data.ByteArray.Raw.read40be : +-- data.ByteArray.Raw -> Nat ->{Exception} Nat + +data.ByteArray.Raw.read40be.doc : Doc +data.ByteArray.Raw.read40be.doc = + {{ + `read24be arr ix` yields a 40-bit big endian value starting at __byte__ + offset `ix` in the array. + }} + +-- builtin data.ByteArray.Raw.read64be : +-- data.ByteArray.Raw -> Nat ->{Exception} Nat + +data.ByteArray.Raw.read64be.doc : Doc +data.ByteArray.Raw.read64be.doc = + {{ + Reads a 64-bit big-endian unsigned integer from a {type data.ByteArray.Raw} + at the given offset. Raises an {type Exception} if the offset is out of + bounds. + }} + +-- builtin data.ByteArray.Raw.read8 : +-- data.ByteArray.Raw -> Nat ->{Exception} Nat + +data.ByteArray.Raw.read8.doc : Doc +data.ByteArray.Raw.read8.doc = + use Raw fromBytes + use data.ByteArray.Raw read8 + use fromList impl + {{ + Reads an 8-bit unsigned integer from the given raw byte array at the given + index, returning it as a {type Nat}. Throws an {type ArrayFailure} + {type Exception} if the index is out of bounds. + + # Example + + ``` + catch do read8 (fromBytes 0xs01020304) 2 + ``` + + ``` + catch do read8 (fromBytes 0xs01020304) 42 + ``` + }} + +-- builtin data.ByteArray.Raw.size : data.ByteArray.Raw -> Nat + +data.ByteArray.Raw.size.doc : Doc +data.ByteArray.Raw.size.doc = {{ Gets the size of the given raw byte array. }} + +data.ByteArray.Raw.toByteList : data.ByteArray.Raw -> [Nat] +data.ByteArray.Raw.toByteList arr = + use List :+ + use Nat + < + sz = data.ByteArray.Raw.size arr + f acc = cases + i + | i < sz -> f (acc :+ data.ByteArray.Raw.read8 arr i) (i + 1) + | otherwise -> acc + unsafeRun! do f [] 0 + +data.ByteArray.Raw.toByteList.doc : Doc +data.ByteArray.Raw.toByteList.doc = + {{ + Produces a list of bytes in the array. + + ``` + toByteList (Raw.fromBytes 0xs01020304) + ``` + }} + +data.ByteArray.Raw.toBytes : data.ByteArray.Raw -> Bytes +data.ByteArray.Raw.toBytes arr = fromList.impl (toByteList arr) + +data.ByteArray.Raw.toBytes.doc : Doc +data.ByteArray.Raw.toBytes.doc = + {{ + Produces a {type Bytes} values containing the same bytes as the array. + + ``` + Raw.toBytes (Raw.fromBytes 0xs10203040) + ``` + }} + +data.ByteArray.read16be : data.ByteArray -> Nat ->{Exception} Nat +data.ByteArray.read16be = cases + ba@(BArr o l a), n + | n Nat.+ 1 Nat.< l -> data.ByteArray.Raw.read16be a (o Nat.+ n) + | otherwise -> ArrayFailure.raise "read16be" (ba, n) + +data.ByteArray.read16be.doc : Doc +data.ByteArray.read16be.doc = + {{ + Reads a 16-bit big endian value starting at the **byte** offset given by the + second argument. + + Throws an exception if there are insufficient bytes in the array, past the + offset. + }} + +data.ByteArray.read32be : data.ByteArray -> Nat ->{Exception} Nat +data.ByteArray.read32be = cases + ba@(BArr o l a), n + | n Nat.+ 3 Nat.< l -> data.ByteArray.Raw.read32be a (o Nat.+ n) + | otherwise -> ArrayFailure.raise "read32be" (ba, n) + +data.ByteArray.read32be.doc : Doc +data.ByteArray.read32be.doc = + {{ + Reads a 32-bit big endian value starting at the **byte** offset given by the + second argument. + + Throws an exception if there are insufficient bytes in the array, past the + offset. + }} + +data.ByteArray.read64be : data.ByteArray -> Nat ->{Exception} Nat +data.ByteArray.read64be = cases + ba@(BArr o l a), n + | n Nat.+ 7 Nat.< l -> data.ByteArray.Raw.read64be a (o Nat.+ n) + | otherwise -> ArrayFailure.raise "read64be" (ba, n) + +data.ByteArray.read64be.doc : Doc +data.ByteArray.read64be.doc = + {{ + Reads a 64-bit big endian value starting at the **byte** offset given by the + second argument. + + Throws an exception if there are insufficient bytes in the array, past the + offset. + }} + +data.ByteArray.read8 : data.ByteArray -> Nat ->{Exception} Nat +data.ByteArray.read8 arr i = + match arr with + BArr off len src| i Nat.< len -> + data.ByteArray.Raw.read8 src (off Nat.+ i) + _ -> ArrayFailure.raise "data.ByteArray.read8: index out of bounds" i + +data.ByteArray.read8.doc : Doc +data.ByteArray.read8.doc = + {{ + Gets a single byte from an array. + + Throws an exception if the index is out of bounds. + + ``` + catch do data.ByteArray.read8 (ByteArray.fromList [0, 1, 2, 3]) 5 + ``` + }} + +data.ByteArray.size : data.ByteArray -> Nat +data.ByteArray.size = cases BArr _ length _ -> length + +data.ByteArray.size.doc : Doc +data.ByteArray.size.doc = + {{ + Gets the size of a {type data.ByteArray}, in bytes. + + # Example + + ``` + data.ByteArray.size (ByteArray.fromBytes 0xs0102030405) + ``` + }} + +test> data.ByteArray.size.test = test.verify do + use Random natIn + Each.repeat 100 + l = List.replicate (natIn 0 40) do natIn 0 255 + ensureEqual (List.size l) (data.ByteArray.size (ByteArray.fromList l)) + +data.ByteArray.slice : + Nat -> Nat -> data.ByteArray ->{Exception} data.ByteArray +data.ByteArray.slice o l = cases + BArr off len arr| o Nat.+ l Nat.<= len -> BArr (off Nat.+ o) l arr + _ -> + ArrayFailure.raise "data.ByteArray.slice: not enough elements" (o Nat.+ l) + +data.ByteArray.slice.doc : Doc +data.ByteArray.slice.doc = + use ByteArray slice + {{ + {slice} returns a new {type data.ByteArray} representing a portion of the + original. + + The first argument specifies how many bytes to drop from the beginning of the + array. The second argument specifies how many values to retain. The original + array must have a length at least as long as the offset plus the new length. + + This operation is fast, replacing only the offset and length, and retaining + the underlying storage. The potential downside is that more memory may be + used than necessary. + + ``` + catch do ByteArray.toList (slice 1 2 (ByteArray.fromList [1, 2, 3, 4, 5])) + ``` + }} + +data.ByteArray.take : Nat -> data.ByteArray -> data.ByteArray +data.ByteArray.take n = cases + ba@(BArr o l a) + | l Nat.<= n -> ba + | otherwise -> BArr o n a + +data.ByteArray.take.doc : Doc +data.ByteArray.take.doc = + {{ + Returns a new {type data.ByteArray} representing an initial portion of the + original. + + This operation is fast, but shares the underlying storage with the original + array. The potential downside is that more memory may be used than is + necessary. + }} + +test> data.ByteArray.take.test = + test.verify do + use ByteArray fromList toList + use Random natIn + Each.repeat 100 + l = List.replicate (natIn 0 30) do natIn 0 255 + n = natIn 0 60 + ensureEqual + (toList (fromList (List.take n l))) + (toList (ByteArray.take n (fromList l))) + +data.ByteArray.toList : data.ByteArray -> [Nat] +data.ByteArray.toList = cases + BArr off len arr -> + use List :+ + use Nat + >= + max = off + len + go acc i = + if i >= max then acc + else go (acc :+ data.ByteArray.Raw.read8 arr i) (i + 1) + handle go [] off with impossible + +data.ByteArray.toList.doc : Doc +data.ByteArray.toList.doc = + {{ + Gets the list of bytes in a byte array. + + ``` + ByteArray.toList (ByteArray.fromList [1, 2, 3]) + ``` + }} + +test> data.ByteArray.toList.test = test.verify do + use Random natIn + Each.repeat 100 + l = List.replicate (natIn 0 40) do natIn 0 255 + ensureEqual l (ByteArray.toList (ByteArray.fromList l)) + +data.ByteArray.write8 : + data.ByteArray -> Nat -> Nat ->{Exception} data.ByteArray +data.ByteArray.write8 arr i v = + match arr with + BArr off len src| i Nat.< len -> + Scope.run do + dst = Scope.Raw.byteArray len + data.ByteArray.Raw.copyTo! dst 0 src off len + Raw.write8 dst i v + BArr 0 len (ByteArray.Raw.freeze! dst) + _ -> ArrayFailure.raise "data.ByteArray.write8: index out of bounds" i + +data.ByteArray.write8.doc : Doc +data.ByteArray.write8.doc = + use ByteArray fromList + {{ + Writes a single byte to an array. + + Throws an exception if the index is out of bounds. + + Note: this operation is slow (O(length)), because it must copy the array's + contents to a new array. + + ``` + catch do + arr1 = data.ByteArray.write8 (fromList [0, 1, 2, 3]) 1 5 + arr1 === fromList [0, 5, 2, 3] + ``` + }} + +data.deprecated.Heap.breakOffMax : Heap k v -> (k, v, Optional (Heap k v)) +data.deprecated.Heap.breakOffMax h = + (k, v) = Heap.max h + (k, v, Heap.pop h) + +data.deprecated.Heap.breakOffMax.doc : Doc +data.deprecated.Heap.breakOffMax.doc = + use Heap breakOffMax + {{ + `` breakOffMax h `` returns the largest element of the heap `h` and the heap + with that element removed. + + # Example + + ``` + (k, v, h) = + breakOffMax + (Heap.fromList ((1, "a") +| [(2, "b"), (3, "c"), (4, "d"), (5, "e")])) + (k, v, Optional.fold (do []) (List.Nonempty.toList << Heap.toList) h) + ``` + }} + +data.deprecated.Heap.children : Heap k v -> [Heap k v] +data.deprecated.Heap.children = cases Heap _ _ _ cs -> cs + +data.deprecated.Heap.children.doc : Doc +data.deprecated.Heap.children.doc = + {{ + Returns the children of a {type Heap} as a {type List} of {type Heap}s. + + # Example + + ``` + List.map + Heap.toList (children (Heap.fromList ((1, "a") +| [(2, "b"), (3, "c")]))) + ``` + }} + +data.deprecated.Heap.doc : Doc +data.deprecated.Heap.doc = + {{ + A {type Heap} is a + [max-heap](https://en.wikipedia.org/wiki/Heap_(data_structure)) of key-value + pairs, with the property that the key of each node is greater than or equal + to the keys of its children. The maximum key is always at the root of the + heap. + + {{ + docCallout + (Some {{ ⚠️ }}) + {{ + This type is deprecated and will be removed in a future release. + + Please use the + [Heap](https://share.unison-lang.org/@stew/p/code/latest/types/public/projects/dataextra/latest/heap/Heap) + type from the + [data-extra](https://share.unison-lang.org/@stew/p/code/latest/types/public/projects/dataextra/latest/) + library instead. + }} }} + + The {type Heap} type is parameterized by the key type and the value type. + + # Constructing a heap + + Construct a heap from a list of key-value pairs: + + @signature{mayFromList} + + Construct a heap from a list of keys only. The values will be the keys + themselves: + + @signature{fromKeys} + + Construct a heap with a single key-value pair: + + @signature{Heap.singleton} + + # Adding and removing elements + + Insert a key-value pair into a heap: + + @signature{Heap.insert} + + Remove the maximum key-value pair from a heap: + + @signature{Heap.pop} + + Add a heap to another heap: + + @signature{Heap.union} + + # Querying a heap + + Get the maximum key-value pair from a heap: + + @signature{Heap.max} + + Get the child heaps of a heap: + + @signature{children} + + Get the size of a heap: + + @signature{Heap.size} + }} + +data.deprecated.Heap.fromKeys : [a] -> Optional (Heap a a) +data.deprecated.Heap.fromKeys as = mayFromList (List.map (a -> (a, a)) as) + +data.deprecated.Heap.fromKeys.doc : Doc +data.deprecated.Heap.fromKeys.doc = + {{ + Constructs a {type Heap} from a {type List} of keys. The values are set to + the keys themselves. + + # Example + + ``` + Optional.map Heap.toList (fromKeys [1, 2, 3]) + ``` + + # See also + + {Heap.fromList} + }} + +data.deprecated.Heap.fromList : List.Nonempty (k, v) -> Heap k v +data.deprecated.Heap.fromList kvs = + List.foldLeft + (cases h, (k, v) -> Heap.insert k v h) + (uncurry Heap.singleton (Nonempty.head kvs)) + (Nonempty.tail kvs) + +data.deprecated.Heap.fromList.doc : Doc +data.deprecated.Heap.fromList.doc = + {{ + Create a {type Heap} from a non-empty list of key-value pairs. + + # Example + + ``` + Heap.fromList ((1, "one") +| [(2, "two"), (3, "three")]) + ``` + }} + +data.deprecated.Heap.insert : k -> v -> Heap k v -> Heap k v +data.deprecated.Heap.insert k v h = Heap.union h (Heap.singleton k v) + +data.deprecated.Heap.insert.doc : Doc +data.deprecated.Heap.insert.doc = + {{ Puts a new key (priority) and value in the [Heap]({type Heap}). }} + +data.deprecated.Heap.max : Heap k v -> (k, v) +data.deprecated.Heap.max = cases Heap _ k v _ -> (k, v) + +data.deprecated.Heap.max.doc : Doc +data.deprecated.Heap.max.doc = + {{ + Gets the value under the maximum key in a {type Heap} together with the key + itself. + + # Example + + ``` + Optional.map + Heap.max (mayFromList [(1, "Lions"), (2, "Tigers"), (3, "Bears")]) + ``` + }} + +data.deprecated.Heap.maxKey : Heap k v -> k +data.deprecated.Heap.maxKey = cases Heap _ k _ _ -> k + +data.deprecated.Heap.maxKey.doc : Doc +data.deprecated.Heap.maxKey.doc = + {{ + Returns the maximum key in the heap. + + # Example + + ``` + Heap.maxKey (Heap.fromList ((1, "a") +| [(2, "b"), (3, "c")])) + ``` + }} + +data.deprecated.Heap.mayFromList : [(k, v)] -> Optional (Heap k v) +data.deprecated.Heap.mayFromList kvs = + op a b = match a with + None -> b + Some a -> + match b with + None -> Some a + Some b -> Some (Heap.union a b) + single = cases (k, v) -> Some (Heap.singleton k v) + List.foldBalanced single op None kvs + +data.deprecated.Heap.pop : Heap k v -> Optional (Heap k v) +data.deprecated.Heap.pop h = + use Heap union + use List size unsafeAt + go h subs = + if size subs === 0 then h + else + if size subs === 1 then union h (unsafeAt 0 subs) + else + union + (union h (unsafeAt 0 subs)) (go (unsafeAt 1 subs) (List.drop 2 subs)) + match List.uncons (children h) with + None -> None + Some (s0, subs) -> Some (go s0 subs) + +data.deprecated.Heap.pop.doc : Doc +data.deprecated.Heap.pop.doc = + use Optional toAbort + {{ + Removes the maximum key and its associated value from a {type Heap} and + returns the rest of the {type Heap}, or {None} if there are no more elements. + + # Example + + ``` + toOptional! do + h = toAbort (mayFromList [(1, "Lions"), (2, "Tigers"), (3, "Bears")]) + h' = toAbort (Heap.pop h) + Heap.max h' + ``` + }} + +data.deprecated.Heap.singleton : k -> v -> Heap k v +data.deprecated.Heap.singleton k v = Heap 1 k v [] + +data.deprecated.Heap.singleton.doc : Doc +data.deprecated.Heap.singleton.doc = + {{ + Creates a {type Heap} with a single element. + + # Example + + ``` + Heap.singleton ?k 1 + ``` + }} + +data.deprecated.Heap.size : Heap k v -> Nat +data.deprecated.Heap.size = cases Heap n _ _ _ -> n + +data.deprecated.Heap.size.doc : Doc +data.deprecated.Heap.size.doc = + {{ + Get the number of elements in a {type Heap}. + + # Example + + ``` + Heap.size (Heap.fromList ((1, ?a) +| [(2, ?b), (3, ?c)])) + ``` + }} + +data.deprecated.Heap.sort : [a] -> [a] +data.deprecated.Heap.sort as = sortDescending as |> List.reverse + +data.deprecated.Heap.sort.doc : Doc +data.deprecated.Heap.sort.doc = + use Heap sort + {{ + Heap-sort a {type List}. This is a stable sort, meaning that it preserves the + order of elements that are equal. + + The time complexity is `O(n log n)` and the space usage is `O(n)`. + + # Examples + + ``` + sort [5, 2, 4, 4, 2, 6] + ``` + + ``` + sort [] + ``` + }} + +data.deprecated.Heap.sortDescending : [a] -> [a] +data.deprecated.Heap.sortDescending as = + step = cases + None -> None + Some h -> Some (Heap.max h, Heap.pop h) + List.unfold (fromKeys as) step |> List.map at1 + +data.deprecated.Heap.sortDescending.doc : Doc +data.deprecated.Heap.sortDescending.doc = + {{ + Sorts the given {type List} in descending order, using heap-sort. + + # Example + + ``` + sortDescending [5, 3, 1, 4, 2] + ``` + }} + +data.deprecated.Heap.take : Nat -> Heap k v -> [(k, v)] +data.deprecated.Heap.take n h = + use List :+ + use Nat - == + go acc n h = + if n == 0 then acc + else + (k, v, h') = Heap.breakOffMax h + match h' with + Some h' -> go (acc :+ (k, v)) (n - 1) h' + _ -> acc :+ (k, v) + go [] n h + +data.deprecated.Heap.take.doc : Doc +data.deprecated.Heap.take.doc = + use Heap take + {{ + `` take n h `` returns the largest `n` elements of the heap `h`. + + # Example + + ``` + take + 3 (Heap.fromList ((1, "a") +| [(2, "b"), (3, "c"), (4, "d"), (5, "e")])) + ``` + }} + +data.deprecated.Heap.toList : Heap k v -> List.Nonempty (k, v) +data.deprecated.Heap.toList = cases + Heap _ k v tail -> + (k, v) + +| List.flatMap + (List.Nonempty.toList << data.deprecated.Heap.toList) tail + +data.deprecated.Heap.toList.doc : Doc +data.deprecated.Heap.toList.doc = + {{ + Convert a {type Heap} to a non-empty list of key-value pairs, sorted by key + in descending order. + + # Example + + ``` + Heap.toList (Heap.fromList ((1, "one") +| [(2, "two"), (3, "three")])) + ``` + }} + +data.deprecated.Heap.union : Heap k v -> Heap k v -> Heap k v +data.deprecated.Heap.union h1 h2 = + use List +: + use Nat + + (Heap n k1 v1 hs1, Heap m k2 v2 hs2) = (h1, h2) + if Universal.gteq k1 k2 then Heap (n + m) k1 v1 (h2 +: hs1) + else Heap (n + m) k2 v2 (h1 +: hs2) + +data.deprecated.Heap.union.doc : Doc +data.deprecated.Heap.union.doc = + use Heap fromList + {{ + Returns the union of two {type Heap}s. If a key is present in both + {type Heap}s, the value from the first {type Heap} is used. + + # Example + + ``` + Heap.union + (fromList ((1, "a") +| [(2, "b")])) (fromList ((2, "c") +| [(3, "d")])) + ``` + + ``` + fromList ((1, "a") +| [(2, "b"), (3, "d")]) + ``` + }} + +(data.deprecated.Weighted.<|>) : Weighted a -> Weighted a -> Weighted a +(data.deprecated.Weighted.<|>) = cases + Weighted.Fail, n -> n + Yield x m, n -> Yield x (m data.deprecated.Weighted.<|> n) + Weight w m, Weighted.Fail -> Weight w m + Weight w m, Yield x n -> Yield x (Weight w m data.deprecated.Weighted.<|> n) + Weight w m, Weight w' n -> + if Universal.lt w w' then + Weight w do m() data.deprecated.Weighted.<|> Weight (w' Nat.- w) n + else + if w === w' then Weight w do m() data.deprecated.Weighted.<|> n() + else Weight w do Weight (w Nat.- w') m data.deprecated.Weighted.<|> n() + +data.deprecated.Weighted.append : Weighted a -> Weighted a -> Weighted a +data.deprecated.Weighted.append w1 w2 = match w1 with + Weight n k -> Weight n do data.deprecated.Weighted.append k() w2 + Yield a k -> Yield a (data.deprecated.Weighted.append k w2) + Weighted.Fail -> w2 + +data.deprecated.Weighted.append.doc : Doc +data.deprecated.Weighted.append.doc = + use Weighted append fromList + {{ + `` append w1 w2 `` first yields values from `w1`, then `w2`. For example: + + ``` + Weighted.sample 5 (append (fromList [1, 2, 3]) (fromList [4, 5, 6])) + ``` + }} + +data.deprecated.Weighted.append.examples.ex : [Nat] +data.deprecated.Weighted.append.examples.ex = + use Weighted fromList + Weighted.sample + 10 (Weighted.append (fromList [1, 2, 3]) (fromList [4, 5, 6])) + +data.deprecated.Weighted.dedupe : Weighted a -> Weighted a +data.deprecated.Weighted.dedupe = + use Weighted Fail + go seen = cases + Yield a w -> + if Set.contains a seen then go seen w + else Yield a (go (Set.insert a seen) w) + Weight n w -> Weight n do go seen w() + Fail -> Fail + go Set.empty + +data.deprecated.Weighted.dedupe.doc : Doc +data.deprecated.Weighted.dedupe.doc = + {{ Removes duplicate elements from a {type Weighted}. }} + +data.deprecated.Weighted.doc : Doc +data.deprecated.Weighted.doc = + {{ + {type Weighted} is a lazy sequence with weights, such that the lowest-weight + elements are returned first. This allows you to search infinite spaces + productively, by guarding recursive calls with weights. + + It is used by the {type Gen} ability's {Gen.sample} function to generate + values, prioritizing lower-weight values. + }} + +data.deprecated.Weighted.drop : Nat -> Weighted a -> Weighted a +data.deprecated.Weighted.drop = cases + 0, ws -> ws + n, Weighted.Fail -> Weighted.Fail + n, Weight w ws -> data.deprecated.Weighted.drop n ws() + n, Yield _ ws -> data.deprecated.Weighted.drop (n Nat.- 1) ws + +data.deprecated.Weighted.drop.doc : Doc +data.deprecated.Weighted.drop.doc = + use Weighted drop fromList + {{ + Drops the first `n` elements from a {type Weighted} value. If the provided + {type Weighted} value contains fewer than `n` elements, the result is + {Weighted.Fail}. + + # Examples + + ``` + drop 3 (fromList [1, 2, 3, 4, 5]) + ``` + + ``` + drop 4 (fromList [1, 2, 3]) + ``` + + # See also + + * {Weighted.take} + }} + +data.deprecated.Weighted.Fail.doc : Doc +data.deprecated.Weighted.Fail.doc = + {{ A {type Weighted} value that contains no elements. }} + +data.deprecated.Weighted.filter : (a -> Boolean) -> Weighted a -> Weighted a +data.deprecated.Weighted.filter p = cases + Yield a k -> + (if p a then Yield a else id) (data.deprecated.Weighted.filter p k) + Weight n k -> Weight n do data.deprecated.Weighted.filter p k() + w -> w + +data.deprecated.Weighted.filter.doc : Doc +data.deprecated.Weighted.filter.doc = + {{ + Takes a predicate and a {type Weighted} and returns a new {type Weighted} + containing only the elements that satisfy the predicate. + + # Example + + ``` + Weighted.sample 10 (Weighted.filter Nat.isEven natsInOrder) + ``` + }} + +data.deprecated.Weighted.flatMap : + (a -> Weighted b) -> Weighted a -> Weighted b +data.deprecated.Weighted.flatMap f = cases + Weighted.Fail -> Weighted.Fail + Yield x m -> f x Weighted.<|> data.deprecated.Weighted.flatMap f m + Weight w m -> Weight w do data.deprecated.Weighted.flatMap f m() + +data.deprecated.Weighted.flatMap.doc : Doc +data.deprecated.Weighted.flatMap.doc = + use Nat + + {{ + Maps the given function over the values of the {type Weighted} and + interleaves the result of each application with the rest of the + {type Weighted} from that point forward. + + # Example + + ``` + Weighted.sample + 20 + (Weighted.flatMap + (old -> + Weighted.fromList + (List.map (new -> (old, new + 100)) (List.range 0 old))) + natsInOrder) + ``` + }} + +data.deprecated.Weighted.floats : Weighted Float +data.deprecated.Weighted.floats = + use Weighted <|> + is = Weighted.drop 1 Weighted.nats + Weighted.append + (yield 0.0 <|> yield -0.0 <|> yield minFloat <|> yield maxFloat) + (Weighted.map (x -> Float.fromRepresentation x) is) + +data.deprecated.Weighted.floats.doc : Doc +data.deprecated.Weighted.floats.doc = + {{ + A {type Weighted} of {type Float}s. Generates pseudo-random {type Float}s in + the range {minFloat} to {maxFloat}. + + # Example + + ``` + Weighted.sample 12 Weighted.floats + ``` + }} + +data.deprecated.Weighted.fromList : [a] -> Weighted a +data.deprecated.Weighted.fromList = cases + [] -> Weighted.Fail + a +: as -> + yield a Weighted.<|> (weight 1 do data.deprecated.Weighted.fromList as) + +data.deprecated.Weighted.fromList.doc : Doc +data.deprecated.Weighted.fromList.doc = + {{ + Creates a {type Weighted} from a {type List} of elements. Each element has a + weight one higher than the previous element. The first element has a weight + of ``0``. + }} + +data.deprecated.Weighted.ints : Weighted Int +data.deprecated.Weighted.ints = + use Int negate + use Weighted <|> + go n = + yield n + <|> (weight 1 do + go + (if Universal.gt n +0 then negate n + else Int.increment (negate n))) + List.foldLeft + (a n -> a <|> yield n) Weighted.Fail [+0, +1, -1, maxInt, minInt] + <|> go +2 + +data.deprecated.Weighted.ints.doc : Doc +data.deprecated.Weighted.ints.doc = + {{ + A {type Weighted} that generates {type Int} values starting with ``+0``, and + then alternating positive and negative values. + + ``` + Weighted.sample 10 Weighted.ints + ``` + + # See also + + * {Weighted.nats} + * {natsInOrder} + }} + +data.deprecated.Weighted.lists : Weighted a -> Weighted [a] +data.deprecated.Weighted.lists w = + use List +: + use Weighted <|> + yield [] + <|> (weight 1 do + Weighted.mergeWith (+:) w (data.deprecated.Weighted.lists w)) + +data.deprecated.Weighted.lists.doc : Doc +data.deprecated.Weighted.lists.doc = + use Weighted <|> lists sample + {{ + Takes a {type Weighted} value and yields lists of elements from it. The lists + are generated in order of increasing combined weight with the empty list + first. Each consecutive list has a weight one higher than the weight of its + elements combined, plus the weight of the previous list. + + # Examples + + ``` + sample 10 (lists natsInOrder) + ``` + + If the weights of later elements are much larger than the weights of + earlier elements, then longer lists of the earlier elements will be + generated before the later elements start appearing. + + ``` + sample + 13 + (lists (yield ?a <|> (weight 2 do yield ?b <|> (weight 2 do yield ?c)))) + ``` + + Note that weights are cumulative, so in the above example the weight of the + element `` ?c `` is 4, not 2. + }} + +data.deprecated.Weighted.map : (a -> b) -> Weighted a -> Weighted b +data.deprecated.Weighted.map f = cases + Weighted.Fail -> Weighted.Fail + Yield x w -> Yield (f x) (data.deprecated.Weighted.map f w) + Weight a w -> weight a do data.deprecated.Weighted.map f w() + +data.deprecated.Weighted.map.doc : Doc +data.deprecated.Weighted.map.doc = + {{ + Transforms the values of a {type Weighted} using the given function. + + # Example + + ``` + Weighted.sample 10 (Weighted.map List.size (Weighted.lists Weighted.nats)) + ``` + }} + +data.deprecated.Weighted.mergeWith : + (a -> b -> c) -> Weighted a -> Weighted b -> Weighted c +data.deprecated.Weighted.mergeWith f as bs = + Weighted.flatMap (a -> Weighted.map (b -> f a b) bs) as + +data.deprecated.Weighted.mergeWith.doc : Doc +data.deprecated.Weighted.mergeWith.doc = + use Nat + + use Weighted fromList + {{ + Merges two {type Weighted} values, using the given function to combine the + values of elements. For every pair of elements in the two {type Weighted} + values, the function is called with the two values. The result of the + function is used as the value of the element in the merged {type Weighted} + value, and the weight of the element is the sum of the weights of the two + elements in the original {type Weighted} values. + + # Example + + ``` + Weighted.sample + 100 + (Weighted.mergeWith (+) (fromList [1, 2, 3]) (fromList [100, 200, 300])) + ``` + }} + +data.deprecated.Weighted.nats : Weighted Nat +data.deprecated.Weighted.nats = + use Weighted <|> + go n = yield n <|> (weight 1 do go (lsfr n)) + yield 0 <|> (weight 1 do go maxNat) + +data.deprecated.Weighted.nats.doc : Doc +data.deprecated.Weighted.nats.doc = + {{ + A {type Weighted} that generates {type Nat} values starting with ``+0``, + ``maxNat``, and then {type Nat} values in a pseudo-random order. + + ``` + Weighted.sample 10 Weighted.nats + ``` + + # See also + + * {Weighted.ints} + * {natsInOrder} + }} + +data.deprecated.Weighted.natsInOrder : Weighted Nat +data.deprecated.Weighted.natsInOrder = + use Nat + + use Weighted <|> + go n = yield n <|> (weight 1 do go (n + 1)) + go 0 + +data.deprecated.Weighted.normalFloats : Weighted Float +data.deprecated.Weighted.normalFloats = + use Float / fromNat + Weighted.map (x -> fromNat x / fromNat maxNat) Weighted.nats + +data.deprecated.Weighted.normalFloats.doc : Doc +data.deprecated.Weighted.normalFloats.doc = + {{ + A {type Weighted} of {type Float}s. Generates pseudo-random {type Float}s in + the range `` 0.0 `` to ``1.0``. + + # Example + + ``` + Weighted.sample 12 normalFloats + ``` + }} + +data.deprecated.Weighted.sample : Nat -> Weighted a -> [a] +data.deprecated.Weighted.sample n wsa = + use List +: + use Nat - + use data.deprecated.Weighted sample + if Universal.gt n 0 then + match wsa with + Weighted.Fail -> [] + Yield a ms -> a +: sample (n - 1) ms + Weight _ w -> sample n w() + else [] + +data.deprecated.Weighted.sample.doc : Doc +data.deprecated.Weighted.sample.doc = + {{ + Returns a {type List} of of the given length, sampled from the given + {type Weighted}. + + # Example + + ``` + Weighted.sample 12 natsInOrder + ``` + }} + +data.deprecated.Weighted.take : Nat -> Weighted a -> Weighted a +data.deprecated.Weighted.take n = cases + Weighted.Fail -> Weighted.Fail + Yield x tail -> + if n Nat.== 0 then Weighted.Fail + else Yield x (data.deprecated.Weighted.take (n Nat.- 1) tail) + Weight w tail -> Weight w do data.deprecated.Weighted.take n tail() + +data.deprecated.Weighted.take.doc : Doc +data.deprecated.Weighted.take.doc = + {{ + Construct a new {type Weighted} that yields the first `n` elements from the + given {type Weighted} and then stops. + + # Examples + + ``` + Weighted.sample 10 (Weighted.take 5 natsInOrder) + ``` + }} + +data.deprecated.Weighted.weight : Nat ->{e} '{e} Weighted a ->{e} Weighted a +data.deprecated.Weighted.weight w ws = Weight w ws + +data.deprecated.Weighted.weight.doc : Doc +data.deprecated.Weighted.weight.doc = + {{ Adds the given weight to the elements of the given {type Weighted}. }} + +data.deprecated.Weighted.yield : a -> Weighted a +data.deprecated.Weighted.yield a = Yield a Weighted.Fail + +data.deprecated.Weighted.Yield.doc : Doc +data.deprecated.Weighted.Yield.doc = + {{ + A {type Weighted} value that yields the given element and continues with the + given {type Weighted} value. + }} + +data.deprecated.Weighted.yield.doc : Doc +data.deprecated.Weighted.yield.doc = + use Weighted <|> sample + {{ + A {type Weighted} value that yields the given element and then stops. + + # Examples + + ``` + sample 10 (yield 1) + ``` + + ``` + sample 10 (yield ?a <|> (weight 2 do yield ?b)) + ``` + }} + +data.Graph.build : [(v, k, [k])] -> Graph v +data.Graph.build input = + use Array.Raw freeze! + use List :+ + use Map get + use Nat + + use Raw write + use Scope.Raw array + prepro i = cases + acc, [] -> acc + acc, (d, k, es) +: rest -> prepro (i + 1) (acc :+ (k, (i, d, es))) rest + data = Map.fromList <| prepro 0 [] input + Scope.run do + n = Map.size data + adj = array n + ann = array n + fill = cases + [] -> AdjLists (freeze! adj) (freeze! ann) + k +: ks -> + (i, d, es) = getOrBug "Graph.build: fill" <| get k data + is = List.filterMap (e -> Optional.map at1 <| get e data) es + write adj i is + write ann i d + fill ks + unsafeRun! do fill (Map.keys data) + +data.Graph.build.doc : Doc +data.Graph.build.doc = + {{ + Creates a graph from a list of information about the nodes. + + The triples in the input represent the following: + + 1. Arbitrary data associated with each node + 2. A key used to identify the node + 3. A list of keys specifying the out edges the node + + Edges with targets that do not exist as sources in the list are simply + removed. + }} + +data.Graph.doc : Doc +data.Graph.doc = + {{ + A representation of a directed graph as adjacency lists stored in an array. + The nodes correspond to the array indices, and each index lists the indices + of other nodes with an edge from the given index. + + The graph also stores data associated with each node. + }} + +data.Graph.edgeCount : Graph v -> Nat +data.Graph.edgeCount = cases + AdjLists es _ -> + use Nat + < + go cnt = cases + i + | i < data.Array.Raw.size es -> + go (cnt + List.size (data.Array.Raw.read es i)) (i + 1) + | otherwise -> cnt + unsafeRun! do go 0 0 + +data.Graph.edgeCount.doc : Doc +data.Graph.edgeCount.doc = {{ Yields the number of edges in a {type Graph}. }} + +data.Graph.edges : Graph v -> Nat ->{Exception} [Nat] +data.Graph.edges = cases AdjLists es _ -> data.Array.Raw.read es + +data.Graph.edges.doc : Doc +data.Graph.edges.doc = + {{ + Gives the vertices with an edge from the given vertex. + + Note: can throw an exception if an invalid vertex number is supplied. + }} + +data.Graph.isReachable : Graph v -> Nat -> Nat ->{Exception} Boolean +data.Graph.isReachable gr m n = + use Graph edges + use List any + use Nat == + search seen k = + if NatSet.contains k seen then false + else + seen' = NatSet.insert k seen + n == k || any (search seen') (edges gr k) + any (search NatSet.empty) (edges gr m) + +data.Graph.isReachable.doc : Doc +data.Graph.isReachable.doc = + {{ + Checks if the second vertex is reachable from the first by following one or + more edges. + + If the two vertices are equal, this will check that there is actually a cycle + in the graph with those vertices as endpoints. + }} + +data.Graph.reverse : Graph v ->{Exception} Graph v +data.Graph.reverse = cases + AdjLists es no -> + use List ++ + use Nat + < + n = data.Array.Raw.size es + f i n = (n, [i]) + backs : [(Nat, [Nat])] -> Nat -> [(Nat, [Nat])] + backs acc = cases + i + | i < n -> + backs (List.map (f i) (data.Array.Raw.read es i) ++ acc) (i + 1) + | otherwise -> acc + nm = NatMap.fromListWith (++) (backs [] 0) + nes = Scope.run do + arr = Scope.Raw.array n + fill i = + if i < n then + Raw.write arr i (NatMap.getOrElse i [] nm) + fill (i + 1) + else () + fill 0 + Array.Raw.freeze! arr + AdjLists nes no + +data.Graph.reverse.doc : Doc +data.Graph.reverse.doc = + {{ + Yields the {type Graph} obtained by reversing all the edges in the given + graph. + }} + +data.Graph.SCC.add : a -> SCC a -> SCC a +data.Graph.SCC.add a = cases + Acyclic b -> Cyclic [a, b] + Cyclic as -> Cyclic (a List.+: as) + +data.Graph.SCC.add.doc : Doc +data.Graph.SCC.add.doc = + {{ + Adds a value to an existing {type SCC}. Any {type SCC} with multiple values + will necessarily be {Cyclic}. + }} + +data.Graph.SCC.augment : Boolean -> a -> Optional (SCC a) -> SCC a +data.Graph.SCC.augment cyc a = cases + None + | cyc -> Cyclic [a] + | otherwise -> Acyclic a + Some cc -> SCC.add a cc + +data.Graph.SCC.augment.doc : Doc +data.Graph.SCC.augment.doc = + {{ + A helper function for building an {type SCC} in a map, like {type Map} or + {type NatMap}. If no {type SCC} is supplied, a new one is created, with the + {type Boolean} argument specifying whether the new component should be + {Cyclic} or not. Otherwise the value is added to the given {type SCC}. + }} + +data.Graph.SCC.doc : Doc +data.Graph.SCC.doc = + {{ + {type SCC} represents the strongly connected components of a {type Graph}. An + {type SCC} contains the data associated to the vertices in the component. + + {Cyclic} represents a cycle of one or more elements in the graph, while + {Acyclic} represents a component with a solitary node with no path to itself. + }} + +data.Graph.SCC.map : (a ->{g1} b) -> SCC a ->{g1} SCC b +data.Graph.SCC.map f = cases + Acyclic x -> Acyclic (f x) + Cyclic xs -> Cyclic (List.map f xs) + +data.Graph.SCC.map.doc : Doc +data.Graph.SCC.map.doc = + {{ + {type SCC} forms a functor in the obvious way, so we can map over the stored + values. + }} + +data.Graph.sccs : Graph v -> [SCC v] +data.Graph.sccs graph = + n = vertexCount graph + unsafeRun! do + (_, stack) = sccs.crawls graph (NatSet.empty, []) (List.range 0 n) + rgraph = Graph.reverse graph + f st no = uncurry (classify rgraph no) st no + let + (_, sccm) = List.foldLeft f (NatSet.empty, NatMap.empty) stack + NatMap.values sccm + +data.Graph.sccs.classifies : + Graph v + -> Nat + -> (NatSet, NatMap (SCC v)) + -> [Nat] + ->{Exception} (NatSet, NatMap (SCC v)) +data.Graph.sccs.classifies graph root = + List.foldLeft (uncurry <| data.Graph.sccs.classify graph root) + +data.Graph.sccs.classifies.doc : Doc +data.Graph.sccs.classifies.doc = + {{ Applies {classify} to several vertices in sequence. }} + +data.Graph.sccs.classify : + Graph v + -> Nat + -> NatSet + -> NatMap (SCC v) + -> Nat + ->{Exception} (NatSet, NatMap (SCC v)) +data.Graph.sccs.classify graph root seen groups node = + use Nat == + if NatSet.contains node seen then (seen, groups) + else + seen' = NatSet.insert node seen + nexts = Graph.edges graph node + cyc = root == node && List.any ((==) node) nexts + annot = vertex graph node + groups' = NatMap.alter (Some << augment cyc annot) root groups + data.Graph.sccs.classifies graph root (seen', groups') nexts + +data.Graph.sccs.classify.doc : Doc +data.Graph.sccs.classify.doc = + {{ + Assigns vertices of a graph to connected components. The connected components + are identified by (numbers for) vertices in the graph, and the `root` + argument is used as the identifier that `node` should be assigned to. + }} + +data.Graph.sccs.crawl : + Graph v -> NatSet -> [Nat] -> Nat ->{Exception} (NatSet, [Nat]) +data.Graph.sccs.crawl graph seen stk node = + use List +: + if NatSet.contains node seen then (seen, stk) + else + seen' = NatSet.insert node seen + nexts = Graph.edges graph node + Tuple.mapRight ((+:) node) + <| data.Graph.sccs.crawls graph (seen', stk) nexts + +data.Graph.sccs.crawl.doc : Doc +data.Graph.sccs.crawl.doc = + {{ + Traverses a portion of a graph from a given node, pushing the encountered + vertices onto a stack. The {type NatSet} is used to keep track of which nodes + have already been traversed. + }} + +data.Graph.sccs.crawls : + Graph v -> (NatSet, [Nat]) -> [Nat] ->{Exception} (NatSet, [Nat]) +data.Graph.sccs.crawls graph = + List.foldLeft (uncurry <| data.Graph.sccs.crawl graph) + +data.Graph.sccs.crawls.doc : Doc +data.Graph.sccs.crawls.doc = + {{ {sccs.crawl}s multiple vertices in a graph in order. }} + +data.Graph.sccs.doc : Doc +data.Graph.sccs.doc = + {{ + Finds the strongly connected components of a graph using + [Kosaraju's algorithm](https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm). + + The algorithm works by first traversing the graph and placing vertices onto a + stack in post-order. Then, the post-order stack is used in conjunction with a + reversed graph to calculate the connected components. + }} + +data.Graph.stronglyConnectedComponents : [(v, k, [k])] -> [SCC v] +data.Graph.stronglyConnectedComponents graph = Graph.sccs (Graph.build graph) + +data.Graph.stronglyConnectedComponents.doc : Doc +data.Graph.stronglyConnectedComponents.doc = + {{ + Finds the strongly connected components of a graph presentation. + + The graph input is represented as a list of triples. The data in the triples + is: + + 1. Arbitrary data associated with each vertex, yielded in the output + 2. A key used to identify the vertex + 3. A list of keys specifying the out edges the vertex + + Any edges targeting nodes that do not occur in the second component of a + triple are just omitted from the graph, and will not show up in the output. + }} + +test> data.Graph.tests.build = + test.verify do + use List all + use Nat < + use Random listOf nat natIn + Each.repeat 50 + gt = do (nat(), nat(), listOf nat do natIn 0 4) + gd = listOf gt do natIn 0 10 + gr = Graph.build gd + n = vertexCount gr + ensureWith "build well-formed" + <| all (k -> all (j -> j < n) (Graph.edges gr k)) (List.range 0 n) + +data.Graph.tests.checkAcyclic : Graph v -> Nat ->{Exception} Boolean +data.Graph.tests.checkAcyclic gr v = Boolean.not (isReachable gr v v) + +data.Graph.tests.checkCyclic : Graph v -> [Nat] ->{Exception} Boolean +data.Graph.tests.checkCyclic gr vs = + use List all + all (v -> all (isReachable gr v) vs) vs + +data.Graph.tests.checkTopOrder : Graph v -> [v] ->{Exception} () +data.Graph.tests.checkTopOrder gr vs = + use List all + vn v = match vertexNum gr v with + None -> test.raiseFailure "checkTopOrder: vertex not found" v + Some n -> n + edg m n = + use Nat != + es = Graph.edges gr n + all ((!=) m) es && all (edg m) es + go = cases + [] -> () + v +: vs -> + n = vn v + ensureWith "checkTopOrder: no edge" + <| all (Boolean.not << flip (isReachable gr) n << vn) vs + go vs + go vs + +data.Graph.tests.checkTopOrder.doc : Doc +data.Graph.tests.checkTopOrder.doc = + {{ + Checks that, in a topological order, if v precedes u, then v does not occur + by following edges from u. + }} + +data.Graph.tests.randomDAG : (Nat -> a) ->{Random} Graph a +data.Graph.tests.randomDAG f = + use List +: + use Nat - == + vtxs = Random.natIn 0 21 + edg acc n = + if n == 0 then acc + else + m = n - 1 + acc' = if Random.boolean() then m +: acc else acc + edg acc' m + nod n = (f n, n, edg [] n) + Graph.build (shuffle <| List.map nod (List.range 0 vtxs)) + +data.Graph.tests.randomGraph : (Nat -> a) ->{Random} Graph a +data.Graph.tests.randomGraph f = + use Nat - + vtxs = Random.natIn 0 21 + edg acc n = + use List +: + use Nat > + acc' = if Random.boolean() then n +: acc else acc + if n > 0 then edg acc' (n - 1) else acc' + nod n = (f n, n, edg [] (vtxs - 1)) + Graph.build (List.map nod (List.range 0 vtxs)) + +data.Graph.tests.randomGraph.doc : Doc +data.Graph.tests.randomGraph.doc = + {{ Constructs a random {type Graph} for testing purposes. }} + +test> data.Graph.tests.reverse = test.verify do + Each.repeat 50 + gr = randomGraph Nat.toText + rgr = Graph.reverse gr + ensureEqual (vertexCount gr) (vertexCount rgr) + ensureEqual (edgeCount gr) (edgeCount rgr) + matchEdges gr rgr + +data.Graph.tests.reverse.matchEdges : Graph v1 -> Graph v ->{Exception} () +data.Graph.tests.reverse.matchEdges gr rgr = + use Nat - == + go i = + use Graph edges + use List all any + to = all (j -> any ((==) i) (edges rgr j)) (edges gr i) + fro = all (j -> any ((==) i) (edges gr j)) (edges rgr i) + ensureWith "reverse.matchEdges" (to && fro) + n = vertexCount gr + if n == 0 then () else go (n - 1) + +data.Graph.tests.reverse.matchEdges.doc : Doc +data.Graph.tests.reverse.matchEdges.doc = + {{ + Checks that for every edge in each graph, there is a reversed edge in the + other graph. + }} + +test> data.Graph.tests.sccs = test.verify do + Each.repeat 50 + gr = randomGraph id + p = cases + Acyclic v -> checkAcyclic gr v + Cyclic vs -> checkCyclic gr vs + ensureWith "sccs" <| List.all p (Graph.sccs gr) + +test> data.Graph.tests.topSort.test = test.verify do + Each.repeat 50 + gr = randomDAG id + vs = Abort.toGenericException "topSort failed" () do topSort gr + checkTopOrder gr vs + +data.Graph.topSort : Graph v ->{Abort} [v] +data.Graph.topSort graph = + use NatSet empty + n = vertexCount graph + result = + unsafeRun! do topSort.crawls graph empty (empty, []) (List.range 0 n) + at2 result + +data.Graph.topSort.crawl : + Graph v -> NatSet -> (NatSet, [v]) -> Nat ->{Exception, Abort} (NatSet, [v]) +data.Graph.topSort.crawl graph loop0 state n = + use List +: + use NatSet contains insert + if contains n (at1 state) then state + else + if contains n loop0 then abort + else + loop = insert n loop0 + let + (seen, sort) = + data.Graph.topSort.crawls graph loop state (Graph.edges graph n) + (insert n seen, vertex graph n +: sort) + +data.Graph.topSort.crawl.doc : Doc +data.Graph.topSort.crawl.doc = + {{ + Traverses a graph from a given node, pushing vertex information onto a + topological sorting. + + The {type NatSet} paired with the sort stores the vertex numbers that have + already been successfully visited, so that they are not repeated. + + The other {type NatSet} stores the vertex numbers that are part of the + current path through the graph, which allows for detecting cycles. + + The strategy is to do depth-first traversal of the graph. The loop detection + is modified before following edges, while the sort and 'success' maps are + modified post-order. This means that the resulting list should contain a + given vertex at an earlier position than any vertex reachable from it. + }} + +data.Graph.topSort.crawls : + Graph v + -> NatSet + -> (NatSet, [v]) + -> [Nat] + ->{Exception, Abort} (NatSet, [v]) +data.Graph.topSort.crawls graph loop = + List.foldLeft (data.Graph.topSort.crawl graph loop) + +data.Graph.topSort.doc : Doc +data.Graph.topSort.doc = + {{ + Calculates a topological sorting the vertices of a {type Graph}. Such + sortings are not necessarily unique, and depend on the representation details + of the graph. + + If the {type Graph} contains cycles, there can be no meaningful ordering of + the vertices in the cycle, so this function will {type Abort} in that + scenario. + + The algorithm repeatedly {topSort.crawl}s from each vertex in the graph, + which results in post-order traversals of the graph, with the exact + representation determining which relatively unordered pieces are listed + first. + }} + +data.Graph.vertex : Graph v -> Nat ->{Exception} v +data.Graph.vertex = cases AdjLists _ vs -> data.Array.Raw.read vs + +data.Graph.vertex.doc : Doc +data.Graph.vertex.doc = + {{ + Gives the data associated with a vertex in the graph. + + Note: can throw an exception if an invalid vertex number is supplied. + }} + +data.Graph.vertexCount : Graph v -> Nat +data.Graph.vertexCount = cases AdjLists es _ -> data.Array.Raw.size es + +data.Graph.vertexCount.doc : Doc +data.Graph.vertexCount.doc = + {{ Yields the number of vertices in a {type Graph}. }} + +data.Graph.vertexNum : Graph v -> v ->{Exception} Optional Nat +data.Graph.vertexNum gr v = + use Nat + >= + (AdjLists _ vs) = gr + n = data.Array.Raw.size vs + go i = + if i >= n then None + else if data.Array.Raw.read vs i === v then Some i else go (i + 1) + go 0 + +data.Graph.vertexNum.doc : Doc +data.Graph.vertexNum.doc = + {{ + Finds the vertex number associated with some vertex data. If the initial + vertex data was not unique, then an arbitrary matching number may be + returned. + }} + +data.Id.apply : Id i -> Id (i ->{g} t) ->{g} Id t +data.Id.apply x = cases Id f -> Id.map f x + +data.Id.apply.doc : Doc +data.Id.apply.doc = {{ Apply the wrapped function to the wrapped value. }} + +data.Id.doc : Doc +data.Id.doc = + {{ + The {type Id} type is a trivial type that simply wraps another type without + adding any additional structure. + + This type is useful as a base case for recursive types, or as a trivial type + argument where a type constructor is required. + + # Example + + A branching tree type that is abstract in the branching functor can be + defined as follows: + + ``` unison + structural type Tree f a = Empty | Branch a (f (Tree f a)) + ``` + + The {type Id} type can be used as the `f` argument to the `Tree` type + constructor, in which case the tree is simply a list: + + ``` unison + structural type MyList a = List (Tree Id a) + ``` + }} + +data.Id.flatMap : (i ->{g} o) -> Id i ->{g} o +data.Id.flatMap f = cases Id x -> f x + +data.Id.flatMap.doc : Doc +data.Id.flatMap.doc = + {{ + Transform the wrapped value with a function that returns its argument in + {type Id}. + }} + +data.Id.map : (i ->{g} o) -> Id i ->{g} Id o +data.Id.map f = cases Id x -> Id (f x) + +data.Id.map.doc : Doc +data.Id.map.doc = {{ Transform the wrapped value. }} + +-- builtin data.List.++ : [a] -> [a] -> [a] + +data.List.++.doc : Doc +data.List.++.doc = + use List ++ + {{ + Append two {type List} values. + + # Examples + + ``` + [1, 2, 3] ++ [4, 5, 6] + ``` + + ``` + [1, 2, 3] ++ [] + ``` + + ``` + [] ++ [4, 5, 6] + ``` + + ``` + [] ++ [] + ``` + }} + +-- builtin data.List.+: : a -> [a] -> [a] + +data.List.+:.doc : Doc +data.List.+:.doc = + use List +: + {{ + Prepend an element to a {type List} value. + + # Examples + + ``` + 1 +: [2, 3] + ``` + + ``` + 1 +: [] + ``` + }} + +-- builtin data.List.:+ : [a] -> a -> [a] + +data.List.:+.doc : Doc +data.List.:+.doc = + use List :+ + {{ + Append an element to a {type List} value. + + # Examples + + ``` + [1, 2] :+ 3 + ``` + + ``` + [] :+ 1 + ``` + }} + +data.List.align : [a] -> [b] -> [OneOrBoth a b] +data.List.align = List.alignWith id + +data.List.align.doc : Doc +data.List.align.doc = + use List align + {{ + Aligns two lists into a list of {type OneOrBoth} values. + + The result will have the same length as the longer of the two lists, and each + element will be a {type OneOrBoth} containing the corresponding elements from + the two input lists. If one of the lists is shorter than the other, the + result will contain {This} or {That} values accordingly. + + # Examples + + ``` + align [1, 2, 3] ["a", "b"] + ``` + + ``` + align [1, 2] ["a", "b", "c"] + ``` + + ``` + align [1, 2, 3] ["a", "b", "c"] + ``` + + # See also + + * {List.alignWith} - a variant where you can specify a function to apply to + the aligned elements. + }} + +data.List.alignWith : (OneOrBoth a b ->{g} c) -> [a] -> [b] ->{g} [c] +data.List.alignWith f = + use List :+ + go acc = cases + [], [] -> acc + [], y +: ys -> go (acc :+ f (That y)) [] ys + x +: xs, [] -> go (acc :+ f (This x)) xs [] + x +: xs, y +: ys -> go (acc :+ f (Both x y)) xs ys + go [] + +data.List.alignWith.doc : Doc +data.List.alignWith.doc = + use List alignWith + use Nat + + use OneOrBoth fold + use Text size + {{ + Aligns two lists into a list of values using a function. + + The result will have the same length as the longer of the two lists, and each + element will be the result of applying the given function to the + corresponding elements from the two input lists. If one of the lists is + shorter than the other, the result will contain values accordingly. + + # Examples + + ``` + alignWith (fold id size (x y -> x + size y)) [1, 2, 3] ["a", "b"] + ``` + + ``` + alignWith (fold id size (x y -> x + size y)) [1, 2] ["a", "b", "c"] + ``` + + ``` + alignWith (fold id size (x y -> x + size y)) [1, 2, 3] ["a", "b", "c"] + ``` + + # See also + + * {List.align} - a variant that returns a list of {type OneOrBoth} values. + }} + +data.List.all : (a ->{e} Boolean) -> [a] ->{e} Boolean +data.List.all predicate = cases + [] -> true + x +: rest -> predicate x && data.List.all predicate rest + +data.List.all.doc : Doc +data.List.all.doc = + use List all + use Nat isEven + {{ + `` all predicate list `` returns `` true `` if `predicate` is true for every + element of `list`. + + # Examples + + ``` + all isEven [2, 4, 6] + ``` + + ``` + all Nat.isOdd [1, 2, 3] + ``` + + {all} returns `` true `` for the empty list: + + ``` + all isEven [] + ``` + }} + +test> data.List.all.tests.deMorgan = runs 100 do + bs = gen.listOf gen.boolean () + expect (List.all id bs === List.none Boolean.not bs) + +test> data.List.all.tests.homomorphism = + runs 100 do + deprecated.laws.homomorphism + (gen.listOf gen.boolean) (List.all id) (List.++) (a b -> a && b) + +data.List.allPairs : [a] -> [(a, a)] +data.List.allPairs xs = + Each.toList do match each (dropRight 1 (List.tails xs)) with + x +: rest -> + y = each rest + (x, y) + _ -> bug "empty tails" + +data.List.allPairs.doc : Doc +data.List.allPairs.doc = + {{ + Returns all pairs of elements from a {type List}. The order of elements in + each pair is always the order in which they appear in the original list. That + is, if `x` only appears before `y` in the original list, then the pair + `(x, y)` will appear in the result, but not `(y, x)`. + + If an element appears more than once in the original list, then pairs with + that element will be repeated in the result. + + If the input contains fewer than two elements, the result is empty. + + Considering the elements of the list as vertices of a graph, this function + returns all the undirected edges of the graph. + + # Examples + + ``` + allPairs [1, 2, 3] + ``` + + ``` + allPairs [1, 2, 2, 3] + ``` + + ``` + allPairs [1] + ``` + + # See also + + * {slidingPairs} for all pairs of adjacent elements. + * {List.tails} for all suffixes of a list. + * {inits} for all prefixes of a list. + * {subsequences} for all subsequences of a list. + }} + +data.List.any : (a ->{e} Boolean) -> [a] ->{e} Boolean +data.List.any predicate = cases + [] -> false + x +: rest -> predicate x || data.List.any predicate rest + +data.List.any.doc : Doc +data.List.any.doc = + use List any + use Nat isEven + {{ + `` any predicate list `` returns `` true `` if `predicate` is true for at + least one element of `list`. + + # Examples + + ``` + any isEven [1, 2, 3] + ``` + + ``` + any Nat.isOdd [2, 4, 6] + ``` + + ``` + any isEven [] + ``` + }} + +test> data.List.any.tests.deMorgan = runs 100 do + use Boolean not + bs = gen.listOf gen.boolean () + expect (List.any id bs === not (List.all not bs)) + +test> data.List.any.tests.homomorphism = + runs 100 do + deprecated.laws.homomorphism + (gen.listOf gen.boolean) (List.any id) (List.++) (a b -> a || b) + +data.List.anyIndexOf : a -> [a] -> Optional Nat +data.List.anyIndexOf a s = + ao = Some a + search (i -> Universal.compare ao (List.at i s)) 0 (List.size s) + +data.List.anyIndexOf.doc : Doc +data.List.anyIndexOf.doc = + {{ + `` anyIndexOf e xs `` returns the first index where the element `e` occurs in + the list `xs` using a binary search approach, provided the input list is + sorted in ascending order, or {None} if the element is not found. + + {anyIndexOf} is not guaranteed to find the index of the element unless the + input is sorted in ascending order according to {Universal.ordering}. In + certain circumstances the logic of the binary search will exit early and not + return an index even if the element exists in the list. + + # Examples: + + ``` + anyIndexOf 0 [] + ``` + + ``` + anyIndexOf 1 [1, 2, 3] + ``` + + ``` + anyIndexOf 6 [5, 5, 6, 6, 7, 7, 9] + ``` + + ## Corner cases + + ``` + anyIndexOf 1 [1, 2, 1, 2, 1] + ``` + + ``` + anyIndexOf 2 [1, 2, 1, 2, 1] + ``` + + ``` + anyIndexOf 2 [2, 1, 1, 2, 1] + ``` + }} + +data.List.anyIndexOf.evaluated.empty : Optional Nat +data.List.anyIndexOf.evaluated.empty = anyIndexOf 0 [] + +data.List.anyIndexOf.evaluated.lower : Optional Nat +data.List.anyIndexOf.evaluated.lower = anyIndexOf 2 input.lower + +data.List.anyIndexOf.evaluated.notSorted : Optional Nat +data.List.anyIndexOf.evaluated.notSorted = anyIndexOf 1 input.lower + +data.List.anyIndexOf.evaluated.sorted : Optional Nat +data.List.anyIndexOf.evaluated.sorted = anyIndexOf 1 input.sorted + +data.List.anyIndexOf.evaluated.upper : Optional Nat +data.List.anyIndexOf.evaluated.upper = anyIndexOf 2 input.upper + +data.List.anyIndexOf.examples.input.lower : [Nat] +data.List.anyIndexOf.examples.input.lower = [1, 2, 1, 2, 1] + +data.List.anyIndexOf.examples.input.upper : [Nat] +data.List.anyIndexOf.examples.input.upper = [2, 1, 1, 2, 1] + +test> data.List.anyIndexOf.tests.empty = check (anyIndexOf 0 [] === None) + +test> data.List.anyIndexOf.tests.lower = + check (anyIndexOf 2 input.lower === None) + +test> data.List.anyIndexOf.tests.notSorted = + check (anyIndexOf 1 input.lower === Some 2) + +test> data.List.anyIndexOf.tests.sorted = + check (anyIndexOf 1 input.sorted === Some 0) + +test> data.List.anyIndexOf.tests.upper = + check (anyIndexOf 2 input.upper === None) + +data.List.apply : [a ->{e} b] -> [a] ->{e} [b] +data.List.apply fs xs = Each.toList do + f = each fs + x = each xs + f x + +data.List.apply.doc : Doc +data.List.apply.doc = + use List apply + {{ + Applies every function in the first list to every element in the second list. + The result is a list whose length is the product of the lengths of the two + lists. + + # Examples + + ``` + apply [Nat.increment, Nat.decrement] [1, 2, 3] + ``` + + ``` + apply [Nat.isOdd, Nat.isEven] [1, 2, 3] + ``` + }} + +-- builtin data.List.at : Nat -> [a] -> Optional a + +data.List.at.doc : Doc +data.List.at.doc = + use List at + {{ + `` at n `` gets the element at the position `n` in the list (using + [zero-based indexing](https://en.wikipedia.org/wiki/Zero-based_numbering)), + or returns {None} if the list has fewer than `n+1` elements. + + # Examples + + ``` + at 0 [10, 20, 30] + ``` + + ``` + at 2 [10, 20, 30] + ``` + + ``` + at 3 [10, 20, 30] + ``` + }} + +test> data.List.at.tests.index = runs 100 do + use Nat * + as = gen.listOf natInOrder () + n = gen.natIn 0 (List.size as * 2) () + expect (List.at n as === List.head (List.drop n as)) + +data.List.at! : Nat -> [a] ->{Abort} a +data.List.at! n as = match List.at n as with + Some a -> a + None -> abort + +data.List.at!.doc : Doc +data.List.at!.doc = + use Abort toBug + use List at! + {{ + `` at! n `` gets the element at the position `n` in the list (using + [zero-based indexing](https://en.wikipedia.org/wiki/Zero-based_numbering)), + or aborts with {abort} if the list has fewer than `n+1` elements. + + # Examples + + ``` + toBug do at! 0 [10, 20, 30] + ``` + + ``` + toBug do at! 2 [10, 20, 30] + ``` + + ``` + toBug do at! 3 [10, 20, 30] + ``` + }} + +data.List.chunk : Nat -> [a] -> [[a]] +data.List.chunk n xs = + use List ++ + use Nat == + go acc = cases + [] -> acc + xs -> go (acc ++ [List.take n xs]) (List.drop n xs) + if n == 0 then [] else go [] xs + +data.List.chunk.doc : Doc +data.List.chunk.doc = + use List chunk + {{ + `` chunk n xs `` splits the list `xs` into chunks of size `n`. The final + chunk may be smaller than `n`. + + # Examples + + ``` + chunk 2 [1, 2, 3, 4, 5, 6, 7, 8, 9] + ``` + + ``` + chunk 3 [1, 2, 3, 4, 5, 6, 7, 8, 9] + ``` + }} + +data.List.compare : (a ->{f} b ->{g} Ordering) -> [a] -> [b] ->{f, g} Ordering +data.List.compare ord xs ys = + use List size + compared = List.zipWith ord xs ys + match List.find (x -> Boolean.not (x === Equal)) compared with + None -> Universal.ordering (size xs) (size ys) + Some o -> o + +data.List.compare.doc : Doc +data.List.compare.doc = + use List compare + use Universal ordering + {{ + Compares two lists for {type Ordering}. The given function is used to compare + the elements. + + # Examples + + ``` + compare ordering [1, 2, 3] [1, 2, 3] + ``` + + ``` + compare ordering [1, 2, 3] [1, 2, 4] + ``` + + ``` + compare ordering [1, 2, 3] [1, 2] + ``` + + ``` + compare ordering [1, 2, 3] [1, 2, 3, 4] + ``` + }} + +data.List.concatOptional : Optional [a] -> [a] +data.List.concatOptional = cases + Some xs -> xs + None -> [] + +data.List.concatOptional.doc : Doc +data.List.concatOptional.doc = + {{ + Unwraps an {type Optional} {type List} to just a {type List}. + + # Examples + + ``` + concatOptional None + ``` + + ``` + concatOptional (Some [1, 2, 3]) + ``` + }} + +test> data.List.concatOptional.tests.prop1 = + go _ = + xs = gen.listOf natInOrder () + expect (concatOptional (Some xs) === xs) + runs 100 go + +test> data.List.concatOptional.tests.test1 = check (concatOptional None === []) + +data.List.contains : a -> [a] -> Boolean +data.List.contains a = cases + hd +: tl + | hd === a -> true + | otherwise -> data.List.contains a tl + [] -> false + +data.List.contains.doc : Doc +data.List.contains.doc = + {{ {List.contains} returns `` true `` if the element is found in the list. }} + +test> data.List.contains.tests.negative1 = check (List.contains 0 [] === false) + +test> data.List.contains.tests.negative2 = + check (List.contains 0 [1, 2, 3] === false) + +test> data.List.contains.tests.negative3 = + check (List.contains 1 [0, 0, 0] === false) + +test> data.List.contains.tests.positive = + go _ = + use List ++ :+ + use gen listOf + a = listOf natInOrder () + b = natInOrder() + c = listOf natInOrder () + expect (List.contains b (a :+ b ++ c)) + runs 100 go + +data.List.containsNot : a -> [a] -> Boolean +data.List.containsNot a as = Boolean.not <| List.contains a as + +data.List.containsNot.doc : Doc +data.List.containsNot.doc = + {{ + `` containsNot x xs `` returns `` true `` if the element `x` is __not__ found + in the list `xs`. + }} + +data.List.count : (a ->{e} Boolean) -> [a] ->{e} Nat +data.List.count f as = + use Nat + + List.foldLeft (acc a -> (if f a then acc + 1 else acc)) 0 as + +data.List.count.doc : Doc +data.List.count.doc = + use List count + {{ + @signature{count} + + {List.count f as} returns the number of times the function `f` returns `true` + for elements in list `as`. + + # Example + + ``` + count Nat.isEven [1, 2, 3, 4, 5] + ``` + }} + +data.List.countElement : a -> [a] -> Nat +data.List.countElement a = List.count ((===) a) + +data.List.countElement.doc : Doc +data.List.countElement.doc = + {{ + @signature{countElement} + + {List.countElement a as} returns the number of times the element `a` appears + in the list `as`. + + # Example + + ``` + countElement 2 [1, 2, 3, 2, 3, 3, 4] + ``` + }} + +data.List.deleteAt : Nat -> [a] -> [a] +data.List.deleteAt n as = + use List ++ slice + use Nat + + slice 0 n as ++ slice (n + 1) (List.size as) as + +data.List.deleteAt.doc : Doc +data.List.deleteAt.doc = + {{ + Deletes from the list the element at the given (0-based) index. + + # Examples + + ``` + deleteAt 1 [1, 2, 3] + ``` + + Does nothing if the requested index is past the end of the list: + + ``` + deleteAt 9 [1, 2, 3] + ``` + }} + +test> data.List.deleteAt.test = runs 100 do + use List ++ + use gen listOf + xs = listOf natInOrder () + ys = listOf natInOrder () + x = natInOrder() + expect (deleteAt (List.size xs) (xs ++ [x] ++ ys) === (xs ++ ys)) + +data.List.deleteFirst : (a ->{g} Boolean) -> [a] ->{g} [a] +data.List.deleteFirst p = cases + x +: xs -> if p x then xs else x List.+: data.List.deleteFirst p xs + [] -> [] + +data.List.deleteFirst.doc : Doc +data.List.deleteFirst.doc = + {{ + Deletes the first element for which the predicate holds true. If no such + element is found, leaves the list unchanged + }} + +test> data.List.deleteFirst.tests.ex1 = + use Nat == + check (deleteFirst (n -> n == 1) [3, 2, 1, 2, 3] === [3, 2, 2, 3]) + +test> data.List.deleteFirst.tests.ex2 = + use Nat == + check (deleteFirst (n -> n == 1) [3, 2, 2, 3] === [3, 2, 2, 3]) + +test> data.List.deleteFirst.tests.ex3 = + use Nat == + check (deleteFirst (n -> n == 1) [] === []) + +data.List.deprecated.lubIndexOf : a -> [a] -> Nat +data.List.deprecated.lubIndexOf a s = lubIndexOf' a 0 s + +data.List.deprecated.lubIndexOf.doc : Doc +data.List.deprecated.lubIndexOf.doc = + {{ + `` lubIndexOf x xs `` returns the index in the {type List} `xs` where the + element `x` occurs, using a head-to-tail scan of the {type List}. It returns + the size of the {type List} if `x` is not found. + + {lubIndexOf} is only guaranteed to find the index if the elements are sorted + in ascending order according to {Universal.ordering}. + + # Examples + + ``` + lubIndexOf 0 [] + ``` + + ``` + lubIndexOf 0 [1, 2, 3] + ``` + + ``` + lubIndexOf 2 [1, 2, 3] + ``` + + {lubIndexOf} returns the size of the input if it's not ordered: + + ``` + lubIndexOf 3 [1, 3, 2, 1] + ``` + }} + +data.List.deprecated.lubIndexOf.evaluated.empty : Nat +data.List.deprecated.lubIndexOf.evaluated.empty = lubIndexOf 0 [] + +data.List.deprecated.lubIndexOf.evaluated.notSorted : Nat +data.List.deprecated.lubIndexOf.evaluated.notSorted = lubIndexOf 3 [1, 3, 2, 1] + +data.List.deprecated.lubIndexOf.evaluated.sorted : Nat +data.List.deprecated.lubIndexOf.evaluated.sorted = lubIndexOf 1 [1, 2, 3] + +test> data.List.deprecated.lubIndexOf.tests.empty = + check (lubIndexOf 0 [] === 0) + +test> data.List.deprecated.lubIndexOf.tests.notSorted = + check (lubIndexOf 3 [1, 3, 2, 1] === 4) + +test> data.List.deprecated.lubIndexOf.tests.sorted = + check (lubIndexOf 1 [1, 2, 3] === 0) + +data.List.deprecated.lubIndexOf' : a -> Nat -> [a] -> Nat +data.List.deprecated.lubIndexOf' a start s = + ao = Some a + findLowestZero (i -> Universal.compare ao (List.at i s)) start (List.size s) + +data.List.deprecated.lubIndexOf'.doc : Doc +data.List.deprecated.lubIndexOf'.doc = + {{ + `` lubIndexOf' x n xs `` returns the index in the {type List} `xs` where the + element `x` occurs, using a scan of the {type List} starting from index `n`. + It returns the size of the {type List} if `x` is not found after index `n`. + + {lubIndexOf'} is only guaranteed to find the index if the elements are sorted + in ascending order according to {Universal.ordering}. + + # Examples + + ``` + lubIndexOf' 0 0 [] + ``` + + ``` + lubIndexOf' 2 0 [1, 2, 3] + ``` + + ``` + lubIndexOf' 2 2 [3, 2, 1, 2, 3] + ``` + + {lubIndexOf'} returns the size of the {type List} if the portion of the + input after the specified index is not ordered in ascending order: + + ``` + lubIndexOf' 3 1 [1, 3, 2, 1] + ``` + }} + +data.List.diagonal : [[a]] -> [a] +data.List.diagonal = List.join << diagonals + +data.List.diagonal.doc : Doc +data.List.diagonal.doc = + {{ + Returns a diagonalization of a {type List} of {type List}s. The result begins + with the first element of the first list, then the first element of the + second list, the second element of the first list, the first element of the + third list, the second element of the second list, the third element of the + first list, etc. + + # Examples + + ``` + diagonal [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + ``` + + ``` + diagonal [[], [1], [2, 3], [4, 5, 6], [7, 8, 9, 10]] + ``` + + ``` + fromCharList + (List.take + 21 + (diagonal + (List.map + toCharList + [ "🟥🟧🟨🟩🟦🟪" + , "🟧🟨🟩🟦🟪🟫" + , "🟨🟩🟦🟪🟫🔴" + , "🟩🟦🟪🟫🔴🟠" + , "🟦🟪🟫🔴🟠🟡" + , "🟪🟫🔴🟠🟡🟢" + , "🟫🔴🟠🟡🟢🟣" + , "🔴🟠🟡🟢🟣🟤" + ]))) + ``` + + # See also + + * {diagonals} + * {transpose} + }} + +data.List.diagonals : [[a]] -> [[a]] +data.List.diagonals = + use List tail + go b es = + use List +: + ts = List.filterMap tail b + deprecated.mapOptional List.head b +: (match es with + [] -> transpose ts + e +: es -> go (e +: ts) es) + List.join << Optional.toList << tail << go [] + +data.List.diagonals.doc : Doc +data.List.diagonals.doc = + use List map + {{ + Returns the diagonals of a {type List} of {type List}s. Considered as a 2D + arrangement with the first list as the top row, the second list as the second + row, etc., the diagonals are the lists of elements that run from bottom left + to top right. The first diagonal is just the first element of the first list, + the second diagonal is the first element of the second list and the second + element of the first list, the third diagonal is the first element of the + third list and the second element of the second list and the third element of + the first list, etc. + + # Examples + + ``` + diagonals [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + ``` + + ``` + diagonals [[], [1], [2, 3], [4, 5, 6], [7, 8, 9, 10]] + ``` + + ``` + map + fromCharList + (diagonals + (map + toCharList + [ "🟥🟧🟨🟩🟦🟪" + , "🟧🟨🟩🟦🟪🟫" + , "🟨🟩🟦🟪🟫🔴" + , "🟩🟦🟪🟫🔴🟠" + , "🟦🟪🟫🔴🟠🟡" + , "🟪🟫🔴🟠🟡🟢" + , "🟫🔴🟠🟡🟢🟣" + , "🔴🟠🟡🟢🟣🟤" + ])) + ``` + + # See also + + * {diagonal} + * {transpose} + }} + +data.List.distinct : [a] -> [a] +data.List.distinct = + use List :+ + go seen acc = cases + a +: as + | Set.contains a seen -> go seen acc as + | otherwise -> go (Set.insert a seen) (acc :+ a) as + [] -> acc + go Set.empty [] + +data.List.distinct.doc : Doc +data.List.distinct.doc = + {{ + Returns a {type List} of the distinct elements of a {type List}, according to + the universal equality function {===}. The first occurrence of each element + is retained. + + # Examples + + ``` + distinct [1, 2, 3, 2, 1] + ``` + + With emoji + + ``` + distinct [?🍓, ?🍅, ?🍓, ?🍎, ?🍅, ?🍎, ?🍅] + ``` + + # See also + + * {distinctBy} + * {groupBy} + * {groupConsecutive} + }} + +test> data.List.distinct.tests.preservation = runs 1000 do + use List all at + use Nat <= == + use Optional toAbort + l = gen.listOf gen.nat () + d = distinct l + dr = List.range 0 (List.size d) + allUnique = all (i -> all (j -> i == j || at i d !== at j d) dr) dr + orderPreserved = + all + (i -> + Optional.getOrElse + false (toOptional! do + di = toAbort (at i d) + ix = toAbort (List.firstIndexOf di l) + i <= ix)) dr + allPresent = all (e -> List.contains e d) l + expect (allUnique && orderPreserved && allPresent) + +data.List.distinctBy : (a ->{g} b) -> [a] ->{g} [a] +data.List.distinctBy f = + use List :+ + go seen acc = cases + a +: as -> + b = f a + if Set.contains b seen then go seen acc as + else go (Set.insert b seen) (acc :+ a) as + [] -> acc + go Set.empty [] + +data.List.distinctBy.doc : Doc +data.List.distinctBy.doc = + {{ + The expression `` distinctBy f xs `` removes duplicate elements from the + {type List} `xs`, but only considers any two elements to be the same if the + function `f` returns the same value for both. For any group of duplicate + elements, only the first such element in the {type List} is retained. + + # Example + + ``` + xs = ["the", "cat", "is", "on", "the", "roof"] + distinctBy Text.size xs + ``` + }} + +test> data.List.distinctBy.tests.preservation = runs 1000 do + use List all at + use Nat <= == + use Optional toAbort + l = gen.listOf gen.nat () + f = flip Nat.mod 10 + d = distinctBy f l + dr = List.range 0 (List.size d) + allUnique = all (i -> all (j -> i == j || at i d !== at j d) dr) dr + orderPreserved = + all + (i -> + Optional.getOrElse + false (toOptional! do + di = toAbort (at i d) + ix = toAbort (List.firstIndexOf di l) + i <= ix)) dr + allPresent = all (e -> List.contains e d) l + expect (allUnique && orderPreserved && allPresent) + +test> data.List.distinctBy.tests.removesDuplicates = runs 100 do + xs = gen.listOf natInOrder () + distinct = distinctBy id xs + isDistinct ys = + s = Heap.sort ys + go = cases + [] -> true + x +: xs -> List.dropWhile (y -> y === x) xs === xs && go xs + go s + expect (List.all (x -> List.contains x distinct) xs && isDistinct distinct) + +data.List.doc : Doc +data.List.doc = + use List.docs constructing converting + use docs elements nonempty + {{ + The {type List} type is a general-purpose data type that represents finite + sequences of elements. {type List} is built into Unison. + + Lists are immutable (no destructive updates), finitite (no infinite lists), + and homogenous (every element must have the same type). {type List} is + parameterized on the type of elements of the lists. For example, in a type + expression like `List t` (or equivalently `[t]`), the type `t` is the type of + the elements. + + {{ + docAside + {{ + The internal representation of the {type List} type is a + [Finger Tree](http://www.staff.city.ac.uk/~ross/papers/FingerTree.html). + This provides fast (constant-time) access to the first and last element, as + well as relatively efficient (logarithmic time) concatenation, insertion, + and deletion. + }} }} + + This document provides a high-level overview of the main operations on lists, + organized into the following categories: + + * [Constructing lists]({constructing}) + * [Adding and removing elements]({addingAndRemoving}) + * [Accessing and querying elements]({elements}) + * [Combining lists]({combining}) + * [Splitting and rearranging lists]({splittingAndRearranging}) + * [Traversing and transforming lists]({traversing}) + * [Nonempty lists]({nonempty}) + * [Converting lists to other types]({converting}) + + There are many more operations on lists than are enumerated here. See the + docs for individual functions for more details. + + {{ constructing }} + + {{ addingAndRemoving }} + + {{ elements }} + + {{ combining }} + + {{ splittingAndRearranging }} + + {{ traversing }} + + {{ nonempty }} + + {{ converting }} + }} + +data.List.docs.addingAndRemoving : Doc +data.List.docs.addingAndRemoving = + use List +: :+ intersperse + {{ + # Adding, removing, and replacing elements of lists + + {+:} adds an element to the front of a list: + + ``` + 1 +: [2, 3, 4] + ``` + + {:+} adds an element to the end of a list: + + ``` + [1, 2, 3] :+ 4 + ``` + + {insertAt} inserts an element at any point in a list: + + ``` + insertAt 1 "green" ["red", "blue"] + ``` + + {intersperse} inserts an element between all the elements of a list: + + ``` + intersperse 0 [1, 2, 3] + ``` + + {deleteAt} removes a specific element from a list: + + ``` + deleteAt 2 [5, 3, 8, 9] + ``` + + {replace} replaces a specific element with another one: + + ``` + replace 1 "cheese" ["flour", "butter", "eggs"] + ``` + }} + +data.List.docs.combining : Doc +data.List.docs.combining = + use List ++ join zip + {{ + # Combining lists + + {++} concatenates two lists. + + ``` + [1, 2, 3] ++ [4, 5, 6] + ``` + + {join} concatenates a whole list of lists. + + ``` + join [[1, 2, 3], [], [4, 5], [6]] + ``` + + {intercalate} inserts a list between other lists. + + ``` + intercalate [10, 20] [[1, 2], [], [3]] + ``` + + {zip} makes a list of pairs from two lists, each with elements of both + lists occuring at the same position. + + ``` + zip ["red", "green", "blue"] [5, 8, 2] + ``` + }} + +data.List.docs.constructing : Doc +data.List.docs.constructing = + use List empty fill replicate singleton + use Nat * + {{ + # Constructing lists + + A __list literal__ is an expression of the form {{ + docExample 3 do a b c -> [a, b, c] }} (in that case the list contains three + elements, `a`, `b`, and `c`). + + {empty} is the empty list (also written ``[]``): + + ``` + empty + ``` + + {singleton} creates a list with one element: + + ``` + singleton 4 + ``` + + `` fill n expr `` creates a list of `n` copies of `expr`: + + ``` + fill 3 "boing" + ``` + + {fill'} is like {fill} but allows the value to be computed lazily: + + ``` + lcg 1 do fill' 8 do Random.natIn 1 7 + ``` + + `` initialize n f `` creates a list of length `n`, filled with the values + of `f i` as `i` runs from `0` up to `n`: + + ``` + initialize 4 (x -> x * 2) + ``` + + `` replicate n op `` performs the (possibly effectful) computation `op` `n` + times and collects the results in a list: + + ``` + deprecated.sample 4 do replicate 3 natInOrder + ``` + + {List.range} and {List.rangeClosed} create lists of all natural numbers (of + type {type Nat}) in the specified range: + + ``` + List.range 1 4 + ``` + + ``` + List.rangeClosed 1 4 + ``` + + {Int.range} and {Int.rangeClosed} create lists of all integers (of type + {type Int}) in the specified range: + + ``` + Int.range -3 +3 + ``` + + ``` + Int.rangeClosed -3 +3 + ``` + }} + +data.List.docs.converting : Doc +data.List.docs.converting = + use List toMap toSet + use fromList impl + {{ + # Conversions to/from lists + + {mayNonempty} attempts to convert a list to a {type List.Nonempty}: + + ``` + mayNonempty [1, 2, 3] + ``` + + ``` + mayNonempty [] + ``` + + {toSet} constructs a {type Set} from a {type List}, and {Set.toList} + converts the other way: + + ``` + Set.toList (toSet [5, 8, 8, 5, 5, 2, 2]) + ``` + + {toMap} converts a list of key-value pairs to a {type Map}, and + {Map.toList} converts the other way: + + ``` + Map.toList (toMap [(5, 8), (8, 5), (5, 2), (2, 0)]) + ``` + + {fromCharList} converts a list of characters to {type Text}, and + {toCharList} converts the other way: + + ``` + fromCharList [?a, ?b, ?c] + ``` + + ``` + toCharList "abc" + ``` + + {impl} converts a list of {type Nat} numbers to {type Bytes}, and + {Bytes.toList} converts the other way: + + ``` + impl (List.range 0 4) + ``` + + ``` + Bytes.toList 0xsdeadbeef + ``` + + {Stream.toList} enumerates all elements on a {type Stream}, as a list: + + ``` + Stream.toList do + emit 1 + emit 2 + emit 3 + ``` + }} + +data.List.docs.elements : Doc +data.List.docs.elements = + use List all any at contains drop dropWhile filter head init last maximum minimum size tail take takeWhile + use Nat isEven + {{ + # Accessing and querying elements + + {size} gets the number of elements in the list. + + ``` + size [5, 8, 2] + ``` + + {head} gets the first element of the list. + + ``` + head [1, 2, 3] + ``` + + {last} gets the last element. + + ``` + last [1, 2, 3] + ``` + + {tail} gets all but the first element. + + ``` + tail [1, 2, 3] + ``` + + {init} gets all but the last element. + + ``` + init [1, 2, 3] + ``` + + {at} gets the element at a given position in the list. + + ``` + at 1 ["a", "b", "c"] + ``` + + {take} gets a specified number of elements from the front of the list. + + ``` + take 2 ["a", "b", "c"] + ``` + + {drop} removes a specified number of elements from the front of the list. + + ``` + drop 1 ["a", "b", "c"] + ``` + + {takeWhile} gets elements from the front of the list until a given function + returns `false`. + + ``` + takeWhile isEven [2, 4, 6, 7, 8, 9] + ``` + + {dropWhile} removes elements from the front of the list until a given + function returns `false`. + + ``` + dropWhile isEven [2, 4, 6, 7, 8, 9] + ``` + + {filter} finds all the elements that satisfy some {type Boolean} function. + + ``` + filter isEven [2, 4, 6, 7, 8, 9] + ``` + + {any} checks if any elements satisfy some {type Boolean} function. + + ``` + any isEven [2, 4, 6, 7, 8, 9] + ``` + + {all} checks if all elements satisfy some {type Boolean} function. + + ``` + all isEven [2, 4, 6, 7, 8, 9] + ``` + + {{ docLink (docEmbedTermLink do contains) }} checks if an element is in the + list. + + ``` + contains 2 [1, 2, 3] + ``` + + {maximum} finds the largest element in the list. + + ``` + maximum [5, 8, 2] + ``` + + {minimum} finds the smallest element in the list. + + ``` + minimum [5, 8, 2] + ``` + }} + +data.List.docs.nonempty : Doc +data.List.docs.nonempty = + {{ + # Nonempty lists + + The {type List} type allows lists to potentially be empty. For lists that + should not be allowed to be empty, use the type {type List.Nonempty}. A + number of operations on {type List} produce values of type + {type List.Nonempty} to ensure that the result has at least one element. + For example: + + @signature{List.scanLeft} @signature{List.scanRight} + @signature{nonEmptySubsequences} @signature{groupBy} + @signature{groupConsecutive} @signature{groupMap} + @signature{groupSublistsBy} + }} + +data.List.docs.splittingAndRearranging : Doc +data.List.docs.splittingAndRearranging = + use Heap sort + use List reverse slice span split splitAt tails + use Nat == isEven + {{ + # Splitting, slicing, and rearranging lists + + {reverse} flips the order of the elements of a list: + + ``` + reverse [5, 8, 2] + ``` + + {sort} puts the elements of a list in ascending order: + + ``` + sort [5, 8, 2] + ``` + + {sortDescending} puts the elements of a list in descending order: + + ``` + sortDescending [5, 8, 2] + ``` + + {sortBy} sorts a list on some specified property of the elements: + + ``` + sortBy Text.size ["four", "six", "three"] + ``` + + {split} breaks a list into sub-lists on a delimiter matching a condition: + + ``` + split (x -> x == 0) [1, 2, 3, 0, 4, 0, 2, 1, 0, 0, 1] + ``` + + {splitAt} breaks a list into two lists, at a specific index: + + ``` + splitAt 2 [1, 2, 3, 4, 5] + ``` + + {span} breaks a list into two lists, at the first element for which a + condition returns ``false``. + + ``` + span (x -> Universal.lt x 3) [1, 2, 3, 4, 5] + ``` + + ``` + span isEven [1, 2, 3] + ``` + + {halve} splits a list into two lists of roughly equal size: + + ``` + halve [1, 2, 3, 4] + ``` + + ``` + halve [1, 2, 3] + ``` + + {distinct} and {distinctBy} remove any duplicate elements from the list. + + ``` + distinct [5, 8, 8, 5, 5, 2, 2] + ``` + + ``` + distinctBy isEven [5, 8, 8, 5, 5, 2, 2] + ``` + + {slice} extracts a sub-list from a list: + + ``` + slice 2 5 [5, 8, 8, 5, 5, 2, 2] + ``` + + {powerslice} returns all contiguous sub-lists of a list: + + ``` + powerslice [1, 2, 3] + ``` + + {tails} returns all suffixes of a list, longest first: + + ``` + tails [1, 2, 3] + ``` + + {inits} returns all prefixes of a list, shortest first: + + ``` + inits [1, 2, 3] + ``` + }} + +data.List.docs.traversing : Doc +data.List.docs.traversing = + use List flatMap foldBalanced foldLeft foldRight foreach map scanLeft scanRight + use Nat + + use Text ++ size + {{ + # Traversing and transforming lists + + {map} applies a function to every element of a list, collecting the + results: + + ``` + map size ["one", "two", "three"] + ``` + + {foreach} applies a function to every element of a list only for its + effects, ignoring the results: + + ``` + Stream.toList do ["one", "two", "three"] |> foreach (x -> emit (size x)) + ``` + + {flatMap} applies a list-valued function to every element of a list, + collecting the results in one list: + + ``` + flatMap (n -> List.fill n "boing") [0, 1, 2] + ``` + + {mapIndexed} applies a function to every element of a list together with + the index of the element in the list: + + ``` + mapIndexed (n x -> (n, x)) ["rip", "rap", "rup"] + ``` + + {foldLeft} iterates over a list from left to right, accumulating elements + into a result using a given function: + + ``` + foldLeft (++) "It's " ["Super", "Duper", "Awesome"] + ``` + + {foldRight} iterates over a list from right to left, accumulating elements + into a result using the given function: + + ``` + foldRight (++) " Cool" ["Really", "Rather", "Super"] + ``` + + {foldBalanced} applies a function to every element of a list and then + combines the results using a binary function: + + ``` + foldBalanced size (+) 0 ["abc", "def", "ghi"] + ``` + + {scanLeft} iterates over a list just like {foldLeft}, but returning all + intermediate results: + + ``` + scanLeft (+) 0 [1, 2, 3, 4, 5] + ``` + + {scanRight} iterates over a list just like {foldRight}, but returning all + intermediate results: + + ``` + scanRight (+) 0 [1, 2, 3, 4, 5] + ``` + }} + +-- builtin data.List.drop : Nat -> [a] -> [a] + +data.List.drop.doc : Doc +data.List.drop.doc = + use List drop + {{ + Drops the first `n` elements of a {type List}. + + # Examples + + ``` + drop 2 [1, 2, 3, 4, 5] + ``` + + ``` + drop 0 [1, 2, 3, 4, 5] + ``` + + ``` + drop 5 [1, 2, 3, 4, 5] + ``` + + ``` + drop 6 [1, 2, 3, 4, 5] + ``` + }} + +data.List.dropLast : [a] -> [a] +data.List.dropLast = cases + [] -> [] + xs :+ _ -> xs + +data.List.dropLast.doc : Doc +data.List.dropLast.doc = + {{ + `` dropLast xs `` drops the last element of the list `xs`. If the list is + empty it returns an empty list. + + ``` + dropLast [1, 2, 3] + ``` + + ``` + dropLast [] + ``` + }} + +data.List.dropRight : Nat -> [a] -> [a] +data.List.dropRight n xs = + use Nat - + List.take (List.size xs - n) xs + +data.List.dropRight.doc : Doc +data.List.dropRight.doc = + {{ + `` dropRight n xs `` removes the last (rightmost) `n` elements of `xs`, or if + `n` is greater than or equal to ``List.size xs``, returns the empty + {type List}. + + ``` + dropRight 3 [1, 2, 3, 4, 5] + ``` + + ``` + dropRight 0 [1, 2, 3, 4, 5] + ``` + + ``` + dropRight 11 [1, 2, 3, 4, 5] + ``` + }} + +data.List.dropRightWhile : (a ->{g} Boolean) -> [a] ->{g} [a] +data.List.dropRightWhile f xs = match xs with + [] -> [] + init :+ last -> if f last then data.List.dropRightWhile f init else xs + +data.List.dropRightWhile.doc : Doc +data.List.dropRightWhile.doc = + use List dropRightWhile + use Nat > + {{ + `` dropRightWhile p xs `` removes elements from the end (i.e. from the right) + of the {type List} `xs` as long as the function `p` returns ``true``. It + removes the longest suffix whose elements all result in `` true `` when `p` + is applied to them. + + ``` + dropRightWhile (x -> x > 10) [1, 2, 3, 4, 500, 600, 700] + ``` + + Here, the last element doesn't match the predicate, so the list is left + alone: + + ``` + dropRightWhile (x -> x > 10) [1, 2, 3, 4, 500, 600, 700, 0] + ``` + }} + +test> data.List.dropRightWhile.tests = test.verify do + use List reverse + use Nat < + use Random natIn + Each.repeat 50 + x = natIn 0 5 + xs = List.replicate (natIn 0 20) do natIn 0 10 + xs1 = List.dropRightWhile (n -> n < x) xs + xs2 = List.dropWhile (n -> n < x) (reverse xs) |> reverse + ensureEqual xs1 xs2 + +test> data.List.dropRightWhile.tests.all = + use Nat < + check (List.dropRightWhile (flip (<) 9) [1, 2, 3] === []) + +test> data.List.dropRightWhile.tests.last = + use Nat < + check + (List.dropRightWhile (flip (<) 3) [1, 2, 3, 4, 5, 1, 2] === [1, 2, 3, 4, 5]) + +test> data.List.dropRightWhile.tests.none = + use Nat < + check (List.dropRightWhile (flip (<) 0) [1, 2, 3] === [1, 2, 3]) + +data.List.dropUntil : (a ->{e} Boolean) -> [a] ->{e} [a] +data.List.dropUntil f xs = + go = cases + x +: xs + | f x -> xs + | otherwise -> go xs + _ -> [] + go xs + +data.List.dropUntil.doc : Doc +data.List.dropUntil.doc = + use List dropUntil + {{ + Drops elements from a list until a predicate is satisfied. The element that + satisfies the predicate is dropped as well. + + # Examples + + ``` + dropUntil Nat.isEven [1, 2, 3, 4, 5] + ``` + + ``` + dropUntil Nat.isOdd [1, 2, 3, 4, 5] + ``` + }} + +data.List.dropWhile : (a ->{e} Boolean) -> [a] ->{e} [a] +data.List.dropWhile f = cases + [] -> [] + x +: xs' | f x -> data.List.dropWhile f xs' + xs -> xs + +data.List.dropWhile.doc : Doc +data.List.dropWhile.doc = + use List dropWhile + use Nat <= + {{ + `` dropWhile p xs `` constructs a new {type List} from `xs`, removing the + longest prefix whose elements all match the predicate `p`. The result will be + a suffix of the input list, where the first element in the result is the + first element in the input for which the predicate is false. + + # Example + + ``` + dropWhile (x -> x <= 3) [1, 2, 3, 4, 5] + ``` + }} + +data.List.dropWhile.examples.ex1 : [Nat] +data.List.dropWhile.examples.ex1 = + List.dropWhile (x -> Universal.lt x 3) [1, 2, 3, 4, 5] + +test> data.List.dropWhile.tests.all = + check (List.dropWhile (flip Universal.lt 9) [1, 2, 3] === []) + +test> data.List.dropWhile.tests.middle = + check + (List.dropWhile (flip Universal.lt 3) [1, 2, 3, 4, 5, 1, 2] + === [3, 4, 5, 1, 2]) + +test> data.List.dropWhile.tests.none = + check (List.dropWhile (flip Universal.lt 0) [1, 2, 3] === [1, 2, 3]) + +data.List.empty : [a] +data.List.empty = [] + +data.List.empty.doc : Doc +data.List.empty.doc = {{ The empty {type List}, or ``[]``. }} + +data.List.equals : (a ->{f} b ->{g} Boolean) -> [a] -> [b] ->{f, g} Boolean +data.List.equals eq xs ys = + use List size + size xs === size ys && List.all id (List.zipWith eq xs ys) + +data.List.equals.doc : Doc +data.List.equals.doc = + use List equals + use Nat == + {{ + Compares two lists for equality. The given function is used to compare the + elements. + + # Examples + + ``` + equals (==) [1, 2, 3] [1, 2, 3] + ``` + + ``` + equals (==) [1, 2, 3] [1, 2, 4] + ``` + + ``` + equals (==) [1, 2, 3] [1, 2] + ``` + + ``` + equals (==) [1, 2, 3] [1, 2, 3, 4] + ``` + }} + +data.List.every : Nat -> [a] -> [a] +data.List.every n as = skip n (List.drop n as) + +data.List.every.doc : Doc +data.List.every.doc = + {{ + Returns every `n`th element of a {type List} (0-based indexing). + + If `n` is 0, returns the original {type List}. + + # Examples + + ``` + every 2 [1, 2, 3, 4, 5] + ``` + + ``` + every 1 [1, 2, 3, 4, 5] + ``` + + ``` + every 0 [1, 2, 3, 4, 5] + ``` + }} + +data.List.fill : Nat -> a -> [a] +data.List.fill length value = initialize length (const value) + +data.List.fill.doc : Doc +data.List.fill.doc = + use List fill + {{ + `` fill length value `` creates a {type List} of length `length` where every + element in the {type List} is the given `value`. + + # Example + + ``` + fill 4 "a" + ``` + }} + +data.List.fill.examples.ex1 : [Text] +data.List.fill.examples.ex1 = List.fill 4 "a" + +test> data.List.fill.tests.ex1 = + actual = List.fill 4 "a" + expected = ["a", "a", "a", "a"] + check (actual === expected) + +data.List.fill' : Nat -> '{g} a ->{g} [a] +data.List.fill' length value = initialize length (_ -> value()) + +data.List.fill'.doc : Doc +data.List.fill'.doc = + {{ + `` fill' length value `` evaluates `value` `length` times and returns a list + of the results. Because evaluating `value` can perform an effect, this can + result in varying elements in the output. + + # Examples: + + If `value` doesn't perform any effects, then the output will be a list of + the same value `length` times. + + ``` + fill' 4 do "hi" + ``` + + If `value` performs an effect, then the elements in the output won't + necessarily be the same. + + ``` + (do fill' 3 Random.int) |> lcg 42 + ``` + }} + +test> data.List.fill'.tests = test.verify do + ensureEqual (fill' 3 do 42) [42, 42, 42] + Scope.run do + n = Scope.ref 0 + incrementAndGet = do + Ref.modify n Nat.increment + Ref.read n + ensureEqual (fill' 3 incrementAndGet) [1, 2, 3] + +data.List.filter : (a ->{𝕖} Boolean) -> [a] ->{𝕖} [a] +data.List.filter pred as = + use List :+ + List.foldLeft (acc a -> (if pred a then acc :+ a else acc)) [] as + +data.List.filter.doc : Doc +data.List.filter.doc = + use List filter + {{ + Given a predicate and a list of elements, {filter} constructs a list + containing only the elements that satisfy the predicate. + + # Examples + + ``` + filter Nat.isEven [] + ``` + + ``` + filter Nat.isOdd [2, 3, 4, 5] + ``` + }} + +test> data.List.filter.tests.empty = + check (List.filter (x -> Universal.gt x 3) [] === []) + +test> data.List.filter.tests.negative = + check (List.filter (x -> Universal.gt x 3) [0, 1, 2] === []) + +test> data.List.filter.tests.positive = + check (List.filter (x -> Universal.gt x 3) [3, 4, 5] === [4, 5]) + +data.List.filterMap : (a ->{e} Optional b) -> [a] ->{e} [b] +data.List.filterMap f as = + use List :+ + go acc = cases + a +: as -> + b = f a + match b with + Some b -> go (acc :+ b) as + None -> go acc as + [] -> acc + go [] as + +data.List.filterMap.doc : Doc +data.List.filterMap.doc = + use List filterMap + {{ + {filterMap} maps an {type Optional}-valued function over a {type List}, then + accumulates and returns all {Some} values. + + # Example + + Use {filterMap} to look up multiple values in a map at once: + + ``` + dict = List.toMap [("apple", ?🍎), ("pear", ?🍐), ("orange", ?🍊)] + filterMap (k -> Map.get k dict) ["apple", "orange", "banana"] + ``` + }} + +data.List.filterMap.examples.ex1 : [Nat] +data.List.filterMap.examples.ex1 = + dict = List.toMap [("Foo", 42), ("Bar", 100), ("Baz", 40)] + List.filterMap (k -> Map.get k dict) ["Foo", "Bar", "Qux"] + +test> data.List.filterMap.tests.identity = runs 1000 do + l = gen.listOf gen.nat () + expect (List.filterMap Some l === l) + +test> data.List.filterMap.tests.mapsAndFilters = runs 1000 do + use Nat + + l = gen.listOf gen.nat () + f n = if Nat.isEven n then Some (n + 1) else None + expect (List.filterMap f l === List.somes (List.map f l)) + +data.List.find : (a ->{g} Boolean) -> [a] ->{g} Optional a +data.List.find p = cases + [] -> None + x +: rest -> if p x then Some x else data.List.find p rest + +data.List.find.doc : Doc +data.List.find.doc = + {{ + Look for a item that matches the predicate, returning the value if found, or + None otherwise + }} + +test> data.List.find.tests.ex1 = + check match ((List.find (const true) []) : Optional Nat) with + None -> true + _ -> false + +test> data.List.find.tests.ex2 = + check match List.find (n -> n Nat.== 2) [1, 2, 3] with + Some 2 -> true + _ -> false + +data.List.find! : (a ->{g} Boolean) -> [a] ->{g, Abort} a +data.List.find! p list = Optional.toAbort (List.find p list) + +data.List.find!.doc : Doc +data.List.find!.doc = + {{ + Look for a item that matches the predicate, returning the value if found, or + aborting otherwise + }} + +data.List.findFirstIndex : (a ->{e} Boolean) -> [a] ->{e} Optional Nat +data.List.findFirstIndex f xs = + use Nat + + go i = cases + x +: xs + | f x -> Some i + | otherwise -> go (i + 1) xs + _ -> None + go 0 xs + +data.List.findLastIndex : (a ->{e} Boolean) -> [a] ->{e} Optional Nat +data.List.findLastIndex f xs = + use Nat - + go i = cases + xs :+ x + | f x -> Some i + | otherwise -> go (i - 1) xs + _ -> None + go (List.size xs - 1) xs + +data.List.findMap : (a ->{g} Optional b) -> [a] ->{g} Optional b +data.List.findMap f = cases + h +: tail -> + match f h with + Some b -> Some b + None -> data.List.findMap f tail + [] -> None + +data.List.findMap.doc : Doc +data.List.findMap.doc = + use Nat fromText + {{ + `` findMap f xs `` calls the function `f` for every element in the list until + `f` returns a {Some}. + + # Examples + + ``` + findMap fromText ["alice", "bob", "32", "64"] + ``` + + ``` + findMap fromText ["alice", "bob", "juan"] + ``` + + # See also + + * @inlineSignature{List.filterMap} + * @inlineSignature{List.find} + }} + +test> data.List.findMap.tests.ex1 = + check (findMap Nat.fromText ["alice", "bob", "32", "64"] === Some 32) + +test> data.List.findMap.tests.ex2 = + check (findMap Nat.fromText ["alice", "bob"] === None) + +data.List.firstIndexOf : a -> [a] -> Optional Nat +data.List.firstIndexOf a as = + use Nat + + go : Nat -> [a] -> Optional Nat + go index = cases + [] -> None + head +: tail | head === a -> Some index + _ +: tail -> go (index + 1) tail + go 0 as + +data.List.firstIndexOf.doc : Doc +data.List.firstIndexOf.doc = + use List firstIndexOf + {{ + `` firstIndexOf x xs `` returns the first index in the {type List} `xs` where + the element `x` occurs, using a head-to-tail scan of the {type List}, or + {None} if `x` is not found. + + # Examples + + ``` + firstIndexOf 0 [] + ``` + + ``` + firstIndexOf 0 [1, 2, 3] + ``` + + ``` + firstIndexOf 2 [1, 2, 3] + ``` + }} + +test> data.List.firstIndexOf.tests.negative1 = + check (List.firstIndexOf 0 [] === None) + +test> data.List.firstIndexOf.tests.negative2 = + check (List.firstIndexOf 0 [1, 2, 3] === None) + +test> data.List.firstIndexOf.tests.negative3 = + check (List.firstIndexOf 1 [0, 0, 0] === None) + +test> data.List.firstIndexOf.tests.positive1 = + check (List.firstIndexOf 1 [1, 2, 3] === Some 0) + +test> data.List.firstIndexOf.tests.positive2 = + check (List.firstIndexOf 3 [3, 1, 2, 3] === Some 0) + +test> data.List.firstIndexOf.tests.positive3 = + check (List.firstIndexOf 1 [3, 1, 2, 3] === Some 1) + +data.List.flatMap : (a ->{e} [b]) -> [a] ->{e} [b] +data.List.flatMap f = + use List ++ + List.foldLeft (acc e -> acc ++ f e) [] + +data.List.flatMap.doc : Doc +data.List.flatMap.doc = + use List flatMap + use Nat * + {{ + `` flatMap `` maps over a {type List} and concatenates the results together. + + # Example + + ``` + flatMap (x -> [x, x * x]) [1, 2, 3, 4] + ``` + }} + +data.List.flatMap.examples.ex1 : [Nat] +data.List.flatMap.examples.ex1 = + use Nat * + List.flatMap (x -> [x, x * x]) [1, 2, 3, 4] + +data.List.flatMap.examples.ex2 : [Nat] +data.List.flatMap.examples.ex2 = + xs = [(1, 2), (2, 3), (3, 4)] + List.flatMap + (cases (amount, element) -> List.map (do element) (List.range 0 amount)) xs + +test> data.List.flatMap.tests.associative = runs 100 do + use Int * + + use List flatMap + m = gen.listOf gen.int () + f x = [x + +1, x + +2] + g x = [x * +2, x * +3] + expect (flatMap g (flatMap f m) === flatMap (x -> flatMap g (f x)) m) + +test> data.List.flatMap.tests.flatMapIdentityIsJoin = runs 100 do + use gen listOf + xs = listOf (listOf natInOrder) () + expect (List.join xs === List.flatMap id xs) + +test> data.List.flatMap.tests.identity = runs 100 do + x = gen.listOf gen.int () + expect (List.flatMap (x -> [x]) x === x) + +data.List.flatMapRight : (a ->{e} [b]) -> [a] ->{e} [b] +data.List.flatMapRight f = + use List ++ + List.foldRight (e acc -> f e ++ acc) [] + +data.List.flatMapRight.doc : Doc +data.List.flatMapRight.doc = + use List flatMapRight + use Nat * + {{ + `` flatMapRight `` maps over a {type List} and concatenates the results + together, working right to left. + + # Examples + + ``` + flatMapRight (x -> [x, x * x]) [1, 2, 3, 4] + ``` + + The direction of traversal matters only for effects. This example emits the + elements of a {type List} onto a {type Stream}, and the elements are + emitted in reverse order. + + ``` + Stream.toList do + flatMapRight + (x -> let + emit x + []) [1, 2, 3, 4, 5] + ``` + }} + +data.List.flatMapRight.examples.ex1 : [Nat] +data.List.flatMapRight.examples.ex1 = + use Nat * + List.flatMapRight (x -> [x, x * x]) [1, 2, 3, 4] + +data.List.flatMapRight.examples.ex2 : [Nat] +data.List.flatMapRight.examples.ex2 = + xs = [(1, 2), (2, 3), (3, 4)] + List.flatMapRight + (cases (amount, element) -> List.map (do element) (List.range 0 amount)) xs + +test> data.List.flatMapRight.tests.flatMapIdentityIsJoin = runs 100 do + use gen listOf + xs = listOf (listOf natInOrder) () + expect (List.join xs === List.flatMapRight id xs) + +data.List.foldBalanced : (a ->{e} b) -> (b ->{e} b ->{e} b) -> b -> [a] ->{e} b +data.List.foldBalanced f op z as = + use List size + use data.List foldBalanced + if size as === 0 then z + else + if size as === 1 then f (List.unsafeAt 0 as) + else + (left, right) = halve as + op (foldBalanced f op z left) (foldBalanced f op z right) + +data.List.foldBalanced.doc : Doc +data.List.foldBalanced.doc = + use List foldBalanced range + use Nat + + use Text ++ + {{ + Folds a {type List} in a balanced manner by recursively folding the first + half of the list, then the second half, and finally combining the results. + Otherwise works like {foldBalanced}. + + # Example + + ``` + foldBalanced id (x y -> x + y) 0 (range 0 10) + ``` + + ``` + pair t1 t2 = "(" ++ t1 ++ ", " ++ t2 ++ ")" + foldBalanced Nat.toText pair "" (range 0 10) + ``` + }} + +data.List.foldDelimited : + (b ->{g2} b ->{g1} b) -> (a ->{g} b) -> b -> b -> b -> [a] ->{g, g1, g2} b +data.List.foldDelimited combine map prefix delimiter suffix input = + go : b -> [a] -> b + go acc = cases + [] -> acc + l +: [] -> combine acc (map l) + h +: t -> go (combine (combine acc (map h)) delimiter) t + combine (go prefix input) suffix + +data.List.foldDelimited.doc : Doc +data.List.foldDelimited.doc = + use Text ++ + {{ + Folds a list, inserting a delimiter between each element, and adding a prefix + and suffix. + + Arguments: + + 1. `combine` - a function that combines the prefix, delimiter, and suffix, + and each mapped element. + 2. `map` - a function that maps each element of the list to a value of the + result type. + 3. `prefix` - the prefix to add to the result. + 4. `delimiter` - the delimiter to add between each element. + 5. `suffix` - the suffix to add to the result. + 6. `input` - the list to fold. + + # Example + + This example folds a list of numbers into a {type Text}, with a prefix of + "(" and a suffix of ")" and a delimiter of ", ": + + ``` + foldDelimited (++) Nat.toText "(" ", " ")" [1, 2, 3] + ``` + + See also: {List.foldBalanced}. + }} + +data.List.foldLeft : (b ->{𝕖} a ->{𝕖} b) -> b -> [a] ->{𝕖} b +data.List.foldLeft f b = + go b = cases + a +: as -> go (f b a) as + [] -> b + go b + +data.List.foldLeft.doc : Doc +data.List.foldLeft.doc = + use Text ++ + {{ + Fold the elements in the list, associating to the left, using the given + binary operator and initial value. + + # Example + + ``` + List.foldLeft (++) "x" ["a", "b", "c"] + ``` + }} + +test> data.List.foldLeft.tests.dual = runs 1000 do + use gen boolean + l = gen.listOf boolean () + f = logic() + z = boolean() + expect (List.foldLeft f z l === List.foldRight (flip f) z (List.reverse l)) + +test> data.List.foldLeft.tests.endomorphic = runs 1000 do + use List foldLeft + use gen boolean + l = gen.listOf boolean () + f = logic() + z = boolean() + expect (foldLeft f z l === (List.map (flip f) l |> foldLeft (>>) id <| z)) + +test> data.List.foldLeft.tests.length = runs 1000 do + l = gen.listOf gen.boolean () + expect (List.foldLeft (x y -> Nat.increment x) 0 l === List.size l) + +data.List.foldMap.doc : Doc +data.List.foldMap.doc = + use List foldMap + use Nat * + + {{ + `` foldMap f op z xs `` transforms every value of a list `xs` with a unary + function `f`, then applies a binary operator `op` to the resulting values. + The initial value `z` is used for the empty list. + + The operation `op` is assumed to be associative, and the order in which the + values are transformed and combined is unspecified. + + # Examples + + ``` + foldMap (x -> x * 2) (+) 0 [1, 2, 3] + ``` + + ``` + foldMap (x -> [x]) (flip (List.++)) [] [1, 2, 3] + ``` + + If we use a non-associative operation, we can reveal the order in which the + values are actually combined: + + ``` + foldMap + (x -> Nat.toText x) + (a b -> "(" Text.++ a Text.++ " + " Text.++ b Text.++ ")") + "" + (List.range 0 10) + ``` + }} + +data.List.foldRight : (a ->{e} b ->{e} b) -> b -> [a] ->{e} b +data.List.foldRight f b = + go b = cases + as :+ a -> go (f a b) as + [] -> b + go b + +data.List.foldRight.doc : Doc +data.List.foldRight.doc = + use Text ++ + {{ + Fold the elements in the list, associating to the right, using the given + binary operator. + + # Example + + ``` + List.foldRight (++) "x" ["a", "b", "c"] + ``` + }} + +test> data.List.foldRight.tests.dual = runs 1000 do + use gen boolean + l = gen.listOf boolean () + f = logic() + z = boolean() + expect (List.foldRight f z l === List.foldLeft (flip f) z (List.reverse l)) + +test> data.List.foldRight.tests.endomorphic = runs 1000 do + use List foldRight + use gen boolean + l = gen.listOf boolean () + f = logic() + z = boolean() + expect (foldRight f z l === (List.map f l |> foldRight (<<) id <| z)) + +test> data.List.foldRight.tests.homomorphism = runs 100 do + use List +: + xs = gen.listOf natInOrder () + expect (List.foldRight (+:) [] xs === xs) + +test> data.List.foldRight.tests.length = runs 1000 do + l = gen.listOf gen.boolean () + expect (List.foldRight (const Nat.increment) 0 l === List.size l) + +data.List.foreach : (a ->{g} ()) -> [a] ->{g} () +data.List.foreach f = cases + [] -> () + h +: t -> + ignore (f h) + data.List.foreach f t + +data.List.foreach.deprecated : (a ->{g} b) -> [a] ->{g} () +data.List.foreach.deprecated f = cases + [] -> () + h +: t -> + ignore (f h) + data.List.foreach.deprecated f t + +data.List.foreach.deprecated.doc : Doc +data.List.foreach.deprecated.doc = + use Text ++ + use foreach deprecated + {{ + `` deprecated f xs `` traverses the {type List} `xs` and applies the function + `f` to each element in turn, ignoring the results. Returns {Unit} as the + function is applied only for its effects. + + # Examples + + ``` + Stream.toList do deprecated emit ["duck", "rabbit", "beaver"] + ``` + + ``` + Scope.run do + r = Scope.ref "" + deprecated (x -> Ref.modify r (v -> v ++ x)) ["cat", "hat", "saw"] + Ref.read r + ``` + }} + +test> data.List.foreach.deprecated.tests = test.verify do + n = Each.range 0 20 + ns = List.replicate n do Random.natIn 0 1000 + ensure ((Stream.toList do foreach.deprecated (n -> emit n) ns) === ns) + +data.List.foreach.doc : Doc +data.List.foreach.doc = + use List foreach + use Text ++ + {{ + {{ docExample 2 do f xs -> foreach f xs }} traverses the {type List} `xs` and + applies the function `f` to each element in turn, ignoring the results. + Returns {Unit} as the function is applied only for its effects. + + It's equivalent to ``ignore (List.map f xs)``, but does not create an + intermediate list. + + # Examples + + ``` + Stream.toList do foreach emit ["duck", "rabbit", "beaver"] + ``` + + ``` + Scope.run do + r = Scope.ref "" + ["cat", "hat", "saw"] |> foreach (x -> Ref.modify r (v -> v ++ x)) + Ref.read r + ``` + }} + +data.List.foreach.flipped : [a] -> (a ->{g} ()) ->{g} () +data.List.foreach.flipped list f = match list with + [] -> () + x +: xs -> + ignore (f x) + data.List.foreach.flipped xs f + +data.List.foreach.flipped.deprecated : [a] -> (a ->{g} b) ->{g} () +data.List.foreach.flipped.deprecated list f = match list with + [] -> () + x +: xs -> + ignore (f x) + data.List.foreach.flipped.deprecated xs f + +data.List.foreach.flipped.deprecated.doc : Doc +data.List.foreach.flipped.deprecated.doc = + use Nat + + use foreach flipped + {{ + {{ docExample 2 do xs f -> flipped xs f }} applies the function `f` to every + element of the {type List} `xs`, ignoring the results, but combining the + effects. + + It's equivalent to ``ignore (List.map f xs)``. + + See also {foreach.deprecated} which takes its arguments in the opposite + order. + + # Example + + ``` + withInitialValue 0 do + flipped [10, 20, 30] (x -> Store.modify (s -> s + x)) + Store.get + ``` + }} + +data.List.foreach.flipped.doc : Doc +data.List.foreach.flipped.doc = + use Nat + + use foreach flipped + {{ + {{ docExample 2 do xs f -> flipped xs f }} applies the function `f` to every + element of the {type List} `xs`, ignoring the results, but combining the + effects. + + It's equivalent to ``ignore (List.map f xs)``, but does not create an + intermediate list. + + See also {{ docLink (docEmbedTermLink do List.foreach) }} which takes its + arguments in the opposite order. + + # Example + + ``` + withInitialValue 0 do + flipped [10, 20, 30] (x -> Store.modify (s -> s + x)) + Store.get + ``` + }} + +data.List.groupBy : (v ->{e} k) -> [v] ->{e} Map k (List.Nonempty v) +data.List.groupBy f = + List.foldLeft + (m v -> + Map.alter + (cases + None -> Some (List.Nonempty.singleton v) + Some vs -> Some (Nonempty.cons v vs)) (f v) m) Map.empty + +data.List.groupBy.doc : Doc +data.List.groupBy.doc = + {{ + Partitions the {type List} into a {type Map} according to a discriminator + function. + + The expression `` groupBy d list `` applies the function `d` to every element + in the {type List} `list`, constructing a {type Map} keyed by the results, + and under each key a {type List.Nonempty} of the elements of the {type List} + `list` for which the function `d` returned that key. + + # Example + + ``` + xs = [1, 2, 3, 4] + Map.toList (groupBy Nat.isEven xs) + ``` + }} + +data.List.groupBy.examples.ex1 : [[Nat]] +data.List.groupBy.examples.ex1 = + List.map List.Nonempty.toList (groupSublistsBy (===) [5, 4, 4, 6]) + +data.List.groupBy.examples.ex2 : [[Nat]] +data.List.groupBy.examples.ex2 = + List.map + List.Nonempty.toList (groupSublistsBy Universal.lt [5, 6, 7, 5, 9, 7, 6]) + +test> data.List.groupBy.tests.groups = + runs 100 do + use Heap sort + use List Nonempty.toList all + xs = gen.listOf gen.boolean () + f = yesNo() + groups = groupBy f xs + list = Map.toList groups + allCorrect = + all (cases (k, v) -> all (a -> k === f a) (Nonempty.toList v)) list + noJunk = + sort (List.flatMap Nonempty.toList (Map.values groups)) === sort xs + expect (allCorrect && noJunk) + +data.List.groupConsecutive : [a] -> [List.Nonempty a] +data.List.groupConsecutive = groupSublistsBy (===) + +data.List.groupConsecutive.doc : Doc +data.List.groupConsecutive.doc = + {{ + {groupConsecutive} is equivalent to ``groupSublistsBy (==)``. + + # Example + + ``` + groupConsecutive [?🐱, ?🐱, ?🐹, ?🐰, ?🐹, ?🐹, ?🐹] + ``` + }} + +test> data.List.groupConsecutive.tests.base = + use List.Nonempty singleton + check + (groupConsecutive [1, 2, 2, 3, 2] + === [singleton 1, 2 +| [2], singleton 3, singleton 2]) + +data.List.groupMap : + (a ->{e} k) -> (a ->{e} v) -> [a] ->{e} Map k (List.Nonempty v) +data.List.groupMap key value = + List.foldLeft + (m a -> + Map.alter + (cases + None -> Some (List.Nonempty.singleton (value a)) + Some as -> Some (Nonempty.cons (value a) as)) (key a) m) Map.empty + +data.List.groupMap.doc : Doc +data.List.groupMap.doc = + {{ + Partitions the {type List} into a {type Map} according to a pair of + functions, one that generates the key and another that generates the value. + + The expression `` groupMap key val list `` applies the functions `key` and + `val` to every element in the {type List} `list`, constructing an entry in + the resulting {type Map}. If `key` takes any two values in the {type List} to + the same key, the last value is used. + + # Example + + ``` + xs = ["the", "early", "bird", "gets", "the", "worm"] + Map.toList + (Map.map + (Set.toList << List.Nonempty.toSet) + (groupMap Text.size (Text.map ascii.toUpper) xs)) + ``` + }} + +test> data.List.groupMap.tests.groups = runs 100 do + use List map toSet + use Set == + xs = gen.listOf gen.boolean () + f = yesNo() + g = yesNo() + groups = groupMap f g xs + list = Map.toList groups + vals = List.flatMap (List.Nonempty.toList << at2) list + keyProp = toSet (map f xs) == toSet (map at1 list) + valueProp = List.all (x -> List.contains (g x) vals) xs + expect (keyProp && valueProp) + +data.List.groupMapReduce : + (a ->{e} k) -> (a ->{e} v) -> (v ->{e} v ->{e} v) -> [a] ->{e} Map k v +data.List.groupMapReduce key value combine = + List.foldLeft + (m a -> + Map.alter + (cases + None -> Some (value a) + Some v -> Some (combine v (value a))) (key a) m) Map.empty + +data.List.groupMapReduce.doc : Doc +data.List.groupMapReduce.doc = + use List ++ + {{ + Partitions the {type List} into a {type Map} according to a pair of functions + — one that generates the key and another that generates the value — combining + duplicates with a third function. + + The expression `` List.groupMapReduce k v f xs `` applies the functions `k` + and `v` to every element in the {type List} `xs`, constructing an entry in + the resulting {type Map}. If `k` takes any two values in the {type List} to + the same key, they're combined with the function `f` with the semantics of a + left fold. + + That is, + + @typecheck ``` + groupMapReduce key val combine list = + Map.map (reduceLeft combine) (groupMap key val list) + ``` + + # Example + + ``` + xs = [5, 4, 4, 6] + Map.toList (List.groupMapReduce Nat.isEven List.singleton (++) xs) + ``` + }} + +test> data.List.groupMapReduce.tests.groups = runs 100 do + xs = gen.listOf gen.boolean () + f = yesNo() + g = yesNo() + h = logic() + expect (groupMapReduce f g h xs === Map.map (reduceLeft h) (groupMap f g xs)) + +data.List.groupSublistsBy : + (a ->{e} a ->{e} Boolean) -> [a] ->{e} [List.Nonempty a] +data.List.groupSublistsBy p' = cases + [] -> [] + x' +: xs' -> + use List +: + go p z = cases + [] -> ([], []) + x +: xs -> + (ys, zs) = go p x xs + if p z x then (x +: ys, zs) else ([], x +| ys +: zs) + let + (ys', zs') = go p' x' xs' + x' +| ys' +: zs' + +data.List.groupSublistsBy.doc : Doc +data.List.groupSublistsBy.doc = + use List map + use Nat < == + {{ + `` groupSublistsBy f xs `` groups elements of `xs` into a list of sublists. + Consecutive elements of `xs` appear in the same group if `f` applied to them + is ``true``. + + Note that `` List.join (Nonempty.toList (List.groupSublistsBy f xs)) `` is + always `xs`, regardless of what `f` is. + + # Examples + + Grouping by an equality condition will group together consecutive runs of + the same element: + + ``` + map List.Nonempty.toList (groupSublistsBy (==) [5, 4, 4, 6]) + ``` + + Grouping by an order on the elements will result in ordered sublists: + + ``` + map List.Nonempty.toList (groupSublistsBy (<) [5, 6, 7, 5, 9, 7, 6]) + ``` + }} + +data.List.groupWith : (a ->{e} a ->{f} Boolean) -> [a] ->{e, f} [[a]] +data.List.groupWith p xs = + use List +: + f x a q = + (ys, zs) = a (p x) + if q x then (x +: ys, zs) else ([], x +: ys +: zs) + at2 (List.foldRight f (const ([], [])) xs (const false)) + +data.List.groupWith.doc : Doc +data.List.groupWith.doc = + use Nat <= + {{ + `` groupWith p xs `` groups the elements of the list `xs` into sublists where + each sublist contains successive elements that compare `` true `` under the + comparison function `p`. + + # Examples + + We can use {groupWith} to group equal elements of a sorted list: + + ``` + groupWith (Nat.==) [1, 2, 2, 3, 3, 3, 4, 4, 4, 4] + ``` + + On an unordered list, this will group elements that are equal and adjacent: + + ``` + List.map fromCharList (groupWith (Char.==) (toCharList "hello")) + ``` + + We can group runs of elements that are less than or equal to the next + element: + + ``` + groupWith (<=) [1, 2, 2, 3, 1, 2, 0, 4, 5, 2] + ``` + + We can also use abilities in the comparison function. Here we use the + {type Each} ability to generate all partitions of a list: + + ``` + Each.toList do groupWith (do do each [true, false]) [1, 2, 3] + ``` + }} + +data.List.halve : [a] -> ([a], [a]) +data.List.halve s = + use Nat / + n = List.size s / 2 + (List.take n s, List.drop n s) + +data.List.halve.doc : Doc +data.List.halve.doc = + {{ + Splits a {type List} into a prefix and suffix of equal length. If the + {type List} has an odd number of elements, the suffix will have one more + element than the prefix. + + # Examples + + ``` + halve [1, 2, 3, 4, 5] + ``` + + ``` + halve [1, 2, 3, 4] + ``` + }} + +data.List.head : [a] -> Optional a +data.List.head = cases + [] -> None + a +: _ -> Some a + +data.List.head.doc : Doc +data.List.head.doc = + use List head + {{ + Returns the first element of a {type List}, or {None} if it's empty. The + complexity is O(1). + + # Examples + + ``` + head List.empty + ``` + + ``` + head [4] + ``` + + ``` + head [1, 2, 3] + ``` + }} + +data.List.head.examples.evaluated.elems : Optional Nat +data.List.head.examples.evaluated.elems = List.head examples.elems + +data.List.head.examples.evaluated.empty : Optional a +data.List.head.examples.evaluated.empty = List.head List.empty + +data.List.head.examples.evaluated.single : Optional Nat +data.List.head.examples.evaluated.single = List.head examples.single + +test> data.List.head.test = runs 100 do + use List +: head + x = natInOrder() + xs = gen.listOf natInOrder () + expect (head (x +: xs) === Some x && head [] === None) + +data.List.indexed : [a] -> [(a, Nat)] +data.List.indexed = + use List :+ + use Nat + + go acc i = cases + a +: as -> go (acc :+ (a, i)) (i + 1) as + [] -> acc + go [] 0 + +data.List.indexed.doc : Doc +data.List.indexed.doc = + {{ + Returns a {type List} of pairs of the form `(element, index)` for each + element in the given {type List}, where the `index` is the position of the + `element` in the {type List}. + + # Example + + ``` + List.indexed [?a, ?b, ?c] + ``` + }} + +test> data.List.indexed.tests.isIndexed = runs 1000 do + l = gen.listOf gen.boolean () + expect (List.indexed l === List.zip l (List.range 0 (List.size l))) + +data.List.indexOfSublist : [a] -> [a] -> Optional Nat +data.List.indexOfSublist needle haystack = + use Nat + + use Optional toAbort + if List.isPrefixOf needle haystack then Some 0 + else + toOptional! do + rest = toAbort (List.tail haystack) + toAbort (data.List.indexOfSublist needle rest) + 1 + +data.List.indexOfSublist.doc : Doc +data.List.indexOfSublist.doc = + {{ + `` indexOfSublist needle haystack `` finds the index of the first occurrence + of 'needle' as a sublist of 'haystack', or {None} if 'needle' doesn't occur + in 'haystack'. + + # Examples + + ``` + indexOfSublist (toCharList "ca") (toCharList "abcab") + ``` + + ``` + indexOfSublist (toCharList "ab") (toCharList "abab") + ``` + + ``` + indexOfSublist (toCharList "pq") (toCharList "abab") + ``` + }} + +data.List.init : [a] -> Optional [a] +data.List.init = cases + [] -> None + xs :+ _ -> Some xs + +data.List.init.doc : Doc +data.List.init.doc = + use List init + {{ + Returns all but the last element of a {type List}. + + # Examples + + ``` + init [1, 2, 3, 4, 5] + ``` + + ``` + init [1] + ``` + + ``` + init [] + ``` + }} + +data.List.initialize : Nat -> (Nat ->{e} a) ->{e} [a] +data.List.initialize length initializer = + use List :+ + use Nat + + go : Nat -> [a] -> [a] + go i acc = + if Universal.gteq i length then acc else go (i + 1) (acc :+ initializer i) + go 0 [] + +data.List.initialize.doc : Doc +data.List.initialize.doc = + use Nat * + {{ + `` initialize n f `` makes a list of length `n` by calling `f` on the numbers + `0` to `n` (exclusive). + + # Examples + + ``` + initialize 10 (x -> x) + ``` + + ``` + initialize 0 (x -> "hello") + ``` + + ``` + initialize 10 (x -> x * 10) + ``` + }} + +data.List.initialize.examples.ex1 : [Nat] +data.List.initialize.examples.ex1 = + use Nat * + initialize 4 (i -> i * 2) + +test> data.List.initialize.tests.tests.ex1 = + use Nat * + actual = initialize 4 (i -> i * 2) + expected = [0, 2, 4, 6] + check (actual === expected) + +data.List.inits : [a] -> [[a]] +data.List.inits l = + use List :+ + is = match l with + [] -> [] + xs :+ _ -> data.List.inits xs + is :+ l + +data.List.inits.doc : Doc +data.List.inits.doc = + {{ + Returns a {type List} of all initial segments of the input. The first element + of the result is the empty {type List}, and the last element is the input + {type List} itself. + + # Examples + + ``` + inits [1, 2, 3] + ``` + }} + +data.List.insertAfter : (a ->{e} a ->{f} Boolean) -> a -> [a] ->{e, f} [a] +data.List.insertAfter p = cases + x, [] -> [x] + x, yys@(y +: ys) -> + if p x y then x List.+: yys else y List.+: data.List.insertAfter p x ys + +data.List.insertAfter.doc : Doc +data.List.insertAfter.doc = + use Nat <= + {{ + `` insertAfter p x ys `` inserts the element `x` into the list `ys` before + the first element `y` where `p x y` returns ``false``. If no such element is + found, `x` is appended to the end of the list. + + # Examples + + {insertAfter} can be used to insert an element into a sorted list: + + ``` + insertAfter (<=) 3 [1, 2, 4, 5] + ``` + + We can implement a simple insertion sort using {insertAfter}: + + ``` + List.foldRight (insertAfter (<=)) [] [3, 1, 4, 1, 5, 9, 2, 6] + ``` + + We can use abilities in the comparison function. Here we generate an + identity matrix by inserting `1` after each element in the list `[0,0,0]`: + + ``` + Each.toList do insertAfter (do do each [true, false]) 1 [0, 0, 0] + ``` + }} + +data.List.insertAt : Nat -> a -> [a] -> [a] +data.List.insertAt i a as = + use List ++ + List.take i as ++ [a] ++ List.drop i as + +data.List.insertAt.doc : Doc +data.List.insertAt.doc = + {{ + Inserts an element into a list at a given index (0-based). If the index is + out of bounds, the element is inserted at the end of the list. + + # Examples + + ``` + insertAt 2 "c" ["a", "b", "d"] + ``` + + ``` + insertAt 42 "e" ["a", "b", "c", "d"] + ``` + }} + +data.List.insertSortedDistinct : a -> [a] -> [a] +data.List.insertSortedDistinct a = cases + [] -> [a] + [a2] + | a === a2 -> [a2] + | Universal.lteq a a2 -> [a, a2] + | otherwise -> [a2, a] + as -> + match halve as with + (l, r@(mid +: _)) -> + if Universal.lt a mid then data.List.insertSortedDistinct a l List.++ r + else l List.++ data.List.insertSortedDistinct a r + _ -> bug "insertSortedDistinct: halve returned a list of size < 2" + +data.List.insertSortedDistinct.doc : Doc +data.List.insertSortedDistinct.doc = + {{ + `` insertSortedDistinct a as `` inserts `a` into `as`, assuming `as` is + ordered ascending via {Universal.ordering}, using binary search to find the + insertion point. + + # Examples + + ``` + insertSortedDistinct 4 [0, 1, 2, 3, 5] + ``` + + If `a` already exists in `as`, this will just return `as`: + + ``` + insertSortedDistinct 1 [0, 1, 2, 3, 4] + ``` + + If `as` is not sorted in ascending order, this function will insert the + element in a spot you might not expect: + + ``` + insertSortedDistinct 3 [6, 5, 4, 2, 1, 0] + ``` + }} + +test> data.List.insertSortedDistinct.tests = test.verify do + use Nat == + size = Each.range 0 32 + i = Each.range 0 size + ns = List.range 0 size + ns' = deleteFirst (n -> n == i) ns + ignore "make sure insertion works" + ensure (insertSortedDistinct i ns' === ns) + ignore "and make sure insertion never produces dupes" + ensure (insertSortedDistinct i ns === ns) + +data.List.intercalate : [a] -> [[a]] -> [a] +data.List.intercalate between = cases + [] -> [] + xs +: xxs -> xs List.++ List.flatMap (as -> between List.++ as) xxs + +data.List.intercalate.doc : Doc +data.List.intercalate.doc = + {{ + `` intercalate xs xss `` is equivalent to + ``List.join (List.intersperse xs xss)``. It inserts the list `xs` in between + the lists in `xss` and concatenates the result. + + # Example + + ``` + intercalate + ["cha", "cha", "cha"] [["one", "two"], ["three", "four"], ["..."]] + ``` + }} + +test> data.List.intercalate.tests.empty = check (intercalate [] [] === []) + +test> data.List.intercalate.tests.empty1 = + check (intercalate [] [[1], [2]] === [1, 2]) + +test> data.List.intercalate.tests.empty2 = check (intercalate [1] [] === []) + +test> data.List.intercalate.tests.simple = + check (intercalate [3, 4] [[1, 2], [5, 6]] === [1, 2, 3, 4, 5, 6]) + +test> data.List.intercalate.tests.size = + use List size + use Nat * + + use gen listOf + go _ = + expect + let + l = listOf natInOrder () + v = listOf (listOf natInOrder) () + i = intercalate l v + actual = size i + expected = + size (List.join v) + size l * truncate0 (subtractToInt (size v) 1) + if Boolean.not (expected === actual) then + bug ("Not equal!", expected, actual, l, v) + else true + runs 100 go + +data.List.interleave : [a] -> [a] -> [a] +data.List.interleave xs ys = + use List ++ +: :+ + go : [a] -> [a] -> [a] -> [a] + go acc = cases + [], ys -> acc ++ ys + xs, [] -> acc ++ xs + x +: xs, y +: ys -> go (acc :+ x) (y +: ys) xs + go [] xs ys + +data.List.interleave.doc : Doc +data.List.interleave.doc = + use List interleave + {{ + Combines two lists into a single list, alternating elements from each list. + If the two lists have different lengths, a suffix of the longer list will + appear at the end of the result. + + # Examples + + ``` + interleave [1, 2, 3] [4, 5, 6, 7] + ``` + + ``` + interleave [1, 2, 3, 4] [5, 6, 7] + ``` + + ``` + interleave [1, 2, 3, 4] [5] + ``` + + # Relationships and properties + + {interleave} is the inverse of {uncollate}: + + ``` + uncurry interleave (uncollate [1, 2, 3, 4, 5]) + ``` + + However, {uncollate} is not the exact inverse of {interleave}: + + ``` + uncollate (interleave [1, 2] [3, 5, 7, 9]) + ``` + }} + +data.List.intersperse : a -> [a] -> [a] +data.List.intersperse sep ls = + use List ++ +: + prependToAll : a -> [a] -> [a] + prependToAll sep = cases + [] -> [] + a +: as -> [sep, a] ++ prependToAll sep as + match ls with + [] -> [] + a +: as -> a +: prependToAll sep as + +data.List.intersperse.doc : Doc +data.List.intersperse.doc = + {{ + Takes an element and a {type List} and intersperses that element between the + elements of the list. + + # Example + + ``` + List.intersperse "potato" ["one", "two", "three", "four"] + ``` + }} + +test> data.List.intersperse.tests.base = + sep = ?, + list = [?a, ?b, ?c, ?d] + check (List.intersperse sep list === [?a, ?,, ?b, ?,, ?c, ?,, ?d]) + +test> data.List.intersperse.tests.empty = + sep = 0 + list = [] + check (List.intersperse sep list === []) + +data.List.isEmpty : [a] -> Boolean +data.List.isEmpty xs = xs === [] + +data.List.isEmpty.doc : Doc +data.List.isEmpty.doc = + use List isEmpty + {{ + Returns `` true `` if the list is empty, `` false `` otherwise. + + # Examples + + ``` + isEmpty [] + ``` + + ``` + isEmpty [1, 2, 3] + ``` + }} + +data.List.isInfixOf : [a] -> [a] -> Boolean +data.List.isInfixOf infix list = + List.isPrefixOf infix list || (match list with + _ +: xs -> data.List.isInfixOf infix xs + [] -> false) + +data.List.isInfixOf.doc : Doc +data.List.isInfixOf.doc = + {{ + `` isInfixOf is xs `` checks whether `xs` contains `is`, as a contiguous + sub-list. + + # Examples + + ``` + isInfixOf [2, 3] [1, 2, 3, 4] + ``` + + ``` + isInfixOf [1, 2] [1, 2, 3, 4] + ``` + + ``` + isInfixOf [3, 4] [1, 2, 3, 4] + ``` + + ``` + isInfixOf [3] [1, 2, 3, 4] + ``` + + ``` + isInfixOf [1, 2] [1] + ``` + + The empty list is a sub-list of any list: + + ``` + isInfixOf [] [1, 2, 3, 4] + ``` + + Note that the sub-list needs to appear as a contiguous block, so: + + ``` + isInfixOf [1, 3] [1, 2, 3, 4] + ``` + }} + +test> data.List.isInfixOf.tests.prop1 = + go _ = + use List ++ + use gen listOf + a = listOf natInOrder () + b = listOf natInOrder () + c = listOf natInOrder () + expect (isInfixOf b (a ++ b ++ c)) + runs 100 go + +data.List.isPrefixOf : [a] -> [a] -> Boolean +data.List.isPrefixOf prefix list = match prefix with + p +: ps -> + match list with + x +: xs -> p === x && data.List.isPrefixOf ps xs + [] -> false + [] -> true + +data.List.isPrefixOf.doc : Doc +data.List.isPrefixOf.doc = + use List isPrefixOf + {{ + `` isPrefixOf ps xs `` checks whether `ps` is a prefix of `xs`. + + # Examples + + ``` + isPrefixOf [1, 2] [1, 2, 3] + ``` + + ``` + isPrefixOf [1, 2] [1, 2] + ``` + + ``` + isPrefixOf [1, 2] [1, 3, 4] + ``` + + ``` + isPrefixOf [1, 2] [1] + ``` + + The empty list is a prefix of any list: + + ``` + isPrefixOf [] [1, 2, 3] + ``` + }} + +test> data.List.isPrefixOf.tests.prop1 = + go _ = + use List ++ + use gen listOf + a = listOf natInOrder () + b = listOf natInOrder () + expect (List.isPrefixOf a (a ++ b)) + runs 100 go + +test> data.List.isPrefixOf.tests.prop2 = + go _ = + use List ++ size + use gen listOf + a = listOf natInOrder () + b = listOf natInOrder () + c = listOf natInOrder () + expect (size a !== size b || a === b === List.isPrefixOf a (b ++ c)) + runs 100 go + +data.List.isSortedBy : (a -> a ->{e} Boolean) -> [a] ->{e} Boolean +data.List.isSortedBy f = cases + [] -> true + [x] -> true + x +: (y +: rest) -> f x y && data.List.isSortedBy f (y List.+: rest) + +data.List.isSortedBy.doc : Doc +data.List.isSortedBy.doc = + use Nat <= + {{ + Returns `` true `` if the given {type List} is sorted according to the given + comparison function, `` false `` otherwise. + + # Example + + ``` + isSortedBy (<=) [1, 2, 3] + ``` + + ``` + isSortedBy (<=) [1, 3, 2] + ``` + }} + +data.List.isSuffixOf : [a] -> [a] -> Boolean +data.List.isSuffixOf suffix list = match suffix with + ss :+ s -> + match list with + xs :+ x -> s === x && data.List.isSuffixOf ss xs + [] -> false + [] -> true + +data.List.isSuffixOf.doc : Doc +data.List.isSuffixOf.doc = + use List isSuffixOf + {{ + `` isSuffixOf ss xs `` checks whether `ss` is a suffix of `xs`. + + Examples: + + ``` + isSuffixOf [2, 3] [1, 2, 3] + ``` + + ``` + isSuffixOf [2, 3] [2, 3] + ``` + + ``` + isSuffixOf [1, 3] [1, 2, 3] + ``` + + ``` + isSuffixOf [2, 3] [3] + ``` + + The empty list is a suffix of any list: + + ``` + isSuffixOf [] [1, 2, 3] + ``` + }} + +test> data.List.isSuffixOf.tests.prop1 = + go _ = + use List ++ + use gen listOf + a = listOf natInOrder () + b = listOf natInOrder () + expect (List.isSuffixOf b (a ++ b)) + runs 100 go + +test> data.List.isSuffixOf.tests.prop2 = + go _ = + use List ++ size + use gen listOf + a = listOf natInOrder () + b = listOf natInOrder () + c = listOf natInOrder () + expect (size b !== size c || b === c === List.isSuffixOf c (a ++ b)) + runs 100 go + +data.List.iterate : (a ->{Abort} a) -> a -> [a] +data.List.iterate f init = unfold! init do + s = Store.get + Store.put (f s) + s + +data.List.iterate.doc : Doc +data.List.iterate.doc = + use Nat + < + {{ + Generate a list of values from a seed value and a function, by repeatedly + applying the function to the seed. For a seed value x and a function f, the + output will be + + `[x, f x, f (f x), f (f (f x))]` + + and so on, until f calls abort. + + For example: + + ``` + List.iterate (x -> (if x < 5 then x + 1 else abort)) 0 + ``` + }} + +test> data.List.iterate.tests.ex1 = + use Nat + < + check + (List.iterate (n -> (if n < 10 then n + 1 else abort)) 1 + === [1, 2, 3, 4, 5, 6, 7, 8, 9]) + +data.List.join : [[a]] -> [a] +data.List.join = + use List ++ + List.foldLeft (++) [] + +data.List.join.doc : Doc +data.List.join.doc = + use List join + {{ + {join} flattens a list of lists into a single list. + + # Examples + + ``` + join [[1, 2], [3, 4], [5]] + ``` + + ``` + join [[]] + ``` + }} + +test> data.List.join.tests.associative = runs 100 do + use List join + use gen listOf + x = listOf (listOf (listOf natInOrder)) () + expect (join (join x) === join (List.map join x)) + +test> data.List.join.tests.homomorphism = runs 100 do + use List ++ + use gen listOf + x = listOf natInOrder () + y = listOf natInOrder () + expect (List.join [x, y] === (x ++ y)) + +test> data.List.join.tests.unit = runs 100 do + use List join + x = gen.listOf natInOrder () + expect (join [x] === join (List.map (a -> [a]) x)) + +data.List.last : [a] -> Optional a +data.List.last = cases + _ :+ x -> Some x + [] -> None + +data.List.last.doc : Doc +data.List.last.doc = + use List last + {{ + Returns the last element of a list, or {None} if it's empty. The complexity + is O(1). + + # Examples + + ``` + last [] + ``` + + ``` + last [4] + ``` + + ``` + last [1, 2, 3] + ``` + }} + +data.List.last.examples.elems : [Nat] +data.List.last.examples.elems = [1, 2, 3] + +data.List.last.examples.evaluated.elems : Optional Nat +data.List.last.examples.evaluated.elems = List.last examples.elems + +data.List.last.examples.evaluated.empty : Optional a +data.List.last.examples.evaluated.empty = List.last examples.empty + +data.List.last.examples.evaluated.single : Optional Nat +data.List.last.examples.evaluated.single = List.last examples.single + +data.List.last.examples.single : [Nat] +data.List.last.examples.single = [1] + +test> data.List.last.tests.elems = check (List.last examples.elems === Some 3) + +test> data.List.last.tests.empty = check (List.last examples.empty === None) + +test> data.List.last.tests.isHeadOnReversedList = runs 100 do + xs = gen.listOf natInOrder () + expect ((xs |> List.last) === (xs |> List.reverse |> List.head)) + +test> data.List.last.tests.single = + check (List.last examples.single === Some 1) + +data.List.lefts : [Either a b] -> [a] +data.List.lefts = at1 << partitionEithers + +data.List.lefts.doc : Doc +data.List.lefts.doc = + {{ + Accumulate all of the elements that are {Left} in a list of {type Either}s. + + # Example + + ``` + lefts [Left 2, Right "bump", Left 10, Right "thump"] + ``` + }} + +test> data.List.lefts.tests.ex1 = + check + let + actual = lefts [Left 2, Right "Hello", Left 10, Right "Hello again"] + expected = [2, 10] + assert (actual === expected) ("Not equal!", actual, expected) true + +data.List.map : (a ->{𝕖} b) -> [a] ->{𝕖} [b] +data.List.map f = + use List :+ + go acc = cases + a +: as -> go (acc :+ f a) as + [] -> acc + go [] + +data.List.map.doc : Doc +data.List.map.doc = + use List map + use Nat increment + {{ + Apply a function to each element of a {type List}. + + # Examples + + ``` + map increment [1, 2, 3] + ``` + + ``` + map increment [] + ``` + }} + +test> data.List.map.tests.functor = runs 1000 do + use List map + l = gen.listOf gen.boolean () + f = yesNo() + g = yesNo() + expect (map id l === l && map (f >> g) l === map g (map f l)) + +data.List.map2 : (a ->{e} b ->{e} c) -> [a] -> [b] ->{e} [c] +data.List.map2 f xs ys = Each.toList do + x = each xs + y = each ys + f x y + +data.List.map2.doc : Doc +data.List.map2.doc = + use List map2 + use Nat + + {{ + Applies a function to every pair of elements from two lists. The result is a + list whose length is the product of the lengths of the two lists. + + # Examples + + ``` + map2 (+) [1, 2, 3] [4, 5, 6] + ``` + + ``` + map2 Tuple.pair [1, 2, 3] [4, 5, 6] + ``` + }} + +data.List.mapIndexed : (Nat ->{𝕖} a ->{𝕖} b) -> [a] ->{𝕖} [b] +data.List.mapIndexed f = + use List :+ + use Nat + + go acc i = cases + a +: as -> go (acc :+ f i a) (i + 1) as + [] -> acc + go [] 0 + +data.List.mapIndexed.doc : Doc +data.List.mapIndexed.doc = + {{ + Like {List.map}, applies a function over a {type List} of elements, but gets + passed the index to the element in the list. + + # Examples + + Replace the list elements with their index: + + ``` + mapIndexed const ["one", "two", "three"] + ``` + + Add indexes to the elements in pairs: + + ``` + mapIndexed Tuple.pair [5, 3, 1] + ``` + }} + +test> data.List.mapIndexed.tests.ex1 = + check (mapIndexed const ["Foo", "Bar", "Baz"] === [0, 1, 2]) + +test> data.List.mapIndexed.tests.ex2 = + check (mapIndexed (i v -> (i, v)) [5, 3, 1] === [(0, 5), (1, 3), (2, 1)]) + +test> data.List.mapIndexed.tests.mapsAndIndexes = runs 1000 do + use Nat + + l = gen.listOf gen.nat () + f n i = n + i + expect (mapIndexed f l === List.zipWith f l (List.range 0 (List.size l))) + +data.List.mapRight : (a ->{g} b) -> [a] ->{g} [b] +data.List.mapRight f xs = + use List +: + g a bs = f a +: bs + List.foldRight g [] xs + +data.List.mapRight.doc : Doc +data.List.mapRight.doc = + use List map mapRight + use Nat + + use Store get put + {{ + `` mapRight f xs `` is the same as `` map f xs `` except that the effects of + `f` will happen in reverse order. + + # Examples + + This uses the {type Store} ability with {map} to number the elements of the + list: + + ``` + withInitialValue 0 do + map + (x -> let + n = get + put (n + 1) + (n, x)) ["a", "b", "c"] + ``` + + Using {mapRight} will number the elements of the list in reverse order: + + ``` + withInitialValue 0 do + mapRight + (x -> let + n = get + put (n + 1) + (n, x)) ["a", "b", "c"] + ``` + + This throws the last element of the list: + + ``` + toEither do mapRight throw [1, 2, 3] + ``` + + Whereas using {map} will throw the first element of the list: + + ``` + toEither do map throw [1, 2, 3] + ``` + }} + +data.List.maximum : [a] -> Optional a +data.List.maximum list = + go x = cases + [] -> Some x + y +: ys | Universal.gt y x -> go y ys + _ +: ys -> go x ys + match list with + x +: xs -> go x xs + [] -> None + +data.List.maximum.doc : Doc +data.List.maximum.doc = {{ Finds the largest element in the list. }} + +test> data.List.maximum.tests.base = + check (List.maximum [1, 2, 90, 3, 5] === Some 90) + +test> data.List.maximum.tests.empty = check (List.maximum [] === None) + +data.List.maximumBy : (a ->{e} a ->{e} Ordering) -> [a] ->{e} Optional a +data.List.maximumBy f xs = + go = cases + None, x +: xs -> go (Some x) xs + Some y, x +: xs + | f x y === Greater -> go (Some x) xs + | otherwise -> go (Some y) xs + y, _ -> y + go None xs + +data.List.maximumOn : + (b ->{f} b ->{g} Ordering) -> (a ->{e} b) -> [a] ->{e, f, g} Optional a +data.List.maximumOn ord p as = + f a e = + g = cases + None, q -> Some (q, e) + b@(Some (o, y)), q + | ord o q === Less -> Some (q, e) + | otherwise -> b + g a (p e) + Optional.map at2 (List.foldLeft f None as) + +data.List.maximumOn.doc : Doc +data.List.maximumOn.doc = + use Universal ordering + {{ + `` maximumOn ord p as `` returns the element of the list `as` that has the + maximum value under the function `p` according to the ordering function + `ord`. If the list is empty, this function returns {None}. + + If there are multiple elements with the maximum value, the first one is + returned. + + # Examples + + We can find the longest {type Text} in a {type List}: + + ``` + maximumOn ordering Text.size ["the", "quick", "brown", "fox"] + ``` + + We can use abilities in the comparison function. Here we find the most + frequent element in a list by using the {type Store} ability to keep track + of the frequency of each element: + + ``` + withInitialValue Bag.empty do + maximumOn + ordering (x -> let + b = Store.get + c = Bag.count x b + Store.put (Bag.add x b) + c) [1, 2, 2, 3, 3, 3, 4, 4, 4, 4] + ``` + }} + +data.List.mayNonempty : [a] -> Optional (List.Nonempty a) +data.List.mayNonempty = cases + [] -> None + x +: xs -> Some (Nonempty.Nonempty x xs) + +data.List.mayNonempty.doc : Doc +data.List.mayNonempty.doc = + {{ + Returns the given list as a {type List.Nonempty} if it has at least one + element, otherwise {None}. + }} + +data.List.minimum : [a] -> Optional a +data.List.minimum list = + go x = cases + [] -> Some x + y +: ys | Universal.lt y x -> go y ys + _ +: ys -> go x ys + match list with + x +: xs -> go x xs + [] -> None + +data.List.minimum.doc : Doc +data.List.minimum.doc = {{ Finds the smallest element in the list. }} + +test> data.List.minimum.tests.base = + check (List.minimum [+1, +20, -30, +4] === Some -30) + +data.List.minimumBy : (a ->{e} a ->{e} Ordering) -> [a] ->{e} Optional a +data.List.minimumBy f xs = + go = cases + None, x +: xs -> go (Some x) xs + Some y, x +: xs + | f x y === Less -> go (Some x) xs + | otherwise -> go (Some y) xs + y, _ -> y + go None xs + +data.List.modifyAt : Nat -> (a ->{g} a) -> [a] ->{g} Optional [a] +data.List.modifyAt i f xs = match List.splitAt i xs with + (before, x +: xs) -> Some (before List.++ (f x List.+: xs)) + _ -> None + +data.List.modifyAt.doc : Doc +data.List.modifyAt.doc = + use Nat + + {{ + `` modifyAt i f xs `` updates the `i`-th (0-based) element of the list `xs` + using the function `f`. + + It returns {None} if the index is out of bounds. + + ``` + modifyAt 0 (x -> x + 10) [0, 1, 2, 3] + ``` + + ``` + modifyAt 2 (x -> x + 10) [0, 1, 2, 3] + ``` + + ``` + modifyAt 1000 (x -> x + 10) [0, 1, 2, 3] + ``` + }} + +test> data.List.modifyAt.tests = + use Nat + + check + (List.map + (i -> modifyAt i (x -> x + 10) [0, 1, 2, 3, 4, 5]) [0, 1, 2, 3, 4, 5, 6] + === [ Some [10, 1, 2, 3, 4, 5] + , Some [0, 11, 2, 3, 4, 5] + , Some [0, 1, 12, 3, 4, 5] + , Some [0, 1, 2, 13, 4, 5] + , Some [0, 1, 2, 3, 14, 5] + , Some [0, 1, 2, 3, 4, 15] + , None + ]) + +data.List.mostFrequent : [a] -> Optional a +data.List.mostFrequent as = withInitialValue Bag.empty do + maximumOn + Universal.ordering (x -> let + b = Store.get + c = Bag.count x b + Store.put (Bag.add x b) + c) as + +data.List.mostFrequent.doc : Doc +data.List.mostFrequent.doc = + {{ + Returns the most frequent element in a list. If there are multiple elements + with the same frequency, the first one is returned. + + # Example + + ``` + mostFrequent [1, 2, 2, 3, 3, 3, 4, 4, 4, 4] + ``` + }} + +data.List.none : (i ->{e} Boolean) -> [i] ->{e} Boolean +data.List.none predicate = List.all (x -> Boolean.not (predicate x)) + +data.List.none.doc : Doc +data.List.none.doc = + {{ + `` List.none p xs `` returns whether the predicate `p` is false for every + element of the {type List} `xs`. Returns `` true `` if `xs` is empty. + }} + +data.List.nonempty : [a] ->{Abort} List.Nonempty a +data.List.nonempty = cases + x +: xs -> x +| xs + [] -> abort + +(data.List.Nonempty.++) : List.Nonempty a -> List.Nonempty a -> List.Nonempty a +(data.List.Nonempty.++) = cases + Nonempty.Nonempty x xs -> + cases + Nonempty.Nonempty y ys -> Nonempty.Nonempty x (xs List.:+ y List.++ ys) + +data.List.Nonempty.++.doc : Doc +data.List.Nonempty.++.doc = + use Nonempty ++ + {{ + Appends one nonempty list to another. The list `` x ++ y `` contains all the + elements from `x` followed by all the elements from `y`. + }} + +(data.List.Nonempty.+|) : a -> [a] -> List.Nonempty a +(data.List.Nonempty.+|) = Nonempty.Nonempty + +data.List.Nonempty.+|.doc : Doc +data.List.Nonempty.+|.doc = + {{ + Constructs a {type List.Nonempty} from an element for the {Nonempty.head} and + a {type List} for the {Nonempty.tail}. + + # Example + + ``` + 1 +| [2, 3] + ``` + }} + +data.List.Nonempty.align : + List.Nonempty a -> List.Nonempty b -> List.Nonempty (OneOrBoth a b) +data.List.Nonempty.align = List.Nonempty.alignWith id + +data.List.Nonempty.align.doc : Doc +data.List.Nonempty.align.doc = + use List.Nonempty align + {{ + Aligns two non-empty lists into a non-empty list of {type OneOrBoth} values. + + The result will have the same length as the longer of the two lists, and each + element will be a {type OneOrBoth} containing the corresponding elements from + the two input lists. If one of the lists is shorter than the other, the + result will contain {This} or {That} values accordingly. + + # Examples + + ``` + align (1 +| [2, 3]) ("a" +| ["b"]) + ``` + + ``` + align (1 +| [2]) ("a" +| ["b", "c"]) + ``` + + ``` + align (1 +| [2, 3]) ("a" +| ["b", "c"]) + ``` + + # See also + + * {List.Nonempty.alignWith} - a variant where you can specify a function to + apply to the aligned elements. + }} + +test> data.List.Nonempty.align.tests = test.verify do + use List.Nonempty align + labeled "more elements in the first list" do + actual = align (1 +| [2, 3]) ("a" +| ["b"]) + ensureEqual actual (Both 1 "a" +| [Both 2 "b", This 3]) + labeled "more elements in the second list" do + actual = align (1 +| [2]) ("a" +| ["b", "c"]) + ensureEqual actual (Both 1 "a" +| [Both 2 "b", That "c"]) + labeled "same number of elements" do + actual = align (1 +| [2, 3]) ("a" +| ["b", "c"]) + ensureEqual actual (Both 1 "a" +| [Both 2 "b", Both 3 "c"]) + +data.List.Nonempty.alignWith : + (OneOrBoth a b ->{g} c) + -> List.Nonempty a + -> List.Nonempty b + ->{g} List.Nonempty c +data.List.Nonempty.alignWith f = cases + Nonempty.Nonempty x xs, Nonempty.Nonempty y ys -> + f (Both x y) +| List.alignWith f xs ys + +data.List.Nonempty.alignWith.doc : Doc +data.List.Nonempty.alignWith.doc = + use List.Nonempty alignWith + use Nat + + use OneOrBoth fold + use Text size + {{ + Aligns two non-empty lists into a non-empty list of values using a function. + + The result will have the same length as the longer of the two lists, and each + element will be the result of applying the given function to the + corresponding elements from the two input lists. If one of the lists is + shorter than the other, the result will contain values accordingly. + + # Examples + + ``` + alignWith (fold id size (x y -> x + size y)) (1 +| [2, 3]) ("a" +| ["b"]) + ``` + + ``` + alignWith (fold id size (x y -> x + size y)) (1 +| [2]) ("a" +| ["b", "c"]) + ``` + + ``` + alignWith + (fold id size (x y -> x + size y)) (1 +| [2, 3]) ("a" +| ["b", "c"]) + ``` + + # See also + + * {List.Nonempty.align} - a variant that returns a list of {type OneOrBoth} + values. + }} + +test> data.List.Nonempty.alignWith.tests = test.verify do + use List.Nonempty alignWith + use Nat + + use Text size + f = OneOrBoth.fold id size (x y -> x + size y) + labeled "more elements in the first list" do + actual = alignWith f (1 +| [2, 3]) ("a" +| ["b"]) + ensureEqual actual (2 +| [3, 3]) + labeled "more elements in the second list" do + actual = alignWith f (1 +| [2]) ("a" +| ["b", "c"]) + ensureEqual actual (2 +| [3, 1]) + labeled "same number of elements" do + actual = alignWith f (1 +| [2, 3]) ("a" +| ["b", "c"]) + ensureEqual actual (2 +| [3, 4]) + +test> data.List.Nonempty.append.tests.associative = runs 100 do + use Nonempty append + x = atLeastOne natInOrder () + y = atLeastOne natInOrder () + z = atLeastOne natInOrder () + expect (append x (append y z) === append (append x y) z) + +test> data.List.Nonempty.append.tests.homomorphism = + runs 100 do + use List size + use List.Nonempty toList + use Nat + + x = atLeastOne natInOrder () + y = atLeastOne natInOrder () + expect + (size (toList x) + size (toList y) + === size (toList (Nonempty.append x y))) + +data.List.Nonempty.appendList : List.Nonempty a -> [a] -> List.Nonempty a +data.List.Nonempty.appendList = cases + Nonempty.Nonempty a as -> bs -> Nonempty.Nonempty a (as List.++ bs) + +data.List.Nonempty.appendList.doc : Doc +data.List.Nonempty.appendList.doc = + {{ + Appends a {type List} to the end of a {type List.Nonempty} list. + + # Examples + + ``` + appendList (Nonempty.Nonempty 1 [2, 3]) [4, 5] + ``` + }} + +data.List.Nonempty.at : Nat -> List.Nonempty a -> Optional a +data.List.Nonempty.at index = cases + Nonempty.Nonempty head tail -> + match index with + 0 -> Some head + _ -> List.at (index Nat.- 1) tail + +data.List.Nonempty.at.doc : Doc +data.List.Nonempty.at.doc = + use Nonempty Nonempty at + {{ + `` at n `` gets the element at the position `n` in the list (using + [zero-based indexing](https://en.wikipedia.org/wiki/Zero-based_numbering)), + or returns {None} if the list has fewer than `n+1` elements. + + # Examples + + ``` + at 0 (Nonempty 10 [20, 30]) + ``` + + ``` + at 2 (Nonempty 10 [20, 30]) + ``` + + ``` + at 3 (Nonempty 10 [20, 30]) + ``` + }} + +data.List.Nonempty.cons : a -> List.Nonempty a -> List.Nonempty a +data.List.Nonempty.cons a = Nonempty.Nonempty a << List.Nonempty.toList + +data.List.Nonempty.cons.doc : Doc +data.List.Nonempty.cons.doc = + {{ + `` Nonempty.cons x xs `` adds the element `x` to the front of the nonempty + list `xs`. + }} + +data.List.Nonempty.doc : Doc +data.List.Nonempty.doc = + use Nat + + use Nonempty ++ + {{ + {type List.Nonempty} is a type of list that always has at least one element. + Otherwise this type is identical to {type List}. + + The following is a summary of operations available on {type List.Nonempty}. + + # Constructing nonempty lists + + A {type List.Nonempty} list consists of a single element followed by a + regular {type List}: + + ``` + Nonempty.Nonempty 1 [2, 3] + ``` + + You can use the {+|} function to add an element to the front of a + {type List}, constructing a {type List.Nonempty}: + + ``` + 1 +| [2, 3] + ``` + + You can also put the element at the end, using {|+}: + + ``` + [1, 2] |+ 3 + ``` + + `` List.Nonempty.singleton x `` is a {type List.Nonempty} with just the + element `x`. + + # Adding and removing elements + + `` Nonempty.cons x xs `` adds the element `x` at the front of the + {type List.Nonempty} `xs`. + + `` Nonempty.snoc xs x `` adds the element `x` at the end of the + {type List.Nonempty} `xs`. + + `` appendList xs ys `` appends the {type List} ys onto the end of the + {type List.Nonempty} `xs`. + + `` Nonempty.tail xs `` removes the first element of `xs`, returning a + {type List}. + + `` Nonempty.init xs `` removes the last element of `xs`, returning a + {type List}. + + # Accessing and querying elements + + `` List.Nonempty.size xs `` gets the number of elements in the + {type List.Nonempty} `xs`. + + `` Nonempty.head xs `` gets the first element of `xs`. + + `` Nonempty.last xs `` gets the last element of `xs`. + + # Combining nonempty lists + + `` xs ++ ys `` appends `ys` to the end of `xs`. + + `` Nonempty.join xs `` concatenates a whole {type List.Nonempty} list full + of {type List.Nonempty} lists. + + # Traversals + + {type List.Nonempty} allows combining elements with a binary function + without regard to the empty case or a default value, using {reduceLeft} and + {reduceRight}: + + @signature{reduceLeft} @signature{reduceRight} + + ``` + reduceLeft (+) (5 +| [4, 6]) + ``` + + Other traversal functions are equivalent to their {type List} counterparts: + + @signature{Nonempty.scanLeft} @signature{Nonempty.scanRight} + @signature{List.Nonempty.map} @signature{List.Nonempty.flatMap} + @signature{List.Nonempty.foldLeft} + @signature{List.Nonempty.foldRight} + @signature{List.Nonempty.foldMap} @signature{Nonempty.reverse} + + # Conversions to/from other types + + @signature{List.Nonempty.toList} @signature{List.Nonempty.toSet} + }} + +data.List.nonempty.doc : Doc +data.List.nonempty.doc = + {{ + Turns a {type List} into a {type List.Nonempty} if it is non-empty, otherwise + calls {abort}. + }} + +data.List.Nonempty.examples.construction.ex1 : List.Nonempty Nat +data.List.Nonempty.examples.construction.ex1 = Nonempty.Nonempty 1 [2, 3] + +data.List.Nonempty.examples.construction.ex2 : Optional (List.Nonempty Nat) +data.List.Nonempty.examples.construction.ex2 = mayNonempty [1, 2, 3] + +data.List.Nonempty.filterMap : + (a ->{g} Optional b) -> List.Nonempty a ->{g} Optional (List.Nonempty b) +data.List.Nonempty.filterMap f = cases + Nonempty.Nonempty a [] -> Optional.map List.Nonempty.singleton (f a) + Nonempty.Nonempty a (a1 +: as) -> + match f a with + None -> data.List.Nonempty.filterMap f (Nonempty.Nonempty a1 as) + Some b -> + match data.List.Nonempty.filterMap f (Nonempty.Nonempty a1 as) with + Some bs -> Some (Nonempty.cons b bs) + None -> Some (List.Nonempty.singleton b) + +data.List.Nonempty.filterMap.doc : Doc +data.List.Nonempty.filterMap.doc = + use Nat > + {{ + Maps a function over the values of a non-empty list, possibly removing some + values. It is equivalent to running ``map f |> somes``. + + # Example + + ``` + f a = if Text.size a > 5 then Some a else None + List.Nonempty.filterMap f ("Circuit" +| ["Quasar", "Voyage"]) + ``` + + # See also + + * {Nonempty.traverseOptional} - where `` None `` is returned if any `f a` + returns `` None `` + }} + +test> data.List.Nonempty.filterMap.test.example = test.verify do + use List.Nonempty filterMap + use Nat > + use Text size + labeled "with all Nones" do + f a = if size a > 6 then Some a else None + actual = filterMap f ("Circuit" +| ["Quasar", "Voyage"]) + ensureEqual actual (Some ("Circuit" +| [])) + labeled "with some Somes" do + f a = if size a > 10 then Some a else None + actual = filterMap f ("Circuit" +| ["Quasar", "Voyage"]) + ensureEqual actual None + +data.List.Nonempty.flatMap : + (a ->{e} List.Nonempty b) -> List.Nonempty a ->{e} List.Nonempty b +data.List.Nonempty.flatMap f = cases + Nonempty.Nonempty a as -> + match List.flatMap (List.Nonempty.toList << f) as with + x +: xs -> f a Nonempty.++ Nonempty.Nonempty x xs + [] -> f a + +data.List.Nonempty.flatMap.doc : Doc +data.List.Nonempty.flatMap.doc = + {{ + Builds a new nonempty list which contains all the elements that result from + applying a function to each of the elements of an existing list. + }} + +test> data.List.Nonempty.flatMap.tests.flatMapIdIsjoin = runs 100 do + xs = atLeastOne (atLeastOne natInOrder) () + expect (List.Nonempty.flatMap id xs === Nonempty.join xs) + +data.List.Nonempty.foldLeft : + (b ->{e} a ->{e} b) -> b -> List.Nonempty a ->{e} b +data.List.Nonempty.foldLeft f b = cases + Nonempty.Nonempty h t -> List.foldLeft f (f b h) t + +data.List.Nonempty.foldLeft.doc : Doc +data.List.Nonempty.foldLeft.doc = + use Nat + + {{ + Folds a {type List.Nonempty} from left to right using the given function and + initial accumulator value. + + # Example + + ``` + List.Nonempty.foldLeft (+) 0 (1 +| [2, 3]) + ``` + }} + +test> data.List.Nonempty.foldLeft.tests.listConsistency = + runs 100 do + use Int - + use gen int + nel = atLeastOne int () + z = int() + op = (-) + expect + (assertEquals + (List.Nonempty.foldLeft op z nel) + (List.foldLeft op z (List.Nonempty.toList nel))) + +test> data.List.Nonempty.foldLeft.tests.multiple = + use Nat + + check + (assertEquals + (List.Nonempty.foldLeft (+) 1 (Nonempty.Nonempty 2 [3, 4])) 10) + +test> data.List.Nonempty.foldLeft.tests.single = + use Nat + + check + (assertEquals (List.Nonempty.foldLeft (+) 1 (Nonempty.Nonempty 2 [])) 3) + +data.List.Nonempty.foldMap : + (b ->{e} b ->{e} b) -> (a ->{e} b) -> List.Nonempty a ->{e} b +data.List.Nonempty.foldMap semigroup f = cases + Nonempty.Nonempty a as -> + List.foldLeft (acc x -> semigroup acc (f x)) (f a) as + +data.List.Nonempty.foldMap.doc : Doc +data.List.Nonempty.foldMap.doc = + use Text ++ + {{ + Transform every value of a nonempty list with a unary function, then apply an + associative binary operator to all the results. + + # Example + + ``` + List.Nonempty.foldMap (++) Nat.toText (1 +| [2, 3]) + ``` + }} + +data.List.Nonempty.foldMap.examples.ex1 : Text +data.List.Nonempty.foldMap.examples.ex1 = + use Text ++ + List.Nonempty.foldMap (++) Nat.toText (1 +| [2, 3]) + +test> data.List.Nonempty.foldMap.test = + runs 100 do + use Nat toText + use Text ++ + x = natInOrder() + y = natInOrder() + z = natInOrder() + f = List.Nonempty.foldMap (++) toText + expect + (f (x +| [y, z]) === (toText x ++ toText y ++ toText z) + && f (x +| [y]) === (toText x ++ toText y) + && f (List.Nonempty.singleton x) === toText x) + +data.List.Nonempty.foldRight : + (a ->{e} b ->{e} b) -> b -> List.Nonempty a ->{e} b +data.List.Nonempty.foldRight f b = cases + Nonempty.Nonempty h t -> f h (List.foldRight f b t) + +data.List.Nonempty.foldRight.doc : Doc +data.List.Nonempty.foldRight.doc = + use Nat + + {{ + Folds a {type List.Nonempty} from right to left using the given function and + initial accumulator value. + + # Example + + ``` + List.Nonempty.foldRight (+) 0 (1 +| [2, 3]) + ``` + }} + +test> data.List.Nonempty.foldRight.tests.listConsistency = + runs 100 do + use Int - + use gen int + nel = atLeastOne int () + z = int() + op = (-) + expect + (assertEquals + (List.Nonempty.foldRight op z nel) + (List.foldRight op z (List.Nonempty.toList nel))) + +test> data.List.Nonempty.foldRight.tests.multiple = + use Int - + check + (assertEquals + (List.Nonempty.foldRight (-) +1 (Nonempty.Nonempty +5 [+4, +2])) + (+5 - +4 - +2 - +1)) + +test> data.List.Nonempty.foldRight.tests.single = + use Int - + check + (assertEquals (List.Nonempty.foldRight (-) +1 (Nonempty.Nonempty +3 [])) +2) + +data.List.Nonempty.head : List.Nonempty a -> a +data.List.Nonempty.head = cases Nonempty.Nonempty a _ -> a + +data.List.Nonempty.head.doc : Doc +data.List.Nonempty.head.doc = + {{ Returns the first element of a nonempty list. }} + +test> data.List.Nonempty.head.test = runs 100 do + a = natInOrder() + as = gen.listOf natInOrder () + expect (Nonempty.head (Nonempty.Nonempty a as) === a) + +data.List.Nonempty.init : List.Nonempty a -> [a] +data.List.Nonempty.init = cases + Nonempty.Nonempty a [] -> [] + Nonempty.Nonempty a (xs :+ _) -> a List.+: xs + +data.List.Nonempty.init.doc : Doc +data.List.Nonempty.init.doc = + {{ Returns all but the last element of a nonempty list. }} + +test> data.List.Nonempty.init.test = runs 100 do + a = natInOrder() + as = gen.listOf natInOrder () + expect (Nonempty.init (as |+ a) === as) + +data.List.Nonempty.join : List.Nonempty (List.Nonempty a) -> List.Nonempty a +data.List.Nonempty.join = cases + Nonempty.Nonempty (Nonempty.Nonempty a as) nels -> + Nonempty.Nonempty a (as List.++ List.flatMap List.Nonempty.toList nels) + +data.List.Nonempty.join.doc : Doc +data.List.Nonempty.join.doc = + {{ + Concatenates all the nonempty lists in a nonempty list, so that the resulting + list contains all the elements of those lists, in the order they appear in + the input. + + # Example + + ``` + Nonempty.join (1 +| [2] +| [3 +| [4, 5]]) + ``` + }} + +data.List.Nonempty.join.examples.ex1 : List.Nonempty Nat +data.List.Nonempty.join.examples.ex1 = + use Nonempty Nonempty + Nonempty.join (Nonempty (Nonempty 1 [2]) [Nonempty 3 [4, 5]]) + +test> data.List.Nonempty.join.tests.associative = runs 100 do + use Nonempty join + nels = atLeastOne (atLeastOne (atLeastOne natInOrder)) () + expect (join (join nels) === join (List.Nonempty.map join nels)) + +test> data.List.Nonempty.join.tests.unit = runs 100 do + use List.Nonempty singleton + use Nonempty join + nats = atLeastOne natInOrder () + right = join (List.Nonempty.map singleton nats) + left = join (singleton nats) + expect ((left === right && left === nats) === right === nats) + +data.List.Nonempty.last : List.Nonempty a -> a +data.List.Nonempty.last = cases + Nonempty.Nonempty a [] -> a + Nonempty.Nonempty a (xs :+ x) -> x + +data.List.Nonempty.last.doc : Doc +data.List.Nonempty.last.doc = + {{ Returns the last element of a nonempty list. }} + +test> data.List.Nonempty.last.test = runs 100 do + a = natInOrder() + as = gen.listOf natInOrder () + expect (Nonempty.last (as |+ a) === a) + +data.List.Nonempty.map : (a ->{e} b) -> List.Nonempty a ->{e} List.Nonempty b +data.List.Nonempty.map f = cases + Nonempty.Nonempty a as -> Nonempty.Nonempty (f a) (List.map f as) + +data.List.Nonempty.map.doc : Doc +data.List.Nonempty.map.doc = + {{ Applies the given function to every element of the nonempty list. }} + +test> data.List.Nonempty.map.tests.functor = runs 100 do + nats = atLeastOne natInOrder () + nats' = List.Nonempty.map id nats + expect (nats' === nats) + +data.List.Nonempty.maximum : List.Nonempty a -> a +data.List.Nonempty.maximum list = + use Nonempty Nonempty + go = cases + y, xs -> + match List.maximum xs with + Some x + | Universal.gt y x -> y + | otherwise -> x + _ -> y + match list with + Nonempty x [] -> x + Nonempty x xs -> go x xs + +data.List.Nonempty.maximum.doc : Doc +data.List.Nonempty.maximum.doc = + {{ + Returns the maximum element of a nonempty list. + + # Examples + + ``` + Nonempty.maximum (1 +| [2, 3, 4, 5]) + ``` + + ``` + Nonempty.maximum (5 +| [4, 3, 2, 1]) + ``` + + ``` + Nonempty.maximum (List.Nonempty.singleton 1) + ``` + + ``` + Nonempty.maximum (1 +| [2, 3, 4, 5, 4, 3, 2, 1]) + ``` + + # See also + + * {Nonempty.maximumBy} - Returns the maximum element of a nonempty list + using a custom comparison function. + * {Nonempty.minimum} - Returns the minimum element of a nonempty list. + * {List.maximum} - Returns the maximum element of a (possibly empty) list. + }} + +test> data.List.Nonempty.maximum.test = test.verify do + use List +: + _ = Each.range 0 100 + x = Random.nat! + xs = Random.listOf Random.nat do 10 + f = compareOn id + lhs = Nonempty.maximum (x +| xs) |> Some + rhs = List.maximum (x +: xs) + ensure (lhs === rhs) + +data.List.Nonempty.maximumBy : + (a ->{e} a ->{e} Ordering) -> List.Nonempty a ->{e} a +data.List.Nonempty.maximumBy f list = + use Nonempty Nonempty + go = cases + y, xs -> + match List.maximumBy f xs with + None -> y + Some x + | f x y === Greater -> x + | otherwise -> y + match list with + Nonempty x [] -> x + Nonempty x xs -> go x xs + +data.List.Nonempty.maximumBy.doc : Doc +data.List.Nonempty.maximumBy.doc = + use Universal ordering + {{ + Returns the maximum element of a nonempty list using a custom comparison + function. + + # Examples + + ``` + Nonempty.maximumBy ordering (1 +| [2, 3, 4, 5]) + ``` + + ``` + Nonempty.maximumBy (compose2 Ordering.inverse ordering) (1 +| [2, 3, 4, 5]) + ``` + + ``` + Nonempty.maximumBy ordering (List.Nonempty.singleton 1) + ``` + + ``` + Nonempty.maximumBy ordering (1 +| [2, 3, 4, 5, 4, 3, 2, 1]) + ``` + + # See also + + * {Nonempty.maximum} - Returns the maximum element of a nonempty list. + * {Nonempty.minimumBy} - Returns the minimum element of a nonempty list + using a custom comparison function. + * {List.maximumBy} - Returns the maximum element of a (possibly empty) list + using a custom comparison function. + }} + +test> data.List.Nonempty.maximumBy.test = test.verify do + use List +: + _ = Each.range 0 100 + x = Random.nat! + xs = Random.listOf Random.nat do 10 + f = compareOn id + lhs = Nonempty.maximumBy f (x +| xs) |> Some + rhs = List.maximumBy f (x +: xs) + ensure (lhs === rhs) + +data.List.Nonempty.minimum : List.Nonempty a -> a +data.List.Nonempty.minimum list = + use Nonempty Nonempty + go = cases + y, xs -> + match List.minimum xs with + Some x + | Universal.lt y x -> y + | otherwise -> x + _ -> y + match list with + Nonempty x [] -> x + Nonempty x xs -> go x xs + +data.List.Nonempty.minimum.doc : Doc +data.List.Nonempty.minimum.doc = + {{ + Returns the minimum element of a nonempty list. + + # Examples + + ``` + Nonempty.minimum (1 +| [2, 3, 4, 5]) + ``` + + ``` + Nonempty.minimum (5 +| [4, 3, 2, 1]) + ``` + + ``` + Nonempty.minimum (List.Nonempty.singleton 1) + ``` + + ``` + Nonempty.minimum (1 +| [2, 3, 4, 5, 4, 3, 2, 1]) + ``` + + # See also + + * {Nonempty.maximum} - Returns the maximum element of a nonempty list. + * {Nonempty.minimumBy} - Returns the minimum element of a nonempty list + using a custom comparison function. + * {List.minimum} - Returns the minimum element of a (possibly empty) list. + }} + +test> data.List.Nonempty.minimum.test = test.verify do + use List +: + _ = Each.range 0 100 + x = Random.nat! + xs = Random.listOf Random.nat do 10 + f = compareOn id + lhs = Nonempty.minimum (x +| xs) |> Some + rhs = List.minimum (x +: xs) + ensure (lhs === rhs) + +data.List.Nonempty.minimumBy : + (a ->{e} a ->{e} Ordering) -> List.Nonempty a ->{e} a +data.List.Nonempty.minimumBy f list = + use Nonempty Nonempty + go = cases + y, xs -> + match List.minimumBy f xs with + None -> y + Some x + | f x y === Less -> x + | otherwise -> y + match list with + Nonempty x [] -> x + Nonempty x xs -> go x xs + +data.List.Nonempty.minimumBy.doc : Doc +data.List.Nonempty.minimumBy.doc = + use Universal ordering + {{ + Returns the minimum element of a nonempty list using a custom comparison + function. + + # Examples + + ``` + Nonempty.minimumBy ordering (1 +| [2, 3, 4, 5]) + ``` + + ``` + Nonempty.minimumBy (compose2 Ordering.inverse ordering) (1 +| [2, 3, 4, 5]) + ``` + + ``` + Nonempty.minimumBy ordering (List.Nonempty.singleton 1) + ``` + + ``` + Nonempty.minimumBy ordering (1 +| [2, 3, 4, 5, 4, 3, 2, 1]) + ``` + + # See also + + * {Nonempty.maximumBy} - Returns the maximum element of a nonempty list + using a custom comparison function. + * {Nonempty.minimum} - Returns the minimum element of a nonempty list. + * {List.minimumBy} - Returns the minimum element of a (possibly empty) list + using a custom comparison function. + }} + +test> data.List.Nonempty.minimumBy.test = test.verify do + use List +: + _ = Each.range 0 100 + x = Random.nat! + xs = Random.listOf Random.nat do 10 + f = compareOn id + lhs = Nonempty.minimumBy f (x +| xs) |> Some + rhs = List.minimumBy f (x +: xs) + ensure (lhs === rhs) + +data.List.Nonempty.prependList : [a] -> List.Nonempty a -> List.Nonempty a +data.List.Nonempty.prependList = cases + [], xs -> xs + x +: xs, Nonempty.Nonempty y ys -> + Nonempty.Nonempty x (xs List.++ (y List.+: ys)) + +data.List.Nonempty.prependList.doc : Doc +data.List.Nonempty.prependList.doc = + {{ + Prepends a list to a non-empty list. + + # Examples + + ``` + prependList [1, 2] (3 +| [4]) + ``` + + ``` + prependList [] (3 +| [4]) + ``` + }} + +data.List.Nonempty.randomChoice : List.Nonempty a ->{Random} a +data.List.Nonempty.randomChoice l = + randomIndex = Random.natIn 0 (List.Nonempty.size l) + Nonempty.at randomIndex l + |> getOrBug "List.Nonempty.randomChoice: index out of bounds" + +data.List.Nonempty.randomChoice.doc : Doc +data.List.Nonempty.randomChoice.doc = + use List.Nonempty randomChoice + use Nonempty Nonempty + {{ + Returns a random element from the given {type List.Nonempty}. + + # Examples + + ``` + lcg 4096 do randomChoice (Nonempty 0 [1, 3, 5, 7, 9]) + ``` + + ``` + lcg 2510 do randomChoice (Nonempty 0 [1, 3, 5, 7, 9]) + ``` + }} + +test> data.List.Nonempty.randomChoice.test = test.verify do + list = Nonempty.Nonempty 0 [1, 2, 3, 4, 5, 6, 7, 8, 9] + set = List.toSet [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + Each.repeat 1000 + e = List.Nonempty.randomChoice list + ensure (Set.contains e set) + +data.List.Nonempty.reduceLeft : (a ->{e} a ->{e} a) -> List.Nonempty a ->{e} a +data.List.Nonempty.reduceLeft f = cases + Nonempty.Nonempty a as -> List.foldLeft f a as + +data.List.Nonempty.reduceLeft.doc : Doc +data.List.Nonempty.reduceLeft.doc = + use Nat / + {{ + Applies a binary operator to all elements of the nonempty list, associating + to the left. + + Contrast with {reduceRight} which associates to the right. + + # Example + + ``` + reduceLeft (/) (10 +| [5, 2]) + ``` + + This is equivalent to: + + ``` + 10 / 5 / 2 + ``` + }} + +test> data.List.Nonempty.reduceLeft.test = + runs 100 do + use Nat - + x = natInOrder() + y = natInOrder() + z = natInOrder() + expect + (reduceLeft (-) (x +| [y, z]) === x - y - z + && reduceLeft (-) (x +| [y]) === x - y + && reduceLeft (-) (List.Nonempty.singleton x) === x) + +data.List.Nonempty.reduceRight : (a ->{e} a ->{e} a) -> List.Nonempty a ->{e} a +data.List.Nonempty.reduceRight f as = + List.foldRight f (Nonempty.last as) (Nonempty.init as) + +data.List.Nonempty.reduceRight.doc : Doc +data.List.Nonempty.reduceRight.doc = + use Nat / + {{ + Applies a binary operator to all elements of the nonempty list, associating + to the right. + + Contrast with {reduceLeft} which associates to the left. + + # Example + + ``` + reduceRight (/) (10 +| [5, 2]) + ``` + + This is equivalent to: + + ``` + 10 / 5 / 2 + ``` + }} + +test> data.List.Nonempty.reduceRight.test = + runs 100 do + use Nat - + x = natInOrder() + y = natInOrder() + z = natInOrder() + expect + (reduceRight (-) (x +| [y, z]) === x - y - z + && reduceRight (-) (x +| [y]) === x - y + && reduceRight (-) (List.Nonempty.singleton x) === x) + +data.List.Nonempty.reverse : List.Nonempty a -> List.Nonempty a +data.List.Nonempty.reverse = cases + Nonempty.Nonempty a as -> List.reverse as |+ a + +data.List.Nonempty.reverse.doc : Doc +data.List.Nonempty.reverse.doc = + {{ + Reverses a {type List.Nonempty} list. + + # Example + + ``` + Nonempty.reverse (Nonempty.Nonempty 1 [2, 3, 4]) + ``` + }} + +data.List.Nonempty.scanLeft : + (a ->{e} a ->{e} a) -> List.Nonempty a ->{e} List.Nonempty a +data.List.Nonempty.scanLeft f = cases + Nonempty.Nonempty x xs -> List.scanLeft f x xs + +data.List.Nonempty.scanLeft.doc : Doc +data.List.Nonempty.scanLeft.doc = + use Nat + / == > + use Nonempty scanLeft + {{ + `` scanLeft f xs `` applies the function `f` to the first two elements of + `xs`, then applies `f` to the result and the next element of `xs`, and so on. + Returns the list of intermediate results, with the final result as the last + element of the output. The first element of the output list is always the + same as the first element of the input list. + + Note that the last element of the output list will equal ``reduceLeft f xs``. + + See also {scanLeft} which scans the list in the other direction. + + # Examples + + ``` + scanLeft (+) (1 +| [2, 3, 4]) + ``` + + ``` + scanLeft (/) (64 +| [4, 2, 8]) + ``` + + ``` + scanLeft (/) (List.Nonempty.singleton 12) + ``` + + ``` + scanLeft Boolean.and (3 > 1 +| [3 > 2, 4 > 6, 5 == 5]) + ``` + + ``` + scanLeft Universal.max (3 +| [6, 12, 4, 55, 11]) + ``` + + ``` + scanLeft (x y -> (x + y) / 2) (3 +| [5, 10, 5]) + ``` + }} + +test> data.List.Nonempty.scanLeft.test = + runs 100 do + use Int - + use Nonempty Nonempty + use gen int + x = int() + y = int() + z = int() + expect + (Nonempty.scanLeft (-) (Nonempty x [y, z]) + === Nonempty x [x - y, x - y - z]) + +data.List.Nonempty.scanRight : + (a ->{e} a ->{e} a) -> List.Nonempty a ->{e} List.Nonempty a +data.List.Nonempty.scanRight f = cases + Nonempty.Nonempty x (ys :+ y) -> List.scanRight f y (x List.+: ys) + xs -> xs + +data.List.Nonempty.scanRight.doc : Doc +data.List.Nonempty.scanRight.doc = + use Nat + / == > + use Nonempty scanRight + {{ + `` scanRight f xs `` applies the function `f` to the last two elements of + `xs`, then applies `f` to the result and the previous element of `xs`, and so + on. Returns the list of intermediate results, with the final result at the + head. The last element of the output list is always the same as the last + element of the input list. + + Note that the first element of the output list will equal + ``reduceRight f xs``. + + See also {scanRight} which scans the list in the other direction. + + # Examples + + ``` + scanRight (+) (1 +| [2, 3, 4]) + ``` + + ``` + scanRight (/) (32 +| [16, 8, 2]) + ``` + + ``` + scanRight (/) (List.Nonempty.singleton 12) + ``` + + ``` + scanRight Boolean.and (3 > 1 +| [3 > 2, 4 > 6, 5 == 5]) + ``` + + ``` + scanRight Universal.max (3 +| [6, 12, 4, 55, 11]) + ``` + + ``` + scanRight (x y -> (x + y) / 2) (3 +| [5, 10, 5]) + ``` + }} + +test> data.List.Nonempty.scanRight.test = + runs 100 do + use Int - + use Nonempty Nonempty + use gen int + x = int() + y = int() + z = int() + expect + (Nonempty.scanRight (-) (Nonempty x [y, z]) + === Nonempty (x - y - z) [y - z, z]) + +data.List.Nonempty.sequenceOptional : + List.Nonempty (Optional a) -> Optional (List.Nonempty a) +data.List.Nonempty.sequenceOptional = cases + Nonempty.Nonempty None _ -> None + Nonempty.Nonempty (Some a) as -> + Optional.map2 (+|) (Some a) (List.sequenceOptional as) + +data.List.Nonempty.sequenceOptional.doc : Doc +data.List.Nonempty.sequenceOptional.doc = + use Nonempty sequenceOptional + {{ + {sequenceOptional} transforms a {type List.Nonempty} of {type Optional} + values into an {type Optional} of {type List.Nonempty} of values so that: + + * If one of the values is {None} then the whole result is {None}. + * Otherwise all the values are collected into ``Some values``. + + # Examples + + ``` + sequenceOptional (Some 1 +| [None, Some 3]) + ``` + + ``` + sequenceOptional (Some 1 +| [Some 2, Some 3]) + ``` + }} + +test> data.List.Nonempty.sequenceOptional.test.example = test.verify do + use Nonempty sequenceOptional + labeled "with some Nones" do + actual = sequenceOptional (Some 1 +| [None, Some 3]) + ensureEqual actual None + labeled "with no Nones" do + actual = sequenceOptional (Some 1 +| [Some 2, Some 3]) + ensureEqual actual (Some (1 +| [2, 3])) + +data.List.Nonempty.singleton : a -> List.Nonempty a +data.List.Nonempty.singleton a = Nonempty.Nonempty a [] + +data.List.Nonempty.singleton.doc : Doc +data.List.Nonempty.singleton.doc = + {{ Constructs a nonempty list with just the given element. }} + +test> data.List.Nonempty.singleton.test = runs 100 do + n = natInOrder() + expect (List.Nonempty.size (List.Nonempty.singleton n) === 1) + +data.List.Nonempty.size : List.Nonempty a -> Nat +data.List.Nonempty.size = cases Nonempty.Nonempty _ as -> List.size as Nat.+ 1 + +data.List.Nonempty.size.doc : Doc +data.List.Nonempty.size.doc = + {{ Returns the number of elements in the given nonempty list. }} + +test> data.List.Nonempty.size.test = + runs 100 do + use Nat + + units = atLeastOne (do ()) () + expect + (List.Nonempty.size units + === List.foldLeft + (+) 0 (List.Nonempty.toList (List.Nonempty.map (const 1) units))) + +data.List.Nonempty.snoc : List.Nonempty a -> a -> List.Nonempty a +data.List.Nonempty.snoc = cases + Nonempty.Nonempty head middle -> + last -> Nonempty.Nonempty head (middle List.:+ last) + +data.List.Nonempty.snoc.doc : Doc +data.List.Nonempty.snoc.doc = + {{ Adds the given element to the end of the nonempty list. }} + +data.List.Nonempty.somes : + List.Nonempty (Optional a) -> Optional (List.Nonempty a) +data.List.Nonempty.somes = cases + Nonempty.Nonempty None [] -> None + Nonempty.Nonempty None (a +: as) -> + data.List.Nonempty.somes (Nonempty.Nonempty a as) + Nonempty.Nonempty (Some a) as -> Some (Nonempty.Nonempty a (List.somes as)) + +data.List.Nonempty.somes.doc : Doc +data.List.Nonempty.somes.doc = + use Nonempty somes + {{ + @signature{somes} + + `` somes xs `` flattens a {type List.Nonempty} of {type Optional} values + returning a non-empty list of the values present within the {type Optional} + values in `xs`. If the initial list contains only {None} values then {None} + is returned. + + # Example + + ``` + somes (Some 1 +| [Some 2, None, Some 3, None]) + ``` + }} + +test> data.List.Nonempty.somes.tests.ex1 = + test.verify do + use Nonempty somes + ensureEqual + (somes (Some 1 +| [Some 2, None, Some 3, None])) (Some (1 +| [2, 3])) + ensureEqual (somes (None +| [None, None])) None + +data.List.Nonempty.tail : List.Nonempty a -> [a] +data.List.Nonempty.tail = cases Nonempty.Nonempty _ as -> as + +data.List.Nonempty.tail.doc : Doc +data.List.Nonempty.tail.doc = + {{ Returns all but the first element of a nonempty list. }} + +test> data.List.Nonempty.tail.test = runs 100 do + a = natInOrder() + as = gen.listOf natInOrder () + expect (Nonempty.tail (Nonempty.Nonempty a as) === as) + +data.List.Nonempty.toList : List.Nonempty a -> [a] +data.List.Nonempty.toList = cases Nonempty.Nonempty a as -> a List.+: as + +data.List.Nonempty.toList.doc : Doc +data.List.Nonempty.toList.doc = + {{ Turns a nonempty list into an ordinary {type List}. }} + +data.List.Nonempty.toSet : List.Nonempty a -> Set a +data.List.Nonempty.toSet = cases + Nonempty.Nonempty x xs -> Set.insert x (List.toSet xs) + +data.List.Nonempty.toSet.doc : Doc +data.List.Nonempty.toSet.doc = + {{ Converts a {type List.Nonempty} to its {type Set} of distinct elements. }} + +data.List.Nonempty.traverseOptional : + (a ->{g} Optional b) -> List.Nonempty a ->{g} Optional (List.Nonempty b) +data.List.Nonempty.traverseOptional f = cases + Nonempty.Nonempty a [] -> Optional.map List.Nonempty.singleton (f a) + Nonempty.Nonempty a (a1 +: as) -> + Optional.map2 + Nonempty.cons + (f a) + (data.List.Nonempty.traverseOptional f (Nonempty.Nonempty a1 as)) + +data.List.Nonempty.traverseOptional.doc : Doc +data.List.Nonempty.traverseOptional.doc = + use Nat + isEven + use Nonempty traverseOptional + {{ + {traverseOptional} applies a function to each element of a + {type List.Nonempty}. If any of the results are {None} then the whole result + is {None}. Otherwise all the results are collected into ``Some results``. + + # Examples + + ``` + traverseOptional + (n -> (if isEven n then Some (n + 1) else None)) (1 +| [2, 3]) + ``` + + ``` + traverseOptional + (n -> (if isEven n then Some (n + 1) else None)) (2 +| [4, 6]) + ``` + }} + +test> data.List.Nonempty.traverseOptional.test.example = test.verify do + use Nat + + use Nonempty traverseOptional + f n = if Nat.isEven n then Some (n + 1) else None + labeled "with some Nones" do + actual = traverseOptional f (1 +| [2, 3]) + ensureEqual actual None + labeled "with no Nones" do + actual = traverseOptional f (2 +| [4, 6]) + ensureEqual actual (Some (3 +| [5, 7])) + +data.List.Nonempty.zipWith : + (a ->{g1} b ->{g} c) + -> List.Nonempty a + -> List.Nonempty b + ->{g1, g} List.Nonempty c +data.List.Nonempty.zipWith f = cases + Nonempty.Nonempty x xs, Nonempty.Nonempty y ys -> + f x y +| List.zipWith f xs ys + +data.List.Nonempty.zipWith.doc : Doc +data.List.Nonempty.zipWith.doc = + use Nat + + use Nonempty zipWith + use Tuple pair + {{ + Apply a function to corresponding elements of two nonempty lists, producing a + nonempty list of the results. The output list is the same length as the + shorter of the two input lists. Each element of the output list is the result + of applying the function to the elements of the input lists at the same + position. If one of the input lists is shorter than the other, the extra + elements of the longer list are ignored. + + # Examples + + ``` + xs = 1 +| [2, 3, 4, 5] + ys = 7 +| [8, 9, 10, 11] + zipWith (+) xs ys + ``` + + The length of the output list is the length of the shorter input list: + + ``` + xs = 1 +| [2, 3] + ys = 7 +| [8, 9, 10, 11] + zipWith pair xs ys + ``` + + ``` + xs = 1 +| [2, 3, 4, 5] + ys = 7 +| [8, 9] + zipWith pair xs ys + ``` + }} + +(data.List.Nonempty.|+) : [a] -> a -> List.Nonempty a +(data.List.Nonempty.|+) = cases + [] -> a -> Nonempty.Nonempty a [] + x +: xs -> a -> Nonempty.Nonempty x (xs List.:+ a) + +data.List.Nonempty.|+.doc : Doc +data.List.Nonempty.|+.doc = + {{ + Constructs a {type List.Nonempty} from a {type List} for the {Nonempty.init} + and an element for the {Nonempty.last}. + + # Example + + ``` + [1, 2] |+ 3 + ``` + }} + +data.List.nonEmptySubsequences : [a] -> [List.Nonempty a] +data.List.nonEmptySubsequences list = + use List +: Nonempty + f : a -> Nonempty a -> [Nonempty a] -> [Nonempty a] + f x ys r = ys +: (Nonempty.cons x ys +: r) + match list with + [] -> [] + x +: xs -> + List.Nonempty.singleton x + +: List.foldRight (f x) [] (data.List.nonEmptySubsequences xs) + +data.List.nonEmptySubsequences.doc : Doc +data.List.nonEmptySubsequences.doc = + {{ + Returns the list of all subsequences of the argument, which have at least one + element. + }} + +test> data.List.nonEmptySubsequences.tests.base = + use List.Nonempty singleton + check + (nonEmptySubsequences [1, 2, 3] + === [ singleton 1 + , singleton 2 + , 1 +| [2] + , singleton 3 + , 1 +| [3] + , 2 +| [3] + , 1 +| [2, 3] + ]) + +data.List.of : x -> Nat -> [x] +data.List.of = flip List.fill + +data.List.of.doc : Doc +data.List.of.doc = + use List of + {{ + `` of x sz `` creates a new {type List} of size `sz`, filled with the value + `x`. + + # Example + + ``` + of 0 4 + ``` + + # See also + + * {List.fill} for a version of this that takes the arguments in the + opposite order. + }} + +data.List.partition : (a ->{g} Boolean) -> [a] ->{g} ([a], [a]) +data.List.partition p = + use List +: + go = cases + (accTrue, accFalse) -> + cases + [] -> (accTrue, accFalse) + x +: xs -> + if p x then go (x +: accTrue, accFalse) xs + else go (accTrue, x +: accFalse) xs + go ([], []) + +data.List.partition.doc : Doc +data.List.partition.doc = + {{ + Partitions a list into two lists, one containing the elements that satisfy + the given predicate, and one containing the elements that don't. + + # Example + + ``` + List.partition Nat.isEven [1, 2, 3, 4, 5] + ``` + }} + +data.List.partitionBy : (a ->{g} Boolean) -> [a] ->{g} [List.Nonempty a] +data.List.partitionBy f = groupSublistsBy (a b -> iff (f a) (f b)) + +data.List.partitionBy.doc : Doc +data.List.partitionBy.doc = + {{ + Partitions a list into sublists, where each sublist contains elements that + are equal according to the given function. + + # Example + + ``` + List.map + List.Nonempty.toList (partitionBy Nat.isEven [5, 2, 4, 4, 2, 6, 7, 7, 8]) + ``` + }} + +data.List.partitionEithers : [Either a b] -> ([a], [b]) +data.List.partitionEithers = + use List +: + List.foldRight + (cases + Left a, (lefts, rights) -> (a +: lefts, rights) + Right b, (lefts, rights) -> (lefts, b +: rights)) ([], []) + +data.List.partitionEithers.doc : Doc +data.List.partitionEithers.doc = + {{ + Partition the elements in a list, accumulating them into a tuple containing a + list of the {Left}s and a list of the {Right}s + + # Example + + ``` + partitionEithers [Left 2, Right "Hello", Left 10, Right "Hi again"] + ``` + }} + +test> data.List.partitionEithers.tests.ex1 = + check + let + actual = + partitionEithers [Left 2, Right "Hello", Left 10, Right "Hello again"] + expected = ([2, 10], ["Hello", "Hello again"]) + assert (actual === expected) ("Not equal!", actual, expected) true + +data.List.partitionMap : (a ->{g} Either b c) -> [a] ->{g} ([b], [c]) +data.List.partitionMap f = + use List +: + go = cases + (accLeft, accRight) -> + cases + [] -> (accLeft, accRight) + x +: xs -> + match f x with + Left l -> go (l +: accLeft, accRight) xs + Right r -> go (accLeft, r +: accRight) xs + go ([], []) + +data.List.partitionMap.doc : Doc +data.List.partitionMap.doc = + {{ + Partitions a list into two lists, one containing the elements that are {Left} + values, and one containing the elements that are {Right} values (after + applying the given function). + + # Example + + ``` + partitionMap + (cases + 0 -> Left "zero" + n -> Right n) [0, 1, 2, 3, 4, 5] + ``` + }} + +data.List.powerslice : [a] -> [[a]] +data.List.powerslice l = + use List +: + [] +: List.flatMap (dropLast << List.tails) (inits l) + +data.List.powerslice.doc : Doc +data.List.powerslice.doc = + {{ + `` powerslice xs `` returns a list of all contiguous sub-lists of the list + `xs`. + + # Examples + + ``` + powerslice [1, 2, 3] + ``` + + ``` + powerslice [] + ``` + }} + +data.List.powerslice.examples.ex1 : [[Nat]] +data.List.powerslice.examples.ex1 = powerslice [1, 2, 3] + +data.List.powerslice.examples.ex2 : [[a]] +data.List.powerslice.examples.ex2 = powerslice [] + +test> data.List.powerslice.tests.prop1 = + go _ = + use List ++ + use gen listOf + a = listOf natInOrder () + b = listOf natInOrder () + c = listOf natInOrder () + expect (List.contains b (powerslice (a ++ b ++ c))) + runs 100 go + +test> data.List.powerslice.tests.test1 = + check + (powerslice [1, 2, 3] === [[], [1], [1, 2], [2], [1, 2, 3], [2, 3], [3]]) + +data.List.randomChoice : [a] ->{Exception, Random} a +data.List.randomChoice list = + randomIndex = Random.natIn 0 (List.size list) + List.at randomIndex list + |> Optional.toException "List.randomChoice: empty List" (typeLink List) + +data.List.randomChoice.doc : Doc +data.List.randomChoice.doc = + use List randomChoice + {{ + Picks a random element from the given {type List}. Assumes that the + {type List} is not empty, so an empty {type List} will cause a runtime + exception. + + # Examples + + ``` + catch do lcg 4096 do randomChoice [0, 3, 5, 7] + ``` + + ``` + catch do lcg 2510 do randomChoice [?x, ?y, ?z] + ``` + + ``` + catch do lcg 128 do randomChoice [char.digit, hex] () + ``` + }} + +test> data.List.randomChoice.test = test.verify do + list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + set = List.toSet list + Each.repeat 1000 + e = List.randomChoice list + ensure (Set.contains e set) + +data.List.range.doc : Doc +data.List.range.doc = + {{ + {{ docExample 2 do min max -> List.range min max }} creates a list of + {type Nat} values from `min` to `max` (excluding `max`). + + Returns the empty list if the `min` value is less than or equal to the `max` + value. + + # Examples + + ``` + List.range 1 10 + ``` + + ``` + List.range 0 0 + ``` + + ## See also + + {List.rangeClosed} generates a range inclusive of the upper limit. + + {Int.range} and {Int.rangeClosed} create ranges on {type Int}. + }} + +test> data.List.rangeClosed.tests.empty = _checkRangeClosed 1 0 [] + +test> data.List.rangeClosed.tests.empty2 = _checkRangeClosed 2 1 [] + +test> data.List.rangeClosed.tests.empty3 = _checkRangeClosed 10 9 [] + +test> data.List.rangeClosed.tests.fromBig = + _checkRangeClosed 99 101 [99, 100, 101] + +test> data.List.rangeClosed.tests.fromOne = _checkRangeClosed 1 3 [1, 2, 3] + +test> data.List.rangeClosed.tests.fromZero = _checkRangeClosed 0 2 [0, 1, 2] + +test> data.List.rangeClosed.tests.rangeSize = runs 100 do + use Int >= + use List rangeClosed + use Nat + == > + x = natInOrder() + y = natInOrder() + if x > y then expect (List.size (rangeClosed x y) == 0) + else + diff = subtractToInt y x + size = Int.toRepresentation diff + 1 + expect (List.size (rangeClosed x y) == size && diff >= +0) + +test> data.List.rangeClosed.tests.single0 = _checkRangeClosed 0 0 [0] + +test> data.List.rangeClosed.tests.single1 = _checkRangeClosed 1 1 [1] + +test> data.List.rangeClosed.tests.single99 = _checkRangeClosed 99 99 [99] + +data.List.rangeClosed.tests._checkRangeClosed : Nat -> Nat -> [Nat] -> [Result] +data.List.rangeClosed.tests._checkRangeClosed low high res = + check (List.rangeClosed low high === res) + +data.List.replace : Nat -> a -> [a] -> [a] +data.List.replace i a as = + use List ++ + use Nat + + List.take i as ++ [a] ++ List.drop (i + 1) as + +data.List.replace.doc : Doc +data.List.replace.doc = + {{ + Replaces the element at the given index in the {type List} with the given + element. + + # Examples + + ``` + replace 1 ?b [?a, ?c, ?d] + ``` + + ``` + replace 0 ?a [?b, ?c, ?d] + ``` + + ``` + replace 2 ?d [?a, ?b, ?c] + ``` + + If the index is out of bounds, the element is added to the end of the + {type List}: + + ``` + replace 9 ?d [?a, ?b, ?c] + ``` + }} + +data.List.replicate : Nat -> '{e} a ->{e} [a] +data.List.replicate n op = + use Nat - + List.unfold n (n -> (if n === 0 then None else Some (op(), n - 1))) + +data.List.replicate.doc : Doc +data.List.replicate.doc = + use List replicate + use Random nat + {{ + `` replicate n op `` does `!op` `n` times, returning a list of the results. + + # Examples + + ``` + lcg 0 do replicate 5 nat + ``` + + ``` + lcg 0 do replicate 0 nat + ``` + }} + +test> data.List.replicate.test = runs 100 do + n = natInOrder() + h = cases + { ask -> k } -> handle k() with h + { a } -> a + units : [()] + units = handle List.replicate n do ask with h + expect (List.size units === n) + +data.List.replicate.test.doc : Doc +data.List.replicate.test.doc = + {{ + Tests that {List.replicate} produces effects correctly and results in a list + of the appropriate length. + }} + +data.List.reverse : [a] -> [a] +data.List.reverse as = + use List +: + List.foldLeft (acc a -> a +: acc) [] as + +data.List.reverse.doc : Doc +data.List.reverse.doc = + use List reverse + {{ + Returns a {type List} with the elements of the given {type List} in reverse + order. + + # Examples + + ``` + reverse [1, 2, 3] + ``` + + ``` + reverse [] + ``` + }} + +data.List.rights : [Either a b] -> [b] +data.List.rights = at2 << partitionEithers + +data.List.rights.doc : Doc +data.List.rights.doc = + {{ + Accumulate all of the elements that are {Right} in a list of {type Either}s + + # Example + + ``` + rights [Left 2, Right "Hello", Left 10, Right "Hi again"] + ``` + }} + +test> data.List.rights.tests.ex1 = + check + let + actual = rights [Left 2, Right "Hello", Left 10, Right "Hello again"] + expected = ["Hello", "Hello again"] + assert (actual === expected) ("Not equal!", actual, expected) true + +data.List.scanLeft : (b ->{e} a ->{e} b) -> b -> [a] ->{e} List.Nonempty b +data.List.scanLeft f initial values = + go acc f q = cases + [] -> acc + x +: xs -> + next = f q x + go (Nonempty.snoc acc next) f next xs + go (List.Nonempty.singleton initial) f initial values + +data.List.scanLeft.doc : Doc +data.List.scanLeft.doc = + use List scanLeft + use Nat * + / + {{ + `` scanLeft f z xs `` applies the function `f` to `z` and the first element + of `xs`, then applies `f` to the result and the next element of `xs`, and so + on. Returns the list of intermediate results. + + See also {List.scanRight} which scans the list in the other direction. + + # Properties + + * The final result (the last element of the output) is + ``List.foldLeft f z xs``. + * The first element of the output list is always `z`. + * The output list (a {type List.Nonempty}) is always 1 longer than the + input list. + * `` scanLeft f 0 [1, 2, 3] `` = {{ + docExample 1 do f -> 0 +| [f 0 1, f (f 0 1) 2, f (f (f 0 1) 2) 3] }} + + # Examples + + ``` + scanLeft (/) 128 [8, 4, 2] + ``` + + ``` + scanLeft (/) 3 [] + ``` + + ``` + scanLeft Nat.min 5 [5, 2, 4, 4, 2, 6] + ``` + + ``` + scanLeft Nat.max 5 [1, 2, 3, 4, 5, 6, 7] + ``` + + ``` + scanLeft (x y -> 2 * x + y) 4 [1, 2, 3] + ``` + }} + +data.List.scanLeft.examples.ex1 : List.Nonempty Nat +data.List.scanLeft.examples.ex1 = + use Nat / + List.scanLeft (/) 64 [4, 2, 4] + +data.List.scanLeft.examples.ex2 : List.Nonempty Nat +data.List.scanLeft.examples.ex2 = + use Nat / + List.scanLeft (/) 3 [] + +data.List.scanLeft.examples.ex3 : List.Nonempty Nat +data.List.scanLeft.examples.ex3 = List.scanLeft Universal.max 5 [1, 2, 3, 4] + +data.List.scanLeft.examples.ex4 : List.Nonempty Nat +data.List.scanLeft.examples.ex4 = + List.scanLeft Universal.max 5 [1, 2, 3, 4, 5, 6, 7] + +data.List.scanLeft.examples.ex5 : List.Nonempty Nat +data.List.scanLeft.examples.ex5 = + use Nat * + + List.scanLeft (x y -> 2 * x + y) 4 [1, 2, 3] + +test> data.List.scanLeft.test = + runs 100 do + use Nat + + xs = gen.listOf natInOrder () + expect + (List.scanLeft (x y -> y + 1) 0 xs === (0 +| List.map (x -> x + 1) xs)) + +data.List.scanRight : (a ->{e} b ->{e} b) -> b -> [a] ->{e} List.Nonempty b +data.List.scanRight f initial values = + use List Nonempty + go : Nonempty b -> (a ->{e} b ->{e} b) -> b -> [a] -> Nonempty b + go acc f q = cases + [] -> acc + xs :+ x -> + next = f x q + go (Nonempty.cons next acc) f next xs + go (List.Nonempty.singleton initial) f initial values + +data.List.scanRight.doc : Doc +data.List.scanRight.doc = + use List scanRight + use Nat * + / + {{ + `` scanRight f z xs `` applies the function `f` to the last element of `xs` + and `z`, then applies `f` to the result and the previous element of `xs`, and + so on. Returns the list of intermediate results. + + See also {List.scanLeft} which scans the list in the other direction. + + # Properties + + * The final result (the first element of the output) is + ``List.foldRight f z xs``. + * The last element of the output list is always `z`. + * The output list (a {type List.Nonempty}) is always 1 longer than the + input list. + * `` scanRight f 0 [1, 2, 3] `` = {{ + docExample 1 do f -> f 1 (f 2 (f 3 0)) +| [f 2 (f 3 0), f 3 0, 0] }} + + # Examples + + ``` + scanRight (/) 2 [128, 32, 8] + ``` + + ``` + scanRight (/) 3 [] + ``` + + ``` + scanRight Nat.min 5 [5, 2, 4, 4, 2, 6] + ``` + + ``` + scanRight Nat.max 5 [1, 2, 3, 4, 5, 6, 7] + ``` + + ``` + scanRight (x y -> 2 * x + y) 4 [1, 2, 3] + ``` + }} + +data.List.scanRight.examples.ex1 : List.Nonempty Nat +data.List.scanRight.examples.ex1 = + use Nat + + List.scanRight (+) 5 [1, 2, 3, 4] + +data.List.scanRight.examples.ex2 : List.Nonempty Nat +data.List.scanRight.examples.ex2 = + use Nat / + List.scanRight (/) 2 [8, 12, 24, 4] + +data.List.scanRight.examples.ex3 : List.Nonempty Nat +data.List.scanRight.examples.ex3 = + use Nat / + List.scanRight (/) 3 [] + +data.List.scanRight.examples.ex4 : List.Nonempty Boolean +data.List.scanRight.examples.ex4 = + use Universal gt + List.scanRight Boolean.and true [gt 1 2, gt 3 2, 5 === 5] + +data.List.scanRight.examples.ex5 : List.Nonempty Nat +data.List.scanRight.examples.ex5 = + List.scanRight Universal.max 18 [3, 6, 12, 4, 55, 11] + +test> data.List.scanRight.test = + runs 100 do + use Nat + + xs = gen.listOf natInOrder () + expect + (List.scanRight (x y -> x + 1) 0 xs === (List.map (x -> x + 1) xs |+ 0)) + +data.List.sequenceOptional : [Optional a] -> Optional [a] +data.List.sequenceOptional = cases + [] -> None + [Some a] -> Some [a] + [None] -> None + Some a +: as -> Some (as -> a List.+: as) <*> data.List.sequenceOptional as + None +: as -> None + +data.List.sequenceOptional.doc : Doc +data.List.sequenceOptional.doc = + use List sequenceOptional + {{ + {sequenceOptional} transforms a {type List} of {type Optional} values into an + {type Optional} of {type List} of values so that: + + * If one of the values is {None} then the whole result is {None}. + * Otherwise all the values are collected into ``Some values``. + + # Examples + + ``` + sequenceOptional [Some 1, None, Some 3] + ``` + + ``` + sequenceOptional [Some 1, Some 2, Some 3] + ``` + }} + +data.List.singleton : a -> [a] +data.List.singleton x = [x] + +data.List.singleton.doc : Doc +data.List.singleton.doc = + {{ + `` List.singleton x `` is equivalent to `[x]`, a list with the single element + `x`. + }} + +-- builtin data.List.size : [a] -> Nat + +data.List.size.doc : Doc +data.List.size.doc = + use List size + {{ + Get the number of elements in a {type List}. + + # Examples + + ``` + size [1, 2, 3, 4, 5] + ``` + + ``` + size [] + ``` + }} + +data.List.skip : Nat -> [a] -> [a] +data.List.skip n as = + use List :+ + go acc = cases + x +: xs -> go (acc :+ x) (List.drop n xs) + [] -> acc + go [] as + +data.List.skip.doc : Doc +data.List.skip.doc = + {{ + Skips elements of a {type List}. Returns the first element, then skips `n` + elements, then returns the next element, and so on. + + # Examples + + ``` + skip 2 [1, 2, 3, 4, 5] + ``` + + ``` + skip 1 [1, 2, 3, 4, 5] + ``` + + ``` + skip 0 [1, 2, 3, 4, 5] + ``` + }} + +data.List.slice : Nat -> Nat -> [a] -> [a] +data.List.slice start stopExclusive s = + use Nat - + List.take (stopExclusive - start) (List.drop start s) + +data.List.slice.doc : Doc +data.List.slice.doc = + use List slice + {{ + Returns a slice of a {type List} from the given start index (inclusive) to + the given end index (exclusive). If the start index is greater than the end + index, the result is the empty {type List}. + + # Examples + + ``` + slice 1 3 [1, 2, 3, 4, 5] + ``` + + ``` + slice 3 1 [1, 2, 3, 4, 5] + ``` + }} + +data.List.slidingPairs : [a] -> [(a, a)] +data.List.slidingPairs as = + pair = cases + x +: rest@(y +: _) -> Some ((x, y), rest) + _ -> None + List.unfold as pair + +data.List.slidingPairs.doc : Doc +data.List.slidingPairs.doc = + {{ + Returns a list of all adjacent character pairs in the {type List}, in the + order that they appear. + + For example: + + ``` + slidingPairs [1, 2, 3, 4, 5] + ``` + + # See also + + * {slidingWindow} + }} + +test> data.List.slidingPairs.tests.concat = runs 100 do + use List map + use Nat - + use Text == + t = Text.ascii() + s = Text.size t + ps = slidingPairs (toCharList t) + fst = fromCharList (map at1 ps) + snd = fromCharList (map at2 ps) + expect (fst == Text.take (s - 1) t && snd == Text.drop 1 t) + +data.List.slidingWindow : Nat -> [a] -> [[a]] +data.List.slidingWindow length xs = + use List :+ + use Nat + - == + if length == 0 then bug "Length of window must not be zero." + else + count = List.size xs + 1 - length + go acc xs = cases + 0 -> acc + n -> + window = List.take length xs + rest = List.drop 1 xs + go (acc :+ window) rest (n - 1) + go [] xs count + +data.List.slidingWindow.doc : Doc +data.List.slidingWindow.doc = + {{ + Returns a list of sliding windows containing elements drawn from the input + list. Each window is a list with the given length. + + {{ docCallout None {{ The length must not be zero. }} }} + + # Examples + + ``` + slidingWindow 1 [1, 2, 3, 4, 5] + ``` + + ``` + slidingWindow 3 [1, 2, 3, 4, 5] + ``` + + ``` + slidingWindow 5 [1, 2, 3, 4, 5] + ``` + + ``` + slidingWindow 6 [1, 2, 3, 4, 5] + ``` + + ``` + slidingWindow 1 [] + ``` + + # See also + + * {slidingPairs} + }} + +test> data.List.slidingWindow.tests.ex1 = + check (slidingWindow 1 [1, 2, 3, 4, 5] === [[1], [2], [3], [4], [5]]) + +test> data.List.slidingWindow.tests.ex2 = + check (slidingWindow 3 [1, 2, 3, 4, 5] === [[1, 2, 3], [2, 3, 4], [3, 4, 5]]) + +test> data.List.slidingWindow.tests.ex3 = + check (slidingWindow 5 [1, 2, 3, 4, 5] === [[1, 2, 3, 4, 5]]) + +test> data.List.slidingWindow.tests.ex4 = + check (slidingWindow 6 [1, 2, 3, 4, 5] === []) + +test> data.List.slidingWindow.tests.ex5 = check (slidingWindow 1 [] === []) + +test> data.List.slidingWindow.tests.isSlidingPairsForLength2 = runs 100 do + x = natInOrder() + xs = gen.listOf natInOrder () + pairToList = cases (x, y) -> [x, y] + expect (slidingWindow 2 xs === (slidingPairs xs |> List.map pairToList)) + +test> data.List.snoc.tests.isReversedConsOnReversedList = runs 100 do + use List +: reverse + x = natInOrder() + xs = gen.listOf natInOrder () + expect (List.snoc xs x === (x +: (xs |> reverse) |> reverse)) + +data.List.somes : [Optional a] -> [a] +data.List.somes xs = + use List +: + List.foldRight + (v a -> (match v with + Some x -> x +: a + None -> a)) [] xs + +data.List.somes.doc : Doc +data.List.somes.doc = + use List somes + {{ + @signature{somes} + + {List.somes as} flattens a {type List} of {type Optional}s returning a list + of the values present within the {type Optional}s in `as`. + + # Example + + ``` + somes [Some 1, Some 2, None, Some 3, None] + ``` + }} + +test> data.List.somes.tests.ex1 = + check + let + actual = List.somes [None, None, Some 2, None, None, None, Some 10] + expected = [2, 10] + assert (actual === expected) ("Not equal!", actual, expected) true + +data.List.sort : [a] -> [a] +data.List.sort = sortWith Universal.lteq + +data.List.sort.doc : Doc +data.List.sort.doc = + use List sort + {{ + Sort a list of values using the universal ordering {Universal.lteq}. + + # Examples + + ``` + sort [5, 2, 4, 4, 2, 6] + ``` + + ``` + fromCharList (sort (toCharList "abracadabra")) + ``` + }} + +test> data.List.sort.test = test.verify do + use List all contains size + use Nat == + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 0 100 + sorted = List.sort xs + ensure (isSortedBy Universal.lteq sorted) + ensure (all (x -> contains x xs) sorted) + ensure (all (x -> contains x sorted) xs) + ensure (size sorted == size xs) + +data.List.sortBy : (a ->{e} b) -> [a] ->{e} [a] +data.List.sortBy f as = + use List map + tweak = cases (p1, p2) -> (f p1, p2, p1) + List.sort (map tweak (List.indexed as)) |> map at3 + +data.List.sortBy.doc : Doc +data.List.sortBy.doc = + {{ + Sorts a list by the result of a function applied to each element. + + This function is stable, meaning that the relative order of elements that + compare equal is preserved. + + # Examples + + ``` + sortBy id [3, 2, 1] + ``` + + ``` + sortBy + Text.size + ["the", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"] + ``` + }} + +data.List.sortWith : (a -> a ->{e} Boolean) -> [a] ->{e} [a] +data.List.sortWith f list = + use List ++ :+ + use data.List sortWith + merge acc = cases + [], ys -> acc ++ ys + xs, [] -> acc ++ xs + xss@(x +: xs), yss@(y +: ys) -> + if f x y then merge (acc :+ x) xs yss else merge (acc :+ y) xss ys + match list with + [] -> [] + [x] -> [x] + xs -> + (left, right) = halve xs + merge [] (sortWith f left) (sortWith f right) + +data.List.sortWith.doc : Doc +data.List.sortWith.doc = + use Nat <= >= + {{ + Sorts a {type List} according to the given comparison function. + + # Example + + ``` + sortWith (<=) [1, 3, 2] + ``` + + ``` + sortWith (>=) [1, 2, 3] + ``` + }} + +test> data.List.sortWith.tests.isOrdered = runs 100 do + use Nat <= + xs = gen.listOf natInOrder () + sorted = sortWith (<=) xs + expect (isSortedBy (<=) sorted) + +test> data.List.sortWith.tests.length = runs 100 do + use List size + use Nat <= == + xs = gen.listOf natInOrder () + sorted = sortWith (<=) xs + expect (size sorted == size xs) + +test> data.List.sortWith.tests.permutation = runs 100 do + use Nat <= + xs = gen.listOf natInOrder () + sorted = sortWith (<=) xs + expect (List.all (x -> List.contains x sorted) xs) + +data.List.span : (a ->{e} Boolean) -> [a] ->{e} ([a], [a]) +data.List.span f xs = (List.takeWhile f xs, List.dropWhile f xs) + +data.List.span.doc : Doc +data.List.span.doc = + use List span + use Nat < + {{ + `` span f xs `` returns a tuple where the first element is the longest prefix + of `xs` of elements that satisfy `f`, and the second element is the remainder + of the list. + + Equivalent to ``(List.takeWhile f xs, List.dropWhile f xs)``. + + # Example + + ``` + span (x -> x < 9) [5, 6, 7, 5, 9, 7, 6] + ``` + }} + +test> data.List.span.tests.alle = + check (List.span (flip Universal.lt 0) [1, 2, 3] === ([], [1, 2, 3])) + +test> data.List.span.tests.allf = + check (List.span (flip Universal.lt 9) [1, 2, 3] === ([1, 2, 3], [])) + +test> data.List.span.tests.middle = + check + (List.span (flip Universal.lt 3) [1, 2, 3, 4, 1, 2, 3, 4] + === ([1, 2], [3, 4, 1, 2, 3, 4])) + +data.List.split : (a ->{e} Boolean) -> [a] ->{e} [[a]] +data.List.split f = + use List :+ + go acc = cases + [] -> acc + x +: xs + | f x -> go (acc :+ []) xs + | otherwise -> + match acc with + [] -> bug "splitTailRec: impossible" + ys :+ y -> go (ys :+ (y :+ x)) xs + go [[]] + +data.List.split.doc : Doc +data.List.split.doc = + use List split + {{ + `` split p xs `` splits the {type List} `xs` into components delimited by + separators, where a separator is any element for which `p` returns ``true``. + The resulting components do not contain the separators. Two adjacent + separators result in an empty component in the output, as does a separator at + the end of the input. + + # Examples + + ``` + split isSpace (toCharList "one two three") + ``` + + ``` + split Nat.isEven [5, 6, 7, 5, 9, 7, 6] + ``` + }} + +test> data.List.split.tests.base = + check + (List.split ((===) 0) [1, 2, 0, 3, 4, 5, 0, 6, 7, 8] + === [[1, 2], [3, 4, 5], [6, 7, 8]]) + +test> data.List.split.tests.double = + check + (List.split ((===) 0) [0, 0, 1, 2, 3, 0, 4, 5, 0, 0, 1, 2, 3, 0, 0] + === [[], [], [1, 2, 3], [4, 5], [], [1, 2, 3], [], []]) + +test> data.List.split.tests.empty = check (List.split ((===) 0) [] === [[]]) + +data.List.splitAt : Nat -> [a] -> ([a], [a]) +data.List.splitAt i xs = (List.take i xs, List.drop i xs) + +data.List.splitAt.doc : Doc +data.List.splitAt.doc = + use List splitAt + {{ + `` splitAt n xs `` returns a tuple where the first element is the prefix of + length `n` and the second element is the remainder of `xs`. + + # Examples + + ``` + splitAt 0 [?a, ?b, ?c, ?d] + ``` + + ``` + splitAt 2 [?a, ?b, ?c, ?d] + ``` + + ``` + splitAt 9 [?a, ?b, ?c, ?d] + ``` + }} + +test> data.List.splitAt.tests.base = + check (List.splitAt 3 [1, 2, 3, 4, 5] === ([1, 2, 3], [4, 5])) + +test> data.List.splitAt.tests.ob = + check (List.splitAt 10 [1, 2] === ([1, 2], [])) + +test> data.List.splitAt.tests.zero = + check (List.splitAt 0 [1, 2] === ([], [1, 2])) + +data.List.stripPrefix : [a] -> [a] -> Optional [a] +data.List.stripPrefix prefix list = + (p, s) = List.splitAt (List.size prefix) list + if p === prefix then Some s else None + +data.List.stripPrefix.doc : Doc +data.List.stripPrefix.doc = + {{ + `` stripPrefix prefix list `` drops the given prefix from the list. It + returns {None} if the list does not start with the prefix, or {Some} + containing the elements after the prefix. + + # Examples + + ``` + stripPrefix [1, 2, 3] [1, 2, 3, 4, 5] + ``` + + ``` + stripPrefix [9, 0, 2] [1, 2, 3, 4, 5] + ``` + }} + +test> data.List.stripPrefix.tests.all = + check (stripPrefix [1, 2, 3] [1, 2, 3] === Some []) + +test> data.List.stripPrefix.tests.base = + check (stripPrefix [1, 2] [1, 2, 3] === Some [3]) + +test> data.List.stripPrefix.tests.none = + check (stripPrefix [1, 2] [3, 1, 2] === None) + +data.List.subsequences : [a] -> [[a]] +data.List.subsequences list = + use List +: + [] +: List.map List.Nonempty.toList (nonEmptySubsequences list) + +data.List.subsequences.doc : Doc +data.List.subsequences.doc = + {{ + Returns the list of all subsequences of the argument. + + # Examples + + ``` + subsequences [1, 2, 3] + ``` + + ``` + subsequences [] + ``` + }} + +test> data.List.subsequences.tests.base = + check (subsequences [1, 2] === [[], [1], [2], [1, 2]]) + +data.List.tail : [a] -> Optional [a] +data.List.tail = cases + _ +: xs -> Some xs + [] -> None + +data.List.tail.doc : Doc +data.List.tail.doc = + use List tail + {{ + Returns all elements of the list except the first one, or {None} if it's + empty. + + # Examples + + ``` + tail [1, 2, 3, 4, 5] + ``` + + ``` + tail [1] + ``` + + ``` + tail [] + ``` + }} + +test> data.List.tail.tests.isReversedInitOnReversedList = + runs 100 do + use List reverse + xs = gen.listOf natInOrder () + expect + ((xs |> List.tail) + === (xs |> reverse |> List.init |> Optional.map reverse)) + +test> data.List.tail.tests.prop1 = + go _ = + use List +: + xs = gen.listOf natInOrder () + expect (List.tail (1 +: xs) === Some xs) + runs 100 go + +test> data.List.tail.tests.test1 = check (List.tail [1, 2, 3] === Some [2, 3]) + +test> data.List.tail.tests.test2 = check (List.tail [] === None) + +data.List.tails : [a] -> [[a]] +data.List.tails l = l List.+: (match l with + [] -> [] + _ +: xs -> data.List.tails xs) + +data.List.tails.doc : Doc +data.List.tails.doc = + {{ + Returns a {type List} of all final segments of the input. The first element + of the result is the input {type List} itself, and the last element is the + empty {type List}. + + # Examples + + ``` + List.tails [1, 2, 3] + ``` + }} + +-- builtin data.List.take : Nat -> [a] -> [a] + +data.List.take.doc : Doc +data.List.take.doc = + use List take + {{ + Takes the first `n` elements of a {type List}. + + # Examples + + ``` + take 2 [1, 2, 3, 4, 5] + ``` + + ``` + take 0 [1, 2, 3, 4, 5] + ``` + + ``` + take 5 [1, 2, 3, 4, 5] + ``` + + ``` + take 6 [1, 2, 3, 4, 5] + ``` + }} + +data.List.takeRight : Nat -> [a] -> [a] +data.List.takeRight n xs = + use Nat - + List.drop (List.size xs - n) xs + +data.List.takeRight.doc : Doc +data.List.takeRight.doc = + {{ + Returns the last n elements of a list. + + # Example + + ``` + takeRight 2 [1, 2, 3, 4] + ``` + }} + +data.List.takeUntil : (a ->{e} Boolean) -> [a] ->{e} [a] +data.List.takeUntil f xs = + use List :+ + go acc = cases + x +: xs | (>>) f Boolean.not x -> go (acc :+ x) xs + x +: _ | f x -> acc :+ x + _ -> acc + go [] xs + +data.List.takeUntil.doc : Doc +data.List.takeUntil.doc = + use List takeUntil + {{ + Takes elements from a list until a predicate is satisfied. The element that + satisfies the predicate is included in the result. + + # Examples + + ``` + takeUntil Nat.isEven [1, 2, 3, 4, 5] + ``` + + ``` + takeUntil Nat.isOdd [1, 2, 3, 4, 5] + ``` + }} + +data.List.takeWhile : (a ->{e} Boolean) -> [a] ->{e} [a] +data.List.takeWhile f xs = + use List :+ + go acc = cases + x +: xs | f x -> go (acc :+ x) xs + _ -> acc + go [] xs + +data.List.takeWhile.doc : Doc +data.List.takeWhile.doc = + use List takeWhile + use Nat < + {{ + `` takeWhile p xs `` returns the longest prefix of `xs` with elements that + satisfy `p`. + + # Examples + + ``` + takeWhile (x -> x < 9) [5, 6, 7, 5, 9, 7, 6] + ``` + + ``` + takeWhile Nat.isEven [] + ``` + }} + +test> data.List.takeWhile.tests.all = + check (List.takeWhile (flip Universal.lt 9) [1, 2, 3] === [1, 2, 3]) + +test> data.List.takeWhile.tests.middle = + check (List.takeWhile (flip Universal.lt 3) [1, 2, 3, 4, 5] === [1, 2]) + +test> data.List.takeWhile.tests.none = + check (List.takeWhile (flip Universal.gt 10) [1, 2, 11] === []) + +test> data.List.tests.sequenceOptional.empty = + test.verify do ensureEqual (List.sequenceOptional []) None + +test> data.List.tests.sequenceOptional.non_empty = + test.verify do + ensureEqual (List.sequenceOptional [Some 1, Some 2]) (Some [1, 2]) + +test> data.List.tests.sequenceOptional.non_empty_2 = + test.verify do + ensureEqual (List.sequenceOptional [Some 1, None, Some 2]) None + +test> data.List.tests.traverseOptional.empty = + test.verify do + ensureEqual + (List.traverseOptional + (n -> (if Nat.mod n 2 Nat.== 0 then Some (n Nat.+ 1) else None)) []) + None + +test> data.List.tests.traverseOptional.non_empty = + test.verify do + ensureEqual + (List.traverseOptional + (n -> (if Nat.mod n 2 Nat.== 0 then Some (n Nat.+ 1) else None)) [1, 2]) + None + +test> data.List.tests.traverseOptional.non_empty_2 = + test.verify do + ensureEqual + (List.traverseOptional + (n -> (if Nat.mod n 2 Nat.== 0 then Some (n Nat.+ 1) else None)) + [2, 4, 6]) + (Some [3, 5, 7]) + +data.List.toMap.doc : Doc +data.List.toMap.doc = + use List toMap + use Map get + {{ + Converts a {type List} of key-value pairs to a {type Map}. If the {type List} + contains duplicate keys, the value from the last pair is used. + + # Examples + + ``` + get 1 (toMap [(1, "one"), (2, "two"), (3, "three")]) + ``` + + ``` + get 1 (toMap [(1, "one"), (2, "two"), (1, "uno")]) + ``` + }} + +data.List.toSet.doc : Doc +data.List.toSet.doc = + {{ + Converts a {type List} to a {type Set}. + + # Example + + ``` + Set.toText (List.toSet [?🍎, ?🍌, ?🍊, ?🍌, ?🍎]) + ``` + }} + +data.List.toText : (a ->{g} Text) -> [a] ->{g} Text +data.List.toText show = + use Text ++ + foldDelimited (++) show "[" ", " "]" + +data.List.toText.doc : Doc +data.List.toText.doc = + use Nat <= + use Text ++ + {{ + `` List.toText show items `` renders the `items` list as text, using `show` + to render each individual element. + + # Examples + + ``` + List.toText Nat.toText [1, 2, 3] + ``` + + ``` + List.toText Nat.toText [] + ``` + + ``` + List.toText + (s -> (if Text.size s <= 3 then s else Text.take 3 s ++ "...")) + ["abcd", "efg", "hijk", "lmnop", "qrs", "tuv", "wxyz"] + ``` + }} + +test> data.List.toText.tests = test.verify do + use Text ++ + showWithPlus n = "+" ++ Nat.toText n + ensureEqual "[]" (List.toText showWithPlus []) + ensureEqual "[+1, +2, +3]" (List.toText showWithPlus [1, 2, 3]) + +data.List.transpose : [[a]] -> [[a]] +data.List.transpose = cases + [] -> [] + [] +: xss -> data.List.transpose xss + [xs] -> List.map List.singleton xs + x +: xs +: xss -> + (hds, tls) = List.unzip (List.filterMap List.uncons xss) + use List +: + combine y h ys t = y +: h +: data.List.transpose (ys +: t) + combine x hds xs tls + +data.List.transpose.doc : Doc +data.List.transpose.doc = + use List map + {{ + `` transpose rows `` swaps the rows and columns of a list of lists. The + result is a list of lists, where the first list contains the first elements + of each of the input lists, the second list contains the second elements, and + so on. + + If any of the input lists are empty, they are ignored. + + # Examples + + ``` + transpose [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + ``` + + If the list is rectangular (all its rows are the same length), then we can + think of this as a rotation of the list of lists by 90 degrees clockwise + followed by a horizontal flip, or equivalently a rotation by 90 degrees + counter-clockwise followed by a vertical flip. + + ``` + map + fromCharList + (transpose + (map + toCharList + [ "⬜🟥🟥⬜⬜⬜🟥🟥⬜" + , "🟥🟥🟥🟥⬜🟥🟥🟥🟥" + , "🟥🟥🟥🟥🟥🟥🤖🟥🟥" + , "🟥🟥🟥🟥🟥🟥🟥🟥🟥" + , "⬜🟥🟥🟥🟥🟥🟥🟥⬜" + , "⬜⬜🟥🟥🟥🟥🟥⬜⬜" + , "⬜⬜⬜🟥🟥🟥⬜⬜⬜" + , "⬜⬜⬜⬜🟥⬜⬜⬜⬜" + ])) + ``` + + However, this intution does not quite work if the lists are not all the + same length: + + ``` + map + fromCharList + (transpose + (map + toCharList + [ "⬜🟥🟥⬜⬜⬜🟥🟥" + , "🟥🟥🟥🟥⬜🟥🟥🟥🟥" + , "🟥🟥🟥🟥🟥🟥🤖🟥🟥" + , "🟥🟥🟥🟥🟥🟥🟥🟥🟥" + , "⬜🟥🟥🟥🟥🟥🟥🟥" + , "⬜⬜🟥🟥🟥🟥🟥" + , "⬜⬜⬜🟥🟥🟥" + , "⬜⬜⬜⬜🟥" + , "" + ])) + ``` + }} + +test> data.List.transpose.tests.identityProp = test.verify do + use Random listOf + _ = "For a square matrix, `transpose . transpose = id`" + _ = Each.repeat 1000 + zeroToTen = do Random.natIn 0 10 + xss = listOf (do listOf zeroToTen do 4) zeroToTen + ensureEqual xss (transpose (transpose xss)) + +test> data.List.transpose.tests.joinProp = test.verify do + use List join sort + use Random listOf + _ = "The join of the result is a permutation of the join of the input." + _ = Each.repeat 1000 + zeroToTen = do Random.natIn 0 10 + xss = listOf (do listOf zeroToTen zeroToTen) zeroToTen + transposed = transpose xss + ensureEqual (sort (join transposed)) (sort (join xss)) + +test> data.List.transpose.tests.lengthProp = + test.verify do + use List map size + use Random listOf + _ = + "The length of each row of the result has the same length as the corresponding column of the input." + _ = Each.repeat 1000 + zeroToTen = do Random.natIn 0 10 + xss = listOf (do listOf zeroToTen zeroToTen) zeroToTen + transposed = transpose xss + ensureEqual + (map size transposed) + (map + (n -> size (List.filterMap (xs -> List.at n xs) xss)) + (List.range 0 (size transposed))) + +data.List.traverseOptional : (a ->{g} Optional b) -> [a] ->{g} Optional [b] +data.List.traverseOptional f = cases + [] -> None + [a] -> Optional.map List.singleton (f a) + a +: as1 -> Optional.map2 (List.+:) (f a) (data.List.traverseOptional f as1) + +data.List.traverseOptional.doc : Doc +data.List.traverseOptional.doc = + use List traverseOptional + use Nat + isEven + {{ + {traverseOptional} applies a function to each element of a {type List}. If + one of the results is {None} then the whole result is {None}. Otherwise all + the results are collected into ``Some results``. + + # Examples + + ``` + traverseOptional (n -> (if isEven n then Some (n + 1) else None)) [1, 2, 3] + ``` + + ``` + traverseOptional (n -> (if isEven n then Some (n + 1) else None)) [2, 4, 6] + ``` + }} + +data.List.uncollate : [a] -> ([a], [a]) +data.List.uncollate = + use List :+ + go acc1 acc2 = cases + [] -> (acc1, acc2) + [x] -> (acc1 :+ x, acc2) + [x, y] ++ xs -> go (acc1 :+ x) (acc2 :+ y) xs + go [] [] + +data.List.uncollate.doc : Doc +data.List.uncollate.doc = + use List interleave + {{ + Splits a list into two lists, alternating elements from the original list. If + the original list has an odd number of elements, the first list will have one + more element than the second. + + # Examples + + ``` + uncollate [1, 2, 3, 4, 5] + ``` + + ``` + uncollate [1, 2, 3, 4] + ``` + + # Relationships and properties + + * {interleave} is the inverse of this function. However, this function is + not the exact inverse of {interleave}: + + ``` + uncurry interleave (uncollate [1, 2, 3, 4, 5]) + ``` + + ``` + uncollate (interleave [1, 2] [3, 5, 7, 9]) + ``` + }} + +data.List.uncons : [a] -> Optional (a, [a]) +data.List.uncons = cases + x +: xs -> Some (x, xs) + [] -> None + +data.List.uncons.doc : Doc +data.List.uncons.doc = + use List uncons + {{ + Returns the first element of a list paired with the rest of the list, or + {None} if the list is empty. + + # Examples + + ``` + uncons [1, 2, 3] + ``` + + ``` + uncons [] + ``` + }} + +data.List.uncons.tests.applyOrEmpty : + (i1 ->{g1} i ->{g} [elem]) -> Optional (i1, i) ->{g1, g} [elem] +data.List.uncons.tests.applyOrEmpty f = cases + Some (x, y) -> f x y + None -> [] + +test> data.List.uncons.tests.isHeadAndTailMerged = + runs 100 do + xs = gen.listOf natInOrder () + expect + ((xs |> List.uncons) === ((List.head xs, List.tail xs) |> tests.merge)) + +test> data.List.uncons.tests.isInverseOfCons = runs 100 do + use List +: + xs = gen.listOf natInOrder () + expect (xs === (xs |> List.uncons |> applyOrEmpty (+:))) + +test> data.List.uncons.tests.isReversedUnsnocOnReversedList = runs 100 do + use List reverse + xs = gen.listOf natInOrder () + revOptPair = Optional.map cases (xs, x) -> (x, reverse xs) + expect ((xs |> List.uncons) === (xs |> reverse |> List.unsnoc |> revOptPair)) + +data.List.uncons.tests.merge : (Optional a1, Optional a) -> Optional (a1, a) +data.List.uncons.tests.merge = cases + (Some x, Some y) -> Some (x, y) + (None, None) -> None + _ -> bug "" + +data.List.unfold : s -> (s ->{𝕖} Optional (a, s)) ->{𝕖} [a] +data.List.unfold s0 f = + use List :+ + go f s acc = match f s with + None -> acc + Some (a, s) -> go f s (acc :+ a) + go f s0 [] + +data.List.unfold.doc : Doc +data.List.unfold.doc = + use List unfold + use Nat + < + {{ + Creates a list by iterating a state transition function starting from a seed + value. + + The function is applied to the initial state, and the result is either {None} + to indicate that the list is finished, or {Some} containing the next element + and the next state. + + # Examples + + All even natural numbers less than 10: + + ``` + unfold 0 (x -> (if x < 10 then Some (x, x + 2) else None)) + ``` + + The Fibonacci sequence: + + ``` + unfold (0, 1) cases + (x, y) -> if x < 100 then Some (x, (y, x + y)) else None + ``` + + Uses {unfold} to {List.zip} two lists together: + + ``` + unfold (toCharList "abc", [1, 2, 3]) cases + (c +: cs, n +: ns) -> Some ((c, n), (cs, ns)) + _ -> None + ``` + }} + +data.List.unsafeAt : Nat -> [a] -> a +data.List.unsafeAt n as = match List.at n as with + Some a -> a + None -> bug ("List index out of bounds: " Text.++ Nat.toText n) + +data.List.unsafeAt.doc : Doc +data.List.unsafeAt.doc = + use List unsafeAt + {{ + `` unsafeAt n `` gets the element at the position `n` in the list (using + [zero-based indexing](https://en.wikipedia.org/wiki/Zero-based_numbering)), + or crashes the program if the list has fewer than `n+1` elements. + + # Examples + + ``` + unsafeAt 0 [10, 20, 30] + ``` + + ``` + unsafeAt 2 [10, 20, 30] + ``` + + ``` + unsafeAt 3 [10, 20, 30] + ``` + }} + +data.List.unsnoc : [a] -> Optional ([a], a) +data.List.unsnoc = cases + xs :+ x -> Some (xs, x) + [] -> None + +data.List.unsnoc.doc : Doc +data.List.unsnoc.doc = + use List unsnoc + {{ + Returns the list without its last element paired with the last element, or + {None} if the list is empty. + + # Examples + + ``` + unsnoc [1, 2, 3] + ``` + + ``` + unsnoc [] + ``` + }} + +test> data.List.unsnoc.tests.isInitAndLastMerged = + runs 100 do + xs = gen.listOf natInOrder () + expect + ((xs |> List.unsnoc) === ((List.init xs, List.last xs) |> tests.merge)) + +test> data.List.unsnoc.tests.isInverseOfSnoc = runs 100 do + use List :+ + xs = gen.listOf natInOrder () + expect (xs === (xs |> List.unsnoc |> applyOrEmpty (:+))) + +data.List.unzip : [(a, b)] -> ([a], [b]) +data.List.unzip = cases + [] -> ([], []) + (a, b) +: xs -> + (as, bs) = data.List.unzip xs + (a List.+: as, b List.+: bs) + +data.List.unzip.doc : Doc +data.List.unzip.doc = + use List unzip + {{ + `` unzip pairs `` splits a list of pairs into two lists. + + # Examples + + ``` + unzip [(1, 2), (3, 4), (5, 6)] + ``` + + ``` + unzip (Bag.occurrenceList (Bag.fromText "🍓🍉🍇🍋🍊🍓🍉🍇🍋🍈🍓🍉🍋🍊")) + ``` + }} + +data.List.updateAt : (a ->{g1} a) -> Nat -> [a] ->{g1} [a] +data.List.updateAt f n as = match List.splitAt n as with + (bs, c +: cs) -> bs List.++ (f c List.+: cs) + _ -> as + +data.List.updateAt.doc : Doc +data.List.updateAt.doc = + use Nat * + + {{ + `` updateAt f n xs `` modifies the `n`th element of `xs` by applying the + function `f` to it. If there is no `n`th element, this does nothing. + + # Example + + Adds `1` to the `0`th element of the list: + + ``` + updateAt (x -> x + 1) 0 [1, 2, 3] + ``` + + Does nothing, as there is no `3`rd element of the list: + + ``` + updateAt (x -> x * 2) 3 [1, 2, 3] + ``` + }} + +test> data.List.updateAt.tests.updates = runs 1000 do + use List at + use Nat + == + xs = gen.listOf natInOrder () + n = natInOrder() + f x = x + 1 + updated = updateAt f n xs + oa = Optional.map f (at n xs) + ob = at n updated + expect match (oa, ob) with + (None, None) -> true + (Some a, Some b) -> a == b + _ -> false + +data.List.zip : [a] -> [b] -> [(a, b)] +data.List.zip = + use List :+ + go acc as bs = match (as, bs) with + (a +: as, b +: bs) -> go (acc :+ (a, b)) as bs + _ -> acc + go [] + +data.List.zip.doc : Doc +data.List.zip.doc = + use List zip + {{ + Take corresponding elements from two {type List}s and combine them into a + {type List} of pairs. + + If the two lists are of different lengths, the result will be the same length + as the shorter list. + + # Examples + + ``` + zip [1, 2, 3] [?a, ?b, ?c] + ``` + + ``` + zip [1, 2, 3] [?a, ?b] + ``` + + ``` + zip [1, 2] [?a, ?b, ?c] + ``` + + ``` + zip [1, 2] [] + ``` + + ``` + zip [] [?a, ?b, ?c] + ``` + + ``` + zip [] [] + ``` + }} + +test> data.List.zip.tests.length = runs 1000 do + use List size + use gen boolean listOf + l1 = listOf boolean () + l2 = listOf boolean () + expect (size (List.zip l1 l2) === Nat.min (size l1) (size l2)) + +test> data.List.zip.tests.productLaw = + runs 1000 do + use List at! size + use gen boolean listOf + l1 = listOf boolean () + l2 = listOf boolean () + z = List.zip l1 l2 + r = List.range 0 (Nat.min (size l1) (size l2)) + expect + (List.all (i -> List.at i z === (toOptional! do (at! i l1, at! i l2))) r) + +data.List.zipWith : (a ->{𝕖} b ->{𝕖} c) -> [a] -> [b] ->{𝕖} [c] +data.List.zipWith f a b = + use List :+ + go acc as bs = match (as, bs) with + (ah +: at, bh +: bt) -> go (acc :+ f ah bh) at bt + _ -> acc + go [] a b + +data.List.zipWith.doc : Doc +data.List.zipWith.doc = + use List zipWith + use Nat + + use Tuple pair + {{ + Apply a function to corresponding elements of two lists, producing a list of + the results. The output list is the same length as the shorter of the two + input lists. Each element of the output list is the result of applying the + function to the elements of the input lists at the same position. If one of + the input lists is shorter than the other, the extra elements of the longer + list are ignored. + + # Examples + + ``` + xs = [1, 2, 3, 4, 5] + ys = [7, 8, 9, 10, 11] + zipWith (+) xs ys + ``` + + The length of the output list is the length of the shorter input list: + + ``` + xs = [1, 2, 3] + ys = [7, 8, 9, 10, 11] + zipWith pair xs ys + ``` + + ``` + xs = [1, 2, 3, 4, 5] + ys = [7, 8, 9] + zipWith pair xs ys + ``` + + We can find all adjacent pairs in a list by zipping it with its own tail: + + ``` + xs = 1 +| [2, 3, 4, 5] + zipWith pair (List.Nonempty.toList xs) (Nonempty.tail xs) + ``` + }} + +test> data.List.zipWith.tests.edge1 = + use Nat + + check + let + actual = List.zipWith (+) [1, 5, 10] [] + expected = [] + if Boolean.not (actual === expected) then + bug ("Not equal!", actual, expected) + else true + +test> data.List.zipWith.tests.edge2 = + use Nat + + check + let + actual = List.zipWith (+) [] [] + expected = [] + if Boolean.not (actual === expected) then + bug ("Not equal!", actual, expected) + else true + +test> data.List.zipWith.tests.edge3 = runs 100 do + use Nat + + xs = gen.listOf natInOrder () + expect (List.zipWith (+) xs [] === []) + +test> data.List.zipWith.tests.edge4 = runs 100 do + use Nat + + xs = gen.listOf natInOrder () + expect (List.zipWith (+) [] xs === []) + +test> data.List.zipWith.tests.ex1 = + use Nat + + check + let + actual = List.zipWith (+) [1, 5, 10] [1, 2, 3] + expected = [2, 7, 13] + if Boolean.not (actual === expected) then + bug ("Not equal!", actual, expected) + else true + +test> data.List.zipWith.tests.ex2 = + use Nat + + check + let + xs = [1, 2, 3, 4, 5] + ys = [7, 8, 9, 10, 11] + actual = List.zipWith (+) xs ys + expected = [8, 10, 12, 14, 16] + assert (actual === expected) ("Not equal", actual, expected) true + +test> data.List.zipWith.tests.zipWithRange = runs 100 do + xs = gen.listOf natInOrder () + usingZipWith = List.zipWith (x y -> (x, y)) (List.range 0 (List.size xs)) xs + usingMapIndexed = mapIndexed (x y -> (x, y)) xs + expect (usingZipWith === usingMapIndexed) + +(data.Map.==) : Map k v -> Map k v -> Boolean +x data.Map.== y = + use List sort + use Map toList + sort (toList x) === sort (toList y) + +data.Map.adjust : (v ->{e} v) -> k -> Map k v ->{e} Map k v +data.Map.adjust f = Map.adjustWithKey (const f) + +data.Map.adjust.doc : Doc +data.Map.adjust.doc = + use List reverse + use Map adjust toList + {{ + `` adjust f k m `` updates the value under the key `k` in the {type Map} `m` + with the result of applying the function `f` to the existing value under `k`. + Does nothing if the key `k` is not in the {type Map}. + + # Examples + + ``` + a = Map.fromList [(3, [4, 5]), (5, [1, 2])] + toList (adjust reverse 5 a) + ``` + + ``` + toList (adjust reverse 7 Map.empty) + ``` + }} + +test> data.Map.adjust.tests.adjusts = runs 100 do + use List reverse + use Map get + m1 = tests.mapOf natInOrder (gen.listOf natInOrder) () + k = natInOrder() + m2 = Map.adjust reverse k m1 + v1 = get k m1 + v2 = get k m2 + expect (assertEquals v2 (Optional.map reverse v1)) + +test> data.Map.adjust.tests.alterMap = runs 100 do + use List reverse + m = tests.mapOf natInOrder (gen.listOf natInOrder) () + k = natInOrder() + expect (Map.adjust reverse k m === Map.alter (Optional.map reverse) k m) + +data.Map.adjustWithKey : (k ->{e} v ->{e} v) -> k -> Map k v ->{e} Map k v +data.Map.adjustWithKey f k = cases + internal.Tip -> internal.Tip + internal.Bin sx kx x l r -> + match Universal.ordering k kx with + Less -> internal.Bin sx kx x (data.Map.adjustWithKey f k l) r + Greater -> internal.Bin sx kx x l (data.Map.adjustWithKey f k r) + Equal -> internal.Bin sx kx (f kx x) l r + +data.Map.adjustWithKey.doc : Doc +data.Map.adjustWithKey.doc = + use List +: + use Map adjustWithKey toList + {{ + `` adjustWithKey f k m `` updates the value under the key `k` in the + {type Map} `m` with the result of applying the function `f` to both `k` and + the existing value under `k`. Does nothing if the key `k` is not in the + {type Map}. + + # Examples + + ``` + a = Map.fromList [(5, [1, 2]), (3, [4, 5])] + toList (adjustWithKey (+:) 5 a) + ``` + + ``` + toList (adjustWithKey (+:) 5 Map.empty) + ``` + }} + +test> data.Map.adjustWithKey.tests.adjusts = runs 100 do + use List :+ reverse + use Map get + m1 = tests.mapOf natInOrder (gen.listOf natInOrder) () + k = natInOrder() + m2 = Map.adjustWithKey (k v -> reverse v :+ k) k m1 + v1 = get k m1 + v2 = get k m2 + expect (assertEquals v2 (Optional.map (v -> reverse v :+ k) v1)) + +data.Map.align : Map k a -> Map k b -> Map k (OneOrBoth a b) +data.Map.align = Map.alignWith id + +data.Map.align.doc : Doc +data.Map.align.doc = + use Map fromList + {{ + Aligns two maps into a map of {type OneOrBoth} values. + + The result will have the same keys as the union of the keys of the two input + maps, and each value will be a {type OneOrBoth} containing the corresponding + values from the two input maps. If a key is present in only one of the input + maps, the result will contain {This} or {That} values accordingly. If a key + is present in both input maps, the result will contain a {Both} value. + + # Example + + ``` + Map.toList + (Map.align + (fromList [(1, "hello"), (2, "world")]) (fromList [(2, 42), (3, 43)])) + ``` + + # See also + + * {Map.alignWith} - a variant where you can specify a function to apply to + the values. + }} + +data.Map.alignWith : + (OneOrBoth a b ->{g} c) -> Map k a -> Map k b ->{g} Map k c +data.Map.alignWith f m1 m2 = Map.alignWithKey (_ x -> f x) m1 m2 + +data.Map.alignWith.doc : Doc +data.Map.alignWith.doc = + use Map fromList + use Text ++ + {{ + Aligns two maps into a map of values using a function. + + The result will have the same keys as the union of the keys of the two input + maps, and each value will be the result of applying the given function to the + corresponding values from the two input maps – {This} and {That} for keys + that are present in only one of the input maps, and {Both} for keys that are + present in both input maps. + + # Example + + ``` + f = cases + This a -> "only in the first map: " ++ a + That b -> "only in the second map: " ++ b + Both a b -> "in both maps: " ++ a ++ " and " ++ b + Map.values + (Map.alignWith + f + (fromList [(1, "circuit"), (2, "quasar")]) + (fromList [(2, "voyage"), (3, "harmony")])) + ``` + + # See also + + * {Map.align} - a variant that returns a map of {type OneOrBoth} values. + * {Map.alignWithKey} - a variant where the function also receives the key. + * {Map.mergeWith} - a variant that allows the function to remove keys from + the result map. + }} + +data.Map.alignWithKey : + (k ->{e} OneOrBoth a b ->{f} c) -> Map k a -> Map k b ->{e, f} Map k c +data.Map.alignWithKey f m1 m2 = + match (m1, m2) with + (_, internal.Tip) -> Map.mapWithKey (k a -> f k (This a)) m1 + (internal.Tip, _) -> Map.mapWithKey (k b -> f k (That b)) m2 + (internal.Bin _ k1 x1 l1 r1, internal.Bin _ k2 x2 l2 r2) -> + (l2, mb, r2) = splitLookup k1 m2 + use Map.internal link + use data.Map alignWithKey + l1l2 = alignWithKey f l1 l2 + r1r2 = alignWithKey f r1 r2 + match mb with + None -> link k1 (f k1 (This x1)) l1l2 r1r2 + Some x2 -> link k1 (f k1 (Both x1 x2)) l1l2 r1r2 + +data.Map.alignWithKey.doc : Doc +data.Map.alignWithKey.doc = + use Map fromList + use Nat toText + use Text ++ + {{ + Aligns two maps into a map of values using a function. + + The result will have the same keys as the union of the keys of the two input + maps, and each value will be the result of applying the given function to the + corresponding key-value pairs from the two input maps. The function receives + {This} and {That} for values under keys that are present in only one of the + input maps, and {Both} for keys that are present in both input maps. + + # Example + + ``` + f k = cases + This a -> "only in the first map: " ++ toText k ++ " -> " ++ a + That b -> "only in the second map: " ++ toText k ++ " -> " ++ b + Both a b -> "in both maps: " ++ toText k ++ " -> " ++ a ++ " and " ++ b + Map.values + (Map.alignWithKey + f + (fromList [(1, "circuit"), (2, "quasar")]) + (fromList [(2, "voyage"), (3, "harmony")])) + ``` + + # See also + + * {Map.align} - a variant that returns a map of {type OneOrBoth} values. + * {Map.alignWith} - a variant where the function doesn't take the key. + }} + +test> data.Map.alignWithKey.tests.effects = test.verify do + use Map == singleton + m = singleton "foo" () + go k v = + emit (k, v) + v + let + (seen, result) = toListWithResult do Map.alignWithKey go m m + labeled "result" do ensuring do result == singleton "foo" (Both () ()) + labeled "seen" do ensuring do seen === [("foo", Both () ())] + +data.Map.alter : (Optional v ->{e} Optional v) -> k -> Map k v ->{e} Map k v +data.Map.alter f k = cases + internal.Tip -> + match f None with + None -> internal.Tip + Some x -> Map.singleton k x + internal.Bin sx kx x l r -> + match Universal.ordering k kx with + Less -> balance kx x (data.Map.alter f k l) r + Greater -> balance kx x l (data.Map.alter f k r) + Equal -> + match f (Some x) with + Some x' -> internal.Bin sx kx x' l r + None -> glue l r + +data.Map.alter.doc : Doc +data.Map.alter.doc = + use Map fromList toList + {{ + The expression `` Map.alter f k m `` changes the value under the key `k` in + the {type Map} `m`, or the absence thereof. {Map.alter} can be used to + insert, delete, or update a value in a {type Map}. + + The functions {Map.alter} and {Map.get} stand in a naturality relationship, + as follows: + + `` get k (alter f k m) === f (get k m) `` + + # Examples + + If the key is absent in the {type Map} and the function returns {None}, + then {Map.alter} leaves the {type Map} alone: + + ``` + m = fromList [(5, "a"), (3, "b")] + toList (Map.alter (const None) 7 m) + ``` + + If the function returns {None} and the key is present in the {type Map}, + then {Map.alter} deletes the key: + + ``` + m = fromList [(5, "a"), (3, "b")] + toList (Map.alter (const None) 5 m) + ``` + + If the function returns {Some} and the key is present in the {type Map}, + then {Map.alter} replaces the value under the key: + + ``` + m = fromList [(5, ?a), (3, ?b)] + toList (Map.alter (Optional.map ascii.toUpper) 5 m) + ``` + + If the function returns {Some} and the key is not in the {type Map}, then + {Map.alter} adds the key: + + ``` + m = fromList [(5, "a"), (3, "b")] + toList (Map.alter (const (Some "c")) 7 m) + ``` + }} + +test> data.Map.alter.tests.functor = runs 100 do + m = tests.mapOf natInOrder natInOrder () + k = natInOrder() + expect (Map.alter id k m === m) + +test> data.Map.alter.tests.homomorphism = runs 100 do + use Map alter + use gen boolean + m = tests.mapOf natInOrder boolean () + k = natInOrder() + f = someOrNone yesNo() boolean() () + g = someOrNone yesNo() boolean() () + expect (alter (f << g) k m === alter f k (alter g k m)) + +test> data.Map.alter.tests.naturality = runs 100 do + use Map get + use gen boolean + m = tests.mapOf natInOrder boolean () + k = natInOrder() + f = someOrNone yesNo() boolean() () + x = get k (Map.alter f k m) + y = f (get k m) + expect (x === y) + +data.Map.breakOffMax : Map k v -> Optional ((k, v), Map k v) +data.Map.breakOffMax = cases + internal.Bin _ k x l internal.Tip -> Some ((k, x), l) + internal.Bin _ k x l r -> + f = cases (km, r') -> (km, Map.internal.balanceL k x l r') + Optional.map f (data.Map.breakOffMax r) + internal.Tip -> None + +data.Map.breakOffMax.doc : Doc +data.Map.breakOffMax.doc = + {{ + Finds the maximal key in the {type Map} and removes it. Returns the key, the + value under the key, and the remainder of the {type Map} with that key + removed. If the {type Map} is empty, returns {None}. + + # Example + + ``` + Optional.map + (Tuple.mapRight Map.toList) + (Map.breakOffMax (Map.fromList [(5, "a"), (3, "b")])) + ``` + }} + +test> data.Map.breakOffMax.tests.isMax = + runs 100 do + x = sortBy at1 (gen.listOf (pairOf natInOrder Text.ascii) ()) + kv = List.last x + m = Map.fromList x + expect + (assertEquals + (Map.breakOffMax m) + (Optional.map (kv -> (kv, Map.delete (at1 kv) m)) kv)) + +data.Map.breakOffMax! : Map k v ->{Abort} ((k, v), Map k v) +data.Map.breakOffMax! = cases + internal.Bin _ k x l internal.Tip -> ((k, x), l) + internal.Bin _ k x l r -> + f = cases (km, r') -> (km, Map.internal.balanceL k x l r') + f (data.Map.breakOffMax! r) + internal.Tip -> abort + +data.Map.breakOffMax!.doc : Doc +data.Map.breakOffMax!.doc = + {{ + Finds the maximal key in the {type Map} and removes it. Returns the key, the + value under the key, and the remainder of the {type Map} with that key + removed. If the {type Map} is empty, calls {abort}. + + # Example + + ``` + toOptional! do + Tuple.mapRight + Map.toList (breakOffMax! (Map.fromList [(5, "a"), (3, "b")])) + ``` + }} + +data.Map.breakOffMin : Map k v -> Optional ((k, v), Map k v) +data.Map.breakOffMin = cases + internal.Bin _ k x internal.Tip r -> Some ((k, x), r) + internal.Bin _ k x l r -> + f = cases (km, l') -> (km, Map.internal.balanceR k x l' r) + Optional.map f (data.Map.breakOffMin l) + internal.Tip -> None + +data.Map.breakOffMin.doc : Doc +data.Map.breakOffMin.doc = + {{ + Finds the minimal key in the {type Map} and removes it. Returns the key, the + value under the key, and the remainder of the {type Map} with that key + removed. If the {type Map} is empty, returns {None}. + + # Example + + ``` + Optional.map + (Tuple.mapRight Map.toList) + (Map.breakOffMin (Map.fromList [(5, "a"), (3, "b")])) + ``` + }} + +test> data.Map.breakOffMin.tests.isMin = + runs 100 do + x = + distinctBy + at1 (sortBy at1 (gen.listOf (pairOf natInOrder Text.ascii) ())) + kv = List.head x + m = Map.fromList x + expect + (assertEquals + (Map.breakOffMin m) + (Optional.map (kv -> (kv, Map.delete (at1 kv) m)) kv)) + +data.Map.breakOffMin! : Map k v ->{Abort} ((k, v), Map k v) +data.Map.breakOffMin! = cases + internal.Bin _ k x internal.Tip r -> ((k, x), r) + internal.Bin _ k x l r -> + f = cases (km, l') -> (km, Map.internal.balanceR k x l' r) + f (data.Map.breakOffMin! l) + internal.Tip -> abort + +data.Map.breakOffMin!.doc : Doc +data.Map.breakOffMin!.doc = + {{ + Finds the minimal key in the {type Map} and removes it. Returns the key, the + value under the key, and the remainder of the {type Map} with that key + removed. If the {type Map} is empty, calls {abort}. + + # Example + + ``` + Abort.toOptional do + Tuple.mapRight + Map.toList (breakOffMin! (Map.fromList [(5, "a"), (3, "b")])) + ``` + }} + +data.Map.contains : k -> Map k v -> Boolean +data.Map.contains k m = match Map.get k m with + None -> false + Some _ -> true + +data.Map.contains.doc : Doc +data.Map.contains.doc = + {{ + The expression `` Map.contains k m `` returns `` true `` if the key `k` + exists in the {type Map} `m`, and `` false `` otherwise. + }} + +test> data.Map.contains.tests.delete = runs 100 do + m = tests.mapOf natInOrder natInOrder () + k = natInOrder() + expect (Boolean.not (Map.contains k (Map.delete k m))) + +test> data.Map.contains.tests.put = runs 100 do + m = tests.mapOf natInOrder natInOrder () + k = natInOrder() + v = natInOrder() + expect (Map.contains k (Map.insert k v m)) + +data.Map.delete : k -> Map k v -> Map k v +data.Map.delete k = cases + internal.Tip -> internal.Tip + internal.Bin _ kx x l r -> + match Universal.ordering k kx with + Less -> Map.internal.balanceR kx x (data.Map.delete k l) r + Greater -> Map.internal.balanceL kx x l (data.Map.delete k r) + Equal -> glue l r + +data.Map.delete.doc : Doc +data.Map.delete.doc = + {{ + Deletes a key and its value from the {type Map}. Does nothing if the key is + not in the {type Map}. + }} + +test> data.Map.delete.tests.deletes = + runs 100 do + k = natInOrder() + expect + (Boolean.not + (Map.contains k (Map.delete k (tests.mapOf natInOrder natInOrder ())))) + +data.Map.difference : Map k a -> Map k b -> Map k a +data.Map.difference = cases + internal.Tip, _ -> internal.Tip + t1, internal.Tip -> t1 + t1, internal.Bin _ k _ l2 r2 -> + (l1, r1) = Map.split k t1 + use Map size + use Nat + + use data.Map difference + l1l2 = difference l1 l2 + r1r2 = difference r1 r2 + if size l1l2 + size r1r2 === size t1 then t1 else link2 l1l2 r1r2 + +data.Map.difference.doc : Doc +data.Map.difference.doc = + use Map difference fromList + {{ + The expression `` difference m1 m2 `` constructs a new {type Map} with all + elements `(k,v)` from `m1` except those where `k` exist as a key in `m2`. + + # Example + + ``` + m1 = fromList [(1, ?a), (2, ?b)] + m2 = fromList [(1, ?a), (3, ?c)] + Map.toList (difference m1 m2) + ``` + }} + +test> data.Map.difference.tests.prop = runs 100 do + use Map contains keys + use tests mapOf + m1 = mapOf natInOrder natInOrder () + m2 = mapOf natInOrder natInOrder () + m3 = Map.difference m1 m2 + k1 = keys m1 + k2 = keys m2 + k3 = keys m3 + expect (List.all (flip contains m1) k3 && List.none (flip contains m3) k2) + +data.Map.doc : Doc +data.Map.doc = + use Map contains delete fromList get insert size toList + use Nat + + {{ + {type Map} is a sorted finite map, supporting efficient lookup and deletion + of entries by key. Each key is associated with a single unique value. + + The {insert} and {get} and related functions all use {Universal.ordering} for + ordering the keys. + + ``` + fromList [("apple", 1), ("pear", 2)] |> get "pear" + ``` + + # Common functions + + @signatures{fromList, get, insert, delete, contains, toList} + + # More examples + + ``` + toList (insert "apple" 1 Map.empty) + ``` + + ``` + Map.union (fromList [("mango", 2)]) (fromList [("orange", 3)]) |> toList + ``` + + ``` + delete "blueberry" (fromList [("apple", 1), ("blueberry", 2)]) |> toList + ``` + + ``` + contains "mango" (fromList [("apple", 1), ("blueberry", 2)]) + ``` + + ``` + size (fromList [("apple", 1), ("blueberry", 2)]) + ``` + + ``` + Map.unionWith (+) (fromList [("a", 1)]) (fromList [("a", 1), ("b", 2)]) + |> toList + ``` + + There are a lot more functions defined. Browse the namespace under + {type Map} to see more. + + # Implementation notes + + The type is implemented as a balanced binary tree of logarithmic depth, so + most operations (such as {get} or {insert}) that touch an individual key + take logarithmic time. + + ``` + fromList [("a", "v1"), ("b", "v2"), ("c", "v3"), ("d", "v4"), ("e", "v5")] + ``` + + The size of each subtree is cached at non-leaf nodes, and the entry at each + node has a key which is bigger than all entries in the left subtree, and + smaller than all entries in the right subtree. Operations like {insert} and + {delete} maintain a balanced tree so its depth is logarithmic in the + {size}. + }} + +data.Map.empty : Map k v +data.Map.empty = internal.Tip + +data.Map.empty.doc : Doc +data.Map.empty.doc = {{ The empty {type Map}. }} + +data.Map.equals.doc : Doc +data.Map.equals.doc = + {{ + Checks if two {type Map}s are equal. They're equal precisely when they have + all the same keys, and each key has the same value in one {type Map} as it + does in the other. + }} + +test> data.Map.equals.tests.reflexive = runs 100 do + m = tests.mapOf Text.ascii natInOrder () + expect (Map.equals m m) + +test> data.Map.equals.tests.symmetric = runs 100 do + use Map equals + use gen boolean + use tests mapOf + a = mapOf boolean boolean () + b = mapOf boolean boolean () + expect (equals a b === equals b a) + +test> data.Map.equals.tests.transitive = runs 100 do + use Map equals + use gen boolean + use tests mapOf + a = mapOf boolean boolean () + b = mapOf boolean boolean () + c = mapOf boolean boolean () + expect (implies (equals a b && equals b c) (equals a c)) + +data.Map.filter : (v ->{g} Boolean) -> Map k v ->{g} Map k v +data.Map.filter p = Map.filterWithKey (_ v -> p v) + +data.Map.filter.doc : Doc +data.Map.filter.doc = + {{ + Filters a {type Map} by retaining only entries where the value satisfies the + given predicate. + + # Example + + This filters a map to retain only entries where the value is even: + + ``` + Map.toList (Map.filter Nat.isEven (Map.fromList [(1, 2), (2, 3), (3, 4)])) + ``` + + # See also + + * {Map.filterWithKey} - Filters a map by the keys and values. + * {{ docLink (docEmbedTermLink do Map.filterKeys) }} - Filters a map by the + keys. + * {Map.mapOptional} - Maps over a map, possibly removing entries. + }} + +data.Map.filterAlignWithKey : + (k ->{e} OneOrBoth a b ->{f} Optional c) + -> Map k a + -> Map k b + ->{e, f} Map k c +data.Map.filterAlignWithKey f m1 m2 = + match (m1, m2) with + (_, internal.Tip) -> Map.mapOptionalWithKey (k a -> f k (This a)) m1 + (internal.Tip, _) -> Map.mapOptionalWithKey (k b -> f k (That b)) m2 + (internal.Bin _ k1 x1 l1 r1, internal.Bin _ k2 x2 l2 r2) -> + (l2, mb, r2) = splitLookup k1 m2 + use Map.internal link + use data.Map filterAlignWithKey + l1l2 = filterAlignWithKey f l1 l2 + r1r2 = filterAlignWithKey f r1 r2 + match mb with + None -> + match f k1 (This x1) with + Some v -> link k1 v l1l2 r1r2 + None -> link2 l1l2 r1r2 + Some x2 -> + match f k1 (Both x1 x2) with + Some v -> link k1 v l1l2 r1r2 + None -> link2 l1l2 r1r2 + +data.Map.filterAlignWithKey.doc : Doc +data.Map.filterAlignWithKey.doc = + use Map filterAlignWithKey fromList toList + use Nat + == + {{ + Aligns two maps by their keys and applies a function to the aligned pairs. + The function receives the key and a {type OneOrBoth} value, which is either + {This} if the key is only in the first map, {That} if the key is only in the + second map, or {Both} if the key is in both maps. The function can return a + value for the aligned pair, which will be the value under that key in the + result, or {None} to omit that key from the result. + + # Examples + + This aligns two maps and sum the values under each key: + + ``` + toList + (filterAlignWithKey + (k ob -> (match ob with + This x -> Some x + That y -> Some y + Both x y -> Some (x + y))) + (fromList [(1, 2), (2, 3)]) + (fromList [(2, 4), (3, 5)])) + ``` + + This aligns two maps and filters out keys where the values are equal, + taking the minimum of the two values otherwise: + + ``` + toList + (filterAlignWithKey + (k ob -> (match ob with + Both x y + | x == y -> None + | otherwise -> Some (Nat.min x y) + This x -> Some x + That y -> Some y)) + (fromList [(1, 2), (2, 3)]) + (fromList [(2, 3), (3, 5)])) + ``` + }} + +test> data.Map.filterAlignWithKey.tests.difference = + test.verify do + use List zip + use Map fromList toList + _ = Each.repeat 100 + n = do Random.natIn 0 20 + list = do Random.listOf n n + m1 = fromList (zip list() list()) + m2 = fromList (zip list() list()) + f _ = cases + This x -> Some x + That _ -> None + Both _ _ -> None + ensureEqual + (toList (Map.filterAlignWithKey f m1 m2)) (toList (Map.difference m1 m2)) + +test> data.Map.filterAlignWithKey.tests.effects = test.verify do + use Map == singleton + m = singleton "foo" () + go k v = + emit (k, v) + Some v + let + (seen, result) = toListWithResult do Map.filterAlignWithKey go m m + labeled "result" do ensuring do result == singleton "foo" (Both () ()) + labeled "seen" do ensuring do seen === [("foo", Both () ())] + +test> data.Map.filterAlignWithKey.tests.empty = test.verify do + use List zip + use Map fromList + _ = Each.repeat 100 + n = do Random.natIn 0 20 + list = do Random.listOf n n + m1 = fromList (zip list() list()) + m2 = fromList (zip list() list()) + f _ = cases + This _ -> None + That _ -> None + Both _ _ -> None + ensureEqual (Map.toList (Map.filterAlignWithKey f m1 m2)) [] + +test> data.Map.filterAlignWithKey.tests.intersection = + test.verify do + use List zip + use Map fromList toList + use Nat + + _ = Each.repeat 100 + n = do Random.natIn 0 20 + list = do Random.listOf n n + m1 = fromList (zip list() list()) + m2 = fromList (zip list() list()) + f _ = cases + This _ -> None + That _ -> None + Both x y -> Some (x + y) + ensureEqual + (toList (Map.filterAlignWithKey f m1 m2)) + (toList (Map.intersectWith (+) m1 m2)) + +test> data.Map.filterAlignWithKey.tests.leftIdentity = test.verify do + use List zip + use Map fromList toList + _ = Each.repeat 100 + n = do Random.natIn 0 20 + list = do Random.listOf n n + m1 = fromList (zip list() list()) + m2 = fromList (zip list() list()) + f _ = cases + This x -> Some x + That y -> None + Both x _ -> Some x + ensureEqual (toList (Map.filterAlignWithKey f m1 m2)) (toList m1) + +test> data.Map.filterAlignWithKey.tests.rightIdentity = test.verify do + use List zip + use Map fromList toList + _ = Each.repeat 100 + n = do Random.natIn 0 20 + list = do Random.listOf n n + m1 = fromList (zip list() list()) + m2 = fromList (zip list() list()) + f _ = cases + This x -> None + That y -> Some y + Both _ y -> Some y + ensureEqual (toList (Map.filterAlignWithKey f m1 m2)) (toList m2) + +test> data.Map.filterAlignWithKey.tests.union = + test.verify do + use List zip + use Map fromList toList + use Nat + + _ = Each.repeat 100 + n = do Random.natIn 0 20 + list = do Random.listOf n n + m1 = fromList (zip list() list()) + m2 = fromList (zip list() list()) + f _ = cases + This x -> Some x + That y -> Some y + Both x y -> Some (x + y) + ensureEqual + (toList (Map.filterAlignWithKey f m1 m2)) + (toList (Map.unionWith (+) m1 m2)) + +data.Map.filterKeys : (k ->{g} Boolean) -> Map k v ->{g} Map k v +data.Map.filterKeys p = Map.filterWithKey (k _ -> p k) + +data.Map.filterKeys.doc : Doc +data.Map.filterKeys.doc = + {{ + Filters a {type Map} by retaining only entries where the key satisfies the + given predicate. + + # Example + + This filters a map to retain only entries where the key is even: + + ``` + Map.toList + (Map.filterKeys Nat.isEven (Map.fromList [(1, 2), (2, 3), (3, 4)])) + ``` + + # See also + + * {Map.filterWithKey} - Filters a map by the keys and values. + * {{ docLink (docEmbedTermLink do Map.filter) }} - Filters a map by the + values. + * {Map.mapOptional} - Maps over a map, possibly removing entries. + }} + +data.Map.filterWithKey : (k ->{e} a ->{f} Boolean) -> Map k a ->{e, f} Map k a +data.Map.filterWithKey f = cases + internal.Bin n k x l r -> + use data.Map filterWithKey + l' = filterWithKey f l + r' = filterWithKey f r + match f k x with + false -> glue l' r' + true -> Map.internal.link k x l' r' + internal.Tip -> internal.Tip + +data.Map.filterWithKey.doc : Doc +data.Map.filterWithKey.doc = + use Map filterKeys + use Nat == + {{ + Filters a {type Map} by retaining only entries that satisfy the given + predicate. + + # Example + + This filters a map to retain only entries where the key and value are + equal: + + ``` + Map.toList (Map.filterWithKey (==) (Map.fromList [(1, 1), (2, 3), (3, 3)])) + ``` + + # See also + + * {filterKeys} - Filters a map by the values. + * {filterKeys} - Filters a map by the keys. + * {Map.mapOptional} - Maps over a map, possibly removing entries. + }} + +data.Map.foldLeft : (a ->{e} b ->{e} a) -> a -> Map k b ->{e} a +data.Map.foldLeft f z = cases + internal.Tip -> z + m@(internal.Bin _ _ x l r) -> + data.Map.foldLeft f (f (data.Map.foldLeft f z l) x) r + +data.Map.foldLeft.doc : Doc +data.Map.foldLeft.doc = + use Map foldLeft + use Text ++ + {{ + The expression `` foldLeft f z m `` folds the values in the {type Map} `m` + using the left-associative binary operator `f`, starting with `z`. The values + in the {type Map} are passed to `f` in key-ascending order. + + # Example + + ``` + m = Map.fromList [(1, "a"), (2, "b"), (3, "c")] + f acc v = acc ++ v + foldLeft f "x" m + ``` + }} + +test> data.Map.foldLeft.tests.onElements = runs 100 do + use Text ++ + m = tests.mapOf natInOrder Text.ascii () + expect (Map.foldLeft (++) "" m === List.foldLeft (++) "" (Map.values m)) + +data.Map.foldLeftWithKey : (a ->{e} k ->{e} b ->{e} a) -> a -> Map k b ->{e} a +data.Map.foldLeftWithKey f z m = + go z' = cases + internal.Tip -> z' + internal.Bin _ kx x l r -> go (f (go z' l) kx x) r + go z m + +data.Map.foldLeftWithKey.doc : Doc +data.Map.foldLeftWithKey.doc = + use Map foldLeftWithKey + use Text ++ + {{ + The expression `` foldLeftWithKey f z m `` folds the keys and values in the + {type Map} `m` using the left-associative operator `f` starting with `z`. The + values in the {type Map} are passed to the function `f` in key-ascending + order. + + # Example + + ``` + m = Map.fromList [(1, "a"), (2, "b"), (3, "c")] + f acc k v = acc ++ Text.repeat k v + foldLeftWithKey f "x" m + ``` + }} + +test> data.Map.foldLeftWithKey.tests.onAssoc = + runs 100 do + use Text ++ ascii + m = tests.mapOf ascii ascii () + f acc k v = acc ++ k ++ v + expect + (Map.foldLeftWithKey f "" m + === List.foldLeft (uncurry << f) "" (Map.toList m)) + +data.Map.foldMap : + (a ->{e} a ->{e} a) -> (k ->{e} v ->{e} a) -> Map k v ->{e} Optional a +data.Map.foldMap f g = cases + internal.Tip -> None + internal.Bin sz kx x l r -> + Some (data.Map.Nonempty.foldMap f g (Map.Nonempty.Bin sz kx x l r)) + +data.Map.foldMap.doc : Doc +data.Map.foldMap.doc = + use Nat + + {{ + Folds a {type Map} using a function to apply to each key-value pair and a + function to combine the results of each application. Returns {None} if the + map is empty. + + # Example + + ``` + Map.foldMap (a b -> a + b) (k v -> k + v) (Map.fromList [(1, 2), (2, 3)]) + ``` + }} + +data.Map.foldRight : (a ->{e} b ->{e} b) -> b -> Map k a ->{e} b +data.Map.foldRight f z = cases + internal.Tip -> z + m@(internal.Bin _ _ x l r) -> + data.Map.foldRight f (f x (data.Map.foldRight f z r)) l + +data.Map.foldRight.doc : Doc +data.Map.foldRight.doc = + use Map foldRight + use Text ++ + {{ + The expression `` foldRight f z m `` folds the values in the {type Map} `m` + using the right-associative binary operator `f` starting with `z`. The values + in the {type Map} are passed to `f` in key-ascending order. + + ``` + m = Map.fromList [(1, "a"), (2, "b"), (3, "c")] + foldRight (++) "x" m + ``` + }} + +test> data.Map.foldRight.tests.onElements = runs 100 do + use Text ++ ascii + m = tests.mapOf ascii ascii () + f v acc = v ++ acc + expect (Map.foldRight f "" m === List.foldRight f "" (Map.values m)) + +data.Map.foldRightWithKey : (k ->{e} a ->{e} b ->{e} b) -> b -> Map k a ->{e} b +data.Map.foldRightWithKey f z m = + go z' = cases + internal.Tip -> z' + internal.Bin _ kx x l r -> go (f kx x (go z' r)) l + go z m + +data.Map.foldRightWithKey.doc : Doc +data.Map.foldRightWithKey.doc = + use Map foldRightWithKey + use Text ++ + {{ + The expression `` foldRightWithKey f z m `` folds the keys and values in the + {type Map} `m` using the right-associative operator `f` starting with `z`. + The values in the {type Map} are passed to the function `f` in key-ascending + order. + + ``` + m = Map.fromList [(1, "a"), (2, "b"), (3, "c")] + f k v acc = Text.repeat k v ++ acc + foldRightWithKey f "x" m + ``` + }} + +test> data.Map.foldRightWithKey.tests.onElements = + runs 100 do + use Text ++ ascii + m = tests.mapOf ascii ascii () + f k v acc = k ++ v ++ acc + expect + (Map.foldRightWithKey f "" m + === List.foldRight (uncurry f) "" (Map.toList m)) + +data.Map.foreach : Map k v -> (k ->{e} v ->{e} ()) ->{e} () +data.Map.foreach = cases + internal.Tip, _ -> () + internal.Bin sx kx x l r, f -> + use data.Map foreach + f kx x + foreach l f + foreach r f + +data.Map.foreach.doc : Doc +data.Map.foreach.doc = + use Text ++ + {{ + Applies an effectful function to each key-value pair in the map. + + # Example + + @typecheck ``` + Map.foreach + (Map.fromList + [ ("👋", "wave") + , ("👍", "thumbsup") + , ("👎", "thumbsdown") + , ("👌", "ok_hand") + , ("👊", "punch") + , ("👏", "clap") + ]) + cases k, v -> printLine (k ++ " " ++ v) + ``` + }} + +data.Map.fromList : [(k, v)] -> Map k v +data.Map.fromList = cases + [] -> internal.Tip + [(kx, x)] -> Map.singleton kx x + (kx0, x0) +: xs0 -> + use Int shiftRight + use Map singleton + use Map.internal link putMax + notOrdered kx = cases + [] -> false + (ky, _) +: _ -> Universal.gteq kx ky + fromList' t0 xs = List.foldLeft (cases t, (k, x) -> Map.insert k x t) t0 xs + create s = cases + [] -> (internal.Tip, [], []) + xs@(xp +: xss) -> + if s === +1 then + (kx, x) = xp + if notOrdered kx xss then (singleton kx x, [], xss) + else (singleton kx x, xss, []) + else + match create (shiftRight s 1) xs with + res@(_, [], _) -> res + (l, [(ky, y)], zs) -> (putMax ky y l, [], zs) + (l, ys@((ky, y) +: yss), _) -> + if notOrdered ky yss then (l, [], ys) + else + (r, zs, ws) = create (shiftRight s 1) yss + (link ky y l r, zs, ws) + go = cases + _, t, [] -> t + _, t, [(kx, x)] -> putMax kx x t + s, l, xs@((kx, x) +: xss) -> + if notOrdered kx xss then fromList' l xs + else + match create s xss with + (r, ys, []) -> go (Int.shiftLeft s 1) (link kx x l r) ys + (r, _, ys) -> fromList' (link kx x l r) ys + if notOrdered kx0 xs0 then fromList' (singleton kx0 x0) xs0 + else go +1 (singleton kx0 x0) xs0 + +data.Map.fromList.doc : Doc +data.Map.fromList.doc = + {{ + Constructs a {type Map} from a {type List} of key/value pairs. If the + {type List} contains more than one value for the same key, the last value for + the key is used. See {Map.fromListWith} and {Map.fromListWithKey} if you want + to combine values under duplicate keys in some other way. + }} + +test> data.Map.fromList.tests.roundtrip = + runs 100 do + kvs = gen.listOf (pairOf natInOrder natInOrder) () + trip = Map.toList (Map.fromList kvs) + sorted = sortBy at1 kvs + bins = groupSublistsBy (cases (k, _), (k', _) -> k === k') sorted + expect + (assertEquals + (List.flatMap + (Optional.toList << List.last) (List.map List.Nonempty.toList bins)) + trip) + +data.Map.fromListWith : (v ->{e} v ->{e} v) -> [(k, v)] ->{e} Map k v +data.Map.fromListWith f = Map.fromListWithKey (const f) + +data.Map.fromListWith.doc : Doc +data.Map.fromListWith.doc = + {{ + Constructs a {type Map} from a {type List} of key/value pairs with a + combining function. If the {type List} contains more than one value for the + same key, the values are combined using the function. + + See also {Map.fromList} and {Map.fromListWithKey}. + }} + +test> data.Map.fromListWith.tests.roundtrip = runs 100 do + use Nat + + kvs = gen.listOf (pairOf natInOrder natInOrder) () + trip = Map.toList (Map.fromListWith (+) kvs) + sorted = sortBy at1 kvs + bins = groupSublistsBy (cases (k, _), (k', _) -> k === k') sorted + addUp = List.map (reduceRight cases (k, v), (k', v') -> (k, v + v')) bins + expect (assertEquals addUp trip) + +data.Map.fromListWithKey : + (k ->{e} v ->{e} v ->{e} v) -> [(k, v)] ->{e} Map k v +data.Map.fromListWithKey f xs = + List.foldLeft (cases t, (k, x) -> Map.putWithKey f k x t) Map.empty xs + +data.Map.fromListWithKey.doc : Doc +data.Map.fromListWithKey.doc = + {{ + Constructs a {type Map} from a {type List} of key/value pairs with a + combining function. If the {type List} contains more than one value for the + same key, the values are combined using the function, which can take the key + into account. + + See also {Map.fromList} and {Map.fromListWith}. + }} + +test> data.Map.fromListWithKey.tests.roundtrip = runs 100 do + use Nat + + kvs = gen.listOf (pairOf natInOrder natInOrder) () + trip = Map.toList (Map.fromListWithKey (k v v' -> k + v + v') kvs) + sorted = sortBy at1 kvs + bins = groupSublistsBy (cases (k, _), (k', _) -> k === k') sorted + addUp = List.map (reduceRight cases (k, v), (_, v') -> (k, k + v + v')) bins + expect (assertEquals addUp trip) + +data.Map.get : k -> Map k v -> Optional v +data.Map.get k = cases + internal.Tip -> None + internal.Bin _ kx x l r -> + match Universal.ordering k kx with + Less -> data.Map.get k l + Greater -> data.Map.get k r + Equal -> Some x + +data.Map.get.doc : Doc +data.Map.get.doc = + {{ + The expression `` Map.get k m `` returns the value under the key `k` in the + {type Map} `m`, or {None} if the key `k` is not present in the {type Map}. + }} + +test> data.Map.get.tests.spec = runs 100 do + use Map get + ks = Set.toList (setOf natInOrder ()) + k = natInOrder() + kvs = List.zip ks ks + m = Map.fromList kvs + allPresent = List.all (cases (k', v) -> get k' m === Some v) kvs + noJunk = get k m === None || List.any (cases (k', _) -> k' === k) kvs + expect (allPresent && noJunk) + +data.Map.getMax : Map k v -> Optional (k, v) +data.Map.getMax = + use internal Bin Tip + go k x = cases + Tip -> (k, x) + Bin _ k' v _ r -> go k' v r + cases + Tip -> None + Bin _ k x _ r -> Some (go k x r) + +data.Map.getMax.doc : Doc +data.Map.getMax.doc = + {{ + Finds the maximal key of the {type Map} and the value under that key. Returns + {None} if the {type Map} is empty. + }} + +test> data.Map.getMax.tests.isMax = runs 100 do + x = sortBy at1 (gen.listOf (pairOf natInOrder Text.ascii) ()) + kv = List.last x + m = Map.fromList x + expect (assertEquals (Map.getMax m) kv) + +data.Map.getMin : Map k v -> Optional (k, v) +data.Map.getMin = + use internal Bin Tip + go k x = cases + Tip -> (k, x) + Bin _ k v l _ -> go k v l + cases + Tip -> None + Bin _ k x l _ -> Some (go k x l) + +data.Map.getMin.doc : Doc +data.Map.getMin.doc = + {{ + Finds the minimal key of the {type Map} and the value under that key. Returns + {None} if the {type Map} is empty. + }} + +test> data.Map.getMin.tests.isMin = runs 100 do + x = sortBy at1 (gen.listOf (pairOf natInOrder Text.ascii) ()) + kv = List.head x + m = Map.fromList (List.reverse x) + expect (assertEquals (Map.getMin m) kv) + +data.Map.getOrAbort : k -> Map k v ->{Abort} v +data.Map.getOrAbort k = cases + internal.Tip -> abort + internal.Bin _ kx x l r -> + match Universal.ordering k kx with + Less -> data.Map.getOrAbort k l + Greater -> data.Map.getOrAbort k r + Equal -> x + +data.Map.getOrAbort.doc : Doc +data.Map.getOrAbort.doc = + {{ + `Map.getOrAbort k m` returns the value under the key `k` in the map `m`, or + calls {abort} if `k` is not in the map. + }} + +data.Map.getOrElse : v -> k -> Map k v -> v +data.Map.getOrElse def k m = match Map.get k m with + None -> def + Some x -> x + +data.Map.getOrElse.doc : Doc +data.Map.getOrElse.doc = + use Map fromList getOrElse + {{ + The expression `` getOrElse v k m `` returns the value under the key `k` in + the {type Map} `m` if the key `k` is present, or the default value `v` if `k` + is not present in the {type Map} `m`. + + ``` + m = fromList [(1, "one"), (3, "three")] + getOrElse "oops" 2 m + ``` + + ``` + m = fromList [(1, "one"), (3, "three")] + getOrElse "oops" 1 m + ``` + }} + +test> data.Map.getOrElse.tests.spec = runs 100 do + use Text ascii + m = tests.mapOf natInOrder ascii () + k = natInOrder() + v = ascii() + expect (Optional.getOrElse v (Map.get k m) === Map.getOrElse v k m) + +data.Map.getOrThrow : e -> k -> Map k v ->{Throw e} v +data.Map.getOrThrow e k m = match Map.get k m with + None -> throw e + Some v -> v + +data.Map.getOrThrow.doc : Doc +data.Map.getOrThrow.doc = + {{ + `` Map.getOrThrow e k m `` returns the value of the key `k` in the {type Map} + `m`, or throws `e` with {type Throw} if the key is not present. + }} + +data.Map.insert : k -> v -> Map k v -> Map k v +data.Map.insert kx x = cases + internal.Tip -> Map.singleton kx x + internal.Bin sz ky y l r -> + match Universal.ordering kx ky with + Less -> Map.internal.balanceL ky y (data.Map.insert kx x l) r + Greater -> Map.internal.balanceR ky y l (data.Map.insert kx x r) + Equal -> internal.Bin sz kx x l r + +data.Map.insert.doc : Doc +data.Map.insert.doc = + use Map empty get insert + {{ + Inserts a key-value pair into a {type Map}. If the key is already present, + the value is replaced. + + # Examples + + ``` + get 1 (insert 1 "one" empty) + ``` + + ``` + get 1 (insert 1 "uno" (insert 1 "one" empty)) + ``` + }} + +data.Map.insertNonempty : k -> v -> Map k v -> Map.Nonempty k v +data.Map.insertNonempty kx x = cases + internal.Tip -> Map.Nonempty.singleton kx x + internal.Bin sz ky y l r -> + match Universal.ordering kx ky with + Less -> Nonempty.internal.balanceL ky y (Map.insert kx x l) r + Greater -> Nonempty.internal.balanceR ky y l (Map.insert kx x r) + Equal -> Map.Nonempty.Bin sz kx x l r + +data.Map.insertNonempty.doc : Doc +data.Map.insertNonempty.doc = + use Map fromList insertNonempty + use Map.Nonempty toList + {{ + Inserts a key-value pair into a {type Map}, returning a {type Map.Nonempty}. + If the key is already present in the map, the value is replaced. + + # Example + + ``` + toList (insertNonempty 2 4 (fromList [(1, 2), (2, 3)])) + ``` + + ``` + toList (insertNonempty 3 5 (fromList [(1, 2), (2, 3)])) + ``` + }} + +data.Map.internal.balance : k -> v -> Map k v -> Map k v -> Map k v +data.Map.internal.balance k x l r = + use Map size + use Map.internal bin + use Nat * + + use Universal gt lt + use internal Bin + rotateL : a -> b -> Map a b -> Map a b -> Map a b + rotateL k x l = cases + r@(Bin _ _ _ ly ry) | lt (size ly) (ratio * size ry) -> singleL k x l r + r -> doubleL k x l r + rotateR : a -> b -> Map a b -> Map a b -> Map a b + rotateR k x = cases + l@(Bin _ _ _ ly ry) | lt (size ry) (ratio * size ly) -> singleR k x l + l -> doubleR k x l + singleL : a -> b -> Map a b -> Map a b -> Map a b + singleL k1 x1 t1 = cases + Bin _ k2 x2 t2 t3 -> bin k2 x2 (bin k1 x1 t1 t2) t3 + _ -> bug "singleL: Tip" + singleR : a -> b -> Map a b -> Map a b -> Map a b + singleR k1 x1 = cases + Bin _ k2 x2 t1 t2, t3 -> bin k2 x2 t1 (bin k1 x1 t2 t3) + _, _ -> bug "singleR: Tip" + doubleL : a -> b -> Map a b -> Map a b -> Map a b + doubleL k1 x1 t1 = cases + Bin _ k2 x2 (Bin _ k3 x3 t2 t3) t4 -> + bin k3 x3 (bin k1 x1 t1 t2) (bin k2 x2 t3 t4) + _ -> bug "doubleL: Tip" + doubleR : a -> b -> Map a b -> Map a b -> Map a b + doubleR k1 x1 = cases + Bin _ k2 x2 t1 (Bin _ k3 x3 t2 t3), t4 -> + bin k3 x3 (bin k2 x2 t1 t2) (bin k1 x1 t3 t4) + _, _ -> bug "doubleR: Tip" + sizeL = size l + sizeR = size r + sizeX = sizeL + sizeR + 1 + if Universal.lteq (sizeL + sizeR) 1 then Bin sizeX k x l r + else + if gt sizeR (delta * sizeL) then rotateL k x l r + else + if gt sizeL (delta * sizeR) then rotateR k x l r else Bin sizeX k x l r + +data.Map.internal.balanceL : k -> v -> Map k v -> Map k v -> Map k v +data.Map.internal.balanceL k x l = cases + internal.Tip -> + match l with + internal.Tip -> Map.singleton k x + internal.Bin _ _ _ internal.Tip internal.Tip -> + internal.Bin 2 k x l internal.Tip + internal.Bin _ lk lx internal.Tip (internal.Bin _ lrk lrx _ _) -> + internal.Bin 3 lrk lrx (Map.singleton lk lx) (Map.singleton k x) + internal.Bin _ lk lx ll@(internal.Bin _ _ _ _ _) internal.Tip -> + internal.Bin 3 lk lx ll (Map.singleton k x) + internal.Bin + ls + lk + lx + ll@(internal.Bin lls _ _ _ _) + lr@(internal.Bin lrs lrk lrx lrl lrr) -> + if Universal.lt lrs (ratio Nat.* lls) then + internal.Bin + (1 Nat.+ ls) + lk + lx + ll + (internal.Bin (1 Nat.+ lrs) k x lr internal.Tip) + else + internal.Bin + (1 Nat.+ ls) + lrk + lrx + (internal.Bin (1 Nat.+ lls Nat.+ Map.size lrl) lk lx ll lrl) + (internal.Bin (1 Nat.+ Map.size lrr) k x lrr internal.Tip) + r@(internal.Bin rs _ _ _ _) -> + match l with + internal.Tip -> internal.Bin (1 Nat.+ rs) k x internal.Tip r + internal.Bin ls lk lx ll lr -> + if Universal.gt ls (delta Nat.* rs) then + match (ll, lr) with + (internal.Bin lls _ _ _ _, internal.Bin lrs lrk lrx lrl lrr) -> + if Universal.lt lrs (ratio Nat.* lls) then + internal.Bin + (1 Nat.+ ls Nat.+ rs) + lk + lx + ll + (internal.Bin (1 Nat.+ rs Nat.+ lrs) k x lr r) + else + internal.Bin + (1 Nat.+ ls Nat.+ rs) + lrk + lrx + (internal.Bin (1 Nat.+ lls Nat.+ Map.size lrl) lk lx ll lrl) + (internal.Bin (1 Nat.+ rs Nat.+ Map.size lrr) k x lrr r) + (_, _) -> bug "failure in balanceL" + else internal.Bin (1 Nat.+ ls Nat.+ rs) k x l r + +data.Map.internal.balanceR : k -> v -> Map k v -> Map k v -> Map k v +data.Map.internal.balanceR k x l r = + match l with + internal.Tip -> + match r with + internal.Tip -> internal.Bin 1 k x internal.Tip internal.Tip + internal.Bin _ _ _ internal.Tip internal.Tip -> + internal.Bin 2 k x internal.Tip r + internal.Bin _ rk rx internal.Tip rr@(internal.Bin _ _ _ _ _) -> + internal.Bin + 3 rk rx (internal.Bin 1 k x internal.Tip internal.Tip) rr + internal.Bin _ rk rx (internal.Bin _ rlk rlx _ _) internal.Tip -> + internal.Bin + 3 + rlk + rlx + (internal.Bin 1 k x internal.Tip internal.Tip) + (internal.Bin 1 rk rx internal.Tip internal.Tip) + internal.Bin + rs + rk + rx + rl@(internal.Bin rls rlk rlx rll rlr) + rr@(internal.Bin rrs _ _ _ _) + | Universal.lt rls (ratio Nat.* rrs) -> + internal.Bin + (1 Nat.+ rs) + rk + rx + (internal.Bin (1 Nat.+ rls) k x internal.Tip rl) + rr + | otherwise -> + internal.Bin + (1 Nat.+ rs) + rlk + rlx + (internal.Bin (1 Nat.+ Map.size rll) k x internal.Tip rll) + (internal.Bin (1 Nat.+ rrs Nat.+ Map.size rlr) rk rx rlr rr) + internal.Bin ls _ _ _ _ -> + match r with + internal.Tip -> internal.Bin (1 Nat.+ ls) k x l internal.Tip + internal.Bin rs rk rx rl rr + | Universal.gt rs (delta Nat.* ls) -> + match (rl, rr) with + (internal.Bin rls rlk rlx rll rlr, internal.Bin rrs _ _ _ _) + | Universal.lt rls (ratio Nat.* rrs) -> + internal.Bin + (1 Nat.+ ls Nat.+ rs) + rk + rx + (internal.Bin (1 Nat.+ ls Nat.+ rls) k x l rl) + rr + | otherwise -> + internal.Bin + (1 Nat.+ ls Nat.+ rs) + rlk + rlx + (internal.Bin (1 Nat.+ ls Nat.+ Map.size rll) k x l rll) + (internal.Bin (1 Nat.+ rrs Nat.+ Map.size rlr) rk rx rlr rr) + (_, _) -> bug "Failure in balanceR" + | otherwise -> + internal.Bin (1 Nat.+ ls Nat.+ rs) k x l r + +data.Map.internal.bin : k -> v -> Map k v -> Map k v -> Map k v +data.Map.internal.bin k x l r = + use Map size + use Nat + + internal.Bin (size l + size r + 1) k x l r + +data.Map.internal.delta : Nat +data.Map.internal.delta = 3 + +data.Map.internal.glue : Map k v -> Map k v -> Map k v +data.Map.internal.glue = cases + internal.Tip -> id + l@(internal.Bin sl kl xl ll lr) -> + cases + internal.Tip -> l + r@(internal.Bin sr kr xr rl rr) + | Universal.gt sl sr -> + (MaxView km m l') = maxViewSure kl xl ll lr + Map.internal.balanceR km m l' r + | otherwise -> + (MinView km m r') = minViewSure kr xr rl rr + Map.internal.balanceL km m l r' + +data.Map.internal.link : k -> v -> Map k v -> Map k v -> Map k v +data.Map.internal.link kx x = cases + internal.Tip, r -> Map.internal.putMin kx x r + l, internal.Tip -> Map.internal.putMax kx x l + l@(internal.Bin sizeL ky y ly ry), r@(internal.Bin sizeR kz z lz rz) -> + if Universal.lt (delta Nat.* sizeL) sizeR then + Map.internal.balanceL kz z (data.Map.internal.link kx x l lz) rz + else + if Universal.lt (delta Nat.* sizeR) sizeL then + Map.internal.balanceR ky y ly (data.Map.internal.link kx x ry r) + else Map.internal.bin kx x l r + +data.Map.internal.link2 : Map k v -> Map k v -> Map k v +data.Map.internal.link2 l r = + match (l, r) with + (internal.Tip, r) -> r + (l, internal.Tip) -> l + (internal.Bin sizeL kx x lx rx, internal.Bin sizeR ky y ly ry) -> + if Universal.lt (delta Nat.* sizeL) sizeR then + Map.internal.balanceL ky y (data.Map.internal.link2 l ly) ry + else + if Universal.lt (delta Nat.* sizeR) sizeL then + Map.internal.balanceR kx x lx (data.Map.internal.link2 rx r) + else glue l r + +data.Map.internal.maxViewSure : k -> v -> Map k v -> Map k v -> MaxView k v +data.Map.internal.maxViewSure k x = cases + l, internal.Tip -> MaxView k x l + l, internal.Bin _ kr xr rl rr -> + (MaxView km xm r') = data.Map.internal.maxViewSure kr xr rl rr + MaxView km xm (Map.internal.balanceL k x l r') + +data.Map.internal.minViewSure : k -> v -> Map k v -> Map k v -> MinView k v +data.Map.internal.minViewSure k x = cases + internal.Tip -> MinView k x + internal.Bin _ kl xl ll lr -> + r -> let + (MinView km xm l') = data.Map.internal.minViewSure kl xl ll lr + MinView km xm (Map.internal.balanceR k x l' r) + +data.Map.internal.putMax : k -> v -> Map k v -> Map k v +data.Map.internal.putMax kx x = cases + internal.Tip -> Map.singleton kx x + internal.Bin _ ky y l r -> + Map.internal.balanceR ky y l (data.Map.internal.putMax kx x r) + +data.Map.internal.putMin : k -> v -> Map k v -> Map k v +data.Map.internal.putMin kx x = cases + internal.Tip -> Map.singleton kx x + internal.Bin _ ky y l r -> + Map.internal.balanceL ky y (data.Map.internal.putMin kx x l) r + +data.Map.internal.putWithKeyR : + (k ->{e} v ->{e} v ->{e} v) -> k -> v -> Map k v ->{e} Map k v +data.Map.internal.putWithKeyR f kx x = cases + internal.Tip -> Map.singleton kx x + internal.Bin sy ky y l r -> + match Universal.ordering kx ky with + Less -> + Map.internal.balanceL ky y (data.Map.internal.putWithKeyR f kx x l) r + Greater -> + Map.internal.balanceR ky y l (data.Map.internal.putWithKeyR f kx x r) + Equal -> internal.Bin sy ky (f ky y x) l r + +data.Map.internal.putWithR : + (v ->{e} v ->{e} v) -> k -> v -> Map k v ->{e} Map k v +data.Map.internal.putWithR f kx x = cases + internal.Tip -> Map.singleton kx x + internal.Bin sy ky y l r -> + match Universal.ordering kx ky with + Less -> + Map.internal.balanceL ky y (data.Map.internal.putWithR f kx x l) r + Greater -> + Map.internal.balanceR ky y l (data.Map.internal.putWithR f kx x r) + Equal -> bug "putWithR: key already present" + +data.Map.internal.ratio : Nat +data.Map.internal.ratio = 2 + +data.Map.internal.splitLookup : k -> Map k v -> (Map k v, Optional v, Map k v) +data.Map.internal.splitLookup k = cases + internal.Tip -> (internal.Tip, None, internal.Tip) + internal.Bin _ kx x l r -> + match Universal.ordering k kx with + Less -> + (lt, z, gt) = data.Map.internal.splitLookup k l + (lt, z, Map.internal.link kx x gt r) + Greater -> + (lt, z, gt) = data.Map.internal.splitLookup k r + (Map.internal.link kx x l lt, z, gt) + Equal -> (l, Some x, r) + +data.Map.intersect : Map k a -> Map k b -> Map k a +data.Map.intersect = Map.intersectWith const + +data.Map.intersect.doc : Doc +data.Map.intersect.doc = + use Map fromList + {{ + The intersection of two {type Map}s. Returns data in the first {type Map} for + keys existing in both {type Map}s. + + ``` + x = fromList [(5, "a"), (3, "b")] + y = fromList [(5, "A"), (7, "C")] + Map.toList (Map.intersect x y) + ``` + }} + +test> data.Map.intersect.tests.associative = + runs 100 do + laws.associative (tests.mapOf natInOrder Text.ascii) Map.intersect + +test> data.Map.intersect.tests.idempotent = runs 100 do + use Map == intersect toList + x = tests.mapOf Text.ascii natInOrder () + b = intersect x x == x + if b then expect b else bug (toList (intersect x x), toList x) + +test> data.Map.intersect.tests.zero = runs 100 do + use Map empty intersect + m = tests.mapOf natInOrder Text.ascii () + expect (intersect empty m === empty && intersect m empty === empty) + +data.Map.intersectWith : + (a ->{e} b ->{e} c) -> Map k a -> Map k b ->{e} Map k c +data.Map.intersectWith f = Map.intersectWithKey (const f) + +data.Map.intersectWith.doc : Doc +data.Map.intersectWith.doc = + use Map fromList intersectWith + use Nat + + {{ + Intersection with a combining function. The expression `` + intersectWith f m1 m2 `` combines data in the {type Map} `m1` and the + {type Map} `m2` with the function `f`, for keys existing in both maps. It + discards other data and keys. + + ``` + x = fromList [("a", 5), ("b", 3)] + y = fromList [("a", 6), ("c", 7)] + Map.toList (intersectWith (+) x y) + ``` + }} + +test> data.Map.intersectWith.tests.commutative = + runs 200 do + laws.commutative + (tests.mapOf Text.ascii natInOrder) (Map.intersectWith (Nat.+)) + +test> data.Map.intersectWith.tests.idempotent = runs 100 do + use Map == + use Nat * + + x = tests.mapOf Text.ascii natInOrder () + expect (Map.intersectWith (+) x x == Map.map (v -> v * 2) x) + +test> data.Map.intersectWith.tests.zero = runs 100 do + use Map == empty + use Nat + + x = tests.mapOf Text.ascii natInOrder () + expect (Map.intersectWith (+) x empty == empty) + +data.Map.intersectWithKey : + (k ->{e} a ->{e} b ->{e} c) -> Map k a -> Map k b ->{e} Map k c +data.Map.intersectWithKey f = cases + internal.Tip, _ -> internal.Tip + _, internal.Tip -> internal.Tip + internal.Bin _ k x1 l1 r1, t2 -> + (l2, mb, r2) = splitLookup k t2 + use data.Map intersectWithKey + l1l2 = intersectWithKey f l1 l2 + r1r2 = intersectWithKey f r1 r2 + match mb with + Some x2 -> Map.internal.link k (f k x1 x2) l1l2 r1r2 + None -> link2 l1l2 r1r2 + +data.Map.intersectWithKey.doc : Doc +data.Map.intersectWithKey.doc = + use Map fromList intersectWithKey + use Nat + + {{ + Intersection with a combining function. The expression `` + intersectWithKey f m1 m2 `` combines data and keys in the {type Map} `m1` and + the {type Map} `m2` using the function `f`, for keys existing in both maps. + It discards other data and keys. + + # Example + + ``` + x = fromList [(1, 5), (2, 4)] + y = fromList [(1, 6), (3, 7)] + Map.toList (intersectWithKey (k v1 v2 -> k + v1 + v2) x y) + ``` + }} + +test> data.Map.intersectWithKey.tests.commutative = + runs 200 do + laws.commutative + (tests.mapOf natInOrder natInOrder) + (Map.intersectWithKey (k a b -> k Nat.+ a Nat.+ b)) + +test> data.Map.intersectWithKey.tests.idempotent = + runs 100 do + use Map == + use Nat + + x = tests.mapOf natInOrder natInOrder () + expect + (Map.intersectWithKey (k a b -> k + a + b) x x + == Map.mapWithKey (k v -> k + v + v) x) + +test> data.Map.intersectWithKey.tests.zero = runs 100 do + use Map == empty + use Nat + + x = tests.mapOf natInOrder natInOrder () + expect (Map.intersectWithKey (k a b -> k + a + b) x empty == empty) + +data.Map.isEmpty : Map k v -> Boolean +data.Map.isEmpty = cases + internal.Tip -> true + _ -> false + +data.Map.isEmpty.doc : Doc +data.Map.isEmpty.doc = + {{ + Returns `` true `` if the {type Map} has no elements, otherwise ``false``. + + See {Map.empty}. + }} + +test> data.Map.isEmpty.tests.spec = runs 100 do + use Map == + use gen boolean + m = tests.mapOf boolean boolean () + expect (m == Map.empty === Map.isEmpty m) + +data.Map.keys : Map k a -> [k] +data.Map.keys = + use List +: + Map.foldRightWithKey (k _ ks -> k +: ks) [] + +data.Map.keys.doc : Doc +data.Map.keys.doc = + {{ Returns all the keys in the {type Map}, as a {type List}. }} + +test> data.Map.keys.tests.spec = + runs 100 do + kvs = gen.listOf (pairOf natInOrder gen.boolean) () + m = Map.fromList kvs + ks = Map.keys m + kk = natInOrder() + allPresent = List.all (k -> List.any (cases (k', _) -> k === k') kvs) ks + noJunk = + implies + (Boolean.not (List.contains kk ks)) + (List.none (cases (k, _) -> k === kk) kvs) + expect (allPresent && noJunk) + +data.Map.lookup.doc : Doc +data.Map.lookup.doc = + use Map fromList lookup + {{ + Looks up a key in a {type Map} and returns the corresponding value, if any. + + # Examples + + ``` + lookup 1 (fromList [(1, "one"), (2, "two"), (3, "three")]) + ``` + + ``` + lookup 1 (fromList [(2, "two"), (3, "three")]) + ``` + }} + +data.Map.map : (a ->{e} b) -> Map k a ->{e} Map k b +data.Map.map f = Map.mapWithKey (const f) + +data.Map.map.doc : Doc +data.Map.map.doc = + use Map map + {{ + The expression `` map f m `` applies the function `f` to all values in the + {type Map} `m` returning a new {type Map} with the results. If the input + {type Map} contains a value `v` under a key `k`, then the output {type Map} + will contain `f v` under the key `k`. + + # Example + + ``` + m = Map.fromList [(1, "abc"), (2, "xyz")] + Map.toList (map Text.reverse m) + ``` + }} + +test> data.Map.map.tests.functor = runs 100 do + use Map == + m = tests.mapOf natInOrder natInOrder () + expect (Map.map id m == m) + +data.Map.mapKeys : (k1 ->{g} k2) -> Map k1 v ->{g} Map k2 v +data.Map.mapKeys f = + use List +: + Map.fromList << Map.foldRightWithKey (k x xs -> (f k, x) +: xs) [] + +data.Map.mapKeys.doc : Doc +data.Map.mapKeys.doc = + use Map fromList mapKeys toList + {{ + The expression `` mapKeys f m `` creates a new {type Map} by applying `f` to + each key of the {type Map} `m`. The size of the result may be smaller if `f` + maps two or more distinct keys to the same new key. In this case the value at + the greatest (by {Universal.ordering}) of the original keys is retained. + + # Examples + + ``` + m = fromList [("abc", 1), ("xyz", 2)] + toList (mapKeys Text.reverse m) + ``` + + ``` + m = fromList [("abc", 1), ("xyz", 2)] + toList (mapKeys (k -> 7) m) + ``` + }} + +test> data.Map.mapKeys.tests.functorish = runs 100 do + use Map == + m = tests.mapOf natInOrder natInOrder () + expect (Map.mapKeys id m == m) + +test> data.Map.mapKeys.tests.retainGreatest = runs 100 do + use Map == + m = tests.mapOf natInOrder natInOrder () + kvs = sortBy at1 (Map.toList m) + mapped = Map.mapKeys (const 1) m + expect match List.last kvs with + Some (k, v) -> mapped == Map.singleton 1 v + None -> Map.isEmpty mapped + +data.Map.mapKeysWith : + (v ->{g} v ->{g} v) -> (k1 ->{g} k2) -> Map k1 v ->{g} Map k2 v +data.Map.mapKeysWith c f = + use List +: + Map.fromListWith c << Map.foldRightWithKey (k x xs -> (f k, x) +: xs) [] + +data.Map.mapKeysWith.doc : Doc +data.Map.mapKeysWith.doc = + use Map fromList mapKeysWith toList + use Nat + + {{ + The expression `` mapKeysWith c f m `` creates a new {type Map} by applying + `f` to each key of the {type Map} `m`. The size of the result may be smaller + if `f` maps two or more distinct keys to the same new key. In this case the + associated values will be combined using the function `c`. The value under + the greater (by universal equality) of the original keys is passed as the + first argument to `c`. + + # Examples + + ``` + m = fromList [("abc", 1), ("xyz", 2)] + toList (mapKeysWith (+) Text.reverse m) + ``` + + ``` + m = fromList [("abc", 1), ("xyz", 2)] + toList (mapKeysWith (+) (const 7) m) + ``` + }} + +test> data.Map.mapKeysWith.tests.functorish = runs 100 do + use Map == + use Nat + + m = tests.mapOf natInOrder natInOrder () + expect (Map.mapKeysWith (+) id m == m) + +test> data.Map.mapKeysWith.tests.greatestFirst = + runs 100 do + use Map == + use Text ++ + m = tests.mapOf natInOrder Text.ascii () + kvs = List.reverse (sortBy at1 (Map.toList m)) + mapped = Map.mapKeysWith (++) (const 1) m + expect match List.last kvs with + Some (k, v) -> + mapped == Map.singleton 1 (List.foldLeft (++) "" (List.map at2 kvs)) + None -> Map.isEmpty mapped + +data.Map.mapOptional : (a ->{f} Optional b) -> Map k a ->{f} Map k b +data.Map.mapOptional f = Map.mapOptionalWithKey (_ -> f) + +data.Map.mapOptional.doc : Doc +data.Map.mapOptional.doc = + use Nat > + {{ + Maps a function over the values of a map, possibly removing some keys. + + The result will have a subset of the keys from the input map, and each value + will be the result of applying the given function to the corresponding value + from the input map. The function should return {None} to indicate that the + key should be removed from the result map, and {Some} to indicate that the + key should be present in the result map with the given value. + + # Example + + ``` + f a = if Text.size a > 5 then Some a else None + Map.toList + (Map.mapOptional + f (Map.fromList [(1, "Circuit"), (2, "Quasar"), (3, "Voyage")])) + ``` + + # See also + + * {Map.mapOptionalWithKey} - a variant that also receives the key. + }} + +data.Map.mapOptionalWithKey : + (k ->{e} a ->{f} Optional b) -> Map k a ->{e, f} Map k b +data.Map.mapOptionalWithKey f = cases + internal.Bin n k x l r -> + use data.Map mapOptionalWithKey + l' = mapOptionalWithKey f l + r' = mapOptionalWithKey f r + match f k x with + None -> glue l' r' + Some y -> Map.internal.link k y l' r' + internal.Tip -> internal.Tip + +data.Map.mapOptionalWithKey.doc : Doc +data.Map.mapOptionalWithKey.doc = + {{ + Maps a function over the values of a map, possibly removing some keys. + + The result will have a subset of the keys from the input map, and each value + will be the result of applying the given function to the corresponding + key-value pair. The function should return {None} to indicate that the key + should be removed from the result map, and {Some} to indicate that the key + should be present in the result map with the given value. + + # Example + + ``` + f k a = if Nat.isEven k then Some a else None + Map.toList + (Map.mapOptionalWithKey + f (Map.fromList [(1, "Circuit"), (2, "Quasar"), (3, "Voyage")])) + ``` + + # See also + + * {Map.mapOptional} - a variant that does not receive the key. + }} + +data.Map.mapWithKey : (k ->{e} a ->{e} b) -> Map k a ->{e} Map k b +data.Map.mapWithKey f = cases + internal.Tip -> internal.Tip + internal.Bin sx kx x l r -> + internal.Bin + sx kx (f kx x) (data.Map.mapWithKey f l) (data.Map.mapWithKey f r) + +data.Map.mapWithKey.doc : Doc +data.Map.mapWithKey.doc = + use Map mapWithKey + use Nat + + {{ + The expression `` mapWithKey f m `` applies the function `f` to all values + and keys in the {type Map} `m` returning a new @Map with the results as the + values. If the input {type Map} contains a value `v` under a key `k`, then + the output {type Map} will contain the value `f k v` under the key `k`. + + # Example + + ``` + m = Map.fromList [(1, 9), (2, 3)] + Map.toList (mapWithKey (+) m) + ``` + }} + +test> data.Map.mapWithKey.tests.functorish = runs 100 do + use Map == + m = tests.mapOf natInOrder natInOrder () + expect (Map.mapWithKey (flip const) m == m) + +test> data.Map.mapWithKey.tests.worksLikeList = + runs 100 do + use Nat + + kvs = + distinctBy + at1 (sortBy at1 (gen.listOf (pairOf natInOrder natInOrder) ())) + expect + (assertEquals + (List.map (uncurry (+)) kvs) + (Map.values (Map.mapWithKey (+) (Map.fromList kvs)))) + +data.Map.mergeWith : + (OneOrBoth a b ->{g} Optional c) -> Map k a -> Map k b ->{g} Map k c +data.Map.mergeWith f m1 m2 = Map.mergeWithKey (_ x -> f x) m1 m2 + +data.Map.mergeWith.doc : Doc +data.Map.mergeWith.doc = + use Map fromList mergeWith toList + use Text ++ + {{ + Merges two maps into a map of values using a function. + + The result will have a subset of the union of keys from the two input maps, + and each value will be the result of applying the given function to the + corresponding values. The function receives {This} for values under keys that + are present in only the first input map, {That} for values under keys that + are present in only the second input map, and {Both} for keys that are + present in both input maps. The function should return {None} to indicate + that the key should be removed from the result map, and {Some} to indicate + that the key should be present in the result map with the given value. + + # Example + + We can use this function to find the symmetric difference of two maps: + + ``` + f = cases + This a -> Some a + That b -> Some b + Both _ _ -> None + toList + (mergeWith + f + (fromList [(1, "Circuit"), (2, "Quasar")]) + (fromList [(2, "Voyage"), (3, "Harmony")])) + ``` + + Or we can use it to find the intersection of two maps: + + ``` + f = cases + This a -> None + That b -> None + Both a b -> Some a + toList + (mergeWith + f + (fromList [(1, "Circuit"), (2, "Quasar")]) + (fromList [(2, "Voyage"), (3, "Harmony")])) + ``` + + Or we can use it to find the union of two maps: + + ``` + f = cases + This a -> Some a + That b -> Some b + Both a b -> Some (a ++ b) + toList + (mergeWith + f + (fromList [(1, "Circuit"), (2, "Quasar")]) + (fromList [(2, "Voyage"), (3, "Harmony")])) + ``` + + Or something else entirely! + + # See also + + * {Map.mergeWithKey} - a variant where the function also receives the key. + * {Map.alignWith} - a variant that does not remove keys from the result + map. + }} + +data.Map.mergeWithKey : + (k ->{e} OneOrBoth a b ->{f} Optional c) + -> Map k a + -> Map k b + ->{e, f} Map k c +data.Map.mergeWithKey f m1 m2 = + match (m1, m2) with + (_, internal.Tip) -> Map.mapOptionalWithKey (k a -> f k (This a)) m1 + (internal.Tip, _) -> Map.mapOptionalWithKey (k b -> f k (That b)) m2 + (internal.Bin _ k1 x1 l1 r1, internal.Bin _ k2 x2 l2 r2) -> + (l2, mb, r2) = splitLookup k1 m2 + use Map.internal link + use data.Map mergeWithKey + l1l2 = mergeWithKey f l1 l2 + r1r2 = mergeWithKey f r1 r2 + match mb with + None -> + match f k1 (This x1) with + None -> glue l1l2 r1r2 + Some x -> link k1 x l1l2 r1r2 + Some x2 -> + match f k1 (Both x1 x2) with + None -> glue l1l2 r1r2 + Some x -> link k1 x l1l2 r1r2 + +data.Map.mergeWithKey.doc : Doc +data.Map.mergeWithKey.doc = + use Map fromList mergeWithKey toList + use Nat isEven + use Text ++ + {{ + Merges two maps into a map of values using a function. + + The result will have a subset of the union of the keys of the two input maps, + and each value will be the result of applying the given function to the + corresponding key-value pairs from the two input maps. The function receives + {This} for values under keys that are present in only the first input map, + {That} for values under keys that are present in only the second input map, + and {Both} for keys that are present in both input maps. The function should + return {None} to indicate that the key should be removed from the result map, + and {Some} to indicate that the key should be present in the result map with + the given value. + + # Example + + We can use this function to find the symmetric difference of two maps: + + ``` + f k = cases + This a -> Some a + That b -> Some b + Both _ _ -> None + toList + (mergeWithKey + f + (fromList [(1, "Circuit"), (2, "Quasar")]) + (fromList [(2, "Voyage"), (3, "Harmony")])) + ``` + + Or we can use it to find the intersection of two maps: + + ``` + f k = cases + This a -> None + That b -> None + Both a b -> Some a + toList + (mergeWithKey + f + (fromList [(1, "Circuit"), (2, "Quasar")]) + (fromList [(2, "Voyage"), (3, "Harmony")])) + ``` + + Or we can use it to find the union of two maps: + + ``` + f k = cases + This a -> Some a + That b -> Some b + Both a b -> Some (a ++ b) + toList + (mergeWithKey + f + (fromList [(1, "Circuit"), (2, "Quasar")]) + (fromList [(2, "Voyage"), (3, "Harmony")])) + ``` + + Or something more complex, like this function that selects odd keys from + the first map and even keys from the second map: + + ``` + f k = cases + This a -> if isEven k then Some a else None + That b -> if Nat.isOdd k then Some b else None + Both a b -> if isEven k then Some a else Some b + toList + (mergeWithKey + f + (fromList [(1, "Circuit"), (2, "Quasar")]) + (fromList [(2, "Voyage"), (3, "Harmony")])) + ``` + }} + +(data.Map.Nonempty.==) : Map.Nonempty k v -> Map.Nonempty k v -> Boolean +x data.Map.Nonempty.== y = + use Map.Nonempty toList + toList x === toList y + +data.Map.Nonempty.==.doc : Doc +data.Map.Nonempty.==.doc = + use Map.Nonempty == + {{ + Checks if two nonempty maps are equal. They're equal exactly when they have + the same keys and values. + + # Example + + ``` + toNonemptyMap ((1, "a") +| [(2, "b")]) + == toNonemptyMap ((1, "a") +| [(2, "b")]) + ``` + }} + +data.Map.Nonempty.adjust : + (v ->{e} v) -> k -> Map.Nonempty k v ->{e} Map.Nonempty k v +data.Map.Nonempty.adjust f = Map.Nonempty.adjustWithKey (const f) + +data.Map.Nonempty.adjust.doc : Doc +data.Map.Nonempty.adjust.doc = + {{ + Adjusts the value at a key in a nonempty map using the provided function. If + the key is not present, the map is unchanged. + + # Example + + ``` + Map.Nonempty.toList + (Map.Nonempty.adjust Nat.increment 1 (toNonemptyMap ((1, 2) +| [(2, 3)]))) + ``` + }} + +test> data.Map.Nonempty.adjust.tests.adjusts = + runs 1000 do + use Boolean not + use Map.Nonempty get + use Text ascii + use gen boolean + m = nonemptyMapOf ascii boolean () + k = ascii() + v = boolean() + expect + (implies + (get k m === Some v) + (get k (Map.Nonempty.adjust not k m) === Some (not v))) + +data.Map.Nonempty.adjustWithKey : + (k ->{e} v ->{e} v) -> k -> Map.Nonempty k v ->{e} Map.Nonempty k v +data.Map.Nonempty.adjustWithKey f k = cases + Map.Nonempty.Bin sx kx x l r -> + match Universal.ordering k kx with + Less -> Map.Nonempty.Bin sx kx x (Map.adjustWithKey f k l) r + Greater -> Map.Nonempty.Bin sx kx x l (Map.adjustWithKey f k r) + Equal -> Map.Nonempty.Bin sx kx (f kx x) l r + +data.Map.Nonempty.adjustWithKey.doc : Doc +data.Map.Nonempty.adjustWithKey.doc = + use Nat + + {{ + Adjusts the value at a key in a nonempty map using the provided function + which also receives the key. If the key is not present, the map is unchanged. + + # Example + + ``` + Map.Nonempty.toList + (Map.Nonempty.adjustWithKey + (k v -> k + v) 1 (toNonemptyMap ((1, 2) +| [(2, 3)]))) + ``` + }} + +test> data.Map.Nonempty.adjustWithKey.tests.adjusts = + runs 1000 do + use Map.Nonempty get + use Nat isOdd + use Text ascii size + use gen boolean + m = nonemptyMapOf ascii boolean () + k = ascii() + v = boolean() + f = logic() + expect + (implies + (get k m === Some v) + (get k (Map.Nonempty.adjustWithKey (k v -> f (isOdd (size k)) v) k m) + === Some (f (isOdd (size k)) v))) + +data.Map.Nonempty.align : + Map.Nonempty k a -> Map.Nonempty k b -> Map.Nonempty k (OneOrBoth a b) +data.Map.Nonempty.align = Map.Nonempty.alignWith id + +data.Map.Nonempty.align.doc : Doc +data.Map.Nonempty.align.doc = + {{ + Aligns two nonempty maps into a nonempty map of {type OneOrBoth} values. + + The result will have the same keys as the union of the keys of the two input + maps, and each value will be a {type OneOrBoth} containing the corresponding + values from the two input maps. If a key is present in only one of the input + maps, the result will contain {This} or {That} values accordingly. If a key + is present in both input maps, the result will contain a {Both} value. + + # Example + + ``` + Map.Nonempty.toList + (Map.Nonempty.align + (toNonemptyMap ((1, "hello") +| [(2, "world")])) + (toNonemptyMap ((2, 42) +| [(3, 43)]))) + ``` + + # See also + + * {Map.Nonempty.alignWith} - a variant where you can specify a function to + apply to the values. + }} + +data.Map.Nonempty.alignWith : + (OneOrBoth a b ->{g} c) + -> Map.Nonempty k a + -> Map.Nonempty k b + ->{g} Map.Nonempty k c +data.Map.Nonempty.alignWith f m1 m2 = + Map.Nonempty.alignWithKey (_ x -> f x) m1 m2 + +data.Map.Nonempty.alignWith.doc : Doc +data.Map.Nonempty.alignWith.doc = + use Text ++ + {{ + Aligns two nonempty maps into a nonempty map of values using a function. + + The result will have the same keys as the union of the keys of the two input + maps, and each value will be the result of applying the given function to the + corresponding values from the two input maps – {This} and {That} for keys + that are present in only one of the input maps, and {Both} for keys that are + present in both input maps. + + # Example + + ``` + f = cases + This a -> "only in the first map: " ++ a + That b -> "only in the second map: " ++ b + Both a b -> "in both maps: " ++ a ++ " and " ++ b + Map.Nonempty.values + (Map.Nonempty.alignWith + f + (toNonemptyMap ((1, "circuit") +| [(2, "quasar")])) + (toNonemptyMap ((2, "voyage") +| [(3, "harmony")]))) + ``` + + # See also + + * {Map.Nonempty.align} - a variant that returns a nonempty map of + {type OneOrBoth} values. + * {Map.Nonempty.alignWithKey} - a variant where the function also receives + the key. + * {Nonempty.mergeWith} - a variant that allows the function to remove keys + from the result map. + }} + +data.Map.Nonempty.alignWithKey : + (k ->{e} OneOrBoth a b ->{f} c) + -> Map.Nonempty k a + -> Map.Nonempty k b + ->{e, f} Map.Nonempty k c +data.Map.Nonempty.alignWithKey f m1 m2 = + use Map alignWithKey + use Map.Nonempty Bin + use Nonempty.internal link + (Bin sz1 k1 x1 l1 r1, Bin sz2 k2 x2 l2 r2) = (m1, m2) + (l2, mb, r2) = splitLookup k1 (Map.Nonempty.toMap m2) + l1l2 = alignWithKey f l1 l2 + r1r2 = alignWithKey f r1 r2 + match mb with + None -> link k1 (f k1 (This x1)) l1l2 r1r2 + Some x2 -> link k1 (f k1 (Both x1 x2)) l1l2 r1r2 + +data.Map.Nonempty.alignWithKey.doc : Doc +data.Map.Nonempty.alignWithKey.doc = + use Nat toText + use Text ++ + {{ + Aligns two nonempty maps into a nonempty map of values using a function. + + The result will have the same keys as the union of the keys of the two input + maps, and each value will be the result of applying the given function to the + corresponding key-value pairs from the two input maps. The function receives + {This} and {That} for values under keys that are present in only one of the + input maps, and {Both} for keys that are present in both input maps. + + # Example + + ``` + f k = cases + This a -> "only in the first map: " ++ toText k ++ " -> " ++ a + That b -> "only in the second map: " ++ toText k ++ " -> " ++ b + Both a b -> "in both maps: " ++ toText k ++ " -> " ++ a ++ " and " ++ b + Map.Nonempty.values + (Map.Nonempty.alignWithKey + f + (toNonemptyMap ((1, "circuit") +| [(2, "quasar")])) + (toNonemptyMap ((2, "voyage") +| [(3, "harmony")]))) + ``` + + # See also + + * {Map.Nonempty.align} - a variant that returns a nonempty map of + {type OneOrBoth} values. + * {Map.Nonempty.alignWith} - a variant where the function doesn't take the + key. + }} + +data.Map.Nonempty.alter : + (Optional v ->{e} Optional v) -> k -> Map.Nonempty k v ->{e} Map k v +data.Map.Nonempty.alter f k = Map.alter f k << Map.Nonempty.toMap + +data.Map.Nonempty.alter.doc : Doc +data.Map.Nonempty.alter.doc = + {{ + Alters the value at a key in a nonempty map using the provided function. The + function receives the current value at the key, if present, and returns the + new value to be stored at the key. If the function returns {None}, the key is + deleted from the map. If the key is not present, the map is unchanged. + + # Example + + ``` + Map.toList + (Map.Nonempty.alter + (const (Some "a")) 1 (toNonemptyMap ((1, "b") +| [(2, "c")]))) + ``` + }} + +data.Map.Nonempty.breakOffMax : Map.Nonempty k v -> ((k, v), Map k v) +data.Map.Nonempty.breakOffMax = cases + Map.Nonempty.Bin _ k x l internal.Tip -> ((k, x), l) + Map.Nonempty.Bin _ k x l r -> + match Map.breakOffMax r with + Some ((k', v'), m) -> ((k', v'), Map.insert k x m) + None -> ((k, x), glue l r) + +data.Map.Nonempty.breakOffMax.doc : Doc +data.Map.Nonempty.breakOffMax.doc = + {{ + Breaks off the maximum key and value from a nonempty map, returning the pair + and the (possibly empty) {type Map} without the pair. + + # Example + + ``` + Tuple.second + Map.toList + (Map.Nonempty.breakOffMax (toNonemptyMap ((1, "a") +| [(2, "b")]))) + ``` + }} + +data.Map.Nonempty.breakOffMin : Map.Nonempty k v -> ((k, v), Map k v) +data.Map.Nonempty.breakOffMin = cases + Map.Nonempty.Bin _ k x internal.Tip r -> ((k, x), r) + Map.Nonempty.Bin _ k x l r -> + match Map.breakOffMin l with + Some ((k', v'), m) -> ((k', v'), Map.insert k x m) + None -> ((k, x), glue l r) + +data.Map.Nonempty.breakOffMin.doc : Doc +data.Map.Nonempty.breakOffMin.doc = + {{ + Breaks off the minimum key and value from a nonempty map, returning the pair + and the (possibly empty) {type Map} without the pair. + + # Example + + ``` + Tuple.second + Map.toList + (Map.Nonempty.breakOffMin (toNonemptyMap ((1, "a") +| [(2, "b")]))) + ``` + }} + +data.Map.Nonempty.contains : k -> Map.Nonempty k v -> Boolean +data.Map.Nonempty.contains k m = match Map.Nonempty.get k m with + None -> false + Some _ -> true + +data.Map.Nonempty.contains.doc : Doc +data.Map.Nonempty.contains.doc = + {{ + Checks if a key is present in a nonempty map. + + # Example + + ``` + Map.Nonempty.contains 1 (toNonemptyMap ((1, "a") +| [(2, "b")])) + ``` + }} + +data.Map.Nonempty.delete : k -> Map.Nonempty k v -> Map k v +data.Map.Nonempty.delete k = Map.delete k << Map.Nonempty.toMap + +data.Map.Nonempty.delete.doc : Doc +data.Map.Nonempty.delete.doc = + {{ + Deletes a key from a nonempty map, returning the (possibly empty) map without + the key. + + # Example + + ``` + Map.toList (Map.Nonempty.delete 1 (toNonemptyMap ((1, "a") +| [(2, "b")]))) + ``` + }} + +data.Map.Nonempty.doc : Doc +data.Map.Nonempty.doc = + use Map Nonempty.toList + use Map.Nonempty contains delete get insert size + use Nat + + {{ + {type Map.Nonempty} is a nonempty version of {type Map}. It is a sorted + finite map from keys to values, with at least one key/value pair. + + The type is parameterized by the key type `k` and the value type `v`. + + The {insert} and {get} and related functions all use {Universal.ordering} for + ordering the keys. + + ``` + toNonemptyMap (("apple", 1) +| [("pear", 2)]) |> get "pear" + ``` + + # Common functions + + @signatures{toNonemptyMap, get, insert, delete, contains, Nonempty.toList} + + # More examples + + ``` + Nonempty.toList (Map.Nonempty.singleton "apple" 1) + ``` + + ``` + Map.Nonempty.union + (toNonemptyMap (List.Nonempty.singleton ("mango", 2))) + (toNonemptyMap (List.Nonempty.singleton ("orange", 3))) + |> Nonempty.toList + ``` + + ``` + delete "blueberry" (toNonemptyMap (("apple", 1) +| [("blueberry", 2)])) + |> Map.toList + ``` + + ``` + contains "mango" (toNonemptyMap (("apple", 1) +| [("blueberry", 2)])) + ``` + + ``` + size (toNonemptyMap (("apple", 1) +| [("blueberry", 2)])) + ``` + + ``` + Map.Nonempty.unionWith + (+) + (toNonemptyMap (List.Nonempty.singleton ("a", 1))) + (toNonemptyMap (("a", 1) +| [("b", 2)])) + |> Nonempty.toList + ``` + + There are a lot more functions defined. Browse the namespace under + {type Map.Nonempty} to see more. + + # Implementation notes + + The type is implemented as a balanced binary tree of logarithmic depth, so + most operations (such as {get} or {insert}) that touch an individual key + take logarithmic time. + + ``` + toNonemptyMap + (("a", "v1") +| [("b", "v2"), ("c", "v3"), ("d", "v4"), ("e", "v5")]) + ``` + + The size of each subtree is cached at non-leaf nodes, and the entry at each + node has a key which is bigger than all entries in the left subtree, and + smaller than all entries in the right subtree. Operations like {insert} and + {delete} maintain a balanced tree so its depth is logarithmic in the + {size}. + }} + +test> data.Map.Nonempty.equals.tests.reflexive = runs 1000 do + m = nonemptyMapOf Text.ascii gen.boolean () + expect (Map.Nonempty.equals m m) + +test> data.Map.Nonempty.equals.tests.symmetrical = runs 1000 do + use Map.Nonempty equals + use Text ascii + use gen boolean + m = nonemptyMapOf ascii boolean () + n = nonemptyMapOf ascii boolean () + expect (iff (equals m n) (equals n m)) + +test> data.Map.Nonempty.equals.tests.transitive = runs 1000 do + use Map.Nonempty equals + use Text ascii + use gen boolean + m = nonemptyMapOf ascii boolean () + n = nonemptyMapOf ascii boolean () + o = nonemptyMapOf ascii boolean () + p = implies (equals m n && equals n o) (equals m o) + if p then expect p else bug (m, n, o) + +data.Map.Nonempty.filter : (v ->{g} Boolean) -> Map.Nonempty k v ->{g} Map k v +data.Map.Nonempty.filter p = Map.Nonempty.toMap >> Map.filter p + +data.Map.Nonempty.filter.doc : Doc +data.Map.Nonempty.filter.doc = + {{ + Filters a {type Map.Nonempty} by retaining only entries where the value + satisfies the given predicate. + + # Example + + This filters a map to retain only entries where the value is even: + + ``` + Map.toList + (Map.Nonempty.filter + Nat.isEven (toNonemptyMap ((1, 2) +| [(2, 3), (3, 4)]))) + ``` + + # See also + + * {Map.Nonempty.filterWithKey} - Filters a map by the keys and values. + * {{ docLink (docEmbedTermLink do Nonempty.filterKeys) }} - Filters a map + by the keys. + }} + +data.Map.Nonempty.filterAlignWithKey : + (k ->{e} OneOrBoth a b ->{f} Optional c) + -> Map.Nonempty k a + -> Map.Nonempty k b + ->{e, f} Map k c +data.Map.Nonempty.filterAlignWithKey f = cases + m1@(Map.Nonempty.Bin sz1 k1 x1 l1 r1), m2@(Map.Nonempty.Bin sz2 k2 x2 l2 r2) -> + (l2, mb, r2) = splitLookup k1 (Map.Nonempty.toMap m2) + use Map filterAlignWithKey + use Map.internal link + l1l2 = filterAlignWithKey f l1 l2 + r1r2 = filterAlignWithKey f r1 r2 + match mb with + None -> + match f k1 (This x1) with + Some v -> link k1 v l1l2 r1r2 + None -> link2 l1l2 r1r2 + Some x2 -> + match f k1 (Both x1 x2) with + Some v -> link k1 v l1l2 r1r2 + None -> link2 l1l2 r1r2 + +data.Map.Nonempty.filterAlignWithKey.doc : Doc +data.Map.Nonempty.filterAlignWithKey.doc = + use Map toList + use Nat + == + {{ + Aligns two nonempty maps by their keys and applies a function to the aligned + pairs. The function receives the key and a {type OneOrBoth} value, which is + either {This} if the key is only in the first map, {That} if the key is only + in the second map, or {Both} if the key is in both maps. The function can + return a value for the aligned pair, which will be the value under that key + in the result, or {None} to omit that key from the result. + + # Examples + + This aligns two nonempty maps and sum the values under each key: + + ``` + toList + (Nonempty.filterAlignWithKey + (k ob -> (match ob with + This x -> Some x + That y -> Some y + Both x y -> Some (x + y))) + (toNonemptyMap ((1, 2) +| [(2, 3)])) + (toNonemptyMap ((2, 4) +| [(3, 5)]))) + ``` + + This aligns two nonempty maps and filters out keys where the values are + equal, taking the minimum of the two values otherwise: + + ``` + toList + (Nonempty.filterAlignWithKey + (k ob -> (match ob with + Both x y + | x == y -> None + | otherwise -> Some (Nat.min x y) + This x -> Some x + That y -> Some y)) + (toNonemptyMap ((1, 2) +| [(2, 3)])) + (toNonemptyMap ((2, 4) +| [(3, 5)]))) + ``` + + # See also + + * {Map.filterAlignWithKey} - The same operation on (possibly empty) maps. + * {Map.Nonempty.alignWithKey} - A similar operation that doesn't filter out + keys. + }} + +data.Map.Nonempty.filterKeys : + (k ->{g} Boolean) -> Map.Nonempty k v ->{g} Map k v +data.Map.Nonempty.filterKeys p = Map.Nonempty.toMap >> Map.filterKeys p + +data.Map.Nonempty.filterKeys.doc : Doc +data.Map.Nonempty.filterKeys.doc = + {{ + Filters a {type Map.Nonempty} by retaining only entries where the key + satisfies the given predicate. + + # Example + + This filters a map to retain only entries where the key is even: + + ``` + Map.toList + (Nonempty.filterKeys + Nat.isEven (toNonemptyMap ((1, 2) +| [(2, 3), (3, 4)]))) + ``` + + # See also + + * {Map.Nonempty.filterWithKey} - Filters a map by the keys and values. + * {{ docLink (docEmbedTermLink do Map.Nonempty.filter) }} - Filters a map + by the values. + }} + +data.Map.Nonempty.filterWithKey : + (k ->{e} a ->{f} Boolean) -> Map.Nonempty k a ->{e, f} Map k a +data.Map.Nonempty.filterWithKey p = Map.Nonempty.toMap >> Map.filterWithKey p + +data.Map.Nonempty.filterWithKey.doc : Doc +data.Map.Nonempty.filterWithKey.doc = + use Nat == + use Nonempty filterKeys + {{ + Filters a {type Map.Nonempty} by retaining only entries that satisfy the + given predicate. + + # Example + + This filters a map to retain only entries where the key and value are + equal: + + ``` + Map.toList + (Map.Nonempty.filterWithKey + (==) (toNonemptyMap ((1, 1) +| [(2, 3), (3, 3)]))) + ``` + + # See also + + * {filterKeys} - Filters a map by the values. + * {filterKeys} - Filters a map by the keys. + }} + +data.Map.Nonempty.foldLeft : + (a ->{e} b ->{e} a) -> a -> Map.Nonempty k b ->{e} a +data.Map.Nonempty.foldLeft f z = cases + Map.Nonempty.Bin _ _ x l r -> Map.foldLeft f (f (Map.foldLeft f z l) x) r + +data.Map.Nonempty.foldLeft.doc : Doc +data.Map.Nonempty.foldLeft.doc = + use Nat + + {{ + Folds a nonempty map from left (smallest key) to right (largest key) using a + function to combine the values under the keys. + + # Example + + ``` + Map.Nonempty.foldLeft (a b -> a + b) 0 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.foldLeftWithKey : + (a ->{e} k ->{e} b ->{e} a) -> a -> Map.Nonempty k b ->{e} a +data.Map.Nonempty.foldLeftWithKey f z = cases + Map.Nonempty.Bin _ k x l r -> + Map.foldLeftWithKey f (f (Map.foldLeftWithKey f z l) k x) r + +data.Map.Nonempty.foldLeftWithKey.doc : Doc +data.Map.Nonempty.foldLeftWithKey.doc = + use Nat + + {{ + Folds a nonempty map from left (smallest key) to right (largest key) using a + function to combine the keys and values. + + # Example + + ``` + Nonempty.foldLeftWithKey + (a k v -> a + k + v) 0 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.foldMap : + (a ->{e} a ->{e} a) -> (k ->{e} v ->{e} a) -> Map.Nonempty k v ->{e} a +data.Map.Nonempty.foldMap f g = cases + Map.Nonempty.Bin _ kx x l r -> + match (data.Map.foldMap f g l, data.Map.foldMap f g r) with + (None, None) -> g kx x + (Some l, None) -> f l (g kx x) + (None, Some r) -> f (g kx x) r + (Some l, Some r) -> f (f l (g kx x)) r + +data.Map.Nonempty.foldMap.doc : Doc +data.Map.Nonempty.foldMap.doc = + use Nat + + {{ + Folds a nonempty map using a function to apply to each key-value pair and a + function to combine the results of each application. + + # Example + + ``` + Map.Nonempty.foldMap + (a b -> a + b) (k v -> k + v) (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.foldRight : + (a ->{e} b ->{e} b) -> b -> Map.Nonempty k a ->{e} b +data.Map.Nonempty.foldRight f z = cases + m@(Map.Nonempty.Bin _ _ x l r) -> + Map.foldRight f (f x (Map.foldRight f z r)) l + +data.Map.Nonempty.foldRight.doc : Doc +data.Map.Nonempty.foldRight.doc = + use Nat + + {{ + Folds a nonempty map from right (largest key) to left (smallest key) using a + function to combine the values under the keys. + + # Example + + ``` + Map.Nonempty.foldRight + (a b -> a + b) 0 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.foldRightWithKey : + (k ->{e} a ->{e} b ->{e} b) -> b -> Map.Nonempty k a ->{e} b +data.Map.Nonempty.foldRightWithKey f z = cases + Map.Nonempty.Bin _ k x l r -> + Map.foldRightWithKey f (f k x (Map.foldRightWithKey f z r)) l + +data.Map.Nonempty.foldRightWithKey.doc : Doc +data.Map.Nonempty.foldRightWithKey.doc = + use Nat + + {{ + Folds a nonempty map from right (largest key) to left (smallest key) using a + function to combine the keys and values. + + # Example + + ``` + Nonempty.foldRightWithKey + (k v b -> k + v + b) 0 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.foreach : Map.Nonempty k v -> (k ->{e} v ->{e} ()) ->{e} () +data.Map.Nonempty.foreach = cases + Map.Nonempty.Bin sx kx x l r, f -> + use Map foreach + f kx x + foreach l f + foreach r f + +data.Map.Nonempty.foreach.doc : Doc +data.Map.Nonempty.foreach.doc = + use Nat + + use Store get + {{ + Applies a function to each key and value in a nonempty map and discards the + results. + + # Example + + ``` + withInitialValue 0 do + Nonempty.foreach + (toNonemptyMap ((1, 2) +| [(2, 3)])) (k v -> let + x = get + Store.put (x + k + v)) + get + ``` + }} + +data.Map.Nonempty.fromList : List.Nonempty (k, v) -> Map.Nonempty k v +data.Map.Nonempty.fromList = + List.Nonempty.foldMap Map.Nonempty.union (uncurry Map.Nonempty.singleton) + +data.Map.Nonempty.fromList.doc : Doc +data.Map.Nonempty.fromList.doc = + {{ + Creates a nonempty map from a nonempty list of key-value pairs. + + # Example + + ``` + Map.Nonempty.fromList + (("a", "v1") +| [("b", "v2"), ("c", "v3"), ("d", "v4"), ("e", "v5")]) + ``` + }} + +data.Map.Nonempty.fromListWith : + (v ->{e} v ->{e} v) -> List.Nonempty (k, v) ->{e} Map.Nonempty k v +data.Map.Nonempty.fromListWith f = Map.Nonempty.fromListWithKey (const f) + +data.Map.Nonempty.fromListWith.doc : Doc +data.Map.Nonempty.fromListWith.doc = + use Text ++ + {{ + Creates a nonempty map from a nonempty list of key-value pairs, combining + values with the given function. + + # Example + + ``` + Map.Nonempty.fromListWith + (++) + (("a", "v1") +| [("a", "v2"), ("a", "v3"), ("a", "v4"), ("a", "v5")]) + ``` + }} + +data.Map.Nonempty.fromListWithKey : + (k ->{e} v ->{e} v ->{e} v) -> List.Nonempty (k, v) ->{e} Map.Nonempty k v +data.Map.Nonempty.fromListWithKey f = cases + Nonempty.Nonempty (kx, x) xs -> + m = Map.Nonempty.singleton kx x + List.foldRight (cases (k, x), t -> Nonempty.putWithKey f k x t) m xs + +data.Map.Nonempty.fromListWithKey.doc : Doc +data.Map.Nonempty.fromListWithKey.doc = + use Text ++ + {{ + Creates a nonempty map from a nonempty list of key-value pairs, combining + values and keys with the given function. + + # Example + + ``` + Map.Nonempty.fromListWithKey + (k v1 v2 -> k ++ v1 ++ v2) + (("a", "v1") +| [("a", "v2"), ("a", "v3"), ("a", "v4"), ("a", "v5")]) + ``` + }} + +data.Map.Nonempty.get : k -> Map.Nonempty k v -> Optional v +data.Map.Nonempty.get k = cases + Map.Nonempty.Bin _ kx x l r -> + match Universal.ordering k kx with + Less -> Map.get k l + Greater -> Map.get k r + Equal -> Some x + +data.Map.Nonempty.get.doc : Doc +data.Map.Nonempty.get.doc = + {{ + Gets the value associated with a key in a nonempty map. + + # Example + + ``` + Map.Nonempty.get 2 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.getMax : Map.Nonempty k v -> Optional (k, v) +data.Map.Nonempty.getMax = + go k x = cases + internal.Tip -> (k, x) + internal.Bin _ k' v _ r -> go k' v r + cases Map.Nonempty.Bin _ k x _ r -> Some (go k x r) + +data.Map.Nonempty.getMax.doc : Doc +data.Map.Nonempty.getMax.doc = + {{ + Gets the largest key and its associated value in a nonempty map. + + # Example + + ``` + Map.Nonempty.getMax (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.getMin : Map.Nonempty k v -> Optional (k, v) +data.Map.Nonempty.getMin = + go k x = cases + internal.Tip -> (k, x) + internal.Bin _ k v l _ -> go k v l + cases Map.Nonempty.Bin _ k x l _ -> Some (go k x l) + +data.Map.Nonempty.getMin.doc : Doc +data.Map.Nonempty.getMin.doc = + {{ + Gets the smallest key and its associated value in a nonempty map. + + # Example + + ``` + Map.Nonempty.getMin (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.getOrAbort : k -> Map.Nonempty k v ->{Abort} v +data.Map.Nonempty.getOrAbort k = cases + Map.Nonempty.Bin _ kx x l r -> + match Universal.ordering k kx with + Less -> Map.getOrAbort k l + Greater -> Map.getOrAbort k r + Equal -> x + +data.Map.Nonempty.getOrAbort.doc : Doc +data.Map.Nonempty.getOrAbort.doc = + use Map.Nonempty getOrAbort + {{ + Gets the value associated with a key in a nonempty map, or calls {abort} if + the key is not present. + + # Example + + ``` + toOptional! do getOrAbort 2 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + + ``` + toOptional! do getOrAbort 3 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.getOrElse : v -> k -> Map.Nonempty k v -> v +data.Map.Nonempty.getOrElse def k m = match Map.Nonempty.get k m with + None -> def + Some x -> x + +data.Map.Nonempty.getOrElse.doc : Doc +data.Map.Nonempty.getOrElse.doc = + use Map.Nonempty getOrElse + {{ + Gets the value associated with a key in a nonempty map, or returns the given + default value if the key is not present. + + # Example + + ``` + getOrElse 0 2 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + + ``` + getOrElse 0 3 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.getOrThrow : e -> k -> Map.Nonempty k v ->{Throw e} v +data.Map.Nonempty.getOrThrow e k m = match Map.Nonempty.get k m with + None -> throw e + Some v -> v + +data.Map.Nonempty.getOrThrow.doc : Doc +data.Map.Nonempty.getOrThrow.doc = + {{ + Gets the value associated with the given key, or calls {throw} with the given + error value if the key is not present. + + # Example + + ``` + toEither do + Nonempty.getOrThrow "not found" 3 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.insert : k -> v -> Map.Nonempty k v -> Map.Nonempty k v +data.Map.Nonempty.insert kx x = cases + Map.Nonempty.Bin sz ky y l r -> + match Universal.ordering kx ky with + Less -> Nonempty.internal.balanceL ky y (Map.insert kx x l) r + Greater -> Nonempty.internal.balanceR ky y l (Map.insert kx x r) + Equal -> Map.Nonempty.Bin sz kx x l r + +data.Map.Nonempty.insert.doc : Doc +data.Map.Nonempty.insert.doc = + use Map.Nonempty insert toList + {{ + Inserts a new key and value into the map. If the key is already present in + the map, the old value is replaced with the new value. + + # Example + + ``` + toList (insert 3 4 (toNonemptyMap ((1, 2) +| [(2, 3)]))) + ``` + + ``` + toList (insert 2 4 (toNonemptyMap ((1, 2) +| [(2, 3)]))) + ``` + }} + +data.Map.Nonempty.internal.balanceL : + k -> v -> Map k v -> Map k v -> Map.Nonempty k v +data.Map.Nonempty.internal.balanceL k x l = cases + internal.Tip -> + match l with + internal.Tip -> Map.Nonempty.singleton k x + internal.Bin _ _ _ internal.Tip internal.Tip -> + Map.Nonempty.Bin 2 k x l internal.Tip + internal.Bin _ lk lx internal.Tip (internal.Bin _ lrk lrx _ _) -> + Map.Nonempty.Bin 3 lrk lrx (Map.singleton lk lx) (Map.singleton k x) + internal.Bin _ lk lx ll@(internal.Bin _ _ _ _ _) internal.Tip -> + Map.Nonempty.Bin 3 lk lx ll (Map.singleton k x) + internal.Bin + ls + lk + lx + ll@(internal.Bin lls _ _ _ _) + lr@(internal.Bin lrs lrk lrx lrl lrr) -> + if Universal.lt lrs (ratio Nat.* lls) then + Map.Nonempty.Bin + (1 Nat.+ ls) + lk + lx + ll + (internal.Bin (1 Nat.+ lrs) k x lr internal.Tip) + else + Map.Nonempty.Bin + (1 Nat.+ ls) + lrk + lrx + (internal.Bin (1 Nat.+ lls Nat.+ Map.size lrl) lk lx ll lrl) + (internal.Bin (1 Nat.+ Map.size lrr) k x lrr internal.Tip) + r@(internal.Bin rs _ _ _ _) -> + match l with + internal.Tip -> Map.Nonempty.Bin (1 Nat.+ rs) k x internal.Tip r + internal.Bin ls lk lx ll lr -> + if Universal.gt ls (delta Nat.* rs) then + match (ll, lr) with + (internal.Bin lls _ _ _ _, internal.Bin lrs lrk lrx lrl lrr) -> + if Universal.lt lrs (ratio Nat.* lls) then + Map.Nonempty.Bin + (1 Nat.+ ls Nat.+ rs) + lk + lx + ll + (internal.Bin (1 Nat.+ rs Nat.+ lrs) k x lr r) + else + Map.Nonempty.Bin + (1 Nat.+ ls Nat.+ rs) + lrk + lrx + (internal.Bin (1 Nat.+ lls Nat.+ Map.size lrl) lk lx ll lrl) + (internal.Bin (1 Nat.+ rs Nat.+ Map.size lrr) k x lrr r) + (_, _) -> bug "failure in Nonempty.balanceL" + else Map.Nonempty.Bin (1 Nat.+ ls Nat.+ rs) k x l r + +data.Map.Nonempty.internal.balanceR : + k -> v -> Map k v -> Map k v -> Map.Nonempty k v +data.Map.Nonempty.internal.balanceR k x l r = + match l with + internal.Tip -> + match r with + internal.Tip -> Map.Nonempty.Bin 1 k x internal.Tip internal.Tip + internal.Bin _ _ _ internal.Tip internal.Tip -> + Map.Nonempty.Bin 2 k x internal.Tip r + internal.Bin _ rk rx internal.Tip rr@(internal.Bin _ _ _ _ _) -> + Map.Nonempty.Bin + 3 rk rx (internal.Bin 1 k x internal.Tip internal.Tip) rr + internal.Bin _ rk rx (internal.Bin _ rlk rlx _ _) internal.Tip -> + Map.Nonempty.Bin + 3 + rlk + rlx + (internal.Bin 1 k x internal.Tip internal.Tip) + (internal.Bin 1 rk rx internal.Tip internal.Tip) + internal.Bin + rs + rk + rx + rl@(internal.Bin rls rlk rlx rll rlr) + rr@(internal.Bin rrs _ _ _ _) + | Universal.lt rls (ratio Nat.* rrs) -> + Map.Nonempty.Bin + (1 Nat.+ rs) + rk + rx + (internal.Bin (1 Nat.+ rls) k x internal.Tip rl) + rr + | otherwise -> + Map.Nonempty.Bin + (1 Nat.+ rs) + rlk + rlx + (internal.Bin (1 Nat.+ Map.size rll) k x internal.Tip rll) + (internal.Bin (1 Nat.+ rrs Nat.+ Map.size rlr) rk rx rlr rr) + internal.Bin ls _ _ _ _ -> + match r with + internal.Tip -> Map.Nonempty.Bin (1 Nat.+ ls) k x l internal.Tip + internal.Bin rs rk rx rl rr + | Universal.gt rs (delta Nat.* ls) -> + match (rl, rr) with + (internal.Bin rls rlk rlx rll rlr, internal.Bin rrs _ _ _ _) + | Universal.lt rls (ratio Nat.* rrs) -> + Map.Nonempty.Bin + (1 Nat.+ ls Nat.+ rs) + rk + rx + (internal.Bin (1 Nat.+ ls Nat.+ rls) k x l rl) + rr + | otherwise -> + Map.Nonempty.Bin + (1 Nat.+ ls Nat.+ rs) + rlk + rlx + (internal.Bin (1 Nat.+ ls Nat.+ Map.size rll) k x l rll) + (internal.Bin (1 Nat.+ rrs Nat.+ Map.size rlr) rk rx rlr rr) + (_, _) -> bug "Failure in balanceR" + | otherwise -> + Map.Nonempty.Bin (1 Nat.+ ls Nat.+ rs) k x l r + +data.Map.Nonempty.internal.link : + k -> v -> Map k v -> Map k v -> Map.Nonempty k v +data.Map.Nonempty.internal.link kx x = cases + internal.Tip, r -> Nonempty.internal.putMin kx x r + l, internal.Tip -> Nonempty.internal.putMax kx x l + l@(internal.Bin sizeL ky y ly ry), r@(internal.Bin sizeR kz z lz rz) -> + if Universal.lt (delta Nat.* sizeL) sizeR then + Nonempty.internal.balanceL kz z (Map.internal.link kx x l lz) rz + else + if Universal.lt (delta Nat.* sizeR) sizeL then + Nonempty.internal.balanceR ky y ly (Map.internal.link kx x ry r) + else Map.Nonempty.Bin (Map.size l Nat.+ Map.size r Nat.+ 1) kx x l r + +data.Map.Nonempty.internal.putMax : k -> v -> Map k v -> Map.Nonempty k v +data.Map.Nonempty.internal.putMax kx x = cases + internal.Tip -> Map.Nonempty.singleton kx x + internal.Bin _ ky y l r -> + Nonempty.internal.balanceR ky y l (Map.internal.putMax kx x r) + +data.Map.Nonempty.internal.putMin : k -> v -> Map k v -> Map.Nonempty k v +data.Map.Nonempty.internal.putMin kx x = cases + internal.Tip -> Map.Nonempty.singleton kx x + internal.Bin _ ky y l r -> + Nonempty.internal.balanceL ky y (Map.internal.putMin kx x l) r + +data.Map.Nonempty.internal.putWithKeyR : + (k ->{e} v ->{e} v ->{e} v) + -> k + -> v + -> Map.Nonempty k v + ->{e} Map.Nonempty k v +data.Map.Nonempty.internal.putWithKeyR f kx x = cases + Map.Nonempty.Bin sy ky y l r -> + match Universal.ordering kx ky with + Less -> + Nonempty.internal.balanceL ky y (Map.internal.putWithKeyR f kx x l) r + Greater -> + Nonempty.internal.balanceR ky y l (Map.internal.putWithKeyR f kx x r) + Equal -> Map.Nonempty.Bin sy ky (f ky y x) l r + +data.Map.Nonempty.intersect : Map.Nonempty k a -> Map.Nonempty k b -> Map k a +data.Map.Nonempty.intersect = Map.Nonempty.intersectWith const + +data.Map.Nonempty.intersect.doc : Doc +data.Map.Nonempty.intersect.doc = + {{ + Returns a map containing the keys that are present in both maps. The values + in the returned map are the values from the first map. + + # Example + + ``` + Map.toList + (Map.Nonempty.intersect + (toNonemptyMap ((1, 2) +| [(2, 3)])) + (toNonemptyMap ((2, 4) +| [(3, 5)]))) + ``` + }} + +data.Map.Nonempty.intersectWith : + (a ->{e} b ->{e} c) -> Map.Nonempty k a -> Map.Nonempty k b ->{e} Map k c +data.Map.Nonempty.intersectWith f = Map.Nonempty.intersectWithKey (const f) + +data.Map.Nonempty.intersectWith.doc : Doc +data.Map.Nonempty.intersectWith.doc = + use Nat + + {{ + Returns a map containing the keys that are present in both maps. The values + in the returned map are the results of applying the given function to the + values from the two maps. + + # Example + + ``` + Map.toList + (Map.Nonempty.intersectWith + (+) + (toNonemptyMap ((1, 2) +| [(2, 3)])) + (toNonemptyMap ((2, 4) +| [(3, 5)]))) + ``` + }} + +data.Map.Nonempty.intersectWithKey : + (k ->{e} a ->{e} b ->{e} c) + -> Map.Nonempty k a + -> Map.Nonempty k b + ->{e} Map k c +data.Map.Nonempty.intersectWithKey f = cases + Map.Nonempty.Bin _ k x1 l1 r1, t2 -> + (l2, mb, r2) = splitLookup k (Map.Nonempty.toMap t2) + use Map intersectWithKey + l1l2 = intersectWithKey f l1 l2 + r1r2 = intersectWithKey f r1 r2 + match mb with + Some x2 -> Map.internal.link k (f k x1 x2) l1l2 r1r2 + None -> link2 l1l2 r1r2 + +data.Map.Nonempty.intersectWithKey.doc : Doc +data.Map.Nonempty.intersectWithKey.doc = + use Nat + + {{ + Returns a map containing the keys that are present in both maps. The values + in the returned map are the results of applying the given function to the + values from the two maps. The function is passed the key as the first + argument. + + # Example + + ``` + Map.toList + (Map.Nonempty.intersectWithKey + (k v1 v2 -> k + v1 + v2) + (toNonemptyMap ((1, 2) +| [(2, 3)])) + (toNonemptyMap ((2, 4) +| [(3, 5)]))) + ``` + }} + +data.Map.Nonempty.keys : Map.Nonempty k a -> List.Nonempty k +data.Map.Nonempty.keys = cases + Map.Nonempty.Bin _ k _ l r -> prependList (Map.keys l) (k +| Map.keys r) + +data.Map.Nonempty.keys.doc : Doc +data.Map.Nonempty.keys.doc = + {{ + Returns the keys in the map as a non-empty list. + + # Example + + ``` + Map.Nonempty.keys (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.map : (a ->{e} b) -> Map.Nonempty k a ->{e} Map.Nonempty k b +data.Map.Nonempty.map f = Map.Nonempty.mapWithKey (const f) + +data.Map.Nonempty.map.doc : Doc +data.Map.Nonempty.map.doc = + {{ + Applies a function to every value in the map. + + # Example + + ``` + Map.Nonempty.toList + (Map.Nonempty.map Nat.increment (toNonemptyMap ((1, 2) +| [(2, 3)]))) + ``` + }} + +data.Map.Nonempty.mapKeys : + (k1 ->{g} k2) -> Map.Nonempty k1 v ->{g} Map.Nonempty k2 v +data.Map.Nonempty.mapKeys f = cases + Map.Nonempty.Bin sz k v l r -> + Map.insertNonempty (f k) v (Map.union (Map.mapKeys f l) (Map.mapKeys f r)) + +data.Map.Nonempty.mapKeys.doc : Doc +data.Map.Nonempty.mapKeys.doc = + {{ + Applies a function to every key in the map. + + # Example + + ``` + Map.Nonempty.toList + (Nonempty.mapKeys Nat.increment (toNonemptyMap ((1, 2) +| [(2, 3)]))) + ``` + }} + +data.Map.Nonempty.mapKeysWith : + (v ->{g} v ->{g} v) + -> (k1 ->{g} k2) + -> Map.Nonempty k1 v + ->{g} Map.Nonempty k2 v +data.Map.Nonempty.mapKeysWith c f = cases + Map.Nonempty.Bin sz k v l r -> + Map.Nonempty.fromListWith + c + ((f k, v) + +| Map.toList + (Map.union (Map.mapKeysWith c f l) (Map.mapKeysWith c f r))) + +data.Map.Nonempty.mapKeysWith.doc : Doc +data.Map.Nonempty.mapKeysWith.doc = + use Map.Nonempty toList + use Nat + / + use Nonempty mapKeysWith + {{ + Applies a function to every key in the map, combining values with the given + function if the keys map to the same value. + + # Example + + ``` + toList (mapKeysWith (+) Nat.increment (toNonemptyMap ((1, 2) +| [(2, 3)]))) + ``` + + ``` + toList (mapKeysWith (+) (x -> x / 2) (toNonemptyMap ((4, 2) +| [(5, 3)]))) + ``` + }} + +data.Map.Nonempty.mapWithKey : + (k ->{e} a ->{e} b) -> Map.Nonempty k a ->{e} Map.Nonempty k b +data.Map.Nonempty.mapWithKey f = cases + Map.Nonempty.Bin sx kx x l r -> + Map.Nonempty.Bin sx kx (f kx x) (Map.mapWithKey f l) (Map.mapWithKey f r) + +data.Map.Nonempty.mapWithKey.doc : Doc +data.Map.Nonempty.mapWithKey.doc = + use Nat + + {{ + Applies a function to every key-value pair in the map, replacing the values + with the results of the function. Leaves the keys unchanged. + + # Example + + ``` + Map.Nonempty.toList + (Map.Nonempty.mapWithKey + (k v -> (k, v + 1)) (toNonemptyMap ((1, 2) +| [(2, 3)]))) + ``` + }} + +data.Map.Nonempty.mergeWith : + (OneOrBoth a b ->{g} Optional c) + -> Map.Nonempty k a + -> Map.Nonempty k b + ->{g} Map k c +data.Map.Nonempty.mergeWith f m1 m2 = Nonempty.mergeWithKey (_ x -> f x) m1 m2 + +data.Map.Nonempty.mergeWith.doc : Doc +data.Map.Nonempty.mergeWith.doc = + use Map toList + use Nonempty mergeWith + use Text ++ + {{ + Merges two nonempty maps into a (possibly empty) {type Map} of values using a + function. + + The result will have a subset of the union of the keys from the two input + maps, and each value will be the result of applying the given function to the + corresponding key-value pairs. The function receives {This} for values under + keys that are present in only the first input map, {That} for values under + keys that are present in only the second input map, and {Both} for keys that + are present in both input maps. The function should return {None} to indicate + that the key should be removed from the result map, and {Some} to indicate + that the key should be present in the result map with the given value. + + # Example + + We can use this function to find the symmetric difference of two nonempty + maps: + + ``` + f = cases + This a -> Some a + That b -> Some b + Both _ _ -> None + toList + (mergeWith + f + (toNonemptyMap ((1, "Circuit") +| [(2, "Quasar")])) + (toNonemptyMap ((2, "Voyage") +| [(3, "Harmony")]))) + ``` + + Or we can use it to find the intersection of two nonempty maps: + + ``` + f = cases + This a -> None + That b -> None + Both a b -> Some a + toList + (mergeWith + f + (toNonemptyMap ((1, "Circuit") +| [(2, "Quasar")])) + (toNonemptyMap ((2, "Voyage") +| [(3, "Harmony")]))) + ``` + + Or we can use it to find the union of two nonempty maps: + + ``` + f = cases + This a -> Some a + That b -> Some b + Both a b -> Some (a ++ b) + toList + (mergeWith + f + (toNonemptyMap ((1, "Circuit") +| [(2, "Quasar")])) + (toNonemptyMap ((2, "Voyage") +| [(3, "Harmony")]))) + ``` + + Or something else entirely! + + # See also + + * {Nonempty.mergeWithKey} - a variant where the function also receives the + key. + * {Map.Nonempty.alignWith} - a variant that does not remove keys from the + result map. + }} + +data.Map.Nonempty.mergeWithKey : + (k ->{e} OneOrBoth a b ->{f} Optional c) + -> Map.Nonempty k a + -> Map.Nonempty k b + ->{e, f} Map k c +data.Map.Nonempty.mergeWithKey f m1 m2 = + use Map mergeWithKey + use Map.Nonempty Bin + use Map.internal link + (Bin sz1 k1 x1 l1 r1, Bin sz2 k2 x2 l2 r2) = (m1, m2) + (l2, mb, r2) = splitLookup k1 (Map.Nonempty.toMap m2) + l1l2 = mergeWithKey f l1 l2 + r1r2 = mergeWithKey f r1 r2 + match mb with + None -> + match f k1 (This x1) with + None -> glue l1l2 r1r2 + Some x -> link k1 x l1l2 r1r2 + Some x2 -> + match f k1 (Both x1 x2) with + None -> glue l1l2 r1r2 + Some x -> link k1 x l1l2 r1r2 + +data.Map.Nonempty.mergeWithKey.doc : Doc +data.Map.Nonempty.mergeWithKey.doc = + use Map toList + use Nat isEven + use Nonempty mergeWithKey + use Text ++ + {{ + Merges two nonempty maps into a (possibly empty) {type Map} of values using a + function. + + The result will have a subset of the union of the keys from the two input + maps, and each value will be the result of applying the given function to the + corresponding key-value pairs. The function receives {This} for values under + keys that are present in only the first input map, {That} for values under + keys that are present in only the second input map, and {Both} for keys that + are present in both input maps. The function should return {None} to indicate + that the key should be removed from the result map, and {Some} to indicate + that the key should be present in the result map with the given value. + + # Example + + We can use this function to find the symmetric difference of two nonempty + maps: + + ``` + f k = cases + This a -> Some a + That b -> Some b + Both _ _ -> None + toList + (mergeWithKey + f + (toNonemptyMap ((1, "Circuit") +| [(2, "Quasar")])) + (toNonemptyMap ((2, "Voyage") +| [(3, "Harmony")]))) + ``` + + Or we can use it to find the intersection of two nonempty maps: + + ``` + f k = cases + This a -> None + That b -> None + Both a b -> Some a + toList + (mergeWithKey + f + (toNonemptyMap ((1, "Circuit") +| [(2, "Quasar")])) + (toNonemptyMap ((2, "Voyage") +| [(3, "Harmony")]))) + ``` + + Or we can use it to find the union of two nonempty maps: + + ``` + f k = cases + This a -> Some a + That b -> Some b + Both a b -> Some (a ++ b) + toList + (mergeWithKey + f + (toNonemptyMap ((1, "Circuit") +| [(2, "Quasar")])) + (toNonemptyMap ((2, "Voyage") +| [(3, "Harmony")]))) + ``` + + Or something more complex, like this function that selects odd keys from + the first map and even keys from the second map: + + ``` + f k = cases + This a -> if isEven k then Some a else None + That b -> if Nat.isOdd k then Some b else None + Both a b -> if isEven k then Some a else Some b + toList + (mergeWithKey + f + (toNonemptyMap ((1, "Circuit") +| [(2, "Quasar")])) + (toNonemptyMap ((2, "Voyage") +| [(3, "Harmony")]))) + ``` + + # See also + + * {Nonempty.mergeWith} - a variant that allows the function to remove keys + from the result map. + }} + +data.Map.Nonempty.nth : Nat -> Map.Nonempty k v -> Optional (k, v) +data.Map.Nonempty.nth n = cases + Map.Nonempty.Bin sz k v l r -> + use Map nth + use Nat + - + sizel = Map.size l + match Universal.ordering sizel n with + Greater -> nth n l + Equal -> Some (k, v) + Less -> nth (n - sizel + 1) r + +data.Map.Nonempty.nth.doc : Doc +data.Map.Nonempty.nth.doc = + use Map.Nonempty nth + {{ + {{ docExample 2 do i m -> nth i m }} returns the key-value pair in `m` with + the `i`-th smallest key, where `i`=0 is the smallest key (according to + {Universal.ordering}). + + # Example + + ``` + nth 0 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + + ``` + nth 1 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + + ``` + nth 2 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +test> data.Map.Nonempty.nth.tests = + test.verify do + use Random natIn + Each.repeat 100 + s = + (natIn 0 20, natIn 0 10) + +| (List.replicate (natIn 0 19) do (natIn 0 20, natIn 0 10)) + |> toNonemptyMap + ensure + (List.somes + (List.map + (i -> Map.Nonempty.nth i s) (List.range 0 (Map.Nonempty.size s))) + === Map.Nonempty.toList s) + +data.Map.Nonempty.putGetWithKey : + (k ->{e} v ->{e} v ->{e} v) + -> k + -> v + -> Map.Nonempty k v + ->{e} (Optional v, Map.Nonempty k v) +data.Map.Nonempty.putGetWithKey f kx x = cases + Map.Nonempty.Bin sy ky y l r -> + match Universal.ordering kx ky with + Less -> + (found, l') = Map.putGetWithKey f kx x l + (found, Nonempty.internal.balanceL ky y l' r) + Greater -> + (found, r') = Map.putGetWithKey f kx x r + (found, Nonempty.internal.balanceR ky y l r') + Equal -> (Some y, Map.Nonempty.Bin sy kx (f kx x y) l r) + +data.Map.Nonempty.putGetWithKey.doc : Doc +data.Map.Nonempty.putGetWithKey.doc = + use Nat + + use Nonempty putGetWithKey + {{ + Inserts a key-value pair into a {type Map.Nonempty}, returning the old value + and the new map. If the key is already present in the map, the new and old + values are combined with the given function. The function is passed the key, + the new value, and the old value, in that order. + + # Example + + ``` + putGetWithKey + (k old new -> k + old + new) 2 4 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + + ``` + putGetWithKey + (k old new -> k + old + new) 3 5 (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.putWith : + (v ->{e} v ->{e} v) -> k -> v -> Map.Nonempty k v ->{e} Map.Nonempty k v +data.Map.Nonempty.putWith f = Nonempty.putWithKey (_ x y -> f x y) + +data.Map.Nonempty.putWith.doc : Doc +data.Map.Nonempty.putWith.doc = + use Map.Nonempty toList + use Nat + + use Nonempty putWith + {{ + Inserts a key-value pair into a {type Map.Nonempty}, combining values with + the given function if the key is already present in the map. + + # Example + + ``` + toList (putWith (+) 2 4 (toNonemptyMap ((1, 2) +| [(2, 3)]))) + ``` + + ``` + toList (putWith (+) 3 5 (toNonemptyMap ((1, 2) +| [(2, 3)]))) + ``` + }} + +data.Map.Nonempty.putWithKey : + (k ->{e} v ->{e} v ->{e} v) + -> k + -> v + -> Map.Nonempty k v + ->{e} Map.Nonempty k v +data.Map.Nonempty.putWithKey f kx x = cases + Map.Nonempty.Bin sy ky y l r -> + match Universal.ordering kx ky with + Less -> Nonempty.internal.balanceL ky y (Map.putWithKey f kx x l) r + Greater -> Nonempty.internal.balanceR ky y l (Map.putWithKey f kx x r) + Equal -> Map.Nonempty.Bin sy kx (f kx x y) l r + +data.Map.Nonempty.putWithKey.doc : Doc +data.Map.Nonempty.putWithKey.doc = + use Map.Nonempty toList + use Nat + + use Nonempty putWithKey + {{ + Inserts a key-value pair into a {type Map.Nonempty}, combining values with + the given function if the key is already present in the map. The function is + passed the key, the new value, and the old value, in that order. + + # Example + + ``` + toList + (putWithKey + (k v1 v2 -> k + v1 + v2) 2 4 (toNonemptyMap ((1, 2) +| [(2, 3)]))) + ``` + + ``` + toList + (putWithKey + (k v1 v2 -> k + v1 + v2) 3 5 (toNonemptyMap ((1, 2) +| [(2, 3)]))) + ``` + }} + +data.Map.Nonempty.randomChoice : Map.Nonempty k v ->{Random} (k, v) +data.Map.Nonempty.randomChoice map = + randomIndex = Random.natIn 0 (Map.Nonempty.size map) + Map.Nonempty.nth randomIndex map + |> getOrBug "Map.Nonempty.randomChoice: index out of bounds" + +data.Map.Nonempty.randomChoice.doc : Doc +data.Map.Nonempty.randomChoice.doc = + use Map.Nonempty randomChoice + use Nonempty Nonempty + {{ + Picks a random key-value pair from the given {type Map.Nonempty}. Assumes + that the {type Map.Nonempty} is not empty, so an empty {type Map.Nonempty} + will raise an {type Exception}. + + # Examples + + ``` + lcg 4096 do + randomChoice + (toNonemptyMap + (Nonempty (5, "five") [(4, "four"), (2, "two"), (1, "one")])) + ``` + + ``` + lcg 2510 do + randomChoice + (toNonemptyMap + (Nonempty (5, "five") [(4, "four"), (2, "two"), (1, "one")])) + ``` + }} + +test> data.Map.Nonempty.randomChoice.test = test.verify do + map = toNonemptyMap ((0, 0) +| [(1, 1), (2, 2), (3, 3), (4, 4)]) + Each.repeat 1000 + e = Map.Nonempty.randomChoice map + ensure (Map.Nonempty.contains (at1 e) map) + +data.Map.Nonempty.randomKey : Map.Nonempty k v ->{Random} k +data.Map.Nonempty.randomKey map = Map.Nonempty.randomChoice map |> at1 + +data.Map.Nonempty.randomKey.doc : Doc +data.Map.Nonempty.randomKey.doc = + use Map.Nonempty randomKey + use Nonempty Nonempty + {{ + Picks a random key from the given {type Map.Nonempty}. + + # Examples + + ``` + lcg 4096 do + randomKey + (toNonemptyMap + (Nonempty + (6, "six") [(5, "five"), (4, "four"), (2, "two"), (1, "one")])) + ``` + + ``` + lcg 2510 do + randomKey + (toNonemptyMap + (Nonempty + (6, "six") [(5, "five"), (4, "four"), (2, "two"), (1, "one")])) + ``` + }} + +data.Map.Nonempty.randomValue : Map.Nonempty k v ->{Random} v +data.Map.Nonempty.randomValue map = Map.Nonempty.randomChoice map |> at2 + +data.Map.Nonempty.randomValue.doc : Doc +data.Map.Nonempty.randomValue.doc = + use Map.Nonempty randomValue + use Nonempty Nonempty + {{ + Picks a random value from the given {type Map.Nonempty}. + + # Examples + + ``` + lcg 4096 do + randomValue + (toNonemptyMap + (Nonempty + (6, "six") [(5, "five"), (4, "four"), (2, "two"), (1, "one")])) + ``` + + ``` + lcg 2510 do + randomValue + (toNonemptyMap + (Nonempty + (6, "six") [(5, "five"), (4, "four"), (2, "two"), (1, "one")])) + ``` + }} + +data.Map.Nonempty.singleton : k -> v -> Map.Nonempty k v +data.Map.Nonempty.singleton k x = + use internal Tip + Map.Nonempty.Bin 1 k x Tip Tip + +data.Map.Nonempty.singleton.doc : Doc +data.Map.Nonempty.singleton.doc = + {{ + Creates a singleton nonempty map containing a single key-value pair. + + # Example + + ``` + Map.Nonempty.toList (Map.Nonempty.singleton 1 2) + ``` + }} + +data.Map.Nonempty.size : Map.Nonempty k v -> Nat +data.Map.Nonempty.size = cases Map.Nonempty.Bin sz _ _ _ _ -> sz + +data.Map.Nonempty.size.doc : Doc +data.Map.Nonempty.size.doc = + {{ + Returns the number of key-value pairs in the map. + + # Example + + ``` + Map.Nonempty.size (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.tests.nonemptyMapOf : + '{Gen} k -> '{Gen} v -> '{Gen} Map.Nonempty k v +data.Map.Nonempty.tests.nonemptyMapOf k v = + do + Gen.sample + (dedupe + (toWeighted do toNonemptyMap (atLeastOneDistinct (pairOf k v) ()))) + +data.Map.Nonempty.tests.nonemptyMapOf.doc : Doc +data.Map.Nonempty.tests.nonemptyMapOf.doc = + {{ + Generates nonempty maps using the given generators for keys and values. + + # Example + + ``` + List.map + Map.Nonempty.toList + (deprecated.sample 10 (nonemptyMapOf gen.nat gen.boolean)) + ``` + }} + +data.Map.Nonempty.toList : Map.Nonempty k v -> [(k, v)] +data.Map.Nonempty.toList = + use List +: + Nonempty.foldRightWithKey (k x xs -> (k, x) +: xs) [] + +data.Map.Nonempty.toList.doc : Doc +data.Map.Nonempty.toList.doc = + {{ + Converts a {type Map.Nonempty} to a list of key-value pairs. + + # Example + + ``` + Map.Nonempty.toList (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.toMap : Map.Nonempty k v -> Map k v +data.Map.Nonempty.toMap = cases + Map.Nonempty.Bin _ k v l r -> Map.internal.link k v l r + +data.Map.Nonempty.toMap.doc : Doc +data.Map.Nonempty.toMap.doc = + {{ + Converts a non-empty map to a {type Map}. + + # Example + + ``` + Map.Nonempty.toMap (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.toNonemptyList : Map.Nonempty k v -> List.Nonempty (k, v) +data.Map.Nonempty.toNonemptyList = cases + Map.Nonempty.Bin _ k v l r -> + prependList (Map.toList l) ((k, v) +| Map.toList r) + +data.Map.Nonempty.toNonemptyList.doc : Doc +data.Map.Nonempty.toNonemptyList.doc = + {{ + Converts a {type Map.Nonempty} to a nonempty list of key-value pairs. + + # Example + + ``` + Nonempty.toNonemptyList (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.Nonempty.union : + Map.Nonempty k v -> Map.Nonempty k v -> Map.Nonempty k v +data.Map.Nonempty.union = Map.Nonempty.unionWith const + +data.Map.Nonempty.union.doc : Doc +data.Map.Nonempty.union.doc = + {{ + Merges two {type Map.Nonempty}s, preferring the values from the first map + when there are duplicate keys. + + # Example + + ``` + Map.Nonempty.toList + (Map.Nonempty.union + (toNonemptyMap ((1, 2) +| [(2, 3)])) + (toNonemptyMap ((2, 4) +| [(3, 5)]))) + ``` + }} + +data.Map.Nonempty.unions : List.Nonempty (Map.Nonempty k v) -> Map.Nonempty k v +data.Map.Nonempty.unions maps = reduceRight Map.Nonempty.union maps + +data.Map.Nonempty.unions.doc : Doc +data.Map.Nonempty.unions.doc = + {{ + Merges a nonempty list of {type Map.Nonempty}s, preferring the values from + the first map when there are duplicate keys. + + # Example + + ``` + Map.Nonempty.toList + (Map.Nonempty.unions + (toNonemptyMap ((1, 2) +| [(2, 3)]) + +| [toNonemptyMap ((2, 4) +| [(3, 5)])])) + ``` + }} + +data.Map.Nonempty.unionWith : + (v ->{e} v ->{e} v) + -> Map.Nonempty k v + -> Map.Nonempty k v + ->{e} Map.Nonempty k v +data.Map.Nonempty.unionWith f = Map.Nonempty.unionWithKey (const f) + +data.Map.Nonempty.unionWith.doc : Doc +data.Map.Nonempty.unionWith.doc = + use Nat + + {{ + Merges two {type Map.Nonempty}s, combining values with the given function + when there are duplicate keys. + + # Example + + ``` + Map.Nonempty.toList + (Map.Nonempty.unionWith + (+) + (toNonemptyMap ((1, 2) +| [(2, 3)])) + (toNonemptyMap ((2, 4) +| [(3, 5)]))) + ``` + }} + +data.Map.Nonempty.unionWithKey : + (k ->{e} v ->{e} v ->{e} v) + -> Map.Nonempty k v + -> Map.Nonempty k v + ->{e} Map.Nonempty k v +data.Map.Nonempty.unionWithKey f t1 t2 = + match (t1, t2) with + (_, Map.Nonempty.Bin _ k x internal.Tip internal.Tip) -> + Nonempty.internal.putWithKeyR f k x t1 + (Map.Nonempty.Bin _ k x internal.Tip internal.Tip, _) -> + Nonempty.putWithKey f k x t2 + (Map.Nonempty.Bin _ k1 x1 l1 r1, _) -> + (l2, mb, r2) = splitLookup k1 (Map.Nonempty.toMap t2) + use Map unionWithKey + use Nonempty.internal link + l1l2 = unionWithKey f l1 l2 + r1r2 = unionWithKey f r1 r2 + match mb with + None -> link k1 x1 l1l2 r1r2 + Some x2 -> link k1 (f k1 x1 x2) l1l2 r1r2 + +data.Map.Nonempty.unionWithKey.doc : Doc +data.Map.Nonempty.unionWithKey.doc = + use Nat + + {{ + Merges two {type Map.Nonempty}s, combining values with the given function + when there are duplicate keys. The function is passed the key, the value from + the first map, and the value from the second map, in that order. + + # Example + + ``` + Map.Nonempty.toList + (Map.Nonempty.unionWithKey + (k v1 v2 -> k + v1 + v2) + (toNonemptyMap ((1, 2) +| [(2, 3)])) + (toNonemptyMap ((2, 4) +| [(3, 5)]))) + ``` + }} + +data.Map.Nonempty.update : + (v ->{e} Optional v) -> k -> Map.Nonempty k v ->{e} Map k v +data.Map.Nonempty.update f k = + Map.updateWithKey (const f) k << Map.Nonempty.toMap + +data.Map.Nonempty.update.doc : Doc +data.Map.Nonempty.update.doc = + use Nat + == + {{ + Updates a value at a key in a {type Map.Nonempty}, using the given function + to compute the new value. If the function returns {None}, the key is deleted + from the map. + + # Example + + ``` + Map.toList + (Map.Nonempty.update + (x -> (if x == 2 then None else Some (x + 1))) + 2 + (toNonemptyMap ((1, 2) +| [(2, 3)]))) + ``` + }} + +data.Map.Nonempty.upsert : + (Optional v ->{e} v) -> k -> Map.Nonempty k v ->{e} Map.Nonempty k v +data.Map.Nonempty.upsert f k = cases + Map.Nonempty.Bin sz kx x l r -> + match Universal.ordering k kx with + Less -> Nonempty.internal.balanceL kx x (Map.upsert f k l) r + Greater -> Nonempty.internal.balanceR kx x l (Map.upsert f k r) + Equal -> Map.Nonempty.Bin sz kx (f (Some x)) l r + +data.Map.Nonempty.upsert.doc : Doc +data.Map.Nonempty.upsert.doc = + use Map.Nonempty toList + use Nat + + use Nonempty upsert + {{ + Updates or inserts a value under a key in a {type Map.Nonempty}. The + expression `` upsert f k m `` updates the value under `k` in the + {type Map.Nonempty} `m`, if present, by passing the existing value to the + function `f` (or passing {None} if it's not present). Either way, the value + under `k` becomes the return value of `f`. + + # Examples + + In this example, we increment the value under key `5`, since it's present: + + ``` + m = toNonemptyMap ((5, 10) +| [(3, 21)]) + f = cases + None -> 0 + Some x -> x + 1 + toList (upsert f 5 m) + ``` + + In this example, we insert the value `0` under key `7`, since it's not + present: + + ``` + toNonemptyMap ((5, 10) +| [(3, 21)]) + |> upsert (Optional.fold (do 0) Nat.increment) 7 + |> toList + ``` + }} + +data.Map.Nonempty.values : Map.Nonempty k v -> List.Nonempty v +data.Map.Nonempty.values = cases + Map.Nonempty.Bin _ _ v l r -> + appendList + (prependList (Map.values l) (List.Nonempty.singleton v)) (Map.values r) + +data.Map.Nonempty.values.doc : Doc +data.Map.Nonempty.values.doc = + {{ + Converts a {type Map.Nonempty} to a nonempty list of the values in the map. + + # Example + + ``` + Map.Nonempty.values (toNonemptyMap ((1, 2) +| [(2, 3)])) + ``` + }} + +data.Map.nth : Nat -> Map k v -> Optional (k, v) +data.Map.nth n = cases + internal.Tip -> None + internal.Bin sz k v l r -> + use Nat + - + use data.Map nth + sizel = Map.size l + match Universal.ordering sizel n with + Greater -> nth n l + Equal -> Some (k, v) + Less -> nth (n - sizel + 1) r + +data.Map.nth.doc : Doc +data.Map.nth.doc = + use Map nth + {{ + {{ docExample 2 do i m -> nth i m }} returns the key-value pair in `m` with + the `i`-th smallest key, where `i`=0 is the smallest key (according to + {Universal.ordering}). + + Is the same as {{ + docExample 2 do i as -> List.at i (sortBy at1 (Map.toList as)) }} but doesn't + require instantiating the intermediate {type List}. + + ``` + s = + Map.fromList [(6, "six"), (5, "five"), (4, "four"), (2, "two"), (1, "one")] + List.map (i -> nth i s) (List.range 0 (Map.size s)) + ``` + }} + +test> data.Map.nth.tests = + test.verify do + use Random natIn + Each.repeat 100 + s = + (List.replicate (natIn 0 20) do (natIn 0 20, natIn 0 10)) |> Map.fromList + ensure + (List.somes (List.map (i -> Map.nth i s) (List.range 0 (Map.size s))) + === Map.toList s) + +data.Map.put.doc : Doc +data.Map.put.doc = + {{ + Puts a new key and value in the {type Map}. + + The expression `` Map.put k v m `` puts the key `k` and associated value `v` + in the {type Map} `m`. If the key `k` is already present in the {type Map} + `m`, the old value is replaced with `v`. + }} + +data.Map.putGetWithKey : + (k ->{e} v ->{e} v ->{e} v) -> k -> v -> Map k v ->{e} (Optional v, Map k v) +data.Map.putGetWithKey f kx x = cases + internal.Tip -> (None, Map.singleton kx x) + internal.Bin sy ky y l r -> + match Universal.ordering kx ky with + Less -> + (found, l') = data.Map.putGetWithKey f kx x l + (found, Map.internal.balanceL ky y l' r) + Greater -> + (found, r') = data.Map.putGetWithKey f kx x r + (found, Map.internal.balanceR ky y l r') + Equal -> (Some y, internal.Bin sy kx (f kx x y) l r) + +data.Map.putGetWithKey.doc : Doc +data.Map.putGetWithKey.doc = + {{ + Puts a new key and value in the {type Map} while also getting the old value + under that key, if any. + + The expression `` Map.putGetWithKey f k v m `` is equivalent to + ``(Map.get k m, putWithKey f k v m)``. It returns a pair where the first + element is the value under the key `k` in the {type Map} `m` (or {None} if + `k` is not present) and the second element is the new {type Map} after + putting the value `v` under the key `k` using the function `f` to combine `v` + with the old value. + }} + +test> data.Map.putGetWithKey.tests.putsAndGets = runs 100 do + use gen boolean + m = tests.mapOf boolean boolean () + k = boolean() + v = boolean() + f = logic() + g = logic() + p k n o = f k (g n o) + expect (Map.putGetWithKey p k v m === (Map.get k m, Map.putWithKey p k v m)) + +data.Map.putWith : (v ->{e} v ->{e} v) -> k -> v -> Map k v ->{e} Map k v +data.Map.putWith f = Map.putWithKey (_ x y -> f x y) + +data.Map.putWith.doc : Doc +data.Map.putWith.doc = + use Map fromList putWith toList + use Text ++ + {{ + The expression `` putWith f k v m `` puts a new key `k` and associated value + `v` in the {type Map} `m`. If the key `k` is already present in the + {type Map}, the new value `v` is combined with the old value using the + function `f`. The function `f` will receive the new value `v` as its first + argument and the old value under `k` as the second argument. + + # Examples + + ``` + m = fromList [(5, "a"), (3, "b")] + toList (putWith (++) 5 "x" m) + ``` + + ``` + m = fromList [(5, "a"), (3, "b")] + toList (putWith (++) 7 "x" m) + ``` + + ``` + toList (putWith (++) 5 "x" Map.empty) + ``` + }} + +test> data.Map.putWith.tests.puts = runs 100 do + use Map get + use Nat + + m = tests.mapOf natInOrder natInOrder () + k = natInOrder() + v = natInOrder() + added = Map.putWith (+) k v m + expect match get k m with + None -> get k added === Some v + Some x -> get k added === Some (x + v) + +data.Map.putWithKey : + (k ->{e} v ->{e} v ->{e} v) -> k -> v -> Map k v ->{e} Map k v +data.Map.putWithKey f kx x = cases + internal.Tip -> Map.singleton kx x + internal.Bin sy ky y l r -> + match Universal.ordering kx ky with + Less -> Map.internal.balanceL ky y (data.Map.putWithKey f kx x l) r + Greater -> Map.internal.balanceR ky y l (data.Map.putWithKey f kx x r) + Equal -> internal.Bin sy kx (f kx x y) l r + +data.Map.putWithKey.doc : Doc +data.Map.putWithKey.doc = + use Map putWith toList + use Text ++ + {{ + Adds a key and value to the {type Map}, combining the key, new value and old + value with a function. + + The expression `` Map.putWithKey f k v m `` puts a new key `k` and associated + value `v` in the {type Map} `m`. If the key `k` is already present in `m`, + the new value `v` is combined with the key and old value using the function + `f`. The function `f` will receive the new value `v` as its second argument + and the old value under `k` as the third argument. + + # Examples + + ``` + f new old = new ++ "|" ++ old + m = Map.fromList [(5, "a"), (3, "b")] + toList (putWith f 5 "x" m) + ``` + + ``` + toList (putWith const 7 "x" Map.empty) + ``` + }} + +data.Map.randomChoice : Map k v ->{Exception, Random} (k, v) +data.Map.randomChoice map = + randomIndex = Random.natIn 0 (Map.size map) + Map.nth randomIndex map + |> Optional.toException "Map.randomChoice: empty map" (typeLink Map) + +data.Map.randomChoice.doc : Doc +data.Map.randomChoice.doc = + use Map fromList randomChoice + {{ + Picks a random key-value pair from the given {type Map}. Assumes that the + {type Map} is not empty, so an empty map will raise an {type Exception}. + + # Examples + + ``` + catch do + lcg 4096 do randomChoice (fromList [(0, ?a), (3, ?b), (5, ?c), (7, ?d)]) + ``` + + ``` + catch do + lcg 2510 do randomChoice (fromList [(0, ?a), (3, ?b), (5, ?c), (7, ?d)]) + ``` + }} + +test> data.Map.randomChoice.test = test.verify do + map = Map.fromList [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)] + Each.repeat 1000 + e = Map.randomChoice map + ensure (Map.contains (at1 e) map) + +data.Map.randomKey : Map k v ->{Exception, Random} k +data.Map.randomKey map = Map.randomChoice map |> at1 + +data.Map.randomKey.doc : Doc +data.Map.randomKey.doc = + use Map fromList randomKey + {{ + Picks a random key from the given {type Map}. Assumes that the {type Map} is + not empty, so an empty map will raise an {type Exception}. + + # Examples + + ``` + catch do + lcg 4096 do randomKey (fromList [(0, ?a), (3, ?b), (5, ?c), (7, ?d)]) + ``` + + ``` + catch do + lcg 2510 do randomKey (fromList [(0, ?a), (3, ?b), (5, ?c), (7, ?d)]) + ``` + }} + +data.Map.randomValue : Map k v ->{Exception, Random} v +data.Map.randomValue map = Map.randomChoice map |> at2 + +data.Map.randomValue.doc : Doc +data.Map.randomValue.doc = + use Map fromList randomValue + {{ + Picks a random value from the given {type Map}. Assumes that the {type Map} + is not empty, so an empty map will raise an {type Exception}. + + # Examples + + ``` + catch do + lcg 4096 do randomValue (fromList [(0, ?a), (3, ?b), (5, ?c), (7, ?d)]) + ``` + + ``` + catch do + lcg 2510 do randomValue (fromList [(0, ?a), (3, ?b), (5, ?c), (7, ?d)]) + ``` + }} + +data.Map.singleton : k -> v -> Map k v +data.Map.singleton k x = + use internal Tip + internal.Bin 1 k x Tip Tip + +data.Map.singleton.doc : Doc +data.Map.singleton.doc = + {{ Constructs a new {type Map} with a single key and value. }} + +test> data.Map.singleton.tests.roundtrip = runs 100 do + k = natInOrder() + v = natInOrder() + expect (Map.get k (Map.singleton k v) === Some v) + +data.Map.size : Map k v -> Nat +data.Map.size = cases + internal.Tip -> 0 + internal.Bin sz _ _ _ _ -> sz + +data.Map.size.doc : Doc +data.Map.size.doc = {{ Gets the number of elements in the {type Map}. }} + +test> data.Map.size.tests.numberOfKeys = runs 100 do + m = tests.mapOf natInOrder natInOrder () + expect (List.size (Map.keys m) === Map.size m) + +data.Map.split : k -> Map k a -> (Map k a, Map k a) +data.Map.split k = cases + internal.Tip -> (internal.Tip, internal.Tip) + internal.Bin _ kx x l r -> + match Universal.ordering k kx with + Less -> + (lt, gt) = data.Map.split k l + (lt, Map.internal.link kx x gt r) + Greater -> + (lt, gt) = data.Map.split k r + (Map.internal.link kx x l lt, gt) + Equal -> (l, r) + +data.Map.split.doc : Doc +data.Map.split.doc = + {{ + Splits the {type Map} in two parts around a given key. The expression `` + Map.split k m `` is a pair `` (m1, m2) `` where all keys in `m1` are smaller + than `k` and all keys in `m2` are larger than `k`. Any key equal to `k` is + discarded. + }} + +test> data.Map.split.tests.splits = runs 100 do + use List ++ all + use Map keys + m = tests.mapOf natInOrder natInOrder () + k = natInOrder() + let + (m1, m2) = Map.split k m + smaller = all (x -> Universal.lt x k) (keys m1) + larger = all (x -> Universal.gt x k) (keys m2) + equal = List.none (x -> x === k) (keys m1 ++ keys m2) + expect (smaller && larger && equal) + +data.Map.takeLargest : Nat -> Map k v -> Map k v +data.Map.takeLargest n m = + use Map size + use Nat - == >= + if n == 0 then Map.empty + else + if n >= size m then m + else + match Map.nth (size m - n - 1) m with + None -> m + Some (k, _) -> at2 (Map.split k m) + +data.Map.takeLargest.doc : Doc +data.Map.takeLargest.doc = + use Map fromList toList + {{ + `` takeSmallest n m `` returns a submap of the largest `n` entries of `m` + (ordered by key). Runs in logarithmic time. + + # Examples + + ``` + fromList [(2, 2), (3, 3), (1, 1), (4, 4)] |> takeLargest 2 |> toList + ``` + + ``` + Map.empty |> takeLargest 2 |> toList + ``` + + ``` + fromList [(2, 2), (3, 3), (1, 1), (4, 4)] |> takeLargest 100 |> toList + ``` + + **Also see:** {takeSmallest} + }} + +test> data.Map.takeLargest.tests = + test.verify do + use Each repeat + use Map size + use Nat + + use Random natIn + repeat 100 + kvs = Each.toList do + repeat (natIn 0 25) + (natIn 0 20, natIn 0 20) + m = Map.fromList kvs + k = Each.range 0 (size m) + labeled "takeLargest 0" do ensureEqual (takeLargest 0 m) Map.empty + labeled "takeLargest (n = size)" do + ensureEqual + (Map.toList (takeLargest (size m + each [0, 1, 2]) m)) (Map.toList m) + labeled "takeLargest (consistent with toList impl)" do + use Map toList + mk = toList m + ensureEqual (toList (takeLargest k m)) (takeRight k mk) + +data.Map.takeSmallest : Nat -> Map k v -> Map k v +data.Map.takeSmallest n m = + use Nat == + if n == 0 then Map.empty + else + match Map.nth n m with + None -> m + Some (k, _) -> at1 (Map.split k m) + +data.Map.takeSmallest.doc : Doc +data.Map.takeSmallest.doc = + use Map fromList toList + {{ + `` takeSmallest n m `` returns a submap of the smallest `n` entries of `m` + (ordered by key). Runs in logarithmic time. + + # Examples + + ``` + fromList [(2, 2), (3, 3), (1, 1), (4, 4)] |> takeSmallest 2 |> toList + ``` + + ``` + Map.empty |> takeSmallest 2 |> toList + ``` + + ``` + fromList [(2, 2), (3, 3), (1, 1), (4, 4)] |> takeSmallest 100 |> toList + ``` + + **Also see:** {takeLargest} + }} + +test> data.Map.takeSmallest.tests = + test.verify do + use Each repeat + use Map size + use Nat + + use Random natIn + repeat 100 + kvs = Each.toList do + repeat (natIn 0 25) + (natIn 0 20, natIn 0 20) + m = Map.fromList kvs + k = Each.range 0 (size m) + labeled "takeSmallest 0" do ensureEqual (takeSmallest 0 m) Map.empty + labeled "takeSmallest (n = size)" do + ensureEqual + (Map.toList (takeSmallest (size m + each [0, 1, 2]) m)) (Map.toList m) + labeled "takeSmallest (consistent with toList impl)" do + use Map toList + mk = toList m + ensureEqual (toList (takeSmallest k m)) (List.take k mk) + +data.Map.tests.mapOf.doc : Doc +data.Map.tests.mapOf.doc = + {{ + Constructs a generator for {type Map} with keys of type `k` and values of + type `v`, given generators for `k` and `v`. + }} + +data.Map.toList : Map k v -> [(k, v)] +data.Map.toList = + use List +: + Map.foldRightWithKey (k x xs -> (k, x) +: xs) [] + +data.Map.toList.doc : Doc +data.Map.toList.doc = {{ Gets the list of key-value pairs in the {type Map}. }} + +test> data.Map.toList.tests.roundtrip = + runs 100 do + kvs = + distinctBy + at1 (sortBy at1 (gen.listOf (pairOf natInOrder natInOrder) ())) + expect (Map.toList (Map.fromList kvs) === kvs) + +data.Map.union : Map k v -> Map k v -> Map k v +data.Map.union = Map.unionWith const + +data.Map.union.doc : Doc +data.Map.union.doc = + use Map fromList + {{ + Left-biased union of two {type Map}s. Contains data from both, and uses + values from the first if there are duplicate keys. + + # Example + + ``` + x = fromList [(5, "a"), (3, "b")] + y = fromList [(5, "A"), (7, "C")] + Map.toList (Map.union x y) + ``` + }} + +test> data.Map.union.tests.associative = + runs 100 do laws.associative (tests.mapOf natInOrder Text.ascii) Map.union + +test> data.Map.union.tests.idempotent = runs 100 do + use Map == toList union + x = tests.mapOf Text.ascii natInOrder () + b = union x x == x + if b then expect b else bug (toList (union x x), toList x) + +test> data.Map.union.tests.unit = runs 100 do + use Map == empty union + x = tests.mapOf Text.ascii natInOrder () + expect (union x empty == x && x == union empty x) + +data.Map.unions : [Map k v] -> Map k v +data.Map.unions maps = List.foldRight Map.union Map.empty maps + +data.Map.unions.doc : Doc +data.Map.unions.doc = + use Map singleton toList unions + {{ + Takes a list of maps and unions them together with {Map.union}. If there are + duplicate keys, the first value under that key wins. + + # Examples + + ``` + toList (unions [Map.empty, singleton "a" 1, singleton "b" 2]) + ``` + + ``` + toList (unions [singleton "a" 1, singleton "a" 2]) + ``` + }} + +data.Map.unionWith : (v ->{e} v ->{e} v) -> Map k v -> Map k v ->{e} Map k v +data.Map.unionWith f = Map.unionWithKey (const f) + +data.Map.unionWith.doc : Doc +data.Map.unionWith.doc = + use Map fromList + use Text ++ + {{ + Union of two maps. Contains data from both maps, and uses the given function + to combine values if there are duplicate keys. + + # Example + + ``` + x = fromList [(5, "a"), (3, "b")] + y = fromList [(5, "A"), (7, "C")] + Map.toList (Map.unionWith (++) x y) + ``` + }} + +test> data.Map.unionWith.tests.commutative = runs 100 do + use Map == unionWith + use Nat + + gen = tests.mapOf natInOrder natInOrder + x = gen() + y = gen() + expect (unionWith (+) x y == unionWith (+) y x) + +test> data.Map.unionWith.tests.idempotent = runs 100 do + use Map == + use Nat * + + x = tests.mapOf Text.ascii natInOrder () + expect (Map.unionWith (+) x x == Map.map (v -> v * 2) x) + +test> data.Map.unionWith.tests.unit = runs 100 do + use Map == + use Nat + + x = tests.mapOf Text.ascii natInOrder () + expect (Map.unionWith (+) x Map.empty == x) + +data.Map.unionWithKey : + (k ->{e} v ->{e} v ->{e} v) -> Map k v -> Map k v ->{e} Map k v +data.Map.unionWithKey f t1 t2 = + match (t1, t2) with + (_, internal.Tip) -> t1 + (_, internal.Bin _ k x internal.Tip internal.Tip) -> + Map.internal.putWithKeyR f k x t1 + (internal.Bin _ k x internal.Tip internal.Tip, _) -> + Map.putWithKey f k x t2 + (internal.Tip, _) -> t2 + (internal.Bin _ k1 x1 l1 r1, _) -> + (l2, mb, r2) = splitLookup k1 t2 + use Map.internal link + use data.Map unionWithKey + l1l2 = unionWithKey f l1 l2 + r1r2 = unionWithKey f r1 r2 + match mb with + None -> link k1 x1 l1l2 r1r2 + Some x2 -> link k1 (f k1 x1 x2) l1l2 r1r2 + +data.Map.unionWithKey.doc : Doc +data.Map.unionWithKey.doc = + use Map fromList + use Text ++ + {{ + Union of two maps. Contains data from both maps, and uses the given function + to combine values, and the key, for any duplicate key. + + # Example + + ``` + x = fromList [(5, "a"), (3, "b")] + y = fromList [(5, "A"), (7, "C")] + f key left right = Nat.toText key ++ ":" ++ left ++ "|" ++ right + Map.toList (Map.unionWithKey f x y) + ``` + }} + +test> data.Map.unionWithKey.tests.commutative = runs 100 do + use Map == unionWithKey + use Nat + + gen = tests.mapOf natInOrder natInOrder + x = gen() + y = gen() + f k a b = k + a + b + expect (unionWithKey f x y == unionWithKey f y x) + +test> data.Map.unionWithKey.tests.idempotent = + runs 100 do + use Map == + use Nat + + x = tests.mapOf natInOrder natInOrder () + expect + (Map.unionWithKey (k a b -> k + a + b) x x + == Map.mapWithKey (k v -> k + v + v) x) + +test> data.Map.unionWithKey.tests.unit = runs 100 do + use Map == + use Nat + + x = tests.mapOf natInOrder natInOrder () + expect (Map.unionWithKey (k a b -> k + a + b) x Map.empty == x) + +data.Map.update : (v ->{e} Optional v) -> k -> Map k v ->{e} Map k v +data.Map.update f = Map.updateWithKey (const f) + +data.Map.update.doc : Doc +data.Map.update.doc = + use Map fromList toList update + use Nat + == + {{ + Updates or deletes a value under a key in a {type Map.} The expression `` + update f k m `` updates the value under `k` in the {type Map} `m`, if + present, by passing the existing value to the function `f`. If `f` returns + ``None``, the key `k` is deleted from the {type Map}. If `f` returns + ``Some x``, the value under `k` becomes `x`. + + # Examples + + ``` + m = fromList [(5, 10), (3, 21)] + f x = if Nat.mod x 2 == 0 then Some (x + 1) else None + toList (update f 5 m) + ``` + + ``` + toList (update (x -> Some x) 7 Map.empty) + ``` + + ``` + m = fromList [(5, 10), (3, 21)] + toList (update (const None) 3 m) + ``` + }} + +test> data.Map.update.tests.updates = runs 100 do + use Map get + use Nat + + m = tests.mapOf natInOrder natInOrder () + k = natInOrder() + b = gen.boolean() + h v = if b then Some (k + v) else None + updated = get k (Map.update h k m) + expect match get k m with + None -> updated === None + Some x -> Boolean.not b && updated === None || updated === h x + +data.Map.updateWithKey : + (k ->{e} v ->{e} Optional v) -> k -> Map k v ->{e} Map k v +data.Map.updateWithKey f k = cases + internal.Tip -> internal.Tip + internal.Bin sx kx x l r -> + match Universal.ordering k kx with + Less -> Map.internal.balanceR kx x (data.Map.updateWithKey f k l) r + Greater -> Map.internal.balanceL kx x l (data.Map.updateWithKey f k r) + Equal -> + match f kx x with + Some x' -> internal.Bin sx kx x' l r + None -> glue l r + +data.Map.updateWithKey.doc : Doc +data.Map.updateWithKey.doc = + use Map fromList toList updateWithKey + use Nat + == + {{ + Updates or deletes a value under a key in a {type Map}. The expression `` + updateWithKey f k m `` updates the value under `k` in the {type Map} `m`, if + present, by passing both the key `k` and its value to the function `f`. If + `f` returns {None}, the key `k` is deleted from the {type Map}. If `f` + returns ``Some x``, the value under `k` becomes `x`. + + # Example + + ``` + m = fromList [(5, 10), (3, 21)] + f k x = if Nat.mod x 2 == 0 then Some (x + k) else None + toList (updateWithKey f 5 m) + ``` + + ``` + toList (updateWithKey (k v -> Some k) 7 Map.empty) + ``` + + ``` + m = fromList [(5, 10), (3, 21)] + toList (updateWithKey (k v -> None) 3 m) + ``` + }} + +test> data.Map.updateWithKey.tests.updates = runs 100 do + use Map get + use gen boolean + m = tests.mapOf boolean boolean () + k = boolean() + f = logic() + b = boolean() + h k v = if b then Some (f k v) else None + updated = get k (Map.updateWithKey h k m) + expect match get k m with + None -> updated === None + Some x -> Boolean.not b && updated === None || updated === h k x + +data.Map.upsert : (Optional a ->{g} a) -> k -> Map k a ->{g} Map k a +data.Map.upsert f = Map.alter (f >> Some) + +data.Map.upsert.doc : Doc +data.Map.upsert.doc = + use Map fromList toList upsert + use Nat + + {{ + Updates or inserts a value under a key in a {type Map}. The expression `` + upsert f k m `` updates the value under `k` in the {type Map} `m`, if + present, by passing the existing value to the function `f` (or passing {None} + if it's not present). Either way, the value under `k` becomes the return + value of `f`. + + # Examples + + In this example, we increment the value under key `5`, since it's present: + + ``` + m = fromList [(5, 10), (3, 21)] + f = cases + None -> 0 + Some x -> x + 1 + toList (upsert f 5 m) + ``` + + In this example, we insert the value `0` under key `7`, since it's not + present: + + ``` + fromList [(5, 10), (3, 21)] + |> upsert (Optional.fold (do 0) Nat.increment) 7 + |> toList + ``` + }} + +data.Map.values : Map k v -> [v] +data.Map.values = + use List +: + Map.foldRightWithKey (_ v vs -> v +: vs) [] + +data.Map.values.doc : Doc +data.Map.values.doc = + {{ Gets all the values in a {type Map}, as a {type List}. }} + +test> data.Map.values.tests.roundtrip = + runs 100 do + kvs = + distinctBy + at1 (sortBy at1 (gen.listOf (pairOf natInOrder natInOrder) ())) + m = Map.fromList kvs + expect (Map.values m === List.map at2 kvs) + +data.Multimap.insert : k -> v -> Map k [v] -> Map k [v] +data.Multimap.insert k v m = match Map.get k m with + None -> Map.insert k [v] m + Some vs -> Map.insert k (vs List.:+ v) m + +data.Multimap.insert.doc : Doc +data.Multimap.insert.doc = + use Map empty toList + use Multimap insert + {{ + Inserts a key-value pair into the given {type Map} and returns the resulting + {type Map}. If the key already exists in the {type Map}, the value is added + to the existing {type List} of values. + + # Examples + + ``` + toList (insert "a" 1 (insert "a" 2 (insert "b" 3 empty))) + ``` + + ``` + toList (insert "a" 1 (insert "b" 2 empty)) + ``` + }} + +data.Multimap.lookup : k -> Map k [elem] -> [elem] +data.Multimap.lookup k m = Optional.getOrElse [] (Map.get k m) + +data.Multimap.lookup.doc : Doc +data.Multimap.lookup.doc = + use Map empty + use Multimap insert lookup + {{ + Looks up the given key in the given {type Map} and returns the corresponding + {type List} of values, or the empty {type List} if the key is not present in + the {type Map}. + + # Examples + + ``` + lookup "a" (insert "a" 1 (insert "a" 2 (insert "b" 3 empty))) + ``` + + ``` + lookup "a" (insert "b" 2 empty) + ``` + }} + +data.NatBag.add : Nat -> NatBag -> NatBag +data.NatBag.add n b = toNatBag (add.nonempty n b) + +data.NatBag.add.doc : Doc +data.NatBag.add.doc = + {{ + Adds a single {type Nat} to a {type NatBag}. Constructs a new {type NatBag} + with the element added. If the element already exists in the {type NatBag}, + the number of times it appears in the new {type NatBag} will be one more than + the number of times it appears in the original {type NatBag}. + + # Example + + ``` + NatBag.toList (NatBag.add 2 (NatBag.fromList [1, 2, 3])) + ``` + + # See also + + * {add.nonempty} for a version of this that returns a + {type NatBag.Nonempty} + * {NatBag.addMany} to add a {type List} of {type Nat}s to a {type NatBag}. + * {NatBag.addAll} to add two {type NatBag}s together. + }} + +data.NatBag.add.nonempty : Nat -> NatBag -> NatBag.Nonempty +data.NatBag.add.nonempty n = cases + NatBag m -> NatBag.Nonempty (NatMap.insertWith (Nat.+) n 1 m) + +data.NatBag.add.nonempty.doc : Doc +data.NatBag.add.nonempty.doc = + {{ + Adds a single {type Nat} to a {type NatBag}. Constructs a new + {type NatBag.Nonempty} with the element added. If the element already exists + in the {type NatBag}, the number of times it appears in the new + {type NatBag.Nonempty} will be one more than the number of times it appears + in the original {type NatBag}. + + # Example + + ``` + NatBag.Nonempty.toList (add.nonempty 2 (NatBag.fromList [1, 2, 3])) + ``` + + # See also + + * {NatBag.addMany} to add a {type List} of {type Nat}s to a {type NatBag}. + * {NatBag.addAll} to add two {type NatBag}s together. + }} + +test> data.NatBag.add.nonempty.test = + test.verify do + use List +: + use Random natIn + _ = Each.range 0 100 + x = natIn 0 1000 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + ensureEqual + (NatBag.toList (NatBag.add x (NatBag.fromList xs))) (List.sort (x +: xs)) + +data.NatBag.addAll : NatBag -> NatBag -> NatBag +data.NatBag.addAll = cases + NatBag m1, NatBag m2 -> NatBag (NatMap.unionWith (Nat.+) m1 m2) + +data.NatBag.addAll.doc : Doc +data.NatBag.addAll.doc = + use NatBag fromList + {{ + Adds two {type NatBag}s together. Constructs a new {type NatBag} with the + elements of both {type NatBag}s. The number of times an element appears in + the new {type NatBag} is the sum of the number of times it appears in the two + {type NatBag}s. + + # Example + + ``` + NatBag.toList + (NatBag.addAll (fromList [1, 2, 2, 3]) (fromList [2, 3, 3, 4])) + ``` + + # See also + + * {add.nonempty} to add a single {type Nat} to a {type NatBag}. + * {NatBag.addMany} to add a {type List} of {type Nat}s to a {type NatBag}. + * {NatBag.difference} to subtract one {type NatBag} from another. + * {NatBag.intersect} to find the intersection of two {type NatBag}s. + * {NatBag.union} to find the union of two {type NatBag}s. + }} + +test> data.NatBag.addAll.test = + test.verify do + use List ++ + use NatBag fromList + use Random listOf natIn + _ = Each.range 0 100 + xs = listOf (do natIn 0 1000) do natIn 0 100 + ys = listOf (do natIn 0 1000) do natIn 0 100 + ensureEqual + (NatBag.toList (NatBag.addAll (fromList xs) (fromList ys))) + (Heap.sort (xs ++ ys)) + +data.NatBag.addMany : NatBag -> [Nat] -> NatBag +data.NatBag.addMany = cases + NatBag m, nats -> + NatBag + (List.foldLeft + (nm n -> toNatMap (NatMap.insertWith (Nat.+) n 1 nm)) m nats) + +data.NatBag.addMany.doc : Doc +data.NatBag.addMany.doc = + {{ + Adds a {type List} of {type Nat}s to a {type NatBag}. Constructs a new + {type NatBag} with the elements added. If an element already exists in the + {type NatBag}, the number of times it appears in the new {type NatBag} will + be incremented by the number of times it appears in the {type List}. + + # Example + + ``` + NatBag.toList (NatBag.addMany (NatBag.fromList [1, 2, 3]) [2, 3, 4]) + ``` + + # See also + + * {add.nonempty} to add a single {type Nat} to a {type NatBag}. + * {NatBag.addAll} to add two {type NatBag}s together. + }} + +test> data.NatBag.addMany.test = + test.verify do + use List ++ + use Random listOf natIn + _ = Each.range 0 100 + xs = listOf (do natIn 0 1000) do natIn 0 100 + ns = listOf (do natIn 0 1000) do natIn 0 100 + ensureEqual + (NatBag.toList (NatBag.addMany (NatBag.fromList xs) ns)) + (List.sort (ns ++ xs)) + +data.NatBag.addN : Nat -> Nat -> NatBag -> NatBag +data.NatBag.addN = cases + n, elem, b -> + NatBag.addAll (NatBag.scale n (toNatBag (NatBag.singleton elem))) b + +data.NatBag.addN.doc : Doc +data.NatBag.addN.doc = + use NatBag addN fromList + {{ + Adds a given number of occurrences of a {type Nat} to a {type NatBag}. + + # Examples + + ``` + addN 2 3 (fromList [2, 3, 4]) + ``` + + ``` + addN 2 3 (fromList [1, 4]) + ``` + + # See also + + * {add.nonempty} to add a single occurrence of a {type Nat} to a + {type NatBag}. + * {NatBag.addAll} to add all a whole bag to another. + }} + +test> data.NatBag.addN.test = test.verify do + use Nat + == + use NatBag count + use Random natIn + _ = Each.range 0 100 + x = natIn 0 1000 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + n = natIn 0 100 + b = NatBag.fromList xs + ensure (count x (NatBag.addN n x b) == count x b + n) + +data.NatBag.all : (Nat ->{g} Boolean) ->{g} NatBag ->{g} Boolean +data.NatBag.all p = NatSet.all p << NatBag.toNatSet + +data.NatBag.all.doc : Doc +data.NatBag.all.doc = + use Nat isEven + use NatBag all fromList + {{ + Determines whether all {type Nat}s in a {type NatBag} satisfy a predicate. + + # Examples + + ``` + all isEven (fromList [1, 2, 3, 4]) + ``` + + ``` + all isEven (fromList [2, 4, 6]) + ``` + + # See also + + * {NatBag.any} to determine whether any {type Nat} in a {type NatBag} + satisfies a predicate. + }} + +test> data.NatBag.all.test : [Result] +data.NatBag.all.test = test.verify do + use Nat isEven + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + b = NatBag.fromList xs + ensure (iff (NatBag.all isEven b) (List.all isEven xs)) + +data.NatBag.any : (Nat ->{g} Boolean) ->{g} NatBag ->{g} Boolean +data.NatBag.any p = NatSet.any p << NatBag.toNatSet + +data.NatBag.any.doc : Doc +data.NatBag.any.doc = + use Nat isEven + use NatBag any fromList + {{ + Determines whether any {type Nat} in a {type NatBag} satisfies a predicate. + + # Examples + + ``` + any isEven (fromList [1, 2, 3, 4]) + ``` + + ``` + any isEven (fromList [1, 3, 5]) + ``` + + # See also + + * {NatBag.all} to determine whether all {type Nat}s in a {type NatBag} + satisfy a predicate. + }} + +test> data.NatBag.any.test : [Result] +data.NatBag.any.test = test.verify do + use Nat isEven + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + b = NatBag.fromList xs + ensure (iff (NatBag.any isEven b) (List.any isEven xs)) + +data.NatBag.contains : Nat -> NatBag -> Boolean +data.NatBag.contains n b = + use Nat > + NatBag.count n b > 0 + +data.NatBag.contains.doc : Doc +data.NatBag.contains.doc = + use NatBag contains fromList + {{ + Determines whether a {type NatBag} contains a given {type Nat}. + + # Examples + + ``` + contains 1 (fromList [1, 2, 3, 4]) + ``` + + ``` + contains 5 (fromList [1, 2, 3, 4]) + ``` + + # See also + + * {NatBag.count} to count the number of occurrences of a given {type Nat} + in a {type NatBag}. + }} + +test> data.NatBag.contains.test : [Result] +data.NatBag.contains.test = test.verify do + use Each range + use Random natIn + ignore (range 0 100) + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + b = NatBag.fromList xs + ignore (range 0 1000) + n = natIn 0 1000 + ensure (iff (NatBag.contains n b) (List.contains n xs)) + +data.NatBag.convolve : + (Nat ->{e} Nat ->{e} Nat) -> NatBag -> NatBag ->{e} NatBag +data.NatBag.convolve f b1 b2 = + g k1 v1 m = NatBag.addAll m (NatBag.scale v1 (NatBag.map (k2 -> f k1 k2) b2)) + NatMap.foldWithKey g NatBag.empty (NatBag.counts b1) + +data.NatBag.convolve.doc : Doc +data.NatBag.convolve.doc = + use Nat * + + use NatBag convolve flatMap fromList map toList + {{ + `` convolve f xs ys `` applies the function `f` to every pair of elements `x` + and `y`, where `x` is an element of `xs` and `y` is an element of `ys`. This + is called the **convolution** of `xs` and `ys` with the function `f`. + + # Examples + + ``` + toList (convolve (*) (fromList [1, 2, 3, 4]) (fromList [1, 2, 3, 4])) + ``` + + ``` + toList (convolve (+) (fromList [1, 2, 3, 4]) (fromList [1, 2, 3, 4])) + ``` + + # See also + + {flatMap} is a generalization of {convolve}. For example, the following two + expressions are equivalent: + + ``` + toList (convolve (*) (fromList [1, 2, 3, 4]) (fromList [1, 2, 3, 4])) + ``` + + ``` + toList + (flatMap + (x -> map (y -> x * y) (fromList [1, 2, 3, 4])) (fromList [1, 2, 3, 4])) + ``` + + {map} is a special case of {convolve}: + + ``` + toList (convolve (x _ -> x + 1) (fromList [1, 2, 3, 4]) (fromList [0])) + ``` + + ``` + toList (map (x -> x + 1) (fromList [1, 2, 3, 4])) + ``` + }} + +data.NatBag.count : Nat -> NatBag -> Nat +data.NatBag.count = cases n, NatBag m -> NatMap.getOrElse n 0 m + +data.NatBag.count.doc : Doc +data.NatBag.count.doc = + use NatBag count fromList + {{ + Returns the number of occurrences of a {type Nat} in a {type NatBag}. + + # Examples + + ``` + count 3 (fromList [1, 2, 3, 3, 4]) + ``` + + ``` + count 3 (fromList [1, 2, 4]) + ``` + }} + +test> data.NatBag.count.test : [Result] +data.NatBag.count.test = test.verify do + use Nat == + use Random natIn + _ = Each.range 0 100 + x = natIn 0 1000 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + b = NatBag.fromList xs + ensure (NatBag.count x b == List.size (List.filter (y -> y == x) xs)) + +data.NatBag.counts : NatBag -> NatMap Nat +data.NatBag.counts = cases NatBag m -> m + +data.NatBag.counts.doc : Doc +data.NatBag.counts.doc = + use NatBag counts fromList + {{ + Returns a {type NatMap} containing the number of occurrences of each + {type Nat} in a {type NatBag}. + + # Examples + + ``` + counts (fromList [1, 2, 3, 4]) + ``` + + ``` + counts (fromList [1, 2, 3, 4, 1, 2, 1]) + ``` + + # See also + + * {NatBag.count} to count the number of occurrences of a given {type Nat} + in a {type NatBag}. + }} + +test> data.NatBag.counts.test : [Result] +data.NatBag.counts.test = test.verify do + use Map == + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + b = NatBag.fromList xs + ensure (NatMap.toMap (NatBag.counts b) == Bag.counts (Bag.fromList xs)) + +data.NatBag.difference : NatBag -> NatBag -> NatBag +data.NatBag.difference = cases + NatBag m1, NatBag m2 -> + NatBag + (NatMap.differenceWith + (x y -> Optional.filter (z -> z Nat.> 0) (Some (x Nat.- y))) m1 m2) + +data.NatBag.difference.doc : Doc +data.NatBag.difference.doc = + use NatBag fromList + {{ + Computes the difference between two {type NatBag}s. Constructs a new + {type NatBag} with the elements of the first argument, minus the elements of + the second argument. If an element appears `n` times in the first and `m` + times in the second, the element will appear `n - m` times in the result. If + the number of times an element appears in the second argument is greater than + the number of times it appears in the first, the element will not appear in + the result. + + # Example + + ``` + NatBag.toList (NatBag.difference (fromList [1, 2, 3]) (fromList [2, 3, 4])) + ``` + + # See also + + * {NatBag.union} to compute the union of two bags. + * {NatBag.intersect} to compute the intersection. + * {NatBag.remove} to remove a single element from a bag. + * {NatBag.removeAll} to remove all occurrences of an element. + * {NatBag.removeN} to remove a specific number of occurrences. + }} + +test> data.NatBag.difference.test = + test.verify do + use Nat == + use NatBag fromList + use Random listOf natIn + _ = Each.range 0 100 + xs = listOf (do natIn 0 1000) do natIn 0 100 + ys = listOf (do natIn 0 1000) do natIn 0 100 + zs = List.foldRight (y xs -> deleteFirst ((==) y) xs) xs ys + ensureEqual + (NatBag.toList (NatBag.difference (fromList xs) (fromList ys))) + (List.sort zs) + +data.NatBag.doc : Doc +data.NatBag.doc = + use NatBag filterMap + {{ + A multiset of {type Nat} values. This specialized type is much more efficient + than the generic {type Bag} type when the elements are of type {type Nat} or + can be encoded as {type Nat} values. + + {{ + docAside + {{ + The implementation of {type NatBag} is based on a + [patricia tree](https://en.wikipedia.org/wiki/Radix_tree) and uses a + {type NatMap} for its representation + }} }} + + # Constructing bags + + The empty bag: + + @signature{NatBag.empty} + + A bag with a single value in it. Note that this returns + {type NatBag.Nonempty} rather than {type NatBag}: + + @signature{NatBag.singleton} + + Construct a bag from a list of values: + + @signature{NatBag.fromList} + + Construct a bag from a list of values and their multiplicities: + + @signature{NatBag.fromOccurrenceList} + + Construct a bag from a {type NatSet}: + + @signature{NatBag.fromNatSet}a + + Construct a {type NatBag} from a {type Bag}: + + @signature{fromBag} + + # Querying bags + + Check if a bag is empty: + + @signature{NatBag.isEmpty} + + Check if a bag contains a value: + + @signature{NatBag.contains} + + Get the number of elements in a bag: + + @signature{NatBag.size} + + Get the number of times a value appears in a bag: + + @signature{NatBag.count} + + Get the counts of all values in a bag, as a {type NatMap}: + + @signature{NatBag.counts} + + Get the counts of all values in a bag, as a {type List}: + + @signature{NatBag.occurrenceList} + + Check if all values satisfy a predicate: + + @signature{NatBag.all} + + Check if any values satisfy a predicate: + + @signature{NatBag.any} + + Get the largest value in a bag: + + @signature{NatBag.getMax} + + Get the smallest value in a bag: + + @signature{NatBag.getMin} + + Partition a bag into two bags based on a predicate: + + @signature{NatBag.partition} + + # Adding elements + + Add a value to a bag: + + @signature{NatBag.add} + + Add a value to a bag, returning a nonempty bag: + + @signature{add.nonempty} + + Add a whole bag to a bag: + + @signature{NatBag.addAll} + + Add a list of values to a bag: + + @signature{NatBag.addMany} + + Add N occurrences of a value to a bag: + + @signature{NatBag.addN} + + # Removing elements + + Remove a value from a bag: + + @signature{NatBag.remove} + + Remove all occurrences of a value from a bag: + + @signature{NatBag.removeAll} + + Remove N occurrences of a value from a bag: + + @signature{NatBag.removeN} + + # Combining bags + + Union of two bags: + + @signature{NatBag.union} + + Intersection of two bags: + + @signature{NatBag.intersect} + + Difference between two bags: + + @signature{NatBag.difference} + + # Transforming bags + + Apply function to every element of a bag: + + @signature{NatBag.map} + + Apply a bag-valued function to every element of a bag, collecting the + results into one bag: + + @signature{NatBag.flatMap} + + Apply a partial function to every element of a bag, discarding results that + are {None}: + + @signature{filterMap} + + Apply function to every element of a bag, removing elements for which the + function returns {None}: + + @signature{filterMap} + + Apply a function to all pairs from two bags: + + @signature{NatBag.convolve} + + Remove elements that don't satisfy a predicate: + + @signature{NatBag.filter} + + Reduce a bag to a single value by reducing with a binary function: + + @signatures{NatBag.foldLeft, NatBag.foldRight} + + Scale the counts of all values in a bag: + + @signature{NatBag.scale} + + # Comparing bags + + Check if all elements in one bag occur in another: + + @signature{NatBag.from} + + Check if two bags are exactly equal: + + @signature{NatBag.equals} + + Check if one bag is a subbag of another: + + @signatures{NatBag.subbag, NatBag.superbag} + + # Conversions to other types + + Convert a bag to a list: + + @signature{NatBag.toList} + + Convert to a {type NatSet}: + + @signature{NatBag.toNatSet} + + Convert to a {type Bag}: + + @signature{NatBag.toBag} + + # See also + + * {NatMap} for a {type NatMap} from {type Nat} to any type. + * {NatSet} for a {type NatSet} of {type Nat}s. + }} + +data.NatBag.empty : NatBag +data.NatBag.empty = NatBag NatMap.empty + +data.NatBag.empty.doc : Doc +data.NatBag.empty.doc = + {{ + The empty {type NatBag}. + + # Examples + + ``` + NatBag.toList NatBag.empty + ``` + + # See also + + * {NatBag.isEmpty} to check if a {type NatBag} is empty. + }} + +data.NatBag.equals : NatBag -> NatBag -> Boolean +data.NatBag.equals = cases + NatBag m1, NatBag m2 -> NatMap.equalBy (Nat.==) m1 m2 + +data.NatBag.filter : (Nat ->{e} Boolean) -> NatBag ->{e} NatBag +data.NatBag.filter p = + NatBag << NatMap.filterWithKey (k _ -> p k) << NatBag.counts + +data.NatBag.filter.doc : Doc +data.NatBag.filter.doc = + {{ + Filters a {type NatBag} by removing all {type Nat}s that do not satisfy the + predicate. + + # Examples + + ``` + NatBag.toList + (NatBag.filter Nat.isEven (NatBag.fromList [1, 2, 3, 4, 1, 2, 1])) + ``` + + # See also + + * {NatBag.partition} to partition a {type NatBag} into two {type NatBag}s. + * {NatBag.filterMap} to filter a {type NatBag} with a function that returns + {type Optional}. + }} + +data.NatBag.filterMap : (Nat ->{e} Optional Nat) -> NatBag ->{e} NatBag +data.NatBag.filterMap f b = + g k v m = match f k with + Some k' -> NatBag.addN v k' m + None -> m + NatMap.foldWithKey g NatBag.empty (NatBag.counts b) + +data.NatBag.filterMap.doc : Doc +data.NatBag.filterMap.doc = + use Nat * + {{ + Replaces each {type Nat} in a {type NatBag} with the result of applying a + function to it. If the function returns {None}, the {type Nat} is removed + from the {type NatBag}. If the function returns {Some}, the {type Nat} is + replaced with the {type Nat} in the {Some}. + + # Examples + + ``` + NatBag.toList + (NatBag.filterMap + (x -> (if Nat.isEven x then Some (x * 2) else None)) + (NatBag.fromList [1, 2, 3, 4, 1, 2, 1])) + ``` + + # See also + + * {NatBag.partition} to partition a {type NatBag} into two {type NatBag}s + based on a predicate. + * {NatBag.filter} to filter a {type NatBag} by removing all {type Nat}s + that do not satisfy a predicate. + }} + +data.NatBag.flatMap : (Nat ->{e} NatBag) -> NatBag ->{e} NatBag +data.NatBag.flatMap f b = + g k v m = NatBag.addAll m (NatBag.scale v (f k)) + NatMap.foldWithKey g NatBag.empty (NatBag.counts b) + +data.NatBag.flatMap.doc : Doc +data.NatBag.flatMap.doc = + use NatBag flatMap fromList toList + {{ + Maps each {type Nat} in a {type NatBag} to a {type NatBag} and combines the + results. + + # Examples + + ``` + toList (flatMap (n -> fromList [n, n]) (fromList [1, 2, 3, 4])) + ``` + + ``` + toList (flatMap (n -> fromList [n, n]) (fromList [1, 2, 3, 4, 1, 2, 1])) + ``` + + # See also + + * {NatBag.map} to transform each {type Nat} in a {type NatBag} to a + different {type Nat}. + * {NatBag.scale} to scale the number of occurrences of each {type Nat} in a + {type NatBag}. + }} + +data.NatBag.foldLeft : (a ->{e} Nat ->{e} a) -> a -> NatBag ->{e} a +data.NatBag.foldLeft f a b = + g k v a = f a k + NatMap.foldWithKey g a (NatBag.counts b) + +data.NatBag.foldLeft.doc : Doc +data.NatBag.foldLeft.doc = + use Nat + + {{ + Folds a {type NatBag} with a binary function associating to the left. + + # Examples + + ``` + NatBag.foldLeft (+) 0 (NatBag.fromList [1, 2, 3, 4, 1, 2, 1]) + ``` + + # See also + + * {NatBag.foldRight} to fold a {type NatBag} from right to left. + }} + +data.NatBag.foldRight : (Nat ->{e} a ->{e} a) -> a -> NatBag ->{e} a +data.NatBag.foldRight f a b = + g k v a = f k a + NatMap.foldWithKey g a (NatBag.counts b) + +data.NatBag.foldRight.doc : Doc +data.NatBag.foldRight.doc = + use Nat + + {{ + Folds a {type NatBag} with a binary function associating to the right. + + # Examples + + ``` + NatBag.foldRight (+) 0 (NatBag.fromList [1, 2, 3, 4, 1, 2, 1]) + ``` + + # See also + + * {NatBag.foldLeft} to fold a {type NatBag} from left to right. + }} + +data.NatBag.from : NatBag -> NatBag -> Boolean +data.NatBag.from b1 b2 = + use NatBag toNatSet + NatSet.subset (toNatSet b1) (toNatSet b2) + +data.NatBag.from.doc : Doc +data.NatBag.from.doc = + use NatBag fromList + {{ + Checks if all the {type Nat}s in the first {type NatBag} are also present in + the second {type NatBag}. + + # Examples + + ``` + NatBag.from (fromList [1, 2, 3]) (fromList [1, 2, 3, 4, 1, 2, 1]) + ``` + + # See also + + * {NatBag.subbag} to check if all the {type Nat}s in the first bag occur at + least as many times in the second bag. + * {NatBag.superbag} to check if all the {type Nat}s in the second bag occur + at least as many times in the first bag. + }} + +data.NatBag.fromBag : Bag Nat -> NatBag +data.NatBag.fromBag = cases MkBag m -> NatBag (NatMap.fromMap m) + +data.NatBag.fromBag.doc : Doc +data.NatBag.fromBag.doc = + use Bag fromList + use NatBag toList + {{ + Converts a {type Bag} of {type Nat}s to a {type NatBag}. + + # Examples + + ``` + toList (fromBag (fromList [1, 2, 3, 4])) + ``` + + ``` + toList (fromBag (fromList [1, 2, 3, 4, 1, 2, 1])) + ``` + + # See also + + * {NatBag.toBag} to convert the other way. + }} + +data.NatBag.fromList : [Nat] -> NatBag +data.NatBag.fromList = cases + [] -> NatBag NatMap.empty + xs -> List.foldRight (v m -> toNatBag (add.nonempty v m)) NatBag.empty xs + +data.NatBag.fromList.doc : Doc +data.NatBag.fromList.doc = + use NatBag toList + {{ + Converts a {type List} of {type Nat}s into a {type NatBag}. If an element + appears multiple times in the {type List}, it will appear multiple times in + the {type NatBag}. + + # Example + + ``` + toList (NatBag.fromList [1, 2, 2, 3]) + ``` + + # See also + + * {toList} to convert a {type NatBag} to a {type List}. + * {NatBag.fromNatSet} to convert a {type NatSet} to a {type NatBag}. + }} + +test> data.NatBag.fromList.test = test.verify do + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + ensureEqual (NatBag.toList (NatBag.fromList xs)) (List.sort xs) + +data.NatBag.fromNatSet : NatSet -> NatBag +data.NatBag.fromNatSet = cases + NatSet None -> NatBag NatMap.empty + NatSet (Some t) -> + NatBag + (toNatMap + (NatSet.Nonempty.foldMap + (NatMap.Nonempty.unionWith (Nat.+)) (flip NatMap.singleton 1) t)) + +data.NatBag.fromNatSet.doc : Doc +data.NatBag.fromNatSet.doc = + {{ + Converts a {type NatSet} into a {type NatBag}. + + # Example + + ``` + NatBag.toList (NatBag.fromNatSet (NatSet.fromList [1, 2, 2, 3])) + ``` + + # See also + + * {NatBag.fromList} to convert a {type List} to a {type NatBag}. + * {NatBag.toNatSet} to convert a {type NatBag} to a {type NatSet}. + }} + +test> data.NatBag.fromNatSet.test = + test.verify do + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 0 100 + ensureEqual + (NatBag.toList (NatBag.fromNatSet (NatSet.fromList xs))) + (List.sort (Set.toList (Set.fromList xs))) + +data.NatBag.fromOccurrenceList : [(Nat, Nat)] -> NatBag +data.NatBag.fromOccurrenceList = NatBag << NatMap.fromList + +data.NatBag.fromOccurrenceList.doc : Doc +data.NatBag.fromOccurrenceList.doc = + use NatBag toList + {{ + Converts a list of {type Nat}s and their number of occurrences to a + {type NatBag}. + + # Examples + + ``` + toList (NatBag.fromOccurrenceList [(1, 3), (2, 2), (3, 1), (4, 1)]) + ``` + + # See also + + * {toList} to convert a {type NatBag} to a list of {type Nat}s. + * {NatBag.occurrenceList} to convert a {type NatBag} to a list of + {type Nat}s and their number of occurrences. + }} + +data.NatBag.getMax : NatBag ->{Abort} Nat +data.NatBag.getMax b = NatMap.maxKey (NatBag.counts b) + +data.NatBag.getMax.doc : Doc +data.NatBag.getMax.doc = + {{ + Returns the largest {type Nat} in a {type NatBag}, or calls {abort} if the + {type NatBag} is empty. + + # Examples + + ``` + toOptional! do NatBag.getMax (NatBag.fromList [1, 2, 3, 4, 1, 2, 1]) + ``` + + # See also + + * {NatBag.getMin} to return the smallest {type Nat} in a {type NatBag}. + }} + +data.NatBag.getMin : NatBag ->{Abort} Nat +data.NatBag.getMin b = NatMap.minKey (NatBag.counts b) + +data.NatBag.getMin.doc : Doc +data.NatBag.getMin.doc = + {{ + Returns the smallest {type Nat} in a {type NatBag}, or calls {abort} if the + {type NatBag} is empty. + + # Examples + + ``` + toOptional! do NatBag.getMin (NatBag.fromList [1, 2, 3, 4, 1, 2, 1]) + ``` + + # See also + + * {NatBag.getMax} to return the largest {type Nat} in a {type NatBag}. + }} + +data.NatBag.intersect : NatBag -> NatBag -> NatBag +data.NatBag.intersect = cases + NatBag m1, NatBag m2 -> NatBag (NatMap.intersectWith Nat.min m1 m2) + +data.NatBag.intersect.doc : Doc +data.NatBag.intersect.doc = + use NatBag fromList + {{ + Computes the intersection between two {type NatBag}s. Constructs a new + {type NatBag} with the elements that appear in both arguments. If an element + appears `n` times in the first and `m` times in the second, the element will + appear `` Nat.min n m `` times in the result. + + # Example + + ``` + NatBag.toList (NatBag.intersect (fromList [1, 2, 3]) (fromList [2, 3, 4])) + ``` + + # See also + + * {NatBag.union} to compute the union of two {type NatBag}s. + * {NatBag.difference} to compute the difference between two {type NatBag}s. + * {NatBag.remove} to remove a single {type Nat} from a {type NatBag}. + * {NatBag.removeN} to remove multiple occurrences of a {type Nat}. + * {NatBag.removeAll} to remove all occurrences of a {type Nat}. + }} + +test> data.NatBag.intersect.test = + verifyAndIgnore do + use Nat == + use NatBag fromList + use Random listOf natIn + _ = Each.range 0 100 + xs = listOf (do natIn 0 1000) do natIn 0 100 + ys = listOf (do natIn 0 1000) do natIn 0 100 + zs = List.foldRight (y xs -> deleteFirst ((==) y) xs) xs ys + ensureEqual + (NatBag.toList (NatBag.difference (fromList xs) (fromList ys))) + (List.sort zs) + +data.NatBag.isEmpty : NatBag -> Boolean +data.NatBag.isEmpty = cases NatBag m -> NatMap.isEmpty m + +data.NatBag.isEmpty.doc : Doc +data.NatBag.isEmpty.doc = + use NatBag empty isEmpty + {{ + Checks if a {type NatBag} is empty. + + # Examples + + ``` + isEmpty empty + ``` + + ``` + isEmpty (NatBag.fromList [1, 2, 3, 4, 1, 2, 1]) + ``` + + # See also + + * {empty} to get the empty {type NatBag}. + * {NatBag.count} to get the number of occurrences of a {type Nat} in a + {type NatBag}. + }} + +data.NatBag.map : (Nat ->{e} Nat) -> NatBag ->{e} NatBag +data.NatBag.map f b = + g k v m = NatBag.addN v (f k) m + NatMap.foldWithKey g NatBag.empty (NatBag.counts b) + +data.NatBag.map.doc : Doc +data.NatBag.map.doc = + use Nat * + use NatBag fromList map toList + {{ + Transforms each {type Nat} in a {type NatBag} to a different {type Nat} using + a given function. + + # Examples + + ``` + toList (map (n -> n * 2) (fromList [1, 2, 3, 4])) + ``` + + ``` + toList (map (n -> n * 2) (fromList [1, 2, 3, 4, 1, 2, 1])) + ``` + + # See also + + * {NatBag.flatMap} to map each {type Nat} in a {type NatBag} to a + {type NatBag} and combine the results. + * {NatBag.scale} to scale the number of occurrences of each {type Nat} in a + {type NatBag}. + }} + +data.NatBag.Nonempty.add : Nat -> NatBag.Nonempty -> NatBag.Nonempty +data.NatBag.Nonempty.add n = cases + NatBag.Nonempty m -> NatBag.Nonempty (Nonempty.insertWith (Nat.+) n 1 m) + +data.NatBag.Nonempty.add.doc : Doc +data.NatBag.Nonempty.add.doc = + {{ + Adds a single {type Nat} to a {type NatBag.Nonempty}. Constructs a new + {type NatBag.Nonempty} with the element added. If the element already exists + in the {type NatBag.Nonempty}, the number of times it appears in the new + {type NatBag.Nonempty} will be one more than the number of times it appears + in the original {type NatBag.Nonempty}. + + # Example + + ``` + NatBag.Nonempty.toList + (Nonempty.add 2 (NatBag.Nonempty.fromList (1 +| [2, 3]))) + ``` + + # See also + + * {Nonempty.addMany} to add a {type List} of {type Nat}s to a + {type NatBag.Nonempty}. + * {Nonempty.addAll} to add two {type NatBag.Nonempty}s together. + }} + +test> data.NatBag.Nonempty.add.test = + test.verify do + use List :+ + use Random natIn + _ = Each.range 0 100 + x = natIn 0 1000 + x' = natIn 0 1000 + xs = Random.listOf (do natIn 0 1000) do natIn 0 10 + id + ensureEqual + (List.Nonempty.toList + (NatBag.Nonempty.toList + (Nonempty.add x (NatBag.Nonempty.fromList (x' +| xs))))) + (List.sort (xs :+ x' :+ x)) + +data.NatBag.Nonempty.addAll : + NatBag.Nonempty -> NatBag.Nonempty -> NatBag.Nonempty +data.NatBag.Nonempty.addAll = cases + NatBag.Nonempty m1, NatBag.Nonempty m2 -> + NatBag.Nonempty (NatMap.Nonempty.unionWith (Nat.+) m1 m2) + +data.NatBag.Nonempty.addAll.doc : Doc +data.NatBag.Nonempty.addAll.doc = + use NatBag.Nonempty fromList + {{ + Adds two {type NatBag.Nonempty}s together. + + # Example + + ``` + NatBag.Nonempty.toList + (Nonempty.addAll (fromList (1 +| [2, 2, 3])) (fromList (2 +| [3, 3, 4]))) + ``` + + # See also + + * {Nonempty.add} to add a single {type Nat} to a {type NatBag.Nonempty}. + * {Nonempty.addMany} to add a {type List} of {type Nat}s to a + {type NatBag.Nonempty}. + * {NatBag.Nonempty.difference} to subtract one {type NatBag.Nonempty} from + another. + * {NatBag.Nonempty.intersect} to find the intersection of two + {type NatBag.Nonempty}s. + * {NatBag.Nonempty.union} to find the union of two {type NatBag.Nonempty}s. + }} + +test> data.NatBag.Nonempty.addAll.test = + test.verify do + use List ++ +: + use NatBag.Nonempty fromList + use Random listOf nat natIn + _ = Each.range 0 100 + x = nat() + xs = listOf (do natIn 0 1000) do natIn 0 100 + y = nat() + ys = listOf (do natIn 0 1000) do natIn 0 100 + ensureEqual + (List.Nonempty.toList + (NatBag.Nonempty.toList + (Nonempty.addAll (fromList (x +| xs)) (fromList (y +| ys))))) + (List.sort (x +: xs ++ (y +: ys))) + +data.NatBag.Nonempty.addMany : NatBag.Nonempty -> [Nat] -> NatBag.Nonempty +data.NatBag.Nonempty.addMany = cases + NatBag.Nonempty m, nats -> + NatBag.Nonempty + (List.foldLeft (flip (flip (Nonempty.insertWith (Nat.+)) 1)) m nats) + +data.NatBag.Nonempty.addMany.doc : Doc +data.NatBag.Nonempty.addMany.doc = + {{ + Adds a {type List} of {type Nat}s to a {type NatBag.Nonempty}. Constructs a + new {type NatBag.Nonempty} with the elements added. If an element already + exists in the {type NatBag.Nonempty}, the number of times it appears in the + new {type NatBag.Nonempty} will be incremented by the number of times it + appears in the {type List}. + + # Example + + ``` + NatBag.Nonempty.toList + (Nonempty.addMany (NatBag.Nonempty.fromList (1 +| [2, 3])) [2, 3, 4]) + ``` + + # See also + + * {Nonempty.add} to add a single {type Nat} to a {type NatBag.Nonempty}. + * {Nonempty.addAll} to add two {type NatBag.Nonempty}s together. + }} + +test> data.NatBag.Nonempty.addMany.test = + test.verify do + use List ++ :+ + use Random listOf natIn + _ = Each.range 0 100 + x = Random.nat() + xs = listOf (do natIn 0 1000) do natIn 0 100 + ns = listOf (do natIn 0 1000) do natIn 0 100 + ensureEqual + (List.Nonempty.toList + (NatBag.Nonempty.toList + (Nonempty.addMany (NatBag.Nonempty.fromList (x +| xs)) ns))) + (List.sort (ns ++ xs :+ x)) + +data.NatBag.Nonempty.addN : Nat -> Nat -> NatBag.Nonempty -> NatBag.Nonempty +data.NatBag.Nonempty.addN = cases + 0, _, b -> b + n, elem, NatBag.Nonempty m -> + use Nat + + use NatBag Nonempty + m' = NatMap.Nonempty.adjust (v -> v + n) elem m + if NatMap.Nonempty.contains elem m' then Nonempty m' + else Nonempty (NatMap.Nonempty.insert elem n m') + +data.NatBag.Nonempty.addN.doc : Doc +data.NatBag.Nonempty.addN.doc = + use NatBag.Nonempty fromList + use Nonempty addN + {{ + Adds a given number of occurrences of a {type Nat} to a + {type NatBag.Nonempty}. + + # Examples + + ``` + addN 2 3 (fromList (2 +| [3, 4])) + ``` + + ``` + addN 2 3 (fromList (1 +| [4])) + ``` + + # See also + + * {Nonempty.add} to add a single occurrence of a {type Nat} to a + {type NatBag.Nonempty}. + * {Nonempty.addAll} to add a whole bag to another. + }} + +test> data.NatBag.Nonempty.addN.test = test.verify do + use Nat + == + use Nonempty count + use Random natIn + _ = Each.range 0 100 + x = natIn 0 1000 + y = natIn 0 1000 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + n = natIn 0 100 + b = NatBag.Nonempty.fromList (x +| xs) + ensure (count y (Nonempty.addN n y b) == count y b + n) + +data.NatBag.Nonempty.count : Nat -> NatBag.Nonempty -> Nat +data.NatBag.Nonempty.count = cases + n, NatBag.Nonempty m -> NatMap.Nonempty.getOrElse n 0 m + +data.NatBag.Nonempty.count.doc : Doc +data.NatBag.Nonempty.count.doc = + use NatBag.Nonempty fromList + use Nonempty count + {{ + Returns the number of occurrences of a {type Nat} in a + {type NatBag.Nonempty}. + + # Examples + + ``` + count 3 (fromList (1 +| [2, 3, 3, 4])) + ``` + + ``` + count 3 (fromList (1 +| [2, 4])) + ``` + }} + +test> data.NatBag.Nonempty.count.test = + test.verify do + use List +: + use Nat == + use Random natIn + _ = Each.range 0 100 + x = natIn 0 1000 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + b = NatBag.Nonempty.fromList (x +| xs) + ensure + (Nonempty.count x b == List.size (List.filter (y -> y == x) (x +: xs))) + +data.NatBag.Nonempty.counts : NatBag.Nonempty -> NatMap.Nonempty Nat +data.NatBag.Nonempty.counts = cases NatBag.Nonempty m -> m + +data.NatBag.Nonempty.counts.doc : Doc +data.NatBag.Nonempty.counts.doc = + {{ + Returns the {type NatMap} of counts in a {type NatBag.Nonempty}. + + # Examples + + ``` + Nonempty.counts (NatBag.Nonempty.fromList (1 +| [2, 3, 4, 1, 2, 1])) + ``` + + # See also + + * {NatBag.count} to return the count of a single {type Nat}. + * {Nonempty.occurrenceList} to return a list of all {type Nat}s in the bag, + together with their counts. + }} + +data.NatBag.Nonempty.difference : NatBag.Nonempty -> NatBag.Nonempty -> NatBag +data.NatBag.Nonempty.difference b1 b2 = + NatBag.difference (toNatBag b1) (toNatBag b2) + +data.NatBag.Nonempty.difference.doc : Doc +data.NatBag.Nonempty.difference.doc = + use NatBag.Nonempty fromList + {{ + Computes the difference between two {type NatBag.Nonempty}s. Constructs a new + {type NatBag} with the elements of the first argument, minus the elements of + the second argument. If an element appears `n` times in the first and `m` + times in the second, the element will appear `n - m` times in the result. If + the number of times an element appears in the second argument is greater than + the number of times it appears in the first, the element will not appear in + the result. + + # Example + + ``` + NatBag.toList + (NatBag.Nonempty.difference + (fromList (1 +| [2, 3])) (fromList (2 +| [3, 4]))) + ``` + + # See also + + * {NatBag.Nonempty.union} to compute the union of two bags. + * {NatBag.Nonempty.intersect} to compute the intersection. + * {Nonempty.remove} to remove a single element from a bag. + * {Nonempty.removeAll} to remove all occurrences of an element. + * {Nonempty.removeN} to remove a specific number of occurrences. + }} + +test> data.NatBag.Nonempty.difference.test = + test.verify do + use List +: + use Nat == + use NatBag.Nonempty fromList + use Random listOf nat natIn + _ = Each.range 0 100 + x = nat() + xs = listOf (do natIn 0 1000) do natIn 0 100 + y = nat() + ys = listOf (do natIn 0 1000) do natIn 0 100 + zs = List.foldRight (y xs -> deleteFirst ((==) y) xs) xs ys + ensureEqual + (NatBag.toList + (NatBag.Nonempty.difference (fromList (x +| xs)) (fromList (y +| ys)))) + (List.sort (x +: zs)) + +data.NatBag.Nonempty.equals : NatBag.Nonempty -> NatBag.Nonempty -> Boolean +data.NatBag.Nonempty.equals = cases + NatBag.Nonempty m1, NatBag.Nonempty m2 -> Nonempty.equalBy (Nat.==) m1 m2 + +data.NatBag.Nonempty.equals.doc : Doc +data.NatBag.Nonempty.equals.doc = + use NatBag.Nonempty fromList + {{ + Checks whether two {type NatBag.Nonempty} are equal. Two + {type NatBag.Nonempty} are equal if they contain the same elements with the + same multiplicities. + + # Example + + ``` + NatBag.Nonempty.equals (fromList (1 +| [2, 3])) (fromList (1 +| [2, 3])) + ``` + }} + +test> data.NatBag.Nonempty.equals.test : [Result] +data.NatBag.Nonempty.equals.test = test.verify do + use NatBag.Nonempty equals fromList + use Random listOf natIn + _ = Each.range 0 100 + x = natIn 0 1000 + xs = listOf (do natIn 0 1000) do natIn 0 100 + y = natIn 0 1000 + ys = listOf (do natIn 0 1000) do natIn 0 100 + z = natIn 0 1000 + zs = listOf (do natIn 0 1000) do natIn 0 100 + b1 = fromList (x +| xs) + b2 = fromList (y +| ys) + b3 = fromList (z +| zs) + ensure (equals b1 b1) + ensure (equals b2 b2) + ensure (equals b3 b3) + ensure (iff (equals b1 b2) (equals b2 b1)) + ensure (iff (equals b1 b3) (equals b3 b1)) + ensure (iff (equals b2 b3) (equals b3 b2)) + ensure (iff (equals b1 b2) (equals b1 b2)) + ensure (iff (equals b1 b3) (equals b1 b3)) + ensure (iff (equals b2 b3) (equals b2 b3)) + ensure (implies (equals b1 b2 && equals b2 b3) (equals b1 b3)) + ensure (implies (equals b1 b2 && equals b1 b3) (equals b2 b3)) + ensure (implies (equals b2 b3 && equals b1 b3) (equals b1 b2)) + ensure (implies (equals b2 b3 && equals b2 b1) (equals b1 b3)) + ensure (implies (equals b3 b1 && equals b2 b1) (equals b2 b3)) + ensure (implies (equals b3 b1 && equals b3 b2) (equals b2 b1)) + +data.NatBag.Nonempty.fromList : List.Nonempty Nat -> NatBag.Nonempty +data.NatBag.Nonempty.fromList = + List.Nonempty.foldMap Nonempty.addAll NatBag.singleton + +data.NatBag.Nonempty.fromList.doc : Doc +data.NatBag.Nonempty.fromList.doc = + use NatBag.Nonempty toList + {{ + Converts a {type List.Nonempty} of {type Nat}s into a {type NatBag.Nonempty}. + If an element appears multiple times in the {type List.Nonempty}, it will + appear multiple times in the {type NatBag.Nonempty}. + + # Example + + ``` + toList (NatBag.Nonempty.fromList (1 +| [2, 2, 3])) + ``` + + # See also + + * {toList} to convert a {type NatBag.Nonempty} to a {type List.Nonempty}. + * {Nonempty.fromNatSet} to convert a {type NatSet.Nonempty} to a + {type NatBag.Nonempty}. + }} + +test> data.NatBag.Nonempty.fromList.test : [Result] +data.NatBag.Nonempty.fromList.test = + test.verify do + use List +: + use Random natIn + _ = Each.range 0 100 + x = Random.nat() + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + ensureEqual + (List.Nonempty.toList + (NatBag.Nonempty.toList (NatBag.Nonempty.fromList (x +| xs)))) + (Heap.sort (x +: xs)) + +data.NatBag.Nonempty.fromNatSet : NatSet.Nonempty -> NatBag.Nonempty +data.NatBag.Nonempty.fromNatSet s = + NatSet.Nonempty.foldMap Nonempty.addAll NatBag.singleton s + +data.NatBag.Nonempty.fromNatSet.doc : Doc +data.NatBag.Nonempty.fromNatSet.doc = + {{ + Converts a {type NatSet.Nonempty} into a {type NatBag.Nonempty}. If an + element appears multiple times in the {type NatSet.Nonempty}, it will appear + multiple times in the {type NatBag.Nonempty}. + + # Example + + ``` + NatBag.Nonempty.toList + (Nonempty.fromNatSet (NatSet.Nonempty.fromList (1 +| [2, 2, 3]))) + ``` + + # See also + + * {NatBag.Nonempty.toNatSet} to convert a {type NatBag.Nonempty} to a + {type NatSet.Nonempty}. + * {NatBag.Nonempty.fromList} to convert a {type List.Nonempty} to a + {type NatBag.Nonempty}. + }} + +test> data.NatBag.Nonempty.fromNatSet.test : [Result] +data.NatBag.Nonempty.fromNatSet.test = + test.verify do + use List +: + use Random natIn + _ = Each.range 0 100 + x = Random.nat() + xs = Random.listOf (do natIn 0 100) do natIn 0 100 + ensureEqual + (List.Nonempty.toList + (NatBag.Nonempty.toList + (Nonempty.fromNatSet (NatSet.Nonempty.fromList (x +| xs))))) + (distinct (Heap.sort (x +: xs))) + +data.NatBag.Nonempty.fromOccurrenceList : + List.Nonempty (Nat, Nat) -> NatBag.Nonempty +data.NatBag.Nonempty.fromOccurrenceList l = + NatBag.Nonempty (NatMap.Nonempty.fromList l) + +data.NatBag.Nonempty.fromOccurrenceList.doc : Doc +data.NatBag.Nonempty.fromOccurrenceList.doc = + {{ + Constructs a {type NatBag.Nonempty} from a non-empty list of {type Nat}s and + their counts. + + # Examples + + ``` + NatBag.Nonempty.toList + (Nonempty.fromOccurrenceList ((1, 2) +| [(2, 3), (3, 4), (4, 1)])) + ``` + + # See also + + * {Nonempty.occurrenceList} to return a list of all {type Nat}s in the bag, + together with their counts. + * {NatBag.Nonempty.fromList} to construct a {type NatBag.Nonempty} from a + non-empty list of {type Nat}s. + }} + +data.NatBag.Nonempty.getMax : NatBag.Nonempty -> Nat +data.NatBag.Nonempty.getMax b = Nonempty.maxKey (Nonempty.counts b) + +data.NatBag.Nonempty.getMax.doc : Doc +data.NatBag.Nonempty.getMax.doc = + {{ + Returns the largest {type Nat} in a {type NatBag.Nonempty}. + + # Examples + + ``` + NatBag.Nonempty.getMax (NatBag.Nonempty.fromList (1 +| [2, 3, 4, 1, 2, 1])) + ``` + + # See also + + * {NatBag.Nonempty.getMin} to return the smallest {type Nat} in a + {type NatBag.Nonempty}. + }} + +data.NatBag.Nonempty.getMin : NatBag.Nonempty -> Nat +data.NatBag.Nonempty.getMin b = Nonempty.minKey (Nonempty.counts b) + +data.NatBag.Nonempty.getMin.doc : Doc +data.NatBag.Nonempty.getMin.doc = + {{ + Returns the smallest {type Nat} in a {type NatBag.Nonempty}. + + # Examples + + ``` + NatBag.Nonempty.getMin (NatBag.Nonempty.fromList (1 +| [2, 3, 4, 1, 2, 1])) + ``` + + # See also + + * {NatBag.Nonempty.getMax} to return the largest {type Nat} in a + {type NatBag.Nonempty}. + }} + +data.NatBag.Nonempty.intersect : NatBag.Nonempty -> NatBag.Nonempty -> NatBag +data.NatBag.Nonempty.intersect = cases + NatBag.Nonempty m1, NatBag.Nonempty m2 -> + NatBag (NatMap.Nonempty.intersectWith Nat.min m1 m2) + +data.NatBag.Nonempty.intersect.doc : Doc +data.NatBag.Nonempty.intersect.doc = + use NatBag.Nonempty fromList + {{ + Computes the intersection between two {type NatBag.Nonempty}s. Constructs a + new {type NatBag} with the elements that appear in both arguments. If an + element appears `n` times in the first and `m` times in the second, the + element will appear `` Nat.min n m `` times in the result. + + # Example + + ``` + NatBag.toList + (NatBag.Nonempty.intersect + (fromList (1 +| [2, 3])) (fromList (2 +| [3, 4]))) + ``` + + # See also + + * {NatBag.Nonempty.union} to compute the union of two + {type NatBag.Nonempty}s. + * {NatBag.Nonempty.difference} to compute the difference between two + {type NatBag.Nonempty}s. + * {Nonempty.remove} to remove a single {type Nat} from a + {type NatBag.Nonempty}. + * {Nonempty.removeN} to remove multiple occurrences of a {type Nat}. + * {NatBag.removeAll} to remove all occurrences of a {type Nat}. + }} + +data.NatBag.Nonempty.nth : Nat -> NatBag.Nonempty -> Optional Nat +data.NatBag.Nonempty.nth index bag = + use Nat + - + loop : Nat -> NatMap.Nonempty Nat -> Nat -> (Optional Nat, Nat) + loop index map visited = match map with + NatMap.Nonempty.Tip key value -> + match Universal.ordering (index - visited) value with + Less -> (Some key, visited + value) + otherwise -> (None, visited + value) + NatMap.Nonempty.Bin p m sz l r -> + match loop index l visited with + (Some k, v) -> (Some k, v) + (None, v) -> loop index r v + loop index (Nonempty.counts bag) 0 |> at1 + +data.NatBag.Nonempty.nth.doc : Doc +data.NatBag.Nonempty.nth.doc = + use NatBag.Nonempty nth + {{ + {{ docExample 2 do i b -> nth i b }} returns the `i`-th element in `b`, where + `i`=0 is the smallest element (according to {Universal.ordering}). + + Is the same as {{ + docExample 2 do i as -> Nonempty.at i (NatBag.Nonempty.toList as) }} but + doesn't require instantiating the intermediate {type List}. + + ``` + b = NatBag.Nonempty.fromList (3 +| [1, 2, 3, 4, 5, 1, 1]) + List.map (i -> nth i b) (List.range 0 (NatBag.Nonempty.size b)) + ``` + }} + +test> data.NatBag.Nonempty.nth.tests = + test.verify do + use Random natIn + Each.repeat 100 + s = + natIn 0 10 +| (List.replicate (natIn 0 19) do natIn 0 10) + |> NatBag.Nonempty.fromList + ensure + (List.somes + (List.map + (i -> NatBag.Nonempty.nth i s) + (List.range 0 (NatBag.Nonempty.size s))) + === (NatBag.Nonempty.toList s |> List.Nonempty.toList)) + +data.NatBag.Nonempty.occurrenceList : + NatBag.Nonempty -> List.Nonempty (Nat, Nat) +data.NatBag.Nonempty.occurrenceList b = + NatMap.Nonempty.toList (Nonempty.counts b) + +data.NatBag.Nonempty.occurrenceList.doc : Doc +data.NatBag.Nonempty.occurrenceList.doc = + {{ + Returns a list of all {type Nat}s in a {type NatBag.Nonempty}, together with + their counts. + + # Examples + + ``` + Nonempty.occurrenceList + (NatBag.Nonempty.fromList (1 +| [2, 3, 4, 1, 2, 1])) + ``` + + # See also + + * {Nonempty.fromOccurrenceList} to construct a {type NatBag.Nonempty} from + a non-empty list of {type Nat}s and their counts. + * {Nonempty.counts} to return the {type NatMap} of elements and their + counts. + * {NatBag.Nonempty.toList} to return a list of all {type Nat}s in the bag. + }} + +data.NatBag.Nonempty.randomChoice : NatBag.Nonempty ->{Random} Nat +data.NatBag.Nonempty.randomChoice bag = + randomIndex = Random.natIn 0 (NatBag.Nonempty.size bag) + NatBag.Nonempty.nth randomIndex bag + |> getOrBug "NatBag.Nonempty.randomChoice: index out of bounds" + +test> data.NatBag.Nonempty.randomChoice.test = test.verify do + list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 1, 4, 6, 6] + set = Set.fromList list + bag = NatBag.Nonempty.fromList (Abort.toBug do List.nonempty list) + Each.repeat 1000 + e = NatBag.Nonempty.randomChoice bag + ensure (Set.contains e set) + +data.NatBag.Nonempty.remove : Nat -> NatBag.Nonempty -> NatBag +data.NatBag.Nonempty.remove = Nonempty.removeN 1 + +data.NatBag.Nonempty.remove.doc : Doc +data.NatBag.Nonempty.remove.doc = + {{ + Removes a {type Nat} from a {type NatBag.Nonempty}. Constructs a new + {type NatBag} with the element removed. If the element appears `n` times in + the {type NatBag}, the element will appear `n - 1` times in the result. If + the element does not appear in the {type NatBag}, the result will be the same + as the original {type NatBag}. + + # Example + + ``` + NatBag.toList (Nonempty.remove 2 (NatBag.Nonempty.fromList (1 +| [2, 3]))) + ``` + + # See also + + * {Nonempty.removeN} to remove multiple {type Nat}s from a + {type NatBag.Nonempty}. + * {Nonempty.removeAll} to remove all occurrences of a {type Nat} from a + {type NatBag.Nonempty}. + * {Nonempty.add} to add a single {type Nat} to a {type NatBag.Nonempty}. + * {Nonempty.addN} to add multiple occurrences of a {type Nat} to a + {type NatBag.Nonempty}. + * {Nonempty.addAll} to add a whole bag to another. + }} + +test> data.NatBag.Nonempty.remove.test : [Result] +data.NatBag.Nonempty.remove.test = + test.verify do + use Random natIn + _ = Each.range 0 10 + x = natIn 0 10 + xs = Random.listOf (do natIn 0 10) do natIn 0 10 + ensureEqual + (NatBag.toList (Nonempty.remove x (NatBag.Nonempty.fromList (x +| xs)))) + (Heap.sort xs) + +data.NatBag.Nonempty.removeAll : Nat -> NatBag.Nonempty -> NatBag +data.NatBag.Nonempty.removeAll = cases + n, NatBag.Nonempty m -> NatBag (NatMap.Nonempty.alter (const None) n m) + +data.NatBag.Nonempty.removeAll.doc : Doc +data.NatBag.Nonempty.removeAll.doc = + use NatBag toList + use NatBag.Nonempty fromList + use Nonempty removeAll + {{ + Removes all occurrences of a {type Nat} from a {type NatBag.Nonempty}. + Returns a (possibly empty) {type NatBag}. + + # Examples + + ``` + toList (removeAll 3 (fromList (1 +| [2, 3, 3, 4]))) + ``` + + ``` + toList (removeAll 3 (fromList (1 +| [2, 4]))) + ``` + + # See also + + * {Nonempty.remove} to remove a single occurrence of a {type Nat} from a + {type NatBag.Nonempty}. + * {Nonempty.removeN} to remove a specific number of occurrences of a + {type Nat} from a {type NatBag.Nonempty}. + * {NatBag.Nonempty.difference} to subtract one bag from another. + }} + +test> data.NatBag.Nonempty.removeAll.test : [Result] +data.NatBag.Nonempty.removeAll.test = + test.verify do + use Nat != + use Random natIn + _ = Each.range 0 100 + x = natIn 0 1000 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + b = NatBag.Nonempty.fromList (x +| xs) + ensure + (NatBag.equals + (Nonempty.removeAll x b) + (NatBag.fromList (List.filter (z -> z != x) xs))) + +data.NatBag.Nonempty.removeMax : NatBag.Nonempty -> NatBag +data.NatBag.Nonempty.removeMax b = + max = NatBag.Nonempty.getMax b + Nonempty.remove max b + +data.NatBag.Nonempty.removeMax.doc : Doc +data.NatBag.Nonempty.removeMax.doc = + {{ + Removes one occurrence of the largest {type Nat} in a {type NatBag.Nonempty}. + + # Examples + + ``` + NatBag.toList + (removeMax (NatBag.Nonempty.fromList (1 +| [2, 3, 4, 1, 2, 1]))) + ``` + + # See also + + * {removeMin} to remove the smallest {type Nat}. + }} + +data.NatBag.Nonempty.removeMin : NatBag.Nonempty -> NatBag +data.NatBag.Nonempty.removeMin b = + min = NatBag.Nonempty.getMin b + Nonempty.remove min b + +data.NatBag.Nonempty.removeMin.doc : Doc +data.NatBag.Nonempty.removeMin.doc = + {{ + Removes one occurrence of the smallest {type Nat} from a + {type NatBag.Nonempty}. + + # Examples + + ``` + NatBag.toList + (removeMin (NatBag.Nonempty.fromList (1 +| [2, 3, 4, 1, 2, 1]))) + ``` + + # See also + + * {removeMax} to remove the largest {type Nat}. + }} + +data.NatBag.Nonempty.removeN : Nat -> Nat -> NatBag.Nonempty -> NatBag +data.NatBag.Nonempty.removeN = cases + n, elem, NatBag.Nonempty m -> + NatBag + (NatMap.Nonempty.alter + (Optional.filter (z -> z Nat.> 0) << Optional.map (z -> z Nat.- n)) + elem + m) + +data.NatBag.Nonempty.removeN.doc : Doc +data.NatBag.Nonempty.removeN.doc = + use NatBag.Nonempty fromList + use Nonempty removeN + {{ + Removes a given number of occurrences of a {type Nat} from a + {type NatBag.Nonempty}. If the number of occurrences of the {type Nat} in the + {type NatBag.Nonempty} is less than the given number, then the {type Nat} is + removed entirely. + + # Examples + + ``` + removeN 2 3 (fromList (2 +| [3, 4])) + ``` + + ``` + removeN 2 3 (fromList (1 +| [3, 3, 3, 4])) + ``` + + # See also + + * {Nonempty.remove} to remove a single occurrence of a {type Nat} from a + {type NatBag.Nonempty}. + * {Nonempty.removeAll} to remove all occurrences of a {type Nat} from a + {type NatBag.Nonempty}. + * {NatBag.Nonempty.difference} to subtract a {type NatBag.Nonempty} from + another. + }} + +test> data.NatBag.Nonempty.removeN.test : [Result] +data.NatBag.Nonempty.removeN.test = test.verify do + use Nat - == + use Random natIn + _ = Each.range 0 100 + x = natIn 0 1000 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + n = natIn 0 100 + b = NatBag.Nonempty.fromList (x +| xs) + ensure (NatBag.count x (Nonempty.removeN n x b) == Nonempty.count x b - n) + +data.NatBag.Nonempty.size : NatBag.Nonempty -> Nat +data.NatBag.Nonempty.size b = + use Nat + + Nonempty.fold (+) 0 (Nonempty.counts b) + +data.NatBag.Nonempty.size.doc : Doc +data.NatBag.Nonempty.size.doc = + {{ + Returns the sum of all occurrences of all {type Nat} in a + {type NatBag.Nonempty}. + + # Examples + + ``` + NatBag.Nonempty.size + (NatBag.Nonempty.fromList (Nonempty.Nonempty 0 [1, 2, 3, 4, 1, 2, 1])) + ``` + }} + +data.NatBag.Nonempty.subbag : NatBag.Nonempty -> NatBag.Nonempty -> Boolean +data.NatBag.Nonempty.subbag = flip Nonempty.superbag + +data.NatBag.Nonempty.subbag.doc : Doc +data.NatBag.Nonempty.subbag.doc = + use NatBag.Nonempty fromList + use Nonempty subbag + {{ + Determines whether a {type NatBag.Nonempty} is a __subbag__ of another. + + A {type NatBag.Nonempty} is a subbag of another if it contains as many or + fewer occurrences of each {type Nat} as the other. + + Every {type NatBag.Nonempty} is a subbag of itself. + + # Examples + + ``` + subbag (fromList (2 +| [3, 4])) (fromList (1 +| [2, 3, 4])) + ``` + + ``` + subbag (fromList (2 +| [3, 4])) (fromList (1 +| [2, 3, 3, 4])) + ``` + + # See also + + * {Nonempty.superbag} to determine whether a {type NatBag.Nonempty} is a + superbag of another. + * {NatBag.Nonempty.intersect} to compute the largest {type NatBag} that is + a subbag of both of two bags. + * {NatBag.Nonempty.union} to compute the smallest {type NatBag.Nonempty} + that is a superbag of both of two bags. + }} + +test> data.NatBag.Nonempty.subbag.test : [Result] +data.NatBag.Nonempty.subbag.test = test.verify do + use NatBag.Nonempty fromList + use Random listOf natIn + _ = Each.range 0 100 + x = natIn 0 1000 + y = natIn 0 1000 + xs = listOf (do natIn 0 1000) do natIn 0 100 + ys = listOf (do natIn 0 1000) do natIn 0 100 + b1 = fromList (x +| xs) + b2 = fromList (y +| ys) + i = NatBag.Nonempty.intersect b1 b2 + u = NatBag.Nonempty.union b1 b2 + ensure (Nonempty.subbag b1 b1) + ensure (Nonempty.subbag b2 b2) + ensure (NatBag.subbag i (toNatBag b1)) + ensure (NatBag.subbag i (toNatBag b2)) + ensure (Nonempty.subbag b1 u) + ensure (Nonempty.subbag b2 u) + +data.NatBag.Nonempty.superbag : NatBag.Nonempty -> NatBag.Nonempty -> Boolean +data.NatBag.Nonempty.superbag = cases + NatBag.Nonempty m1, NatBag.Nonempty m2 -> + Nonempty.isSubmapOfBy (Nat.<=) m2 m1 + +data.NatBag.Nonempty.superbag.doc : Doc +data.NatBag.Nonempty.superbag.doc = + use NatBag.Nonempty fromList + {{ + Checks whether the first {type NatBag.Nonempty} is a __superbag__ of the + second. A {type NatBag.Nonempty} is a superbag of another if it contains all + the elements of the other, and possibly more. + + Returns `` true `` if the first {type NatBag.Nonempty} contains at least as + many occurrences of each element as the second. + + Every {type NatBag.Nonempty} is a superbag of itself. + + # Example + + ``` + Nonempty.superbag (fromList (1 +| [2, 3])) (fromList (2 +| [3, 4])) + ``` + + # See also + + * {Nonempty.subbag} to check whether a {type NatBag.Nonempty} is a subbag + of another. + * {NatBag.Nonempty.union} to compute the largest bag that is a superbag of + both arguments. + * {NatBag.Nonempty.intersect} to compute the smallest bag that is a subbag + of both arguments. + }} + +test> data.NatBag.Nonempty.superbag.test : [Result] +data.NatBag.Nonempty.superbag.test = test.verify do + use NatBag.Nonempty fromList union + use Nonempty superbag + use Random listOf nat natIn + _ = Each.range 0 100 + x = nat() + xs = listOf (do natIn 0 1000) do natIn 0 100 + y = nat() + ys = listOf (do natIn 0 1000) do natIn 0 100 + b1 = fromList (x +| xs) + b2 = fromList (y +| ys) + ensure (superbag b1 b1) + ensure (superbag b2 b2) + ensure (superbag (union b1 b2) b1) + ensure (superbag (union b1 b2) b2) + +data.NatBag.Nonempty.toBag : NatBag.Nonempty -> Bag Nat +data.NatBag.Nonempty.toBag = cases + NatBag.Nonempty m -> MkBag (Map.Nonempty.toMap (NatMap.Nonempty.toMap m)) + +data.NatBag.Nonempty.toBag.doc : Doc +data.NatBag.Nonempty.toBag.doc = + use Bag toList + use NatBag.Nonempty fromList + use Nonempty toBag + {{ + Converts a {type NatBag.Nonempty} to a {type Bag} of {type Nat}s. + + # Examples + + ``` + toList (toBag (fromList (1 +| [2, 3, 3, 4]))) + ``` + + ``` + toList (toBag (fromList (1 +| [2, 4]))) + ``` + }} + +test> data.NatBag.Nonempty.toBag.test : [Result] +data.NatBag.Nonempty.toBag.test = test.verify do + use Bag == + use List +: + use Random natIn + _ = Each.range 0 100 + x = natIn 0 1000 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + b = NatBag.Nonempty.fromList (x +| xs) + ensure (Nonempty.toBag b == Bag.fromList (x +: xs)) + +data.NatBag.Nonempty.toList : NatBag.Nonempty -> List.Nonempty Nat +data.NatBag.Nonempty.toList = cases + NatBag.Nonempty m -> + foldMapWithKey (Nonempty.++) (cases k, v -> k +| List.fill (v Nat.- 1) k) m + +data.NatBag.Nonempty.toList.doc : Doc +data.NatBag.Nonempty.toList.doc = + use NatBag.Nonempty fromList + {{ + Converts a {type NatBag.Nonempty} into a {type List.Nonempty} of {type Nat}s. + If an element appears multiple times in the {type NatBag.Nonempty}, it will + appear multiple times in the {type List.Nonempty}. + + # Example + + ``` + NatBag.Nonempty.toList (fromList (1 +| [2, 2, 3])) + ``` + + # See also + + * {fromList} to convert a {type List.Nonempty} to a {type NatBag.Nonempty}. + * {NatBag.Nonempty.toNatSet} to convert a {type NatBag.Nonempty} to a + {type NatSet.Nonempty}. + }} + +test> data.NatBag.Nonempty.toList.test : [Result] +data.NatBag.Nonempty.toList.test = + verifyAndIgnore do + use List +: + use Random natIn + _ = Each.range 0 100 + x = Random.nat() + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + ensureEqual + (List.Nonempty.toList + (NatBag.Nonempty.toList (NatBag.Nonempty.fromList (x +| xs)))) + (Heap.sort (x +: xs)) + +data.NatBag.Nonempty.toNatBag : NatBag.Nonempty -> NatBag +data.NatBag.Nonempty.toNatBag = cases + NatBag.Nonempty m -> NatBag (NatMap (Some m)) + +data.NatBag.Nonempty.toNatBag.doc : Doc +data.NatBag.Nonempty.toNatBag.doc = + {{ + Converts a {type NatBag.Nonempty} to a {type NatBag}. + + # Example + + ``` + NatBag.toList (toNatBag (NatBag.Nonempty.fromList (2 +| [3, 4]))) + ``` + }} + +data.NatBag.Nonempty.toNatSet : NatBag.Nonempty -> NatSet.Nonempty +data.NatBag.Nonempty.toNatSet = cases NatBag.Nonempty m -> Nonempty.keySet m + +data.NatBag.Nonempty.toNatSet.doc : Doc +data.NatBag.Nonempty.toNatSet.doc = + {{ + Converts a {type NatBag.Nonempty} into a {type NatSet.Nonempty}. If an + element appears multiple times in the {type NatBag.Nonempty}, it will appear + only once in the {type NatSet.Nonempty}. + + # Example + + ``` + Nonempty.toListAscending + (NatBag.Nonempty.toNatSet (NatBag.Nonempty.fromList (1 +| [2, 2, 3]))) + ``` + + # See also + + * {Nonempty.fromNatSet} to convert a {type NatSet.Nonempty} to a + {type NatBag.Nonempty}. + * {NatBag.Nonempty.toList} to convert a {type NatBag.Nonempty} to a + {type List.Nonempty}. + }} + +test> data.NatBag.Nonempty.toNatSet.test : [Result] +data.NatBag.Nonempty.toNatSet.test = + test.verify do + use List +: + use Random natIn + _ = Each.range 0 100 + x = Random.nat() + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + ensure + (List.Nonempty.toList + (Nonempty.toListAscending + (NatBag.Nonempty.toNatSet (NatBag.Nonempty.fromList (x +| xs)))) + === distinct (Heap.sort (x +: xs))) + +data.NatBag.Nonempty.union : + NatBag.Nonempty -> NatBag.Nonempty -> NatBag.Nonempty +data.NatBag.Nonempty.union = cases + NatBag.Nonempty m1, NatBag.Nonempty m2 -> + NatBag.Nonempty (NatMap.Nonempty.unionWith Nat.max m1 m2) + +data.NatBag.Nonempty.union.doc : Doc +data.NatBag.Nonempty.union.doc = + use NatBag.Nonempty fromList + {{ + Computes the union of two {type NatBag.Nonempty}s. This is the smallest + {type NatBag.Nonempty} that is a {Nonempty.superbag} of both arguments. + Returns a new {type NatBag.Nonempty} with the elements that appear in either + argument, such that if an element appears `n` times in the first and `m` + times in the second, the element will appear `` Nat.max n m `` times in the + result. + + # Example + + ``` + NatBag.Nonempty.toList + (NatBag.Nonempty.union (fromList (1 +| [2, 3])) (fromList (2 +| [3, 4]))) + ``` + + # See also + + * {NatBag.Nonempty.intersect} to compute the intersection between two + {type NatBag.Nonempty}s. + * {NatBag.Nonempty.difference} to compute the difference between two + {type NatBag.Nonempty}s. + * {Nonempty.add} to add a single {type Nat} to a {type NatBag.Nonempty}. + * {Nonempty.addN} to add multiple occurrences of a {type Nat} to a + {type NatBag.Nonempty}. + * {Nonempty.addAll} to add a whole bag to another. + }} + +test> data.NatBag.Nonempty.union.test : [Result] +data.NatBag.Nonempty.union.test = test.verify do + use NatBag.Nonempty fromList union + use Nonempty superbag + use Random listOf nat natIn + _ = Each.range 0 100 + x = nat() + xs = listOf (do natIn 0 1000) do natIn 0 100 + y = nat() + ys = listOf (do natIn 0 1000) do natIn 0 100 + b1 = fromList (x +| xs) + b2 = fromList (y +| ys) + ensure (superbag (union b1 b2) b1) + ensure (superbag (union b1 b2) b2) + +data.NatBag.nth : Nat -> NatBag -> Optional Nat +data.NatBag.nth index bag = match NatBag.counts bag with + NatMap None -> None + NatMap (Some nonemptyMap) -> + use Nat + - + loop : Nat -> NatMap.Nonempty Nat -> Nat -> (Optional Nat, Nat) + loop index map visited = match map with + NatMap.Nonempty.Tip key value -> + match Universal.ordering (index - visited) value with + Less -> (Some key, visited + value) + otherwise -> (None, visited + value) + NatMap.Nonempty.Bin p m sz l r -> + match loop index l visited with + (Some k, v) -> (Some k, v) + (None, v) -> loop index r v + loop index nonemptyMap 0 |> at1 + +data.NatBag.nth.doc : Doc +data.NatBag.nth.doc = + use NatBag nth + {{ + {{ docExample 2 do i b -> nth i b }} returns the `i`-th element in `b`, where + `i`=0 is the smallest element (according to {Universal.ordering}). + + Is the same as {{ docExample 2 do i as -> List.at i (NatBag.toList as) }} but + doesn't require instantiating the intermediate {type List}. + + ``` + b = NatBag.fromList [3, 1, 2, 3, 4, 5, 1, 1] + List.map (i -> nth i b) (List.range 0 (NatBag.size b)) + ``` + }} + +test> data.NatBag.nth.tests = + test.verify do + use Random natIn + Each.repeat 100 + s = (List.replicate (natIn 0 20) do natIn 0 10) |> NatBag.fromList + ensure + (List.somes + (List.map (i -> NatBag.nth i s) (List.range 0 (NatBag.size s))) + === NatBag.toList s) + +data.NatBag.occurrenceList : NatBag -> [(Nat, Nat)] +data.NatBag.occurrenceList = cases NatBag m -> NatMap.toList m + +data.NatBag.occurrenceList.doc : Doc +data.NatBag.occurrenceList.doc = + {{ + Converts a {type NatBag} to a list of {type Nat}s and their number of + occurrences. + + # Examples + + ``` + NatBag.occurrenceList (NatBag.fromList [1, 2, 3, 4, 1, 2, 1]) + ``` + + # See also + + * {NatBag.toList} to convert a {type NatBag} to a list of {type Nat}s. + * {NatBag.fromOccurrenceList} to convert a list of {type Nat}s and their + number of occurrences to a {type NatBag}. + }} + +data.NatBag.partition : (Nat ->{e} Boolean) -> NatBag ->{e} (NatBag, NatBag) +data.NatBag.partition p b = + use NatBag addN empty + g k v = cases (t, f) -> if p k then (addN v k t, f) else (t, addN v k f) + NatMap.foldWithKey g (empty, empty) (NatBag.counts b) + +data.NatBag.partition.doc : Doc +data.NatBag.partition.doc = + use NatBag toList + {{ + Partitions a {type NatBag} into two {type NatBag}s, one containing all + {type Nat}s that satisfy the predicate and the other containing all + {type Nat}s that do not satisfy the predicate. + + # Examples + + ``` + (evens, odds) = + NatBag.partition Nat.isEven (NatBag.fromList [1, 2, 3, 4, 1, 2, 1]) + (toList evens, toList odds) + ``` + + # See also + + * {NatBag.filter} to filter a {type NatBag} by removing all {type Nat}s + that do not satisfy the predicate. + }} + +data.NatBag.randomChoice : NatBag ->{Random} Nat +data.NatBag.randomChoice bag = + randomIndex = Random.natIn 0 (NatBag.size bag) + match NatBag.nth randomIndex bag with + Some v -> v + None -> bug "NatBag.randomChoice: index out of bounds" + +data.NatBag.randomChoice.doc : Doc +data.NatBag.randomChoice.doc = + use NatBag fromList randomChoice + {{ + Returns a random {type Nat} from the given {type NatBag}. Assumes that the + {type NatBag} is not empty, so an empty {type NatBag} will cause a runtime + exception. + + # Examples + + ``` + lcg 4096 do randomChoice (fromList [0, 3, 5, 7]) + ``` + + ``` + lcg 2510 do randomChoice (fromList [0, 3, 5, 7]) + ``` + }} + +test> data.NatBag.randomChoice.test = test.verify do + bag = NatBag.fromList [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 1, 4, 6, 6] + Each.repeat 1000 + e = NatBag.randomChoice bag + ensure (NatBag.contains e bag) + +data.NatBag.remove : Nat -> NatBag -> NatBag +data.NatBag.remove = NatBag.removeN 1 + +data.NatBag.remove.doc : Doc +data.NatBag.remove.doc = + {{ + Removes a {type Nat} from a {type NatBag}. Constructs a new {type NatBag} + with the element removed. If the element appears `n` times in the + {type NatBag}, the element will appear `n - 1` times in the result. If the + element does not appear in the {type NatBag}, the result will be the same as + the original {type NatBag}. + + # Example + + ``` + NatBag.toList (NatBag.remove 2 (NatBag.fromList [1, 2, 3])) + ``` + + # See also + + * {NatBag.removeN} to remove multiple {type Nat}s from a {type NatBag}. + * {NatBag.removeAll} to remove all occurrences of a {type Nat} from a + {type NatBag}. + * {add.nonempty} to add a single {type Nat} to a {type NatBag}. + * {NatBag.addN} to add multiple occurrences of a {type Nat} to a + {type NatBag}. + * {NatBag.addAll} to add a whole bag to another. + }} + +test> data.NatBag.remove.test = + test.verify do + use Nat == + use Random natIn + _ = Each.range 0 100 + x = natIn 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 0 100 + ensureEqual + (NatBag.toList (NatBag.remove x (NatBag.fromList xs))) + (List.sort (deleteFirst (y -> y == x) xs)) + +data.NatBag.removeAll : Nat -> NatBag -> NatBag +data.NatBag.removeAll = cases + n, NatBag m -> NatBag (NatMap.alter (const None) n m) + +data.NatBag.removeAll.doc : Doc +data.NatBag.removeAll.doc = + use NatBag fromList removeAll toList + {{ + Removes all occurrences of a {type Nat} from a {type NatBag}. + + # Examples + + ``` + toList (removeAll 3 (fromList [1, 2, 3, 3, 4])) + ``` + + ``` + toList (removeAll 3 (fromList [1, 2, 4])) + ``` + + # See also + + * {NatBag.remove} to remove a single occurrence of a {type Nat} from a + {type NatBag}. + * {NatBag.removeN} to remove a specific number of occurrences of a + {type Nat} from a {type NatBag}. + }} + +test> data.NatBag.removeAll.test : [Result] +data.NatBag.removeAll.test = + test.verify do + use Nat != + use NatBag fromList + use Random natIn + _ = Each.range 0 100 + x = natIn 0 1000 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + b = fromList xs + ensure + (NatBag.equals + (NatBag.removeAll x b) (fromList (List.filter (z -> z != x) xs))) + +data.NatBag.removeN : Nat -> Nat -> NatBag -> NatBag +data.NatBag.removeN = cases + n, elem, NatBag m -> + NatBag + (NatMap.alter + (Optional.filter (z -> z Nat.> 0) << Optional.map (z -> z Nat.- n)) + elem + m) + +data.NatBag.removeN.doc : Doc +data.NatBag.removeN.doc = + use NatBag fromList removeN + {{ + Removes a given number of occurrences of a {type Nat} from a {type NatBag}. + If the number of occurrences of the {type Nat} in the {type NatBag} is less + than the given number, then the {type Nat} is removed entirely. + + # Examples + + ``` + removeN 2 3 (fromList [2, 3, 4]) + ``` + + ``` + removeN 2 3 (fromList [1, 3, 3, 3, 4]) + ``` + + # See also + + * {NatBag.remove} to remove a single occurrence of a {type Nat} from a + {type NatBag}. + * {NatBag.removeAll} to remove all occurrences of a {type Nat} from a + {type NatBag}. + * {NatBag.difference} to subtract a {type NatBag} from another. + }} + +test> data.NatBag.removeN.test : [Result] +data.NatBag.removeN.test = test.verify do + use List +: + use Nat - == + use NatBag count + use Random natIn + _ = Each.range 0 100 + x = natIn 0 1000 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + n = natIn 0 100 + b = NatBag.fromList (x +: xs) + ensure (count x (NatBag.removeN n x b) == count x b - n) + +data.NatBag.scale : Nat -> NatBag -> NatBag +data.NatBag.scale n b = + use Nat * + NatBag (NatMap.map (x -> x * n) (NatBag.counts b)) + +data.NatBag.scale.doc : Doc +data.NatBag.scale.doc = + use NatBag fromList scale toList + {{ + Multiplies the number of occurrences of each {type Nat} in a {type NatBag} by + a given {type Nat}. + + # Examples + + ``` + toList (scale 2 (fromList [1, 2, 3, 4])) + ``` + + ``` + toList (scale 2 (fromList [1, 2, 3, 4, 1, 2, 1])) + ``` + + # See also + + * {NatBag.map} to transform each {type Nat} in a {type NatBag} to a + different {type Nat}. + * {NatBag.flatMap} to map each {type Nat} in a {type NatBag} to a + {type NatBag} and combine the results. + }} + +data.NatBag.singleton : Nat -> NatBag.Nonempty +data.NatBag.singleton n = NatBag.Nonempty (NatMap.singleton n 1) + +data.NatBag.singleton.doc : Doc +data.NatBag.singleton.doc = + {{ + Constructs a {type NatBag.Nonempty} containing a single {type Nat}. + + # Example + + ``` + NatBag.Nonempty.toList (NatBag.singleton 1) + ``` + + # See also + + * {NatBag.fromList} to construct a {type NatBag} from a {type List} of + {type Nat}s. + * {NatBag.Nonempty.fromList} to construct a {type NatBag.Nonempty} from a + {type List.Nonempty} of {type Nat}s. + }} + +test> data.NatBag.singleton.test = + test.verify do + _ = Each.range 0 100 + x = Random.nat() + ensureEqual + (List.Nonempty.toList (NatBag.Nonempty.toList (NatBag.singleton x))) [x] + +data.NatBag.size : NatBag -> Nat +data.NatBag.size b = + use Nat + + NatMap.fold (+) 0 (NatBag.counts b) + +data.NatBag.size.doc : Doc +data.NatBag.size.doc = + {{ + Returns the sum of all occurrences of all {type Nat}s in a {type NatBag}. + + # Examples + + ``` + NatBag.size (NatBag.fromList [1, 2, 3, 4, 1, 2, 1]) + ``` + + # See also + + * {NatBag.isEmpty} to check if a {type NatBag} is empty. + * {NatBag.count} to count the occurrences of a given {type Nat} in a + {type NatBag}. + }} + +data.NatBag.subbag : NatBag -> NatBag -> Boolean +data.NatBag.subbag = flip NatBag.superbag + +data.NatBag.subbag.doc : Doc +data.NatBag.subbag.doc = + use NatBag fromList subbag + {{ + Determines whether a {type NatBag} is a __subbag__ of another. + + A {type NatBag} is a subbag of another if it contains as many or fewer + occurrences of each {type Nat} as the other. + + Every {type NatBag} is a subbag of itself. + + # Examples + + ``` + subbag (fromList [2, 3, 4]) (fromList [1, 2, 3, 4]) + ``` + + ``` + subbag (fromList [2, 3, 4]) (fromList [1, 2, 3, 3, 4]) + ``` + + # See also + + * {NatBag.superbag} to determine whether a {type NatBag} is a superbag of + another. + * {NatBag.intersect} to compute the largest {type NatBag} that is a subbag + of both of two bags. + * {NatBag.union} to compute the smallest {type NatBag} that is a superbag + of both of two bags. + }} + +test> data.NatBag.subbag.test : [Result] +data.NatBag.subbag.test = test.verify do + use NatBag fromList subbag + use Random listOf natIn + _ = Each.range 0 100 + xs = listOf (do natIn 0 1000) do natIn 0 100 + ys = listOf (do natIn 0 1000) do natIn 0 100 + b1 = fromList xs + b2 = fromList ys + i = NatBag.intersect b1 b2 + u = NatBag.union b1 b2 + ensure (subbag b1 b1) + ensure (subbag b2 b2) + ensure (subbag i b1) + ensure (subbag i b2) + ensure (subbag b1 u) + ensure (subbag b2 u) + +data.NatBag.superbag : NatBag -> NatBag -> Boolean +data.NatBag.superbag = cases + NatBag m1, NatBag m2 -> NatMap.isSubmapOfBy (Nat.<=) m2 m1 + +data.NatBag.superbag.doc : Doc +data.NatBag.superbag.doc = + use NatBag fromList + {{ + Checks whether the first {type NatBag} is a __superbag__ of the second. A + {type NatBag} is a superbag of another if it contains all the elements of the + other, and possibly more. + + Returns `` true `` if the first {type NatBag} contains at least as many + occurrences of each element as the second. + + Every {type NatBag} is a superbag of itself. + + # Example + + ``` + NatBag.superbag (fromList [1, 2, 3]) (fromList [2, 3, 4]) + ``` + }} + +test> data.NatBag.superbag.test = test.verify do + use NatBag fromList superbag + use Random listOf natIn + _ = Each.range 0 100 + xs = listOf (do natIn 0 1000) do natIn 0 100 + ys = listOf (do natIn 0 1000) do natIn 0 100 + b1 = fromList xs + b2 = fromList ys + i = NatBag.intersect b1 b2 + u = NatBag.union b1 b2 + ensure (superbag b1 b1) + ensure (superbag b2 b2) + ensure (superbag b1 i) + ensure (superbag b2 i) + ensure (superbag u b1) + ensure (superbag u b2) + +data.NatBag.toBag : NatBag -> Bag Nat +data.NatBag.toBag = cases NatBag m -> MkBag (NatMap.toMap m) + +data.NatBag.toBag.doc : Doc +data.NatBag.toBag.doc = + use Bag toList + use NatBag fromList toBag + {{ + Converts a {type NatBag} to a {type Bag} of {type Nat}s. + + # Examples + + ``` + toList (toBag (fromList [1, 2, 3, 3, 4])) + ``` + + ``` + toList (toBag (fromList [1, 2, 4])) + ``` + }} + +test> data.NatBag.toBag.test : [Result] +data.NatBag.toBag.test = test.verify do + use Bag == + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + b = NatBag.fromList xs + ensure (NatBag.toBag b == Bag.fromList xs) + +data.NatBag.toList : NatBag -> [Nat] +data.NatBag.toList = cases + NatBag m -> NatMap.foldWithKey (k v acc -> List.fill v k List.++ acc) [] m + +data.NatBag.toList.doc : Doc +data.NatBag.toList.doc = + use NatBag fromList + {{ + Converts a {type NatBag} into a {type List} of {type Nat}s. If an element + appears multiple times in the {type NatBag}, it will appear multiple times in + the {type List}. + + # Example + + ``` + NatBag.toList (fromList [1, 2, 2, 3]) + ``` + + # See also + + * {fromList} to convert a {type List} to a {type NatBag}. + * {NatBag.toNatSet} to convert a {type NatBag} to a {type NatSet}. + }} + +test> data.NatBag.toList.test = verifyAndIgnore do + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + ensureEqual (NatBag.toList (NatBag.fromList xs)) (List.sort xs) + +data.NatBag.toNatSet : NatBag -> NatSet +data.NatBag.toNatSet = cases NatBag m -> NatMap.keySet m + +data.NatBag.toNatSet.doc : Doc +data.NatBag.toNatSet.doc = + {{ + Converts a {type NatBag} into a {type NatSet}. If an element appears multiple + times in the {type NatBag}, it will appear only once in the {type NatSet}. + + # Example + + ``` + NatSet.toList (NatBag.toNatSet (NatBag.fromList [1, 2, 2, 3])) + ``` + + # See also + + * {NatBag.fromNatSet} to convert a {type NatSet} to a {type NatBag}. + * {NatBag.toList} to convert a {type NatBag} to a {type List}. + }} + +test> data.NatBag.toNatSet.test = + test.verify do + use NatSet == + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + ensure + (NatBag.toNatSet (NatBag.fromList xs) == NatSet.fromList (List.sort xs)) + +data.NatBag.union : NatBag -> NatBag -> NatBag +data.NatBag.union = cases + NatBag m1, NatBag m2 -> NatBag (NatMap.unionWith Nat.max m1 m2) + +data.NatBag.union.doc : Doc +data.NatBag.union.doc = + use NatBag fromList + {{ + Computes the union of two {type NatBag}s. This is the smallest {type NatBag} + that is a {NatBag.superbag} of both arguments. Returns a new {type NatBag} + with the elements that appear in either argument, such that if an element + appears `n` times in the first and `m` times in the second, the element will + appear `` Nat.max n m `` times in the result. + + # Example + + ``` + NatBag.toList (NatBag.union (fromList [1, 2, 3]) (fromList [2, 3, 4])) + ``` + + # See also + + * {NatBag.intersect} to compute the intersection between two + {type NatBag}s. + * {NatBag.difference} to compute the difference between two {type NatBag}s. + * {add.nonempty} to add a single {type Nat} to a {type NatBag}. + * {NatBag.addN} to add multiple occurrences of a {type Nat} to a + {type NatBag}. + * {NatBag.addAll} to add a whole bag to another. + }} + +test> data.NatBag.union.test : [Result] +data.NatBag.union.test = test.verify do + use NatBag fromList superbag union + use Random listOf natIn + _ = Each.range 0 100 + xs = listOf (do natIn 0 1000) do natIn 0 100 + ys = listOf (do natIn 0 1000) do natIn 0 100 + b1 = fromList xs + b2 = fromList ys + ensure (superbag (union b1 b2) b1) + ensure (superbag (union b1 b2) b2) + +data.NatMap.adjust : (a ->{g} a) -> Nat -> NatMap a ->{g} NatMap a +data.NatMap.adjust f k t = NatMap.adjustWithKey (do x -> f x) k t + +data.NatMap.adjust.doc : Doc +data.NatMap.adjust.doc = + {{ + Modifies the value at a key in a {type NatMap}, using a function. If the key + is not present, the map is returned unchanged. + + # Example + + ``` + NatMap.toList + (NatMap.adjust + Text.toUppercase 1 (NatMap.fromList [(1, "foo"), (2, "bar")])) + ``` + + # See also + + * {NatMap.adjustWithKey} for a version of this that also receives the key. + * {NatMap.update} for a version that can delete the key. + * {NatMap.updateWithKey} for a version that can delete the key and also + receives the key. + * {NatMap.alter} for a version that can also insert the key if not present. + * {NatMap.map} for a version that can modify all values at once. + * {NatMap.mapWithKey} for a version that can modify all values at once and + also receives the key. + }} + +test> data.NatMap.adjust.test = test.verify do + use NatMap get + use Random listOf nat + _ = Each.range 0 100 + len = Random.natIn 0 100 + keys = listOf nat do len + xs = listOf (do (oneOfNonempty (nat() +| keys), Random.boolean())) do len + k = oneOfNonempty (nat() +| keys) + fs = (do id) +| [do Boolean.not, do const true, do const false] + f = List.Nonempty.foldMap (x y -> do Random.either x y) id fs () + m = NatMap.fromList xs + ensureEqual (get k (NatMap.adjust f k m)) (Optional.map f (get k m)) + +data.NatMap.adjustWithKey : + (Nat ->{g} a ->{g} a) -> Nat -> NatMap a ->{g} NatMap a +data.NatMap.adjustWithKey f k t = + NatMap.updateWithKey (k' x -> Some (f k' x)) k t + +data.NatMap.adjustWithKey.doc : Doc +data.NatMap.adjustWithKey.doc = + use Nat + + {{ + Modifies the value at a key in a {type NatMap}, using a function. If the key + is not present, the map is returned unchanged. The function receives the key + as its first argument and the value as its second. + + # Example + + ``` + NatMap.toList + (NatMap.adjustWithKey + (k x -> k + x) 1 (NatMap.fromList [(1, 10), (2, 20)])) + ``` + + # See also + + * {NatMap.adjust} for a version of this that does not receive the key. + * {NatMap.updateWithKey} for a version that can delete the key. + * {NatMap.alterWithKey} for a version that can also insert the key if not + present. + * {NatMap.mapWithKey} for a version that can modify all values at once. + }} + +test> data.NatMap.adjustWithKey.test = + test.verify do + use Boolean != + use NatMap get + use Random listOf nat + _ = Each.range 0 100 + len = Random.natIn 0 100 + keys = listOf nat do len + xs = listOf (do (oneOfNonempty (nat() +| keys), Random.boolean())) do len + k = oneOfNonempty (nat() +| keys) + f k v = Nat.isEven k != v + m = NatMap.fromList xs + ensureEqual + (get k (NatMap.adjustWithKey f k m)) (Optional.map (f k) (get k m)) + +data.NatMap.align : NatMap a -> NatMap b -> NatMap (OneOrBoth a b) +data.NatMap.align = NatMap.alignWith id + +data.NatMap.align.doc : Doc +data.NatMap.align.doc = + use NatMap fromList + {{ + Aligns two maps into a map of {type OneOrBoth} values. + + The result will have the same keys as the union of the keys of the two input + maps, and each value will be a {type OneOrBoth} containing the corresponding + values from the two input maps. If a key is present in only one of the input + maps, the result will contain {This} or {That} values accordingly. If a key + is present in both input maps, the result will contain a {Both} value. + + # Example + + ``` + NatMap.toList + (NatMap.align + (fromList [(1, "hello"), (2, "world")]) (fromList [(2, 42), (3, 43)])) + ``` + + # See also + + * {NatMap.alignWith} - a variant where you can specify a function to apply + to the values. + }} + +data.NatMap.alignWith : + (OneOrBoth a b ->{g} c) -> NatMap a -> NatMap b ->{g} NatMap c +data.NatMap.alignWith f m1 m2 = NatMap.alignWithKey (_ x -> f x) m1 m2 + +data.NatMap.alignWith.doc : Doc +data.NatMap.alignWith.doc = + use NatMap fromList + use Text ++ + {{ + Aligns two maps into a map of values using a function. + + The result will have the same keys as the union of the keys of the two input + maps, and each value will be the result of applying the given function to the + corresponding values from the two input maps – {This} for keys that only + appear in the first map, {That} for keys that only appear in the second map, + and {Both} for keys that appear in both maps. + + # Example + + ``` + f = cases + This a -> "only in the first map: " ++ a + That b -> "only in the second map: " ++ b + Both a b -> "in both maps: " ++ a ++ " and " ++ b + NatMap.values + (NatMap.alignWith + f + (fromList [(1, "circuit"), (2, "quasar")]) + (fromList [(2, "voyage"), (3, "harmony")])) + ``` + + # See also + + * {NatMap.align} - a variant that returns a map of {type OneOrBoth} values. + * {NatMap.alignWithKey} - a variant where the function also receives the + key. + }} + +data.NatMap.alignWithKey : + (Nat ->{e} OneOrBoth a b ->{f} c) -> NatMap a -> NatMap b ->{e, f} NatMap c +data.NatMap.alignWithKey f = cases + NatMap None, t2 -> NatMap.mapWithKey (k b -> f k (That b)) t2 + t1, NatMap None -> NatMap.mapWithKey (k a -> f k (This a)) t1 + NatMap (Some t1), NatMap (Some t2) -> + NatMap (Some (NatMap.Nonempty.alignWithKey f t1 t2)) + +data.NatMap.alignWithKey.doc : Doc +data.NatMap.alignWithKey.doc = + use Nat toText + use NatMap fromList + use Text ++ + {{ + Aligns two maps into a map of values using a function. + + The result will have the same keys as the union of the keys of the two input + maps, and each value will be the result of applying the given function to the + corresponding key-value pairs from the two input maps. The function receives + {This} for values under keys that are present in only the first input map, + {That} for values under keys that are present in only the second input map, + and {Both} for keys that are present in both input maps. + + # Example + + ``` + f k = cases + This a -> "only in the first map: " ++ toText k ++ " -> " ++ a + That b -> "only in the second map: " ++ toText k ++ " -> " ++ b + Both a b -> "in both maps: " ++ toText k ++ " -> " ++ a ++ " and " ++ b + NatMap.values + (NatMap.alignWithKey + f + (fromList [(1, "circuit"), (2, "quasar")]) + (fromList [(2, "voyage"), (3, "harmony")])) + ``` + + # See also + + * {NatMap.align} - a variant that returns a map of {type OneOrBoth} values. + * {NatMap.alignWith} - a variant where the function doesn't take the key. + }} + +data.NatMap.alter : + (Optional a ->{g} Optional a) -> Nat -> NatMap a ->{g} NatMap a +data.NatMap.alter f k t = NatMap.alterWithKey (do x -> f x) k t + +data.NatMap.alter.doc : Doc +data.NatMap.alter.doc = + use NatMap alter fromList toList + use Text ++ + {{ + An expression `` alter f k t `` alters the value under the key `k` in the + {type NatMap} `t`, or the absence thereof, using the function `f`. + + # Examples + + If the function returns {None}, the key is deleted: + + ``` + toList (alter (x -> None) 1 (fromList [(1, "foo"), (2, "bar")])) + ``` + + If the function returns ``Some x``, the value under the key is updated: + + ``` + toList + (alter + (x -> Some (Optional.getOrElse "" x ++ "baz")) + 1 + (fromList [(1, "foo"), (2, "bar")])) + ``` + + If the key is not present, the function receives {None}, and can insert the + key by returning {Some} of the new value: + + ``` + toList (alter (x -> Some "baz") 3 (fromList [(1, "foo"), (2, "bar")])) + ``` + + # See also + + * {NatMap.alterWithKey} for a version of this that also receives the key. + * {NatMap.update} for a version that does nothing if the key is not + present. + }} + +test> data.NatMap.alter.test = + test.verify do + use NatMap get + use Random boolean listOf nat + _ = Each.range 0 100 + len = Random.natIn 0 10 + keys = listOf nat do len + xs = listOf (do (oneOfNonempty (nat() +| keys), boolean())) do len + b = boolean() + k = oneOfNonempty (nat() +| keys) + f = + oneOfNonempty + (Abort.toBug do + List.nonempty (deprecated.sample maxNat do someOrNone yesNo() b ())) + m = NatMap.fromList xs + ensureEqual (get k (NatMap.alter f k m)) (f (get k m)) + +data.NatMap.alterWithKey : + (Nat ->{g} Optional a ->{g} Optional a) -> Nat -> NatMap a ->{g} NatMap a +data.NatMap.alterWithKey f k = cases + NatMap None -> + match f k None with + None -> NatMap.empty + Some v -> toNatMap (NatMap.singleton k v) + NatMap (Some t) -> Nonempty.alterWithKey f k t + +data.NatMap.alterWithKey.doc : Doc +data.NatMap.alterWithKey.doc = + use Nat * + + use NatMap alterWithKey fromList toList + {{ + An expression `` alterWithKey f k t `` alters the value under the key `k` in + the {type NatMap} `t`, or the absence thereof, using the function `f`. + + The function receives the key as its first argument and the value as its + second (or {None} if the key is not present). + + # Examples + + If the function returns {None}, the key is deleted: + + ``` + toList (alterWithKey (k x -> None) 1 (fromList [(1, "foo"), (2, "bar")])) + ``` + + If the function returns ``Some x``, the value under the key is updated to + `x`: + + ``` + toList + (alterWithKey + (k x -> Optional.map (v -> v + k) x) 1 (fromList [(1, 10), (2, 20)])) + ``` + + If the key is not present, the function receives {None} for the value, and + can insert the key by returning {Some} of the new value. + + ``` + toList + (alterWithKey + (k x -> Optional.orElse x (Some (k * 10))) + 3 + (fromList [(1, 10), (2, 20)])) + ``` + + # See also + + * {NatMap.alter} for a version of this that does not receive the key. + * {NatMap.updateWithKey} for a version that does nothing if the key is not + present. + }} + +test> data.NatMap.alterWithKey.test = + test.verify do + use NatMap get + use Random listOf nat natIn + _ = Each.range 0 100 + len = natIn 0 4 + keys = listOf (do natIn 0 100) do len + xs = listOf (do (oneOfNonempty (nat() +| keys), Random.boolean())) do len + k = oneOfNonempty (nat() +| keys) + b = Nat.isEven k + f = + const + (oneOfNonempty + (Abort.toBug do + List.nonempty (deprecated.sample maxNat do someOrNone yesNo() b ()))) + m = NatMap.fromList xs + ensureEqual (get k (NatMap.alterWithKey f k m)) (f k (get k m)) + +data.NatMap.breakOffMax : NatMap a ->{Abort} ((Nat, a), NatMap a) +data.NatMap.breakOffMax = cases + NatMap None -> abort + NatMap (Some t) -> NatMap.Nonempty.breakOffMax t + +data.NatMap.breakOffMax.doc : Doc +data.NatMap.breakOffMax.doc = + {{ + Returns the maximum key and value in the {type NatMap}, and the map without + that key. + + If the map is empty, this calls {abort}. + + # Example + + ``` + toOptional! do + Tuple.second + NatMap.toList + (NatMap.breakOffMax (NatMap.fromList [(1, "foo"), (2, "bar")])) + ``` + + # See also + + * {NatMap.breakOffMin} for the opposite operation, returning the minimum + key and value. + * {NatMap.deleteMax} for a version that only returns the updated map. + * {NatMap.getMax} for a version that does not remove the key. + }} + +test> data.NatMap.breakOffMax.test = + test.verify do + use Nat >= + use Random listOf nat + _ = Each.range 0 100 + len = Random.natIn 0 100 + keys = listOf nat do len + xs = listOf (do (oneOfNonempty (nat() +| keys), Random.boolean())) do len + m = NatMap.fromList xs + f = cases + Some (k, v), (k', v') -> if k >= k' then Some (k, v) else Some (k', v') + None, (k', v') -> Some (k', v') + maxEntry = List.foldLeft f None (NatMap.toList m) + ensureEqual + (toOptional! do NatMap.breakOffMax m) + (maxEntry |> (Optional.map cases (k, v) -> ((k, v), NatMap.delete k m))) + +data.NatMap.breakOffMin : NatMap a ->{Abort} ((Nat, a), NatMap a) +data.NatMap.breakOffMin = cases + NatMap None -> abort + NatMap (Some t) -> NatMap.Nonempty.breakOffMin t + +data.NatMap.breakOffMin.doc : Doc +data.NatMap.breakOffMin.doc = + {{ + Returns the minimum key and value in the {type NatMap}, and the map without + that key. + + If the map is empty, this calls {abort}. + + # Example + + ``` + toOptional! do + Tuple.second + NatMap.toList + (NatMap.breakOffMin (NatMap.fromList [(1, "foo"), (2, "bar")])) + ``` + + # See also + + * {NatMap.breakOffMax} for the opposite operation, returning the maximum + key and value. + * {NatMap.deleteMin} for a version that only returns the updated map. + * {NatMap.getMin} for a version that does not remove the key. + }} + +test> data.NatMap.breakOffMin.test = + test.verify do + use Nat <= + use Random listOf nat + _ = Each.range 0 100 + len = Random.natIn 0 100 + keys = listOf nat do len + xs = listOf (do (oneOfNonempty (nat() +| keys), Random.boolean())) do len + m = NatMap.fromList xs + f = cases + Some (k, v), (k', v') -> if k <= k' then Some (k, v) else Some (k', v') + None, (k', v') -> Some (k', v') + minEntry = List.foldLeft f None (NatMap.toList m) + ensureEqual + (toOptional! do NatMap.breakOffMin m) + (minEntry |> (Optional.map cases (k, v) -> ((k, v), NatMap.delete k m))) + +data.NatMap.compareBy : + (a ->{g} a ->{g} Ordering) -> NatMap a -> NatMap a ->{g} Ordering +data.NatMap.compareBy f = cases + NatMap None, NatMap None -> Equal + NatMap None, _ -> Less + _, NatMap None -> Greater + NatMap (Some t1), NatMap (Some t2) -> Nonempty.compareBy f t1 t2 + +data.NatMap.compareBy.doc : Doc +data.NatMap.compareBy.doc = + use NatMap compareBy fromList + use Universal ordering + {{ + Compares two {type NatMap}s using the given comparison function. + + Defines a [total order](https://en.wikipedia.org/wiki/Total_order) on + {type NatMap}s. + + The entries are compared in ascending order of key, and the given comparison + function is applied to the values of corresponding entries. + + Returns: + + * `` Less `` for any of the following: + * The first map is a proper prefix of the second map + * The first entry that differs between the maps has a lower key in the + first map. + * The first entry that differs between the maps has a lower value in the + first map, according to the given comparison function. + * `` Equal `` when the maps are equal. + * `` Greater `` for any of the following: + * The second map is a proper prefix of the first map + * The first entry that differs between the maps has a lower key in the + second map. + * The first entry that differs between the maps has a lower value in the + second map, according to the given comparison function. + + # Examples + + The maps are equal: + + ``` + compareBy + ordering + (fromList [(1, "foo"), (2, "bar")]) + (fromList [(1, "foo"), (2, "bar")]) + ``` + + The first map is a prefix of the second map: + + ``` + compareBy + ordering + (fromList [(1, "foo"), (2, "bar")]) + (fromList [(1, "foo"), (2, "bar"), (3, "baz")]) + ``` + + The second map is a prefix of the first map: + + ``` + compareBy + ordering (fromList [(1, "foo"), (2, "bar")]) (fromList [(1, "foo")]) + ``` + + The first value that differs is larger in the second map: + + ``` + compareBy + ordering + (fromList [(1, "foo"), (2, "bar")]) + (fromList [(1, "foo"), (2, "baz")]) + ``` + + The first key that differs is larger in the second map: + + ``` + compareBy + ordering + (fromList [(1, "foo"), (2, "bar")]) + (fromList [(1, "foo"), (3, "bar")]) + ``` + + The first key that differs is larger in the first map: + + ``` + compareBy + ordering + (fromList [(1, "foo"), (3, "bar")]) + (fromList [(1, "foo"), (2, "bar")]) + ``` + + The first value that differs is larger in the first map: + + ``` + compareBy + ordering + (fromList [(1, "foo"), (2, "baz")]) + (fromList [(1, "foo"), (2, "bar")]) + ``` + + # See also + + * {NatMap.equalBy} to compare for equality only. + * {NatMap.submapCompareBy} to check if one map is a submap of another, + using a comparison function. + }} + +test> data.NatMap.compareBy.test = + test.verify do + use NatMap fromList size toList + use Ordering andThen + use Random boolean listOf nat natIn + use Universal ordering + _ = Each.range 0 101 + len = natIn 0 4 + keys = listOf (do natIn 0 100) do len + xs = listOf (do (oneOfNonempty (nat() +| keys), boolean())) do len + ys = listOf (do (oneOfNonempty (nat() +| keys), boolean())) do len + m1 = fromList xs + m2 = fromList ys + listComp = + andThen + (List.foldLeft + andThen Equal (List.zipWith ordering (toList m1) (toList m2))) + (ordering (size m1) (size m2)) + ensureEqual (NatMap.compareBy ordering m1 m2) listComp + +data.NatMap.contains : Nat -> NatMap a -> Boolean +data.NatMap.contains k m = match NatMap.get k m with + None -> false + Some _ -> true + +data.NatMap.contains.doc : Doc +data.NatMap.contains.doc = + {{ + Checks if the given key is present in the {type NatMap}. + + # Example + + ``` + NatMap.contains 1 (NatMap.fromList [(1, "foo"), (2, "bar")]) + ``` + + # See also + + * {NatMap.get} to get the value if it is present. + * {NatMap.isSubmapOf} to check if one map contains all the keys of another. + }} + +data.NatMap.delete : Nat -> NatMap a -> NatMap a +data.NatMap.delete k = cases + NatMap None -> NatMap.empty + NatMap (Some t) -> NatMap.Nonempty.delete k t + +data.NatMap.delete.doc : Doc +data.NatMap.delete.doc = + {{ + Deletes the given key from the {type NatMap}. + + # Example + + ``` + NatMap.toList (NatMap.delete 1 (NatMap.fromList [(1, "foo"), (2, "bar")])) + ``` + + # See also + + * {NatMap.deleteMax} to delete the largest key. + * {NatMap.deleteMin} to delete the smallest key. + * {NatMap.difference} to delete all keys in one map from another. + }} + +data.NatMap.deleteMax : NatMap a ->{Abort} NatMap a +data.NatMap.deleteMax t = + (_, t') = NatMap.breakOffMax t + t' + +data.NatMap.deleteMax.doc : Doc +data.NatMap.deleteMax.doc = + {{ + Deletes the largest key from the {type NatMap}. + + If the map is empty, this returns an empty map. + + # Example + + ``` + toOptional! do + NatMap.toList + (NatMap.deleteMax (NatMap.fromList [(1, "foo"), (2, "bar")])) + ``` + + # See also + + * {NatMap.delete} to delete a specific key. + * {NatMap.deleteMin} to delete the smallest key. + * {NatMap.difference} to delete all keys in one map from another. + }} + +data.NatMap.deleteMin : NatMap a ->{Abort} NatMap a +data.NatMap.deleteMin t = + (_, t') = NatMap.breakOffMin t + t' + +data.NatMap.deleteMin.doc : Doc +data.NatMap.deleteMin.doc = + {{ + Deletes the smallest key from the {type NatMap}. + + If the map is empty, this calls {abort}. + + # Example + + ``` + toOptional! do + NatMap.toList + (NatMap.deleteMin (NatMap.fromList [(1, "foo"), (2, "bar")])) + ``` + + # See also + + * {NatMap.delete} to delete a specific key. + * {NatMap.deleteMax} to delete the largest key. + * {NatMap.difference} to delete all keys in one map from another. + }} + +data.NatMap.difference : NatMap a -> NatMap b -> NatMap a +data.NatMap.difference = NatMap.differenceWith do do None + +data.NatMap.difference.doc : Doc +data.NatMap.difference.doc = + use NatMap fromList + {{ + Deletes all keys in the second {type NatMap} from the first. + + # Example + + ``` + NatMap.toList + (NatMap.difference + (fromList [(1, "foo"), (2, "bar")]) (fromList [(1, "foo")])) + ``` + + # See also + + * {NatMap.differenceWith} to more finely control which keys are deleted, or + replace specific values instead of deleting them. + * {NatMap.delete} to delete a specific key. + * {NatMap.deleteMax} to delete the largest key. + * {NatMap.deleteMin} to delete the smallest key. + * {NatMap.intersect} to delete all keys not in both maps. + }} + +data.NatMap.differenceWith : + (a ->{g} b ->{g} Optional a) -> NatMap a -> NatMap b ->{g} NatMap a +data.NatMap.differenceWith f = NatMap.differenceWithKey do x y -> f x y + +data.NatMap.differenceWith.doc : Doc +data.NatMap.differenceWith.doc = + use NatMap fromList + use Text == + {{ + Deletes keys in the second {type NatMap} from the first, or replaces their + values with the result of the given function. + + # Example + + Delete all keys in the second map from the first unless the values under + those keys are equal in both maps: + + ``` + NatMap.toList + (NatMap.differenceWith + (x y -> (if x == y then Some x else None)) + (fromList [(1, "foo"), (2, "bar")]) + (fromList [(1, "foo"), (2, "baz")])) + ``` + + # See also + + * {NatMap.differenceWithKey} for a version that also receives the key. + * {NatMap.difference} to delete all keys in one map from another. + * {NatMap.intersectWith} to delete all keys not in both maps, using a + function to combine the remaining values. + }} + +data.NatMap.differenceWithKey : + (Nat ->{g} a ->{g} b ->{g} Optional a) -> NatMap a -> NatMap b ->{g} NatMap a +data.NatMap.differenceWithKey f = cases + NatMap None, _ -> NatMap.empty + x, NatMap None -> x + NatMap (Some t1), NatMap (Some t2) -> Nonempty.differenceWithKey f t1 t2 + +data.NatMap.differenceWithKey.doc : Doc +data.NatMap.differenceWithKey.doc = + use Nat == + use NatMap fromList + {{ + Deletes keys in the second {type NatMap} from the first, or replaces their + values with the result of the given function. + + # Example + + Delete all keys in the second map from the first unless the values under + those keys are both equal to the key: + + ``` + NatMap.toList + (NatMap.differenceWithKey + (k x y -> (if x == y && x == k then Some x else None)) + (fromList [(1, 1), (2, 2)]) + (fromList [(1, 1), (2, 3)])) + ``` + + # See also + + * {NatMap.differenceWith} for a version that does not receive the key. + * {NatMap.difference} to delete all keys in one map from another. + * {NatMap.intersectWithKey} to delete all keys not in both maps, using a + function to combine the remaining values. + }} + +data.NatMap.doc : Doc +data.NatMap.doc = + {{ + An efficient implementation of maps from keys of type {type Nat} to values of + some type. This type is parameterised by the type of the values. This + specialized map is much more efficient than the general map type, {type Map}, + when the keys are of type {type Nat} or can be encoded as {type Nat}s. + + {{ + docAside + {{ + The implementation of {type NatMap} uses a patricia tree, as described in + ["Fast Mergeable Integer Maps" by Chris Okasaki and Andy Gill](https://www.cs.tufts.edu/~nr/cs257/archive/chris-okasaki/IntMap98.ps). + The code is largely transliterated to Unison from the Haskell + implementation by Daan Leijen and Andriy Palamarchuk, which is in turn + based on the paper. + }} }} + + # Constructing maps + + The empty map: + + @signature{NatMap.empty} + + A map with a single key/value pair: + + @signature{NatMap.singleton} + + Construct a map from a list of key/value pairs: + + @signature{NatMap.fromList} + + Construct a map from a list of key/value pairs, using a combining function + to resolve conflicts: + + @signature{NatMap.fromListWith} + + Construct a map from a list of key/value pairs, using a combining function + to resolve conflicts, and passing the key to the combining function: + + @signature{NatMap.fromListWithKey} + + # Querying + + Look up the value at a key: + + @signature{NatMap.get} + + Look up the value at a key, or return a default value if the key is not + present: + + @signature{NatMap.getOrElse} + + Check if a key is in the map: + + @signature{NatMap.contains} + + Get the size of the map: + + @signature{NatMap.size} + + Check if the map is empty: + + @signature{NatMap.isEmpty} + + # Inserting + + Insert a key/value pair into the map: + + @signature{NatMap.insert} + + Insert a key/value pair, returning a nonempty map: + + @signature{NatMap.insert.nonempty} + + Insert a key/value pair into the map, using a combining function to resolve + conflicts: + + @signature{NatMap.insertWith} + + Insert a key/value pair into the map, using a combining function to resolve + conflicts, and passing the key to the combining function: + + @signature{NatMap.insertWithKey} + + Insert a key/value pair into the map, using a combining function to resolve + conflicts, and passing the key to the combining function, and returning the + old value if it was present: + + @signature{NatMap.insertGetWithKey} + + # Deleting and updating values + + Delete a key and its value from the map: + + @signature{NatMap.delete} + + Update the value at a key with a function, or remove the key if the + function returns {None}: + + @signature{NatMap.update} + + Update the value at a key with a function, or remove the key if the + function returns {None}, passing the key to the function: + + @signature{NatMap.updateWithKey} + + Update the value at a key with a function, or remove the key if the + function returns {None}, passing the key to the function, and returning the + old value if it was present: + + @signature{NatMap.updateGetWithKey} + + Update, insert, or delete the value at a key with a function, depending on + whether the key is present in the map: + + @signature{NatMap.alter} + + Update, insert, or delete the value at a key with a function, passing the + key to the function. + + @signature{NatMap.alterWithKey} + + # Combining maps + + ## Unions + + Add the entries from one map to another, preferring the entries from the + first map if there are conflicts: + + @signature{NatMap.union} + + Merge two maps, using a combining function to resolve conflicts: + + @signature{NatMap.unionWith} + + Merge two maps, using a combining function to resolve conflicts, and + passing the key to the combining function: + + @signature{NatMap.unionWithKey} + + Combine the entries from a list of maps, preferring the entries from the + first map if there are conflicts: + + @signature{NatMap.unions} + + Merge list of maps, using a combining function to resolve conflicts: + + @signature{NatMap.unionsWith} + + ## Difference + + Remove the entries from one map that are present in another map: + + @signature{NatMap.difference} + + Remove the entries from one map that are present in another map, using a + combining function to resolve conflicts: + + @signature{NatMap.differenceWith} + + Remove the entries from one map that are present in another map, using a + combining function to resolve conflicts, and passing the key to the + combining function: + + @signature{NatMap.differenceWithKey} + + ## Intersection + + Keep only the entries from one map that are present in another map: + + @signature{NatMap.intersect} + + Keep only entries present in both of two maps, using a combining + function to resolve conflicts: + + @signature{NatMap.intersectWith} + + Keep only entries present in both of two maps, using a combining + function to resolve conflicts, and passing the key to the combining + function: + + @signature{NatMap.intersectWithKey} + + # Transforming maps + + Apply a function to every value in a map: + + @signature{NatMap.map} + + Apply a function to every value in a map, passing the key to the function: + + @signature{NatMap.mapWithKey} + + Apply a partial function to every value in a map, removing entries for + which the function returns {None}: + + @signature{NatMap.mapOptional} + + # Summarizing maps + + Fold a function over the entries in a map, from lowest key to highest key: + + @signature{NatMap.fold} + + Fold a function over the entries in a map, from lowest key to highest key, + passing the key to the function: + + @signature{NatMap.foldWithKey} + + # Filtering and partitioning + + Filter a map, keeping only entries for which the predicate returns + ``true``: + + @signature{NatMap.filter} + + Filter a map, keeping only entries for which the predicate returns + ``true``, passing the key to the predicate: + + @signature{NatMap.filterWithKey} + + Partition a map into two maps, one containing entries for which the + predicate returns ``true``, and one containing entries for which the + predicate returns ``false``: + + @signature{NatMap.partition} + + Partition a map into two maps, one containing entries for which the + predicate returns ``true``, and one containing entries for which the + predicate returns ``false``, passing the key to the predicate: + + @signature{NatMap.partitionWithKey} + + Partition a map into two maps, one containing entries for which the + function returns {Left} and one containing entries for which the function + returns {Right}: + + @signature{NatMap.mapEither} + + Partition a map into two maps, one containing entries for which the + function returns {Left} and one containing entries for which the function + returns {Right}, passing the key to the function: + + @signature{NatMap.mapEitherWithKey} + + Split a map into two maps at a given key: + + @signature{NatMap.split} + + # Comparing maps + + Check if two maps are equal according to a given comparison on the values: + + @signature{NatMap.equalBy} + + Get a partial order for two maps given a total order on the values: + + @signature{NatMap.compareBy} + + Check if all entries in one map are present in another map, using a given + comparison function to compare the values: + + @signature{NatMap.isSubmapOfBy} + + Check if all entries in one map are present in another map, using {===} to + compare the values: + + @signature{NatMap.isSubmapOf} + + Check that one map is a proper submap of another map, using a given + comparison function to compare the values: + + @signature{NatMap.isProperSubmapOfBy} + + Check that one map is a proper submap of another map, using {===} to + compare the values: + + @signature{NatMap.isProperSubmapOf} + + # Operations on the minimum or maximum key + + Get the minimum key in a map: + + @signature{NatMap.getMin} + + Get the maximum key in a map: + + @signature{NatMap.getMax} + + Break a map into its minimum key and value, and the rest of the map: + + @signature{NatMap.breakOffMin} + + Break a map into its maximum key and value, and the rest of the map: + + @signature{NatMap.breakOffMax} + + Break a map into the value at the minimum key, and the rest of the map: + + @signature{NatMap.minView} + + Break a map into the value at the maximum key, and the rest of the map: + + @signature{NatMap.maxView} + + Remove the minimum key from a map: + + @signature{NatMap.deleteMin} + + Remove the maximum key from a map: + + @signature{NatMap.deleteMax} + + Update the value at the minimum key with a function: + + @signature{NatMap.updateMin} + + Update the value at the maximum key with a function: + + @signature{NatMap.updateMax} + + Update the value at the minimum key with a function, passing the key to the + function: + + @signature{NatMap.updateMinWithKey} + + Update the value at the maximum key with a function, passing the key to the + function: + + @signature{NatMap.updateMaxWithKey} + + # Convertings maps to other types + + Convert a map to a list of key/value pairs: + + @signature{NatMap.toList} + + Get a list of the keys in a map: + + @signature{NatMap.keys} + + Get a list of the values in a map: + + @signature{NatMap.values} + }} + +data.NatMap.empty : NatMap a +data.NatMap.empty = NatMap None + +data.NatMap.empty.doc : Doc +data.NatMap.empty.doc = {{ The empty {type NatMap}. }} + +data.NatMap.equalBy : + (a ->{g} a ->{g} Boolean) -> NatMap a -> NatMap a ->{g} Boolean +data.NatMap.equalBy f = cases + NatMap (Some t1), NatMap (Some t2) -> Nonempty.equalBy f t1 t2 + NatMap None, NatMap None -> true + NatMap _, _ -> false + +data.NatMap.equalBy.doc : Doc +data.NatMap.equalBy.doc = + use Nat == + use NatMap equalBy fromList + {{ + Checks if two {type NatMap}s are equal by comparing their values using the + given function. + + # Examples + + ``` + equalBy + (x y -> x == y) (fromList [(1, 1), (2, 2)]) (fromList [(1, 1), (2, 2)]) + ``` + + ``` + equalBy + (x y -> x == y) (fromList [(1, 1), (2, 2)]) (fromList [(1, 1), (2, 3)]) + ``` + + # See also + + * {NatMap.compareBy} to order two {type NatMap}s using a custom comparison + function. + * {NatMap.difference} to see where two {type NatMap}s differ. + }} + +data.NatMap.filter : (a ->{g} Boolean) -> NatMap a ->{g} NatMap a +data.NatMap.filter f = NatMap.filterWithKey do x -> f x + +data.NatMap.filter.doc : Doc +data.NatMap.filter.doc = + {{ + Filters a {type NatMap} by retaining only values that satisfy the given + predicate. + + # Example + + ``` + NatMap.toList + (NatMap.filter + Nat.isEven (NatMap.fromList [(1, 11), (2, 22), (3, 33), (4, 44)])) + ``` + + # See also + + * {NatMap.filterWithKey} for a version where the predicate also receives + the key. + * {NatMap.partition} to split a {type NatMap} into two {type NatMap}s based + on a predicate. + * {NatMap.intersect} to retain only keys that occur in another map. + }} + +data.NatMap.filterWithKey : + (Nat ->{g} a ->{g} Boolean) -> NatMap a ->{g} NatMap a +data.NatMap.filterWithKey f = cases + NatMap None -> NatMap.empty + NatMap (Some t) -> NatMap.Nonempty.filterWithKey f t + +data.NatMap.filterWithKey.doc : Doc +data.NatMap.filterWithKey.doc = + use Nat == + {{ + Filters a {type NatMap} by retaining only entries that satisfy the given + predicate. + + # Example + + ``` + NatMap.toList + (NatMap.filterWithKey + (k x -> k == x) (NatMap.fromList [(1, 1), (2, 22), (3, 3), (4, 44)])) + ``` + + # See also + + * {NatMap.filter} for a version that does not receive the key. + * {NatMap.partitionWithKey} to split a {type NatMap} into two + {type NatMap}s based on a predicate. + * {NatMap.intersectWithKey} to retain only keys that occur in another map. + }} + +data.NatMap.fold : (a ->{g} b ->{g} b) -> b -> NatMap a ->{g} b +data.NatMap.fold f = NatMap.foldWithKey do x y -> f x y + +data.NatMap.fold.doc : Doc +data.NatMap.fold.doc = + use Nat + + {{ + Summarizes the values in a {type NatMap} using the given function. The + function is applied to each value in the {type NatMap} and the function + combines the result with the summary so far. + + # Example + + ``` + NatMap.fold (+) 0 (NatMap.fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + # See also + + * {NatMap.foldWithKey} for a version that also receives the key. + }} + +data.NatMap.foldWithKey : + (Nat ->{g} a ->{g} b ->{g} b) -> b -> NatMap a ->{g} b +data.NatMap.foldWithKey f z = cases + NatMap (Some t) -> Nonempty.foldWithKey f z t + NatMap None -> z + +data.NatMap.foldWithKey.doc : Doc +data.NatMap.foldWithKey.doc = + use Nat + + {{ + Summarizes the entries in a {type NatMap} using the given function. The + function is applied to each entry in the {type NatMap} and the function + combines the result with the summary so far. + + # Example + + ``` + NatMap.foldWithKey + (k x y -> k + x + y) 0 (NatMap.fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + # See also + + * {NatMap.fold} for a version that does not receive the key. + }} + +data.NatMap.fromList : [(Nat, a)] -> NatMap a +data.NatMap.fromList = + List.foldLeft + (cases m, (k, v) -> toNatMap (NatMap.insert.nonempty k v m)) NatMap.empty + +data.NatMap.fromList.doc : Doc +data.NatMap.fromList.doc = + use NatMap fromList toList + {{ + Creates a {type NatMap} from a list of key-value pairs. + + If the list contains duplicate keys, the last value for a given key is + retained. + + # Examples + + ``` + toList (fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + ``` + toList (fromList [(1, 10), (2, 20), (1, 30)]) + ``` + + # See also + + * {NatMap.fromListWith} to specify how to combine values for duplicate + keys. + * {NatMap.fromListWithKey} to specify how to combine values for duplicate + keys, taking the key into account. + * {toList} to convert a {type NatMap} to a list of key-value pairs. + }} + +data.NatMap.fromListWith : (a ->{g} a ->{g} a) -> [(Nat, a)] ->{g} NatMap a +data.NatMap.fromListWith f = + List.foldLeft + (cases m, (k, v) -> toNatMap (NatMap.insertWith f k v m)) NatMap.empty + +data.NatMap.fromListWith.doc : Doc +data.NatMap.fromListWith.doc = + use Nat + + use NatMap toList + {{ + Creates a {type NatMap} from a list of key-value pairs, combining duplicate + keys using the given function. + + # Example + + ``` + toList (NatMap.fromListWith (+) [(1, 10), (2, 20), (1, 30)]) + ``` + + # See also + + * {NatMap.fromList} for a version that resolves duplicate keys by retaining + the last value for that key. + * {NatMap.fromListWithKey} for a version that also takes the key into + account when combining duplicates. + * {toList} to convert a {type NatMap} to a list of key-value pairs. + }} + +data.NatMap.fromListWithKey : + (Nat ->{g} a ->{g} a ->{g} a) -> [(Nat, a)] ->{g} NatMap a +data.NatMap.fromListWithKey f = + List.foldLeft + (cases m, (k, v) -> toNatMap (NatMap.insertWithKey f k v m)) NatMap.empty + +data.NatMap.fromListWithKey.doc : Doc +data.NatMap.fromListWithKey.doc = + use Nat + + use NatMap toList + {{ + Creates a {type NatMap} from a list of key-value pairs, combining duplicate + keys using the given function. + + # Example + + ``` + toList + (NatMap.fromListWithKey (k x y -> k + x + y) [(1, 10), (2, 20), (1, 30)]) + ``` + + # See also + + * {NatMap.fromList} for a version that resolves duplicate keys by retaining + the last value for that key. + * {NatMap.fromListWith} for a version that does not take the key into + account when combining duplicates. + * {toList} to convert a {type NatMap} to a list of key-value pairs. + }} + +data.NatMap.fromMap : Map Nat a -> NatMap a +data.NatMap.fromMap = + Map.foldLeftWithKey + (m k v -> toNatMap (NatMap.insert.nonempty k v m)) NatMap.empty + +data.NatMap.fromMap.doc : Doc +data.NatMap.fromMap.doc = + use Map fromList + use NatMap fromMap toList + {{ + Converts a {type Map} of {type Nat}s to a {type NatMap}. + + # Examples + + ``` + toList + (fromMap (fromList [(1, "one"), (2, "two"), (3, "three"), (4, "four")])) + ``` + + ``` + toList + (fromMap + (fromList + [ (1, "one") + , (2, "two") + , (3, "three") + , (4, "four") + , (1, "one") + , (2, "two") + , (1, "one") + ])) + ``` + + # See also + + * {NatMap.toMap} to convert the other way. + }} + +data.NatMap.get : Nat -> NatMap a -> Optional a +data.NatMap.get k = cases + NatMap (Some t) -> NatMap.Nonempty.get k t + NatMap None -> None + +data.NatMap.get.doc : Doc +data.NatMap.get.doc = + {{ + Retrieves the value associated with the given key, if it exists. + + # Example + + ``` + NatMap.get 2 (NatMap.fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + # See also + + * {NatMap.contains} to check if a key is present in a {type NatMap}. + * {NatMap.delete} to delete a key from a {type NatMap}. + * {NatMap.insert.nonempty} to insert a key-value pair into a {type NatMap}. + }} + +data.NatMap.getAbove : Nat -> NatMap v -> Optional (Nat, v) +data.NatMap.getAbove k = cases + NatMap o -> Optional.flatMap (NatMap.Nonempty.getAbove k) o + +data.NatMap.getAbove.doc : Doc +data.NatMap.getAbove.doc = + use NatMap fromList + {{ + Returns the key-value pair in a {type NatMap} where the key is the smallest + one that is strictly larger than a given key. + + # Examples + + ``` + NatMap.getAbove 2 (fromList [(1, ?a), (2, ?b), (3, ?c)]) + ``` + + ``` + NatMap.getAbove 4 (fromList [(1, ?a), (2, ?b), (3, ?c)]) + ``` + + # See also + + * {NatMap.getAtLeast} to get the key-value pair where the key is the + smallest one that is larger than __or equal__ to a given key. + * {NatMap.getBelow} to get the key-value pair where the key is the largest + one that is strictly smaller than a given key. + * {NatMap.getAtMost} to get the key-value pair where the key is the largest + one that is smaller than or equal to a given key. + * {NatMap.Nonempty.getAbove} for the version of this that operates on a + nonempty {type NatMap}. + }} + +data.NatMap.getAtLeast : Nat -> NatMap v -> Optional (Nat, v) +data.NatMap.getAtLeast k = cases + NatMap o -> Optional.flatMap (NatMap.Nonempty.getAtLeast k) o + +data.NatMap.getAtLeast.doc : Doc +data.NatMap.getAtLeast.doc = + use NatMap fromList + {{ + Returns the key-value pair in a {type NatMap} where the key is the smallest + one that is larger than __or equal__ to a given key. + + # Examples + + ``` + NatMap.getAtLeast 2 (fromList [(1, ?a), (2, ?b), (3, ?c)]) + ``` + + ``` + NatMap.getAtLeast 4 (fromList [(1, ?a), (2, ?b), (3, ?c)]) + ``` + + # See also + + * {NatMap.getAbove} to get the key-value pair where the key is the smallest + one that is strictly larger than a given key. + * {NatMap.getBelow} to get the key-value pair where the key is the largest + one that is strictly smaller than a given key. + * {NatMap.getAtMost} to get the key-value pair where the key is the largest + one that is smaller than or equal to a given key. + * {NatMap.Nonempty.getAtLeast} for the version of this that operates on a + nonempty {type NatMap}. + }} + +data.NatMap.getAtMost : Nat -> NatMap v -> Optional (Nat, v) +data.NatMap.getAtMost k = cases + NatMap o -> Optional.flatMap (NatMap.Nonempty.getAtMost k) o + +data.NatMap.getAtMost.doc : Doc +data.NatMap.getAtMost.doc = + use NatMap fromList + {{ + Returns the key-value pair in a {type NatMap} where the key is the largest + one that is smaller than or equal to a given key. + + # Examples + + ``` + NatMap.getAtMost 2 (fromList [(1, ?a), (2, ?b), (3, ?c)]) + ``` + + ``` + NatMap.getAtMost 4 (fromList [(1, ?a), (2, ?b), (3, ?c)]) + ``` + + # See also + + * {NatMap.getBelow} to get the key-value pair where the key is the largest + one that is strictly smaller than a given key. + * {NatMap.getAbove} to get the key-value pair where the key is the smallest + one that is strictly larger than a given key. + * {NatMap.getAtLeast} to get the key-value pair where the key is the + smallest one that is larger than or equal to a given key. + * {NatMap.Nonempty.getAtMost} for the version of this that operates on a + nonempty {type NatMap}. + }} + +data.NatMap.getBelow : Nat -> NatMap v -> Optional (Nat, v) +data.NatMap.getBelow k = cases + NatMap o -> Optional.flatMap (NatMap.Nonempty.getBelow k) o + +data.NatMap.getBelow.doc : Doc +data.NatMap.getBelow.doc = + use NatMap fromList + {{ + Returns the key-value pair in a {type NatMap} where the key is the largest + one that is strictly smaller than a given key. + + # Examples + + ``` + NatMap.getBelow 2 (fromList [(1, ?a), (2, ?b), (3, ?c)]) + ``` + + ``` + NatMap.getBelow 4 (fromList [(1, ?a), (2, ?b), (3, ?c)]) + ``` + + # See also + + * {NatMap.getAtMost} to get the key-value pair where the key is the largest + one that is smaller than or equal to a given key. + * {NatMap.getAbove} to get the key-value pair where the key is the smallest + one that is strictly larger than a given key. + * {NatMap.getAtLeast} to get the key-value pair where the key is the + smallest one that is larger than or equal to a given key. + * {NatMap.Nonempty.getBelow} for the version of this that operates on a + nonempty {type NatMap}. + }} + +data.NatMap.getMax : NatMap a ->{Abort} a +data.NatMap.getMax t = + (v, _) = NatMap.maxView t + v + +data.NatMap.getMax.doc : Doc +data.NatMap.getMax.doc = + {{ + Retrieves the value associated with the largest key in the {type NatMap}. + + # Example + + ``` + toOptional! do NatMap.getMax (NatMap.fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + # See also + + * {NatMap.maxView} for a version of this that also returns the map with the + largest key removed. + * {NatMap.getMin} to retrieve the value associated with the smallest key. + * {NatMap.deleteMax} to remove the largest key from a {type NatMap}. + }} + +data.NatMap.getMin : NatMap a ->{Abort} a +data.NatMap.getMin t = + (v, _) = NatMap.minView t + v + +data.NatMap.getMin.doc : Doc +data.NatMap.getMin.doc = + {{ + Retrieves the value associated with the smallest key in the {type NatMap}. + + # Example + + ``` + toOptional! do NatMap.getMin (NatMap.fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + # See also + + * {NatMap.minView} for a version of this that also returns the map with the + smallest key removed. + * {NatMap.getMax} to retrieve the value associated with the largest key. + * {NatMap.deleteMin} to remove the smallest key. + }} + +data.NatMap.getOrAbort : Nat -> NatMap a ->{Abort} a +data.NatMap.getOrAbort k m = match NatMap.get k m with + None -> abort + Some v -> v + +data.NatMap.getOrAbort.doc : Doc +data.NatMap.getOrAbort.doc = + use NatMap fromList getOrAbort + {{ + Retrieves the value associated with the given key, or calls {abort} if the + key is not present. + + # Examples + + ``` + toOptional! do getOrAbort 2 (fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + ``` + toOptional! do getOrAbort 4 (fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + # See also + + * {NatMap.get} for a version of this that returns {type Optional} instead + of aborting. + * {NatMap.getOrElse} to return a default value if the key is not present. + }} + +data.NatMap.getOrElse : Nat -> a -> NatMap a -> a +data.NatMap.getOrElse k def m = match NatMap.get k m with + None -> def + Some v -> v + +data.NatMap.getOrElse.doc : Doc +data.NatMap.getOrElse.doc = + use NatMap fromList getOrElse + {{ + Retrieves the value associated with the given key, or returns the given + default value if the key is not present. + + # Examples + + ``` + getOrElse 2 0 (fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + ``` + getOrElse 4 0 (fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + # See also + + * {NatMap.get} for a version of this that returns {type Optional} instead + of a default value. + * {NatMap.getOrAbort} to {abort} if the key is not present. + }} + +data.NatMap.insert : Nat -> a -> NatMap a -> NatMap a +data.NatMap.insert k v m = toNatMap (NatMap.insert.nonempty k v m) + +data.NatMap.insert.doc : Doc +data.NatMap.insert.doc = + use NatMap fromList insert toList + {{ + Inserts a key-value pair into a {type NatMap}, replacing the existing value + if the key is already present. + + # Examples + + ``` + toList (insert 2 20 (fromList [(1, 10), (2, 30), (3, 30)])) + ``` + + ``` + toList (insert 4 40 (fromList [(1, 10), (2, 30), (3, 30)])) + ``` + + # See also + + * {NatMap.insert.nonempty} for a version of this that returns a + {type NatMap.Nonempty}. + * {NatMap.insertWith} for a version of this that allows you to specify a + function to combine the new and existing values. + * {NatMap.insertWithKey} for a version that also provides the key to the + combining function. + * {NatMap.insertGetWithKey} for a version that also returns the old value + if the key was already present. + * {NatMap.delete} to delete a key. + * {NatMap.get} to retrieve the value associated with a key. + }} + +data.NatMap.insert.nonempty : Nat -> a -> NatMap a -> NatMap.Nonempty a +data.NatMap.insert.nonempty k v = cases + NatMap (Some t) -> NatMap.Nonempty.insert k v t + NatMap None -> NatMap.singleton k v + +data.NatMap.insert.nonempty.doc : Doc +data.NatMap.insert.nonempty.doc = + use NatMap fromList + use NatMap.Nonempty toList + use NatMap.insert nonempty + {{ + Inserts a key-value pair into a {type NatMap}, replacing the existing value + if the key is already present. Returns a {type NatMap.Nonempty}. + + # Examples + + ``` + toList (nonempty 2 20 (fromList [(1, 10), (2, 30), (3, 30)])) + ``` + + ``` + toList (nonempty 4 40 (fromList [(1, 10), (2, 30), (3, 30)])) + ``` + + # See also + + * {NatMap.insertWith} for a version of this that allows you to specify a + function to combine the new and existing values. + * {NatMap.insertWithKey} for a version that also provides the key to the + combining function. + * {NatMap.insertGetWithKey} for a version that also returns the old value + if the key was already present. + * {NatMap.delete} to delete a key. + * {NatMap.get} to retrieve the value associated with a key. + }} + +data.NatMap.insertGetWithKey : + (Nat ->{g} a ->{g} a ->{g} a) + -> Nat + -> a + -> NatMap a + ->{g} (Optional a, NatMap.Nonempty a) +data.NatMap.insertGetWithKey f k v = cases + NatMap None -> (None, NatMap.singleton k v) + NatMap (Some t) -> Nonempty.insertGetWithKey f k v t + +data.NatMap.insertGetWithKey.doc : Doc +data.NatMap.insertGetWithKey.doc = + use Nat isEven + use NatMap fromList insertGetWithKey + use NatMap.Nonempty toList + {{ + Inserts a key-value pair into a {type NatMap}, returning the old value if the + key was already present. Returns a {type NatMap.Nonempty}. + + This takes a combining function that is passed the key, the new value, and + the old value if the key was already present. The combining function should + return the new value to be stored in the map. + + # Examples + + ``` + (oldKey, newMap) = + insertGetWithKey + (key old new -> (if isEven key then old else new)) + 2 + 20 + (fromList [(1, 10), (2, 30), (3, 30)]) + (oldKey, toList newMap) + ``` + + ``` + (oldKey, newMap) = + insertGetWithKey + (key old new -> (if isEven key then old else new)) + 4 + 40 + (fromList [(1, 10), (2, 30), (3, 30)]) + (oldKey, toList newMap) + ``` + + # See also + + * {NatMap.insertWithKey} for a version of this that does not return the old + value. + }} + +data.NatMap.insertWith : + (a ->{g} a ->{g} a) -> Nat -> a -> NatMap a ->{g} NatMap.Nonempty a +data.NatMap.insertWith f k v t = + NatMap.insertWithKey (do x' y' -> f x' y') k v t + +data.NatMap.insertWith.doc : Doc +data.NatMap.insertWith.doc = + use Nat + + use NatMap fromList insertWith + use NatMap.Nonempty toList + {{ + Inserts a key-value pair into a {type NatMap}, combining the new and existing + values if the key is already present. + + Returns a {type NatMap.Nonempty}. + + Takes a combining function that is passed the new and existing values. The + combining function should return the new value to be stored in the map. + + # Examples + + ``` + toList (insertWith (+) 2 20 (fromList [(1, 10), (2, 30), (3, 30)])) + ``` + + ``` + toList (insertWith (+) 4 40 (fromList [(1, 10), (2, 30), (3, 30)])) + ``` + + # See also + + * {NatMap.insertWithKey} for a version of this that also provides the key + to the combining function. + * {NatMap.insert.nonempty} for a version that just replaces the existing + value. + * {NatMap.insertGetWithKey} for a version that also returns the old value + if the key was already present. + }} + +data.NatMap.insertWithKey : + (Nat ->{g} a ->{g} a ->{g} a) -> Nat -> a -> NatMap a ->{g} NatMap.Nonempty a +data.NatMap.insertWithKey f k v = cases + NatMap None -> NatMap.singleton k v + NatMap (Some t) -> Nonempty.insertWithKey f k v t + +data.NatMap.insertWithKey.doc : Doc +data.NatMap.insertWithKey.doc = + use Nat + - isEven + use NatMap fromList insertWithKey + use NatMap.Nonempty toList + {{ + Inserts a key-value pair into a {type NatMap}, combining the new and existing + values if the key is already present. + + Returns a {type NatMap.Nonempty}. + + Takes a combining function that is passed the key as well as the new and + existing values. The combining function should return the new value to be + stored in the map. + + # Examples + + ``` + toList + (insertWithKey + (key x y -> (if isEven key then x + y else x - y)) + 2 + 20 + (fromList [(1, 10), (2, 30), (3, 30)])) + ``` + + ``` + toList + (insertWithKey + (key x y -> (if isEven key then x + y else x - y)) + 4 + 40 + (fromList [(1, 10), (2, 30), (3, 30)])) + ``` + + # See also + + * {NatMap.insertWith} for a version of this that does not provide the key + to the combining function. + * {NatMap.insert.nonempty} for a version that just replaces the existing + value. + * {NatMap.insertGetWithKey} for a version that also returns the old value + if the key was already present. + }} + +data.NatMap.internal.bim : + Nat -> Nat -> NatMap.Nonempty a -> NatMap.Nonempty a -> NatMap.Nonempty a +data.NatMap.internal.bim p m l r = + use Nat + + use NatMap.Nonempty size + sz = size l + size r + NatMap.Nonempty.Bin p m sz l r + +data.NatMap.internal.bin : Nat -> Nat -> NatMap a -> NatMap a -> NatMap a +data.NatMap.internal.bin = cases + p, m, l, NatMap None -> l + p, m, NatMap None, r -> r + p, m, NatMap (Some l), NatMap (Some r) -> + use Nat + + use NatMap.Nonempty size + sz = size l + size r + NatMap (Some (NatMap.Nonempty.Bin p m sz l r)) + +data.NatMap.internal.bin.doc : Doc +data.NatMap.internal.bin.doc = + {{ + Internal function used to construct a {type NatMap} node. Ensures that the + resulting tree has no empty branches. + }} + +data.NatMap.internal.branchMask : Nat -> Nat -> Nat +data.NatMap.internal.branchMask p1 p2 = highBitMask (Nat.xor p1 p2) + +data.NatMap.internal.branchMask.doc : Doc +data.NatMap.internal.branchMask.doc = + {{ + Internal function used to construct a {type NatMap} node. Computes the mask + of the most significant bit that differs between the two prefixes. + }} + +data.NatMap.internal.highBitMask : Nat -> Nat +data.NatMap.internal.highBitMask i = + use Nat - + setBit (63 - Nat.leadingZeros i) 0 + +data.NatMap.internal.highBitMask.doc : Doc +data.NatMap.internal.highBitMask.doc = + {{ + Internal function used to construct a {type NatMap} node. Computes the mask + of the most significant bit of the given integer. + }} + +data.NatMap.internal.join : + Nat -> NatMap.Nonempty a -> Nat -> NatMap.Nonempty a -> NatMap.Nonempty a +data.NatMap.internal.join p1 t1 p2 t2 = + use Nat == + use NatMap.internal bim + m = branchMask p1 p2 + p = mask p1 m + if Nat.and p1 m == 0 then bim p m t1 t2 else bim p m t2 t1 + +data.NatMap.internal.join.doc : Doc +data.NatMap.internal.join.doc = + {{ + Internal function used to construct a {type NatMap} node. Joins two trees and + computes the shared prefix and critical bit of the resulting node. + }} + +data.NatMap.internal.mask : Nat -> Nat -> Nat +data.NatMap.internal.mask i m = + use Nat - + Nat.and i (Nat.xor (Nat.complement (m - 1)) m) + +data.NatMap.internal.mask.doc : Doc +data.NatMap.internal.mask.doc = + {{ + Internal function used to construct a {type NatMap} node. Computes the prefix + of the given integer masked by the given mask. + }} + +data.NatMap.internal.nomatch : Nat -> Nat -> Nat -> Boolean +data.NatMap.internal.nomatch k p m = + use Nat != + mask k m != p + +data.NatMap.internal.nomatch.doc : Doc +data.NatMap.internal.nomatch.doc = + {{ + Internal function used to find the correct branch of a {type NatMap} node. + Checks whether the given key matches the given prefix and mask. + }} + +data.NatMap.internal.shorter : Nat -> Nat -> Boolean +data.NatMap.internal.shorter = + use Nat > + (>) + +data.NatMap.internal.shorter.doc : Doc +data.NatMap.internal.shorter.doc = + {{ + Internal function used to find the correct branch of a {type NatMap} node. + Checks whether one mask masks a shorter prefix than the other. + }} + +data.NatMap.internal.zero : Nat -> Nat -> Boolean +data.NatMap.internal.zero k m = + use Nat == + Nat.and k m == 0 + +data.NatMap.internal.zero.doc : Doc +data.NatMap.internal.zero.doc = + {{ Internal function to check if the bit at position `m` in `k` is ``0``. }} + +data.NatMap.intersect : NatMap a -> NatMap b -> NatMap a +data.NatMap.intersect = NatMap.intersectWith const + +data.NatMap.intersect.doc : Doc +data.NatMap.intersect.doc = + use NatMap fromList + {{ + Returns a {type NatMap} containing only the keys that are present in both of + the given {type NatMap}s. + + The values of the resulting {type NatMap} are the values of the first + {type NatMap}. + + # Example + + ``` + NatMap.toList + (NatMap.intersect + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(2, 20), (3, 30), (4, 40)])) + ``` + + # See also + + * {NatMap.intersectWith} for a version that also combines the values of the + two {type NatMap}s. + * {NatMap.intersectWithKey} for a version that combines the values and + additionally provides the key to the combining function. + }} + +data.NatMap.intersectWith : + (a ->{g} b ->{g} a) -> NatMap a -> NatMap b ->{g} NatMap a +data.NatMap.intersectWith f = NatMap.intersectWithKey do x y -> f x y + +data.NatMap.intersectWith.doc : Doc +data.NatMap.intersectWith.doc = + use Nat + + use NatMap fromList + {{ + Returns a {type NatMap} containing only the keys that are present in both of + the given {type NatMap}s. The values of the resulting {type NatMap} are + computed by combining the values of the two {type NatMap}s using the given + combining function. + + # Example + + ``` + NatMap.toList + (NatMap.intersectWith + (+) + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(2, 20), (3, 30), (4, 40)])) + ``` + + # See also + + * {NatMap.intersectWithKey} for a version that additionally provides + * {NatMap.intersect} for a version that throws away the values of the + second {type NatMap}. the key to the combining function. + }} + +data.NatMap.intersectWithKey : + (Nat ->{g} a ->{g} b ->{g} a) -> NatMap a -> NatMap b ->{g} NatMap a +data.NatMap.intersectWithKey f = cases + NatMap None, _ -> NatMap.empty + NatMap _, NatMap None -> NatMap.empty + NatMap (Some t1), NatMap (Some t2) -> + NatMap.Nonempty.intersectWithKey f t1 t2 + +data.NatMap.intersectWithKey.doc : Doc +data.NatMap.intersectWithKey.doc = + use Nat + + use NatMap fromList + {{ + Returns a {type NatMap} containing only the keys that are present in both of + the given {type NatMap}s. The values of the resulting {type NatMap} are + computed by combining the values of the two {type NatMap}s using the given + combining function. The combining function can take the key into account. + + # Example + + ``` + NatMap.toList + (NatMap.intersectWithKey + (k x y -> k + x + y) + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(2, 20), (3, 30), (4, 40)])) + ``` + + # See also + + * {NatMap.intersectWith} for a version that does not provide the key to the + combining function. + * {NatMap.intersect} for a version that throws away the values of the + second {type NatMap}. + }} + +data.NatMap.isEmpty : NatMap a -> Boolean +data.NatMap.isEmpty = cases + NatMap None -> true + _ -> false + +data.NatMap.isEmpty.doc : Doc +data.NatMap.isEmpty.doc = + use NatMap fromList isEmpty + {{ + Checks whether the given {type NatMap} is empty. + + # Examples + + ``` + isEmpty (fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + ``` + isEmpty (fromList []) + ``` + + # See also + + * {NatMap.size} to get the number of elements in a {type NatMap}. + }} + +data.NatMap.isProperSubmapOf : NatMap a -> NatMap a -> Boolean +data.NatMap.isProperSubmapOf = NatMap.isProperSubmapOfBy (===) + +data.NatMap.isProperSubmapOf.doc : Doc +data.NatMap.isProperSubmapOf.doc = + use NatMap fromList isProperSubmapOf + {{ + Checks whether the first {type NatMap} is a proper submap of the second + {type NatMap}. + + A {type NatMap} is a proper submap of another {type NatMap} if the second + {type NatMap} contains all the keys of the first {type NatMap} and at least + one more key, and the values under the shared keys are equal according to the + {===} function. + + # Examples + + ``` + isProperSubmapOf + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(1, 10), (2, 20), (3, 30), (4, 40)]) + ``` + + ``` + isProperSubmapOf + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + ``` + isProperSubmapOf + (fromList [(1, 10), (2, 20), (3, 30)]) (fromList [(1, 10), (2, 20)]) + ``` + + # See also + + * {NatMap.isSubmapOf} for a version that returns `` true `` also if the two + {type NatMap}s are identical. + * {NatMap.isProperSubmapOfBy} for a version that allows you to compare the + values under the keys with a custom function. + }} + +data.NatMap.isProperSubmapOfBy : + (a ->{g} a ->{g} Boolean) -> NatMap a -> NatMap a ->{g} Boolean +data.NatMap.isProperSubmapOfBy f t1 t2 = + NatMap.submapCompareBy f t1 t2 === Some Less + +data.NatMap.isProperSubmapOfBy.doc : Doc +data.NatMap.isProperSubmapOfBy.doc = + use Nat == + use NatMap fromList isProperSubmapOfBy + {{ + Checks whether the first {type NatMap} is a proper submap of the second + {type NatMap}, and compares the values under the keys with a custom function. + + Returns `` true `` if the second {type NatMap} contains all the keys of the + first {type NatMap} and at least one more key, and the values under the + common keys are equal according to the given function. + + # Examples + + ``` + isProperSubmapOfBy + (x y -> x == y) + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(1, 10), (2, 20), (3, 30), (4, 40)]) + ``` + + ``` + isProperSubmapOfBy + (x y -> x == y) + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + ``` + isProperSubmapOfBy + (x y -> x == y) + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(1, 10), (2, 20)]) + ``` + + # See also + + * {NatMap.isSubmapOfBy} for a version that returns `` true `` also if the + two {type NatMap}s have exactly the same keys. + * {NatMap.isProperSubmapOf} for a version that compares the values under + the keys using the {===} function. + }} + +data.NatMap.isSubmapOf : NatMap a -> NatMap a -> Boolean +data.NatMap.isSubmapOf = NatMap.isSubmapOfBy (===) + +data.NatMap.isSubmapOf.doc : Doc +data.NatMap.isSubmapOf.doc = + use NatMap fromList isSubmapOf + {{ + Checks whether the first {type NatMap} is a submap of the second + {type NatMap}. + + A {type NatMap} is a submap of another {type NatMap} if the second + {type NatMap} contains all the keys of the first {type NatMap}, and the + values under the shared keys in one map are equal to the values under the + shared keys in the other map according to the {===} function. + + # Examples + + ``` + isSubmapOf + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(1, 10), (2, 20), (3, 30), (4, 40)]) + ``` + + ``` + isSubmapOf + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + ``` + isSubmapOf + (fromList [(1, 10), (2, 20), (3, 30)]) (fromList [(1, 10), (2, 20)]) + ``` + + # See also + + * {NatMap.isProperSubmapOf} for a version that returns `` true `` only if + the second {type NatMap} contains at least one more key than the first. + * {NatMap.isSubmapOfBy} for a version that allows you to compare the values + under the keys with a custom function. + }} + +data.NatMap.isSubmapOfBy : + (a ->{g} a ->{g} Boolean) -> NatMap a -> NatMap a ->{g} Boolean +data.NatMap.isSubmapOfBy f t1 t2 = match NatMap.submapCompareBy f t1 t2 with + Some Less -> true + Some Equal -> true + _ -> false + +data.NatMap.isSubmapOfBy.doc : Doc +data.NatMap.isSubmapOfBy.doc = + use Nat == + use NatMap fromList isSubmapOfBy + {{ + Checks whether the first {type NatMap} is a submap of the second + {type NatMap}, and compares the values under the keys with a custom function. + + Returns `` true `` if the second {type NatMap} contains all the keys of the + first {type NatMap}, and the values under the common keys are equal according + to the given function. + + # Examples + + ``` + isSubmapOfBy + (x y -> x == y) + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(1, 10), (2, 20), (3, 30), (4, 40)]) + ``` + + ``` + isSubmapOfBy + (x y -> x == y) + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + ``` + isSubmapOfBy + (x y -> x == y) + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(1, 10), (2, 20)]) + ``` + + # See also + + * {NatMap.isProperSubmapOfBy} for a version that returns `` true `` only if + the second {type NatMap} contains at least one more key than the first. + * {NatMap.isSubmapOf} for a version that compares the values under the keys + using the {===} function. + }} + +data.NatMap.keys : NatMap a -> [Nat] +data.NatMap.keys = + use List +: + NatMap.foldWithKey (k _ ks -> k +: ks) [] + +data.NatMap.keys.doc : Doc +data.NatMap.keys.doc = + {{ + Returns the keys of a {type NatMap} as a list. + + # Example + + ``` + NatMap.keys (NatMap.fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + # See also + + * {NatMap.values} returns the values rather than the keys. + * {NatMap.toList} returns both the keys and values. + }} + +data.NatMap.keySet : NatMap a -> NatSet +data.NatMap.keySet = cases + NatMap None -> NatSet.empty + NatMap (Some t) -> NatSet (Some (Nonempty.keySet t)) + +data.NatMap.keySet.doc : Doc +data.NatMap.keySet.doc = + use NatMap toList + {{ + Returns the {type NatSet} of keys in a {type NatMap}. + + # Example + + ``` + toList (NatMap.fromList [(1, "a"), (2, "b"), (3, "c")]) + ``` + + # See also + + * {NatMap.keys} to get the {type List} of keys in a {type NatMap}. + * {NatMap.values} to get the {type List} of values in a {type NatMap}. + * {toList} to convert a {type NatMap} to a {type List} of key-value pairs. + }} + +test> data.NatMap.keySet.test : [Result] +data.NatMap.keySet.test = + test.verify do + use NatSet == + use Random nat + _ = Each.range 0 100 + xs = Random.listOf (do Tuple.pair nat() nat()) do Random.natIn 0 100 + ensure + (NatMap.keySet (NatMap.fromList xs) == NatSet.fromList (List.map at1 xs)) + +data.NatMap.map : (a ->{g} b) -> NatMap a ->{g} NatMap b +data.NatMap.map f = NatMap.mapWithKey do x -> f x + +data.NatMap.map.doc : Doc +data.NatMap.map.doc = + use Nat + + {{ + Applies a function to every value in a {type NatMap}. + + # Example + + ``` + NatMap.toList + (NatMap.map (x -> x + 1) (NatMap.fromList [(1, 10), (2, 20), (3, 30)])) + ``` + + # See also + + * {NatMap.mapWithKey} applies a function to both the keys and values. + * {NatMap.mapOptional} applies a function to the values, removing the ones + that return {None}. + * {NatMap.mapEither} applies an {type Either}-valued function to the + values, partitioning the {type NatMap} into two {type NatMap}s. + * {NatMap.adjust} applies a function to the value under a specific key. + }} + +data.NatMap.mapEither : + (a ->{g} Either b c) -> NatMap a ->{g} (NatMap b, NatMap c) +data.NatMap.mapEither f = NatMap.mapEitherWithKey do x -> f x + +data.NatMap.mapEither.doc : Doc +data.NatMap.mapEither.doc = + {{ + Applies an {type Either}-valued function to every value in a {type NatMap}, + partitioning the {type NatMap} into two {type NatMap}s. + + # Example + + ``` + Tuple.bimap + NatMap.toList + (NatMap.mapEither + (x -> (if Nat.isEven x then Left x else Right x)) + (NatMap.fromList [(1, 11), (2, 22), (3, 33)])) + ``` + + # See also + + * {NatMap.mapEitherWithKey} for a version of this that applies the function + to both the keys and values. + * {NatMap.mapOptional} applies a function to the values, removing the ones + that return {None}. + * {NatMap.map} just applies a function to all the values. + * {NatMap.partition} applies a predicate to the values, partitioning the + {type NatMap} into two {type NatMap}s. + }} + +data.NatMap.mapEitherWithKey : + (Nat ->{g} a ->{g} Either b c) -> NatMap a ->{g} (NatMap b, NatMap c) +data.NatMap.mapEitherWithKey f = cases + NatMap (Some t) -> Nonempty.mapEitherWithKey f t + NatMap None -> (NatMap.empty, NatMap.empty) + +data.NatMap.mapEitherWithKey.doc : Doc +data.NatMap.mapEitherWithKey.doc = + {{ + Applies an {type Either}-valued function to every value in a {type NatMap}, + partitioning the {type NatMap} into two {type NatMap}s. + + # Example + + ``` + Tuple.bimap + NatMap.toList + (NatMap.mapEitherWithKey + (k x -> (if Nat.isEven k then Left x else Right x)) + (NatMap.fromList [(1, 10), (2, 20), (3, 30)])) + ``` + + # See also + + * {NatMap.mapEither} for a version of this that applies the function only + to the values. + * {NatMap.mapOptionalWithKey} applies a function to the entries, removing + the ones that return {None}. + * {NatMap.mapWithKey} just applies a function to all the entries. + * {NatMap.partitionWithKey} applies a predicate to the entries, + partitioning the {type NatMap} into two {type NatMap}s. + }} + +data.NatMap.mapOptional : (a ->{g} Optional b) -> NatMap a ->{g} NatMap b +data.NatMap.mapOptional f = NatMap.mapOptionalWithKey do x -> f x + +data.NatMap.mapOptional.doc : Doc +data.NatMap.mapOptional.doc = + {{ + Applies a function to every value in a {type NatMap}, removing the ones that + return {None}. + + # Example + + ``` + NatMap.toList + (NatMap.mapOptional + (x -> (if Nat.isEven x then Some x else None)) + (NatMap.fromList [(1, 11), (2, 22), (3, 33)])) + ``` + + # See also + + * {NatMap.mapOptionalWithKey} for a version of this that applies the + function to both the keys and values. + * {NatMap.mapEither} applies an {type Either}-valued function to the + values, partitioning the {type NatMap} into two {type NatMap}s. + * {NatMap.map} just applies a function to all the values. + * {NatMap.filter} applies a predicate to the values, removing the ones that + return {false}. + }} + +data.NatMap.mapOptionalWithKey : + (Nat ->{g} a ->{g} Optional b) -> NatMap a ->{g} NatMap b +data.NatMap.mapOptionalWithKey f = cases + NatMap (Some t) -> Nonempty.mapOptionalWithKey f t + NatMap None -> NatMap.empty + +data.NatMap.mapOptionalWithKey.doc : Doc +data.NatMap.mapOptionalWithKey.doc = + {{ + Applies a function to every key-value pair in a {type NatMap}, removing the + ones that return {None}. + + # Example + + ``` + NatMap.toList + (NatMap.mapOptionalWithKey + (k x -> (if Nat.isEven k then Some x else None)) + (NatMap.fromList [(1, 10), (2, 20), (3, 30)])) + ``` + + # See also + + * {NatMap.mapOptional} for a version of this that applies the function only + to the values. + * {NatMap.mapEitherWithKey} applies an {type Either}-valued function to the + entries, partitioning the {type NatMap} into two {type NatMap}s. + * {NatMap.mapWithKey} just applies a function to all the entries. + * {NatMap.filterWithKey} applies a predicate to the entries, removing the + ones that return {false}. + }} + +data.NatMap.mapWithKey : (Nat ->{g} a ->{g} b) -> NatMap a ->{g} NatMap b +data.NatMap.mapWithKey f = cases + NatMap (Some t) -> toNatMap (NatMap.Nonempty.mapWithKey f t) + NatMap None -> NatMap.empty + +data.NatMap.mapWithKey.doc : Doc +data.NatMap.mapWithKey.doc = + use Nat + + {{ + Applies a function to every key-value pair in a {type NatMap}. + + # Example + + ``` + NatMap.toList + (NatMap.mapWithKey (+) (NatMap.fromList [(1, 10), (2, 20), (3, 30)])) + ``` + + # See also + + * {NatMap.map} for a version of this that applies the function only to the + values. + * {NatMap.mapOptionalWithKey} applies a function to the entries, removing + the ones that return {None}. + * {NatMap.mapEitherWithKey} applies an {type Either}-valued function to the + entries, partitioning the {type NatMap} into two {type NatMap}s. + }} + +data.NatMap.maxKey : NatMap a ->{Abort} Nat +data.NatMap.maxKey = cases + NatMap (Some t) -> Nonempty.maxKey t + NatMap None -> abort + +data.NatMap.maxKey.doc : Doc +data.NatMap.maxKey.doc = + {{ + Returns the largest key in the map. Calls {abort} if the map is empty. + + # Example + + ``` + toOptional! do + NatMap.fromList [(1, "a"), (2, "b"), (3, "c")] |> NatMap.maxKey + ``` + }} + +data.NatMap.maxView : NatMap a ->{Abort} (a, NatMap a) +data.NatMap.maxView t = + ((_, v), t') = NatMap.breakOffMax t + (v, t') + +data.NatMap.maxView.doc : Doc +data.NatMap.maxView.doc = + {{ + Returns the value of the largest key in the {type NatMap} and the + {type NatMap} with that key removed. + + # Example + + ``` + toOptional! do NatMap.maxView (NatMap.fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + # See also + + * {NatMap.breakOffMax} for a version of this that returns the key as well + as the value. + * {NatMap.minView} for the value under the smallest key. + * {NatMap.breakOffMin} for the key and value under the smallest key. + }} + +data.NatMap.minKey : NatMap a ->{Abort} Nat +data.NatMap.minKey = cases + NatMap (Some t) -> Nonempty.minKey t + NatMap None -> abort + +data.NatMap.minView : NatMap a ->{Abort} (a, NatMap a) +data.NatMap.minView t = + ((_, v), t') = NatMap.breakOffMin t + (v, t') + +data.NatMap.minView.doc : Doc +data.NatMap.minView.doc = + {{ + Returns the value of the smallest key in the {type NatMap} and the + {type NatMap} with that key removed. + + # Example + + ``` + toOptional! do NatMap.minView (NatMap.fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + # See also + + * {NatMap.breakOffMin} for a version of this that returns the key as well + as the value. + * {NatMap.maxView} for the value under the largest key. + * {NatMap.breakOffMax} for the key and value under the largest key. + }} + +data.NatMap.Nonempty.adjust : + (a ->{g} a) -> Nat -> NatMap.Nonempty a ->{g} NatMap.Nonempty a +data.NatMap.Nonempty.adjust f k t = + NatMap.Nonempty.adjustWithKey (do x -> f x) k t + +data.NatMap.Nonempty.adjust.doc : Doc +data.NatMap.Nonempty.adjust.doc = + {{ + Modifies the value at a key in a {type NatMap.Nonempty}, using a function. If + the key is not present, the map is returned unchanged. + + # Example + + ``` + NatMap.Nonempty.toList + (NatMap.Nonempty.adjust + Text.toUppercase + 1 + (NatMap.Nonempty.fromList ((1, "foo") +| [(2, "bar")]))) + ``` + + # See also + + * {NatMap.Nonempty.adjustWithKey} for a version of this that also receives + the key. + * {NatMap.Nonempty.update} for a version that can delete the key. + * {Nonempty.updateWithKey} can delete the key and also take the key into + account. + * {NatMap.Nonempty.alter} can also insert the key if not present. + * {NatMap.Nonempty.map} can modify all values at once. + * {NatMap.Nonempty.mapWithKey} can modify all values at once and also + receives the key. + }} + +data.NatMap.Nonempty.adjustWithKey : + (Nat ->{g} a ->{g} a) -> Nat -> NatMap.Nonempty a ->{g} NatMap.Nonempty a +data.NatMap.Nonempty.adjustWithKey f k = cases + t@(NatMap.Nonempty.Bin prefix mask _ l r) + | nomatch k prefix mask -> t + | Nat.and k mask Nat.== 0 -> + NatMap.internal.bim + prefix mask (data.NatMap.Nonempty.adjustWithKey f k l) r + | otherwise -> + NatMap.internal.bim + prefix mask l (data.NatMap.Nonempty.adjustWithKey f k r) + t@(NatMap.Nonempty.Tip k' v) + | k Nat.== k' -> NatMap.Nonempty.Tip k (f k v) + | otherwise -> t + +data.NatMap.Nonempty.adjustWithKey.doc : Doc +data.NatMap.Nonempty.adjustWithKey.doc = + use Nat + + {{ + Modifies the value at a key in a {type NatMap.Nonempty}, using a function. If + the key is not present, the map is returned unchanged. The function receives + the key as its first argument and the value as its second. + + # Example + + ``` + NatMap.Nonempty.toList + (NatMap.Nonempty.adjustWithKey + (+) 1 (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20)]))) + ``` + + # See also + + * {NatMap.Nonempty.adjust} for a version of this that does not receive the + key. + * {Nonempty.updateWithKey} for a version that can delete the key. + * {Nonempty.alterWithKey} can also insert the key if not present. + * {NatMap.Nonempty.mapWithKey} can modify all values at once. + }} + +data.NatMap.Nonempty.align : + NatMap.Nonempty a -> NatMap.Nonempty b -> NatMap.Nonempty (OneOrBoth a b) +data.NatMap.Nonempty.align = NatMap.Nonempty.alignWith id + +data.NatMap.Nonempty.align.doc : Doc +data.NatMap.Nonempty.align.doc = + use NatMap.Nonempty fromList + {{ + Aligns two nonempty maps into a nonempty map of {type OneOrBoth} values. + + The result will have the same keys as the union of the keys of the two input + maps, and each value will be a {type OneOrBoth} containing the corresponding + values from the two input maps. If a key is present in only one of the input + maps, the result will contain {This} or {That} values accordingly. If a key + is present in both input maps, the result will contain a {Both} value. + + # Example + + ``` + NatMap.Nonempty.toList + (NatMap.Nonempty.align + (fromList ((1, "hello") +| [(2, "world")])) + (fromList ((2, 42) +| [(3, 43)]))) + ``` + + # See also + + * {NatMap.Nonempty.alignWith} - a variant where you can specify a function + to apply to the values. + }} + +data.NatMap.Nonempty.alignWith : + (OneOrBoth a b ->{g} c) + -> NatMap.Nonempty a + -> NatMap.Nonempty b + ->{g} NatMap.Nonempty c +data.NatMap.Nonempty.alignWith f m1 m2 = + NatMap.Nonempty.alignWithKey (_ x -> f x) m1 m2 + +data.NatMap.Nonempty.alignWith.doc : Doc +data.NatMap.Nonempty.alignWith.doc = + use NatMap.Nonempty fromList + use Text ++ + {{ + Aligns two nonempty maps into a nonempty map of values using a function. + + The result will have the same keys as the union of the keys of the two input + maps, and each value will be the result of applying the given function to the + corresponding values from the two input maps – {This} for keys that only + appear in the first map, {That} for keys that only appear in the second map, + and {Both} for keys that appear in both maps. + + # Example + + ``` + f = cases + This a -> "only in the first map: " ++ a + That b -> "only in the second map: " ++ b + Both a b -> "in both maps: " ++ a ++ " and " ++ b + NatMap.Nonempty.values + (NatMap.Nonempty.alignWith + f + (fromList ((1, "circuit") +| [(2, "quasar")])) + (fromList ((2, "voyage") +| [(3, "harmony")]))) + ``` + + # See also + + * {NatMap.Nonempty.align} - a variant that returns a nonempty map of + {type OneOrBoth} values. + * {NatMap.Nonempty.alignWithKey} - a variant where the function also + receives the key. + }} + +data.NatMap.Nonempty.alignWithKey : + (Nat ->{e} OneOrBoth a b ->{f} c) + -> NatMap.Nonempty a + -> NatMap.Nonempty b + ->{e, f} NatMap.Nonempty c +data.NatMap.Nonempty.alignWithKey f = cases + t1@(NatMap.Nonempty.Bin p1 m1 sz1 l1 r1), + t2@(NatMap.Nonempty.Bin p2 m2 sz2 l2 r2) + | shorter m1 m2 -> + if nomatch p2 p1 m1 then + internal.join + p1 + (NatMap.Nonempty.mapWithKey (k a -> f k (This a)) t1) + p2 + (NatMap.Nonempty.mapWithKey (k b -> f k (That b)) t2) + else + if Nat.and p2 m1 Nat.== 0 then + NatMap.internal.bim + p1 + m1 + (data.NatMap.Nonempty.alignWithKey f l1 t2) + (NatMap.Nonempty.mapWithKey (k a -> f k (This a)) r1) + else + NatMap.internal.bim + p1 + m1 + (NatMap.Nonempty.mapWithKey (k a -> f k (This a)) l1) + (data.NatMap.Nonempty.alignWithKey f r1 t2) + | shorter m2 m1 -> + if nomatch p1 p2 m2 then + internal.join + p1 + (NatMap.Nonempty.mapWithKey (k a -> f k (This a)) t1) + p2 + (NatMap.Nonempty.mapWithKey (k b -> f k (That b)) t2) + else + if Nat.and p1 m2 Nat.== 0 then + NatMap.internal.bim + p2 + m2 + (data.NatMap.Nonempty.alignWithKey f t1 l2) + (NatMap.Nonempty.mapWithKey (k b -> f k (That b)) r2) + else + NatMap.internal.bim + p2 + m2 + (NatMap.Nonempty.mapWithKey (k b -> f k (That b)) l2) + (data.NatMap.Nonempty.alignWithKey f t1 r2) + | p1 Nat.== p2 -> + NatMap.internal.bim + p1 + m1 + (data.NatMap.Nonempty.alignWithKey f l1 l2) + (data.NatMap.Nonempty.alignWithKey f r1 r2) + | otherwise -> + internal.join + p1 + (NatMap.Nonempty.mapWithKey (k a -> f k (This a)) t1) + p2 + (NatMap.Nonempty.mapWithKey (k b -> f k (That b)) t2) + NatMap.Nonempty.Tip k v, t -> + t' = NatMap.Nonempty.mapWithKey (k' y -> f k' (That y)) t + match NatMap.Nonempty.get k t with + None -> NatMap.Nonempty.insert k (f k (This v)) t' + Some x -> NatMap.Nonempty.adjust (_ -> f k (Both v x)) k t' + t, NatMap.Nonempty.Tip k v -> + t' = NatMap.Nonempty.mapWithKey (k' x -> f k' (This x)) t + match NatMap.Nonempty.get k t with + None -> NatMap.Nonempty.insert k (f k (That v)) t' + Some x -> NatMap.Nonempty.adjust (_ -> f k (Both x v)) k t' + +data.NatMap.Nonempty.alignWithKey.doc : Doc +data.NatMap.Nonempty.alignWithKey.doc = + use Nat toText + use NatMap.Nonempty fromList + use Text ++ + {{ + Aligns two nonempty maps into a nonempty map of values using a function. + + The result will have the same keys as the union of the keys of the two input + maps, and each value will be the result of applying the given function to the + corresponding key-value pairs from the two input maps. The function receives + {This} for values under keys that are present in only the first input map, + {That} for values under keys that are present in only the second input map, + and {Both} for keys that are present in both input maps. + + # Example + + ``` + f k = cases + This a -> "only in the first map: " ++ toText k ++ " -> " ++ a + That b -> "only in the second map: " ++ toText k ++ " -> " ++ b + Both a b -> "in both maps: " ++ toText k ++ " -> " ++ a ++ " and " ++ b + NatMap.Nonempty.values + (NatMap.Nonempty.alignWithKey + f + (fromList ((1, "circuit") +| [(2, "quasar")])) + (fromList ((2, "voyage") +| [(3, "harmony")]))) + ``` + + # See also + + * {NatMap.Nonempty.align} - a variant that returns a nonempty map of + {type OneOrBoth} values. + * {NatMap.Nonempty.alignWith} - a variant where the function doesn't take + the key. + }} + +data.NatMap.Nonempty.alter : + (Optional a ->{g} Optional a) -> Nat -> NatMap.Nonempty a ->{g} NatMap a +data.NatMap.Nonempty.alter f k t = Nonempty.alterWithKey (do x -> f x) k t + +data.NatMap.Nonempty.alter.doc : Doc +data.NatMap.Nonempty.alter.doc = + use NatMap toList + use NatMap.Nonempty alter fromList + use Text ++ + {{ + An expression `` alter f k t `` alters the value under the key `k` in the + {type NatMap.Nonempty} `t`, or the absence thereof, using the function `f`. + Returns a (possibly empty) {type NatMap} with the new value. + + # Examples + + If the function returns {None}, the key is deleted: + + ``` + toList (alter (do None) 1 (fromList ((1, "foo") +| [(2, "bar")]))) + ``` + + If the function returns ``Some x``, the value under the key is updated: + + ``` + toList + (alter + (x -> Some (Optional.getOrElse "" x ++ "baz")) + 1 + (fromList ((1, "foo") +| [(2, "bar")]))) + ``` + + If the key is not present, the function receives {None}, and can insert the + key by returning {Some} of the new value: + + ``` + toList (alter (do Some "baz") 3 (fromList ((1, "foo") +| [(2, "bar")]))) + ``` + + # See also + + * {Nonempty.alterWithKey} for a version of this that also receives the key. + * {NatMap.Nonempty.update} for a version that does nothing if the key is + not present. + }} + +data.NatMap.Nonempty.alterWithKey : + (Nat ->{g} Optional a ->{g} Optional a) + -> Nat + -> NatMap.Nonempty a + ->{g} NatMap a +data.NatMap.Nonempty.alterWithKey f k = cases + t@(NatMap.Nonempty.Bin prefix mask _ l r) + | nomatch k prefix mask -> + match f k None with + None -> toNatMap t + Some v -> toNatMap (internal.join k (NatMap.Nonempty.Tip k v) prefix t) + | Nat.and k mask Nat.== 0 -> + NatMap.internal.bin + prefix mask (data.NatMap.Nonempty.alterWithKey f k l) (toNatMap r) + | otherwise -> + NatMap.internal.bin + prefix mask (toNatMap l) (data.NatMap.Nonempty.alterWithKey f k r) + t@(NatMap.Nonempty.Tip k' v) + | k Nat.== k' -> + match f k (Some v) with + None -> NatMap.empty + Some v' -> toNatMap (NatMap.Nonempty.Tip k v') + | otherwise -> + match f k None with + None -> toNatMap t + Some v -> toNatMap (internal.join k (NatMap.Nonempty.Tip k v) k' t) + +data.NatMap.Nonempty.alterWithKey.doc : Doc +data.NatMap.Nonempty.alterWithKey.doc = + use Nat * + + use NatMap toList + use NatMap.Nonempty fromList + use Nonempty alterWithKey + {{ + An expression `` alterWithKey f k t `` alters the value under the key `k` in + the {type NatMap.Nonempty} `t`, or the absence thereof, using the function + `f`. + + The function receives the key as its first argument and the value as its + second (or {None} if the key is not present). + + # Examples + + If the function returns {None}, the key is deleted: + + ``` + toList + (alterWithKey (do do None) 1 (fromList ((1, "foo") +| [(2, "bar")]))) + ``` + + If the function returns ``Some x``, the value under the key is updated to + `x`: + + ``` + toList + (alterWithKey + (k x -> Optional.map (v -> v + k) x) + 1 + (fromList ((1, 10) +| [(2, 20)]))) + ``` + + If the key is not present, the function receives {None} for the value, and + can insert the key by returning {Some} of the new value. + + ``` + toList + (alterWithKey + (k x -> Optional.orElse x (Some (k * 10))) + 3 + (fromList ((1, 10) +| [(2, 20)]))) + ``` + + # See also + + * {NatMap.Nonempty.alter} for a version of this that does not receive the + key. + * {Nonempty.updateWithKey} for a version that does nothing if the key is + not present. + }} + +data.NatMap.Nonempty.breakOffMax : NatMap.Nonempty a -> ((Nat, a), NatMap a) +data.NatMap.Nonempty.breakOffMax = cases + t@(NatMap.Nonempty.Bin p m sz l r) -> + use NatMap.internal bin + up = cases + NatMap.Nonempty.Bin p m sz l r -> + (result, r') = up r + (result, bin p m (toNatMap l) r') + NatMap.Nonempty.Tip k v -> ((k, v), NatMap.empty) + let + (result, r') = up r + (result, bin p m (toNatMap l) r') + NatMap.Nonempty.Tip k v -> ((k, v), NatMap.empty) + +data.NatMap.Nonempty.breakOffMax.doc : Doc +data.NatMap.Nonempty.breakOffMax.doc = + {{ + Returns the maximum key and value in the {type NatMap.Nonempty}, and the + (possibly empty) {type NatMap} without that key. + + # Example + + ``` + Tuple.second + NatMap.toList + (NatMap.Nonempty.breakOffMax + (NatMap.Nonempty.fromList ((1, "foo") +| [(2, "bar")]))) + ``` + + # See also + + * {NatMap.Nonempty.breakOffMin} for the opposite operation, returning the + minimum key and value. + * {NatMap.Nonempty.deleteMax} for a version that only returns the updated + map. + * {NatMap.Nonempty.getMax} for a version that does not remove the key. + }} + +data.NatMap.Nonempty.breakOffMin : NatMap.Nonempty a -> ((Nat, a), NatMap a) +data.NatMap.Nonempty.breakOffMin = cases + t@(NatMap.Nonempty.Bin p m sz l r) -> + use NatMap.internal bin + up = cases + NatMap.Nonempty.Bin p m sz l r -> + (result, l') = up l + (result, bin p m l' (toNatMap r)) + NatMap.Nonempty.Tip k v -> ((k, v), NatMap.empty) + let + (result, l') = up l + (result, bin p m l' (toNatMap r)) + NatMap.Nonempty.Tip k v -> ((k, v), NatMap.empty) + +data.NatMap.Nonempty.breakOffMin.doc : Doc +data.NatMap.Nonempty.breakOffMin.doc = + {{ + Returns the minimum key and value in the {type NatMap.Nonempty}, and the map + without that key. + + # Example + + ``` + Tuple.second + NatMap.toList + (NatMap.Nonempty.breakOffMin + (NatMap.Nonempty.fromList ((1, "foo") +| [(2, "bar")]))) + ``` + + # See also + + * {NatMap.Nonempty.breakOffMax} for the opposite operation, returning the + maximum key and value. + * {NatMap.Nonempty.deleteMin} for a version that only returns the updated + map. + * {NatMap.Nonempty.getMin} for a version that does not remove the key. + }} + +data.NatMap.Nonempty.compareBy : + (a ->{g} a ->{g} Ordering) + -> NatMap.Nonempty a + -> NatMap.Nonempty a + ->{g} Ordering +data.NatMap.Nonempty.compareBy f m1 m2 = + use Nat + - < <= > >= + use NatMap.Nonempty Bin Tip size split + use Nonempty maxKey + use Universal ordering + go = cases + Tip k1 v1, Tip k2 v2 -> Ordering.andThen (ordering k1 k2) (f v1 v2) + t1@(Bin p1 mask1 sz1 l1 r1), t2@(Bin p2 mask2 sz2 l2 r2) -> + largest1 = p1 + mask1 - 1 + largest2 = p2 + mask2 - 1 + if largest1 < p2 then Less + else + if largest2 < p1 then Greater + else + match go l1 l2 with + Equal + | size l1 < size l2 -> + match split (maxKey l1) l2 with + (_, _, NatMap None) -> Equal + (_, _, NatMap (Some l2r)) -> + match go r1 l2r with + Equal + | size r1 <= size l2r -> Less + | otherwise -> + match split (maxKey l2) r1 with + (_, _, NatMap None) -> Equal + (_, _, NatMap (Some r1r)) -> go r1r r2 + x -> x + | size l1 > size l2 -> + match split (maxKey l2) l1 with + (_, _, NatMap None) -> Equal + (_, _, NatMap (Some l1r)) -> + match go l1r r2 with + Equal + | size l1r >= size r2 -> Greater + | otherwise -> + match split (maxKey l1) r2 with + (_, _, NatMap None) -> Equal + (_, _, NatMap (Some r2r)) -> go r1 r2r + x -> x + | otherwise -> go r1 r2 + x -> x + t1@(Tip _ _), Bin _ _ _ l2 _ -> go t1 l2 + Bin _ _ _ l1 _, t2@(Tip _ _) -> go l1 t2 + match go m1 m2 with + Equal -> ordering (size m1) (size m2) + x -> x + +data.NatMap.Nonempty.compareBy.doc : Doc +data.NatMap.Nonempty.compareBy.doc = + use NatMap.Nonempty fromList + use Nonempty compareBy + use Universal ordering + {{ + Compares two {type NatMap.Nonempty}s using the given comparison function. + + Defines a [total order](https://en.wikipedia.org/wiki/Total_order) on + {type NatMap.Nonempty}s. + + The entries are compared in ascending order of key, and the given comparison + function is applied to the values of corresponding entries. + + Returns: + + * `` Less `` for any of the following: + * The first map is a proper prefix of the second map + * The first entry that differs between the maps has a lower key in the + first map. + * The first entry that differs between the maps has a lower value in the + first map, according to the given comparison function. + * `` Equal `` when the maps are equal. + * `` Greater `` for any of the following: + * The second map is a proper prefix of the first map + * The first entry that differs between the maps has a lower key in the + second map. + * The first entry that differs between the maps has a lower value in the + second map, according to the given comparison function. + + # Examples + + The maps are equal: + + ``` + compareBy + ordering + (fromList ((1, "foo") +| [(2, "bar")])) + (fromList ((1, "foo") +| [(2, "bar")])) + ``` + + The first map is a prefix of the second map: + + ``` + compareBy + ordering + (fromList ((1, "foo") +| [(2, "bar")])) + (fromList ((1, "foo") +| [(2, "bar"), (3, "baz")])) + ``` + + The second map is a prefix of the first map: + + ``` + compareBy + ordering + (fromList ((1, "foo") +| [(2, "bar")])) + (fromList ((1, "foo") +| [])) + ``` + + The first value that differs is larger in the second map: + + ``` + compareBy + ordering + (fromList ((1, "foo") +| [(2, "bar")])) + (fromList ((1, "foo") +| [(2, "baz")])) + ``` + + The first key that differs is larger in the second map: + + ``` + compareBy + ordering + (fromList ((1, "foo") +| [(2, "bar")])) + (fromList ((1, "foo") +| [(3, "bar")])) + ``` + + The first key that differs is larger in the first map: + + ``` + compareBy + ordering + (fromList ((1, "foo") +| [(3, "bar")])) + (fromList ((1, "foo") +| [(2, "bar")])) + ``` + + The first value that differs is larger in the first map: + + ``` + compareBy + ordering + (fromList ((1, "foo") +| [(2, "baz")])) + (fromList ((1, "foo") +| [(2, "bar")])) + ``` + + # See also + + * {Nonempty.equalBy} to compare for equality only. + * {Nonempty.submapCompareBy} to check if one map is a submap of another, + using a comparison function. + }} + +data.NatMap.Nonempty.contains : Nat -> NatMap.Nonempty a -> Boolean +data.NatMap.Nonempty.contains k m = match NatMap.Nonempty.get k m with + None -> false + Some _ -> true + +data.NatMap.Nonempty.contains.doc : Doc +data.NatMap.Nonempty.contains.doc = + {{ + Checks if the given key is present in the {type NatMap.Nonempty}. + + # Example + + ``` + NatMap.Nonempty.contains + 1 (NatMap.Nonempty.fromList ((1, "foo") +| [(2, "bar")])) + ``` + + # See also + + * {NatMap.Nonempty.get} to get the value if it is present. + * {Nonempty.isSubmapOf} to check if one map contains all the keys of + another. + }} + +data.NatMap.Nonempty.delete : Nat -> NatMap.Nonempty a -> NatMap a +data.NatMap.Nonempty.delete k = cases + t@(NatMap.Nonempty.Bin prefix mask size l r) + | nomatch k prefix mask -> toNatMap t + | Nat.and k mask Nat.== 0 -> + NatMap.internal.bin + prefix mask (data.NatMap.Nonempty.delete k l) (toNatMap r) + | otherwise -> + NatMap.internal.bin + prefix mask (toNatMap l) (data.NatMap.Nonempty.delete k r) + t@(NatMap.Nonempty.Tip k' _) -> + if k Nat.== k' then NatMap.empty else toNatMap t + +data.NatMap.Nonempty.delete.doc : Doc +data.NatMap.Nonempty.delete.doc = + {{ + Deletes the given key from the {type NatMap.Nonempty}. + + # Example + + ``` + NatMap.toList + (NatMap.Nonempty.delete + 1 (NatMap.Nonempty.fromList ((1, "foo") +| [(2, "bar")]))) + ``` + + # See also + + * {NatMap.Nonempty.deleteMax} to delete the largest key. + * {NatMap.Nonempty.deleteMin} to delete the smallest key. + * {NatMap.Nonempty.difference} to delete all keys in one map from another. + }} + +data.NatMap.Nonempty.deleteMax : NatMap.Nonempty a -> NatMap a +data.NatMap.Nonempty.deleteMax t = + (_, t') = NatMap.Nonempty.breakOffMax t + t' + +data.NatMap.Nonempty.deleteMax.doc : Doc +data.NatMap.Nonempty.deleteMax.doc = + {{ + Deletes the largest key from the {type NatMap.Nonempty}. + + If the map is empty, this returns an empty map. + + # Example + + ``` + NatMap.toList + (NatMap.Nonempty.deleteMax + (NatMap.Nonempty.fromList ((1, "foo") +| [(2, "bar")]))) + ``` + + # See also + + * {NatMap.Nonempty.delete} to delete a specific key. + * {NatMap.Nonempty.deleteMin} to delete the smallest key. + * {NatMap.Nonempty.difference} to delete all keys in one map from another. + }} + +data.NatMap.Nonempty.deleteMin : NatMap.Nonempty a -> NatMap a +data.NatMap.Nonempty.deleteMin t = + (_, t') = NatMap.Nonempty.breakOffMin t + t' + +data.NatMap.Nonempty.deleteMin.doc : Doc +data.NatMap.Nonempty.deleteMin.doc = + {{ + Deletes the smallest key from the {type NatMap.Nonempty}. + + If the map is empty, this calls {abort}. + + # Example + + ``` + NatMap.toList + (NatMap.Nonempty.deleteMin + (NatMap.Nonempty.fromList ((1, "foo") +| [(2, "bar")]))) + ``` + + # See also + + * {NatMap.Nonempty.delete} to delete a specific key. + * {NatMap.Nonempty.deleteMax} to delete the largest key. + * {NatMap.Nonempty.difference} to delete all keys in one map from another. + }} + +data.NatMap.Nonempty.difference : + NatMap.Nonempty a -> NatMap.Nonempty b -> NatMap a +data.NatMap.Nonempty.difference = Nonempty.differenceWith do do None + +data.NatMap.Nonempty.difference.doc : Doc +data.NatMap.Nonempty.difference.doc = + use NatMap.Nonempty fromList + {{ + Deletes all keys in the second {type NatMap.Nonempty} from the first. + + # Example + + ``` + NatMap.toList + (NatMap.Nonempty.difference + (fromList ((1, "foo") +| [(2, "bar")])) + (fromList (List.Nonempty.singleton (1, "foo")))) + ``` + + # See also + + * {Nonempty.differenceWith} to more finely control which keys are deleted, + or replace specific values instead of deleting them. + * {NatMap.Nonempty.delete} to delete a specific key. + * {NatMap.Nonempty.deleteMax} to delete the largest key. + * {NatMap.Nonempty.deleteMin} to delete the smallest key. + * {NatMap.Nonempty.intersect} to delete all keys not in both maps. + }} + +data.NatMap.Nonempty.differenceWith : + (a ->{g} b ->{g} Optional a) + -> NatMap.Nonempty a + -> NatMap.Nonempty b + ->{g} NatMap a +data.NatMap.Nonempty.differenceWith f = + Nonempty.differenceWithKey do x y -> f x y + +data.NatMap.Nonempty.differenceWith.doc : Doc +data.NatMap.Nonempty.differenceWith.doc = + use NatMap.Nonempty fromList + use Text == + {{ + Deletes keys in the second {type NatMap.Nonempty} from the first, or replaces + their values with the result of the given function. + + # Example + + Delete all keys in the second map from the first unless the values under + those keys are equal in both maps: + + ``` + NatMap.toList + (Nonempty.differenceWith + (x y -> (if x == y then Some x else None)) + (fromList ((1, "foo") +| [(2, "bar")])) + (fromList ((1, "foo") +| [(2, "baz")]))) + ``` + + # See also + + * {Nonempty.differenceWithKey} for a version that also receives the key. + * {NatMap.Nonempty.difference} to delete all keys in one map from another. + * {NatMap.Nonempty.intersectWith} to delete all keys not in both maps, + using a function to combine the remaining values. + }} + +data.NatMap.Nonempty.differenceWithKey : + (Nat ->{g} a ->{g} b ->{g} Optional a) + -> NatMap.Nonempty a + -> NatMap.Nonempty b + ->{g} NatMap a +data.NatMap.Nonempty.differenceWithKey f = cases + t1@(NatMap.Nonempty.Bin p1 m1 sz1 l1 r1), + t2@(NatMap.Nonempty.Bin p2 m2 sz2 l2 r2) + | shorter m1 m2 -> + if nomatch p2 p1 m1 then toNatMap t1 + else + if Nat.and p2 m1 Nat.== 0 then + NatMap.internal.bin + p1 + m1 + (data.NatMap.Nonempty.differenceWithKey f l1 t2) + (toNatMap r1) + else + NatMap.internal.bin + p1 + m1 + (toNatMap l1) + (data.NatMap.Nonempty.differenceWithKey f r1 t2) + | shorter m2 m1 -> + if nomatch p1 p2 m2 then toNatMap t1 + else + if Nat.and p1 m2 Nat.== 0 then + data.NatMap.Nonempty.differenceWithKey f t1 l2 + else data.NatMap.Nonempty.differenceWithKey f t1 r2 + | p1 Nat.== p2 -> + NatMap.internal.bin + p1 + m1 + (data.NatMap.Nonempty.differenceWithKey f l1 l2) + (data.NatMap.Nonempty.differenceWithKey f r1 r2) + | otherwise -> toNatMap t1 + t1@(NatMap.Nonempty.Tip k v), t2 -> + match NatMap.Nonempty.get k t2 with + Some v' -> + match f k v v' with + Some x -> toNatMap (NatMap.Nonempty.Tip k x) + None -> NatMap.empty + None -> toNatMap t1 + t, NatMap.Nonempty.Tip k v -> Nonempty.updateWithKey (k' x -> f k' x v) k t + +data.NatMap.Nonempty.differenceWithKey.doc : Doc +data.NatMap.Nonempty.differenceWithKey.doc = + use Nat == + use NatMap.Nonempty fromList + {{ + Deletes keys in the second {type NatMap.Nonempty} from the first, or replaces + their values with the result of the given function. + + # Example + + Delete all keys in the second map from the first unless the values under + those keys are both equal to the key: + + ``` + NatMap.toList + (Nonempty.differenceWithKey + (k x y -> (if x == y && x == k then Some x else None)) + (fromList ((1, 1) +| [(2, 2)])) + (fromList ((1, 1) +| [(2, 3)]))) + ``` + + # See also + + * {Nonempty.differenceWith} for a version that does not receive the key. + * {NatMap.Nonempty.difference} to delete all keys in one map from another. + * {NatMap.Nonempty.intersectWithKey} to delete all keys not in both maps, + using a function to combine the remaining values. + }} + +data.NatMap.Nonempty.doc : Doc +data.NatMap.Nonempty.doc = + {{ + An efficient implementation of nonempty maps from keys of type {type Nat} to + values of some type. This type is parameterised by the type of the values. + This specialized map is much more efficient than the general map type, + {type Map}, when the keys are of type {type Nat} or can be encoded as + {type Nat}s. + + This is the nonempty version of {type NatMap}. It's guaranteed to have at + least one entry. + + Most of the functions here have a counterpart in the {type NatMap} namespace. + + Many operations on {type NatMap} return a {type NatMap.Nonempty} when they + are guaranteed to return a nonempty map. Correspondingly, many operations on + {type NatMap.Nonempty} return a {type NatMap} when they may return an empty + map. + + # Constructing nonempty maps + + A map with a single key/value pair: + + @signature{NatMap.Nonempty.singleton} + + Construct a map from a list of key/value pairs: + + @signature{NatMap.Nonempty.fromList} + + Construct a map from a list of key/value pairs, using a combining function + to resolve conflicts:aaaaawwdawdawd + + @signature{NatMap.Nonempty.fromListWith} + + Construct a map from a list of key/value pairs, using a combining function + to resolve conflicts, and passing the key to the combining function: + + @signature{NatMap.Nonempty.fromListWithKey} + + # Querying + + Look up the value at a key: + + @signature{NatMap.Nonempty.get} + + Look up the value at a key, or return a default value if the key is not + present: + + @signature{NatMap.Nonempty.getOrElse} + + Check if a key is in the map: + + @signature{NatMap.Nonempty.contains} + + Get the size of the map: + + @signature{NatMap.Nonempty.size} + + # Inserting + + Insert a key/value pair into the map: + + @signature{NatMap.Nonempty.insert} + + Insert a key/value pair into the map, using a combining function to resolve + conflicts: + + @signature{Nonempty.insertWith} + + Insert a key/value pair into the map, using a combining function to resolve + conflicts, and passing the key to the combining function: + + @signature{Nonempty.insertWithKey} + + Insert a key/value pair into the map, using a combining function to resolve + conflicts, and passing the key to the combining function, and returning the + old value if it was present: + + @signature{Nonempty.insertGetWithKey} + + # Deleting and updating values + + Delete a key and its value from the map: + + @signature{NatMap.Nonempty.delete} + + Update the value at a key with a function, or remove the key if the + function returns {None}: + + @signature{NatMap.Nonempty.update} + + Update the value at a key with a function, or remove the key if the + function returns {None}, passing the key to the function: + + @signature{Nonempty.updateWithKey} + + Update the value at a key with a function, or remove the key if the + function returns {None}, passing the key to the function, and returning the + old value if it was present: + + @signature{Nonempty.updateGetWithKey} + + Update, insert, or delete the value at a key with a function, depending on + whether the key is present in the map: + + @signature{NatMap.Nonempty.alter} + + Update, insert, or delete the value at a key with a function, passing the + key to the function. + + @signature{Nonempty.alterWithKey} + + # Combining maps + + ## Unions + + Add the entries from one map to another, preferring the entries from the + first map if there are conflicts: + + @signature{NatMap.Nonempty.union} + + Merge two maps, using a combining function to resolve conflicts: + + @signature{NatMap.Nonempty.unionWith} + + Merge two maps, using a combining function to resolve conflicts, and + passing the key to the combining function: + + @signature{NatMap.Nonempty.unionWithKey} + + Combine the entries from a list of maps, preferring the entries from the + first map if there are conflicts: + + @signature{NatMap.Nonempty.unions} + + Merge list of maps, using a combining function to resolve conflicts: + + @signature{Nonempty.unionsWith} + + ## Difference + + Remove the entries from one map that are present in another map: + + @signature{NatMap.Nonempty.difference} + + Remove the entries from one map that are present in another map, using a + combining function to resolve conflicts: + + @signature{Nonempty.differenceWith} + + Remove the entries from one map that are present in another map, using a + combining function to resolve conflicts, and passing the key to the + combining function: + + @signature{Nonempty.differenceWithKey} + + ## Intersection + + Keep only the entries from one map that are present in another map: + + @signature{NatMap.Nonempty.intersect} + + Keep only entries present in both of two maps, using a combining + function to resolve conflicts: + + @signature{NatMap.Nonempty.intersectWith} + + Keep only entries present in both of two maps, using a combining + function to resolve conflicts, and passing the key to the combining + function: + + @signature{NatMap.Nonempty.intersectWithKey} + + # Transforming maps + + Apply a function to every value in a map: + + @signature{NatMap.Nonempty.map} + + Apply a function to every value in a map, passing the key to the function: + + @signature{NatMap.Nonempty.mapWithKey} + + Apply a partial function to every value in a map, removing entries for + which the function returns {None}: + + @signature{Nonempty.mapOptional} + + # Summarizing maps + + Fold a function over the entries in a map, from lowest key to highest key: + + @signature{Nonempty.fold} + + Fold a function over the entries in a map, from lowest key to highest key, + passing the key to the function: + + @signature{Nonempty.foldWithKey} + + # Filtering and partitioning + + Filter a map, keeping only entries for which the predicate returns + ``true``: + + @signature{NatMap.Nonempty.filter} + + Filter a map, keeping only entries for which the predicate returns + ``true``, passing the key to the predicate: + + @signature{NatMap.Nonempty.filterWithKey} + + Partition a map into two maps, one containing entries for which the + predicate returns ``true``, and one containing entries for which the + predicate returns ``false``: + + @signature{NatMap.Nonempty.partition} + + Partition a map into two maps, one containing entries for which the + predicate returns ``true``, and one containing entries for which the + predicate returns ``false``, passing the key to the predicate: + + @signature{Nonempty.partitionWithKey} + + Partition a map into two maps, one containing entries for which the + function returns {Left} and one containing entries for which the function + returns {Right}: + + @signature{Nonempty.mapEither} + + Partition a map into two maps, one containing entries for which the + function returns {Left} and one containing entries for which the function + returns {Right}, passing the key to the function: + + @signature{Nonempty.mapEitherWithKey} + + Split a map into two maps at a given key: + + @signature{NatMap.Nonempty.split} + + # Comparing maps + + Check if two maps are equal according to a given comparison on the values: + + @signature{Nonempty.equalBy} + + Get a partial order for two maps given a total order on the values: + + @signature{Nonempty.compareBy} + + Check if all entries in one map are present in another map, using a given + comparison function to compare the values: + + @signature{Nonempty.isSubmapOfBy} + + Check if all entries in one map are present in another map, using {===} to + compare the values: + + @signature{Nonempty.isSubmapOf} + + Check that one map is a proper submap of another map, using a given + comparison function to compare the values: + + @signature{Nonempty.isProperSubmapOfBy} + + Check that one map is a proper submap of another map, using {===} to + compare the values: + + @signature{Nonempty.isProperSubmapOf} + + # Operations on the minimum or maximum key + + Get the minimum key in a map: + + @signature{NatMap.Nonempty.getMin} + + Get the maximum key in a map: + + @signature{NatMap.Nonempty.getMax} + + Break a map into its minimum key and value, and the rest of the map: + + @signature{NatMap.Nonempty.breakOffMin} + + Break a map into its maximum key and value, and the rest of the map: + + @signature{NatMap.Nonempty.breakOffMax} + + Break a map into the value at the minimum key, and the rest of the map: + + @signature{NatMap.Nonempty.minView} + + Break a map into the value at the maximum key, and the rest of the map: + + @signature{NatMap.Nonempty.maxView} + + Remove the minimum key from a map: + + @signature{NatMap.Nonempty.deleteMin} + + Remove the maximum key from a map: + + @signature{NatMap.Nonempty.deleteMax} + + Update the value at the minimum key with a function: + + @signature{Nonempty.updateMin} + + Update the value at the maximum key with a function: + + @signature{Nonempty.updateMax} + + Update the value at the minimum key with a function, passing the key to the + function: + + @signature{Nonempty.updateMinWithKey} + + Update the value at the maximum key with a function, passing the key to the + function: + + @signature{Nonempty.updateMaxWithKey} + + # Convertings maps to other types + + Convert a map to a list of key/value pairs: + + @signature{NatMap.Nonempty.toList} + + Get a list of the keys in a map: + + @signature{NatMap.Nonempty.keys} + + Get a list of the values in a map: + + @signature{NatMap.Nonempty.values} + }} + +data.NatMap.Nonempty.equalBy : + (a ->{g} a ->{g} Boolean) + -> NatMap.Nonempty a + -> NatMap.Nonempty a + ->{g} Boolean +data.NatMap.Nonempty.equalBy f = cases + NatMap.Nonempty.Bin p m sz l r, NatMap.Nonempty.Bin p' m' sz' l' r' -> + data.NatMap.Nonempty.equalBy f l l' && data.NatMap.Nonempty.equalBy f r r' + NatMap.Nonempty.Tip k v, NatMap.Nonempty.Tip k' v' -> k Nat.== k' && f v v' + _, _ -> false + +data.NatMap.Nonempty.equalBy.doc : Doc +data.NatMap.Nonempty.equalBy.doc = + use Nat == + use NatMap.Nonempty fromList + use Nonempty equalBy + {{ + Checks if two {type NatMap.Nonempty}s are equal by comparing their values + using the given function. + + # Examples + + ``` + equalBy + (x y -> x == y) + (fromList ((1, 1) +| [(2, 2)])) + (fromList ((1, 1) +| [(2, 2)])) + ``` + + ``` + equalBy + (x y -> x == y) + (fromList ((1, 1) +| [(2, 2)])) + (fromList ((1, 1) +| [(2, 3)])) + ``` + + # See also + + * {Nonempty.compareBy} to order two {type NatMap.Nonempty}s using a custom + comparison function. + * {NatMap.Nonempty.difference} to see where two {type NatMap.Nonempty}s + differ. + }} + +data.NatMap.Nonempty.filter : + (a ->{g} Boolean) -> NatMap.Nonempty a ->{g} NatMap a +data.NatMap.Nonempty.filter f = NatMap.Nonempty.filterWithKey do x -> f x + +data.NatMap.Nonempty.filter.doc : Doc +data.NatMap.Nonempty.filter.doc = + {{ + Filters a {type NatMap.Nonempty} by retaining only values that satisfy the + given predicate. + + # Example + + ``` + NatMap.toList + (NatMap.Nonempty.filter + Nat.isEven + (NatMap.Nonempty.fromList ((1, 11) +| [(2, 22), (3, 33), (4, 44)]))) + ``` + + # See also + + * {NatMap.Nonempty.filterWithKey} for a version where the predicate also + receives the key. + * {NatMap.Nonempty.partition} to split a {type NatMap.Nonempty} into two + {type NatMap.Nonempty}s based on a predicate. + * {NatMap.Nonempty.intersect} to retain only keys that occur in another + map. + }} + +data.NatMap.Nonempty.filterWithKey : + (Nat ->{g} a ->{g} Boolean) -> NatMap.Nonempty a ->{g} NatMap a +data.NatMap.Nonempty.filterWithKey f = cases + NatMap.Nonempty.Bin p m sz l r -> + use data.NatMap.Nonempty filterWithKey + l' = filterWithKey f l + r' = filterWithKey f r + NatMap.internal.bin p m l' r' + NatMap.Nonempty.Tip k v + | f k v -> toNatMap (NatMap.Nonempty.singleton k v) + | otherwise -> NatMap.empty + +data.NatMap.Nonempty.filterWithKey.doc : Doc +data.NatMap.Nonempty.filterWithKey.doc = + use Nat == + {{ + Filters a {type NatMap.Nonempty} by retaining only entries that satisfy the + given predicate. + + # Example + + ``` + NatMap.toList + (NatMap.Nonempty.filterWithKey + (k x -> k == x) + (NatMap.Nonempty.fromList ((1, 1) +| [(2, 22), (3, 3), (4, 44)]))) + ``` + + # See also + + * {NatMap.Nonempty.filter} for a version that does not receive the key. + * {Nonempty.partitionWithKey} to split a {type NatMap.Nonempty} into two + {type NatMap.Nonempty}s based on a predicate. + * {NatMap.Nonempty.intersectWithKey} to retain only keys that occur in + another map. + }} + +data.NatMap.Nonempty.fold : + (a ->{g} b ->{g} b) -> b -> NatMap.Nonempty a ->{g} b +data.NatMap.Nonempty.fold f = Nonempty.foldWithKey do x y -> f x y + +data.NatMap.Nonempty.fold.doc : Doc +data.NatMap.Nonempty.fold.doc = + use Nat + + {{ + Summarizes the values in a {type NatMap.Nonempty} using the given function. + The function is applied to each value in the {type NatMap.Nonempty} and the + function combines the result with the summary so far. + + # Example + + ``` + Nonempty.fold + (+) 0 (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + # See also + + * {Nonempty.foldWithKey} for a version that also receives the key. + }} + +data.NatMap.Nonempty.foldMap : + (b ->{g} b ->{g} b) -> (a ->{g} b) -> NatMap.Nonempty a ->{g} b +data.NatMap.Nonempty.foldMap f g = foldMapWithKey f do g + +data.NatMap.Nonempty.foldMap.doc : Doc +data.NatMap.Nonempty.foldMap.doc = + use Text ++ + {{ + Summarizes the values in a {type NatMap.Nonempty} using the given functions. + The second function is applied to each value in the {type NatMap.Nonempty} + and the first function combines the results. + + # Example + + ``` + NatMap.Nonempty.foldMap + (++) + Nat.toText + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + # See also + + * {foldMapWithKey} for a version that receives the key. + * {Nonempty.fold} for a version that folds into an accumulator. + }} + +data.NatMap.Nonempty.foldMapWithKey : + (b ->{g} b ->{g} b) -> (Nat ->{g} a ->{g} b) -> NatMap.Nonempty a ->{g} b +data.NatMap.Nonempty.foldMapWithKey f g = cases + NatMap.Nonempty.Bin p m sz l r -> + f + (data.NatMap.Nonempty.foldMapWithKey f g l) + (data.NatMap.Nonempty.foldMapWithKey f g r) + NatMap.Nonempty.Tip k v -> g k v + +data.NatMap.Nonempty.foldMapWithKey.doc : Doc +data.NatMap.Nonempty.foldMapWithKey.doc = + use Nat toText + use Text ++ + {{ + Summarizes the values in a {type NatMap.Nonempty} using the given functions. + The first function is applied to each key-value pair in the + {type NatMap.Nonempty} and the second function combines the results. + + # Example + + ``` + foldMapWithKey + (++) + (k x -> toText k ++ toText x) + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + # See also + + * {Nonempty.foldWithKey} for a version that folds into an accumulator. + * {NatMap.Nonempty.foldMap} for a version that does not receive the key. + }} + +data.NatMap.Nonempty.foldWithKey : + (Nat ->{g} a ->{g} b ->{g} b) -> b -> NatMap.Nonempty a ->{g} b +data.NatMap.Nonempty.foldWithKey f z = cases + NatMap.Nonempty.Bin p m sz l r -> + data.NatMap.Nonempty.foldWithKey + f (data.NatMap.Nonempty.foldWithKey f z r) l + NatMap.Nonempty.Tip k v -> f k v z + +data.NatMap.Nonempty.foldWithKey.doc : Doc +data.NatMap.Nonempty.foldWithKey.doc = + use Nat + + {{ + Summarizes the entries in a {type NatMap.Nonempty} using the given function. + The function is applied to each entry in the {type NatMap.Nonempty} and the + function combines the result with the summary so far. + + The second argument is the initial summary that the entries are then combined + with. + + # Example + + ``` + Nonempty.foldWithKey + (k x y -> k + x + y) + 0 + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + # See also + + * {Nonempty.fold} for a version that does not receive the key. + }} + +data.NatMap.Nonempty.fromList : List.Nonempty (Nat, a) -> NatMap.Nonempty a +data.NatMap.Nonempty.fromList = + List.Nonempty.foldMap + NatMap.Nonempty.union (uncurry NatMap.Nonempty.singleton) + +data.NatMap.Nonempty.fromList.doc : Doc +data.NatMap.Nonempty.fromList.doc = + use NatMap.Nonempty fromList toList + {{ + Creates a {type NatMap.Nonempty} from a list of key-value pairs. + + If the list contains duplicate keys, the last value for a given key is + retained. + + # Examples + + ``` + toList (fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + ``` + toList (fromList ((1, 10) +| [(2, 20), (1, 30)])) + ``` + + # See also + + * {NatMap.Nonempty.fromListWith} to specify how to combine values for + duplicate keys. + * {NatMap.Nonempty.fromListWithKey} to specify how to combine values for + duplicate keys, taking the key into account. + * {toList} to convert a {type NatMap.Nonempty} to a list of key-value + pairs. + }} + +data.NatMap.Nonempty.fromListWith : + (a ->{g} a ->{g} a) -> List.Nonempty (Nat, a) ->{g} NatMap.Nonempty a +data.NatMap.Nonempty.fromListWith f = + List.Nonempty.foldMap + (NatMap.Nonempty.unionWith f) (uncurry NatMap.Nonempty.singleton) + +data.NatMap.Nonempty.fromListWith.doc : Doc +data.NatMap.Nonempty.fromListWith.doc = + use Nat + + use NatMap.Nonempty toList + {{ + Creates a {type NatMap.Nonempty} from a {type List.Nonempty} of key-value + pairs, combining duplicate keys using the given function. + + # Example + + ``` + toList (NatMap.Nonempty.fromListWith (+) ((1, 10) +| [(2, 20), (1, 30)])) + ``` + + # See also + + * {NatMap.Nonempty.fromList} for a version that resolves duplicate keys by + retaining the last value for that key. + * {NatMap.Nonempty.fromListWithKey} for a version that also takes the key + into account when combining duplicates. + * {toList} to convert a {type NatMap.Nonempty} to a list of key-value + pairs. + }} + +data.NatMap.Nonempty.fromListWithKey : + (Nat ->{g} a ->{g} a ->{g} a) + -> List.Nonempty (Nat, a) + ->{g} NatMap.Nonempty a +data.NatMap.Nonempty.fromListWithKey f = + List.Nonempty.foldMap + (NatMap.Nonempty.unionWithKey f) (uncurry NatMap.Nonempty.singleton) + +data.NatMap.Nonempty.fromListWithKey.doc : Doc +data.NatMap.Nonempty.fromListWithKey.doc = + use Nat + + use NatMap.Nonempty toList + {{ + Creates a {type NatMap.Nonempty} from a list of key-value pairs, combining + duplicate keys using the given function. + + # Example + + ``` + toList + (NatMap.Nonempty.fromListWithKey + (k x y -> k + x + y) ((1, 10) +| [(2, 20), (1, 30)])) + ``` + + # See also + + * {NatMap.Nonempty.fromList} for a version that resolves duplicate keys by + retaining the last value for that key. + * {NatMap.Nonempty.fromListWith} for a version that does not take the key + into account when combining duplicates. + * {toList} to convert a {type NatMap.Nonempty} to a list of key-value + pairs. + }} + +data.NatMap.Nonempty.get : Nat -> NatMap.Nonempty a -> Optional a +data.NatMap.Nonempty.get k = cases + NatMap.Nonempty.Bin _ mask _ l r + | Nat.and k mask Nat.== 0 -> data.NatMap.Nonempty.get k l + | otherwise -> data.NatMap.Nonempty.get k r + NatMap.Nonempty.Tip k' v -> if k Nat.== k' then Some v else None + +data.NatMap.Nonempty.get.doc : Doc +data.NatMap.Nonempty.get.doc = + {{ + Retrieves the value associated with the given key, if it exists. + + # Example + + ``` + NatMap.Nonempty.get + 2 (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + # See also + + * {NatMap.Nonempty.contains} to check if a key is present in a + {type NatMap.Nonempty}. + * {NatMap.Nonempty.delete} to delete a key from a {type NatMap.Nonempty}. + * {NatMap.Nonempty.insert} to insert a key-value pair into a + {type NatMap.Nonempty}. + }} + +data.NatMap.Nonempty.getAbove : Nat -> NatMap.Nonempty v -> Optional (Nat, v) +data.NatMap.Nonempty.getAbove k m = + (below, eq, above) = NatMap.Nonempty.split k m + toOptional! do + ((k, v), _) = NatMap.breakOffMin above + (k, v) + +data.NatMap.Nonempty.getAbove.doc : Doc +data.NatMap.Nonempty.getAbove.doc = + use NatMap Nonempty.getAbove + use NatMap.Nonempty fromList + {{ + Returns the key-value pair in a {type NatMap.Nonempty} where the key is the + smallest one that is strictly larger than a given key. + + # Examples + + ``` + Nonempty.getAbove 2 (fromList ((1, ?a) +| [(2, ?b), (3, ?c)])) + ``` + + ``` + Nonempty.getAbove 4 (fromList ((1, ?a) +| [(2, ?b), (3, ?c)])) + ``` + + # See also + + * {NatMap.getAbove} for the version of this that operates on a (possibly + empty) {type NatMap}. + * {NatMap.Nonempty.getAtLeast} to get the key-value pair where the key is + the smallest one that is larger than __or equal__ to a given key. + * {NatMap.Nonempty.getBelow} to get the key-value pair where the key is the + largest one that is strictly smaller than a given key. + * {NatMap.Nonempty.getAtMost} to get the key-value pair where the key is + the largest one that is smaller than or equal to a given key. + }} + +data.NatMap.Nonempty.getAtLeast : Nat -> NatMap.Nonempty v -> Optional (Nat, v) +data.NatMap.Nonempty.getAtLeast k m = + (below, eq, above) = NatMap.Nonempty.split k m + match eq with + Some v -> Some (k, v) + None -> + toOptional! do + ((k, v), _) = NatMap.breakOffMin above + (k, v) + +data.NatMap.Nonempty.getAtLeast.doc : Doc +data.NatMap.Nonempty.getAtLeast.doc = + use NatMap Nonempty.getAtLeast + use NatMap.Nonempty fromList + {{ + Returns the key-value pair in a {type NatMap.Nonempty} where the key is the + smallest one that is larger than or equal to a given key. + + # Examples + + ``` + Nonempty.getAtLeast 2 (fromList ((1, ?a) +| [(2, ?b), (3, ?c)])) + ``` + + ``` + Nonempty.getAtLeast 4 (fromList ((1, ?a) +| [(2, ?b), (3, ?c)])) + ``` + + # See also + + * {NatMap.getAtLeast} for the version of this that operates on a (possibly + empty) {type NatMap}. + * {NatMap.Nonempty.getAbove} to get the key-value pair where the key is the + smallest one that is strictly larger than a given key. + * {NatMap.Nonempty.getBelow} to get the key-value pair where the key is the + largest one that is strictly smaller than a given key. + * {NatMap.Nonempty.getAtMost} to get the key-value pair where the key is + the largest one that is smaller than or equal to a given key. + }} + +data.NatMap.Nonempty.getAtMost : Nat -> NatMap.Nonempty v -> Optional (Nat, v) +data.NatMap.Nonempty.getAtMost k m = + (below, eq, above) = NatMap.Nonempty.split k m + match eq with + Some v -> Some (k, v) + None -> + toOptional! do + ((k, v), _) = NatMap.breakOffMax below + (k, v) + +data.NatMap.Nonempty.getAtMost.doc : Doc +data.NatMap.Nonempty.getAtMost.doc = + use NatMap Nonempty.getAtMost + use NatMap.Nonempty fromList + {{ + Returns the key-value pair in a {type NatMap.Nonempty} where the key is the + largest one that is smaller than or equal to a given key. + + # Examples + + ``` + Nonempty.getAtMost 2 (fromList ((1, ?a) +| [(2, ?b), (3, ?c)])) + ``` + + ``` + Nonempty.getAtMost 4 (fromList ((1, ?a) +| [(2, ?b), (3, ?c)])) + ``` + + # See also + + * {NatMap.getAtMost} for the version of this that operates on a (possibly + empty) {type NatMap}. + * {NatMap.Nonempty.getBelow} to get the key-value pair where the key is the + largest one that is strictly smaller than a given key. + * {NatMap.Nonempty.getAbove} to get the key-value pair where the key is the + smallest one that is strictly larger than a given key. + * {NatMap.Nonempty.getAtLeast} to get the key-value pair where the key is + the smallest one that is larger than or equal to a given key. + }} + +data.NatMap.Nonempty.getBelow : Nat -> NatMap.Nonempty v -> Optional (Nat, v) +data.NatMap.Nonempty.getBelow k m = + (below, eq, above) = NatMap.Nonempty.split k m + toOptional! do + ((k, v), _) = NatMap.breakOffMax below + (k, v) + +data.NatMap.Nonempty.getBelow.doc : Doc +data.NatMap.Nonempty.getBelow.doc = + use NatMap Nonempty.getBelow + use NatMap.Nonempty fromList + {{ + Returns the key-value pair in a {type NatMap.Nonempty} where the key is the + largest one that is strictly smaller than a given key. + + # Examples + + ``` + Nonempty.getBelow 2 (fromList ((1, ?a) +| [(2, ?b), (3, ?c)])) + ``` + + ``` + Nonempty.getBelow 4 (fromList ((1, ?a) +| [(2, ?b), (3, ?c)])) + ``` + + # See also + + * {NatMap.getBelow} for the version of this that operates on a (possibly + empty) {type NatMap}. + * {NatMap.Nonempty.getAtMost} to get the key-value pair where the key is + the largest one that is smaller than or equal to a given key. + * {NatMap.Nonempty.getAbove} to get the key-value pair where the key is the + smallest one that is strictly larger than a given key. + * {NatMap.Nonempty.getAtLeast} to get the key-value pair where the key is + the smallest one that is larger than or equal to a given key. + }} + +data.NatMap.Nonempty.getMax : NatMap.Nonempty a -> a +data.NatMap.Nonempty.getMax t = + (v, _) = NatMap.Nonempty.maxView t + v + +data.NatMap.Nonempty.getMax.doc : Doc +data.NatMap.Nonempty.getMax.doc = + {{ + Retrieves the value associated with the largest key in the + {type NatMap.Nonempty}. + + # Example + + ``` + NatMap.Nonempty.getMax + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + # See also + + * {NatMap.Nonempty.maxView} for a version of this that also returns the map + with the largest key removed. + * {NatMap.Nonempty.getMin} to retrieve the value associated with the + smallest key. + * {NatMap.Nonempty.deleteMax} to remove the largest key from a + {type NatMap.Nonempty}. + }} + +data.NatMap.Nonempty.getMin : NatMap.Nonempty a -> a +data.NatMap.Nonempty.getMin t = + (v, _) = NatMap.Nonempty.minView t + v + +data.NatMap.Nonempty.getMin.doc : Doc +data.NatMap.Nonempty.getMin.doc = + {{ + Retrieves the value associated with the smallest key in the + {type NatMap.Nonempty}. + + # Example + + ``` + NatMap.Nonempty.getMin + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + # See also + + * {NatMap.Nonempty.minView} for a version of this that also returns the map + with the smallest key removed. + * {NatMap.Nonempty.getMax} to retrieve the value associated with the + largest key. + * {NatMap.Nonempty.deleteMin} to remove the smallest key. + }} + +data.NatMap.Nonempty.getOrAbort : Nat -> NatMap.Nonempty a ->{Abort} a +data.NatMap.Nonempty.getOrAbort k m = match NatMap.Nonempty.get k m with + None -> abort + Some v -> v + +data.NatMap.Nonempty.getOrAbort.doc : Doc +data.NatMap.Nonempty.getOrAbort.doc = + use NatMap.Nonempty fromList getOrAbort + {{ + Retrieves the value associated with the given key, or calls {abort} if the + key is not present. + + # Examples + + ``` + toOptional! do getOrAbort 2 (fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + ``` + toOptional! do getOrAbort 4 (fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + # See also + + * {NatMap.Nonempty.get} for a version of this that returns {type Optional} + instead of aborting. + * {NatMap.Nonempty.getOrElse} to return a default value if the key is not + present. + }} + +data.NatMap.Nonempty.getOrElse : Nat -> a -> NatMap.Nonempty a -> a +data.NatMap.Nonempty.getOrElse k def = cases + NatMap.Nonempty.Bin _ mask _ l r + | Nat.and k mask Nat.== 0 -> data.NatMap.Nonempty.getOrElse k def l + | otherwise -> data.NatMap.Nonempty.getOrElse k def r + NatMap.Nonempty.Tip k' v -> if k Nat.== k' then v else def + +data.NatMap.Nonempty.getOrElse.doc : Doc +data.NatMap.Nonempty.getOrElse.doc = + use NatMap.Nonempty fromList getOrElse + {{ + Retrieves the value associated with the given key, or returns the given + default value if the key is not present. + + # Examples + + ``` + getOrElse 2 0 (fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + ``` + getOrElse 4 0 (fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + # See also + + * {NatMap.Nonempty.get} for a version of this that returns {type Optional} + instead of a default value. + * {NatMap.Nonempty.getOrAbort} to {abort} if the key is not present. + }} + +data.NatMap.Nonempty.insert : + Nat -> a -> NatMap.Nonempty a -> NatMap.Nonempty a +data.NatMap.Nonempty.insert k v = cases + t@(NatMap.Nonempty.Bin prefix mask size l r) + | nomatch k prefix mask -> + internal.join k (NatMap.Nonempty.Tip k v) prefix t + | Nat.and k mask Nat.== 0 -> + NatMap.internal.bim prefix mask (data.NatMap.Nonempty.insert k v l) r + | otherwise -> + NatMap.internal.bim prefix mask l (data.NatMap.Nonempty.insert k v r) + t@(NatMap.Nonempty.Tip k' _) + | k Nat.== k' -> NatMap.Nonempty.Tip k v + | otherwise -> internal.join k (NatMap.Nonempty.Tip k v) k' t + +data.NatMap.Nonempty.insert.doc : Doc +data.NatMap.Nonempty.insert.doc = + use NatMap.Nonempty fromList insert toList + {{ + Inserts a key-value pair into a {type NatMap.Nonempty}, replacing the + existing value if the key is already present. + + # Examples + + ``` + toList (insert 2 20 (fromList ((1, 10) +| [(2, 30), (3, 30)]))) + ``` + + ``` + toList (insert 4 40 (fromList ((1, 10) +| [(2, 30), (3, 30)]))) + ``` + + # See also + + * {Nonempty.insertWith} for a version of this that allows you to specify a + function to combine the new and existing values. + * {Nonempty.insertWithKey} for a version that also provides the key to the + combining function. + * {Nonempty.insertGetWithKey} for a version that also returns the old value + if the key was already present. + * {NatMap.Nonempty.delete} to delete a key. + * {NatMap.Nonempty.get} to retrieve the value associated with a key. + }} + +data.NatMap.Nonempty.insertGetWithKey : + (Nat ->{g} a ->{g} a ->{g} a) + -> Nat + -> a + -> NatMap.Nonempty a + ->{g} (Optional a, NatMap.Nonempty a) +data.NatMap.Nonempty.insertGetWithKey f k v = cases + t@(NatMap.Nonempty.Bin prefix mask size l r) + | nomatch k prefix mask -> + (None, internal.join k (NatMap.Nonempty.Tip k v) prefix t) + | Nat.and k mask Nat.== 0 -> + (found, l') = data.NatMap.Nonempty.insertGetWithKey f k v l + (found, NatMap.internal.bim prefix mask l' r) + | otherwise -> + (found, r') = data.NatMap.Nonempty.insertGetWithKey f k v r + (found, NatMap.internal.bim prefix mask l r') + t@(NatMap.Nonempty.Tip k' v') + | k Nat.== k' -> (Some v', NatMap.Nonempty.Tip k (f k v v')) + | otherwise -> (None, internal.join k (NatMap.Nonempty.Tip k v) k' t) + +data.NatMap.Nonempty.insertGetWithKey.doc : Doc +data.NatMap.Nonempty.insertGetWithKey.doc = + use Nat isEven + use NatMap.Nonempty fromList toList + use Nonempty insertGetWithKey + {{ + Inserts a key-value pair into a {type NatMap.Nonempty}, returning the old + value if the key was already present. + + Takes a combining function that is passed the key, the new value, and the old + value if the key was already present. The combining function should return + the new value to be stored in the map. + + # Examples + + ``` + (oldKey, newMap) = + insertGetWithKey + (key old new -> (if isEven key then old else new)) + 2 + 20 + (fromList ((1, 10) +| [(2, 30), (3, 30)])) + (oldKey, toList newMap) + ``` + + ``` + (oldKey, newMap) = + insertGetWithKey + (key old new -> (if isEven key then old else new)) + 4 + 40 + (fromList ((1, 10) +| [(2, 30), (3, 30)])) + (oldKey, toList newMap) + ``` + + # See also + + * {Nonempty.insertWithKey} for a version of this that does not return the + old value. + }} + +data.NatMap.Nonempty.insertWith : + (a ->{g} a ->{g} a) -> Nat -> a -> NatMap.Nonempty a ->{g} NatMap.Nonempty a +data.NatMap.Nonempty.insertWith f k v t = + Nonempty.insertWithKey (do x' y' -> f x' y') k v t + +data.NatMap.Nonempty.insertWith.doc : Doc +data.NatMap.Nonempty.insertWith.doc = + use Nat + + use NatMap.Nonempty fromList toList + use Nonempty insertWith + {{ + Inserts a key-value pair into a {type NatMap.Nonempty}, combining the new and + existing values if the key is already present. + + Takes a combining function that is passed the new and existing values. The + combining function should return the new value to be stored in the map. + + # Examples + + ``` + toList (insertWith (+) 2 20 (fromList ((1, 10) +| [(2, 30), (3, 30)]))) + ``` + + ``` + toList (insertWith (+) 4 40 (fromList ((1, 10) +| [(2, 30), (3, 30)]))) + ``` + + # See also + + * {Nonempty.insertWithKey} for a version of this that also provides the key + to the combining function. + * {NatMap.Nonempty.insert} for a version that just replaces the existing + value. + * {Nonempty.insertGetWithKey} for a version that also returns the old value + if the key was already present. + }} + +data.NatMap.Nonempty.insertWithKey : + (Nat ->{g} a ->{g} a ->{g} a) + -> Nat + -> a + -> NatMap.Nonempty a + ->{g} NatMap.Nonempty a +data.NatMap.Nonempty.insertWithKey f k v = cases + t@(NatMap.Nonempty.Bin prefix mask size l r) + | nomatch k prefix mask -> + internal.join k (NatMap.Nonempty.Tip k v) prefix t + | Nat.and k mask Nat.== 0 -> + NatMap.internal.bim + prefix mask (data.NatMap.Nonempty.insertWithKey f k v l) r + | otherwise -> + NatMap.internal.bim + prefix mask l (data.NatMap.Nonempty.insertWithKey f k v r) + t@(NatMap.Nonempty.Tip k' v') + | k Nat.== k' -> NatMap.Nonempty.Tip k (f k v v') + | otherwise -> internal.join k (NatMap.Nonempty.Tip k v) k' t + +data.NatMap.Nonempty.insertWithKey.doc : Doc +data.NatMap.Nonempty.insertWithKey.doc = + use Nat + - isEven + use NatMap.Nonempty fromList toList + use Nonempty insertWithKey + {{ + Inserts a key-value pair into a {type NatMap.Nonempty}, combining the new and + existing values if the key is already present. + + Takes a combining function that is passed the key as well as the new and + existing values. The combining function should return the new value to be + stored in the map. + + # Examples + + ``` + toList + (insertWithKey + (key x y -> (if isEven key then x + y else x - y)) + 2 + 20 + (fromList ((1, 10) +| [(2, 30), (3, 30)]))) + ``` + + ``` + toList + (insertWithKey + (key x y -> (if isEven key then x + y else x - y)) + 4 + 40 + (fromList ((1, 10) +| [(2, 30), (3, 30)]))) + ``` + + # See also + + * {Nonempty.insertWith} for a version of this that does not provide the key + to the combining function. + * {NatMap.Nonempty.insert} for a version that just replaces the existing + value. + * {Nonempty.insertGetWithKey} for a version that also returns the old value + if the key was already present. + }} + +data.NatMap.Nonempty.intersect : + NatMap.Nonempty a -> NatMap.Nonempty b -> NatMap a +data.NatMap.Nonempty.intersect = NatMap.Nonempty.intersectWith const + +data.NatMap.Nonempty.intersect.doc : Doc +data.NatMap.Nonempty.intersect.doc = + use NatMap.Nonempty fromList + {{ + Returns a {type NatMap.Nonempty} containing only the keys that are present in + both of the given {type NatMap.Nonempty}s. + + The values of the resulting {type NatMap} are the values of the first + {type NatMap.Nonempty}. + + # Example + + ``` + NatMap.toList + (NatMap.Nonempty.intersect + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((2, 20) +| [(3, 30), (4, 40)]))) + ``` + + # See also + + * {NatMap.Nonempty.intersectWith} for a version that also combines the + values of the two {type NatMap.Nonempty}s. + * {NatMap.Nonempty.intersectWithKey} for a version that combines the values + and additionally provides the key to the combining function. + }} + +data.NatMap.Nonempty.intersectWith : + (a ->{g} b ->{g} a) -> NatMap.Nonempty a -> NatMap.Nonempty b ->{g} NatMap a +data.NatMap.Nonempty.intersectWith f = + NatMap.Nonempty.intersectWithKey do x y -> f x y + +data.NatMap.Nonempty.intersectWith.doc : Doc +data.NatMap.Nonempty.intersectWith.doc = + use Nat + + use NatMap.Nonempty fromList + {{ + Returns a {type NatMap.Nonempty} containing only the keys that are present in + both of the given {type NatMap.Nonempty}s. The values of the resulting + {type NatMap.Nonempty} are computed by combining the values of the two + {type NatMap.Nonempty}s using the given combining function. + + # Example + + ``` + NatMap.toList + (NatMap.Nonempty.intersectWith + (+) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((2, 20) +| [(3, 30), (4, 40)]))) + ``` + + # See also + + * {NatMap.Nonempty.intersectWithKey} for a version that additionally + provides + * {NatMap.Nonempty.intersect} for a version that throws away the values of + the second {type NatMap.Nonempty}. the key to the combining function. + }} + +data.NatMap.Nonempty.intersectWithKey : + (Nat ->{g} a ->{g} b ->{g} a) + -> NatMap.Nonempty a + -> NatMap.Nonempty b + ->{g} NatMap a +data.NatMap.Nonempty.intersectWithKey f = cases + t1@(NatMap.Nonempty.Bin p1 m1 sz1 l1 r1), + t2@(NatMap.Nonempty.Bin p2 m2 sz2 l2 r2) + | shorter m1 m2 -> + if nomatch p2 p1 m1 then NatMap.empty + else + if Nat.and p2 m1 Nat.== 0 then + data.NatMap.Nonempty.intersectWithKey f l1 t2 + else data.NatMap.Nonempty.intersectWithKey f r1 t2 + | shorter m2 m1 -> + if nomatch p1 p2 m2 then NatMap.empty + else + if Nat.and p1 m2 Nat.== 0 then + data.NatMap.Nonempty.intersectWithKey f t1 l2 + else data.NatMap.Nonempty.intersectWithKey f t1 r2 + | p1 Nat.== p2 -> + NatMap.internal.bin + p1 + m1 + (data.NatMap.Nonempty.intersectWithKey f l1 l2) + (data.NatMap.Nonempty.intersectWithKey f r1 r2) + | otherwise -> NatMap.empty + t1@(NatMap.Nonempty.Tip k v), t2 -> + match NatMap.Nonempty.get k t2 with + None -> NatMap.empty + Some v' -> toNatMap (NatMap.Nonempty.singleton k (f k v v')) + t1, t2@(NatMap.Nonempty.Tip k v) -> + match NatMap.Nonempty.get k t1 with + None -> NatMap.empty + Some v' -> toNatMap (NatMap.Nonempty.singleton k (f k v' v)) + +data.NatMap.Nonempty.intersectWithKey.doc : Doc +data.NatMap.Nonempty.intersectWithKey.doc = + use Nat + + use NatMap.Nonempty fromList + {{ + Returns a {type NatMap.Nonempty} containing only the keys that are present in + both of the given {type NatMap.Nonempty}s. The values of the resulting + {type NatMap.Nonempty} are computed by combining the values of the two + {type NatMap.Nonempty}s using the given combining function. The combining + function can take the key into account. + + # Example + + ``` + NatMap.toList + (NatMap.Nonempty.intersectWithKey + (k x y -> k + x + y) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((2, 20) +| [(3, 30), (4, 40)]))) + ``` + + # See also + + * {NatMap.Nonempty.intersectWith} for a version that does not provide the + key to the combining function. + * {NatMap.Nonempty.intersect} for a version that throws away the values of + the second {type NatMap.Nonempty}. + }} + +data.NatMap.Nonempty.isProperSubmapOf : + NatMap.Nonempty a -> NatMap.Nonempty a -> Boolean +data.NatMap.Nonempty.isProperSubmapOf = Nonempty.isProperSubmapOfBy (===) + +data.NatMap.Nonempty.isProperSubmapOf.doc : Doc +data.NatMap.Nonempty.isProperSubmapOf.doc = + use NatMap.Nonempty fromList + use Nonempty isProperSubmapOf + {{ + Checks whether the first {type NatMap.Nonempty} is a proper submap of the + second {type NatMap.Nonempty}. + + A {type NatMap.Nonempty} is a proper submap of another {type NatMap.Nonempty} + if the second {type NatMap.Nonempty} contains all the keys of the first + {type NatMap.Nonempty} and at least one more key, and the values under the + shared keys are equal according to the {===} function. + + # Examples + + ``` + isProperSubmapOf + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((1, 10) +| [(2, 20), (3, 30), (4, 40)])) + ``` + + ``` + isProperSubmapOf + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + ``` + isProperSubmapOf + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((1, 10) +| [(2, 20)])) + ``` + + # See also + + * {Nonempty.isSubmapOf} for a version that returns `` true `` also if the + two {type NatMap.Nonempty}s are identical. + * {Nonempty.isProperSubmapOfBy} for a version that allows you to compare + the values under the keys with a custom function. + }} + +data.NatMap.Nonempty.isProperSubmapOfBy : + (a ->{g} a ->{g} Boolean) + -> NatMap.Nonempty a + -> NatMap.Nonempty a + ->{g} Boolean +data.NatMap.Nonempty.isProperSubmapOfBy f t1 t2 = + Nonempty.submapCompareBy f t1 t2 === Some Less + +data.NatMap.Nonempty.isProperSubmapOfBy.doc : Doc +data.NatMap.Nonempty.isProperSubmapOfBy.doc = + use Nat == + use NatMap.Nonempty fromList + use Nonempty isProperSubmapOfBy + {{ + Checks whether the first {type NatMap.Nonempty} is a proper submap of the + second {type NatMap.Nonempty}, and compares the values under the keys with a + custom function. + + Returns `` true `` if the second {type NatMap.Nonempty} contains all the keys + of the first {type NatMap.Nonempty} and at least one more key, and the values + under the common keys are equal according to the given function. + + # Examples + + ``` + isProperSubmapOfBy + (==) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((1, 10) +| [(2, 20), (3, 30), (4, 40)])) + ``` + + ``` + isProperSubmapOfBy + (==) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + ``` + isProperSubmapOfBy + (==) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((1, 10) +| [(2, 20)])) + ``` + + # See also + + * {Nonempty.isSubmapOfBy} for a version that returns `` true `` also if the + two {type NatMap.Nonempty}s have exactly the same keys. + * {Nonempty.isProperSubmapOf} for a version that compares the values under + the keys using the {===} function. + }} + +data.NatMap.Nonempty.isSubmapOf : + NatMap.Nonempty a -> NatMap.Nonempty a -> Boolean +data.NatMap.Nonempty.isSubmapOf = Nonempty.isSubmapOfBy (===) + +data.NatMap.Nonempty.isSubmapOf.doc : Doc +data.NatMap.Nonempty.isSubmapOf.doc = + use NatMap.Nonempty fromList + use Nonempty isSubmapOf + {{ + Checks whether the first {type NatMap.Nonempty} is a submap of the second + {type NatMap.Nonempty}. + + A {type NatMap.Nonempty} is a submap of another {type NatMap.Nonempty} if the + second {type NatMap.Nonempty} contains all the keys of the first + {type NatMap.Nonempty}, and the values under the shared keys in one map are + equal to the values under the shared keys in the other map according to the + {===} function. + + # Examples + + ``` + isSubmapOf + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((1, 10) +| [(2, 20), (3, 30), (4, 40)])) + ``` + + ``` + isSubmapOf + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + ``` + isSubmapOf + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((1, 10) +| [(2, 20)])) + ``` + + # See also + + * {Nonempty.isProperSubmapOf} for a version that returns `` true `` only if + the second {type NatMap.Nonempty} contains at least one more key than the + first. + * {Nonempty.isSubmapOfBy} for a version that allows you to compare the + values under the keys with a custom function. + }} + +data.NatMap.Nonempty.isSubmapOfBy : + (a ->{g} a ->{g} Boolean) + -> NatMap.Nonempty a + -> NatMap.Nonempty a + ->{g} Boolean +data.NatMap.Nonempty.isSubmapOfBy f t1 t2 = + match Nonempty.submapCompareBy f t1 t2 with + Some Less -> true + Some Equal -> true + _ -> false + +data.NatMap.Nonempty.isSubmapOfBy.doc : Doc +data.NatMap.Nonempty.isSubmapOfBy.doc = + use Nat == + use NatMap.Nonempty fromList + use Nonempty isSubmapOfBy + {{ + Checks whether the first {type NatMap.Nonempty} is a submap of the second + {type NatMap.Nonempty}, and compares the values under the keys with a custom + function. + + Returns `` true `` if the second {type NatMap.Nonempty} contains all the keys + of the first {type NatMap.Nonempty}, and the values under the common keys are + equal according to the given function. + + # Examples + + ``` + isSubmapOfBy + (==) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((1, 10) +| [(2, 20), (3, 30), (4, 40)])) + ``` + + ``` + isSubmapOfBy + (==) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + ``` + isSubmapOfBy + (==) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((1, 10) +| [(2, 20)])) + ``` + + # See also + + * {Nonempty.isProperSubmapOfBy} for a version that returns `` true `` only + if the second {type NatMap.Nonempty} contains at least one more key than + the first. + * {Nonempty.isSubmapOf} for a version that compares the values under the + keys using the {===} function. + }} + +data.NatMap.Nonempty.keys : NatMap.Nonempty a -> List.Nonempty Nat +data.NatMap.Nonempty.keys = + use Nonempty ++ + foldMapWithKey (++) (k _ -> List.Nonempty.singleton k) + +data.NatMap.Nonempty.keys.doc : Doc +data.NatMap.Nonempty.keys.doc = + {{ + Returns the keys of a {type NatMap.Nonempty} as a list. + + # Example + + ``` + NatMap.Nonempty.keys + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + # See also + + * {NatMap.Nonempty.values} returns the values rather than the keys. + * {NatMap.Nonempty.toList} returns both the keys and values. + }} + +data.NatMap.Nonempty.keySet : NatMap.Nonempty a -> NatSet.Nonempty +data.NatMap.Nonempty.keySet = + foldMapWithKey NatSet.Nonempty.union (k _ -> NatSet.singleton k) + +data.NatMap.Nonempty.keySet.doc : Doc +data.NatMap.Nonempty.keySet.doc = + {{ + Returns the {type NatSet.Nonempty} of keys in a {type NatMap.Nonempty}. + + # Example + + ``` + Nonempty.toListAscending + (Nonempty.keySet + (NatMap.Nonempty.fromList ((1, "a") +| [(2, "b"), (3, "c")]))) + ``` + + # See also + + * {NatMap.Nonempty.keys} to get the {type List.Nonempty} of keys. + * {NatMap.Nonempty.values} to get the {type List.Nonempty} of values. + * {NatMap.Nonempty.toList} to convert a {type NatMap.Nonempty} to a + {type List.Nonempty} of key-value pairs. + }} + +data.NatMap.Nonempty.map : + (a ->{g} b) -> NatMap.Nonempty a ->{g} NatMap.Nonempty b +data.NatMap.Nonempty.map f = NatMap.Nonempty.mapWithKey do x -> f x + +data.NatMap.Nonempty.map.doc : Doc +data.NatMap.Nonempty.map.doc = + use Nat + + {{ + Applies a function to every value in a {type NatMap.Nonempty}. + + # Example + + ``` + NatMap.Nonempty.toList + (NatMap.Nonempty.map + (x -> x + 1) (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)]))) + ``` + + # See also + + * {NatMap.Nonempty.mapWithKey} applies a function to both the keys and + values. + * {Nonempty.mapOptional} applies a function to the values, removing the + ones that return {None}. + * {Nonempty.mapEither} applies an {type Either}-valued function to the + values, partitioning the {type NatMap.Nonempty} into two {type NatMap}s. + * {NatMap.Nonempty.adjust} applies a function to the value under a specific + key. + }} + +data.NatMap.Nonempty.mapEither : + (a ->{g} Either b c) -> NatMap.Nonempty a ->{g} (NatMap b, NatMap c) +data.NatMap.Nonempty.mapEither f = Nonempty.mapEitherWithKey do x -> f x + +data.NatMap.Nonempty.mapEither.doc : Doc +data.NatMap.Nonempty.mapEither.doc = + {{ + Applies an {type Either}-valued function to every value in a + {type NatMap.Nonempty}, partitioning the {type NatMap.Nonempty} into two + {type NatMap}s. + + # Example + + ``` + Tuple.bimap + NatMap.toList + (Nonempty.mapEither + (x -> (if Nat.isEven x then Left x else Right x)) + (NatMap.Nonempty.fromList ((1, 11) +| [(2, 22), (3, 33)]))) + ``` + + # See also + + * {Nonempty.mapEitherWithKey} for a version of this that applies the + function to both the keys and values. + * {Nonempty.mapOptional} applies a function to the values, removing the + ones that return {None}. + * {NatMap.Nonempty.map} just applies a function to all the values. + * {NatMap.Nonempty.partition} applies a predicate to the values, + partitioning the {type NatMap.Nonempty} into two {type NatMap}s. + }} + +data.NatMap.Nonempty.mapEitherWithKey : + (Nat ->{g} a ->{g} Either b c) + -> NatMap.Nonempty a + ->{g} (NatMap b, NatMap c) +data.NatMap.Nonempty.mapEitherWithKey f = cases + NatMap.Nonempty.Bin p m sz l r -> + (l1, l2) = data.NatMap.Nonempty.mapEitherWithKey f l + (r1, r2) = data.NatMap.Nonempty.mapEitherWithKey f r + (NatMap.internal.bin p m l1 r1, NatMap.internal.bin p m l2 r2) + NatMap.Nonempty.Tip k v -> + match f k v with + Left v' -> (toNatMap (NatMap.Nonempty.Tip k v'), NatMap.empty) + Right v' -> (NatMap.empty, toNatMap (NatMap.Nonempty.Tip k v')) + +data.NatMap.Nonempty.mapEitherWithKey.doc : Doc +data.NatMap.Nonempty.mapEitherWithKey.doc = + {{ + Applies an {type Either}-valued function to every value in a + {type NatMap.Nonempty}, partitioning the {type NatMap.Nonempty} into two + {type NatMap.Nonempty}s. + + # Example + + ``` + Tuple.bimap + NatMap.toList + (Nonempty.mapEitherWithKey + (k x -> (if Nat.isEven k then Left x else Right x)) + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)]))) + ``` + + # See also + + * {Nonempty.mapEither} for a version of this that applies the function only + to the values. + * {Nonempty.mapOptionalWithKey} applies a function to the entries, removing + the ones that return {None}. + * {NatMap.Nonempty.mapWithKey} just applies a function to all the entries. + * {Nonempty.partitionWithKey} applies a predicate to the entries, + partitioning the {type NatMap.Nonempty} into two {type NatMap}s. + }} + +data.NatMap.Nonempty.mapOptional : + (a ->{g} Optional b) -> NatMap.Nonempty a ->{g} NatMap b +data.NatMap.Nonempty.mapOptional f = Nonempty.mapOptionalWithKey do x -> f x + +data.NatMap.Nonempty.mapOptional.doc : Doc +data.NatMap.Nonempty.mapOptional.doc = + {{ + Applies a function to every value in a {type NatMap.Nonempty}, removing the + ones that return {None}. + + # Example + + ``` + NatMap.toList + (Nonempty.mapOptional + (x -> (if Nat.isEven x then Some x else None)) + (NatMap.Nonempty.fromList ((1, 11) +| [(2, 22), (3, 33)]))) + ``` + + # See also + + * {Nonempty.mapOptionalWithKey} for a version of this that applies the + function to both the keys and values. + * {Nonempty.mapEither} applies an {type Either}-valued function to the + values, partitioning the {type NatMap.Nonempty} into two {type NatMap}s. + * {NatMap.Nonempty.map} just applies a function to all the values. + * {NatMap.Nonempty.filter} applies a predicate to the values, removing the + ones that return {false}. + }} + +data.NatMap.Nonempty.mapOptionalWithKey : + (Nat ->{g} a ->{g} Optional b) -> NatMap.Nonempty a ->{g} NatMap b +data.NatMap.Nonempty.mapOptionalWithKey f = cases + NatMap.Nonempty.Bin p m sz l r -> + use data.NatMap.Nonempty mapOptionalWithKey + l' = mapOptionalWithKey f l + r' = mapOptionalWithKey f r + NatMap.internal.bin p m l' r' + NatMap.Nonempty.Tip k v -> + match f k v with + Some v' -> toNatMap (NatMap.Nonempty.singleton k v') + None -> NatMap.empty + +data.NatMap.Nonempty.mapOptionalWithKey.doc : Doc +data.NatMap.Nonempty.mapOptionalWithKey.doc = + {{ + Applies a function to every key-value pair in a {type NatMap.Nonempty}, + removing the ones that return {None}. + + # Example + + ``` + NatMap.toList + (Nonempty.mapOptionalWithKey + (k x -> (if Nat.isEven k then Some x else None)) + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)]))) + ``` + + # See also + + * {Nonempty.mapOptional} for a version of this that applies the function + only to the values. + * {Nonempty.mapEitherWithKey} applies an {type Either}-valued function to + the entries, partitioning the {type NatMap.Nonempty} into two + {type NatMap}s. + * {NatMap.Nonempty.mapWithKey} just applies a function to all the entries. + * {NatMap.Nonempty.filterWithKey} applies a predicate to the entries, + removing the ones that return {false}. + }} + +data.NatMap.Nonempty.mapWithKey : + (Nat ->{g} a ->{g} b) -> NatMap.Nonempty a ->{g} NatMap.Nonempty b +data.NatMap.Nonempty.mapWithKey f = cases + NatMap.Nonempty.Bin p m sz l r -> + NatMap.internal.bim + p + m + (data.NatMap.Nonempty.mapWithKey f l) + (data.NatMap.Nonempty.mapWithKey f r) + NatMap.Nonempty.Tip k v -> NatMap.Nonempty.Tip k (f k v) + +data.NatMap.Nonempty.mapWithKey.doc : Doc +data.NatMap.Nonempty.mapWithKey.doc = + use Nat + + {{ + Applies a function to every key-value pair in a {type NatMap.Nonempty}. + + # Example + + ``` + NatMap.Nonempty.toList + (NatMap.Nonempty.mapWithKey + (+) (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)]))) + ``` + + # See also + + * {NatMap.Nonempty.map} for a version of this that applies the function + only to the values. + * {Nonempty.mapOptionalWithKey} applies a function to the entries, removing + the ones that return {None}. + * {Nonempty.mapEitherWithKey} applies an {type Either}-valued function to + the entries, partitioning the {type NatMap.Nonempty} into two + {type NatMap}s. + }} + +data.NatMap.Nonempty.maxKey : NatMap.Nonempty a -> Nat +data.NatMap.Nonempty.maxKey m = + ((k, _), _) = NatMap.Nonempty.breakOffMax m + k + +data.NatMap.Nonempty.maxKey.doc : Doc +data.NatMap.Nonempty.maxKey.doc = + {{ + Returns the largest key in the map. + + # Example + + ``` + NatMap.Nonempty.fromList ((1, "a") +| [(2, "b"), (3, "c")]) + |> Nonempty.maxKey + ``` + }} + +data.NatMap.Nonempty.maxView : NatMap.Nonempty a -> (a, NatMap a) +data.NatMap.Nonempty.maxView t = + ((_, v), t') = NatMap.Nonempty.breakOffMax t + (v, t') + +data.NatMap.Nonempty.maxView.doc : Doc +data.NatMap.Nonempty.maxView.doc = + {{ + Returns the value of the largest key in the {type NatMap.Nonempty} and the + {type NatMap} with that key removed. + + # Example + + ``` + Tuple.second + NatMap.toList + (NatMap.Nonempty.maxView + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)]))) + ``` + + # See also + + * {NatMap.Nonempty.breakOffMax} for a version of this that returns the key + as well as the value. + * {NatMap.Nonempty.minView} for the value under the smallest key. + * {NatMap.Nonempty.breakOffMin} for the key and value under the smallest + key. + }} + +data.NatMap.Nonempty.minKey : NatMap.Nonempty a -> Nat +data.NatMap.Nonempty.minKey m = + ((k, _), _) = NatMap.Nonempty.breakOffMin m + k + +data.NatMap.Nonempty.minView : NatMap.Nonempty a -> (a, NatMap a) +data.NatMap.Nonempty.minView t = + ((_, v), t') = NatMap.Nonempty.breakOffMin t + (v, t') + +data.NatMap.Nonempty.minView.doc : Doc +data.NatMap.Nonempty.minView.doc = + {{ + Returns the value of the smallest key in the {type NatMap.Nonempty} and the + {type NatMap} with that key removed. + + # Example + + ``` + Tuple.second + NatMap.toList + (NatMap.Nonempty.minView + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)]))) + ``` + + # See also + + * {NatMap.Nonempty.breakOffMin} for a version of this that returns the key + as well as the value. + * {NatMap.Nonempty.maxView} for the value under the largest key. + * {NatMap.Nonempty.breakOffMax} for the key and value under the largest + key. + }} + +data.NatMap.Nonempty.nth : Nat -> NatMap.Nonempty v -> Optional (Nat, v) +data.NatMap.Nonempty.nth index = cases + NatMap.Nonempty.Tip k v -> if index Nat.== 0 then Some (k, v) else None + NatMap.Nonempty.Bin p m sz l r -> + use Nat - < + use data.NatMap.Nonempty nth + leftSize = NatMap.Nonempty.size l + if index < leftSize then nth index l else nth (index - leftSize) r + +data.NatMap.Nonempty.nth.doc : Doc +data.NatMap.Nonempty.nth.doc = + {{ + Returns the key-value pair in the given {type NatMap.Nonempty} with the + `i`-th smallest key, where `i`=0 is the smallest key (according to + {Universal.ordering}). + + Is the same as {{ + docExample 2 do + i as -> + List.at i (sortBy at1 (List.Nonempty.toList (NatMap.Nonempty.toList as))) + }} but doesn't require instantiating the intermediate {type List}. + + ``` + s = + NatMap.Nonempty.fromList + (Nonempty.Nonempty + (6, "six") [(5, "five"), (4, "four"), (2, "two"), (1, "one")]) + List.map + (i -> NatMap.Nonempty.nth i s) (List.range 0 (NatMap.Nonempty.size s)) + ``` + }} + +test> data.NatMap.Nonempty.nth.tests = + test.verify do + use Random natIn + Each.repeat 100 + s = + (natIn 0 20, natIn 0 10) + +| (List.replicate (natIn 0 19) do (natIn 0 20, natIn 0 10)) + |> NatMap.Nonempty.fromList + ensure + (List.somes + (List.map + (i -> NatMap.Nonempty.nth i s) + (List.range 0 (NatMap.Nonempty.size s))) + === (NatMap.Nonempty.toList s |> List.Nonempty.toList)) + +data.NatMap.Nonempty.partition : + (a ->{g} Boolean) -> NatMap.Nonempty a ->{g} (NatMap a, NatMap a) +data.NatMap.Nonempty.partition f = Nonempty.partitionWithKey do x -> f x + +data.NatMap.Nonempty.partition.doc : Doc +data.NatMap.Nonempty.partition.doc = + {{ + Partitions a {type NatMap.Nonempty} into two {type NatMap}s, one containing + the values that satisfy the predicate and one containing the values that do + not. + + # Example + + ``` + Tuple.bimap + NatMap.toList + (NatMap.Nonempty.partition + Nat.isEven (NatMap.Nonempty.fromList ((1, 11) +| [(2, 22), (3, 33)]))) + ``` + + # See also + + * {Nonempty.partitionWithKey} for a version of this that applies the + predicate to both the keys and values. + * {NatMap.Nonempty.filter} for a version that only keeps the values that + satisfy the predicate. + * {Nonempty.mapEither} for partitioning into two {type NatMap}s based on an + {type Either}-valued function. + * {NatMap.Nonempty.split} for partitioning into two {type NatMap}s around a + pivot key. + }} + +data.NatMap.Nonempty.partitionWithKey : + (Nat ->{g} a ->{g} Boolean) -> NatMap.Nonempty a ->{g} (NatMap a, NatMap a) +data.NatMap.Nonempty.partitionWithKey f = cases + NatMap.Nonempty.Bin p m sz l r -> + (l1, l2) = data.NatMap.Nonempty.partitionWithKey f l + (r1, r2) = data.NatMap.Nonempty.partitionWithKey f r + (NatMap.internal.bin p m l1 r1, NatMap.internal.bin p m l2 r2) + NatMap.Nonempty.Tip k v + | f k v -> (toNatMap (NatMap.Nonempty.Tip k v), NatMap.empty) + | otherwise -> (NatMap.empty, toNatMap (NatMap.Nonempty.Tip k v)) + +data.NatMap.Nonempty.partitionWithKey.doc : Doc +data.NatMap.Nonempty.partitionWithKey.doc = + use Nat > + {{ + Partitions a {type NatMap.Nonempty} into two {type NatMap}s, one containing + the entries that satisfy the predicate and one containing the entries that do + not. + + # Example + + ``` + Tuple.bimap + NatMap.toList + (Nonempty.partitionWithKey + (k v -> Nat.isEven k && v > 20) + (NatMap.Nonempty.fromList ((1, 11) +| [(2, 22), (3, 33)]))) + ``` + + # See also + + * {NatMap.Nonempty.partition} for a version of this that applies the + predicate only to the values. + * {NatMap.Nonempty.filterWithKey} for a version that only keeps the entries + that satisfy the predicate. + * {Nonempty.mapEitherWithKey} for partitioning into two {type NatMap}s + based on an {type Either}-valued function. + }} + +data.NatMap.Nonempty.randomChoice : NatMap.Nonempty v ->{Random} (Nat, v) +data.NatMap.Nonempty.randomChoice map = + randomIndex = Random.natIn 0 (NatMap.Nonempty.size map) + NatMap.Nonempty.nth randomIndex map + |> getOrBug "NatMap.Nonempty.randomChoice: index out of bounds" + +data.NatMap.Nonempty.randomChoice.doc : Doc +data.NatMap.Nonempty.randomChoice.doc = + use NatMap.Nonempty fromList randomChoice + use Nonempty Nonempty + {{ + Picks a random key-value pair from the given {type NatMap.Nonempty}. + + # Examples + + ``` + lcg 4096 do + randomChoice + (fromList (Nonempty (5, "five") [(4, "four"), (2, "two"), (1, "one")])) + ``` + + ``` + lcg 2510 do + randomChoice + (fromList (Nonempty (5, "five") [(4, "four"), (2, "two"), (1, "one")])) + ``` + }} + +test> data.NatMap.Nonempty.randomChoice.test = test.verify do + map = NatMap.Nonempty.fromList ((0, 0) +| [(1, 1), (2, 2), (3, 3), (4, 4)]) + Each.repeat 1000 + e = NatMap.Nonempty.randomChoice map + ensure (NatMap.Nonempty.contains (at1 e) map) + +data.NatMap.Nonempty.randomKey : NatMap.Nonempty v ->{Random} Nat +data.NatMap.Nonempty.randomKey map = NatMap.Nonempty.randomChoice map |> at1 + +data.NatMap.Nonempty.randomKey.doc : Doc +data.NatMap.Nonempty.randomKey.doc = + use NatMap.Nonempty fromList randomKey + use Nonempty Nonempty + {{ + Picks a random key from the given {type NatMap.Nonempty}. + + # Examples + + ``` + lcg 4096 do + randomKey + (fromList + (Nonempty + (6, "six") [(5, "five"), (4, "four"), (2, "two"), (1, "one")])) + ``` + + ``` + lcg 2510 do + randomKey + (fromList + (Nonempty + (6, "six") [(5, "five"), (4, "four"), (2, "two"), (1, "one")])) + ``` + }} + +data.NatMap.Nonempty.randomValue : NatMap.Nonempty v ->{Random} v +data.NatMap.Nonempty.randomValue map = NatMap.Nonempty.randomChoice map |> at2 + +data.NatMap.Nonempty.randomValue.doc : Doc +data.NatMap.Nonempty.randomValue.doc = + use NatMap.Nonempty fromList randomValue + use Nonempty Nonempty + {{ + Picks a random value from the given {type NatMap.Nonempty}. + + # Examples + + ``` + lcg 4096 do + randomValue + (fromList + (Nonempty + (6, "six") [(5, "five"), (4, "four"), (2, "two"), (1, "one")])) + ``` + + ``` + lcg 2510 do + randomValue + (fromList + (Nonempty + (6, "six") [(5, "five"), (4, "four"), (2, "two"), (1, "one")])) + ``` + }} + +data.NatMap.Nonempty.restrict : Nat -> Nat -> NatMap.Nonempty v -> NatMap v +data.NatMap.Nonempty.restrict min max m = + (belowMin, eqMin, aboveMin) = NatMap.Nonempty.split min m + (between, eqMax, aboveMax) = NatMap.split max aboveMin + match eqMin with + None -> between + Some v -> NatMap.insert min v between + +data.NatMap.Nonempty.restrict.doc : Doc +data.NatMap.Nonempty.restrict.doc = + use NatMap toList + use NatMap.Nonempty fromList + {{ + Restricts a {type NatMap.Nonempty} to a given range of keys. + + `` Nonempty.restrict min max map `` drops all entries from `map` that are + outside the range between `min` and `max`. + + The range is inclusive of the `min` and exclusive of the `max`. That is, a + key that is exactly `min` will be kept, but a key that is exactly `max` will + be removed. + + # Examples + + ``` + (1, ?a) +| [(2, ?b), (3, ?c), (4, ?d), (5, ?e)] |> fromList + |> Nonempty.restrict 2 4 + |> toList + ``` + + ``` + (1, ?a) +| [(3, ?c), (5, ?e)] |> fromList |> Nonempty.restrict 2 4 + |> toList + ``` + + # See also + + * {NatMap.restrict} for the version of this that operates on a (possibly + empty) {type NatMap}. + * {Nonempty.restrictAbove} to keep only keys above a given key. + * {Nonempty.restrictBelow} to keep only keys below a given key. + }} + +data.NatMap.Nonempty.restrictAbove : Nat -> NatMap.Nonempty v -> NatMap v +data.NatMap.Nonempty.restrictAbove min m = + (_, _, aboveMin) = NatMap.Nonempty.split min m + aboveMin + +data.NatMap.Nonempty.restrictAbove.doc : Doc +data.NatMap.Nonempty.restrictAbove.doc = + {{ + Drops keys from a {type NatMap.Nonempty}, keeping only keys that are strictly + larger than a given key. + + # Example + + ``` + (1, ?a) +| [(2, ?b), (3, ?c), (4, ?d), (5, ?e)] |> NatMap.Nonempty.fromList + |> Nonempty.restrictAbove 2 + |> NatMap.toList + ``` + + # See also + + * {NatMap.restrictAbove} for the version of this that operates on a + (possibly empty) {type NatMap}. + * {Nonempty.restrict} to restrict to a range of keys. + * {Nonempty.restrictBelow} to restrict to a range of keys, keeping only + keys that are strictly smaller than a given key. + }} + +data.NatMap.Nonempty.restrictBelow : Nat -> NatMap.Nonempty v -> NatMap v +data.NatMap.Nonempty.restrictBelow max m = + (belowMax, _, _) = NatMap.Nonempty.split max m + belowMax + +data.NatMap.Nonempty.restrictBelow.doc : Doc +data.NatMap.Nonempty.restrictBelow.doc = + {{ + Drops keys from a {type NatMap.Nonempty}, keeping only keys that are strictly + smaller than a given key. + + # Example + + ``` + (1, ?a) +| [(2, ?b), (3, ?c), (4, ?d), (5, ?e)] |> NatMap.Nonempty.fromList + |> Nonempty.restrictBelow 4 + |> NatMap.toList + ``` + + # See also + + * {NatMap.restrictBelow} for the version of this that operates on a + (possibly empty) {type NatMap}. + * {Nonempty.restrict} to restrict to a range of keys. + * {Nonempty.restrictAbove} to restrict to a range of keys, keeping only + keys that are strictly larger than a given key. + }} + +data.NatMap.Nonempty.singleton.doc : Doc +data.NatMap.Nonempty.singleton.doc = + {{ + Creates a {type NatMap.Nonempty} with a single entry. + + # Example + + ``` + NatMap.Nonempty.toList (NatMap.Nonempty.singleton 1 10) + ``` + + # See also + + * {NatMap.Nonempty.fromList} to create a {type NatMap.Nonempty} from a + {type List.Nonempty} of entries. + }} + +data.NatMap.Nonempty.size : NatMap.Nonempty a -> Nat +data.NatMap.Nonempty.size = cases + NatMap.Nonempty.Bin _ _ sz l r -> sz + NatMap.Nonempty.Tip _ _ -> 1 + +data.NatMap.Nonempty.size.doc : Doc +data.NatMap.Nonempty.size.doc = + {{ + Returns the number of entries in the {type NatMap.Nonempty}. + + # Example + + ``` + NatMap.Nonempty.size + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + }} + +data.NatMap.Nonempty.split : + Nat -> NatMap.Nonempty a -> (NatMap a, Optional a, NatMap a) +data.NatMap.Nonempty.split k = cases + t@(NatMap.Nonempty.Bin p m sz l r) + | nomatch k p m -> + if k Nat.> p then (toNatMap t, None, NatMap.empty) + else (NatMap.empty, None, toNatMap t) + | Nat.and k m Nat.== 0 -> + (lt, result, gt) = data.NatMap.Nonempty.split k l + (lt, result, NatMap.union gt (toNatMap r)) + | otherwise -> + (lt, result, gt) = data.NatMap.Nonempty.split k r + (NatMap.union (toNatMap l) lt, result, gt) + t@(NatMap.Nonempty.Tip k' v) + | k Nat.> k' -> (toNatMap t, None, NatMap.empty) + | k Nat.< k' -> (NatMap.empty, None, toNatMap t) + | otherwise -> (NatMap.empty, Some v, NatMap.empty) + +data.NatMap.Nonempty.split.doc : Doc +data.NatMap.Nonempty.split.doc = + {{ + Splits a {type NatMap.Nonempty} into three parts based on the given key. The + first part contains all entries with keys less than the given key, the second + part contains the entry with that key (if present) and the third part + contains all entries with keys greater than the key. + + # Example + + ``` + NatMap.Nonempty.split + 2 (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + # See also + + * {NatMap.Nonempty.partition} to partition a {type NatMap.Nonempty} into + two parts based on a predicate. + }} + +data.NatMap.Nonempty.submapCompareBy : + (a ->{g} a ->{g} Boolean) + -> NatMap.Nonempty a + -> NatMap.Nonempty a + ->{g} Optional Ordering +data.NatMap.Nonempty.submapCompareBy f = cases + t1@(NatMap.Nonempty.Bin p1 m1 sz1 l1 r1), + t2@(NatMap.Nonempty.Bin p2 m2 sz2 l2 r2) + | shorter m1 m2 -> Some Greater + | shorter m2 m1 -> + if nomatch p1 p2 m2 then Some Greater + else + if Nat.and p1 m2 Nat.== 0 then + data.NatMap.Nonempty.submapCompareBy f t1 l2 + else data.NatMap.Nonempty.submapCompareBy f t1 r2 + | p1 Nat.== p2 -> + match ( data.NatMap.Nonempty.submapCompareBy f l1 l2 + , data.NatMap.Nonempty.submapCompareBy f r1 r2 + ) with + (Some Greater, _) -> Some Greater + (_, Some Greater) -> Some Greater + (Some Equal, Some Equal) -> Some Equal + _ -> Some Less + | otherwise -> None + NatMap.Nonempty.Tip k v, NatMap.Nonempty.Tip k' v' -> + if k Nat.== k' && f v v' then Some Equal else None + NatMap.Nonempty.Tip k v, t2 -> + match NatMap.Nonempty.get k t2 with + Some v' | f v v' -> Some Less + _ -> None + t1, NatMap.Nonempty.Tip k v -> + match NatMap.Nonempty.get k t1 with + Some v' | f v' v -> Some Greater + _ -> None + +data.NatMap.Nonempty.submapCompareBy.doc : Doc +data.NatMap.Nonempty.submapCompareBy.doc = + use Nat == + use NatMap.Nonempty fromList + use Nonempty submapCompareBy + {{ + Checks if the first {type NatMap.Nonempty} is a submap of the second one, + using the given function to compare the values. + + This function returns {Less} if the second {type NatMap.Nonempty} contains + all the keys of the first {type NatMap.Nonempty} and comparing the values + under those keys using the given function returns `` true `` for all of them. + + It returns {Equal} if the two {type NatMap.Nonempty}s contain the same keys + and comparing the values under those keys using the given function returns `` + true `` for all of them. + + It returns {Greater} if the first {type NatMap.Nonempty} contains all the + keys of the second {type NatMap.Nonempty} and comparing the values under + those keys using the given function returns `` true `` for all of them. + + It returns {None} if the two {type NatMap.Nonempty}s contain different keys + or comparing the values under those keys using the given function returns `` + false `` for any of them. + + # Examples + + ``` + submapCompareBy + (==) + (fromList ((1, 10) +| [(2, 20)])) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + ``` + + ``` + submapCompareBy + (==) (fromList ((1, 10) +| [(2, 20)])) (fromList ((1, 10) +| [(2, 20)])) + ``` + + ``` + submapCompareBy + (==) (fromList ((1, 10) +| [(2, 20)])) (fromList ((1, 10) +| [(2, 30)])) + ``` + + # See also + + * {Nonempty.isSubmapOfBy} for a version of this that returns a + {type Boolean} instead of an {type Ordering}. + * {Nonempty.isSubmapOf} returns a {type Boolean} and uses {===} to compare + the values. + }} + +test> data.NatMap.Nonempty.test.diff = + runs 100 do + use List Nonempty.toList + use NatMap Nonempty.fromList + use Set == + use gen nat + xs = atLeastOne (pairOf nat do ()) () + ys = atLeastOne (pairOf nat do ()) () + diff = + Set.fromList + (NatMap.toList + (NatMap.Nonempty.difference + (Nonempty.fromList xs) (Nonempty.fromList ys))) + setDiff = + Set.deletes (Nonempty.toList ys) (Set.fromList (Nonempty.toList xs)) + if diff == setDiff then expect true + else bug (xs, ys, Set.toList diff, Set.toList setDiff) + +data.NatMap.Nonempty.test.gen : '{Gen} t -> '{Gen} NatMap.Nonempty t +data.NatMap.Nonempty.test.gen g = + do NatMap.Nonempty.fromList (atLeastOne (pairOf gen.nat g) ()) + +test> data.NatMap.Nonempty.test.unionAssociative = + runs 100 do + use Nat == + use NatMap.Nonempty union + use Nonempty.test gen + use gen nat + map1 = gen nat () + map2 = gen nat () + map3 = gen nat () + expect + (Nonempty.equalBy + (==) (union map1 (union map2 map3)) (union (union map1 map2) map3)) + +test> data.NatMap.Nonempty.test.unionCommutative = + runs 100 do + use Nat == + use Nonempty.test gen + use gen nat + map2 = gen nat () + map1 = gen nat () + expect + (Nonempty.equalBy + (==) + (NatMap.Nonempty.union map1 map2) + (NatMap.Nonempty.unionWith (flip const) map2 map1)) + +test> data.NatMap.Nonempty.test.unionInsert = + runs 100 do + use Nat == + use gen nat + map = Nonempty.test.gen nat () + key = nat() + value = nat() + expect + (Nonempty.equalBy + (==) + (NatMap.Nonempty.insert key value map) + (NatMap.Nonempty.union (NatMap.Nonempty.singleton key value) map)) + +test> data.NatMap.Nonempty.test.updateDelete = runs 100 do + use Nat == + use gen nat + map = Nonempty.test.gen nat () + key = nat() + updated = NatMap.Nonempty.update (const None) key map + deleted = NatMap.Nonempty.delete key map + p = NatMap.equalBy (==) updated deleted + if p then expect p else bug (map, key, updated, deleted) + +data.NatMap.Nonempty.toList : NatMap.Nonempty a -> List.Nonempty (Nat, a) +data.NatMap.Nonempty.toList = + use Nonempty ++ + foldMapWithKey (++) (k v -> List.Nonempty.singleton (k, v)) + +data.NatMap.Nonempty.toList.doc : Doc +data.NatMap.Nonempty.toList.doc = + use NatMap.Nonempty fromList + {{ + Converts a {type NatMap.Nonempty} to a {type List.Nonempty} of {type Tuple}s + containing the keys and values. + + # Example + + ``` + NatMap.Nonempty.toList (fromList ((1, 10) +| [(2, 20)])) + ``` + + # See also + + * {fromList} for converting a {type List.Nonempty} of {type Tuple}s to a + {type NatMap.Nonempty}. + }} + +data.NatMap.Nonempty.toMap : NatMap.Nonempty a -> Map.Nonempty Nat a +data.NatMap.Nonempty.toMap = + foldMapWithKey Map.Nonempty.union (k v -> Map.Nonempty.singleton k v) + +data.NatMap.Nonempty.toMap.doc : Doc +data.NatMap.Nonempty.toMap.doc = + {{ + Converts a {type NatMap.Nonempty} to a {type Map.Nonempty} of {type Nat}s. + + # Example + + ``` + Map.Nonempty.toList + (NatMap.Nonempty.toMap (NatMap.Nonempty.fromList ((1, 2) +| [(3, 4)]))) + ``` + }} + +data.NatMap.Nonempty.toNatMap : NatMap.Nonempty a -> NatMap a +data.NatMap.Nonempty.toNatMap t = NatMap (Some t) + +data.NatMap.Nonempty.union : + NatMap.Nonempty a -> NatMap.Nonempty a -> NatMap.Nonempty a +data.NatMap.Nonempty.union = NatMap.Nonempty.unionWith const + +data.NatMap.Nonempty.union.doc : Doc +data.NatMap.Nonempty.union.doc = + use NatMap.Nonempty fromList + {{ + Constructs a {type NatMap.Nonempty} containing all the keys and values from + both of two {type NatMap.Nonempty}s. + + This function is biased towards the first {type NatMap.Nonempty}. If a key is + present in both {type NatMap.Nonempty}s, the value from the first + {type NatMap.Nonempty} is used. + + # Examples + + ``` + NatMap.Nonempty.toList + (NatMap.Nonempty.union + (fromList ((1, 10) +| [(2, 20)])) (fromList ((1, 30) +| [(3, 40)]))) + ``` + + # See also + + * {NatMap.Nonempty.unionWith} for a version of this that takes a combining + function for values with the same key. + * {NatMap.Nonempty.unions} for combining a {type List.Nonempty} of + {type NatMap.Nonempty}s. + * {NatMap.Nonempty.intersect} to keep only the values that are in both + {type NatMap.Nonempty}s. + * {NatMap.Nonempty.difference} to keep the values that are only in the + first {type NatMap.Nonempty}. + }} + +data.NatMap.Nonempty.unions : + List.Nonempty (NatMap.Nonempty a) -> NatMap.Nonempty a +data.NatMap.Nonempty.unions = Nonempty.unionsWith const + +data.NatMap.Nonempty.unions.doc : Doc +data.NatMap.Nonempty.unions.doc = + use NatMap.Nonempty fromList + {{ + Constructs a {type NatMap.Nonempty} containing all the keys and values from a + {type List.Nonempty} of {type NatMap.Nonempty}s. + + This function is biased towards the first {type NatMap.Nonempty} in the + {type List}. If a key is present in multiple {type NatMap.Nonempty}s, the + value from the first {type NatMap.Nonempty} is used. + + # Examples + + ``` + NatMap.Nonempty.toList + (NatMap.Nonempty.unions + (fromList ((1, 10) +| [(2, 20)]) +| [fromList ((1, 30) +| [(3, 40)])])) + ``` + + # See also + + * {NatMap.Nonempty.union} for a version of this that combines only two + {type NatMap.Nonempty}s. + * {Nonempty.unionsWith} for a version of this that takes a combining + function for values with the same key. + }} + +data.NatMap.Nonempty.unionsWith : + (a ->{g} a ->{g} a) + -> List.Nonempty (NatMap.Nonempty a) + ->{g} NatMap.Nonempty a +data.NatMap.Nonempty.unionsWith f = + List.Nonempty.foldMap (NatMap.Nonempty.unionWith f) id + +data.NatMap.Nonempty.unionsWith.doc : Doc +data.NatMap.Nonempty.unionsWith.doc = + use Nat + + use NatMap.Nonempty fromList + {{ + Constructs a {type NatMap.Nonempty} containing all the keys and values from a + {type List} of {type NatMap.Nonempty}s. + + If a key is present in multiple {type NatMap.Nonempty}s, the given combining + function is used to combine the values. + + # Examples + + ``` + NatMap.Nonempty.toList + (Nonempty.unionsWith + (+) + (fromList ((1, 10) +| [(2, 20)]) +| [fromList ((1, 30) +| [(3, 40)])])) + ``` + + # See also + + * {NatMap.Nonempty.unionWith} for a version of this that combines only two + {type NatMap.Nonempty}s. + * {NatMap.Nonempty.unions} for a version of this that doesn't take a + combining function and just ignores duplicate keys. + }} + +data.NatMap.Nonempty.unionWith : + (a ->{g} a ->{g} a) + -> NatMap.Nonempty a + -> NatMap.Nonempty a + ->{g} NatMap.Nonempty a +data.NatMap.Nonempty.unionWith f = NatMap.Nonempty.unionWithKey do x y -> f x y + +data.NatMap.Nonempty.unionWith.doc : Doc +data.NatMap.Nonempty.unionWith.doc = + use Nat + + use NatMap.Nonempty fromList + {{ + Constructs a {type NatMap.Nonempty} containing all the keys and values from + both of two {type NatMap.Nonempty}s. + + If a key is present in both {type NatMap.Nonempty}s, the given combining + function is used to combine the values. + + # Examples + + ``` + NatMap.Nonempty.toList + (NatMap.Nonempty.unionWith + (+) (fromList ((1, 10) +| [(2, 20)])) (fromList ((1, 30) +| [(3, 40)]))) + ``` + + # See also + + * {NatMap.Nonempty.union} for a version of this that doesn't take a + combining function and just ignores duplicate keys. + * {NatMap.Nonempty.unionWithKey} for a version of this that also gives the + key to the combining function. + * {Nonempty.unionsWith} for a version of this that combines a {type List} + of {type NatMap.Nonempty}s. + }} + +data.NatMap.Nonempty.unionWithKey : + (Nat ->{g} a ->{g} a ->{g} a) + -> NatMap.Nonempty a + -> NatMap.Nonempty a + ->{g} NatMap.Nonempty a +data.NatMap.Nonempty.unionWithKey f = cases + t1@(NatMap.Nonempty.Bin p1 m1 sz1 l1 r1), + t2@(NatMap.Nonempty.Bin p2 m2 sz2 l2 r2) + | shorter m1 m2 -> + if nomatch p2 p1 m1 then internal.join p1 t1 p2 t2 + else + if Nat.and p2 m1 Nat.== 0 then + NatMap.internal.bim + p1 m1 (data.NatMap.Nonempty.unionWithKey f l1 t2) r1 + else + NatMap.internal.bim + p1 m1 l1 (data.NatMap.Nonempty.unionWithKey f r1 t2) + | shorter m2 m1 -> + if nomatch p1 p2 m2 then internal.join p1 t1 p2 t2 + else + if Nat.and p1 m2 Nat.== 0 then + NatMap.internal.bim + p2 m2 (data.NatMap.Nonempty.unionWithKey f t1 l2) r2 + else + NatMap.internal.bim + p2 m2 l2 (data.NatMap.Nonempty.unionWithKey f t1 r2) + | p1 Nat.== p2 -> + NatMap.internal.bim + p1 + m1 + (data.NatMap.Nonempty.unionWithKey f l1 l2) + (data.NatMap.Nonempty.unionWithKey f r1 r2) + | otherwise -> internal.join p1 t1 p2 t2 + NatMap.Nonempty.Tip k v, t -> Nonempty.insertWithKey f k v t + t, NatMap.Nonempty.Tip k v -> + Nonempty.insertWithKey (k' x' y' -> f k' y' x') k v t + +data.NatMap.Nonempty.unionWithKey.doc : Doc +data.NatMap.Nonempty.unionWithKey.doc = + use NatMap.Nonempty fromList + {{ + Constructs a {type NatMap.Nonempty} containing all the keys and values from + both of two {type NatMap.Nonempty}s. + + If a key is present in both {type NatMap.Nonempty}s, the given combining + function is used to combine the values. The combining function is given the + key as well as the values from each {type NatMap.Nonempty}. + + # Examples + + ``` + NatMap.Nonempty.toList + (NatMap.Nonempty.unionWithKey + (k v1 v2 -> (if Nat.isEven k then v1 else v2)) + (fromList ((1, 10) +| [(2, 20), (3, 30)])) + (fromList ((1, 30) +| [(2, 40), (4, 40)]))) + ``` + + # See also + + * {NatMap.Nonempty.unionWith} for a version of this that doesn't give the + key to the combining function. + * {NatMap.Nonempty.union} for a version of this that doesn't take a + combining function and just ignores duplicate keys. + }} + +data.NatMap.Nonempty.update : + (a ->{g} Optional a) -> Nat -> NatMap.Nonempty a ->{g} NatMap a +data.NatMap.Nonempty.update f k t = Nonempty.updateWithKey (do x -> f x) k t + +data.NatMap.Nonempty.update.doc : Doc +data.NatMap.Nonempty.update.doc = + use Nat + + {{ + Updates or removes a value at a given key in a {type NatMap.Nonempty}. + Returns a (possibly empty) {type NatMap}. + + If the key is not present in the {type NatMap.Nonempty}, the map is returned + unchanged, but still cast to a {type NatMap}. + + If the key is present in the {type NatMap.Nonempty}, the given function is + applied to the value at that key. If the function returns {None}, the key is + removed from the {type NatMap}. Otherwise the value is updated with the + result of the function. + + # Examples + + ``` + NatMap.toList + (NatMap.Nonempty.update + (x -> (if Nat.isEven x then None else Some (x + 1))) + 1 + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20)]))) + ``` + + # See also + + * {Nonempty.updateWithKey} for a version of this that also gives the key to + the update function. + * {Nonempty.updateGetWithKey} for a version that gives the key to the + update function and returns the old value as well. + * {NatMap.Nonempty.alter} for a version that can also insert new values. + * {NatMap.Nonempty.map} for a version that can't add or remove values. + * {Nonempty.mapOptional} for a version that updates all values in the map. + * {Nonempty.updateMax} to update the value under the largest key. + * {Nonempty.updateMin} to update the value under the smallest key. + }} + +data.NatMap.Nonempty.updateGetWithKey : + (Nat ->{g} a ->{g} Optional a) + -> Nat + -> NatMap.Nonempty a + ->{g} (Optional a, NatMap a) +data.NatMap.Nonempty.updateGetWithKey f k = cases + t@(NatMap.Nonempty.Bin prefix mask size l r) + | nomatch k prefix mask -> (None, toNatMap t) + | Nat.and k mask Nat.== 0 -> + (found, l') = data.NatMap.Nonempty.updateGetWithKey f k l + (found, NatMap.internal.bin prefix mask l' (toNatMap r)) + | otherwise -> + (found, r') = data.NatMap.Nonempty.updateGetWithKey f k r + (found, NatMap.internal.bin prefix mask (toNatMap l) r') + t@(NatMap.Nonempty.Tip k' v) + | k Nat.== k' -> + match f k v with + Some v' -> (Some v, toNatMap (NatMap.Nonempty.Tip k v')) + None -> (Some v, NatMap.empty) + | otherwise -> (None, toNatMap t) + +data.NatMap.Nonempty.updateGetWithKey.doc : Doc +data.NatMap.Nonempty.updateGetWithKey.doc = + use Nat + + {{ + Updates or removes a value at a given key in a {type NatMap.Nonempty}, + returning the old value as well. Returns a (possibly empty) {type NatMap}. + + If the key is not present in the {type NatMap.Nonempty}, the map is returned + unchanged and {None} is returned for the value. + + If the key is present in the {type NatMap.Nonempty}, the given function is + applied to the value at that key. If the function returns {None}, the key is + removed from the {type NatMap}. Otherwise the value is updated with the + result of the function. + + # Examples + + ``` + Tuple.second + NatMap.toList + (Nonempty.updateGetWithKey + (k v -> (if Nat.isEven k then None else Some (v + 1))) + 1 + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20)]))) + ``` + + # See also + + * {Nonempty.updateWithKey} for a version of this that doesn't return the + old value. + }} + +data.NatMap.Nonempty.updateMax : + (a ->{g} a) -> NatMap.Nonempty a ->{g} NatMap.Nonempty a +data.NatMap.Nonempty.updateMax f = Nonempty.updateMaxWithKey do x -> f x + +data.NatMap.Nonempty.updateMax.doc : Doc +data.NatMap.Nonempty.updateMax.doc = + use Nat + + {{ + Updates the value under the largest key in a {type NatMap.Nonempty}. + + # Examples + + ``` + NatMap.Nonempty.toList + (Nonempty.updateMax + (x -> x + 1) (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20)]))) + ``` + + # See also + + * {Nonempty.updateMaxWithKey} for a version of this that also gives the key + to the update function. + * {Nonempty.updateMin} to update the value under the smallest key. + }} + +data.NatMap.Nonempty.updateMaxWithKey : + (Nat ->{g} a ->{g} a) -> NatMap.Nonempty a ->{g} NatMap.Nonempty a +data.NatMap.Nonempty.updateMaxWithKey f = cases + t@(NatMap.Nonempty.Bin p m sz l r) -> + use NatMap.Nonempty Tip + use NatMap.internal bim + up = cases + NatMap.Nonempty.Bin p m sz l r -> + r' = up r + bim p m l r' + Tip k v -> Tip k (f k v) + r' = up r + bim p m l r' + NatMap.Nonempty.Tip k v -> NatMap.Nonempty.Tip k (f k v) + +data.NatMap.Nonempty.updateMaxWithKey.doc : Doc +data.NatMap.Nonempty.updateMaxWithKey.doc = + use Nat + + {{ + Updates the value under the largest key in a {type NatMap.Nonempty}, giving + the key to the update function. + + If the {type NatMap.Nonempty} is empty, an {type Abort} is raised. + + # Examples + + ``` + NatMap.Nonempty.toList + (Nonempty.updateMaxWithKey + (k v -> k + v) (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20)]))) + ``` + + # See also + + * {Nonempty.updateMax} for a version of this that doesn't give the key to + the update function. + * {Nonempty.updateMinWithKey} to update the value under the smallest key. + }} + +data.NatMap.Nonempty.updateMin : + (a ->{g} a) -> NatMap.Nonempty a ->{g} NatMap.Nonempty a +data.NatMap.Nonempty.updateMin f = Nonempty.updateMinWithKey do x -> f x + +data.NatMap.Nonempty.updateMin.doc : Doc +data.NatMap.Nonempty.updateMin.doc = + use Nat + + {{ + Updates the value under the smallest key in a {type NatMap.Nonempty}. + + # Examples + + ``` + NatMap.Nonempty.toList + (Nonempty.updateMin + (x -> x + 1) (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20)]))) + ``` + + # See also + + * {Nonempty.updateMinWithKey} for a version of this that also gives the key + to the update function. + * {Nonempty.updateMax} to update the value under the largest key. + }} + +data.NatMap.Nonempty.updateMinWithKey : + (Nat ->{g} a ->{g} a) -> NatMap.Nonempty a ->{g} NatMap.Nonempty a +data.NatMap.Nonempty.updateMinWithKey f = cases + t@(NatMap.Nonempty.Bin p m sz l r) -> + use NatMap.Nonempty Tip + use NatMap.internal bim + up = cases + NatMap.Nonempty.Bin p m sz l r -> + l' = up l + bim p m l' r + Tip k v -> Tip k (f k v) + l' = up l + bim p m l' r + NatMap.Nonempty.Tip k v -> NatMap.Nonempty.Tip k (f k v) + +data.NatMap.Nonempty.updateMinWithKey.doc : Doc +data.NatMap.Nonempty.updateMinWithKey.doc = + use Nat + + {{ + Updates the value under the smallest key in a {type NatMap.Nonempty}, giving + the key to the update function. + + # Examples + + ``` + NatMap.Nonempty.toList + (Nonempty.updateMinWithKey + (k v -> k + v) (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20)]))) + ``` + + # See also + + * {Nonempty.updateMin} for a version of this that doesn't give the key to + the update function. + * {Nonempty.updateMaxWithKey} to update the value under the largest key. + }} + +data.NatMap.Nonempty.updateWithKey : + (Nat ->{g} a ->{g} Optional a) -> Nat -> NatMap.Nonempty a ->{g} NatMap a +data.NatMap.Nonempty.updateWithKey f k = cases + t@(NatMap.Nonempty.Bin prefix mask size l r) + | nomatch k prefix mask -> toNatMap t + | Nat.and k mask Nat.== 0 -> + NatMap.internal.bin + prefix mask (data.NatMap.Nonempty.updateWithKey f k l) (toNatMap r) + | otherwise -> + NatMap.internal.bin + prefix mask (toNatMap l) (data.NatMap.Nonempty.updateWithKey f k r) + t@(NatMap.Nonempty.Tip k' v) + | k Nat.== k' -> + match f k v with + None -> NatMap.empty + Some v' -> toNatMap (NatMap.Nonempty.Tip k v') + | otherwise -> toNatMap t + +data.NatMap.Nonempty.updateWithKey.doc : Doc +data.NatMap.Nonempty.updateWithKey.doc = + use Nat + + {{ + Updates or removes the value under a key in a {type NatMap.Nonempty}, using a + function. The function is given the key and the old value, and returns the + new value, or {None} to remove the key. + + Returns a {type NatMap} because the {type NatMap.Nonempty} may become empty. + + If the key is not present in the {type NatMap.Nonempty}, the map is + unchanged. + + # Examples + + ``` + NatMap.toList + (Nonempty.updateWithKey + (k v -> (if Nat.isEven k then None else Some (v + 1))) + 1 + (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20)]))) + ``` + + # See also + + * {Nonempty.updateGetWithKey} for a version of this that also returns the + old value. + * {NatMap.Nonempty.update} for a version of this that doesn't give the key + to the updating function. + }} + +data.NatMap.Nonempty.values : NatMap.Nonempty a -> List.Nonempty a +data.NatMap.Nonempty.values = + use Nonempty ++ + NatMap.Nonempty.foldMap (++) List.Nonempty.singleton + +data.NatMap.Nonempty.values.doc : Doc +data.NatMap.Nonempty.values.doc = + {{ + Returns the values in a {type NatMap.Nonempty} as a {type List.Nonempty}. + + # Examples + + ``` + NatMap.Nonempty.values (NatMap.Nonempty.fromList ((1, 10) +| [(2, 20)])) + ``` + + # See also + + * {NatMap.Nonempty.keys} to get the keys. + * {NatMap.Nonempty.toList} to get the key-value pairs. + }} + +data.NatMap.nth : Nat -> NatMap v -> Optional (Nat, v) +data.NatMap.nth index = cases + NatMap map -> Optional.flatMap (NatMap.Nonempty.nth index) map + +data.NatMap.nth.doc : Doc +data.NatMap.nth.doc = + use NatMap nth + {{ + {{ docExample 2 do i m -> nth i m }} returns the key-value pair in `m` with + the `i`-th smallest key, where `i`=0 is the smallest key (according to + {Universal.ordering}). + + Is the same as {{ + docExample 2 do i as -> List.at i (sortBy at1 (NatMap.toList as)) }} but + doesn't require instantiating the intermediate {type List}. + + ``` + s = + NatMap.fromList + [(6, "six"), (5, "five"), (4, "four"), (2, "two"), (1, "one")] + List.map (i -> nth i s) (List.range 0 (NatMap.size s)) + ``` + }} + +test> data.NatMap.nth.tests = + test.verify do + use Random natIn + Each.repeat 100 + s = + (List.replicate (natIn 0 20) do (natIn 0 20, natIn 0 10)) + |> NatMap.fromList + ensure + (List.somes + (List.map (i -> NatMap.nth i s) (List.range 0 (NatMap.size s))) + === NatMap.toList s) + +data.NatMap.partition : + (a ->{g} Boolean) -> NatMap a ->{g} (NatMap a, NatMap a) +data.NatMap.partition f = NatMap.partitionWithKey do x -> f x + +data.NatMap.partition.doc : Doc +data.NatMap.partition.doc = + {{ + Partitions a {type NatMap} into two {type NatMap}s, one containing the values + that satisfy the predicate and one containing the values that do not. + + # Example + + ``` + NatMap.partition Nat.isEven (NatMap.fromList [(1, 11), (2, 22), (3, 33)]) + ``` + + # See also + + * {NatMap.partitionWithKey} for a version of this that applies the + predicate to both the keys and values. + * {NatMap.filter} for a version that only keeps the values that satisfy the + predicate. + * {NatMap.mapEither} for partitioning into two {type NatMap}s based on an + {type Either}-valued function. + * {NatMap.split} for partitioning into two {type NatMap}s around a pivot + key. + }} + +data.NatMap.partitionWithKey : + (Nat ->{g} a ->{g} Boolean) -> NatMap a ->{g} (NatMap a, NatMap a) +data.NatMap.partitionWithKey f = cases + NatMap (Some t) -> Nonempty.partitionWithKey f t + NatMap None -> (NatMap.empty, NatMap.empty) + +data.NatMap.partitionWithKey.doc : Doc +data.NatMap.partitionWithKey.doc = + use Nat > + {{ + Partitions a {type NatMap} into two {type NatMap}s, one containing the + entries that satisfy the predicate and one containing the entries that do + not. + + # Example + + ``` + NatMap.partitionWithKey + (k v -> Nat.isEven k && v > 20) + (NatMap.fromList [(1, 11), (2, 22), (3, 33)]) + ``` + + # See also + + * {NatMap.partition} for a version of this that applies the predicate only + to the values. + * {NatMap.filterWithKey} for a version that only keeps the entries that + satisfy the predicate. + * {NatMap.mapEitherWithKey} for partitioning into two {type NatMap}s based + on an {type Either}-valued function. + }} + +data.NatMap.randomChoice : NatMap v ->{Exception, Random} (Nat, v) +data.NatMap.randomChoice map = + randomIndex = Random.natIn 0 (NatMap.size map) + NatMap.nth randomIndex map + |> Optional.toException + "NatMap.randomChoice: empty NatMap" (typeLink NatMap) + +data.NatMap.randomChoice.doc : Doc +data.NatMap.randomChoice.doc = + use NatMap fromList randomChoice + {{ + Picks a random key-value pair from the given {type NatMap}. Assumes that the + {type NatMap} is not empty, so an empty {type NatMap} will cause a runtime + exception. + + # Examples + + ``` + catch do + lcg 4096 do randomChoice (fromList [(0, ?a), (3, ?b), (5, ?c), (7, ?d)]) + ``` + + ``` + catch do + lcg 2510 do randomChoice (fromList [(0, ?a), (3, ?b), (5, ?c), (7, ?d)]) + ``` + }} + +test> data.NatMap.randomChoice.test = test.verify do + map = NatMap.fromList [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)] + Each.repeat 1000 + e = NatMap.randomChoice map + ensure (NatMap.contains (at1 e) map) + +data.NatMap.randomKey : NatMap v ->{Exception, Random} Nat +data.NatMap.randomKey map = NatMap.randomChoice map |> at1 + +data.NatMap.randomKey.doc : Doc +data.NatMap.randomKey.doc = + use NatMap fromList randomKey + {{ + Picks a random {type Nat} key from the given {type NatMap}. Assumes that the + {type NatMap} is not empty, so an empty {type NatMap} will raise an + {type Exception}. + + # Examples + + ``` + catch do + lcg 4096 do randomKey (fromList [(0, ?a), (3, ?b), (5, ?c), (7, ?d)]) + ``` + + ``` + catch do + lcg 2510 do randomKey (fromList [(0, ?a), (3, ?b), (5, ?c), (7, ?d)]) + ``` + }} + +data.NatMap.randomValue : NatMap v ->{Exception, Random} v +data.NatMap.randomValue map = NatMap.randomChoice map |> at2 + +data.NatMap.randomValue.doc : Doc +data.NatMap.randomValue.doc = + use NatMap fromList randomValue + {{ + Picks a random value from the given {type NatMap}. Assumes that the + {type NatMap} is not empty, so an empty {type NatMap} will raise an + {type Exception}. + + # Examples + + ``` + catch do + lcg 4096 do randomValue (fromList [(0, ?a), (3, ?b), (5, ?c), (7, ?d)]) + ``` + + ``` + catch do + lcg 2510 do randomValue (fromList [(0, ?a), (3, ?b), (5, ?c), (7, ?d)]) + ``` + }} + +data.NatMap.restrict : Nat -> Nat -> NatMap v -> NatMap v +data.NatMap.restrict min max = cases + NatMap o -> Optional.fold (do NatMap.empty) (Nonempty.restrict min max) o + +data.NatMap.restrict.doc : Doc +data.NatMap.restrict.doc = + use NatMap fromList toList + {{ + Restricts a {type NatMap} to a given range of keys. + + `` NatMap.restrict min max map `` drops all entries from `map` that are + outside the range between `min` and `max`. + + The range is inclusive of the `min` and exclusive of the `max`. That is, a + key that is exactly `min` will be kept, but a key that is exactly `max` will + be removed. + + # Examples + + ``` + toList + (NatMap.restrict + 2 4 (fromList [(1, ?a), (2, ?b), (3, ?c), (4, ?d), (5, ?e)])) + ``` + + ``` + toList (NatMap.restrict 2 4 (fromList [(1, ?a), (3, ?c), (5, ?e)])) + ``` + + # See also + + * {NatMap.restrictAbove} to keep only keys above a given key. + * {NatMap.restrictBelow} to keep only keys below a given key. + * {Nonempty.restrict} for the version of this that operates on a + {type NatMap.Nonempty}. + }} + +data.NatMap.restrictAbove : Nat -> NatMap v -> NatMap v +data.NatMap.restrictAbove k = cases + NatMap o -> Optional.fold (do NatMap.empty) (Nonempty.restrictAbove k) o + +data.NatMap.restrictAbove.doc : Doc +data.NatMap.restrictAbove.doc = + use NatMap fromList toList + {{ + Restricts a {type NatMap} to keys that are strictly above a given key. + + `` NatMap.restrictAbove k map `` drops all entries from `map` that are less + than or equal to `k`. + + # Examples + + ``` + toList + (NatMap.restrictAbove + 2 (fromList [(1, ?a), (2, ?b), (3, ?c), (4, ?d), (5, ?e)])) + ``` + + ``` + toList + (NatMap.restrictAbove + 2 (fromList [(1, ?a), (2, ?b), (3, ?c), (4, ?d), (5, ?e)])) + ``` + + # See also + + * {NatMap.restrict} to restrict to a range of keys. + * {NatMap.restrictBelow} to keep only keys below a given key. + * {Nonempty.restrictAbove} for the version of this that operates on a + {type NatMap.Nonempty}. + }} + +data.NatMap.restrictBelow : Nat -> NatMap v -> NatMap v +data.NatMap.restrictBelow k = cases + NatMap o -> Optional.fold (do NatMap.empty) (Nonempty.restrictBelow k) o + +data.NatMap.restrictBelow.doc : Doc +data.NatMap.restrictBelow.doc = + use NatMap fromList toList + {{ + Restricts a {type NatMap} to keys that are strictly below a given key. + + `` NatMap.restrictBelow k map `` drops all entries from `map` that are + greater than or equal to `k`. + + # Examples + + ``` + toList + (NatMap.restrictBelow + 4 (fromList [(1, ?a), (2, ?b), (3, ?c), (4, ?d), (5, ?e)])) + ``` + + ``` + toList + (NatMap.restrictBelow + 4 (fromList [(1, ?a), (2, ?b), (3, ?c), (4, ?d), (5, ?e)])) + ``` + + # See also + + * {NatMap.restrict} to restrict to a range of keys. + * {NatMap.restrictAbove} to keep only keys above a given key. + * {Nonempty.restrictBelow} for the version of this that operates on a + {type NatMap.Nonempty}. + }} + +data.NatMap.singleton : Nat -> a -> NatMap.Nonempty a +data.NatMap.singleton k v = NatMap.Nonempty.Tip k v + +data.NatMap.singleton.doc : Doc +data.NatMap.singleton.doc = + use NatMap singleton + {{ + Creates a {type NatMap.Nonempty} with a single entry. + + # Example + + ``` + NatMap.toNonemptyList (singleton 1 10) + ``` + + # See also + + * {NatMap.fromList} to create a {type NatMap} from a list of entries. + * {NatMap.Nonempty.fromList} to create a {type NatMap.Nonempty} from a + {type List.Nonempty}. + * {NatMap.empty} to create an empty {type NatMap}. + * {singleton} to create a {type NatMap.Nonempty} with a single entry. + }} + +data.NatMap.size : NatMap a -> Nat +data.NatMap.size = cases + NatMap (Some t) -> NatMap.Nonempty.size t + NatMap None -> 0 + +data.NatMap.size.doc : Doc +data.NatMap.size.doc = + {{ + Returns the number of entries in the {type NatMap}. + + # Example + + ``` + NatMap.size (NatMap.fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + # See also + + * {NatMap.isEmpty} to check if a {type NatMap} is empty. + }} + +test> data.NatMap.size.test = test.verify do + use Random nat + len = Random.natIn 0 1000 + kvs = Random.listOf (do (nat(), nat)) do len + m = NatMap.fromList kvs + ensureEqual (NatMap.size m) (List.size (NatMap.toList m)) + +data.NatMap.split : Nat -> NatMap a -> (NatMap a, Optional a, NatMap a) +data.NatMap.split k = cases + NatMap (Some t) -> NatMap.Nonempty.split k t + NatMap None -> (NatMap.empty, None, NatMap.empty) + +data.NatMap.split.doc : Doc +data.NatMap.split.doc = + {{ + Splits a {type NatMap} into three parts based on the given key. The first + part contains all entries with keys less than the given key, the second part + contains the entry with that key (if present) and the third part contains all + entries with keys greater than the key. + + # Example + + ``` + NatMap.split 2 (NatMap.fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + # See also + + * {NatMap.partition} to partition a {type NatMap} into two parts based on a + predicate. + }} + +data.NatMap.submapCompareBy : + (a ->{g} a ->{g} Boolean) -> NatMap a -> NatMap a ->{g} Optional Ordering +data.NatMap.submapCompareBy f = cases + NatMap (Some t1), NatMap (Some t2) -> Nonempty.submapCompareBy f t1 t2 + NatMap None, NatMap None -> Some Equal + NatMap None, NatMap (Some _) -> Some Less + NatMap (Some _), NatMap None -> Some Greater + +data.NatMap.submapCompareBy.doc : Doc +data.NatMap.submapCompareBy.doc = + use Nat == + use NatMap fromList submapCompareBy + {{ + Checks if the first {type NatMap} is a submap of the second one, using the + given function to compare the values. + + This function returns {Less} if the second {type NatMap} contains all the + keys of the first {type NatMap} and comparing the values under those keys + using the given function returns `` true `` for all of them. + + It returns {Equal} if the two {type NatMap}s contain the same keys and + comparing the values under those keys using the given function returns `` + true `` for all of them. + + It returns {Greater} if the first {type NatMap} contains all the keys of the + second {type NatMap} and comparing the values under those keys using the + given function returns `` true `` for all of them. + + It returns {None} if the two {type NatMap}s contain different keys or + comparing the values under those keys using the given function returns `` + false `` for any of them. + + # Examples + + ``` + submapCompareBy + (==) (fromList [(1, 10), (2, 20)]) (fromList [(1, 10), (2, 20), (3, 30)]) + ``` + + ``` + submapCompareBy + (==) (fromList [(1, 10), (2, 20)]) (fromList [(1, 10), (2, 20)]) + ``` + + ``` + submapCompareBy + (==) (fromList [(1, 10), (2, 20)]) (fromList [(1, 10), (2, 30)]) + ``` + + # See also + + * {NatMap.isSubmapOfBy} for a version of this that returns a {type Boolean} + instead of an {type Ordering}. + * {NatMap.isSubmapOf} returns a {type Boolean} and uses {===} to compare + the values. + }} + +test> data.NatMap.test.diff = + runs 100 do + use Set == + use gen listOf nat + xs = listOf (pairOf nat do ()) () + ys = listOf (pairOf nat do ()) () + diff = + Set.fromList + (NatMap.toList + (NatMap.difference (NatMap.fromList xs) (NatMap.fromList ys))) + setDiff = Set.deletes ys (Set.fromList xs) + if diff == setDiff then expect true + else bug (xs, ys, Set.toList diff, Set.toList setDiff) + +data.NatMap.test.gen : '{Gen} t -> '{Gen} NatMap t +data.NatMap.test.gen g = do NatMap.fromList (gen.listOf (pairOf gen.nat g) ()) + +test> data.NatMap.test.insertDelete = runs 100 do + use Nat == + use gen nat + key = nat() + value = nat() + map = NatMap.test.gen nat () + present = Boolean.not (NatMap.contains key map) + added = NatMap.insert.nonempty key value map + removed = NatMap.Nonempty.delete key added + p = implies present (NatMap.equalBy (==) map removed) + if p then expect p else bug (map, key, value, present, added, removed) + +test> data.NatMap.test.single = runs 100 do + use Nat == + use gen nat + key = nat() + value = nat() + map1 = NatMap.singleton key value + map2 = NatMap.insert.nonempty key value NatMap.empty + expect (Nonempty.equalBy (==) map1 map2) + +test> data.NatMap.test.unionAssociative = + runs 100 do + use Nat == + use NatMap union + use NatMap.test gen + use gen nat + map1 = gen nat () + map2 = gen nat () + map3 = gen nat () + expect + (NatMap.equalBy + (==) (union map1 (union map2 map3)) (union (union map1 map2) map3)) + +test> data.NatMap.test.unionCommutative = + runs 100 do + use Nat == + use NatMap.test gen + use gen nat + map1 = gen nat () + map2 = gen nat () + expect + (NatMap.equalBy + (==) (NatMap.union map1 map2) (NatMap.unionWith (flip const) map2 map1)) + +test> data.NatMap.test.unionInsert = + runs 100 do + use Nat == + use gen nat + key = nat() + value = nat() + map = NatMap.test.gen nat () + expect + (NatMap.equalBy + (==) + (toNatMap (NatMap.insert.nonempty key value map)) + (NatMap.union (toNatMap (NatMap.singleton key value)) map)) + +test> data.NatMap.test.updateDelete = runs 100 do + use Nat == + use gen nat + key = nat() + map = NatMap.test.gen nat () + updated = NatMap.update (const None) key map + deleted = NatMap.delete key map + p = NatMap.equalBy (==) updated deleted + if p then expect p else bug (map, key, updated, deleted) + +data.NatMap.toList : NatMap a -> [(Nat, a)] +data.NatMap.toList = + use List +: + NatMap.foldWithKey (k v ks -> (k, v) +: ks) [] + +data.NatMap.toList.doc : Doc +data.NatMap.toList.doc = + use NatMap fromList toList + {{ + {toList} converts a {type NatMap} to a {type List} of {type Tuple}s + containing the keys and values. + + # Example + + ``` + toList (fromList [(1, 10), (2, 20)]) + ``` + + # See also + + * {fromList} for converting a {type List} of {type Tuple}s to a + {type NatMap}. + }} + +data.NatMap.toMap : NatMap a -> Map Nat a +data.NatMap.toMap = NatMap.foldWithKey (k v m -> Map.insert k v m) Map.empty + +data.NatMap.toNonemptyList : NatMap.Nonempty a -> List.Nonempty (Nat, a) +data.NatMap.toNonemptyList = cases + NatMap.Nonempty.Tip k v -> List.Nonempty.singleton (k, v) + NatMap.Nonempty.Bin _ _ _ t1 t2 -> + data.NatMap.toNonemptyList t1 Nonempty.++ data.NatMap.toNonemptyList t2 + +data.NatMap.union : NatMap a -> NatMap a -> NatMap a +data.NatMap.union = NatMap.unionWith const + +data.NatMap.union.doc : Doc +data.NatMap.union.doc = + use NatMap fromList + {{ + Constructs a {type NatMap} containing all the keys and values from both of + two {type NatMap}s. + + This function is biased towards the first {type NatMap}. If a key is present + in both {type NatMap}s, the value from the first {type NatMap} is used. + + # Examples + + ``` + NatMap.toList + (NatMap.union (fromList [(1, 10), (2, 20)]) (fromList [(1, 30), (3, 40)])) + ``` + + # See also + + * {NatMap.unionWith} for a version of this that takes a combining function + for values with the same key. + * {NatMap.unions} for combining a {type List} of {type NatMap}s. + * {NatMap.intersect} to keep only the values that are in both + {type NatMap}s. + * {NatMap.difference} to keep the values that are only in the first + {type NatMap}. + }} + +data.NatMap.unions : [NatMap a] -> NatMap a +data.NatMap.unions = NatMap.unionsWith const + +data.NatMap.unions.doc : Doc +data.NatMap.unions.doc = + use NatMap fromList + {{ + Constructs a {type NatMap} containing all the keys and values from a + {type List} of {type NatMap}s. + + This function is biased towards the first {type NatMap} in the {type List}. + If a key is present in multiple {type NatMap}s, the value from the first + {type NatMap} is used. + + # Examples + + ``` + NatMap.toList + (NatMap.unions [fromList [(1, 10), (2, 20)], fromList [(1, 30), (3, 40)]]) + ``` + + # See also + + * {NatMap.union} for a version of this that combines only two + {type NatMap}s. + * {NatMap.unionsWith} for a version of this that takes a combining function + for values with the same key. + }} + +data.NatMap.unionsWith : (a ->{g} a ->{g} a) -> [NatMap a] ->{g} NatMap a +data.NatMap.unionsWith f = List.foldLeft (NatMap.unionWith f) NatMap.empty + +data.NatMap.unionsWith.doc : Doc +data.NatMap.unionsWith.doc = + use Nat + + use NatMap fromList + {{ + Constructs a {type NatMap} containing all the keys and values from a + {type List} of {type NatMap}s. + + If a key is present in multiple {type NatMap}s, the given combining function + is used to combine the values. + + # Examples + + ``` + NatMap.toList + (NatMap.unionsWith + (+) [fromList [(1, 10), (2, 20)], fromList [(1, 30), (3, 40)]]) + ``` + + # See also + + * {NatMap.unionWith} for a version of this that combines only two + {type NatMap}s. + * {NatMap.unions} for a version of this that doesn't take a combining + function and just ignores duplicate keys. + }} + +data.NatMap.unionWith : + (a ->{g} a ->{g} a) -> NatMap a -> NatMap a ->{g} NatMap a +data.NatMap.unionWith f = NatMap.unionWithKey do x y -> f x y + +data.NatMap.unionWith.doc : Doc +data.NatMap.unionWith.doc = + use Nat + + use NatMap fromList + {{ + Constructs a {type NatMap} containing all the keys and values from both of + two {type NatMap}s. + + If a key is present in both {type NatMap}s, the given combining function is + used to combine the values. + + # Examples + + ``` + NatMap.toList + (NatMap.unionWith + (+) (fromList [(1, 10), (2, 20)]) (fromList [(1, 30), (3, 40)])) + ``` + + # See also + + * {NatMap.union} for a version of this that doesn't take a combining + function and just ignores duplicate keys. + * {NatMap.unionWithKey} for a version of this that also gives the key to + the combining function. + * {NatMap.unionsWith} for a version of this that combines a {type List} of + {type NatMap}s. + }} + +data.NatMap.unionWithKey : + (Nat ->{g} a ->{g} a ->{g} a) -> NatMap a -> NatMap a ->{g} NatMap a +data.NatMap.unionWithKey f = cases + NatMap (Some t1), NatMap (Some t2) -> + toNatMap (NatMap.Nonempty.unionWithKey f t1 t2) + NatMap None, NatMap t2 -> NatMap t2 + NatMap t1, NatMap None -> NatMap t1 + +data.NatMap.unionWithKey.doc : Doc +data.NatMap.unionWithKey.doc = + use NatMap fromList + {{ + Constructs a {type NatMap} containing all the keys and values from both of + two {type NatMap}s. + + If a key is present in both {type NatMap}s, the given combining function is + used to combine the values. The combining function is given the key as well + as the values from each {type NatMap}. + + # Examples + + ``` + NatMap.toList + (NatMap.unionWithKey + (k v1 v2 -> (if Nat.isEven k then v1 else v2)) + (fromList [(1, 10), (2, 20), (3, 30)]) + (fromList [(1, 30), (2, 40), (4, 40)])) + ``` + + # See also + + * {NatMap.unionWith} for a version of this that doesn't give the key to the + combining function. + * {NatMap.union} for a version of this that doesn't take a combining + function and just ignores duplicate keys. + }} + +data.NatMap.update : (a ->{g} Optional a) -> Nat -> NatMap a ->{g} NatMap a +data.NatMap.update f k t = NatMap.updateWithKey (do x -> f x) k t + +data.NatMap.update.doc : Doc +data.NatMap.update.doc = + use Nat + + {{ + Updates or removes a value at a given key in a {type NatMap}. + + If the key is not present in the {type NatMap}, the {type NatMap} is returned + unchanged. + + If the key is present in the {type NatMap}, the given function is applied to + the value at that key. If the function returns {None}, the key is removed + from the {type NatMap}. Otherwise the value is updated with the result of the + function. + + # Examples + + ``` + NatMap.toList + (NatMap.update + (x -> (if Nat.isEven x then None else Some (x + 1))) + 1 + (NatMap.fromList [(1, 10), (2, 20)])) + ``` + + # See also + + * {NatMap.updateWithKey} for a version of this that also gives the key to + the update function. + * {NatMap.updateGetWithKey} for a version that gives the key to the update + function and returns the old value as well. + * {NatMap.alter} for a version that can also insert new values. + * {NatMap.map} for a version that can't add or remove values. + * {NatMap.mapOptional} for a version that updates all values in the + {type NatMap}. + * {NatMap.updateMax} to update the value under the largest key. + * {NatMap.updateMin} to update the value under the smallest key. + }} + +data.NatMap.updateGetWithKey : + (Nat ->{g} a ->{g} Optional a) + -> Nat + -> NatMap a + ->{g} (Optional a, NatMap a) +data.NatMap.updateGetWithKey f k = cases + NatMap (Some t) -> Nonempty.updateGetWithKey f k t + NatMap None -> (None, NatMap.empty) + +data.NatMap.updateGetWithKey.doc : Doc +data.NatMap.updateGetWithKey.doc = + use Nat + + {{ + Updates or removes a value at a given key in a {type NatMap}, returning the + old value as well. + + If the key is not present in the {type NatMap}, the {type NatMap} is returned + unchanged and {None} is returned for the value. + + If the key is present in the {type NatMap}, the given function is applied to + the value at that key. If the function returns {None}, the key is removed + from the {type NatMap}. Otherwise the value is updated with the result of the + function. + + # Examples + + ``` + Tuple.second + NatMap.toList + (NatMap.updateGetWithKey + (k v -> (if Nat.isEven k then None else Some (v + 1))) + 1 + (NatMap.fromList [(1, 10), (2, 20)])) + ``` + + # See also + + * {NatMap.updateWithKey} for a version of this that doesn't return the old + value. + }} + +data.NatMap.updateMax : (a ->{g} a) -> NatMap a ->{g, Abort} NatMap a +data.NatMap.updateMax f = NatMap.updateMaxWithKey do x -> f x + +data.NatMap.updateMax.doc : Doc +data.NatMap.updateMax.doc = + use Nat + + {{ + Updates the value under the largest key in a {type NatMap}. + + If the {type NatMap} is empty, an {type Abort} is raised. + + # Examples + + ``` + toOptional! do + NatMap.toList + (NatMap.updateMax (x -> x + 1) (NatMap.fromList [(1, 10), (2, 20)])) + ``` + + # See also + + * {NatMap.updateMaxWithKey} for a version of this that also gives the key + to the update function. + * {NatMap.updateMin} to update the value under the smallest key. + }} + +data.NatMap.updateMaxWithKey : + (Nat ->{g} a ->{g} a) -> NatMap a ->{g, Abort} NatMap a +data.NatMap.updateMaxWithKey f = cases + NatMap (Some t) -> toNatMap (Nonempty.updateMaxWithKey f t) + NatMap None -> abort + +data.NatMap.updateMaxWithKey.doc : Doc +data.NatMap.updateMaxWithKey.doc = + use Nat + + {{ + Updates the value under the largest key in a {type NatMap}, giving the key to + the update function. + + If the {type NatMap} is empty, an {type Abort} is raised. + + # Examples + + ``` + toOptional! do + NatMap.toList + (NatMap.updateMaxWithKey + (k v -> k + v) (NatMap.fromList [(1, 10), (2, 20)])) + ``` + + # See also + + * {NatMap.updateMax} for a version of this that doesn't give the key to the + update function. + * {NatMap.updateMinWithKey} to update the value under the smallest key. + }} + +data.NatMap.updateMin : (a ->{g} a) -> NatMap a ->{g, Abort} NatMap a +data.NatMap.updateMin f = NatMap.updateMinWithKey do x -> f x + +data.NatMap.updateMin.doc : Doc +data.NatMap.updateMin.doc = + use Nat + + {{ + Updates the value under the smallest key in a {type NatMap}. + + If the {type NatMap} is empty, an {type Abort} is raised. + + # Examples + + ``` + toOptional! do + NatMap.toList + (NatMap.updateMin (x -> x + 1) (NatMap.fromList [(1, 10), (2, 20)])) + ``` + + # See also + + * {NatMap.updateMinWithKey} for a version of this that also gives the key + to the update function. + * {NatMap.updateMax} to update the value under the largest key. + }} + +data.NatMap.updateMinWithKey : + (Nat ->{g} a ->{g} a) -> NatMap a ->{g, Abort} NatMap a +data.NatMap.updateMinWithKey f = cases + NatMap (Some t) -> toNatMap (Nonempty.updateMinWithKey f t) + NatMap None -> abort + +data.NatMap.updateMinWithKey.doc : Doc +data.NatMap.updateMinWithKey.doc = + use Nat + + {{ + Updates the value under the smallest key in a {type NatMap}, giving the key + to the update function. + + If the {type NatMap} is empty, an {type Abort} is raised. + + # Examples + + ``` + toOptional! do + NatMap.toList + (NatMap.updateMinWithKey + (k v -> k + v) (NatMap.fromList [(1, 10), (2, 20)])) + ``` + + # See also + + * {NatMap.updateMin} for a version of this that doesn't give the key to the + update function. + * {NatMap.updateMaxWithKey} to update the value under the largest key. + }} + +data.NatMap.updateWithKey : + (Nat ->{g} a ->{g} Optional a) -> Nat -> NatMap a ->{g} NatMap a +data.NatMap.updateWithKey f k = cases + NatMap (Some t) -> Nonempty.updateWithKey f k t + NatMap None -> NatMap.empty + +data.NatMap.updateWithKey.doc : Doc +data.NatMap.updateWithKey.doc = + use Nat + + {{ + Updates or removes the value under a key in a {type NatMap}, using a + function. The function is given the key and the old value, and returns the + new value, or {None} to remove the key. + + If the key is not present in the {type NatMap}, the {type NatMap} is + unchanged. + + # Examples + + ``` + NatMap.toList + (NatMap.updateWithKey + (k v -> (if Nat.isEven k then None else Some (v + 1))) + 1 + (NatMap.fromList [(1, 10), (2, 20)])) + ``` + + # See also + + * {NatMap.updateGetWithKey} for a version of this that also returns the old + value. + * {NatMap.update} for a version of this that doesn't give the key to the + updating function. + }} + +data.NatMap.values : NatMap a -> [a] +data.NatMap.values = + use List +: + NatMap.foldWithKey (const (+:)) [] + +data.NatMap.values.doc : Doc +data.NatMap.values.doc = + {{ + Returns the values in a {type NatMap} as a list. + + # Examples + + ``` + NatMap.values (NatMap.fromList [(1, 10), (2, 20)]) + ``` + + # See also + + * {NatMap.keys} to get the keys. + * {NatMap.toList} to get the key-value pairs. + }} + +(data.NatSet.==) : NatSet -> NatSet -> Boolean +(data.NatSet.==) = cases + NatSet (Some t1), NatSet (Some t2) -> t1 NatSet.Nonempty.== t2 + NatSet None, NatSet None -> true + _, _ -> false + +data.NatSet.all : (Nat ->{g} Boolean) ->{g} NatSet ->{g} Boolean +data.NatSet.all p = NatSet.foldRight (x b -> p x && b) true + +data.NatSet.all.doc : Doc +data.NatSet.all.doc = + use Nat isEven + use NatSet all fromList + {{ + Determines whether all {type Nat}s in a {type NatSet} satisfy a predicate. + + # Examples + + ``` + all isEven (fromList [1, 2, 3, 4]) + ``` + + ``` + all isEven (fromList [2, 4, 6]) + ``` + + # See also + + * {NatSet.any} to determine whether any {type Nat} in a {type NatSet} + satisfies a predicate. + }} + +test> data.NatSet.all.test = test.verify do + use Nat isEven + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + s = NatSet.fromList xs + ensure (iff (NatSet.all isEven s) (List.all isEven xs)) + +data.NatSet.alter : (Boolean ->{g} Boolean) -> Nat -> NatSet ->{g} NatSet +data.NatSet.alter f k = cases + NatSet (Some t) -> NatSet.Nonempty.alter f k t + NatSet None -> + if f false then NatSet (Some (NatSet.singleton k)) else NatSet None + +data.NatSet.alter.doc : Doc +data.NatSet.alter.doc = + use NatSet alter + {{ + Inserts or deletes an element from a {type NatSet} depending on the result of + a function. + + The function receives a {type Boolean} indicating whether the element is + present in the {type NatSet} and returns a {type Boolean} indicating whether + the element should be present in the result. The function is allowed to use + any [ability](https://unison-lang.org/docs/abilities) it needs. + + # Examples + + Insert an element regardless: + + @typecheck ``` + alter (const true) + ``` + + Delete an element regardless: + + @typecheck ``` + alter (const false) + ``` + + Toggle an element: + + @typecheck ``` + alter Boolean.not + ``` + + # See also + + * {NatSet.insert.nonempty} to insert an element into a {type NatSet}. + * {NatSet.delete} to delete an element from a {type NatSet}. + * {NatSet.filter} to delete all elements from a {type NatSet} that do not + satisfy a predicate. + }} + +test> data.NatSet.alter.test = + test.verify do + use NatSet == alter delete + use NatSet.insert nonempty + use Random nat + _ = Each.range 0 100 + xs = Random.listOf nat do Random.natIn 0 100 + n = nat() + s = NatSet.fromList xs + ensure (alter (const true) n s == NatSet (Some (nonempty n s))) + ensure (alter (const false) n s == delete n s) + ensure + (alter Boolean.not n s + == (if NatSet.contains n s then delete n s + else NatSet (Some (nonempty n s)))) + +data.NatSet.any : (Nat ->{g} Boolean) ->{g} NatSet ->{g} Boolean +data.NatSet.any p = NatSet.foldRight (x b -> p x || b) false + +data.NatSet.any.doc : Doc +data.NatSet.any.doc = + use Nat isEven + use NatSet any fromList + {{ + Determines whether any {type Nat} in a {type NatSet} satisfies a predicate. + + # Examples + + ``` + any isEven (fromList [1, 2, 3, 4]) + ``` + + ``` + any isEven (fromList [1, 3, 5]) + ``` + + # See also + + * {NatSet.all} to determine whether all {type Nat}s in a {type NatSet} + satisfy a predicate. + }} + +test> data.NatSet.any.test = test.verify do + use Nat isEven + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 1000) do natIn 0 100 + s = NatSet.fromList xs + ensure (iff (NatSet.any isEven s) (List.any isEven xs)) + +data.NatSet.contains : Nat -> NatSet -> Boolean +data.NatSet.contains k = cases + NatSet (Some t) -> NatSet.Nonempty.contains k t + NatSet None -> false + +data.NatSet.contains.doc : Doc +data.NatSet.contains.doc = + use NatSet contains fromList + {{ + Checks if a {type NatSet} contains a particular element. + + # Examples + + ``` + contains 1 (fromList [1, 2, 3]) + ``` + + ``` + contains 4 (fromList [1, 2, 3]) + ``` + + # See also + + * {NatSet.size} to get the number of elements in a {type NatSet}. + * {NatSet.isEmpty} to check if a {type NatSet} is empty. + }} + +test> data.NatSet.contains.test = test.verify do + use Random nat + _ = Each.range 0 100 + xs = Random.listOf nat do Random.natIn 0 100 + s = NatSet.fromList xs + n = nat() + ensure (NatSet.Nonempty.contains n (NatSet.insert.nonempty n s)) + ensure (Boolean.not (NatSet.contains n (NatSet.delete n s))) + +data.NatSet.delete : Nat -> NatSet -> NatSet +data.NatSet.delete k = cases + NatSet (Some t) -> NatSet.Nonempty.delete k t + NatSet None -> NatSet.empty + +data.NatSet.delete.doc : Doc +data.NatSet.delete.doc = + {{ + Deletes an element from a {type NatSet}. + + # Example + + ``` + NatSet.toList (NatSet.delete 1 (NatSet.fromList [1, 2])) + ``` + + # See also + + * {NatSet.difference} to delete a whole {type NatSet} from another. + * {NatSet.intersect} to delete all elements from a {type NatSet} that are + not in another. + * {NatSet.Nonempty.delete} to delete an element from a + {type NatSet.Nonempty}. + * {NatSet.insert.nonempty} to insert an element into a {type NatSet}. + }} + +data.NatSet.deleteMax : NatSet -> NatSet +data.NatSet.deleteMax = cases + NatSet (Some t) -> NatSet.Nonempty.deleteMax t + NatSet None -> NatSet None + +data.NatSet.deleteMax.doc : Doc +data.NatSet.deleteMax.doc = + {{ + Returns the set without the maximum element. + + # Example + + ``` + NatSet.toList (NatSet.deleteMax (NatSet.fromList [1, 2, 3, 4])) + ``` + + # See also + + * {NatSet.deleteMin} to get the set without the minimum element. + * {NatSet.delete} to get the set without a specific element. + * {NatSet.maxView} to get the maximum element and the set without that + element. + }} + +test> data.NatSet.deleteMax.test = + test.verify do + use Nat != + use NatSet fromList + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 1 100 + r = + toOptional! do + NatSet.deleteMax (fromList xs) + === fromList + (List.filter + (flip (!=) (Optional.toAbort (List.maximum xs))) xs) + ensure (r === Some true) + +data.NatSet.deleteMin : NatSet -> NatSet +data.NatSet.deleteMin = cases + NatSet (Some t) -> NatSet.Nonempty.deleteMin t + NatSet None -> NatSet None + +data.NatSet.deleteMin.doc : Doc +data.NatSet.deleteMin.doc = + {{ + Returns the set without the minimum element. + + # Example + + ``` + NatSet.toList (NatSet.deleteMin (NatSet.fromList [1, 2, 3, 4])) + ``` + + # See also + + * {NatSet.deleteMax} to get the set without the maximum element. + * {NatSet.delete} to get the set without a specific element. + * {NatSet.minView} to get the minimum element and the set without that + element. + }} + +test> data.NatSet.deleteMin.test = + test.verify do + use Nat != + use NatSet fromList + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 1 100 + r = + toOptional! do + NatSet.deleteMin (fromList xs) + === fromList + (List.filter + (flip (!=) (Optional.toAbort (List.minimum xs))) xs) + ensure (r === Some true) + +data.NatSet.difference : NatSet -> NatSet -> NatSet +data.NatSet.difference = cases + NatSet (Some t1), NatSet (Some t2) -> NatSet.Nonempty.difference t1 t2 + NatSet None, NatSet (Some t2) -> NatSet None + NatSet (Some t1), NatSet None -> NatSet (Some t1) + _, _ -> NatSet None + +data.NatSet.difference.doc : Doc +data.NatSet.difference.doc = + use NatSet fromList + {{ + Removes all elements from one {type NatSet} that are in another. If an + element is present in the first input but not the second, it will be present + in the result. + + # Example + + ``` + NatSet.toList (NatSet.difference (fromList [1, 2]) (fromList [2, 3])) + ``` + + # See also + + * {NatSet.union} to add all elements from a {type NatSet} that are in + another. + * {NatSet.intersect} to remove all elements from a {type NatSet} that are + not in another. + * {NatSet.delete} to remove one element from a {type NatSet}. + }} + +test> data.NatSet.difference.test = + test.verify do + use Random listOf nat natIn + _ = Each.range 0 100 + xs = listOf nat do natIn 0 100 + ys = listOf nat do natIn 0 100 + ensureEqual + (NatSet.toList + (NatSet.difference (NatSet.fromList xs) (NatSet.fromList ys))) + (Set.toList (Set.deletes ys (Set.fromList xs))) + +data.NatSet.disjoint : NatSet -> NatSet -> Boolean +data.NatSet.disjoint t1 t2 = NatSet.isEmpty (NatSet.intersect t1 t2) + +data.NatSet.disjoint.doc : Doc +data.NatSet.disjoint.doc = + use NatSet disjoint fromList + {{ + Checks if two {type NatSet} values have no elements in common. + + # Examples + + Two sets with no elements in common: + + ``` + disjoint (fromList [1, 2]) (fromList [3, 4]) + ``` + + Two sets with elements in common: + + ``` + disjoint (fromList [1, 2]) (fromList [2, 3]) + ``` + + # See also + + * {NatSet.intersect} to find all elements in common between two sets. + * {NatSet.subsetCompare} to compare two sets to see if either one is a + subset of the other. + * {NatSet.subset} to check if one set is a superset of another. + * {NatSet.properSubset} to check if one set is a proper superset of + another. + }} + +data.NatSet.doc : Doc +data.NatSet.doc = + use NatSet == + {{ + An efficient implementation of sets of {type Nat} values. This specialized + type is much more efficient than the generic {type Set} type when the + elements are of type {type Nat} or can be encoded as {type Nat} values. + + {{ + docAside + {{ + The implementation of {type NatSet} is based on a + [patricia tree](https://en.wikipedia.org/wiki/Radix_tree). The code is + largely transliterated from the Haskell implementation by Daan Leijen and + Andriy Palamarchuk, which is based on the paper "Fast Mergeable Integer + Maps" by Chris Okasaki and Andy Gill. + }} }} + + # Constructing sets + + The empty set: + + @signature{NatSet.empty} + + A set with a single value in it. Note that this returns + {type NatSet.Nonempty} rather than {type NatSet}: + + @signature{NatSet.singleton} + + Construct a set from a list of values: + + @signature{NatSet.fromList} + + # Querying sets + + Check if a set is empty: + + @signature{NatSet.isEmpty} + + Check if a set contains a value: + + @signature{NatSet.contains} + + Get the number of elements in a set: + + @signature{NatSet.size} + + Get the smallest element above a given value: + + @signature{NatSet.getAbove} + + Get the largest element below a given value: + + @signature{NatSet.getBelow} + + Get the smallest element above or equal to a given value: + + @signature{NatSet.getAtLeast} + + Get the largest element below or equal to a given value: + + @signature{NatSet.getAtMost} + + # Inserting and removing elements + + Insert a value into a set: + + @signature{NatSet.insert} + + Insert a value into a set, returning a nonempty set: + + @signature{NatSet.insert.nonempty} + + Remove a value from a set: + + @signature{NatSet.delete} + + # Combining sets + + Union of two sets: + + @signature{NatSet.union} + + Union of a list of sets: + + @signature{NatSet.unions} + + Intersection of two sets: + + @signature{NatSet.intersect} + + Difference between two sets: + + @signature{NatSet.difference} + + # Transforming sets + + Apply function to every element of a set: + + @signature{NatSet.map} + + Apply function to every element of a set, removing elements for which the + function returns {None}: + + @signature{NatSet.filterMap} + + Change the membership of a key in a set: + + @signature{NatSet.alter} + + # Partitioning and filtering + + Partition a set into two sets based on a predicate: + + @signature{NatSet.partition} + + Filter a set based on a predicate: + + @signature{NatSet.filter} + + Split a set into two sets around a pivot value: + + @signature{NatSet.split} + + Split a set into two sets around a pivot value, returning whether the pivot + value was in the set: + + @signature{NatSet.splitContains} + + # Summarizing sets + + Summarize a set with a left-associative function: + + @signature{NatSet.foldLeft} + + Summarize a set with a right-associative function: + + @signature{NatSet.foldRight} + + Summarize a set by applying a function to each element and combining the + results with a binary function: + + @signature{NatSet.foldMap} + + # Comparing sets + + Check if two sets are equal: + + @signature{==} + + Check if a set is a subset of another set: + + @signature{NatSet.subset} + + Check if a set is a superset of another set: + + @signature{NatSet.superset} + + Check if a set is a proper subset of another set: + + @signature{NatSet.properSubset} + + Check if a set is a proper superset of another set: + + @signature{NatSet.properSuperset} + + Compare two sets for subset ordering: + + @signature{NatSet.subsetCompare} + + Order two sets lexicographically: + + @signature{NatSet.ordering} + + Check if two sets have any elements in common: + + @signature{NatSet.disjoint} + + # Converting sets + + Get the elements of a set as a list, in ascending order: + + @signature{NatSet.toList} + + Get the elements of a set as a list, in descending order: + + @signature{NatSet.toListDescending} + + # Minimum and maximum elements + + Get the smallest element in a set: + + @signature{NatSet.getMin} + + Get the largest element in a set: + + @signature{NatSet.getMax} + + Break off the smallest element in a set: + + @signature{NatSet.minView} + + Break off the largest element in a set: + + @signature{NatSet.maxView} + + Remove the smallest element in a set: + + @signature{NatSet.deleteMin} + + Remove the largest element in a set: + + @signature{NatSet.deleteMax} + }} + +data.NatSet.empty : NatSet +data.NatSet.empty = NatSet None + +data.NatSet.empty.doc : Doc +data.NatSet.empty.doc = {{ The empty {type NatSet}. }} + +data.NatSet.equals.doc : Doc +data.NatSet.equals.doc = + use NatSet fromList + {{ + Checks if two {type NatSet} are equal. + + Two {type NatSet} are equal if they contain the same elements. + + # Example + + ``` + NatSet.equals (fromList [1, 2, 3, 4]) (fromList [1, 2, 3, 4]) + ``` + + # See also + + * {NatSet.ordering} to compare two {type NatSet} lexicographically. + * {NatSet.subsetCompare} to compare two {type NatSet} for subset ordering. + * {NatSet.subset} to check if one {type NatSet} is a subset of another. + * {NatSet.disjoint} to check if two {type NatSet} have no elements in + common. + }} + +test> data.NatSet.equals.tests.reflexive = test.verify do + _ = Each.range 0 100 + xs = Random.listOf Random.nat do Random.natIn 0 100 + s = NatSet.fromList xs + ensure (NatSet.equals s s) + +test> data.NatSet.equals.tests.symmetric = test.verify do + use NatSet equals fromList + use Random listOf nat natIn + _ = Each.range 0 100 + xs = listOf nat do natIn 0 100 + ys = listOf nat do natIn 0 100 + s1 = fromList xs + s2 = fromList ys + ensureEqual (equals s1 s2) (equals s2 s1) + +test> data.NatSet.equals.tests.transitive = test.verify do + use NatSet equals fromList + use Random listOf nat natIn + _ = Each.range 0 100 + xs = listOf nat do natIn 0 100 + ys = listOf nat do natIn 0 100 + zs = listOf nat do natIn 0 100 + s1 = fromList xs + s2 = fromList ys + s3 = fromList zs + ensureEqual (equals s1 s2 && equals s2 s3) (equals s1 s3) + +data.NatSet.filter : (Nat ->{g} Boolean) -> NatSet ->{g} NatSet +data.NatSet.filter f = cases + NatSet (Some t) -> NatSet.Nonempty.filter f t + NatSet None -> NatSet None + +data.NatSet.filter.doc : Doc +data.NatSet.filter.doc = + {{ + Filters a {type NatSet} by a predicate. Returns a new set containing only the + elements for which the predicate returns ``true``. + + # Example + + ``` + NatSet.toList (NatSet.filter Nat.isEven (NatSet.fromList [1, 2, 3, 4])) + ``` + + # See also + + * {NatSet.partition} to split a set into two sets, one containing all + elements that satisfy the predicate, and one containing all elements that + do not. + * {NatSet.filterMap} to filter a set by a function that returns an + {type Optional} value. + * {NatSet.map} to apply a function to every element in a set, and return a + new set with the results. + }} + +test> data.NatSet.filter.test = + test.verify do + use NatSet fromList + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 0 100 + f = natIn 0 100 + ensure + (NatSet.filter ((Nat.==) f) (fromList xs) + NatSet.== fromList (List.filter ((Nat.==) f) xs)) + +data.NatSet.filterMap : (Nat ->{g} Optional Nat) -> NatSet ->{g} NatSet +data.NatSet.filterMap f = cases + NatSet (Some t) -> NatSet.Nonempty.filterMap f t + NatSet None -> NatSet None + +data.NatSet.filterMap.doc : Doc +data.NatSet.filterMap.doc = + use Nat * + {{ + Filters a {type NatSet} by a function that returns an {type Optional} value. + Returns a new set containing only the elements for which the function returns + ``Some``. + + # Example + + ``` + NatSet.toList + (NatSet.filterMap + (n -> (if Nat.isEven n then Some (n * 2) else None)) + (NatSet.fromList [1, 2, 3, 4])) + ``` + + # See also + + * {NatSet.filter} to filter a set by a predicate that returns a + {type Boolean}. + * {NatSet.partition} to split a set into two sets, one containing all + elements that satisfy the predicate, and one containing all elements that + do not. + * {NatSet.map} to apply a function to every element in a set, and return a + new set with the results. + }} + +test> data.NatSet.filterMap.test = + test.verify do + use Nat * + use NatSet fromList + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 0 100 + f = natIn 0 100 + ensure + (NatSet.filterMap + (n -> (if n Nat.== f then Some (n * 2) else None)) (fromList xs) + NatSet.== fromList + (List.filterMap + (n -> (if n Nat.== f then Some (n * 2) else None)) xs)) + +data.NatSet.foldLeft : (a ->{g} Nat ->{g} a) -> a ->{g} NatSet ->{g} a +data.NatSet.foldLeft f z = cases + NatSet (Some t) -> NatSet.Nonempty.foldLeft f z t + NatSet None -> z + +data.NatSet.foldLeft.doc : Doc +data.NatSet.foldLeft.doc = + use Nat + + use NatSet foldLeft fromList + use Text ++ + {{ + `` foldLeft f z s `` summarizes a set `s` by starting with the value `z` and + applying the binary function `f` to each element of `s` with the result so + far, associating to the left. + + # Examples + + Sum all the values in the set: + + ``` + foldLeft (+) 0 (fromList [1, 2, 3, 4]) + ``` + + Concatenate the {type Text} values of all the elements in the set: + + ``` + foldLeft (acc n -> acc ++ Nat.toText n) "" (fromList [1, 2, 3, 4]) + ``` + + # See also + + * {NatSet.foldRight} to associate to the right. + * {NatSet.foldMap} to apply a function to every element and combine the + results with a binary function. + * {NatSet.map} to apply a function to every element. + * {NatSet.filter} to filter out elements that don't satisfy a predicate. + * {NatSet.filterMap} to apply a function to every element and filter out + the results that are {None}. + }} + +test> data.NatSet.foldLeft.test = test.verify do + use Nat + == + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 1 100 + s = NatSet.fromList xs + f acc x = acc + x + r = NatSet.foldLeft f 0 s == List.foldLeft f 0 (distinct xs) + ensure r + +data.NatSet.foldMap : + (a ->{g} a ->{g} a) -> (Nat ->{g} a) -> a -> NatSet ->{g} a +data.NatSet.foldMap f g z = cases + NatSet (Some t) -> NatSet.Nonempty.foldMap f g t + NatSet None -> z + +data.NatSet.foldMap.doc : Doc +data.NatSet.foldMap.doc = + use Nat + + use NatSet foldMap foldRight fromList + use Text ++ + {{ + `` foldMap f g s `` applies the function `g` to every element of the set `s` + and combines the results with the binary function `f`. + + # Examples + + Sum all the values in the set: + + ``` + foldMap (+) id 0 (fromList [1, 2, 3, 4]) + ``` + + Concatenate the {type Text} values of all the elements in the set: + + ``` + foldMap (++) Nat.toText "" (fromList [1, 2, 3, 4]) + ``` + + # See also + + * {foldRight} to accumulate results of a single binary function applied to + every element and the result so far, associating to the right. + * {NatSet.foldLeft} same as {foldRight} but associating to the left. + * {NatSet.map} to apply a function to every element without combining the + results. + * {NatSet.filter} to filter out elements that don't satisfy a predicate. + * {NatSet.filterMap} to apply a function to every element and filter out + the results that are {None}. + }} + +data.NatSet.foldRight : (Nat ->{g} a ->{g} a) -> a ->{g} NatSet ->{g} a +data.NatSet.foldRight f z = cases + NatSet (Some t) -> NatSet.Nonempty.foldRight f z t + NatSet None -> z + +data.NatSet.foldRight.doc : Doc +data.NatSet.foldRight.doc = + use Nat + + use NatSet foldRight fromList + use Text ++ + {{ + `` foldRight f z s `` summarizes a set `s` by starting with the value `z` and + applying the binary function `f` to each element of `s` with the result so + far, associating to the right. + + # Examples + + Sum all the values in the set: + + ``` + foldRight (+) 0 (fromList [1, 2, 3, 4]) + ``` + + Concatenate the {type Text} values of all the elements in the set: + + ``` + foldRight (n acc -> Nat.toText n ++ acc) "" (fromList [1, 2, 3, 4]) + ``` + + # See also + + * {NatSet.foldLeft} to associate to the left. + * {NatSet.foldMap} to apply a function to every element and combine the + results with a binary function. + * {NatSet.map} to apply a function to every element. + * {NatSet.filter} to filter out elements that don't satisfy a predicate. + * {NatSet.filterMap} to apply a function to every element and filter out + the results that are {None}. + }} + +test> data.NatSet.foldRight.test = test.verify do + use Nat + == + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 1 100 + s = NatSet.fromList xs + f x acc = acc + x + r = NatSet.foldRight f 0 s == List.foldRight f 0 (distinct xs) + ensure r + +data.NatSet.fromList : [Nat] -> NatSet +data.NatSet.fromList = + List.foldBalanced + (NatSet << Some << NatSet.singleton) NatSet.union NatSet.empty + +data.NatSet.fromList.doc : Doc +data.NatSet.fromList.doc = + use NatSet toList + {{ + Converts a {type List} of {type Nat} to a {type NatSet}. + + # Example + + ``` + toList (NatSet.fromList [1, 2, 3, 4]) + ``` + + # See also + + * {toList} to convert the other way. + * {NatSet.singleton} to create a {type NatSet.Nonempty} with a single + element. + }} + +data.NatSet.getAbove : Nat -> NatSet -> Optional Nat +data.NatSet.getAbove k = cases + NatSet (Some t) -> NatSet.Nonempty.getAbove k t + NatSet None -> None + +data.NatSet.getAbove.doc : Doc +data.NatSet.getAbove.doc = + use NatSet fromList getAbove + {{ + Returns the smallest element in a {type NatSet} that is strictly larger than + a given one. + + Returns {None} if there is no such element. + + # Examples + + ``` + getAbove 2 (fromList [1, 2, 3]) + ``` + + ``` + getAbove 4 (fromList [1, 2, 3]) + ``` + + # See also + + * {NatSet.getBelow} to get the largest element that is smaller than the + key. + * {NatSet.getAtMost} to get the largest element that is smaller than or + equal to the key. + * {NatSet.getAtLeast} to get the smallest element that is larger than or + equal to the key. + * {NatSet.getMax} to get the largest element. + * {NatSet.getMin} to get the smallest element. + }} + +data.NatSet.getAtLeast : Nat -> NatSet -> Optional Nat +data.NatSet.getAtLeast k = cases + NatSet (Some t) -> NatSet.Nonempty.getAtLeast k t + NatSet None -> None + +data.NatSet.getAtLeast.doc : Doc +data.NatSet.getAtLeast.doc = + use NatSet fromList getAtLeast + {{ + Returns the smallest element in a {type NatSet} that is larger than or equal + to a given one. + + Returns {None} if there is no such element. + + # Examples + + ``` + getAtLeast 2 (fromList [1, 2, 3]) + ``` + + ``` + getAtLeast 4 (fromList [1, 2, 3]) + ``` + + # See also + + * {NatSet.getAbove} to get the smallest element that is strictly larger + than the key. + * {NatSet.getAtMost} to get the largest element that is smaller than or + equal to the key. + * {NatSet.getBelow} to get the largest element that is strictly smaller + than the key. + * {NatSet.getMax} to get the largest element. + * {NatSet.getMin} to get the smallest element. + }} + +data.NatSet.getAtMost : Nat -> NatSet -> Optional Nat +data.NatSet.getAtMost k = cases + NatSet (Some t) -> NatSet.Nonempty.getAtMost k t + NatSet None -> None + +data.NatSet.getAtMost.doc : Doc +data.NatSet.getAtMost.doc = + use NatSet fromList getAtMost + {{ + Returns the largest element in a {type NatSet} that is smaller than or equal + to a given one. + + Returns {None} if there is no such element. + + # Examples + + ``` + getAtMost 2 (fromList [1, 2, 3]) + ``` + + ``` + getAtMost 4 (fromList [1, 2, 3]) + ``` + + # See also + + * {NatSet.getBelow} to get the largest element that is strictly smaller + than the key. + * {NatSet.getAbove} to get the smallest element that is larger than the + key. + * {NatSet.getAtLeast} to get the smallest element that is larger than or + equal to the key. + * {NatSet.getMax} to get the largest element. + * {NatSet.getMin} to get the smallest element. + }} + +data.NatSet.getBelow : Nat -> NatSet -> Optional Nat +data.NatSet.getBelow k = cases + NatSet (Some t) -> NatSet.Nonempty.getBelow k t + NatSet None -> None + +data.NatSet.getBelow.doc : Doc +data.NatSet.getBelow.doc = + use NatSet fromList getBelow + {{ + Returns the largest element in a {type NatSet} that is strictly smaller than + a given one. + + Returns {None} if there is no such element. + + # Examples + + ``` + getBelow 2 (fromList [1, 2, 3]) + ``` + + ``` + getBelow 4 (fromList [1, 2, 3]) + ``` + + # See also + + * {NatSet.getAbove} to get the smallest element that is larger than the + key. + * {NatSet.getAtMost} to get the largest element that is smaller than or + equal to the key. + * {NatSet.getAtLeast} to get the smallest element that is larger than or + equal to the key. + * {NatSet.getMax} to get the largest element. + * {NatSet.getMin} to get the smallest element. + }} + +data.NatSet.getMax : NatSet -> Optional Nat +data.NatSet.getMax = cases + NatSet (Some t) -> Some (NatSet.Nonempty.getMax t) + NatSet None -> None + +data.NatSet.getMax.doc : Doc +data.NatSet.getMax.doc = + {{ + Returns the largest element in a {type NatSet}, or {None} if the set is + empty. + + # Example + + ``` + NatSet.getMax (NatSet.fromList [1, 2, 3]) + ``` + + # See also + + * {NatSet.getMin} to get the smallest element. + * {NatSet.getBelow} to get the largest element that is strictly smaller + than a given key. + * {NatSet.getAbove} to get the smallest element that is larger than a given + key. + * {NatSet.getAtMost} to get the largest element that is smaller than or + equal to a given key. + * {NatSet.getAtLeast} to get the smallest element that is larger than or + equal to a given key. + }} + +data.NatSet.getMin : NatSet -> Optional Nat +data.NatSet.getMin = cases + NatSet (Some t) -> Some (NatSet.Nonempty.getMin t) + NatSet None -> None + +data.NatSet.getMin.doc : Doc +data.NatSet.getMin.doc = + {{ + Returns the smallest element in a {type NatSet}, or {None} if the set is + empty. + + # Example + + ``` + NatSet.getMin (NatSet.fromList [1, 2, 3]) + ``` + + # See also + + * {NatSet.getMax} to get the largest element. + * {NatSet.getBelow} to get the largest element that is strictly smaller + than a given key. + * {NatSet.getAbove} to get the smallest element that is larger than a given + key. + * {NatSet.getAtMost} to get the largest element that is smaller than or + equal to a given key. + * {NatSet.getAtLeast} to get the smallest element that is larger than or + equal to a given key. + }} + +data.NatSet.insert : Nat -> NatSet -> NatSet +data.NatSet.insert n s = NatSet.Nonempty.toNatSet (NatSet.insert.nonempty n s) + +data.NatSet.insert.doc : Doc +data.NatSet.insert.doc = + use NatSet fromList + {{ + Inserts an element into a {type NatSet}. + + # Example + + ``` + NatSet.toList (NatSet.insert 1 (fromList [2, 3])) + ``` + + # See also + + * {NatSet.insert.nonempty} for a version of this that returns a + {type NatSet.Nonempty}. + * {NatSet.union} to add a whole {type NatSet} into another. + * {NatSet.Nonempty.insert} to insert an element into a + {type NatSet.Nonempty}. + * {NatSet.delete} to delete an element from a {type NatSet}. + * {fromList} to create a {type NatSet} from a {type List} of elements. + * {NatSet.singleton} to create a {type NatSet.Nonempty} with a single + element. + }} + +data.NatSet.insert.nonempty : Nat -> NatSet -> NatSet.Nonempty +data.NatSet.insert.nonempty k = cases + NatSet (Some t) -> NatSet.Nonempty.insert k t + NatSet None -> NatSet.singleton k + +data.NatSet.insert.nonempty.doc : Doc +data.NatSet.insert.nonempty.doc = + use NatSet fromList + {{ + Inserts an element into a {type NatSet}. + + # Example + + ``` + Nonempty.toListAscending (NatSet.insert.nonempty 1 (fromList [2, 3])) + ``` + + # See also + + * {NatSet.union} to add a whole {type NatSet} into another. + * {NatSet.Nonempty.insert} to insert an element into a + {type NatSet.Nonempty}. + * {NatSet.delete} to delete an element from a {type NatSet}. + * {fromList} to create a {type NatSet} from a {type List} of elements. + * {NatSet.singleton} to create a {type NatSet.Nonempty} with a single + element. + }} + +data.NatSet.internal.bim : + Nat -> Nat -> NatSet.Nonempty -> NatSet.Nonempty -> NatSet.Nonempty +data.NatSet.internal.bim p m l r = + use Nat + + use NatSet.Nonempty size + NatSet.Nonempty.Bin p m (size l + size r) l r + +data.NatSet.internal.bin : Nat -> Nat -> NatSet -> NatSet -> NatSet +data.NatSet.internal.bin p m = cases + NatSet (Some l), NatSet (Some r) -> + NatSet (Some (NatSet.internal.bim p m l r)) + NatSet None, NatSet (Some r) -> NatSet (Some r) + NatSet (Some l), NatSet None -> NatSet (Some l) + _, _ -> NatSet None + +data.NatSet.internal.bin.doc : Doc +data.NatSet.internal.bin.doc = + {{ + Internal function to combine two {type NatSet} trees ensuring that the result + has no empty branches. + }} + +data.NatSet.internal.bitmapOf : Nat -> Nat +data.NatSet.internal.bitmapOf k = Nat.shiftLeft 1 (suffixOf k) + +data.NatSet.internal.bitmapOf.doc : Doc +data.NatSet.internal.bitmapOf.doc = + {{ + Internal function to get the bitmap of a key. The bitmap is a bit vector + where the bit at position `k` is set. + }} + +data.NatSet.internal.bitPred : + (Nat ->{g} Boolean) -> Nat -> Nat -> Nat ->{g} Nat +data.NatSet.internal.bitPred f p m bi = + use Nat + + if f (p + bi) then Nat.or m (Nat.shiftLeft 1 bi) else m + +data.NatSet.internal.bitPred.doc : Doc +data.NatSet.internal.bitPred.doc = + {{ Helper function to set a bit in a bitmap if a predicate is true. }} + +data.NatSet.internal.foldBitsLeft : + Nat -> (a ->{g} Nat ->{g} a) -> a -> Nat ->{g} a +data.NatSet.internal.foldBitsLeft prefix f z bitmap = + go bm acc = + use Nat + == + bitmask = lowestBitMask bm + if bm == 0 then acc + else go (Nat.xor bm bitmask) (f acc (prefix + Nat.trailingZeros bitmask)) + go bitmap z + +data.NatSet.internal.foldBitsLeft.doc : Doc +data.NatSet.internal.foldBitsLeft.doc = + {{ + Helper function to fold over the bits in a bitmap, starting from the least + significant bit. + }} + +data.NatSet.internal.foldBitsRight : + Nat -> (Nat ->{g} a ->{g} a) -> a ->{g} Nat ->{g} a +data.NatSet.internal.foldBitsRight p f z m = + use Nat + - == + go m z = + if m == 0 then z + else + bitmask = lowestBitMask m + bi = Nat.trailingZeros bitmask + go (Nat.xor m bitmask) (f (p + 63 - bi) z) + go (reverseBits m) z + +data.NatSet.internal.foldBitsRight.doc : Doc +data.NatSet.internal.foldBitsRight.doc = + {{ Internal helper for {NatSet.Nonempty.foldRight} and {NatSet.foldRight}. }} + +data.NatSet.internal.highBit : Nat -> Nat +data.NatSet.internal.highBit n = + use Nat - + 63 - Nat.leadingZeros n + +data.NatSet.internal.highBit.doc : Doc +data.NatSet.internal.highBit.doc = + {{ + Internal function to get the position of the highest bit in a bitmap. Note + that this function is undefined if the bitmap is 0. + }} + +data.NatSet.internal.intersectBitmap : Nat -> Nat -> NatSet.Nonempty -> NatSet +data.NatSet.internal.intersectBitmap p m = cases + t@(NatSet.Nonempty.Bin p2 m2 sz2 l2 r2) + | nomatch p p2 m2 -> NatSet None + | internal.zero p m2 -> data.NatSet.internal.intersectBitmap p m l2 + | otherwise -> data.NatSet.internal.intersectBitmap p m r2 + t@(NatSet.Nonempty.Tip p2 m2) -> + if p Nat.== p2 then tip p (Nat.and m m2) else NatSet None + +data.NatSet.internal.intersectBitmap.doc : Doc +data.NatSet.internal.intersectBitmap.doc = + {{ + Internal function used by {NatSet.Nonempty.intersect}. Takes a prefix and + bitmap, and a {type NatSet.Nonempty}, and masks the leaves of the tree with + the bitmap where the prefix matches. + }} + +data.NatSet.internal.link : + Nat -> NatSet.Nonempty -> Nat -> NatSet.Nonempty -> NatSet.Nonempty +data.NatSet.internal.link p1 t1 p2 t2 = + linkWithMask (branchMask p1 p2) p1 t1 t2 + +data.NatSet.internal.link.doc : Doc +data.NatSet.internal.link.doc = + {{ Internal function to combine two {type NatSet.Nonempty} trees. }} + +data.NatSet.internal.linkWithMask : + Nat -> Nat -> NatSet.Nonempty -> NatSet.Nonempty -> NatSet.Nonempty +data.NatSet.internal.linkWithMask m p1 t1 t2 = + use NatSet.internal bim + p = mask p1 m + if internal.zero p1 m then bim p m t1 t2 else bim p m t2 t1 + +data.NatSet.internal.linkWithMask.doc : Doc +data.NatSet.internal.linkWithMask.doc = + {{ + Internal function to combine two {type NatSet.Nonempty} trees when the branch + mask is known. + }} + +data.NatSet.internal.lowestBitMask : Nat -> Nat +data.NatSet.internal.lowestBitMask n = Nat.and n (twosComplement n) + +data.NatSet.internal.lowestBitMask.doc : Doc +data.NatSet.internal.lowestBitMask.doc = + {{ Helper function to get the bitmask for the lowest set bit in a bitmap. }} + +data.NatSet.internal.prefixOf : Nat -> Nat +data.NatSet.internal.prefixOf k = Nat.and k 18446744073709551552 + +data.NatSet.internal.prefixOf.doc : Doc +data.NatSet.internal.prefixOf.doc = + {{ + Internal function to get the prefix of a key. The prefix is the high-order + bits of the key, with the last 6 bits set to 0. + }} + +data.NatSet.internal.suffixOf : Nat -> Nat +data.NatSet.internal.suffixOf k = Nat.and k 63 + +data.NatSet.internal.suffixOf.doc : Doc +data.NatSet.internal.suffixOf.doc = + {{ + Internal function to get the suffix of a key. The suffix is the low-order 6 + bits of the key. + }} + +data.NatSet.internal.tip : Nat -> Nat -> NatSet +data.NatSet.internal.tip p bm = + use Nat == + if bm == 0 then NatSet None else NatSet (Some (NatSet.Nonempty.Tip p bm)) + +data.NatSet.internal.tip.doc : Doc +data.NatSet.internal.tip.doc = + {{ + Internal function to create a {type NatSet} tree from a prefix and bitmap, + ensuring that the bitmap is non-zero. + }} + +data.NatSet.intersect : NatSet -> NatSet -> NatSet +data.NatSet.intersect = cases + NatSet (Some t1), NatSet (Some t2) -> NatSet.Nonempty.intersect t1 t2 + _, _ -> NatSet None + +data.NatSet.intersect.doc : Doc +data.NatSet.intersect.doc = + use NatSet fromList + {{ + Removes all elements from one {type NatSet} that are not in another. + + Returns a {type NatSet} containing all elements that are present in both + inputs. + + # Example + + ``` + NatSet.toList (NatSet.intersect (fromList [1, 2]) (fromList [2, 3])) + ``` + + # See also + + * {NatSet.union} to return elements in __either__ input. + * {NatSet.difference} to return elements in the first input but not the + second. + * {NatSet.delete} to remove one element from a {type NatSet}. + }} + +test> data.NatSet.intersect.test = + test.verify do + use Random listOf nat natIn + _ = Each.range 0 100 + xs = listOf nat do natIn 0 100 + ys = listOf nat do natIn 0 100 + ensureEqual + (NatSet.toList + (NatSet.intersect (NatSet.fromList xs) (NatSet.fromList ys))) + (Set.toList (Set.intersect (Set.fromList xs) (Set.fromList ys))) + +data.NatSet.isEmpty : NatSet -> Boolean +data.NatSet.isEmpty = cases + NatSet (Some _) -> false + NatSet None -> true + +data.NatSet.isEmpty.doc : Doc +data.NatSet.isEmpty.doc = + use NatSet fromList isEmpty + {{ + Checks if a {type NatSet} is empty. + + # Examples + + ``` + isEmpty (fromList [1, 2, 3]) + ``` + + ``` + isEmpty (fromList []) + ``` + + # See also + + * {NatSet.size} to get the number of elements in a {type NatSet}. + * {NatSet.contains} to check if a {type NatSet} contains a particular + element. + }} + +data.NatSet.map : (Nat ->{g} Nat) -> NatSet ->{g} NatSet +data.NatSet.map f = cases + NatSet (Some t) -> NatSet (Some (NatSet.Nonempty.map f t)) + NatSet None -> NatSet None + +data.NatSet.map.doc : Doc +data.NatSet.map.doc = + use Nat * + {{ + Applies a function to every element in a {type NatSet}. + + # Example + + ``` + NatSet.toList (NatSet.map ((*) 2) (NatSet.fromList [1, 2, 3, 4])) + ``` + + # See also + + * {NatSet.foldMap} to apply a function to every element and combine the + results with a binary function. + * {NatSet.filterMap} to apply a function to every element and filter out + the results that are {None}. + * {NatSet.filter} to filter out elements that don't satisfy a predicate. + * {NatSet.foldLeft} and {NatSet.foldRight} to summarize the elements of a + set. + }} + +test> data.NatSet.map.tests.functor.homomorphism = test.verify do + use Nat * + + use NatSet == map + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 1 100 + s = NatSet.fromList xs + f x = x + 1 + g x = x * 2 + r = map (f >> g) s == map g (map f s) + ensure r + +test> data.NatSet.map.tests.functor.identity = test.verify do + use NatSet == + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 1 100 + s = NatSet.fromList xs + r = NatSet.map id s == s + ensure r + +test> data.NatSet.map.tests.noninjective = test.verify do + use Nat / + use NatSet == fromList + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 1 100 + s = fromList xs + f x = x / 2 + r = NatSet.map f s == fromList (List.map f xs) + ensure r + +data.NatSet.maxView : NatSet ->{Abort} (Nat, NatSet) +data.NatSet.maxView = cases + NatSet (Some t) -> NatSet.Nonempty.maxView t + NatSet None -> abort + +data.NatSet.maxView.doc : Doc +data.NatSet.maxView.doc = + {{ + Returns the maximum element in a {type NatSet}, and the set without that + element. + + Calls {abort} if the set is empty. + + # Example + + ``` + toOptional! do + Tuple.second + NatSet.toList (NatSet.maxView (NatSet.fromList [1, 2, 3, 4])) + ``` + + # See also + + * {NatSet.minView} to get the minimum element and the set without that + element. + * {NatSet.getMax} to just get the maximum element. + * {NatSet.deleteMax} to get the set without the maximum element. + }} + +test> data.NatSet.maxView.test = + test.verify do + use List maximum + use Nat != + use NatSet fromList + use Optional toAbort + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 1 100 + r = + toOptional! do + NatSet.maxView (fromList xs) + === ( toAbort (maximum xs) + , fromList (List.filter (flip (!=) (toAbort (maximum xs))) xs) + ) + ensure (r === Some true) + +data.NatSet.minView : NatSet ->{Abort} (Nat, NatSet) +data.NatSet.minView = cases + NatSet (Some t) -> NatSet.Nonempty.minView t + NatSet None -> abort + +data.NatSet.minView.doc : Doc +data.NatSet.minView.doc = + {{ + Returns the minimum element in a {type NatSet}, and the set without that + element. + + Calls {abort} if the set is empty. + + # Example + + ``` + toOptional! do + Tuple.second + NatSet.toList (NatSet.minView (NatSet.fromList [1, 2, 3, 4])) + ``` + + # See also + + * {NatSet.maxView} to get the maximum element and the set without that + element. + * {NatSet.getMin} to just get the minimum element. + * {NatSet.deleteMin} to get the set without the minimum element. + }} + +test> data.NatSet.minView.test = + test.verify do + use List minimum + use Nat != + use NatSet fromList + use Optional toAbort + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 1 100 + r = + toOptional! do + NatSet.minView (fromList xs) + === ( toAbort (minimum xs) + , fromList (List.filter (flip (!=) (toAbort (minimum xs))) xs) + ) + ensure (r === Some true) + +(data.NatSet.Nonempty.==) : NatSet.Nonempty -> NatSet.Nonempty -> Boolean +(data.NatSet.Nonempty.==) = cases + NatSet.Nonempty.Bin p1 m1 sz1 l1 r1, NatSet.Nonempty.Bin p2 m2 sz2 l2 r2 -> + m1 Nat.== m2 && p1 Nat.== p2 && l1 data.NatSet.Nonempty.== l2 + && r1 data.NatSet.Nonempty.== r2 + NatSet.Nonempty.Tip p1 m1, NatSet.Nonempty.Tip p2 m2 -> + m1 Nat.== m2 && p1 Nat.== p2 + _, _ -> false + +data.NatSet.Nonempty.alter : + (Boolean ->{g} Boolean) -> Nat -> NatSet.Nonempty ->{g} NatSet +data.NatSet.Nonempty.alter f k s = + present = NatSet.Nonempty.contains k s + let + (inserted, deleted) = + if present then (NatSet (Some s), NatSet.Nonempty.delete k s) + else (NatSet (Some (NatSet.Nonempty.insert k s)), NatSet (Some s)) + if f present then inserted else deleted + +data.NatSet.Nonempty.alter.doc : Doc +data.NatSet.Nonempty.alter.doc = + use NatSet.Nonempty alter + {{ + Inserts or deletes an element from a {type NatSet.Nonempty} depending on the + result of a function. + + The function receives a {type Boolean} indicating whether the element is + present in the {type NatSet.Nonempty} and returns a {type Boolean} indicating + whether the element should be present in the result. The function is allowed + to use any [ability](https://unison-lang.org/docs/abilities) it needs. + + # Examples + + Insert an element regardless: + + @typecheck ``` + alter (const true) + ``` + + Delete an element regardless: + + @typecheck ``` + alter (const false) + ``` + + Toggle an element: + + @typecheck ``` + alter Boolean.not + ``` + + # See also + + * {NatSet.Nonempty.insert} to insert an element into a + {type NatSet.Nonempty}. + * {NatSet.Nonempty.delete} to delete an element from a + {type NatSet.Nonempty}. + * {NatSet.Nonempty.filter} to delete all elements from a + {type NatSet.Nonempty} that do not satisfy a predicate. + }} + +data.NatSet.Nonempty.compare : NatSet.Nonempty -> NatSet.Nonempty -> Ordering +data.NatSet.Nonempty.compare xs ys = + use Nonempty toListAscending + Universal.ordering (toListAscending xs) (toListAscending ys) + +data.NatSet.Nonempty.contains : Nat -> NatSet.Nonempty -> Boolean +data.NatSet.Nonempty.contains k = cases + NatSet.Nonempty.Bin p m _ l r + | nomatch k p m -> false + | internal.zero k m -> data.NatSet.Nonempty.contains k l + | otherwise -> data.NatSet.Nonempty.contains k r + NatSet.Nonempty.Tip p bm + | prefixOf k Nat.== p -> isSetBit (suffixOf k) bm + | otherwise -> false + +data.NatSet.Nonempty.contains.doc : Doc +data.NatSet.Nonempty.contains.doc = + use NatSet.Nonempty contains fromList + {{ + Checks if a {type NatSet.Nonempty} contains a particular element. + + # Examples + + ``` + contains 1 (fromList (1 +| [2, 3])) + ``` + + ``` + contains 4 (fromList (1 +| [2, 3])) + ``` + + # See also + + * {NatSet.Nonempty.size} to get the number of elements in a + {type NatSet.Nonempty}. + }} + +data.NatSet.Nonempty.delete : Nat -> NatSet.Nonempty -> NatSet +data.NatSet.Nonempty.delete k = deleteBitmap (prefixOf k) (bitmapOf k) + +data.NatSet.Nonempty.delete.doc : Doc +data.NatSet.Nonempty.delete.doc = + {{ + Deletes an element from a {type NatSet.Nonempty}, returning a {type NatSet}. + + # Example + + ``` + NatSet.toList + (NatSet.Nonempty.delete 1 (NatSet.Nonempty.fromList (1 +| [2]))) + ``` + + # See also + + * {NatSet.Nonempty.difference} to delete a whole {type NatSet.Nonempty} + from another. + * {NatSet.Nonempty.intersect} to delete all elements from a + {type NatSet.Nonempty} that are not in another. + * {NatSet.delete} to delete an element from a {type NatSet}. + * {NatSet.Nonempty.insert} to insert an element into a + {type NatSet.Nonempty}. + }} + +test> data.NatSet.Nonempty.delete.test.deletedIsAbsent = + test.verify do + use Random nat + _ = Each.range 0 100 + xs = Random.listOf nat do Random.natIn 0 100 + n = nat() + s = NatSet.fromList xs + ensure + (Boolean.not + (NatSet.contains + n (NatSet.Nonempty.delete n (NatSet.insert.nonempty n s)))) + +data.NatSet.Nonempty.deleteMax : NatSet.Nonempty -> NatSet +data.NatSet.Nonempty.deleteMax = at2 << NatSet.Nonempty.maxView + +data.NatSet.Nonempty.deleteMax.doc : Doc +data.NatSet.Nonempty.deleteMax.doc = + {{ + Returns the set without the maximum element. + + # Example + + ``` + NatSet.toList + (NatSet.Nonempty.deleteMax (NatSet.Nonempty.fromList (1 +| [2, 3, 4]))) + ``` + + # See also + + * {NatSet.Nonempty.deleteMin} to get the set without the minimum element. + * {NatSet.Nonempty.delete} to get the set without a specific element. + * {NatSet.Nonempty.maxView} to get the maximum element and the set without + that element. + }} + +data.NatSet.Nonempty.deleteMin : NatSet.Nonempty -> NatSet +data.NatSet.Nonempty.deleteMin = at2 << NatSet.Nonempty.minView + +data.NatSet.Nonempty.deleteMin.doc : Doc +data.NatSet.Nonempty.deleteMin.doc = + {{ + Returns the set without the minimum element. + + # Example + + ``` + NatSet.toList + (NatSet.Nonempty.deleteMin (NatSet.Nonempty.fromList (1 +| [2, 3, 4]))) + ``` + + # See also + + * {NatSet.Nonempty.deleteMax} to get the set without the maximum element. + * {NatSet.Nonempty.delete} to get the set without a specific element. + * {NatSet.Nonempty.minView} to get the minimum element and the set without + that element. + }} + +data.NatSet.Nonempty.difference : NatSet.Nonempty -> NatSet.Nonempty -> NatSet +data.NatSet.Nonempty.difference = cases + t1@(NatSet.Nonempty.Bin p1 m1 _ l1 r1), + t2@(NatSet.Nonempty.Bin p2 m2 _ l2 r2) + | shorter m1 m2 -> + if nomatch p2 p1 m1 then NatSet (Some t1) + else + if internal.zero p2 m1 then + NatSet.internal.bin + p1 m1 (data.NatSet.Nonempty.difference l1 t2) (NatSet (Some r1)) + else + NatSet.internal.bin + p1 m1 (NatSet (Some l1)) (data.NatSet.Nonempty.difference r1 t2) + | shorter m2 m1 -> + if nomatch p1 p2 m2 then NatSet (Some t1) + else + if internal.zero p1 m2 then data.NatSet.Nonempty.difference t1 l2 + else data.NatSet.Nonempty.difference t1 r2 + | p1 Nat.== p2 -> + NatSet.internal.bin + p1 + m1 + (data.NatSet.Nonempty.difference l1 l2) + (data.NatSet.Nonempty.difference r1 r2) + | otherwise -> NatSet (Some t1) + t@(NatSet.Nonempty.Bin _ _ _ _ _), NatSet.Nonempty.Tip p m -> + deleteBitmap p m t + t@(NatSet.Nonempty.Tip p m), NatSet.Nonempty.Bin p2 m2 _ l2 r2 -> + if nomatch p p2 m2 then NatSet (Some t) + else + if internal.zero p m2 then data.NatSet.Nonempty.difference t l2 + else data.NatSet.Nonempty.difference t r2 + t@(NatSet.Nonempty.Tip p1 m1), NatSet.Nonempty.Tip p2 m2 -> + if p1 Nat.== p2 then tip p1 (Nat.and m1 (Nat.complement m2)) + else NatSet (Some t) + +data.NatSet.Nonempty.difference.doc : Doc +data.NatSet.Nonempty.difference.doc = + use NatSet.Nonempty fromList + {{ + Removes all elements from one {type NatSet.Nonempty} that are in another. If + an element is present in the first input but not the second, it will be + present in the result. + + # Example + + ``` + NatSet.toList + (NatSet.Nonempty.difference (fromList (1 +| [2])) (fromList (2 +| [3]))) + ``` + + # See also + + * {NatSet.Nonempty.union} to add all elements from a {type NatSet.Nonempty} + that are in another. + * {NatSet.Nonempty.intersect} to remove all elements from a + {type NatSet.Nonempty} that are not in another. + * {NatSet.Nonempty.delete} to remove one element from a + {type NatSet.Nonempty}. + }} + +data.NatSet.Nonempty.disjoint : NatSet.Nonempty -> NatSet.Nonempty -> Boolean +data.NatSet.Nonempty.disjoint t1 t2 = + NatSet.isEmpty (NatSet.Nonempty.intersect t1 t2) + +data.NatSet.Nonempty.disjoint.doc : Doc +data.NatSet.Nonempty.disjoint.doc = + use NatSet.Nonempty fromList + use Nonempty disjoint + {{ + Checks if two {type NatSet.Nonempty} values have no elements in common. + + # Examples + + Two sets with no elements in common: + + ``` + disjoint (fromList (1 +| [2])) (fromList (3 +| [4])) + ``` + + Two sets with elements in common: + + ``` + disjoint (fromList (1 +| [2])) (fromList (2 +| [3])) + ``` + + # See also + + * {NatSet.Nonempty.intersect} to find all elements in common between two + sets. + * {Nonempty.subsetCompare} to compare two sets to see if either one is a + subset of the other. + * {NatSet.Nonempty.subset} to check if one set is a superset of another. + * {Nonempty.properSubset} to check if one set is a proper superset of + another. + }} + +test> data.NatSet.Nonempty.disjoint.test = + test.verify do + use List all + use Nat != + use NatSet fromList + use Random listOf natIn + _ = Each.range 0 100 + xs = listOf (do natIn 0 1000) do natIn 0 100 + ys = listOf (do natIn 0 1000) do natIn 0 100 + d = NatSet.disjoint (fromList xs) (fromList ys) + if all (x -> all (y -> x != y) ys) xs then ensure d + else ensure (Boolean.not d) + +data.NatSet.Nonempty.doc : Doc +data.NatSet.Nonempty.doc = + use NatSet.Nonempty == + {{ + A nonempty set of {type Nat} values. This specialized type is much more + efficient than the generic {type Set} type when the elements are of type + {type Nat} or can be encoded as {type Nat} values. + + {{ + docAside + {{ + The implementation of {type NatSet.Nonempty} is based on a + [patricia tree](https://en.wikipedia.org/wiki/Radix_tree). The code is + largely transliterated from the Haskell implementation by Daan Leijen and + Andriy Palamarchuk, which is based on the paper "Fast Mergeable Integer + Maps" by Chris Okasaki and Andy Gill. + }} }} + + Note that certain operations on {type NatSet.Nonempty} return (possibly + empty) {type NatSet} values, where the presence of at least one element + cannot be guaranteed by the type. Conversely, certain operations on + {type NatSet} return {type NatSet.Nonempty} values, where the presence of at + least one element is guaranteed. + + # Constructing sets + + A set with a single value in it: + + @signature{NatSet.Nonempty.singleton} + + Construct a set from a {type List.Nonempty} of values: + + @signature{NatSet.Nonempty.fromList} + + # Querying sets + + Check if a set contains a value: + + @signature{NatSet.Nonempty.contains} + + Get the number of elements in a set: + + @signature{NatSet.Nonempty.size} + + Get the smallest element above a given value: + + @signature{NatSet.Nonempty.getAbove} + + Get the largest element below a given value: + + @signature{NatSet.Nonempty.getBelow} + + Get the smallest element above or equal to a given value: + + @signature{NatSet.Nonempty.getAtLeast} + + Get the largest element below or equal to a given value: + + @signature{NatSet.Nonempty.getAtMost} + + # Inserting and removing elements + + Insert a value into a set: + + @signature{NatSet.Nonempty.insert} + + Remove a value from a set: + + @signature{NatSet.Nonempty.delete} + + # Combining sets + + Union of two sets: + + @signature{NatSet.Nonempty.union} + + Union of a {type List.Nonempty} of sets: + + @signature{NatSet.Nonempty.unions} + + Intersection of two sets: + + @signature{NatSet.Nonempty.intersect} + + Difference between two sets: + + @signature{NatSet.Nonempty.difference} + + # Transforming sets + + Apply function to every element of a set: + + @signature{NatSet.Nonempty.map} + + Apply function to every element of a set, removing elements for which the + function returns {None}: + + @signature{NatSet.Nonempty.filterMap} + + Change the membership of a key in a set: + + @signature{NatSet.Nonempty.alter} + + # Partitioning and filtering + + Partition a set into two sets based on a predicate: + + @signature{NatSet.Nonempty.partition} + + Filter a set based on a predicate: + + @signature{NatSet.Nonempty.filter} + + Split a set into two sets around a pivot value: + + @signature{NatSet.Nonempty.split} + + Split a set into two sets around a pivot value, returning whether the pivot + value was in the set: + + @signature{Nonempty.splitContains} + + # Summarizing sets + + Summarize a set with a left-associative function: + + @signature{NatSet.Nonempty.foldLeft} + + Summarize a set with a right-associative function: + + @signature{NatSet.Nonempty.foldRight} + + Summarize a set by applying a function to each element and combining the + results with a binary function: + + @signature{NatSet.Nonempty.foldMap} + + # Comparing sets + + Check if two sets are equal: + + @signature{==} + + Check if a set is a subset of another set: + + @signature{NatSet.Nonempty.subset} + + Check if a set is a superset of another set: + + @signature{NatSet.Nonempty.superset} + + Check if a set is a proper subset of another set: + + @signature{Nonempty.properSubset} + + Check if a set is a proper superset of another set: + + @signature{Nonempty.properSuperset} + + Compare two sets for subset ordering: + + @signature{Nonempty.subsetCompare} + + Order two sets lexicographically: + + @signature{Nonempty.ordering} + + Check if two sets have any elements in common: + + @signature{Nonempty.disjoint} + + # Converting sets + + Get the elements of a set as a {type List.Nonempty}, in ascending order: + + @signature{Nonempty.toListAscending} + + Get the elements of a set as a {type List.Nonempty}, in descending order: + + @signature{Nonempty.toListDescending} + + # Minimum and maximum elements + + Get the smallest element in a set: + + @signature{NatSet.Nonempty.getMin} + + Get the largest element in a set: + + @signature{NatSet.Nonempty.getMax} + + Break off the smallest element in a set: + + @signature{NatSet.Nonempty.minView} + + Break off the largest element in a set: + + @signature{NatSet.Nonempty.maxView} + + Remove the smallest element in a set: + + @signature{NatSet.Nonempty.deleteMin} + + Remove the largest element in a set: + + @signature{NatSet.Nonempty.deleteMax} + }} + +data.NatSet.Nonempty.equals.doc : Doc +data.NatSet.Nonempty.equals.doc = + use NatSet.Nonempty fromList + {{ + Checks if two {type NatSet.Nonempty} are equal. + + Two {type NatSet.Nonempty} are equal if they contain the same elements. + + # Example + + ``` + NatSet.Nonempty.equals + (fromList (1 +| [2, 3, 4])) (fromList (1 +| [2, 3, 4])) + ``` + + # See also + + * {Nonempty.ordering} to compare two {type NatSet.Nonempty} + lexicographically. + * {Nonempty.subsetCompare} to compare two {type NatSet.Nonempty} for subset + ordering. + * {NatSet.Nonempty.subset} to check if one {type NatSet.Nonempty} is a + subset of another. + * {Nonempty.disjoint} to check if two {type NatSet.Nonempty} have no + elements in common. + }} + +data.NatSet.Nonempty.filter : + (Nat ->{g} Boolean) -> NatSet.Nonempty ->{g} NatSet +data.NatSet.Nonempty.filter f = cases + NatSet.Nonempty.Bin p m _ l r -> + NatSet.internal.bin + p m (data.NatSet.Nonempty.filter f l) (data.NatSet.Nonempty.filter f r) + NatSet.Nonempty.Tip p m -> tip p (foldBitsLeft 0 (bitPred f p) 0 m) + +data.NatSet.Nonempty.filter.doc : Doc +data.NatSet.Nonempty.filter.doc = + {{ + Filters a {type NatSet.Nonempty} by a predicate. Returns a new set containing + only the elements for which the predicate returns ``true``. + + # Example + + ``` + NatSet.toList + (NatSet.Nonempty.filter + Nat.isEven (NatSet.Nonempty.fromList (1 +| [2, 3, 4]))) + ``` + + # See also + + * {NatSet.Nonempty.partition} to split a set into two sets, one containing + all elements that satisfy the predicate, and one containing all elements + that do not. + * {NatSet.Nonempty.filterMap} to filter a set by a function that returns an + {type Optional} value. + * {NatSet.Nonempty.map} to apply a function to every element in a set, and + return a new set with the results. + }} + +data.NatSet.Nonempty.filterMap : + (Nat ->{g} Optional Nat) ->{g} NatSet.Nonempty ->{g} NatSet +data.NatSet.Nonempty.filterMap f = + NatSet.Nonempty.foldLeft + (acc n -> (match f n with + Some n' -> NatSet (Some (NatSet.insert.nonempty n' acc)) + None -> acc)) NatSet.empty + +data.NatSet.Nonempty.filterMap.doc : Doc +data.NatSet.Nonempty.filterMap.doc = + use Nat * + {{ + Filters a {type NatSet.Nonempty} by a function that returns an + {type Optional} value. Returns a new set containing only the elements for + which the function returns ``Some``. + + # Example + + ``` + NatSet.toList + (NatSet.Nonempty.filterMap + (n -> (if Nat.isEven n then Some (n * 2) else None)) + (NatSet.Nonempty.fromList (1 +| [2, 3, 4]))) + ``` + + # See also + + * {NatSet.Nonempty.filter} to filter by a predicate that returns a + {type Boolean}. + * {NatSet.Nonempty.partition} to split a set into two sets, one containing + all elements that satisfy the predicate, and one containing all elements + that do not. + * {NatSet.Nonempty.map} to apply a function to every element in a set, and + return a new set with the results. + }} + +data.NatSet.Nonempty.foldLeft : + (a ->{g} Nat ->{g} a) -> a ->{g} NatSet.Nonempty ->{g} a +data.NatSet.Nonempty.foldLeft f z = + go z' = cases + NatSet.Nonempty.Tip p m -> foldBitsLeft p f z' m + NatSet.Nonempty.Bin p m _ l r -> go (go z' l) r + go z + +data.NatSet.Nonempty.foldLeft.doc : Doc +data.NatSet.Nonempty.foldLeft.doc = + use Nat + + use NatSet.Nonempty foldLeft fromList + use Text ++ + {{ + `` foldLeft f z s `` summarizes a set `s` by starting with the value `z` and + applying the binary function `f` to each element of `s` with the result so + far, associating to the left. + + # Examples + + Sum all the values in the set: + + ``` + foldLeft (+) 0 (fromList (1 +| [2, 3, 4])) + ``` + + Concatenate the {type Text} values of all the elements in the set: + + ``` + foldLeft (acc n -> acc ++ Nat.toText n) "" (fromList (1 +| [2, 3, 4])) + ``` + + Find the maximum element in the set: + + ``` + foldLeft Nat.max 0 (fromList (1 +| [2, 3, 4])) + ``` + + # See also + + * {NatSet.Nonempty.foldRight} to associate to the right. + * {NatSet.Nonempty.foldMap} to apply a function to every element and + combine the results with a binary function. + * {NatSet.Nonempty.map} to apply a function to every element. + * {NatSet.Nonempty.filter} to filter out elements that don't satisfy a + predicate. + * {NatSet.Nonempty.filterMap} to apply a function to every element and + filter out the results that are {None}. + }} + +data.NatSet.Nonempty.foldMap : + (a ->{g} a ->{g} a) -> (Nat ->{g} a) -> NatSet.Nonempty ->{g} a +data.NatSet.Nonempty.foldMap f g = cases + NatSet.Nonempty.Bin p m _ l r -> + f (data.NatSet.Nonempty.foldMap f g l) (data.NatSet.Nonempty.foldMap f g r) + NatSet.Nonempty.Tip p m -> + use Nat + + first = Nat.trailingZeros m + firstMask = Nat.shiftLeft 1 first + foldBitsLeft + p + (a n -> f a (g n)) + (g (first + p)) + (Nat.and m (Nat.complement firstMask)) + +data.NatSet.Nonempty.foldMap.doc : Doc +data.NatSet.Nonempty.foldMap.doc = + use Nat + + use NatSet.Nonempty foldMap foldRight fromList + use Text ++ + {{ + `` foldMap f g s `` applies the function `g` to every element of the set `s` + and combines the results with the binary function `f`. + + # Examples + + Sum all the values in the set: + + ``` + foldMap (+) id (fromList (1 +| [2, 3, 4])) + ``` + + Concatenate the {type Text} values of all the elements in the set: + + ``` + foldMap (++) Nat.toText (fromList (1 +| [2, 3, 4])) + ``` + + # See also + + * {foldRight} to accumulate results of a single binary function applied to + every element and the result so far, associating to the right. + * {NatSet.Nonempty.foldLeft} same as {foldRight} but associating to the + left. + * {NatSet.Nonempty.map} to apply a function to every element without + combining the results. + * {NatSet.Nonempty.filter} to filter out elements that don't satisfy a + predicate. + * {NatSet.Nonempty.filterMap} to apply a function to every element and + filter out the results that are {None}. + }} + +data.NatSet.Nonempty.foldRight : + (Nat ->{g} a ->{g} a) -> a ->{g} NatSet.Nonempty ->{g} a +data.NatSet.Nonempty.foldRight f z = + go z' = cases + NatSet.Nonempty.Tip p m -> foldBitsRight p f z' m + NatSet.Nonempty.Bin p m _ l r -> go (go z' r) l + go z + +data.NatSet.Nonempty.foldRight.doc : Doc +data.NatSet.Nonempty.foldRight.doc = + use Nat + + use NatSet.Nonempty foldRight fromList + use Text ++ + {{ + `` foldRight f z s `` summarizes a set `s` by starting with the value `z` and + applying the binary function `f` to each element of `s` with the result so + far, associating to the right. + + # Examples + + Sum all the values in the set: + + ``` + foldRight (+) 0 (fromList (1 +| [2, 3, 4])) + ``` + + Concatenate the {type Text} values of all the elements in the set: + + ``` + foldRight (n acc -> Nat.toText n ++ acc) "" (fromList (1 +| [2, 3, 4])) + ``` + + Find the maximum element in the set: + + ``` + foldRight Nat.max 0 (fromList (1 +| [2, 3, 4])) + ``` + + # See also + + * {NatSet.Nonempty.foldLeft} to associate to the left. + * {NatSet.Nonempty.foldMap} to apply a function to every element and + combine the results with a binary function. + * {NatSet.Nonempty.map} to apply a function to every element. + * {NatSet.Nonempty.filter} to filter out elements that don't satisfy a + predicate. + * {NatSet.Nonempty.filterMap} to apply a function to every element and + filter out the results that are {None}. + }} + +data.NatSet.Nonempty.fromList : List.Nonempty Nat -> NatSet.Nonempty +data.NatSet.Nonempty.fromList = + List.Nonempty.foldMap NatSet.Nonempty.union NatSet.Nonempty.singleton + +data.NatSet.Nonempty.fromList.doc : Doc +data.NatSet.Nonempty.fromList.doc = + use Nonempty toListAscending + {{ + Converts a {type List.Nonempty} of {type Nat} to a {type NatSet.Nonempty}. + + # Example + + ``` + toListAscending (NatSet.Nonempty.fromList (1 +| [2, 3, 4])) + ``` + + # See also + + * {toListAscending} to convert the other way. + * {NatSet.Nonempty.singleton} to create a {type NatSet.Nonempty} with a + single element. + }} + +data.NatSet.Nonempty.getAbove : Nat -> NatSet.Nonempty -> Optional Nat +data.NatSet.Nonempty.getAbove k = cases + NatSet.Nonempty.Bin p m _ l r + | nomatch k p m -> + if k Nat.> p then None else Some (NatSet.Nonempty.getMin l) + | internal.zero k m -> + match data.NatSet.Nonempty.getAbove k l with + Some x -> Some x + None -> data.NatSet.Nonempty.getAbove k r + | otherwise -> data.NatSet.Nonempty.getAbove k r + NatSet.Nonempty.Tip p bm + | prefixOf k Nat.< p -> Some (p Nat.+ Nat.trailingZeros bm) + | otherwise -> + use Nat != + == + maskGT = Nat.and bm (twosComplement (Nat.shiftLeft (bitmapOf k) 1)) + if prefixOf k == p && maskGT != 0 then + Some (p + Nat.trailingZeros maskGT) + else None + +data.NatSet.Nonempty.getAbove.doc : Doc +data.NatSet.Nonempty.getAbove.doc = + use NatSet.Nonempty fromList getAbove + {{ + Returns the smallest element in a {type NatSet.Nonempty} that is larger than + a given one. + + Returns {None} if there is no such element. + + # Examples + + ``` + getAbove 2 (fromList (1 +| [2, 3])) + ``` + + ``` + getAbove 4 (fromList (1 +| [2, 3])) + ``` + + # See also + + * {NatSet.Nonempty.getBelow} to get the largest element that is smaller + than the key. + * {NatSet.Nonempty.getAtMost} to get the largest element that is smaller + than or equal to the key. + * {NatSet.Nonempty.getAtLeast} to get the smallest element that is larger + than or equal to the key. + * {NatSet.Nonempty.getMax} to get the largest element. + * {NatSet.Nonempty.getMin} to get the smallest element. + }} + +test> data.NatSet.Nonempty.getAbove.test = test.verify do + use Nat < > >= + use Random nat + _ = Each.range 0 100 + xs = Random.listOf nat do Random.natIn 1 100 + s = NatSet.Nonempty.fromList (Abort.toBug do List.nonempty xs) + n = nat() + match NatSet.Nonempty.getAbove n s with + Some x -> + ensure (x > n) + ys = List.filter (y -> y > n) xs + ensure (List.all (y -> y >= x) ys) + None -> ensure (NatSet.Nonempty.getMax s < n) + +data.NatSet.Nonempty.getAtLeast : Nat -> NatSet.Nonempty -> Optional Nat +data.NatSet.Nonempty.getAtLeast k = cases + NatSet.Nonempty.Bin p m _ l r + | nomatch k p m -> + if k Nat.> p then None else Some (NatSet.Nonempty.getMin l) + | internal.zero k m -> + match data.NatSet.Nonempty.getAtLeast k l with + Some x -> Some x + None -> data.NatSet.Nonempty.getAtLeast k r + | otherwise -> data.NatSet.Nonempty.getAtLeast k r + NatSet.Nonempty.Tip p bm + | prefixOf k Nat.< p -> Some (p Nat.+ Nat.trailingZeros bm) + | otherwise -> + use Nat != + == + maskGE = Nat.and bm (twosComplement (bitmapOf k)) + if prefixOf k == p && maskGE != 0 then + Some (p + Nat.trailingZeros maskGE) + else None + +data.NatSet.Nonempty.getAtLeast.doc : Doc +data.NatSet.Nonempty.getAtLeast.doc = + use NatSet.Nonempty fromList getAtLeast + {{ + Returns the smallest element in a {type NatSet.Nonempty} that is larger than + or equal to a given one. + + Returns {None} if there is no such element. + + # Examples + + ``` + getAtLeast 2 (fromList (1 +| [2, 3])) + ``` + + ``` + getAtLeast 4 (fromList (1 +| [2, 3])) + ``` + + # See also + + * {NatSet.Nonempty.getBelow} to get the largest element that is strictly + smaller than the key. + * {NatSet.Nonempty.getAbove} to get the smallest element that is larger + than the key. + * {NatSet.Nonempty.getAtMost} to get the largest element that is smaller + than or equal to the key. + * {NatSet.Nonempty.getMax} to get the largest element. + * {NatSet.Nonempty.getMin} to get the smallest element. + }} + +test> data.NatSet.Nonempty.getAtLeast.test = test.verify do + use Nat < >= + use Random nat + _ = Each.range 0 100 + xs = Random.listOf nat do Random.natIn 1 100 + s = NatSet.Nonempty.fromList (Abort.toBug do List.nonempty xs) + n = nat() + match NatSet.Nonempty.getAtLeast n s with + Some x -> + ensure (x >= n) + ys = List.filter (y -> y >= n) xs + ensure (List.all (y -> y >= x) ys) + None -> ensure (NatSet.Nonempty.getMax s < n) + +data.NatSet.Nonempty.getAtMost : Nat -> NatSet.Nonempty -> Optional Nat +data.NatSet.Nonempty.getAtMost k = cases + NatSet.Nonempty.Bin p m _ l r + | nomatch k p m -> + if k Nat.< p then None else Some (NatSet.Nonempty.getMax r) + | internal.zero k m -> data.NatSet.Nonempty.getAtMost k l + | otherwise -> + match data.NatSet.Nonempty.getAtMost k r with + Some x -> Some x + None -> data.NatSet.Nonempty.getAtMost k l + NatSet.Nonempty.Tip p bm + | prefixOf k Nat.> p -> Some (p Nat.+ highBit bm) + | otherwise -> + use Nat != + - == + maskLE = Nat.and bm (Nat.shiftLeft (bitmapOf k) 1 - 1) + if prefixOf k == p && maskLE != 0 then Some (p + highBit maskLE) + else None + +data.NatSet.Nonempty.getAtMost.doc : Doc +data.NatSet.Nonempty.getAtMost.doc = + use NatSet.Nonempty fromList getAtMost + {{ + Returns the largest element in a {type NatSet.Nonempty} that is smaller than + or equal to a given one. + + Returns {None} if there is no such element. + + # Examples + + ``` + getAtMost 2 (fromList (1 +| [2, 3])) + ``` + + ``` + getAtMost 4 (fromList (1 +| [2, 3])) + ``` + + # See also + + * {NatSet.Nonempty.getBelow} to get the largest element that is strictly + smaller than the key. + * {NatSet.Nonempty.getAbove} to get the smallest element that is larger + than the key. + * {NatSet.Nonempty.getAtLeast} to get the smallest element that is larger + than or equal to the key. + * {NatSet.Nonempty.getMax} to get the largest element. + * {NatSet.Nonempty.getMin} to get the smallest element. + }} + +test> data.NatSet.Nonempty.getAtMost.test = test.verify do + use Nat <= > + use Random nat + _ = Each.range 0 100 + xs = Random.listOf nat do Random.natIn 1 100 + s = NatSet.Nonempty.fromList (Abort.toBug do List.nonempty xs) + n = nat() + match NatSet.Nonempty.getAtMost n s with + Some x -> + ensure (x <= n) + ys = List.filter (y -> y <= n) xs + ensure (List.all (y -> y <= x) ys) + None -> ensure (NatSet.Nonempty.getMin s > n) + +data.NatSet.Nonempty.getBelow : Nat -> NatSet.Nonempty -> Optional Nat +data.NatSet.Nonempty.getBelow k = cases + NatSet.Nonempty.Bin p m _ l r + | nomatch k p m -> + if k Nat.< p then None else Some (NatSet.Nonempty.getMax r) + | internal.zero k m -> data.NatSet.Nonempty.getBelow k l + | otherwise -> + match data.NatSet.Nonempty.getBelow k r with + Some x -> Some x + None -> data.NatSet.Nonempty.getBelow k l + NatSet.Nonempty.Tip p bm + | prefixOf k Nat.> p -> Some (p Nat.+ highBit bm) + | otherwise -> + use Nat != + - == + maskLT = Nat.and bm (bitmapOf k - 1) + if prefixOf k == p && maskLT != 0 then Some (p + highBit maskLT) + else None + +data.NatSet.Nonempty.getBelow.doc : Doc +data.NatSet.Nonempty.getBelow.doc = + use NatSet.Nonempty fromList getBelow + {{ + Returns the largest element in a {type NatSet.Nonempty} that is smaller than + a given one. + + Returns {None} if there is no such element. + + # Examples + + ``` + getBelow 2 (fromList (1 +| [2, 3])) + ``` + + ``` + getBelow 4 (fromList (1 +| [2, 3])) + ``` + + # See also + + * {NatSet.Nonempty.getAbove} to get the smallest element in a + {type NatSet.Nonempty} that is larger than the key. + * {NatSet.Nonempty.getAtMost} to get the largest element in a + {type NatSet.Nonempty} that is smaller than or equal to the key. + * {NatSet.Nonempty.getAtLeast} to get the smallest element in a + {type NatSet.Nonempty} that is larger than or equal to the key. + * {NatSet.Nonempty.getMax} to get the largest element in a + {type NatSet.Nonempty}. + * {NatSet.Nonempty.getMin} to get the smallest element in a + {type NatSet.Nonempty}. + }} + +test> data.NatSet.Nonempty.getBelow.test = test.verify do + use Nat < <= >= + use Random nat + _ = Each.range 0 100 + xs = Random.listOf nat do Random.natIn 1 100 + s = NatSet.Nonempty.fromList (Abort.toBug do List.nonempty xs) + n = nat() + match NatSet.Nonempty.getBelow n s with + Some x -> + ensure (x < n) + ys = List.filter (y -> y < n) xs + ensure (List.all (y -> y <= x) ys) + None -> ensure (NatSet.Nonempty.getMin s >= n) + +data.NatSet.Nonempty.getMax : NatSet.Nonempty -> Nat +data.NatSet.Nonempty.getMax = cases + NatSet.Nonempty.Tip p bm -> p Nat.+ highBit bm + NatSet.Nonempty.Bin p m _ l r -> data.NatSet.Nonempty.getMax r + +data.NatSet.Nonempty.getMax.doc : Doc +data.NatSet.Nonempty.getMax.doc = + {{ + Returns the largest element in a {type NatSet.Nonempty}. + + # Example + + ``` + NatSet.Nonempty.getMax (NatSet.Nonempty.fromList (1 +| [2, 3])) + ``` + + # See also + + * {NatSet.Nonempty.getMin} to get the smallest element. + * {NatSet.Nonempty.getBelow} to get the largest element that is strictly + smaller than a given key. + * {NatSet.Nonempty.getAbove} to get the smallest element that is larger + than a given key. + * {NatSet.Nonempty.getAtMost} to get the largest element that is smaller + than or equal to a given key. + * {NatSet.Nonempty.getAtLeast} to get the smallest element that is larger + than or equal to a given key. + }} + +test> data.NatSet.Nonempty.getMax.test = test.verify do + use Nat <= + _ = Each.range 0 100 + xs = Random.listOf Random.nat do Random.natIn 1 100 + s = NatSet.Nonempty.fromList (Abort.toBug do List.nonempty xs) + x = NatSet.Nonempty.getMax s + ensure (List.all (y -> y <= x) xs) + +data.NatSet.Nonempty.getMin : NatSet.Nonempty -> Nat +data.NatSet.Nonempty.getMin = cases + NatSet.Nonempty.Tip p bm -> p Nat.+ Nat.trailingZeros bm + NatSet.Nonempty.Bin p m _ l r -> data.NatSet.Nonempty.getMin l + +data.NatSet.Nonempty.getMin.doc : Doc +data.NatSet.Nonempty.getMin.doc = + {{ + Returns the smallest element in a {type NatSet.Nonempty}. + + # Example + + ``` + NatSet.Nonempty.getMin (NatSet.Nonempty.fromList (1 +| [2, 3])) + ``` + + # See also + + * {NatSet.Nonempty.getMax} to get the largest element. + * {NatSet.Nonempty.getBelow} to get the largest element that is strictly + smaller than a given key. + * {NatSet.Nonempty.getAbove} to get the smallest element that is larger + than a given key. + * {NatSet.Nonempty.getAtMost} to get the largest element that is smaller + than or equal to a given key. + * {NatSet.Nonempty.getAtLeast} to get the smallest element that is larger + than or equal to a given key. + }} + +test> data.NatSet.Nonempty.getMin.test = test.verify do + use Nat >= + _ = Each.range 0 100 + xs = Random.listOf Random.nat do Random.natIn 1 100 + s = NatSet.Nonempty.fromList (Abort.toBug do List.nonempty xs) + x = NatSet.Nonempty.getMin s + ensure (List.all (y -> y >= x) xs) + +data.NatSet.Nonempty.insert : Nat -> NatSet.Nonempty -> NatSet.Nonempty +data.NatSet.Nonempty.insert k = insertBitmap (prefixOf k) (bitmapOf k) + +data.NatSet.Nonempty.insert.doc : Doc +data.NatSet.Nonempty.insert.doc = + use NatSet.Nonempty fromList + {{ + Inserts an element into a {type NatSet.Nonempty}. + + # Example + + ``` + Nonempty.toListAscending (NatSet.Nonempty.insert 1 (fromList (2 +| [3]))) + ``` + + # See also + + * {NatSet.insert.nonempty} to insert an element into a {type NatSet}. + * {NatSet.union} to add a whole {type NatSet} into another. + * {NatSet.Nonempty.delete} to delete an element from a + {type NatSet.Nonempty}. + * {fromList} to create a {type NatSet.Nonempty} from a {type List.Nonempty} + of elements. + * {NatSet.Nonempty.singleton} to create a {type NatSet.Nonempty} with a + single element. + }} + +test> data.NatSet.Nonempty.insert.test.containsInserted = test.verify do + use Random nat + _ = Each.range 0 100 + xs = Random.listOf nat do Random.natIn 1 100 + n = nat() + s = NatSet.Nonempty.fromList (Abort.toBug do List.nonempty xs) + ensure (NatSet.Nonempty.contains n (NatSet.Nonempty.insert n s)) + +data.NatSet.Nonempty.internal.deleteBitmap : + Nat -> Nat -> NatSet.Nonempty -> NatSet +data.NatSet.Nonempty.internal.deleteBitmap prefix bitmap = cases + t@(NatSet.Nonempty.Bin p m _ l r) + | nomatch prefix p m -> NatSet (Some t) + | internal.zero prefix m -> + NatSet.internal.bin + p + m + (data.NatSet.Nonempty.internal.deleteBitmap prefix bitmap l) + (NatSet (Some r)) + | otherwise -> + NatSet.internal.bin + p + m + (NatSet (Some l)) + (data.NatSet.Nonempty.internal.deleteBitmap prefix bitmap r) + t@(NatSet.Nonempty.Tip p bm) + | prefix Nat.== p -> tip p (Nat.and bm (Nat.complement bitmap)) + | otherwise -> NatSet (Some t) + +data.NatSet.Nonempty.internal.deleteBitmap.doc : Doc +data.NatSet.Nonempty.internal.deleteBitmap.doc = + {{ + Internal function to delete a bitmap from a {type NatSet.Nonempty} tree. + }} + +data.NatSet.Nonempty.internal.insertBitmap : + Nat -> Nat -> NatSet.Nonempty -> NatSet.Nonempty +data.NatSet.Nonempty.internal.insertBitmap prefix bitmap = cases + t@(NatSet.Nonempty.Bin p m _ l r) + | nomatch prefix p m -> + NatSet.internal.link prefix (NatSet.Nonempty.Tip prefix bitmap) p t + | internal.zero prefix m -> + NatSet.internal.bim + p m (data.NatSet.Nonempty.internal.insertBitmap prefix bitmap l) r + | otherwise -> + NatSet.internal.bim + p m l (data.NatSet.Nonempty.internal.insertBitmap prefix bitmap r) + t@(NatSet.Nonempty.Tip p bm) + | prefix Nat.== p -> NatSet.Nonempty.Tip p (Nat.or bm bitmap) + | otherwise -> + NatSet.internal.link prefix (NatSet.Nonempty.Tip prefix bitmap) p t + +data.NatSet.Nonempty.internal.insertBitmap.doc : Doc +data.NatSet.Nonempty.internal.insertBitmap.doc = + {{ + Internal function to insert a bitmap into a {type NatSet.Nonempty} tree. + }} + +data.NatSet.Nonempty.intersect : NatSet.Nonempty -> NatSet.Nonempty -> NatSet +data.NatSet.Nonempty.intersect = cases + t1@(NatSet.Nonempty.Bin p1 m1 _ l1 r1), + t2@(NatSet.Nonempty.Bin p2 m2 _ l2 r2) + | shorter m1 m2 -> + if nomatch p2 p1 m1 then NatSet None + else + if internal.zero p2 m1 then data.NatSet.Nonempty.intersect l1 t2 + else data.NatSet.Nonempty.intersect r1 t2 + | shorter m2 m1 -> + if nomatch p1 p2 m2 then NatSet None + else + if internal.zero p1 m2 then data.NatSet.Nonempty.intersect t1 l2 + else data.NatSet.Nonempty.intersect t1 r2 + | p1 Nat.== p2 -> + NatSet.internal.bin + p1 + m1 + (data.NatSet.Nonempty.intersect l1 l2) + (data.NatSet.Nonempty.intersect r1 r2) + | otherwise -> NatSet None + t@(NatSet.Nonempty.Bin _ _ _ _ _), NatSet.Nonempty.Tip p m -> + intersectBitmap p m t + NatSet.Nonempty.Tip p m, t2 -> intersectBitmap p m t2 + +data.NatSet.Nonempty.intersect.doc : Doc +data.NatSet.Nonempty.intersect.doc = + use NatSet.Nonempty fromList + {{ + Removes all elements from one {type NatSet.Nonempty} that are not in another. + + Returns a {type NatSet} containing all elements that are present in both + inputs. + + # Example + + ``` + NatSet.toList + (NatSet.Nonempty.intersect (fromList (1 +| [2])) (fromList (2 +| [3]))) + ``` + + # See also + + * {NatSet.Nonempty.union} to return elements in __either__ input. + * {NatSet.Nonempty.difference} to return elements in the first input but + not the second. + * {NatSet.Nonempty.delete} to remove one element from a + {type NatSet.Nonempty}. + }} + +data.NatSet.Nonempty.map : + (Nat ->{g} Nat) -> NatSet.Nonempty ->{g} NatSet.Nonempty +data.NatSet.Nonempty.map f = + NatSet.Nonempty.foldMap + NatSet.Nonempty.union (NatSet.Nonempty.singleton << f) + +data.NatSet.Nonempty.map.doc : Doc +data.NatSet.Nonempty.map.doc = + use Nat * + {{ + Applies a function to every element in a {type NatSet.Nonempty}. + + # Example + + ``` + Nonempty.toListAscending + (NatSet.Nonempty.map ((*) 2) (NatSet.Nonempty.fromList (1 +| [2, 3, 4]))) + ``` + + # See also + + * {NatSet.Nonempty.foldMap} to apply a function to every element and + combine the results with a binary function. + * {NatSet.Nonempty.filterMap} to apply a function to every element and + filter out the results that are {None}. + * {NatSet.Nonempty.filter} to filter out elements that don't satisfy a + predicate. + * {NatSet.Nonempty.foldLeft} and {NatSet.Nonempty.foldRight} to summarize + the elements of a set. + }} + +data.NatSet.Nonempty.maxView : NatSet.Nonempty -> (Nat, NatSet) +data.NatSet.Nonempty.maxView = cases + NatSet.Nonempty.Bin p m _ l r -> + (x, r') = data.NatSet.Nonempty.maxView r + (x, NatSet.internal.bin p m (NatSet (Some l)) r') + NatSet.Nonempty.Tip p m -> + use Nat + + bi = highBit m + (p + bi, tip p (Nat.and m (Nat.complement (Nat.shiftLeft 1 bi)))) + +data.NatSet.Nonempty.maxView.doc : Doc +data.NatSet.Nonempty.maxView.doc = + {{ + Returns the maximum element in a {type NatSet.Nonempty}, and the set without + that element. + + # Example + + ``` + Tuple.second + NatSet.toList + (NatSet.Nonempty.maxView (NatSet.Nonempty.fromList (1 +| [2, 3, 4]))) + ``` + + # See also + + * {NatSet.Nonempty.minView} to get the minimum element and the set without + that element. + * {NatSet.Nonempty.getMax} to just get the maximum element. + * {NatSet.Nonempty.deleteMax} to get the set without the maximum element. + }} + +data.NatSet.Nonempty.minView : NatSet.Nonempty -> (Nat, NatSet) +data.NatSet.Nonempty.minView = cases + NatSet.Nonempty.Bin p m _ l r -> + (x, l') = data.NatSet.Nonempty.minView l + (x, NatSet.internal.bin p m l' (NatSet (Some r))) + NatSet.Nonempty.Tip p m -> + use Nat + + bi = Nat.trailingZeros m + (p + bi, tip p (Nat.and m (Nat.complement (Nat.shiftLeft 1 bi)))) + +data.NatSet.Nonempty.minView.doc : Doc +data.NatSet.Nonempty.minView.doc = + {{ + Returns the minimum element in a {type NatSet.Nonempty}, and the set without + that element. + + # Example + + ``` + Tuple.second + NatSet.toList + (NatSet.Nonempty.minView (NatSet.Nonempty.fromList (1 +| [2, 3, 4]))) + ``` + + # See also + + * {NatSet.Nonempty.maxView} to get the maximum element and the set without + that element. + * {NatSet.Nonempty.getMin} to just get the minimum element. + * {NatSet.Nonempty.deleteMin} to get the set without the minimum element. + }} + +data.NatSet.Nonempty.nth : Nat -> NatSet.Nonempty -> Optional Nat +data.NatSet.Nonempty.nth index = cases + NatSet.Nonempty.Tip p bm -> + if index Nat.< Nat.popCount bm then + use Nat + - trailingZeros + loop : Nat -> Nat -> Nat -> Nat + loop bm count acc = match count with + 0 -> acc + trailingZeros bm + otherwise -> + zeros = trailingZeros bm + newBM = Nat.shiftRight bm (zeros + 1) + loop newBM (count - 1) (acc + zeros + 1) + Some (p + loop bm index 0) + else None + NatSet.Nonempty.Bin prefix mask sz l r -> + use Nat - < + use data.NatSet.Nonempty nth + leftSize = NatSet.Nonempty.size l + if index < leftSize then nth index l else nth (index - leftSize) r + +data.NatSet.Nonempty.nth.doc : Doc +data.NatSet.Nonempty.nth.doc = + use NatSet.Nonempty nth + {{ + {{ docExample 2 do i s -> nth i s }} returns the `i`-th smallest value in + `s`, where `i`=0 is the smallest value (according to {Universal.ordering}). + + Is the same as {{ + docExample 2 do i as -> List.at i (List.sort (NatSet.toList as)) }} but + doesn't require instantiating the intermediate {type List}. + + ``` + s = NatSet.Nonempty.fromList (Nonempty.Nonempty 6 [5, 4, 2, 1]) + List.map (i -> nth i s) (List.range 0 (NatSet.Nonempty.size s)) + ``` + }} + +test> data.NatSet.Nonempty.nth.tests = + test.verify do + use Random natIn + Each.repeat 100 + s = + natIn 0 10 +| (List.replicate (natIn 0 19) do natIn 0 10) + |> NatSet.Nonempty.fromList + ensure + (List.somes + (List.map + (i -> NatSet.Nonempty.nth i s) + (List.range 0 (NatSet.Nonempty.size s))) + === (Nonempty.toListAscending s |> List.Nonempty.toList)) + +data.NatSet.Nonempty.ordering : NatSet.Nonempty -> NatSet.Nonempty -> Ordering +data.NatSet.Nonempty.ordering m1 m2 = + use Nat + - < <= == > >= and + use NatSet.Nonempty Bin Tip getMax size split + use Universal ordering + go = cases + Tip p1 m1, Tip p2 m2 -> + Ordering.andThen + (ordering p1 p2) (if Nat.isPrefixOf m1 m2 then Equal + else + diff = Nat.xor m1 m2 + lowest = and diff (twosComplement diff) + if and lowest m2 == 0 then Less else Greater) + t1@(Bin p1 mask1 sz1 l1 r1), t2@(Bin p2 mask2 sz2 l2 r2) -> + largest1 = p1 + mask1 - 1 + largest2 = p2 + mask2 - 1 + if largest1 < p2 then Less + else + if largest2 < p1 then Greater + else + match go l1 l2 with + Equal + | size l1 < size l2 -> + match split (getMax l1) l2 with + (_, NatSet None) -> Equal + (_, NatSet (Some l2r)) -> + match go r1 l2r with + Equal + | size r1 <= size l2r -> Less + | otherwise -> + match split (getMax l2) r1 with + (_, NatSet None) -> Equal + (_, NatSet (Some r1r)) -> go r1r r2 + x -> x + | size l1 > size l2 -> + match split (getMax l2) l1 with + (_, NatSet None) -> Equal + (_, NatSet (Some l1r)) -> + match go l1r r2 with + Equal + | size l1r >= size r2 -> Greater + | otherwise -> + match split (getMax l1) r2 with + (_, NatSet None) -> Equal + (_, NatSet (Some r2r)) -> go r1 r2r + x -> x + | otherwise -> go r1 r2 + x -> x + t1@(Tip p1 _), Bin p2 m2 _ l2 r2 -> go t1 l2 + Bin p1 m1 _ l1 r1, t2@(Tip p2 _) -> go l1 t2 + match go m1 m2 with + Equal -> ordering (size m1) (size m2) + x -> x + +data.NatSet.Nonempty.partition : + (Nat ->{g} Boolean) -> NatSet.Nonempty ->{g} (NatSet, NatSet) +data.NatSet.Nonempty.partition f = cases + NatSet.Nonempty.Bin p m _ l r -> + (l1, l2) = data.NatSet.Nonempty.partition f l + (r1, r2) = data.NatSet.Nonempty.partition f r + (NatSet.internal.bin p m l1 r1, NatSet.internal.bin p m l2 r2) + NatSet.Nonempty.Tip p m -> + bm1 = foldBitsLeft 0 (bitPred f p) 0 m + (tip p bm1, tip p (Nat.xor m bm1)) + +data.NatSet.Nonempty.partition.doc : Doc +data.NatSet.Nonempty.partition.doc = + {{ + Partitions a {type NatSet.Nonempty} by a predicate. Returns a pair of sets, + the first containing all elements that satisfy the predicate, and the second + containing all elements that do not. + + # Example + + ``` + Tuple.bimap + NatSet.toList + (NatSet.Nonempty.partition + Nat.isEven (NatSet.Nonempty.fromList (1 +| [2, 3, 4]))) + ``` + + # See also + + * {NatSet.Nonempty.filter} to remove all elements that do not satisfy the + predicate. + * {NatSet.Nonempty.filterMap} to filter a set by a function that returns an + {type Optional} value. + * {NatSet.Nonempty.map} to apply a function to every element in a set, and + return a new set with the results. + * {NatSet.Nonempty.split} to split a set based on whether elements are less + than or greater than a given element. + }} + +data.NatSet.Nonempty.properSubset : + NatSet.Nonempty -> NatSet.Nonempty -> Boolean +data.NatSet.Nonempty.properSubset = cases + t1, t2 -> + match Nonempty.subsetCompare t1 t2 with + Some Less -> true + _ -> false + +data.NatSet.Nonempty.properSubset.doc : Doc +data.NatSet.Nonempty.properSubset.doc = + use NatSet.Nonempty fromList + use Nonempty properSubset + {{ + Checks if one {type NatSet.Nonempty} is a proper subset of another. + + A set is a proper subset of another if it contains only elements that are + also in the other set, and the two sets are not equal. + + # Examples + + A set is not a proper subset of itself: + + ``` + properSubset (fromList (1 +| [2])) (fromList (1 +| [2])) + ``` + + A set is a proper subset of another if it contains only elements that are + also in the other set: + + ``` + properSubset (fromList (1 +| [2])) (fromList (1 +| [2, 3])) + ``` + + A set is not a proper subset of another if it contains elements that are + not in the other set: + + ``` + properSubset (fromList (1 +| [2, 3])) (fromList (1 +| [2])) + ``` + + # See also + + * {NatSet.Nonempty.subset} for a version of this that returns `` true `` + when the inputs are equal. + * {Nonempty.subsetCompare} to compare two sets to see if either one is a + subset of the other. + * {Nonempty.disjoint} to check if two sets have no elements in common. + }} + +data.NatSet.Nonempty.properSuperset : + NatSet.Nonempty -> NatSet.Nonempty -> Boolean +data.NatSet.Nonempty.properSuperset t1 t2 = Nonempty.properSubset t2 t1 + +data.NatSet.Nonempty.properSuperset.doc : Doc +data.NatSet.Nonempty.properSuperset.doc = + use NatSet.Nonempty fromList + use Nonempty properSuperset + {{ + Checks if one {type NatSet.Nonempty} is a proper superset of another. + + A set is a proper superset of another if it contains all elements in the + other set, and the two sets are not equal. + + # Examples + + A set is not a proper superset of itself: + + ``` + properSuperset (fromList (1 +| [2])) (fromList (1 +| [2])) + ``` + + A set is a proper superset of another if it contains all elements in the + other set: + + ``` + properSuperset (fromList (1 +| [2, 3])) (fromList (1 +| [2])) + ``` + + A set is not a proper superset of another it doesn't contain all elements + in the other set: + + ``` + properSuperset (fromList (1 +| [2])) (fromList (1 +| [2, 3])) + ``` + + # See also + + * {NatSet.Nonempty.superset} for a version of this that returns `` true `` + when the inputs are equal. + * {Nonempty.subsetCompare} to compare two sets to see if either one is a + superset of the other. + * {Nonempty.disjoint} to check if two sets have no elements in common. + }} + +data.NatSet.Nonempty.randomChoice : NatSet.Nonempty ->{Random} Nat +data.NatSet.Nonempty.randomChoice set = + randomIndex = Random.natIn 0 (NatSet.Nonempty.size set) + NatSet.Nonempty.nth randomIndex set + |> getOrBug "NatSet.Nonempty.randomChoice: index out of bounds" + +data.NatSet.Nonempty.randomChoice.doc : Doc +data.NatSet.Nonempty.randomChoice.doc = + use NatSet.Nonempty fromList randomChoice + use Nonempty Nonempty + {{ + Returns a random {type Nat} from the given {type NatSet.Nonempty}. + + # Examples + + ``` + lcg 4096 do randomChoice (fromList (Nonempty 0 [3, 5, 7])) + ``` + + ``` + lcg 2510 do randomChoice (fromList (Nonempty 0 [3, 5, 7])) + ``` + }} + +test> data.NatSet.Nonempty.randomChoice.test = test.verify do + set = NatSet.Nonempty.fromList (0 +| [1, 2, 3, 4, 5, 6, 7, 8, 9]) + Each.repeat 1000 + e = NatSet.Nonempty.randomChoice set + ensure (NatSet.Nonempty.contains e set) + +data.NatSet.Nonempty.size : NatSet.Nonempty -> Nat +data.NatSet.Nonempty.size = cases + NatSet.Nonempty.Bin _ _ sz l r -> sz + NatSet.Nonempty.Tip _ bm -> Nat.popCount bm + +data.NatSet.Nonempty.size.doc : Doc +data.NatSet.Nonempty.size.doc = + {{ + Returns the number of elements in a {type NatSet.Nonempty}. + + The complexity of this function is `O(n)` where `n` is the number of elements + in the set. However, the constant factor `1/64` is very small, so this + function is quite fast for small sets. + + # Example + + ``` + NatSet.Nonempty.size (NatSet.Nonempty.fromList (1 +| [2])) + ``` + }} + +data.NatSet.Nonempty.split : Nat -> NatSet.Nonempty -> (NatSet, NatSet) +data.NatSet.Nonempty.split x = cases + t@(NatSet.Nonempty.Bin p m _ l r) + | mask x m Nat.== p -> + if internal.zero x m then + (lt, gt) = data.NatSet.Nonempty.split x l + (lt, NatSet.union gt (NatSet (Some r))) + else + (lt, gt) = data.NatSet.Nonempty.split x r + (NatSet.union (NatSet (Some l)) lt, gt) + | otherwise -> + if x Nat.< p then (NatSet None, NatSet (Some t)) + else (NatSet (Some t), NatSet None) + t@(NatSet.Nonempty.Tip p m) + | p Nat.> x -> (NatSet None, NatSet (Some t)) + | p Nat.< prefixOf x -> (NatSet (Some t), NatSet None) + | otherwise -> + use Nat + - and + lowerBitmap = bitmapOf x - 1 + upperBitmap = Nat.complement (lowerBitmap + bitmapOf x) + (tip p (and m lowerBitmap), tip p (and m upperBitmap)) + +data.NatSet.Nonempty.split.doc : Doc +data.NatSet.Nonempty.split.doc = + {{ + Splits a {type NatSet.Nonempty} into two sets based on whether elements are + less than or greater than a given element. + + The first set contains all elements that are less than the given element, and + the second set contains all elements that are greater than the given element. + The given element is not included in either set. + + # Example + + ``` + Tuple.bimap + NatSet.toList + (NatSet.Nonempty.split 3 (NatSet.Nonempty.fromList (1 +| [2, 3, 4]))) + ``` + + # See also + + * {Nonempty.splitContains} to also check whether the given element is in + the set. + * {NatSet.Nonempty.partition} to split a set into two sets based on whether + elements satisfy a predicate. + * {NatSet.Nonempty.filter} to remove all elements that do not satisfy a + predicate. + * {NatSet.Nonempty.filterMap} to filter a set by a function that returns an + {type Optional} value. + * {NatSet.Nonempty.map} to apply a function to every element in a set, and + return a new set with the results. + }} + +data.NatSet.Nonempty.splitContains : + Nat -> NatSet.Nonempty -> (NatSet, Boolean, NatSet) +data.NatSet.Nonempty.splitContains x = cases + t@(NatSet.Nonempty.Bin p m _ l r) + | mask x m Nat.== p -> + if internal.zero x m then + (lt, b, gt) = data.NatSet.Nonempty.splitContains x l + (lt, b, NatSet.union gt (NatSet (Some r))) + else + (lt, b, gt) = data.NatSet.Nonempty.splitContains x r + (NatSet.union (NatSet (Some l)) lt, b, gt) + | otherwise -> + if x Nat.< p then (NatSet None, false, NatSet (Some t)) + else (NatSet (Some t), false, NatSet None) + t@(NatSet.Nonempty.Tip p m) + | p Nat.> x -> (NatSet None, false, NatSet (Some t)) + | p Nat.< prefixOf x -> (NatSet (Some t), false, NatSet None) + | otherwise -> + use Nat != + - and + lowerBitmap = bitmapOf x - 1 + upperBitmap = Nat.complement (lowerBitmap + bitmapOf x) + ( tip p (and m lowerBitmap) + , and m (bitmapOf x) != 0 + , tip p (and m upperBitmap) + ) + +data.NatSet.Nonempty.splitContains.doc : Doc +data.NatSet.Nonempty.splitContains.doc = + use NatSet toList + {{ + Splits a {type NatSet.Nonempty} into two sets based on whether elements are + less than or greater than a given element, and returns whether the given + element is in the set. + + The first set in the result contains all elements that are less than the + given element, and the other set contains all elements that are greater than + the given element. The element is not included in either set, but the middle + element of the result is a {type Boolean} indicating whether the element is + in the set. + + # Example + + ``` + (l, b, r) = + Nonempty.splitContains 3 (NatSet.Nonempty.fromList (1 +| [2, 3, 4])) + (toList l, b, toList r) + ``` + + # See also + + * {NatSet.Nonempty.split} for a version of this function that does not + return whether the element is in the set. + * {NatSet.Nonempty.partition} to split a set into two sets based on whether + elements satisfy a predicate. + * {NatSet.Nonempty.filter} to remove all elements that do not satisfy a + predicate. + * {NatSet.Nonempty.filterMap} to filter a set by a function that returns an + {type Optional} value. + * {NatSet.Nonempty.map} to apply a function to every element in a set, and + return a new set with the results. + }} + +data.NatSet.Nonempty.subset : NatSet.Nonempty -> NatSet.Nonempty -> Boolean +data.NatSet.Nonempty.subset = cases + t1, t2 -> + match Nonempty.subsetCompare t1 t2 with + Some Less -> true + Some Equal -> true + _ -> false + +data.NatSet.Nonempty.subset.doc : Doc +data.NatSet.Nonempty.subset.doc = + use NatSet.Nonempty fromList subset + {{ + Checks if one {type NatSet.Nonempty} is a subset of another. + + A set is a subset of another if it contains only elements that are also in + the other set. + + # Examples + + Every set is a subset of itself: + + ``` + subset (fromList (1 +| [2])) (fromList (1 +| [2])) + ``` + + A set is a subset of another if it contains only elements that are also in + the other set: + + ``` + subset (fromList (1 +| [2])) (fromList (1 +| [2, 3])) + ``` + + A set is not a subset of another if it contains elements that are not in + the other set: + + ``` + subset (fromList (1 +| [2, 3])) (fromList (1 +| [2])) + ``` + + # See also + + * {Nonempty.properSubset} for a version of this that returns `` false `` + when the inputs are equal. + * {Nonempty.subsetCompare} to compare two sets to see if either one is a + subset of the other. + * {Nonempty.disjoint} to check if two sets have no elements in common. + }} + +data.NatSet.Nonempty.subsetCompare : + NatSet.Nonempty -> NatSet.Nonempty -> Optional Ordering +data.NatSet.Nonempty.subsetCompare = cases + t1@(NatSet.Nonempty.Bin p1 m1 _ l1 r1), + t2@(NatSet.Nonempty.Bin p2 m2 _ l2 r2) + | shorter m1 m2 -> + if nomatch p2 p1 m1 then None + else + if internal.zero p2 m1 then + match data.NatSet.Nonempty.subsetCompare l1 t2 with + Some Greater -> Some Greater + Some Equal -> Some Greater + _ -> None + else + match data.NatSet.Nonempty.subsetCompare r1 t2 with + Some Greater -> Some Greater + Some Equal -> Some Greater + _ -> None + | shorter m2 m1 -> + if nomatch p1 p2 m2 then None + else + if internal.zero p1 m2 then + match data.NatSet.Nonempty.subsetCompare t1 l2 with + Some Less -> Some Less + Some Equal -> Some Less + _ -> None + else + match data.NatSet.Nonempty.subsetCompare t1 r2 with + Some Less -> Some Less + Some Equal -> Some Less + _ -> None + | p1 Nat.== p2 -> + use data.NatSet.Nonempty subsetCompare + lc = subsetCompare l1 l2 + rc = subsetCompare r1 r2 + match (lc, rc) with + (Some Equal, Some Equal) -> Some Equal + (Some Less, Some Less) -> Some Less + (Some Greater, Some Greater) -> Some Greater + (Some Less, Some Equal) -> Some Less + (Some Equal, Some Less) -> Some Less + (Some Greater, Some Equal) -> Some Greater + (Some Equal, Some Greater) -> Some Greater + _ -> None + | otherwise -> None + NatSet.Nonempty.Tip p1 m1, NatSet.Nonempty.Tip p2 m2 + | p1 Nat.!= p2 -> None + | m1 Nat.== m2 -> Some Equal + | Nat.and m1 (Nat.complement m2) Nat.== 0 -> Some Less + | Nat.and m2 (Nat.complement m1) Nat.== 0 -> Some Greater + | otherwise -> None + t@(NatSet.Nonempty.Tip p1 _), NatSet.Nonempty.Bin p2 m2 _ l2 r2 + | nomatch p1 p2 m2 -> None + | internal.zero p1 m2 -> + match data.NatSet.Nonempty.subsetCompare t l2 with + Some Less -> Some Less + Some Equal -> Some Less + _ -> None + | otherwise -> + match data.NatSet.Nonempty.subsetCompare t r2 with + Some Less -> Some Less + Some Equal -> Some Less + _ -> None + t1@(NatSet.Nonempty.Bin _ _ _ _ _), t2@(NatSet.Nonempty.Tip _ _) -> + Optional.map Ordering.inverse (data.NatSet.Nonempty.subsetCompare t2 t1) + +data.NatSet.Nonempty.subsetCompare.doc : Doc +data.NatSet.Nonempty.subsetCompare.doc = + use NatSet.Nonempty fromList + use Nonempty subsetCompare + {{ + Compares two {type NatSet.Nonempty} values to see if one is a subset of the + other. + + Returns `` Some Less `` if the first input is a subset of the second, `` + Some Greater `` if the second input is a subset of the first, and `` + Some Equal `` if the inputs are the same set. Returns `` None `` if the sets + are not comparable (i.e. each has elements that are not in the other). + + # Examples + + ``` + subsetCompare (fromList (1 +| [2])) (fromList (2 +| [3])) + ``` + + ``` + subsetCompare (fromList (1 +| [2])) (fromList (1 +| [2])) + ``` + + ``` + subsetCompare (fromList (1 +| [2])) (fromList (1 +| [2, 3])) + ``` + + ``` + subsetCompare (fromList (1 +| [2, 3])) (fromList (1 +| [2])) + ``` + + # See also + + * {NatSet.Nonempty.subset} to check if one set is a subset of another. + * {Nonempty.disjoint} to check if two {type NatSet.Nonempty} have no + elements in common. + }} + +data.NatSet.Nonempty.superset : NatSet.Nonempty -> NatSet.Nonempty -> Boolean +data.NatSet.Nonempty.superset t1 t2 = NatSet.Nonempty.subset t2 t1 + +data.NatSet.Nonempty.superset.doc : Doc +data.NatSet.Nonempty.superset.doc = + use NatSet.Nonempty fromList superset + {{ + Checks if one {type NatSet.Nonempty} is a superset of another. + + A set is a superset of another if it contains all elements in the other set. + + # Examples + + Every set is a superset of itself: + + ``` + superset (fromList (1 +| [2])) (fromList (1 +| [2])) + ``` + + A set is a superset of another if it contains all elements in the other + set: + + ``` + superset (fromList (1 +| [2, 3])) (fromList (1 +| [2])) + ``` + + A set is not a superset of another it doesn't contain all elements in the + other set: + + ``` + superset (fromList (1 +| [2])) (fromList (1 +| [2, 3])) + ``` + + # See also + + * {Nonempty.properSuperset} for a version of this that returns `` false `` + when the inputs are equal. + * {Nonempty.subsetCompare} to compare two sets to see if either one is a + superset of the other. + * {Nonempty.disjoint} to check if two sets have no elements in common. + }} + +data.NatSet.Nonempty.tips : NatSet.Nonempty -> List.Nonempty NatSet.Nonempty +data.NatSet.Nonempty.tips = cases + NatSet.Nonempty.Bin _ _ _ l r -> + data.NatSet.Nonempty.tips l Nonempty.++ data.NatSet.Nonempty.tips r + t@(NatSet.Nonempty.Tip _ _) -> List.Nonempty.singleton t + +data.NatSet.Nonempty.tips.doc : Doc +data.NatSet.Nonempty.tips.doc = + use Nonempty tips + {{ + Returns a list of all the leaves in a {type NatSet.Nonempty}. + + Each leaf is a {type NatSet.Nonempty} with a prefix and a bitmap. The prefix + is the 58-bit common prefix of all numbers in the set. The 64-bit bitmap + indicates which numbers with that prefix are in the set. If the prefix is `p` + and bit `n` is set in the bitmap, then the number `p + n` is in the set. + + # Examples + + ``` + tips (NatSet.Nonempty.singleton 8) + ``` + + ``` + tips (NatSet.Nonempty.fromList (0 +| [1, 2, 4, 8, 16, 32, 64, 128])) + ``` + }} + +data.NatSet.Nonempty.toList : NatSet.Nonempty -> List.Nonempty Nat +data.NatSet.Nonempty.toList = + use Nonempty ++ + NatSet.Nonempty.foldMap (++) List.Nonempty.singleton + +data.NatSet.Nonempty.toList.doc : Doc +data.NatSet.Nonempty.toList.doc = + use NatSet.Nonempty foldRight + {{ + Converts a {type NatSet.Nonempty} to a {type List.Nonempty} of its elements. + + # Example + + ``` + NatSet.Nonempty.toList (NatSet.Nonempty.fromList (1 +| [2, 3, 4])) + ``` + + # See also + + * {NatSet.Nonempty.foldMap} to apply a function to every element and + combine the results. + * {foldRight} to accumulate results of a single binary function applied to + every element and the result so far, associating to the right. + * {NatSet.Nonempty.foldLeft} same as {foldRight} but associating to the + left. + * {NatSet.Nonempty.map} to apply a function to every element without + combining the results. + * {NatSet.Nonempty.filter} to filter out elements that don't satisfy a + predicate. + * {NatSet.Nonempty.filterMap} to apply a function to every element and + filter out the results that are {None}. + }} + +data.NatSet.Nonempty.toListDescending : NatSet.Nonempty -> List.Nonempty Nat +data.NatSet.Nonempty.toListDescending s = + Abort.toBug do List.nonempty << NatSet.toListDescending << NatSet <| Some s + +data.NatSet.Nonempty.toListDescending.doc : Doc +data.NatSet.Nonempty.toListDescending.doc = + {{ + Converts a {type NatSet.Nonempty} to a {type List.Nonempty} of its elements + in descending order. + + # Example + + ``` + Nonempty.toListDescending (NatSet.Nonempty.fromList (1 +| [2, 3, 4])) + ``` + + # See also + + * {Nonempty.toListAscending} to convert to a {type List.Nonempty} in + ascending order. + }} + +data.NatSet.Nonempty.toNatSet : NatSet.Nonempty -> NatSet +data.NatSet.Nonempty.toNatSet = NatSet << Some + +data.NatSet.Nonempty.toNatSet.doc : Doc +data.NatSet.Nonempty.toNatSet.doc = + {{ + Converts a {type NatSet.Nonempty} to a {type NatSet}. + + # Example + + ``` + NatSet.isEmpty + (NatSet.Nonempty.toNatSet (NatSet.Nonempty.fromList (1 +| [2, 3, 4]))) + ``` + }} + +data.NatSet.Nonempty.union : + NatSet.Nonempty -> NatSet.Nonempty -> NatSet.Nonempty +data.NatSet.Nonempty.union = cases + t1@(NatSet.Nonempty.Bin p1 m1 _ l1 r1), + t2@(NatSet.Nonempty.Bin p2 m2 _ l2 r2) + | shorter m1 m2 -> + if nomatch p2 p1 m1 then NatSet.internal.link p1 t1 p2 t2 + else + if internal.zero p2 m1 then + NatSet.internal.bim p1 m1 (data.NatSet.Nonempty.union l1 t2) r1 + else NatSet.internal.bim p1 m1 l1 (data.NatSet.Nonempty.union r1 t2) + | shorter m2 m1 -> + if nomatch p1 p2 m2 then NatSet.internal.link p1 t1 p2 t2 + else + if internal.zero p1 m2 then + NatSet.internal.bim p2 m2 (data.NatSet.Nonempty.union t1 l2) r2 + else NatSet.internal.bim p2 m2 l2 (data.NatSet.Nonempty.union t1 r2) + | p1 Nat.== p2 -> + NatSet.internal.bim + p1 + m1 + (data.NatSet.Nonempty.union l1 l2) + (data.NatSet.Nonempty.union r1 r2) + | otherwise -> NatSet.internal.link p1 t1 p2 t2 + t@(NatSet.Nonempty.Bin _ _ _ _ _), NatSet.Nonempty.Tip p m -> + insertBitmap p m t + NatSet.Nonempty.Tip p m, t -> insertBitmap p m t + +data.NatSet.Nonempty.union.doc : Doc +data.NatSet.Nonempty.union.doc = + use NatSet.Nonempty fromList + {{ + Adds all elements from one {type NatSet.Nonempty} to another. If an element + is present in either input, it will be present in the result. + + # Example + + ``` + Nonempty.toListAscending + (NatSet.Nonempty.union (fromList (1 +| [2])) (fromList (2 +| [3]))) + ``` + + # See also + + * {NatSet.Nonempty.difference} to remove all elements from a + {type NatSet.Nonempty} that are in another. + * {NatSet.Nonempty.intersect} to remove all elements from a + {type NatSet.Nonempty} that are not in another. + * {NatSet.Nonempty.insert} to add one element into a + {type NatSet.Nonempty}. + }} + +data.NatSet.Nonempty.unions : List.Nonempty NatSet.Nonempty -> NatSet.Nonempty +data.NatSet.Nonempty.unions = reduceLeft NatSet.Nonempty.union + +data.NatSet.Nonempty.unions.doc : Doc +data.NatSet.Nonempty.unions.doc = + use NatSet.Nonempty fromList + {{ + Puts all elements from a {type List.Nonempty} of {type NatSet.Nonempty}s into + a single {type NatSet.Nonempty}. If an element is present in any of the + inputs, it will be present in the result. + + # Example + + ``` + Nonempty.toListAscending + (NatSet.Nonempty.unions (fromList (1 +| [2]) +| [fromList (2 +| [3])])) + ``` + + # See also + + * {NatSet.Nonempty.union} to union two {type NatSet.Nonempty}s. + }} + +data.NatSet.ordering : NatSet -> NatSet -> Ordering +data.NatSet.ordering = cases + NatSet (Some s1), NatSet (Some s2) -> Nonempty.ordering s1 s2 + NatSet None, NatSet None -> Equal + NatSet None, NatSet (Some _) -> Less + NatSet (Some _), NatSet None -> Greater + +data.NatSet.ordering.doc : Doc +data.NatSet.ordering.doc = + use NatSet == fromList ordering + {{ + Compares two {type NatSet} for lexicographical {type Ordering}. + + Returns {Less} if the smallest element that differs between the two + {type NatSet}s is less in the first {type NatSet}, and {Greater} if it is + greater. If one {type NatSet} is a prefix of the other, the smaller + {type NatSet} is {Less}. If the two {type NatSet}s have the same elements, + returns {Equal}. + + # Examples + + ``` + ordering (fromList [1, 2, 3, 4]) (fromList [1, 2, 3, 4]) + ``` + + ``` + ordering (fromList [1, 2, 3, 4]) (fromList [1, 2, 3, 5]) + ``` + + ``` + ordering (fromList [1, 2, 3, 4]) (fromList [1, 2, 3]) + ``` + + # See also + + * {==} to check if two {type NatSet} are equal. + * {NatSet.subsetCompare} to compare two {type NatSet} for subset ordering. + * {NatSet.subset} to check if one {type NatSet} is a subset of another. + * {NatSet.disjoint} to check if two {type NatSet} have no elements in + common. + }} + +test> data.NatSet.ordering.test = + test.verify do + use List size + use NatSet fromList toList + use Random listOf natIn + _ = Each.range 0 100 + xs = listOf (do natIn 0 1000) do natIn 0 100 + ys = listOf (do natIn 0 1000) do natIn 0 100 + s1 = fromList xs + s2 = fromList ys + l1 = toList s1 + l2 = toList s2 + lc = List.zipWith Universal.ordering l1 l2 + lo = + List.foldRight + (x acc -> Ordering.andThen x acc) + (Universal.ordering (size l1) (size l2)) + lc + so = NatSet.ordering s1 s2 + ensureEqual lo so + +data.NatSet.partition : (Nat ->{g} Boolean) -> NatSet ->{g} (NatSet, NatSet) +data.NatSet.partition f = cases + NatSet (Some t) -> NatSet.Nonempty.partition f t + NatSet None -> (NatSet None, NatSet None) + +data.NatSet.partition.doc : Doc +data.NatSet.partition.doc = + {{ + Partitions a {type NatSet} by a predicate. Returns a pair of sets, the first + containing all elements that satisfy the predicate, and the second containing + all elements that do not. + + # Example + + ``` + Tuple.bimap + NatSet.toList + (NatSet.partition Nat.isEven (NatSet.fromList [1, 2, 3, 4])) + ``` + + # See also + + * {NatSet.filter} to remove all elements that do not satisfy the predicate. + * {NatSet.filterMap} to filter a set by a function that returns an + {type Optional} value. + * {NatSet.map} to apply a function to every element in a set, and return a + new set with the results. + * {NatSet.Nonempty.split} to split a set based on whether elements are less + than or greater than a given element. + }} + +test> data.NatSet.partition.test = + test.verify do + use List filter + use Nat == + use NatSet fromList + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 0 100 + f = natIn 0 100 + ensure + (NatSet.partition ((==) f) (fromList xs) + === ( fromList (filter ((==) f) xs) + , fromList (filter (Boolean.not << (==) f) xs) + )) + +data.NatSet.properSubset : NatSet -> NatSet -> Boolean +data.NatSet.properSubset = cases + NatSet (Some t1), NatSet (Some t2) -> Nonempty.properSubset t1 t2 + NatSet None, NatSet None -> false + NatSet None, NatSet (Some _) -> true + NatSet (Some _), NatSet None -> false + +data.NatSet.properSubset.doc : Doc +data.NatSet.properSubset.doc = + use NatSet fromList properSubset + {{ + Checks if one {type NatSet} is a proper subset of another. + + A set is a proper subset of another if it contains only elements that are + also in the other set, and the two sets are not equal. + + # Examples + + A set is not a proper subset of itself: + + ``` + properSubset (fromList [1, 2]) (fromList [1, 2]) + ``` + + A set is a proper subset of another if it contains only elements that are + also in the other set: + + ``` + properSubset (fromList [1, 2]) (fromList [1, 2, 3]) + ``` + + A set is not a proper subset of another if it contains elements that are + not in the other set: + + ``` + properSubset (fromList [1, 2, 3]) (fromList [1, 2]) + ``` + + # See also + + * {NatSet.subset} for a version of this that returns `` true `` when the + inputs are equal. + * {NatSet.subsetCompare} to compare two sets to see if either one is a + subset of the other. + * {NatSet.disjoint} to check if two sets have no elements in common. + }} + +data.NatSet.properSuperset : NatSet -> NatSet -> Boolean +data.NatSet.properSuperset t1 t2 = NatSet.properSubset t2 t1 + +data.NatSet.properSuperset.doc : Doc +data.NatSet.properSuperset.doc = + use NatSet fromList properSuperset + {{ + Checks if one {type NatSet} is a proper superset of another. + + A set is a proper superset of another if it contains all elements in the + other set, and the two sets are not equal. + + # Examples + + A set is not a proper superset of itself: + + ``` + properSuperset (fromList [1, 2]) (fromList [1, 2]) + ``` + + A set is a proper superset of another if it contains all elements in the + other set: + + ``` + properSuperset (fromList [1, 2, 3]) (fromList [1, 2]) + ``` + + A set is not a proper superset of another it doesn't contain all elements + in the other set: + + ``` + properSuperset (fromList [1, 2]) (fromList [1, 2, 3]) + ``` + + # See also + + * {NatSet.superset} for a version of this that returns `` true `` when the + inputs are equal. + * {NatSet.subsetCompare} to compare two sets to see if either one is a + superset of the other. + * {NatSet.disjoint} to check if two sets have no elements in common. + }} + +data.NatSet.randomChoice : NatSet ->{Exception, Random} Nat +data.NatSet.randomChoice = cases + NatSet internalSet -> + Optional.map (s -> NatSet.Nonempty.randomChoice s) internalSet + |> Optional.toException + "NatSet.randomChoice: empty set" (typeLink NatSet) + +data.NatSet.randomChoice.doc : Doc +data.NatSet.randomChoice.doc = + use NatSet fromList randomChoice + {{ + Returns a random {type Nat} from the given {type NatSet}. Assumes that the + {type NatSet} is not empty, so an empty {type NatSet} will cause a runtime + exception. + + # Examples + + ``` + catch do lcg 4096 do randomChoice (fromList [0, 3, 5, 7]) + ``` + + ``` + catch do lcg 2510 do randomChoice (fromList [0, 3, 5, 7]) + ``` + }} + +test> data.NatSet.randomChoice.test = test.verify do + set = NatSet.fromList [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + Each.repeat 1000 + e = NatSet.randomChoice set + ensure (NatSet.contains e set) + +data.NatSet.singleton : Nat -> NatSet.Nonempty +data.NatSet.singleton k = NatSet.Nonempty.Tip (prefixOf k) (bitmapOf k) + +data.NatSet.singleton.doc : Doc +data.NatSet.singleton.doc = + {{ + Creates a {type NatSet.Nonempty} with a single element. + + # Example + + ``` + Nonempty.toListAscending (NatSet.singleton 1) + ``` + + # See also + + * {NatSet.fromList} to create a {type NatSet} from a {type List} of + elements. + * {NatSet.Nonempty.fromList} to create a {type NatSet.Nonempty} from a + {type List.Nonempty} of elements. + * {NatSet.empty} for the empty {type NatSet}. + * {NatSet.insert.nonempty} to add an element to a {type NatSet}. + }} + +data.NatSet.size : NatSet -> Nat +data.NatSet.size = cases + NatSet (Some t) -> NatSet.Nonempty.size t + NatSet None -> 0 + +data.NatSet.size.doc : Doc +data.NatSet.size.doc = + {{ + Returns the number of elements in a {type NatSet}. + + The complexity of this function is `O(n)` where `n` is the number of elements + in the set. However, the constant factor `1/64` is very small, so this + function is quite fast for small sets. + + # Example + + ``` + NatSet.size (NatSet.fromList [1, 2]) + ``` + }} + +test> data.NatSet.size.test = test.verify do + len = Random.natIn 0 1000 + xs = Random.listOf Random.nat do len + s = NatSet.fromList xs + ensureEqual (NatSet.size s) (List.size (NatSet.toList s)) + +data.NatSet.split : Nat -> NatSet -> (NatSet, NatSet) +data.NatSet.split x = cases + NatSet (Some t) -> NatSet.Nonempty.split x t + NatSet None -> (NatSet None, NatSet None) + +data.NatSet.split.doc : Doc +data.NatSet.split.doc = + {{ + Splits a {type NatSet} into two sets based on whether elements are less than + or greater than a given element. + + The first set contains all elements that are less than the given element, and + the second set contains all elements that are greater than the given element. + The given element is not included in either set. + + # Example + + ``` + Tuple.bimap NatSet.toList (NatSet.split 3 (NatSet.fromList [1, 2, 3, 4])) + ``` + + # See also + + * {NatSet.splitContains} to also check whether the given element is in the + set. + * {NatSet.partition} to split a set into two sets based on a predicate. + * {NatSet.filter} to remove all elements that do not satisfy a predicate. + * {NatSet.filterMap} to filter a set by a function that returns an + {type Optional} value. + * {NatSet.map} to apply a function to every element in a set, and return a + new set with the results. + }} + +test> data.NatSet.split.test = + test.verify do + use List filter + use Nat < > + use NatSet fromList + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 0 100 + x = natIn 0 100 + ensure + (NatSet.split x (fromList xs) + === ( fromList (filter (flip (<) x) xs) + , fromList (filter (flip (>) x) xs) + )) + +data.NatSet.splitContains : Nat -> NatSet -> (NatSet, Boolean, NatSet) +data.NatSet.splitContains x = cases + NatSet (Some t) -> Nonempty.splitContains x t + NatSet None -> (NatSet None, false, NatSet None) + +data.NatSet.splitContains.doc : Doc +data.NatSet.splitContains.doc = + use NatSet toList + {{ + Splits a {type NatSet} into two sets based on whether elements are less than + or greater than a given element, and returns whether the given element is in + the set. + + The first set in the result contains all elements that are less than the + given element, and the other set contains all elements that are greater than + the given element. The element is not included in either set, but the middle + element of the result is a {type Boolean} indicating whether the element is + in the set. + + # Example + + ``` + (l, b, r) = NatSet.splitContains 3 (NatSet.fromList [1, 2, 3, 4]) + (toList l, b, toList r) + ``` + + # See also + + * {NatSet.split} for a version of this function that does not return + whether the element is in the set. + * {NatSet.partition} to split a set into two sets based on a predicate. + * {NatSet.filter} to remove all elements that do not satisfy a predicate. + * {NatSet.filterMap} to filter a set by a function that returns an + {type Optional} value. + * {NatSet.map} to apply a function to every element in a set, and return a + new set with the results. + }} + +test> data.NatSet.splitContains.test = + test.verify do + use List filter + use Nat < > + use NatSet fromList + use Random natIn + _ = Each.range 0 100 + xs = Random.listOf (do natIn 0 100) do natIn 0 100 + x = natIn 0 100 + ensure + (NatSet.splitContains x (fromList xs) + === ( fromList (filter (flip (<) x) xs) + , List.contains x xs + , fromList (filter (flip (>) x) xs) + )) + +data.NatSet.subset : NatSet -> NatSet -> Boolean +data.NatSet.subset = cases + NatSet (Some t1), NatSet (Some t2) -> NatSet.Nonempty.subset t1 t2 + NatSet None, NatSet None -> true + NatSet None, NatSet (Some _) -> true + NatSet (Some _), NatSet None -> false + +data.NatSet.subset.doc : Doc +data.NatSet.subset.doc = + use NatSet fromList subset + {{ + Checks if one {type NatSet} is a subset of another. + + A set is a subset of another if it contains only elements that are also in + the other set. + + # Examples + + Every set is a subset of itself: + + ``` + subset (fromList [1, 2]) (fromList [1, 2]) + ``` + + A set is a subset of another if it contains only elements that are also in + the other set: + + ``` + subset (fromList [1, 2]) (fromList [1, 2, 3]) + ``` + + A set is not a subset of another if it contains elements that are not in + the other set: + + ``` + subset (fromList [1, 2, 3]) (fromList [1, 2]) + ``` + + # See also + + * {NatSet.properSubset} for a version of this that returns `` false `` when + the inputs are equal. + * {NatSet.subsetCompare} to compare two sets to see if either one is a + subset of the other. + * {NatSet.disjoint} to check if two sets have no elements in common. + }} + +data.NatSet.subsetCompare : NatSet -> NatSet -> Optional Ordering +data.NatSet.subsetCompare = cases + NatSet (Some t1), NatSet (Some t2) -> Nonempty.subsetCompare t1 t2 + NatSet None, NatSet None -> Some Equal + NatSet None, NatSet (Some _) -> Some Less + NatSet (Some _), NatSet None -> Some Greater + +data.NatSet.subsetCompare.doc : Doc +data.NatSet.subsetCompare.doc = + use NatSet fromList subsetCompare + {{ + Compares two {type NatSet} values to see if one is a subset of the other. + + Returns `` Some Less `` if the first input is a subset of the second, `` + Some Greater `` if the second input is a subset of the first, and `` + Some Equal `` if the inputs are the same set. Returns `` None `` if the sets + are not comparable (i.e. each has elements that are not in the other). + + # Examples + + ``` + subsetCompare (fromList [1, 2]) (fromList [2, 3]) + ``` + + ``` + subsetCompare (fromList [1, 2]) (fromList [1, 2]) + ``` + + ``` + subsetCompare (fromList [1, 2]) (fromList [1, 2, 3]) + ``` + + ``` + subsetCompare (fromList [1, 2, 3]) (fromList [1, 2]) + ``` + + # See also + + * {NatSet.subset} to check if one set is a subset of another. + * {NatSet.disjoint} to check if two sets have no elements in common. + }} + +test> data.NatSet.subsetCompare.test = + test.verify do + use List all + use NatSet contains fromList + use Random listOf natIn + _ = Each.range 0 100 + xs = listOf (do natIn 0 100) do natIn 0 100 + ys = listOf (do natIn 0 100) do natIn 0 100 + s1 = fromList xs + s2 = fromList ys + c = NatSet.subsetCompare s1 s2 + allXinY = all (x -> contains x s2) xs + allYinX = all (y -> contains y s1) ys + ensure + (if allXinY && allYinX then c === Some Equal + else + if allXinY then c === Some Less + else if allYinX then c === Some Greater else c === None) + +data.NatSet.superset : NatSet -> NatSet -> Boolean +data.NatSet.superset t1 t2 = NatSet.subset t2 t1 + +data.NatSet.superset.doc : Doc +data.NatSet.superset.doc = + use NatSet fromList superset + {{ + Checks if one {type NatSet} is a superset of another. + + A set is a superset of another if it contains all elements in the other set. + + # Examples + + Every set is a superset of itself: + + ``` + superset (fromList [1, 2]) (fromList [1, 2]) + ``` + + A set is a superset of another if it contains all elements in the other + set: + + ``` + superset (fromList [1, 2, 3]) (fromList [1, 2]) + ``` + + A set is not a superset of another it doesn't contain all elements in the + other set: + + ``` + superset (fromList [1, 2]) (fromList [1, 2, 3]) + ``` + + # See also + + * {NatSet.properSuperset} for a version of this that returns `` false `` + when the inputs are equal. + * {NatSet.subsetCompare} to compare two sets to see if either one is a + superset of the other. + * {NatSet.disjoint} to check if two sets have no elements in common. + }} + +data.NatSet.takeMax : Nat -> NatSet -> NatSet +data.NatSet.takeMax n natSet = + use Nat + + go : Nat -> (NatSet, Nat) -> (NatSet, Nat) + go nextElem = cases + (acc, count) -> + if count === n then (acc, n) else (NatSet.insert nextElem acc, count + 1) + res : (NatSet, Nat) + res = NatSet.foldRight go (NatSet.empty, 0) natSet + at1 res + +data.NatSet.takeMax.doc : Doc +data.NatSet.takeMax.doc = + {{ + Returns a {type NatSet} of the `n` largest elements in the given + {type NatSet}. + + # Example + + ``` + NatSet.toList + <| takeMax 3 (NatSet.fromList [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + ``` + }} + +data.NatSet.takeMin : Nat -> NatSet -> NatSet +data.NatSet.takeMin n natSet = + use Nat + + go : (NatSet, Nat) -> Nat -> (NatSet, Nat) + go tup nextElem = + (acc, count) = tup + if count === n then (acc, n) else (NatSet.insert nextElem acc, count + 1) + NatSet.foldLeft go (NatSet.empty, 0) natSet |> at1 + +data.NatSet.takeMin.doc : Doc +data.NatSet.takeMin.doc = + {{ + Returns a {type NatSet} of the `n` smallest elements in the given + {type NatSet}. + + # Example + + ``` + NatSet.toList + <| takeMin 3 (NatSet.fromList [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + ``` + }} + +data.NatSet.tips : NatSet -> [NatSet.Nonempty] +data.NatSet.tips = cases + NatSet (Some t) -> List.Nonempty.toList (Nonempty.tips t) + NatSet None -> [] + +data.NatSet.tips.doc : Doc +data.NatSet.tips.doc = + use NatSet fromList tips + {{ + Returns a list of all the leaves in a {type NatSet}. + + Each leaf is a {type NatSet.Nonempty} with a prefix and a bitmap. The prefix + is the 58-bit common prefix of all numbers in the set. The 64-bit bitmap + indicates which numbers with that prefix are in the set. If the prefix is `p` + and bit `n` is set in the bitmap, then the number `p + n` is in the set. + + # Examples + + ``` + tips (fromList [8]) + ``` + + ``` + tips (fromList [0, 1, 2, 4, 8, 16, 32, 64, 128]) + ``` + }} + +data.NatSet.toList : NatSet -> [Nat] +data.NatSet.toList = cases + NatSet (Some t) -> List.Nonempty.toList (Nonempty.toListAscending t) + NatSet None -> [] + +data.NatSet.toList.doc : Doc +data.NatSet.toList.doc = + use NatSet foldRight + {{ + Converts a {type NatSet} to a {type List} of its elements. + + # Example + + ``` + NatSet.toList (NatSet.fromList [1, 2, 3, 4]) + ``` + + # See also + + * {NatSet.foldMap} to apply a function to every element and combine the + results. + * {foldRight} to accumulate results of a single binary function applied to + every element and the result so far, associating to the right. + * {NatSet.foldLeft} same as {foldRight} but associating to the left. + * {NatSet.map} to apply a function to every element without combining the + results. + * {NatSet.filter} to filter out elements that don't satisfy a predicate. + * {NatSet.filterMap} to apply a function to every element and filter out + the results that are {None}. + }} + +test> data.NatSet.toList.test = test.verify do + _ = Each.range 0 100 + xs = Random.listOf Random.nat do Random.natIn 0 100 + ensureEqual (NatSet.toList (NatSet.fromList xs)) (distinct (Heap.sort xs)) + +data.NatSet.toListDescending : NatSet -> [Nat] +data.NatSet.toListDescending = cases + NatSet (Some t) -> NatSet.Nonempty.foldLeft (flip (List.+:)) [] t + NatSet None -> [] + +data.NatSet.toListDescending.doc : Doc +data.NatSet.toListDescending.doc = + {{ + Converts a {type NatSet} to a {type List} of its elements in descending + order. + + # Example + + ``` + NatSet.toListDescending (NatSet.fromList [1, 2, 3, 4]) + ``` + + # See also + + * {NatSet.toList} to convert to a {type List} in ascending order. + }} + +test> data.NatSet.toListDescending.test = + test.verify do + _ = Each.range 0 100 + xs = Random.listOf Random.nat do Random.natIn 0 100 + ensureEqual + (NatSet.toListDescending (NatSet.fromList xs)) + (distinct (sortDescending xs)) + +data.NatSet.union : NatSet -> NatSet -> NatSet +data.NatSet.union = cases + NatSet (Some t1), NatSet (Some t2) -> + NatSet (Some (NatSet.Nonempty.union t1 t2)) + NatSet None, NatSet (Some t2) -> NatSet (Some t2) + NatSet (Some t1), NatSet None -> NatSet (Some t1) + _, _ -> NatSet None + +data.NatSet.union.doc : Doc +data.NatSet.union.doc = + use NatSet fromList + {{ + Adds all elements from one {type NatSet} to another. If an element is present + in either input, it will be present in the result. + + # Example + + ``` + NatSet.toList (NatSet.union (fromList [1, 2]) (fromList [2, 3])) + ``` + + # See also + + * {NatSet.difference} to remove all elements from a {type NatSet} that are + in another. + * {NatSet.intersect} to remove all elements from a {type NatSet} that are + not in another. + * {NatSet.insert.nonempty} to add one element into a {type NatSet}. + }} + +test> data.NatSet.union.test = test.verify do + use List ++ + use NatSet == fromList + use Random listOf nat natIn + _ = Each.range 0 100 + xs = listOf nat do natIn 0 100 + ys = listOf nat do natIn 0 100 + u = NatSet.union (fromList xs) (fromList ys) + a = fromList (xs ++ ys) + ensure (u == a) + +data.NatSet.unions : [NatSet] -> NatSet +data.NatSet.unions = List.foldLeft NatSet.union NatSet.empty + +data.NatSet.unions.doc : Doc +data.NatSet.unions.doc = + use NatSet fromList + {{ + Puts all elements from a {type List} of {type NatSet}s into a single + {type NatSet}. If an element is present in any of the inputs, it will be + present in the result. + + # Example + + ``` + NatSet.toList (NatSet.unions [fromList [1, 2], fromList [2, 3]]) + ``` + + # See also + + * {NatSet.union} to union two {type NatSet}s. + }} + +data.OneOrBoth.doc : Doc +data.OneOrBoth.doc = + use Map fromList mergeWith mergeWithKey + use Nat + toText + use OneOrBoth fold merge partition + use Text ++ size toUppercase + {{ + The {type OneOrBoth} type takes two type parameters, `a` and `b`, and + represents a value that is either {This} of type `a`, {That} of type `b`, or + {Both} which contains both an `a` and a `b`. + + This type is useful for representing values that can be one of two types, or + both types at the same time. For example, it can be used to represent the + result of aligning two lists or maps, where each element can be present in + only one of the input lists or maps, or in both. + + It's similar to the {type Either} type, but it can also represent the case + where both values are present. + + # Constructing values + + You can construct a value of type {type OneOrBoth} using the {This}, + {That}, or {Both} constructors: + + ``` + This 42 + ``` + + ``` + That "hello" + ``` + + ``` + Both 42 "hello" + ``` + + # Inspecting values + + You can use the {isThis}, {isThat}, and {isBoth} functions to check which + constructor a value of type {type OneOrBoth} has: + + ``` + isThis (This 42) + ``` + + ``` + isThat (That "hello") + ``` + + ``` + isBoth (Both 42 "hello") + ``` + + # Deconstructing values + + There are several ways of getting rid of a {type OneOrBoth} value. + + ## Pattern matching + + You can use pattern matching to destructure a {type OneOrBoth} value: + + ``` + match That "hello" with + This x -> x + That y -> size y + Both x y -> x + size y + ``` + + ## Folding + + Use the {fold} function to apply one of three functions to a value of + type {type OneOrBoth}: + + ``` + fold id size (x y -> x + size y) (That "hello") + ``` + + ``` + fold id size (x y -> x + size y) (Both 42 "hello") + ``` + + ``` + fold id size (x y -> x + size y) (This 42) + ``` + + ## Conversion to {type Optional} + + You can turn a {type OneOrBoth} value into an {type Optional} value + using the {maybeThis}, {maybeThat}, {justThis}, {justThat}, or + {justBoth} functions: + + @signatures{maybeThis, maybeThat, justThis, justThat, justBoth} + + The difference between {maybeThis} and {justThis} is that {maybeThis} + returns {Some} in the {Both} case, while {justThis} returns {None} in + the {Both} case. Mutatis mutandis for {maybeThat} and {justThat}. + {justBoth} returns {Some} only in the {Both} case. + + ## Conversion to a pair + + You can turn a {type OneOrBoth} value into a pair using the {toTuple} + function: + + @signature{toTuple} + + The function takes two fallback values that are used if the value is + {This} or {That}. + + ## Merging values + + You can merge a {type OneOrBoth} value into a single value using the + {merge} function, if the type on both sides is the same: + + @signature{merge} + + ## Getting rid of nested {type OneOrBoth} values + + You can use the {joinThis} and {joinThat} functions to get rid of nested + {type OneOrBoth} values: + + ``` + joinThis (+) (That (This 42)) + ``` + + ``` + joinThat (++) (This (That "hello")) + ``` + + # Mapping over values + + You can use the {mapThis}, {mapThat}, and {mapBoth} functions to apply + functions to the left, right, or both sides of a {type OneOrBoth}: + + ``` + mapThis toUppercase (Both "hello" "world") + ``` + + ``` + mapThat toUppercase (Both "hello" "world") + ``` + + ``` + mapBoth size toUppercase (Both "hello" "world") + ``` + + # List functions + + ## List alignment + + You can put two lists together into a list of {type OneOrBoth} using + {List.align}. The result will have the same length as the longer of the + two lists, and each element will be a {type OneOrBoth} containing the + corresponding elements from the two input lists. If the first list is + shorter than the second, the result will be padded with {That} values + from the second list, and if the second list is shorter than the first, + the result will be padded with {This} values from the first list: + + ``` + List.align [1, 2, 3] ["a", "b"] + ``` + + You can also use {List.alignWith} to apply a function to the aligned + elements: + + ``` + f = cases + This a -> toText a ++ " -> Nothing" + That b -> "Nothing -> " ++ b + Both a b -> toText a ++ " -> " ++ b + List.alignWith f [1, 2, 3] ["a", "b"] + ``` + + ## Lists of {type OneOrBoth} + + You can use {these}, {those}, {justThese}, and {justThose} to extract + the {This} and {That} values from a list of {type OneOrBoth} values: + + ``` + these [This 1, That "hello", Both 42 "world"] + ``` + + ``` + justThese [This 1, That "hello", Both 42 "world"] + ``` + + Partition a list of {type OneOrBoth} values into three lists using + {partition}: + + ``` + partition [This 1, That "hello", Both 42 "world"] + ``` + + # Map functions + + ## Map alignment + + You can align two maps into a map of {type OneOrBoth} values using + {Map.align}. The result will have the same keys as the union of the keys + of the two input maps, and each value will be a {type OneOrBoth} + containing the corresponding values from the two input maps. If a key is + present in only one of the input maps, the result will contain {This} or + {That} values accordingly. If a key is present in both input maps, the + result will contain a {Both} value: + + ``` + Map.toList + (Map.align + (fromList [(1, "hello"), (2, "world")]) + (fromList [(2, 42), (3, 43)])) + ``` + + {Map.alignWith} is a variant of {Map.align} where you can specify a + function to apply to the values: + + ``` + f = cases + This a -> "only in the first map: " ++ a + That b -> "only in the second map: " ++ b + Both a b -> "in both maps: " ++ a ++ " and " ++ b + Map.values + (Map.alignWith + f + (fromList [(1, "circuit"), (2, "quasar")]) + (fromList [(2, "voyage"), (3, "harmony")])) + ``` + + {Map.alignWithKey} is a variant of {Map.alignWith} where the function + takes the key as an argument: + + @signature{Map.alignWithKey} + + There are also {type Map.Nonempty} variants of these functions: + + @signatures{Map.Nonempty.align, Map.Nonempty.alignWith, Map.Nonempty.alignWithKey} + + And {type NatMap} and {type NatMap.Nonempty} variants as well: + + @signatures{NatMap.align, NatMap.alignWith, NatMap.alignWithKey, NatMap.Nonempty.align, NatMap.Nonempty.alignWith, NatMap.Nonempty.alignWithKey} + + ## Map merging with a function + + {mergeWith} is a highly versatile function that can be used to merge two + maps via {type OneOrBoth}. It can be used to implement map union, + intersection, and difference, among other things: + + @signature{mergeWith} + + {mergeWithKey} is a variant of {mergeWith} where the function takes the + key into account as well: + + @signature{mergeWithKey} + }} + +data.OneOrBoth.fold : + (a ->{e} c) + -> (b ->{f} c) + -> (a ->{g} b ->{h} c) + -> OneOrBoth a b + ->{e, f, g, h} c +data.OneOrBoth.fold f g h = cases + This x -> f x + That y -> g y + Both x y -> h x y + +data.OneOrBoth.fold.doc : Doc +data.OneOrBoth.fold.doc = + use Nat + + use OneOrBoth fold + use Text size + {{ + Applies one of three functions to a value of type {type OneOrBoth}. If the + value is {This}, the first function is applied. If the value is {That}, the + second function is applied. If the value is {Both}, the third function is + applied. + + # Examples + + ``` + fold id size (x y -> x + size y) (That "hello") + ``` + + ``` + fold id size (x y -> x + size y) (Both 42 "hello") + ``` + + ``` + fold id size (x y -> x + size y) (This 42) + ``` + }} + +data.OneOrBoth.isBoth : OneOrBoth a b -> Boolean +data.OneOrBoth.isBoth = cases + This _ -> false + That _ -> false + Both _ _ -> true + +data.OneOrBoth.isBoth.doc : Doc +data.OneOrBoth.isBoth.doc = + {{ + Returns `true` if the value is {Both}, and `false` otherwise. + + # Examples + + ``` + isBoth (This 42) + ``` + + ``` + isBoth (That "hello") + ``` + + ``` + isBoth (Both 42 "hello") + ``` + + # See also + + * {isThis} - returns `true` if the value is {This}. + * {isThat} - returns `true` if the value is {That}. + }} + +data.OneOrBoth.isThat : OneOrBoth a b -> Boolean +data.OneOrBoth.isThat = cases + This _ -> false + That _ -> true + Both _ _ -> false + +data.OneOrBoth.isThat.doc : Doc +data.OneOrBoth.isThat.doc = + {{ + Returns `true` if the value is {That}, and `false` otherwise. + + # Examples + + ``` + isThat (This 42) + ``` + + ``` + isThat (That "hello") + ``` + + ``` + isThat (Both 42 "hello") + ``` + + # See also + + * {isThis} - exactly the opposite of this function. + * {isBoth} - returns `true` if the value is {Both}. + }} + +data.OneOrBoth.isThis : OneOrBoth a b -> Boolean +data.OneOrBoth.isThis = cases + This _ -> true + That _ -> false + Both _ _ -> false + +data.OneOrBoth.isThis.doc : Doc +data.OneOrBoth.isThis.doc = + {{ + Returns `true` if the value is {This}, and `false` otherwise. + + # Examples + + ``` + isThis (This 42) + ``` + + ``` + isThis (That "hello") + ``` + + ``` + isThis (Both 42 "hello") + ``` + + # See also + + * {isThat} - exactly the opposite of this function. + * {isBoth} - returns `true` if the value is {Both}. + }} + +data.OneOrBoth.joinThat : + (b ->{g1} b ->{g} b) -> OneOrBoth (OneOrBoth a b) b ->{g1, g} OneOrBoth a b +data.OneOrBoth.joinThat f = cases + This x -> x + That y -> That y + Both (This x) y -> Both x y + Both (That x) y -> That (f x y) + Both (Both x y) z -> Both x (f y z) + +data.OneOrBoth.joinThat.doc : Doc +data.OneOrBoth.joinThat.doc = + use Text ++ + {{ + Joins a {That} value of a {type OneOrBoth} with the {That} value of a nested + {type OneOrBoth} using a function. If the value is {That}, the value is + returned. If the value is {This}, the nested value is returned. If the value + is {Both}, the function is applied to the two {That} values. + + # Examples + + ``` + joinThat (++) (That "hello") + ``` + + ``` + joinThat (++) (This (That "hello")) + ``` + + ``` + joinThat (++) (Both (This 42) "hello") + ``` + + ``` + joinThat (++) (Both (That "hello") "world") + ``` + + ``` + joinThat (++) (Both (Both 42 "hello") "world") + ``` + + # See also + + * {joinThis} - joins a {This} value of a {type OneOrBoth} with the {This} + value of a nested {type OneOrBoth} using a function. + }} + +data.OneOrBoth.joinThis : + (a ->{g1} a ->{g} a) -> OneOrBoth a (OneOrBoth a b) ->{g1, g} OneOrBoth a b +data.OneOrBoth.joinThis f = cases + This x -> This x + That y -> y + Both x (This y) -> This (f x y) + Both x (That y) -> Both x y + Both x (Both y z) -> Both (f x y) z + +data.OneOrBoth.joinThis.doc : Doc +data.OneOrBoth.joinThis.doc = + use Nat + + {{ + Joins a {This} value of a {type OneOrBoth} with the {This} value of a nested + {type OneOrBoth} using a function. If the value is {This}, the value is + returned. If the value is {That}, the nested value is returned. If the value + is {Both}, the function is applied to the two {This} values. + + # Examples + + ``` + joinThis (+) (This 42) + ``` + + ``` + joinThis (+) (That (This 42)) + ``` + + ``` + joinThis (+) (Both 42 (This 42)) + ``` + + ``` + joinThis (+) (Both 42 (That "hello")) + ``` + + ``` + joinThis (+) (Both 42 (Both 42 "hello")) + ``` + + # See also + + * {joinThat} - joins a {That} value of a {type OneOrBoth} with the {That} + value of a nested {type OneOrBoth} using a function. + }} + +data.OneOrBoth.justBoth : OneOrBoth a b -> Optional (a, b) +data.OneOrBoth.justBoth = cases + This _ -> None + That _ -> None + Both x y -> Some (x, y) + +data.OneOrBoth.justBoth.doc : Doc +data.OneOrBoth.justBoth.doc = + {{ + Returns the {Both} value of a {type OneOrBoth} as an {type Optional}. If the + value is {This} or {That}, {None} is returned. If the value is {Both}, the + values are returned as a pair in {Some}. + + # Examples + + ``` + justBoth (This 42) + ``` + + ``` + justBoth (That "hello") + ``` + + ``` + justBoth (Both 42 "hello") + ``` + + # See also + + * {toTuple} - a variant that uses fallback values for {This} and {That}. + * {OneOrBoth.merge} - a variant that merges the values using a function. + }} + +data.OneOrBoth.justThat : OneOrBoth a b -> Optional b +data.OneOrBoth.justThat = cases + This _ -> None + That y -> Some y + Both _ _ -> None + +data.OneOrBoth.justThat.doc : Doc +data.OneOrBoth.justThat.doc = + {{ + Returns the {That} value of a {type OneOrBoth} as an {type Optional}. If the + value is {That}, the value is returned as {Some}. If the value is {This} or + {Both}, {None} is returned. + + # Examples + + ``` + justThat (This 42) + ``` + + ``` + justThat (That "hello") + ``` + + ``` + justThat (Both 42 "hello") + ``` + + # See also + + * {maybeThat} - a variant that returns {Some} in the {Both} case. + * {justThis} - exactly the opposite of this function. + }} + +data.OneOrBoth.justThese : [OneOrBoth a b] -> [a] +data.OneOrBoth.justThese oob = Each.toList do + use Each fail + x = each oob + match x with + This a -> a + That _ -> fail() + Both _ _ -> fail() + +data.OneOrBoth.justThese.doc : Doc +data.OneOrBoth.justThese.doc = + {{ + Extracts the {This} values from a list of {type OneOrBoth} values. If a value + is {That} or {Both}, it is skipped. + + # Examples + + ``` + justThese [This 1, That "hello", Both 42 "world"] + ``` + + ``` + justThese [This 1, This 2, This 3] + ``` + + ``` + justThese [That "hello", That "world", That "universe"] + ``` + + # See also + + * {these} - a variant that extracts the left sides of {Both} values as + well. + * {justThose} - exactly the opposite of this function. + }} + +data.OneOrBoth.justThis : OneOrBoth a b -> Optional a +data.OneOrBoth.justThis = cases + This x -> Some x + That _ -> None + Both _ _ -> None + +data.OneOrBoth.justThis.doc : Doc +data.OneOrBoth.justThis.doc = + {{ + Returns the {This} value of a {type OneOrBoth} as an {type Optional}. If the + value is {This}, the value is returned as {Some}. If the value is {That} or + {Both}, {None} is returned. + + # Examples + + ``` + justThis (This 42) + ``` + + ``` + justThis (That "hello") + ``` + + ``` + justThis (Both 42 "hello") + ``` + + # See also + + * {maybeThis} - a variant that returns {Some} in the {Both} case. + * {justThat} - exactly the opposite of this function. + }} + +data.OneOrBoth.justThose : [OneOrBoth a b] -> [b] +data.OneOrBoth.justThose oob = Each.toList do + use Each fail + x = each oob + match x with + This _ -> fail() + That b -> b + Both _ b -> fail() + +data.OneOrBoth.justThose.doc : Doc +data.OneOrBoth.justThose.doc = + {{ + Extracts the {That} values from a list of {type OneOrBoth} values. If a value + is {This} or {Both}, it is skipped. + + # Examples + + ``` + justThose [This 1, That "hello", Both 42 "world"] + ``` + + ``` + justThose [That "hello", That "world", That "universe"] + ``` + + ``` + justThose [That "hello", That "world", That "universe"] + ``` + + # See also + + * {those} - a variant that extracts the right sides of {Both} values as + well. + * {justThese} - exactly the opposite of this function. + }} + +data.OneOrBoth.mapBoth : + (a ->{f} c) -> (b ->{g} d) -> OneOrBoth a b ->{f, g} OneOrBoth c d +data.OneOrBoth.mapBoth f g = cases + This x -> This (f x) + That y -> That (g y) + Both x y -> Both (f x) (g y) + +data.OneOrBoth.mapBoth.doc : Doc +data.OneOrBoth.mapBoth.doc = + use Text size toUppercase + {{ + Applies two functions to a value of type {type OneOrBoth}. If the value is + {This}, the first function is applied. If the value is {That}, the second + function is applied. If the value is {Both}, both functions are applied. + + # Examples + + ``` + mapBoth size toUppercase (That "hello") + ``` + + ``` + mapBoth size toUppercase (Both "yes" "hello") + ``` + + ``` + mapBoth size toUppercase (This "hello") + ``` + + # See also + + * {mapThis} - applies a function to the {This} value. + * {mapThat} - applies a function to the {That} value. + }} + +data.OneOrBoth.mapThat : (b ->{g} d) -> OneOrBoth a b ->{g} OneOrBoth a d +data.OneOrBoth.mapThat f = cases + This x -> This x + That y -> That (f y) + Both x y -> Both x (f y) + +data.OneOrBoth.mapThat.doc : Doc +data.OneOrBoth.mapThat.doc = + use Text toUppercase + {{ + Applies a function to the {That} value of a {type OneOrBoth}. If the value is + {This}, the value is returned unchanged. If the value is {That}, the function + is applied. If the value is {Both}, the function is applied to the right + value. + + # Examples + + ``` + mapThat toUppercase (That "hello") + ``` + + ``` + mapThat toUppercase (Both "yes" "hello") + ``` + + ``` + mapThat toUppercase (This "hello") + ``` + + # See also + + * {mapThis} - applies a function to the {This} value. + * {mapBoth} - applies a function to both values. + }} + +data.OneOrBoth.mapThis : (a ->{g} c) -> OneOrBoth a b ->{g} OneOrBoth c b +data.OneOrBoth.mapThis f = cases + This x -> This (f x) + That y -> That y + Both x y -> Both (f x) y + +data.OneOrBoth.mapThis.doc : Doc +data.OneOrBoth.mapThis.doc = + use Text toUppercase + {{ + Applies a function to the {This} value of a {type OneOrBoth}. If the value is + {This}, the function is applied. If the value is {That}, the value is + returned unchanged. If the value is {Both}, the function is applied to the + left value. + + # Examples + + ``` + mapThis toUppercase (That "hello") + ``` + + ``` + mapThis toUppercase (Both "yes" "hello") + ``` + + ``` + mapThis toUppercase (This "hello") + ``` + + # See also + + * {mapThat} - applies a function to the {That} value. + * {mapBoth} - applies a function to both values. + }} + +data.OneOrBoth.maybeThat : OneOrBoth a b -> Optional b +data.OneOrBoth.maybeThat = cases + This _ -> None + That y -> Some y + Both _ y -> Some y + +data.OneOrBoth.maybeThat.doc : Doc +data.OneOrBoth.maybeThat.doc = + {{ + Returns the {That} value of a {type OneOrBoth} as an {type Optional}. If the + value is {This}, {None} is returned. If the value is {That}, the value is + returned as {Some}. If the value is {Both}, the right value is returned as + {Some}. + + # Examples + + ``` + maybeThat (This 42) + ``` + + ``` + maybeThat (That "hello") + ``` + + ``` + maybeThat (Both 42 "hello") + ``` + + # See also + + * {justThat} - a variant that returns {None} in the {Both} case. + * {maybeThis} - exactly the opposite of this function. + }} + +data.OneOrBoth.maybeThis : OneOrBoth a b -> Optional a +data.OneOrBoth.maybeThis = cases + This x -> Some x + That _ -> None + Both x _ -> Some x + +data.OneOrBoth.maybeThis.doc : Doc +data.OneOrBoth.maybeThis.doc = + {{ + Returns the {This} value of a {type OneOrBoth} as an {type Optional}. If the + value is {This}, the value is returned as {Some}. If the value is {That}, + {None} is returned. If the value is {Both}, the left value is returned as + {Some}. + + # Examples + + ``` + maybeThis (This 42) + ``` + + ``` + maybeThis (That "hello") + ``` + + ``` + maybeThis (Both 42 "hello") + ``` + + # See also + + * {justThis} - a variant that returns {None} in the {Both} case. + * {maybeThat} - exactly the opposite of this function. + }} + +data.OneOrBoth.merge : (a ->{f} a ->{g} a) -> OneOrBoth a a ->{f, g} a +data.OneOrBoth.merge f = cases + This x -> x + That y -> y + Both x y -> f x y + +data.OneOrBoth.merge.doc : Doc +data.OneOrBoth.merge.doc = + use Nat + + use OneOrBoth merge + {{ + Merges a value of type {type OneOrBoth} into a single value using a function. + If the value is {This} or {That} the value is returned. If the value is + {Both}, the function is applied to the two values. + + # Examples + + ``` + merge (+) (That 42) + ``` + + ``` + merge (+) (Both 42 42) + ``` + + ``` + merge (+) (This 42) + ``` + + # See also + + * {toTuple} - returns the values as a pair. + * {justBoth} - returns the values as a pair in {Some}. + }} + +data.OneOrBoth.partition : [OneOrBoth a b] -> ([a], [b], [(a, b)]) +data.OneOrBoth.partition oob = + use List :+ + go = cases + (as, bs, boths) -> + cases + This a -> (as :+ a, bs, boths) + That b -> (as, bs :+ b, boths) + Both a b -> (as, bs, boths :+ (a, b)) + List.foldLeft go ([], [], []) oob + +data.OneOrBoth.partition.doc : Doc +data.OneOrBoth.partition.doc = + use OneOrBoth partition + {{ + Partitions a list of {type OneOrBoth} values into three lists: one containing + the {This} values, one containing the {That} values, and one containing the + {Both} values. + + # Examples + + ``` + partition [This 1, That "hello", Both 42 "world"] + ``` + + ``` + partition [This 1, This 2, This 3] + ``` + + ``` + partition [That "hello", That "world", That "universe"] + ``` + + # See also + + * {these} - extracts the {This} values and the left sides of the {Both} + values. + * {those} - extracts the {That} values and the right sides of the {Both} + values. + * {justThese} - extracts the {This} values but not the {Both} values. + {Both} values. + * {justThose} - extracts the {That} values but not the {Both} values. + }} + +data.OneOrBoth.these : [OneOrBoth a b] -> [a] +data.OneOrBoth.these oob = Each.toList do + x = each oob + match x with + This a -> a + That _ -> Each.fail() + Both a _ -> a + +data.OneOrBoth.these.doc : Doc +data.OneOrBoth.these.doc = + {{ + Extracts the {This} values from a list of {type OneOrBoth} values. If a value + is {That}, it is skipped. If a value is {Both}, the left value is extracted. + + # Examples + + ``` + these [This 1, That "hello", Both 42 "world"] + ``` + + ``` + these [This 1, This 2, This 3] + ``` + + ``` + these [That "hello", That "world", That "universe"] + ``` + + # See also + + * {justThese} - a variant that skips {Both} values. + * {those} - exactly the opposite of this function. + }} + +data.OneOrBoth.those : [OneOrBoth a b] -> [b] +data.OneOrBoth.those oob = Each.toList do + x = each oob + match x with + This _ -> Each.fail() + That b -> b + Both _ b -> b + +data.OneOrBoth.those.doc : Doc +data.OneOrBoth.those.doc = + {{ + Extracts the {That} values from a list of {type OneOrBoth} values. If a value + is {This}, it is skipped. If a value is {Both}, the right value is extracted. + + # Examples + + ``` + those [This 1, That "hello", Both 42 "world"] + ``` + + ``` + those [That "hello", That "world", That "universe"] + ``` + + ``` + those [That "hello", That "world", That "universe"] + ``` + + # See also + + * {justThose} - a variant that skips {Both} values. + * {these} - exactly the opposite of this function. + }} + +data.OneOrBoth.toTuple : OneOrBoth a b -> a -> b -> (a, b) +data.OneOrBoth.toTuple = cases + This x, _, b -> (x, b) + That y, a, _ -> (a, y) + Both x y, _, _ -> (x, y) + +data.OneOrBoth.toTuple.doc : Doc +data.OneOrBoth.toTuple.doc = + {{ + Returns a pair of values from a {type OneOrBoth} value and two fallback + values. If the value is {This}, the first fallback value is used. If the + value is {That}, the second fallback value is used. If the value is {Both}, + the fallback values are ignored and the values from the {Both} are used. + + # Examples + + ``` + toTuple (That "hello") 0 "" + ``` + + ``` + toTuple (Both 42 "hello") 0 "" + ``` + + ``` + toTuple (This 42) 0 "" + ``` + + # See also + + * {OneOrBoth.merge} - merges the values using a function. + * {justBoth} - returns the values as a pair in {Some}. + }} + +data.SeqView.doc : Doc +data.SeqView.doc = + {{ + A value of this type is a possibly empty pair. It is used internally by the + Unison compiler to represent pattern matching on sequences. + }} + +data.SeqView.VElem.doc : Doc +data.SeqView.VElem.doc = {{ The non-empty case of {type SeqView}. }} + +data.SeqView.VEmpty.doc : Doc +data.SeqView.VEmpty.doc = {{ The empty case of {type SeqView}. }} + +(data.Set.==) : Set k -> Set k -> Boolean +x data.Set.== y = + use Map == + use Set.internal underlying + underlying x == underlying y + +data.Set.==.doc : Doc +data.Set.==.doc = + use Set == empty fromText singleton + {{ + Checks if two {type Set}s are equal according to {Universal.ordering} on the + elements. + + ``` + empty == empty + ``` + + ``` + fromText "🍎🍊🍐🍇" == fromText "🍇🍐🍊🍎" + ``` + + ``` + singleton 4 == singleton 3 + ``` + }} + +data.Set.all : (a ->{e} Boolean) -> Set a ->{e} Boolean +data.Set.all p = List.all p << Set.toList + +data.Set.all.doc : Doc +data.Set.all.doc = + use Set all fromList + {{ + `` all p s `` returns `` true `` if the {type Boolean} function `p` returns + `` true `` for __all__ elements of the {type Set} `s`, or if the {type Set} + `s` is empty. Otherwise ``false``. + + # Examples + + ``` + all Nat.isEven (fromList [2, 4, 6, 8]) + ``` + + ``` + all Nat.isOdd (fromList []) + ``` + + ``` + all + isWhitespace (Set.fromText "I'm not always right, but I'm never wrong.") + ``` + }} + +data.Set.any : (a ->{e} Boolean) -> Set a ->{e} Boolean +data.Set.any p = List.any p << Set.toList + +data.Set.any.doc : Doc +data.Set.any.doc = + use Nat isEven + use Set any fromList + {{ + `` any p s `` returns `` true `` if the {type Boolean} function `p` returns + `` true `` for __any__ elements of the {type Set} `s`. Otherwise ``false``, + including if the {type Set} is empty. + + # Examples + + ``` + any isEven (fromList [5, 6, 7]) + ``` + + ``` + any isEven (fromList [5, 9, 7]) + ``` + + ``` + any Nat.isOdd (fromList []) + ``` + + ``` + any + isWhitespace (Set.fromText "I'm not always right, but I'm never wrong.") + ``` + }} + +data.Set.contains : k -> Set k -> Boolean +data.Set.contains k = cases internal.Set m -> Map.contains k m + +data.Set.contains.doc : Doc +data.Set.contains.doc = + {{ + Checks if a {type Set} contains a given element. + + # Example + + ``` + Set.contains ?🍎 (Set.fromText "🍎🍊🍐🍇") + ``` + }} + +data.Set.delete : k -> Set k -> Set k +data.Set.delete k s = internal.Set (Map.delete k (Set.internal.underlying s)) + +data.Set.delete.doc : Doc +data.Set.delete.doc = + {{ + Deletes the given element from the {type Set}, if present. + + # Example + + ``` + Set.toText (Set.delete ?🍊 (Set.fromText "🍎🍊🍐🍋")) + ``` + }} + +test> data.Set.delete.test = runs 100 do + xs = setOf natInOrder () + x = natInOrder() + expect (Boolean.not (Set.contains x (Set.delete x xs))) + +data.Set.deletes : [a] -> Set a -> Set a +data.Set.deletes as s = List.foldLeft (s a -> Set.delete a s) s as + +data.Set.deletes.doc : Doc +data.Set.deletes.doc = + use Set deletes + {{ + `` deletes as s `` deletes multiple elements from `as`. + + ``` + deletes [1, 2, 3] (Set.fromList [1, 2, 3, 4, 5, 6]) |> Set.toList + ``` + }} + +test> data.Set.deletes.test = test.verify do + use Set deletes + i = Each.range 0 20 + j = Each.range 0 25 + as = List.range 0 i + s = Set.fromList as + ensure (Set.delete j s === deletes [j] s) + ensure (deletes as s === Set.empty) + +data.Set.difference : Set a -> Set a -> Set a +data.Set.difference xs ys = + use internal Set + (Set x) = xs + (Set y) = ys + z = Map.difference x y + Set z + +data.Set.difference.doc : Doc +data.Set.difference.doc = + use Set fromList + {{ + {{ + Aside + {{ + The __set difference__ of `A` and `B` is also known as the + __relative complement__ of `B` in `A`. + }} }} `Set.difference` returns the + [__set difference__](https://en.wikipedia.org/wiki/Complement_(set_theory) of + two {type Set}s. + + ``` + xs = fromList [?a, ?b, ?c, ?d, ?e, ?f] + ys = fromList [?d, ?e, ?f] + Set.toList (Set.difference xs ys) + ``` + }} + +test> data.Set.difference.test = + test.verify do + use Random listOf nat natIn + use Set fromList toList + _ = Each.range 0 100 + xs = listOf nat do natIn 0 100 + ys = listOf nat do natIn 0 100 + ensureEqual + (toList (Set.difference (fromList xs) (fromList ys))) + (toList (Set.deletes ys (fromList xs))) + +data.Set.doc : Doc +data.Set.doc = + use Set == all any contains delete deletes elementAt empty flatMap flatten foldLeft foldRight fromList fromText insert intersect intersects map singleton size subset superset toList toMap union unions + use Universal ordering + {{ + {type Set} is a sorted finite set, supporting efficient lookup and deletion + of elements. Sorting is done according to {ordering}. + + # Constructing sets + + {empty} is the empty {type Set}: + + @signature{empty} + + {singleton} creates a {type Set} with one element: + + @signature{singleton} + + {fromList} creates a {type Set} from a {type List}: + + @signature{fromList} + + {fromText} creates a {type Set} of {type Char} values from a {type Text} + value: + + @signature{fromText} + + # Inserting and deleting elements + + {insert} adds an element to a {type Set}: + + @signature{insert} + + {delete} removes an element from a {type Set}: + + @signature{delete} + + {deletes} removes a whole {type List} of elements from a {type Set}: + + @signature{deletes} + + # Accessing and querying elements + + `` elementAt n s `` gets the `n`th smallest element from the {type Set} + `s`: + + @signature{elementAt} + + {all} checks if all elements satisfy a predicate: + + @signature{all} + + {any} checks if at least one element satisfies a predicate: + + @signature{any} + + {contains} checks if a specified element is in the {type Set}: + + @signature{contains} + + {size} gets the number of elements in the {type Set}: + + @signature{size} + + # Combining sets + + {intersect} gets the intersection of two {type Set}s: + + @signature{intersect} + + {intersects} gets the intersection of a whole {type List} of {type Set}s: + + @signature{intersects} + + {union} gets the union of two {type Set}s: + + @signature{union} + + {unions} gets the union of a whole {type List} of {type Set}s: + + @signature{unions} + + {flatten} gets the union of a whole {type Set} of {type Set}s: + + @signature{flatten} + + # Comparing sets + + {==} checks if two {type Set}s have all the same elements according to + {ordering}: + + @signature{==} + + `` subset x y `` checks if `x` is a subset of `y`: + + @signature{subset} + + `` superset x y `` checks if `x` is a superset of `y`: + + @signature{superset} + + # Set traversals + + {map} applies a function to every element of a {type Set}, collecting the + results in a new {type Set}: + + @signature{map} + + {flatMap} applies a {type Set}-valued function to every element, unioning + the results: + + @signature{flatMap} + + {foldLeft} passes each element to an accumulating function starting with an + initial value, in ascending order: + + @signature{foldLeft} + + {foldRight} passes each element to an accumulating function, starting with + an initial value, in descending order. + + @signature{foldRight} + + # Conversions to other types + + {toList} gets the {type List} of elements in a {type Set}: + + @signature{toList} + + {toMap} generates a {type Map} where the elements of the {type Set} are the + keys, and the values are a function of the keys. + + @signature{toMap} + + {Set.toText} turns a {type Set} of {type Char} Unicode code points to the + {type Text} consisting of those code points in ascending order. + + # Implementation notes + + The type is implemented as a {type Map} whose values are of type + {type Unit}. + + ``` + fromList ["a", "b", "c", "d", "e", "f"] + ``` + + The size of each subtree is cached at non-leaf nodes, and the entry at each + node has a key which is bigger than all entries in the left subtree, and + smaller than all entries in the right subtree. Operations like {insert} and + {delete} maintain a balanced tree so its depth is logarithmic in the + {size}. + }} + +data.Set.elementAt : Nat -> Set a -> Optional a +data.Set.elementAt n = cases + internal.Set underlying -> Optional.map at1 (Map.nth n underlying) + +data.Set.elementAt.doc : Doc +data.Set.elementAt.doc = + use Set elementAt + {{ + `` elementAt i s `` returns the `i`-th (0-based) element of `s`. + + Is the same as {{ docExample 2 do i as -> List.at i (Set.toList as) }} but + doesn't require instantiating the intermediate {type List}. + + ``` + s = Set.fromList [6, 5, 4, 2, 1, 3] + List.map (i -> elementAt i s) (List.range 0 (Set.size s)) + ``` + }} + +test> data.Set.elementAt.tests = + test.verify do + use Random natIn + Each.repeat 100 + s = (List.replicate (natIn 0 20) do natIn 0 20) |> Set.fromList + ensure + (List.somes + (List.map (i -> Set.elementAt i s) (List.range 0 (Set.size s))) + === Set.toList s) + +data.Set.empty : Set k +data.Set.empty = internal.Set Map.empty + +data.Set.empty.doc : Doc +data.Set.empty.doc = {{ The empty {type Set}. }} + +data.Set.flatMap : (i ->{g} Set k) -> Set i ->{g} Set k +data.Set.flatMap f as = Set.foldLeft (b a -> Set.union b (f a)) Set.empty as + +data.Set.flatMap.doc : Doc +data.Set.flatMap.doc = + use Set flatMap + {{ + `` flatMap f s `` applies the {type Set}-valued function `f` to every element + in the {type Set} `s` and unions the results into a single {type Set}. + + # Example + + ``` + Set.toText + (flatMap Set.fromText (Set.fromList ["please", "and", "thanks"])) + ``` + }} + +test> data.Set.flatMap.tests.associative = runs 100 do + use Map get + use Optional getOrElse + use Set empty flatMap + use tests mapOf + xs = setOf natInOrder () + ys = mapOf natInOrder (setOf natInOrder) () + zs = mapOf natInOrder (setOf natInOrder) () + f x = getOrElse empty (get x ys) + g y = getOrElse empty (get y zs) + r = flatMap f (flatMap g xs) === flatMap (x -> flatMap f (g x)) xs + expect r + +test> data.Set.flatMap.tests.unit = runs 100 do + use Set == flatMap singleton + n = natInOrder() + xs = setOf natInOrder () + ys = tests.mapOf natInOrder (setOf natInOrder) () + f x = Optional.getOrElse Set.empty (Map.get x ys) + left = flatMap f (singleton n) == f n + right = flatMap singleton xs == xs + expect (left && right) + +data.Set.flatten : Set (Set k) -> Set k +data.Set.flatten = Set.flatMap id + +data.Set.flatten.doc : Doc +data.Set.flatten.doc = + {{ + Unions all the sets in a {type Set}. + + # Example + + ``` + Set.toText + (Set.flatten + (Set.fromList (List.map Set.fromText ["please", "and", "thanks"]))) + ``` + }} + +test> data.Set.flatten.tests.associative = runs 100 do + use Set flatten + xs = setOf (setOf (setOf natInOrder)) () + expect (flatten (flatten xs) === flatten (Set.map flatten xs)) + +test> data.Set.flatten.tests.unit = runs 100 do + use Set == flatten singleton + xs = setOf natInOrder () + expect (flatten (singleton xs) == flatten (Set.map singleton xs)) + +data.Set.foldLeft : (b ->{e} a ->{e} b) -> b -> Set a ->{e} b +data.Set.foldLeft f b s = + List.foldLeft f b (Map.keys (Set.internal.underlying s)) + +data.Set.foldLeft.doc : Doc +data.Set.foldLeft.doc = + use Nat - + {{ + Folds the elements in the {type Set} in ascending order (according to + {Universal.ordering}), associating to the left, using the given binary + operator. + + # Example + + ``` + Set.foldLeft (-) 100 (Set.fromList [3, 5, 2]) + ``` + }} + +test> data.Set.foldLeft.tests.homomorphism = runs 100 do + use List :+ + xs = setOf natInOrder () + expect (Set.foldLeft (:+) [] xs === Set.toList xs) + +data.Set.foldRight : (a ->{e} b ->{e} b) -> b -> Set a ->{e} b +data.Set.foldRight f b s = + List.foldRight f b (Map.keys (Set.internal.underlying s)) + +data.Set.foldRight.doc : Doc +data.Set.foldRight.doc = + use Text ++ + {{ + Folds the elements in the {type Set} in ascending order (according to + {Universal.ordering}), associating to the right, using the given binary + operator. + + # Example + + ``` + Set.foldRight (++) "!" (Set.fromList ["yes", "no", "maybe"]) + ``` + }} + +test> data.Set.foldRight.tests.homomorphism = runs 100 do + use List +: + xs = setOf natInOrder () + expect (Set.foldRight (+:) [] xs === Set.toList xs) + +data.Set.fromList : [k] -> Set k +data.Set.fromList ks = internal.Set (Map.fromList (List.map (k -> (k, ())) ks)) + +data.Set.fromList.doc : Doc +data.Set.fromList.doc = + use Set fromList toList + {{ + Gets the {type Set} of elements in a {type List}. + + # Examples + + ``` + toList (fromList []) + ``` + + ``` + toList (fromList [5, 6, 7, 5, 9, 7, 6]) + ``` + }} + +data.Set.inertNonempty.doc : Doc +data.Set.inertNonempty.doc = + use Set insertNonempty + use Set.Nonempty toList + {{ + Insert a value into a {type Set}, returning a nonempty set. + + # Example + + ``` + toList (insertNonempty ?🌸 Set.empty) + ``` + + ``` + toList (insertNonempty ?🍎 (Set.fromList [?🍊, ?🍐])) + ``` + + # See Also + + * {Set.insert} + }} + +data.Set.insert : k -> Set k -> Set k +data.Set.insert k = cases internal.Set s -> internal.Set (Map.insert k () s) + +data.Set.insert.doc : Doc +data.Set.insert.doc = + use Set fromText insert toText + {{ + `` insert k s `` inserts the element `k` into the {type Set} `s`. Returns `s` + if `k` is already in `s`. + + ``` + toText (insert ?🐶 (fromText "🐱🐭🐹")) + ``` + + ``` + toText (insert ?🍎 (fromText "🍎🍊🍐🍇")) + ``` + }} + +data.Set.insertNonempty : k -> Set k -> Set.Nonempty k +data.Set.insertNonempty k = cases + internal.Set s -> Nonempty.Set (Map.insertNonempty k () s) + +data.Set.insertNonempty.doc : Doc +data.Set.insertNonempty.doc = + use Set insertNonempty + {{ + {insertNonempty} inserts an element into a {type Set}, and returns a + {type Set.Nonempty}. If the element was already in the set, the original set + is returned as a {type Set.Nonempty}. + + # Examples + + ``` + Set.Nonempty.toList (insertNonempty 1 (Set.fromList [2, 3, 4])) + ``` + + ``` + Nonempty.toText (insertNonempty ?c (Set.fromText "abracadabra")) + ``` + }} + +data.Set.internal.underlying : Set k -> Map k () +data.Set.internal.underlying = cases internal.Set s -> s + +data.Set.intersect : Set k -> Set k -> Set k +data.Set.intersect s1 s2 = + use Set.internal underlying + internal.Set (Map.intersect (underlying s1) (underlying s2)) + +data.Set.intersect.doc : Doc +data.Set.intersect.doc = + use Set empty fromText intersect toList + {{ + `` intersect x y `` returns the {type Set} of elements that are common to + both `x` and `y`. + + # Examples + + ``` + Set.toText (intersect (fromText "🏈🏀⚾️🎾") (fromText "🏐🏈🏉🎱")) + ``` + + The intersection with the empty {type Set} is always empty: + + ``` + toList (intersect (fromText "🏠🚿🛁") empty) + ``` + + ``` + toList (intersect empty (fromText "🏠🚿🛁")) + ``` + }} + +data.Set.intersects : [Set a] -> Set a +data.Set.intersects = cases + [] -> Set.empty + s +: ss -> List.foldLeft Set.intersect s ss + +data.Set.intersects.doc : Doc +data.Set.intersects.doc = + use Set fromList + {{ + Finds the intersection of all the sets in a list. See {Set.intersect}. + + # Example + + ``` + Set.intersects [fromList [1, 2, 3], fromList [3, 4, 5]] + ``` + }} + +test> data.Set.intersects.test = + check + ([[1, 2, 3], [3, 4, 5]] |> List.map Set.fromList |> Set.intersects + |> Set.toList + |> (===) [3]) + +data.Set.isEmpty : Set k -> Boolean +data.Set.isEmpty = cases internal.Set m -> Map.isEmpty m + +data.Set.isEmpty.doc : Doc +data.Set.isEmpty.doc = + use Set isEmpty + {{ + Returns `` true `` if the provided {type Set} has no elements and `` false `` + otherwise. + + # Examples + + ``` + isEmpty Set.empty + ``` + + ``` + isEmpty (Set.singleton 42) + ``` + + ``` + isEmpty (Set.fromList [1, 2, 3]) + ``` + }} + +data.Set.map : (a ->{e} b) -> Set a ->{e} Set b +data.Set.map f s = internal.Set (Map.mapKeys f (Set.internal.underlying s)) + +data.Set.map.doc : Doc +data.Set.map.doc = + {{ + Applies the given function to every element in the {type Set}, collecting the + results into a new {type Set}. + + # Example + + ``` + Set.toText + (Set.map ascii.toUpper (Set.fromText "Don't sweat the small stuff.")) + ``` + }} + +test> data.Set.map.test = runs 100 do + use Nat * + + use Set map + ks = setOf natInOrder () + f x = x * 2 + g x = x + 2 + expect (map (f << g) ks === map f (map g ks)) + +(data.Set.Nonempty.==) : Set.Nonempty k -> Set.Nonempty k -> Boolean +x data.Set.Nonempty.== y = + use Map.Nonempty == + use Nonempty.internal underlying + underlying x == underlying y + +data.Set.Nonempty.==.doc : Doc +data.Set.Nonempty.==.doc = + use Set.Nonempty == + {{ + Checks if two sets are equal by comparing their elements using + {Universal.ordering}. Two sets are equal if they contain the same elements. + + # Examples + + ``` + fromListAnd 1 [2, 3] == fromListAnd 1 [3, 2, 2] + ``` + + ``` + fromListAnd 1 [2, 3] == fromListAnd 1 [3] + ``` + }} + +data.Set.Nonempty.all : (a ->{e} Boolean) -> Set.Nonempty a ->{e} Boolean +data.Set.Nonempty.all p = List.all p << Set.toList << Set.Nonempty.toSet + +data.Set.Nonempty.all.doc : Doc +data.Set.Nonempty.all.doc = + use Nat isEven + use Nonempty all + {{ + Checks if all elements in a {type Set.Nonempty} satisfy a predicate. + + # Examples + + ``` + all isEven (fromListAnd 2 [4, 6]) + ``` + + ``` + all isEven (fromListAnd 2 [4, 5]) + ``` + }} + +data.Set.Nonempty.any : (a ->{e} Boolean) -> Set.Nonempty a ->{e} Boolean +data.Set.Nonempty.any p = List.any p << Set.toList << Set.Nonempty.toSet + +data.Set.Nonempty.any.doc : Doc +data.Set.Nonempty.any.doc = + use Nat isEven + use Nonempty any + {{ + Checks if any element in a {type Set.Nonempty} satisfies a predicate. + + # Examples + + ``` + any isEven (fromListAnd 2 [4, 6]) + ``` + + ``` + any isEven (fromListAnd 2 [3, 5]) + ``` + }} + +data.Set.Nonempty.contains : k -> Set.Nonempty k -> Boolean +data.Set.Nonempty.contains k = cases + Nonempty.Set m -> Map.Nonempty.contains k m + +data.Set.Nonempty.contains.doc : Doc +data.Set.Nonempty.contains.doc = + use Set.Nonempty contains + {{ + Checks if a {type Set.Nonempty} contains an element. + + # Examples + + ``` + contains 2 (fromListAnd 1 [2, 3]) + ``` + + ``` + contains 2 (fromListAnd 1 [3]) + ``` + }} + +data.Set.Nonempty.delete : k -> Set.Nonempty k -> Set k +data.Set.Nonempty.delete k s = + internal.Set (Map.Nonempty.delete k (Nonempty.internal.underlying s)) + +data.Set.Nonempty.delete.doc : Doc +data.Set.Nonempty.delete.doc = + use Set toList + use Set.Nonempty delete + {{ + Deletes an element from a {type Set.Nonempty}, returning a {type Set}. + + # Examples + + ``` + toList (delete 2 (fromListAnd 1 [2, 3])) + ``` + + ``` + toList (delete 2 (fromListAnd 1 [3])) + ``` + }} + +data.Set.Nonempty.deletes : [a] -> Set.Nonempty a -> Set a +data.Set.Nonempty.deletes as s = + List.foldLeft (s a -> Set.delete a s) (Set.Nonempty.toSet s) as + +data.Set.Nonempty.deletes.doc : Doc +data.Set.Nonempty.deletes.doc = + use Nonempty deletes + use Set toList + {{ + Deletes a {type List} of elements from a {type Set.Nonempty}, returning a + {type Set}. + + # Examples + + ``` + toList (deletes [2, 3] (fromListAnd 1 [2, 3])) + ``` + + ``` + toList (deletes [2, 3] (fromListAnd 1 [3])) + ``` + }} + +data.Set.Nonempty.doc : Doc +data.Set.Nonempty.doc = + use Nonempty all any deletes elementAt flatten intersects + use Set insertNonempty + use Set.Nonempty == contains delete flatMap foldLeft foldRight fromList insert intersect map singleton size subset superset toList toMap union unions + use Universal ordering + {{ + {type Set.Nonempty} is the non-empty version of {type Set}. It is a sorted + finite set with at least one element. Sorting is done according to + {ordering}. + + # Constructing nonempty sets + + {singleton} creates a {type Set.Nonempty} with one element: + + @signature{singleton} + + {fromList} creates a {type Set.Nonempty} from a {type List.Nonempty}: + + @signature{fromList} + + {fromListAnd} creates a {type Set.Nonempty} from an element and a + {type List}: + + @signature{fromListAnd} + + {fromTextAnd} creates a {type Set.Nonempty} from a {type Char} and a + {type Text}: + + @signature{fromTextAnd} + + {insertNonempty} creates a {type Set.Nonempty} from a {type Set} and an + element: + + @signature{insertNonempty} + + # Inserting and deleting elements + + {insert} adds an element to a {type Set.Nonempty}: + + @signature{insert} + + {delete} removes an element from a {type Set.Nonempty}, returning a + (possibly empty) {type Set}. + + @signature{delete} + + {deletes} removes a whole {type List} of elements from a + {type Set.Nonempty}, returning a (possibly empty) {type Set}. + + @signature{deletes} + + # Accessing and querying elements + + `` elementAt n s `` gets the `n`th smallest element from the + {type Set.Nonempty} `s`: + + @signature{elementAt} + + {all} checks if all elements satisfy a predicate: + + @signature{all} + + {any} checks if at least one element satisfies a predicate: + + @signature{any} + + {contains} checks if a specified element is in the {type Set.Nonempty}: + + @signature{contains} + + {size} gets the number of elements in the {type Set.Nonempty}: + + @signature{size} + + # Combining sets + + {intersect} gets the intersection of two {type Set.Nonempty}s, as a + {type Set}: + + @signature{intersect} + + {intersects} gets the intersection of a whole {type List} of + {type Set.Nonempty}s, as a {type Set}: + + @signature{intersects} + + {union} gets the union of two {type Set.Nonempty}s: + + @signature{union} + + {unions} gets the union of a whole {type List} of {type Set.Nonempty}s: + + @signature{unions} + + {flatten} gets the union of a whole {type Set.Nonempty} of + {type Set.Nonempty}s: + + @signature{flatten} + + # Comparing sets + + {==} checks if two {type Set.Nonempty}s have all the same elements + according to {ordering}: + + @signature{==} + + `` subset x y `` checks if `x` is a subset of `y`: + + @signature{subset} + + `` superset x y `` checks if `x` is a superset of `y`: + + @signature{superset} + + # Set.Nonempty traversals + + {map} applies a function to every element of a {type Set.Nonempty}, + collecting the results in a new {type Set.Nonempty}: + + @signature{map} + + {flatMap} applies a {type Set.Nonempty}-valued function to every element, + unioning the results: + + @signature{flatMap} + + {foldLeft} passes each element to an accumulating function starting with an + initial value, in ascending order: + + @signature{foldLeft} + + {foldRight} passes each element to an accumulating function, starting with + an initial value, in descending order. + + @signature{foldRight} + + # Conversions to other types + + {toList} gets the {type List} of elements in a {type Set.Nonempty}: + + @signature{toList} + + {toMap} generates a {type Map.Nonempty} where the elements of the + {type Set.Nonempty} are the keys, and the values are a function of the + keys. + + @signature{toMap} + + {Nonempty.toText} turns a {type Set.Nonempty} of {type Char} Unicode code + points to the {type Text} consisting of those code points in ascending + order. + + # Implementation notes + + The type is implemented as a {type Map.Nonempty} whose values are of type + {type Unit}. + + ``` + fromList ("a" +| ["b", "c", "d", "e", "f"]) + ``` + + The size of each subtree is cached at non-leaf nodes, and the entry at each + node has a key which is bigger than all entries in the left subtree, and + smaller than all entries in the right subtree. Operations like {insert} and + {delete} maintain a balanced tree so its depth is logarithmic in the + {size}. + }} + +data.Set.Nonempty.elementAt : Nat -> Set.Nonempty a -> Optional a +data.Set.Nonempty.elementAt n = cases + Nonempty.Set underlying -> Optional.map at1 (Map.Nonempty.nth n underlying) + +data.Set.Nonempty.elementAt.doc : Doc +data.Set.Nonempty.elementAt.doc = + use Nat + + use Nonempty elementAt + {{ + `` elementAt n s `` gets the `n`th smallest element from the + {type Set.Nonempty} `s`. + + If the {type Set.Nonempty} has fewer than `` n + 1 `` elements, this function + returns {None}. + + Otherwise, this function returns {Some} with the `n`th smallest element. + + # Examples + + ``` + elementAt 0 (fromListAnd 1 [2, 3]) + ``` + + ``` + elementAt 1 (fromListAnd 1 [2, 3]) + ``` + + ``` + elementAt 20 (fromListAnd 1 [2, 3]) + ``` + }} + +data.Set.Nonempty.flatMap : + (i ->{g} Set.Nonempty k) -> Set.Nonempty i ->{g} Set.Nonempty k +data.Set.Nonempty.flatMap f as = + use Set.Nonempty toList + Set.Nonempty.fromList (List.Nonempty.flatMap (a -> toList (f a)) (toList as)) + +data.Set.Nonempty.flatMap.doc : Doc +data.Set.Nonempty.flatMap.doc = + use Set.Nonempty flatMap + {{ + {flatMap} applies a {type Set.Nonempty}-valued function to every element, + unioning the results. + + # Example + + ``` + Nonempty.toText + (flatMap + (a -> fromTextAnd a (Text.toUppercase (Char.toText a))) + (fromTextAnd ?a "bracadabra")) + ``` + }} + +data.Set.Nonempty.flatten : Set.Nonempty (Set.Nonempty k) -> Set.Nonempty k +data.Set.Nonempty.flatten = Set.Nonempty.flatMap id + +data.Set.Nonempty.flatten.doc : Doc +data.Set.Nonempty.flatten.doc = + use Nonempty flatten + {{ + {flatten} gets the union of a whole {type Set.Nonempty} of + {type Set.Nonempty}s. + + # Example + + ``` + Nonempty.toText + (flatten + (fromListAnd + (fromTextAnd ?a "abc") [fromTextAnd ?b "def", fromTextAnd ?c "ghi"])) + ``` + }} + +data.Set.Nonempty.foldLeft : (b ->{e} a ->{e} b) -> b -> Set.Nonempty a ->{e} b +data.Set.Nonempty.foldLeft f b s = + List.Nonempty.foldLeft + f b (Map.Nonempty.keys (Nonempty.internal.underlying s)) + +data.Set.Nonempty.foldLeft.doc : Doc +data.Set.Nonempty.foldLeft.doc = + use List :+ + use Nat + + use Set.Nonempty foldLeft + {{ + {foldLeft} passes each element to an accumulating function starting with an + initial value, in ascending order, associating to the left. + + # Examples + + ``` + foldLeft (a b -> a + b) 0 (fromListAnd 1 [2, 3]) + ``` + + ``` + foldLeft (a b -> a :+ b) [] (fromListAnd 5 [2, 4, 4, 2, 6]) + ``` + }} + +data.Set.Nonempty.foldMap : + (b ->{e} b ->{e} b) -> (a ->{e} b) -> Set.Nonempty a ->{e} b +data.Set.Nonempty.foldMap f g s = + List.Nonempty.foldMap + f g (Map.Nonempty.keys (Nonempty.internal.underlying s)) + +data.Set.Nonempty.foldMap.doc : Doc +data.Set.Nonempty.foldMap.doc = + use Nat + + {{ + {Set.Nonempty.foldMap} combines all elements of a {type Set.Nonempty} into + one using a combining function, and then maps the result using a mapping + function. + + # Example + + ``` + Set.Nonempty.foldMap (+) Nat.increment (fromListAnd 1 [2, 3]) + ``` + + # See also + + * {List.Nonempty.foldMap} + }} + +data.Set.Nonempty.foldRight : + (a ->{e} b ->{e} b) -> b -> Set.Nonempty a ->{e} b +data.Set.Nonempty.foldRight f b s = + List.Nonempty.foldRight + f b (Map.Nonempty.keys (Nonempty.internal.underlying s)) + +data.Set.Nonempty.foldRight.doc : Doc +data.Set.Nonempty.foldRight.doc = + use List +: + use Nat + + use Set.Nonempty foldRight + {{ + {foldRight} passes each element to an accumulating function, starting with an + initial value, in ascending order, associating to the right. + + # Examples + + ``` + foldRight (a b -> a + b) 0 (fromListAnd 1 [2, 3]) + ``` + + ``` + foldRight (a b -> a +: b) [] (fromListAnd 5 [2, 4, 4, 2, 6]) + ``` + }} + +data.Set.Nonempty.fromList : List.Nonempty k -> Set.Nonempty k +data.Set.Nonempty.fromList ks = + Nonempty.Set (toNonemptyMap (List.Nonempty.map (k -> (k, ())) ks)) + +data.Set.Nonempty.fromList.doc : Doc +data.Set.Nonempty.fromList.doc = + use Set.Nonempty fromList + {{ + {fromList} gets a {type Set.Nonempty} from a {type List.Nonempty}. + + # Example + + ``` + Set.Nonempty.toList (fromList (5 +| [2, 4, 4, 2, 6])) + ``` + }} + +data.Set.Nonempty.fromListAnd : k -> [k] -> Set.Nonempty k +data.Set.Nonempty.fromListAnd k ks = Set.Nonempty.fromList (k +| ks) + +data.Set.Nonempty.fromListAnd.doc : Doc +data.Set.Nonempty.fromListAnd.doc = + use Set.Nonempty toList + {{ + Creates a {type Set.Nonempty} from an element a (possibly empty) {type List} + of elements. + + # Examples + + ``` + toList (fromListAnd 1 [2, 3]) + ``` + + ``` + toList (fromListAnd 1 [2, 3, 2]) + ``` + }} + +data.Set.Nonempty.fromTextAnd : Char -> Text -> Set.Nonempty Char +data.Set.Nonempty.fromTextAnd c t = Set.Nonempty.fromList (c +| toCharList t) + +data.Set.Nonempty.fromTextAnd.doc : Doc +data.Set.Nonempty.fromTextAnd.doc = + use Nonempty toText + {{ + Creates a {type Set.Nonempty} from a {type Char} and a {type Text}. + + # Examples + + ``` + toText (fromTextAnd ?🐯 "🔹🔸🔹") + ``` + + ``` + toText (fromTextAnd ?e "spionage") + ``` + }} + +data.Set.Nonempty.insert : k -> Set.Nonempty k -> Set.Nonempty k +data.Set.Nonempty.insert k = cases + Nonempty.Set s -> Nonempty.Set (Map.Nonempty.insert k () s) + +data.Set.Nonempty.insert.doc : Doc +data.Set.Nonempty.insert.doc = + use Set.Nonempty insert + {{ + {insert} adds an element to a {type Set.Nonempty}. + + # Example + + ``` + Set.Nonempty.toList (insert 3 (fromListAnd 1 [2, 4])) + ``` + }} + +data.Set.Nonempty.internal.underlying : Set.Nonempty k -> Map.Nonempty k () +data.Set.Nonempty.internal.underlying = cases Nonempty.Set s -> s + +data.Set.Nonempty.intersect : Set.Nonempty k -> Set.Nonempty k -> Set k +data.Set.Nonempty.intersect s1 s2 = + use Map.Nonempty toMap + use Nonempty.internal underlying + internal.Set (Map.intersect (toMap (underlying s1)) (toMap (underlying s2))) + +data.Set.Nonempty.intersect.doc : Doc +data.Set.Nonempty.intersect.doc = + use Set.Nonempty intersect + {{ + {intersect} gets the intersection of two {type Set.Nonempty}s. + + # Example + + ``` + Set.toList (intersect (fromListAnd 1 [2, 3]) (fromListAnd 2 [3, 4])) + ``` + }} + +data.Set.Nonempty.intersects : [Set.Nonempty a] -> Set a +data.Set.Nonempty.intersects = cases + [] -> Set.empty + s +: ss -> + List.foldLeft + (acc s -> Set.intersect acc (Set.Nonempty.toSet s)) + (Set.Nonempty.toSet s) + ss + +data.Set.Nonempty.intersects.doc : Doc +data.Set.Nonempty.intersects.doc = + use Nonempty intersects + {{ + {intersects} gets the intersection of a {type List} of {type Set.Nonempty}s. + + # Example + + ``` + Set.toList + (intersects + [fromListAnd 1 [2, 3], fromListAnd 2 [3, 4], fromListAnd 3 [4, 5]]) + ``` + }} + +data.Set.Nonempty.map : (a ->{e} b) -> Set.Nonempty a ->{e} Set.Nonempty b +data.Set.Nonempty.map f s = + Nonempty.Set (Nonempty.mapKeys f (Nonempty.internal.underlying s)) + +data.Set.Nonempty.map.doc : Doc +data.Set.Nonempty.map.doc = + use Set.Nonempty map + {{ + {map} applies a function to every element of a {type Set.Nonempty}. + + # Example + + ``` + Nonempty.toText (map Char.toUppercase (fromTextAnd ?a "bracadabra")) + ``` + }} + +data.Set.Nonempty.random : Set.Nonempty a ->{Random} a +data.Set.Nonempty.random s = + n = Random.natIn 0 (Set.Nonempty.size s) + getOrBug ("index out of bounds", n, s) (Nonempty.elementAt n s) + +data.Set.Nonempty.random.doc : Doc +data.Set.Nonempty.random.doc = + use Nonempty random + {{ + {random} gets a random element from a {type Set.Nonempty}. + + # Example + + ``` + lcg 0 do random (fromListAnd 1 [2, 3, 4, 5]) + ``` + }} + +data.Set.Nonempty.randomChoice : Set.Nonempty a ->{Random} a +data.Set.Nonempty.randomChoice = cases + Nonempty.Set map -> + randomIndex = Random.natIn 0 (Map.Nonempty.size map) + Map.Nonempty.nth randomIndex map + |> getOrBug "Set.Nonempty.randomChoice: index out of bounds" + |> at1 + +data.Set.Nonempty.randomChoice.doc : Doc +data.Set.Nonempty.randomChoice.doc = + use Nonempty Nonempty + use Set.Nonempty fromList randomChoice + {{ + Picks a random element from the given {type Set.Nonempty}. + + # Examples + + ``` + lcg 4096 do randomChoice (fromList (Nonempty ?a [?b, ?c, ?d])) + ``` + + ``` + lcg 2510 do randomChoice (fromList (Nonempty ?a [?b, ?c, ?d])) + ``` + }} + +test> data.Set.Nonempty.randomChoice.test = test.verify do + set = Set.Nonempty.fromList (0 +| [1, 2, 3, 4, 5, 6, 7, 8, 9]) + Each.repeat 1000 + e = Set.Nonempty.randomChoice set + ensure (Set.Nonempty.contains e set) + +data.Set.Nonempty.reduce : (a ->{e} a ->{e} a) -> Set.Nonempty a ->{e} a +data.Set.Nonempty.reduce f s = + reduceRight f (Map.Nonempty.keys (Nonempty.internal.underlying s)) + +data.Set.Nonempty.reduce.doc : Doc +data.Set.Nonempty.reduce.doc = + use Nat + + {{ + {reduce} combines all elements of a {type Set.Nonempty} into one using a + combining function. + + # Example + + ``` + reduce (+) (fromListAnd 1 [2, 3]) + ``` + + # See also + + * {reduceRight} + }} + +data.Set.Nonempty.similarity : Set.Nonempty k -> Set.Nonempty k -> Float +data.Set.Nonempty.similarity a b = + use Float + - / > fromNat + use Set Nonempty.size + use Set.Nonempty toSet + i = fromNat (Set.size (Set.intersect (toSet a) (toSet b))) + s = fromNat (Nonempty.size a) + fromNat (Nonempty.size b) - i + if s > 0.0 then i / s else 1.0 + +data.Set.Nonempty.similarity.doc : Doc +data.Set.Nonempty.similarity.doc = + use Nonempty similarity + {{ + Measures the similarity of two {type Set.Nonempty}s. The result is a number + between `` 0.0 `` and ``1.0``, where `` 0.0 `` means they have no elements in + common, and `` 1.0 `` means they are equal. It is calculated as the size of + the intersection divided by the size of the union. + + # Examples + + ``` + similarity (fromListAnd 1 [2, 3]) (fromListAnd 2 [3, 4]) + ``` + + ``` + similarity (fromTextAnd ?c "arnivorous") (fromTextAnd ?c "oronavirus") + ``` + }} + +data.Set.Nonempty.singleton : a -> Set.Nonempty a +data.Set.Nonempty.singleton a = Nonempty.Set (Map.Nonempty.singleton a ()) + +data.Set.Nonempty.singleton.doc : Doc +data.Set.Nonempty.singleton.doc = + use Set.Nonempty singleton + {{ + {singleton} constructs a {type Set.Nonempty} with a single element. + + # Example + + ``` + Set.Nonempty.toList (singleton 1) + ``` + }} + +data.Set.Nonempty.size : Set.Nonempty k -> Nat +data.Set.Nonempty.size s = Map.Nonempty.size (Nonempty.internal.underlying s) + +data.Set.Nonempty.size.doc : Doc +data.Set.Nonempty.size.doc = + use Set.Nonempty size + {{ + {size} gets the number of elements in a {type Set.Nonempty}. + + # Example + + ``` + size (fromListAnd 1 [2, 3]) + ``` + }} + +data.Set.Nonempty.subset : Set.Nonempty a -> Set.Nonempty a -> Boolean +data.Set.Nonempty.subset s1 s2 = + Nonempty.all (a -> Set.Nonempty.contains a s2) s1 + +data.Set.Nonempty.subset.doc : Doc +data.Set.Nonempty.subset.doc = + use Set.Nonempty subset + {{ + {subset} checks if a {type Set.Nonempty} is a subset of another. A + {type Set.Nonempty} is a subset of another if it contains no elements that + are not in the other. + + Every {type Set.Nonempty} is a subset of itself. + + # Example + + ``` + subset (fromListAnd 1 [2, 3]) (fromListAnd 2 [3, 4]) + ``` + + ``` + subset (fromTextAnd ?c "arnivorous") (fromTextAnd ?c "oronavirus") + ``` + }} + +data.Set.Nonempty.superset : Set.Nonempty a -> Set.Nonempty a -> Boolean +data.Set.Nonempty.superset = flip Set.Nonempty.subset + +data.Set.Nonempty.superset.doc : Doc +data.Set.Nonempty.superset.doc = + use Set.Nonempty superset + {{ + {superset} checks if a {type Set.Nonempty} is a superset of another. A + {type Set.Nonempty} is a superset of another if it contains all elements of + the other. + + Every {type Set.Nonempty} is a superset of itself. + + # Example + + ``` + superset (fromListAnd 1 [2, 3]) (fromListAnd 2 [3, 4]) + ``` + + ``` + superset (fromTextAnd ?c "arnivorous") (fromTextAnd ?c "oronavirus") + ``` + }} + +data.Set.Nonempty.toList : Set.Nonempty k -> List.Nonempty k +data.Set.Nonempty.toList = Map.Nonempty.keys << Nonempty.internal.underlying + +data.Set.Nonempty.toList.doc : Doc +data.Set.Nonempty.toList.doc = + use Set.Nonempty toList + {{ + {toList} converts a {type Set.Nonempty} to a {type List.Nonempty}. + + # Example + + ``` + toList (fromListAnd 1 [2, 3]) + ``` + }} + +data.Set.Nonempty.toMap : (k ->{e} v) -> Set.Nonempty k ->{e} Map.Nonempty k v +data.Set.Nonempty.toMap f = + Map.Nonempty.mapWithKey (k _ -> f k) << Nonempty.internal.underlying + +data.Set.Nonempty.toMap.doc : Doc +data.Set.Nonempty.toMap.doc = + use Set.Nonempty toMap + {{ + {toMap} converts a {type Set.Nonempty} to a {type Map.Nonempty}. The keys of + the map are the elements of the set, and the values are the result of + applying the given function to each key. + + # Example + + ``` + Map.Nonempty.toList (toMap Char.toUppercase (fromTextAnd ?a "bracadabra")) + ``` + }} + +data.Set.Nonempty.toSet : Set.Nonempty k -> Set k +data.Set.Nonempty.toSet = + internal.Set << Map.Nonempty.toMap << Nonempty.internal.underlying + +data.Set.Nonempty.toSet.doc : Doc +data.Set.Nonempty.toSet.doc = + use Set toList + use Set.Nonempty toSet + {{ + Converts a {type Set.Nonempty} to a {type Set}. + + # Examples + + ``` + toList (toSet (fromListAnd 1 [2, 3])) + ``` + + ``` + toList (toSet (fromListAnd 1 [3])) + ``` + }} + +data.Set.Nonempty.toText : Set.Nonempty Char -> Text +data.Set.Nonempty.toText chars = + fromCharList (List.Nonempty.toList (Set.Nonempty.toList chars)) + +data.Set.Nonempty.toText.doc : Doc +data.Set.Nonempty.toText.doc = + use Nonempty toText + {{ + {toText} converts a {type Set.Nonempty} of {type Char} to a {type Text}. + + # Example + + ``` + toText (fromTextAnd ?c "arnivorous") + ``` + }} + +data.Set.Nonempty.union : Set.Nonempty k -> Set.Nonempty k -> Set.Nonempty k +data.Set.Nonempty.union s1 s2 = + use Nonempty.internal underlying + Nonempty.Set (Map.Nonempty.union (underlying s1) (underlying s2)) + +data.Set.Nonempty.union.doc : Doc +data.Set.Nonempty.union.doc = + use Set.Nonempty union + {{ + {union} combines two {type Set.Nonempty}s into one. The result contains all + elements from both inputs. + + # Example + + ``` + Set.Nonempty.toList (union (fromListAnd 1 [2, 3]) (fromListAnd 2 [3, 4])) + ``` + + ``` + Nonempty.toText + (union (fromTextAnd ?c "arnivorous") (fromTextAnd ?c "oronavirus")) + ``` + }} + +data.Set.Nonempty.unions : List.Nonempty (Set.Nonempty a) -> Set.Nonempty a +data.Set.Nonempty.unions = cases + Nonempty.Nonempty s ss -> List.foldLeft Set.Nonempty.union s ss + +data.Set.Nonempty.unions.doc : Doc +data.Set.Nonempty.unions.doc = + use Set.Nonempty unions + {{ + {unions} combines a {type List} of {type Set.Nonempty}s into one. The result + contains all elements from all inputs. + + # Example + + ``` + Set.Nonempty.toList + (unions (fromListAnd 1 [2, 3] +| [fromListAnd 2 [3, 4]])) + ``` + + ``` + Nonempty.toText (unions (fromTextAnd ?b "ee" +| [fromTextAnd ?f "ly"])) + ``` + }} + +data.Set.random : Set a ->{Random} Optional a +data.Set.random s = + use Nat == + use Set size + if size s == 0 then None else Set.elementAt (Random.natIn 0 (size s)) s + +data.Set.random.doc : Doc +data.Set.random.doc = + {{ + Returns a random element from a {type Set}. + + # Example + + ``` + splitmix 42 do Set.random (Set.fromList [?🍎, ?🍌, ?🍊]) + ``` + }} + +data.Set.randomChoice : Set a ->{Exception, Random} a +data.Set.randomChoice = cases internal.Set map -> Map.randomChoice map |> at1 + +data.Set.randomChoice.doc : Doc +data.Set.randomChoice.doc = + use Set fromList randomChoice + {{ + Returns a random element from the given {type Set}. Assumes that the + {type Set} is not empty, so an empty {type Set} will cause a runtime + exception. + + # Examples + + ``` + catch do lcg 4096 do randomChoice (fromList [0, 3, 5, 7]) + ``` + + ``` + catch do lcg 2510 do randomChoice (fromList [?x, ?y, ?z]) + ``` + + ``` + catch do lcg 128 do randomChoice (fromList [char.digit, hex]) () + ``` + }} + +test> data.Set.randomChoice.test = test.verify do + set = Set.fromList [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + Each.repeat 1000 + e = Set.randomChoice set + ensure (Set.contains e set) + +data.Set.similarity : Set k -> Set k -> Float +data.Set.similarity a b = + use Float + - / > fromNat + use Set size + i = fromNat (size (Set.intersect a b)) + s = fromNat (size a) + fromNat (size b) - i + if s > 0.0 then i / s else 1.0 + +data.Set.similarity.doc : Doc +data.Set.similarity.doc = + use Set empty fromText similarity singleton + {{ + Measures the similarity of two {type Set}s. This is the number of elements + they have in common, as a proportion of the total number of elements in their + union — a number between `` 0.0 `` (no common elements) and `` 1.0 `` (the + {type Set}s are identical). + + # Examples + + ``` + similarity (fromText "France") (fromText "French") + ``` + + ``` + similarity (singleton 1) (singleton 2) + ``` + + ``` + similarity empty empty + ``` + }} + +data.Set.singleton : a -> Set a +data.Set.singleton a = Set.insert a Set.empty + +data.Set.singleton.doc : Doc +data.Set.singleton.doc = + {{ + Constructs a {type Set} with a single element. + + # Example + + ``` + Set.toList (Set.singleton ?🤖) + ``` + }} + +test> data.Set.singleton.test = + check + let + s = Set.singleton 0 + Set.contains 0 s && Set.size s === 1 + +data.Set.size : Set k -> Nat +data.Set.size s = Map.size (Set.internal.underlying s) + +data.Set.size.doc : Doc +data.Set.size.doc = + use Set size + {{ + Gets the number of elements in a {type Set}. + + # Examples + + ``` + size Set.empty + ``` + + ``` + size (Set.fromList [1, 2, 3]) + ``` + }} + +data.Set.subset : Set a -> Set a -> Boolean +data.Set.subset s1 s2 = Set.all (a -> Set.contains a s2) s1 + +data.Set.subset.doc : Doc +data.Set.subset.doc = + use Set fromText subset + {{ + `` subset s1 s2 `` returns `` true `` if every element in `s1` is also in + `s2`. Otherwise ``false``. + + # Examples + + ``` + subset (fromText "abc") (fromText "abcde") + ``` + + ``` + subset (fromText "wxy") (fromText "xyz") + ``` + + Every {type Set} is a subset of itself: + + ``` + s = fromText "abc" + subset s s + ``` + }} + +data.Set.superset : Set a -> Set a -> Boolean +data.Set.superset = flip Set.subset + +data.Set.superset.doc : Doc +data.Set.superset.doc = + use Set fromText superset + {{ + `` superset s1 s2 `` returns `` true `` if every element in `s2` is also in + `s1`. Otherwise ``false``. + + # Examples + + ``` + superset (fromText "abcde") (fromText "abc") + ``` + + ``` + superset (fromText "wxy") (fromText "xyz") + ``` + + Every {type Set} is a superset of itself: + + ``` + s = fromText "abc" + superset s s + ``` + }} + +data.Set.toList : Set k -> [k] +data.Set.toList = Map.keys << Set.internal.underlying + +data.Set.toList.doc : Doc +data.Set.toList.doc = + use Set toList + {{ + Converts a {type Set} to a {type List} of the elements in that {type Set}. + + # Examples + + ``` + toList (Set.fromText "🍕🌮🍔🍟") + ``` + + ``` + toList Set.empty + ``` + }} + +data.Set.toMap : (k ->{e} v) -> Set k ->{e} Map k v +data.Set.toMap f = Map.mapWithKey (k _ -> f k) << Set.internal.underlying + +data.Set.toMap.doc : Doc +data.Set.toMap.doc = + use Set toMap + {{ + `` toMap v ks `` generates a {type Map} where the keys are the elements of + the {type Set} `ks`, and the values are generated by passing each key to the + function `v`. + + # Examples + + ``` + Map.toList (toMap Text.size (Set.fromList ["one", "two", "three"])) + ``` + }} + +data.Set.union : Set k -> Set k -> Set k +data.Set.union s1 s2 = + use Set.internal underlying + internal.Set (Map.union (underlying s1) (underlying s2)) + +data.Set.union.doc : Doc +data.Set.union.doc = + use Set empty fromText toText union + {{ + `` union x y `` returns the {type Set} of elements that are in either `x` or + `y`. + + # Examples + + ``` + toText (union (fromText "🏈🏀⚾️🎾") (fromText "🏐🏈🏉🎱")) + ``` + + The union with the empty {type Set} is a no-op: + + ``` + toText (union (fromText "🏠🚿🛁") empty) + ``` + + ``` + toText (union empty (fromText "🏠🚿🛁")) + ``` + }} + +data.Set.unions : [Set a] -> Set a +data.Set.unions = List.foldLeft Set.union Set.empty + +data.Set.unions.doc : Doc +data.Set.unions.doc = + {{ + Takes a {type List} of {type Set}s and unions them all into one {type Set}. + + # Example + + ``` + Set.toText (Set.unions (List.map Set.fromText ["🍎🍊🍐🍇", "🥕🌽🥒🥦"])) + ``` + }} + +test> data.Set.unions.test = runs 100 do + use Set == + sets = gen.listOf (setOf natInOrder) () + expect (Set.unions sets == Set.flatten (Set.fromList sets)) + +(data.Stream.++) : + (v1 ->{g, Stream a} v2) -> (v2 ->{g, Stream a} v3) -> v1 ->{g, Stream a} v3 +a data.Stream.++ b = a >> b + +data.Stream.++.doc : Doc +data.Stream.++.doc = + {{ + `s1 ++ s2` appends stream `s2` to the end of `s1`. `(++)` is a special case + of `.base.andThen`, for `Stream` computations. + }} + +(data.Stream.+:) : a -> '{g, Stream a} r -> '{g, Stream a} r +(data.Stream.+:) a s _ = + emit a + s() + +data.Stream.+:.doc : Doc +data.Stream.+:.doc = + {{ `el +: s` prepends a single element `el` to a stream `s`. }} + +data.Stream.all : (a ->{g} Boolean) -> '{g, Stream a} r ->{g} Boolean +data.Stream.all = compose2 (isNone << Stream.head) Stream.dropWhile + +data.Stream.all.doc : Doc +data.Stream.all.doc = + use Nat < <= + use Stream all + {{ + `` all p x `` returns `` true `` if every element of the {type Stream} `x` + satisfies predicate `p`. + + # Examples + + ``` + to 10 |> all (x -> x <= 10) + ``` + + ``` + to 10 |> all (x -> x < 10) + ``` + + ## See also + + * {Stream.any} + }} + +test> data.Stream.all.test = test.verify do + use Nat < <= + use Stream all + s = to 10 + ensureEqual (all (x -> x <= 10) s) true + ensureEqual (all (x -> x < 10) s) false + +data.Stream.any : (a ->{g} Boolean) -> '{g, Stream a} r ->{g} Boolean +data.Stream.any = compose2 isSome Stream.find + +data.Stream.any.doc : Doc +data.Stream.any.doc = + use Nat == + use Stream any + {{ + `` any p x `` returns `` true `` if at least one element of the {type Stream} + `x` satisfies predicate `p`. + + # Examples + + ``` + to 10 |> any ((==) 10) + ``` + + ``` + to 10 |> any ((==) 11) + ``` + + ## See also: + + * {Stream.all} + }} + +test> data.Stream.any.test = test.verify do + use Nat == + use Stream any + s = to 10 + ensureEqual (any ((==) 10) s) true + ensureEqual (any ((==) 11) s) false + +data.Stream.changes : Boolean -> '{g, Stream a} r -> '{g, Stream a} r +data.Stream.changes emitInitial stream = do changes! emitInitial stream + +data.Stream.changes.doc : Doc +data.Stream.changes.doc = + {{ + `` changes emitInitial stream `` returns a delayed computation of + ``changes! emitInitial stream``. + }} + +test> data.Stream.changes.tests.multiple = test.verify do + use Stream toList + input = Stream.fromList [1, 1, 1, 2, 3, 3, 4, 4, 4, 4, 5, 5] + let + res = input |> changes true |> toList + expected = [1, 2, 3, 4, 5] + ensureEqual res expected + res = input |> changes false |> toList + expected = [2, 3, 4, 5] + ensureEqual res expected + +test> data.Stream.changes.tests.pair = test.verify do + use Stream toList + input = Stream.rangeClosed 1 2 + let + res = input |> changes false |> toList + expected = [2] + ensureEqual res expected + res = input |> changes true |> toList + expected = [1, 2] + ensureEqual res expected + +test> data.Stream.changes.tests.repeatSingle = test.verify do + use Stream fromList toList + input = [1, 1] + let + res = fromList [1, 1] |> changes false |> toList + expected = [] + ensureEqual res expected + res = fromList [1, 1] |> changes true |> toList + expected = [1] + ensureEqual res expected + +test> data.Stream.changes.tests.single = test.verify do + use Stream toList + input = do emit 1 + let + res = input |> changes false |> toList + expected = [] + ensureEqual res expected + res = input |> changes true |> toList + expected = [1] + ensureEqual res expected + +data.Stream.changes! : Boolean -> '{g, Stream a} r ->{g, Stream a} r +data.Stream.changes! = changesBy! (===) + +data.Stream.changes!.doc : Doc +data.Stream.changes!.doc = + use Stream fromList toList + {{ + `` changes! emitInitial stream `` transforms the input `stream` into a stream + that only contains elements that are not equal to the previous element + (according to [universal equality]({===})). + + If `emitInitial` is true, the first element of the input stream is always + emitted. Otherwise the first element emitted in the output will be the first + element in the input that __differs__ from the initial element in the input. + + See {changesBy!} for a version that uses a provided equality function instead + of universal equality. + + # Examples + + ``` + toList do changes! true (fromList [1, 2, 2, 2, 3, 3, 4, 5]) + ``` + + ``` + toList do changes! false (fromList [1, 2, 2, 2, 3, 3, 4, 5]) + ``` + + ``` + toList do changes! true do emit 1 + ``` + + ``` + toList do changes! false do emit 1 + ``` + }} + +data.Stream.changesBy : + (a -> a -> Boolean) -> Boolean -> '{g, Stream a} r -> '{g, Stream a} r +data.Stream.changesBy eq emitInitial stream = + do changesBy! eq emitInitial stream + +data.Stream.changesBy.doc : Doc +data.Stream.changesBy.doc = + {{ + `` changesBy eq emitInitial stream `` returns a delayed computation of + ``changesBy! eq emitInitial stream``. + }} + +data.Stream.changesBy! : + (a -> a -> Boolean) -> Boolean -> '{g, Stream a} r ->{g, Stream a} r +data.Stream.changesBy! eq emitInitial stream = + go : a -> Request {Stream a} r ->{g, Stream a} r + go prev = cases + { r } -> r + { emit current -> rest } -> + if eq prev current then handle rest() with go current + else + emit current + handle rest() with go current + handle stream() + with cases + { r } -> r + { emit a -> k } -> + if emitInitial then emit a else () + handle k() with go a + +data.Stream.changesBy!.doc : Doc +data.Stream.changesBy!.doc = + use Nat == + use Stream fromList toList + {{ + `` changesBy! eq emitInitial stream `` transforms the input `stream` into a + stream that only contains elements that are not equal to the previous element + (according to `eq`). + + If `emitInitial` is true, the first element of the input stream is always + emitted. Otherwise the first element emitted in the output will be the first + element in the input that __differs__ from the initial element in the input. + + See {changes!} for a version that uses universal equality instead of a + provided equality function. + + See {changesBy} for a version that returns a delayed result. + + # Examples + + ``` + toList do changesBy! (==) true (fromList [1, 2, 2, 2, 3, 3, 4, 5]) + ``` + + ``` + toList do changesBy! (==) false (fromList [1, 2, 2, 2, 3, 3, 4, 5]) + ``` + + ``` + toList do changesBy! (==) true do emit 1 + ``` + + ``` + toList do changesBy! (==) false do emit 1 + ``` + }} + +data.Stream.chunk : Nat -> '{g, Stream a} r -> '{g, Stream (List.Nonempty a)} r +data.Stream.chunk n = delay (chunk! n) + +data.Stream.chunk.doc : Doc +data.Stream.chunk.doc = + {{ {Stream.chunk} returns a delayed computation of {chunk!}. }} + +test> data.Stream.chunk.tests = + test.verify do + use Each repeat run + use List.Nonempty singleton + use Random natIn + use Stream chunk rangeClosed + run do + repeat 100 + n = Random.nat! + ensureEqual [] (Stream.toList (chunk n do ())) + ensureEqual [singleton 1] (Stream.toList (chunk 1 do emit 1)) + ensureEqual + [singleton 1, singleton 2, singleton 3] + (Stream.toList (chunk 1 (rangeClosed 1 3))) + ensureEqual + [1 +| [2], singleton 3] (Stream.toList (chunk 2 (rangeClosed 1 3))) + ensureEqual [1 +| [2, 3]] (Stream.toList (chunk 3 (rangeClosed 1 3))) + ensureEqual [1 +| [2, 3]] (Stream.toList (chunk 4 (rangeClosed 1 3))) + ensureEqual + [1 +| [2, 3], 4 +| [5, 6], 7 +| [8, 9]] + (Stream.toList (chunk 3 (rangeClosed 1 9))) + ensureEqual + [1 +| [2, 3], 4 +| [5, 6], singleton 7] + (Stream.toList (chunk 3 (rangeClosed 1 7))) + run do + repeat 20 + streamSize = do natIn 0 20 + groupSize = natIn 1 20 + initialList = Random.listOf Random.nat streamSize + finalList = + chunk groupSize (Stream.fromList initialList) + |> concatMap List.Nonempty.toList + |> Stream.toList + ensureEqual initialList finalList + +data.Stream.chunk! : Nat -> '{g, Stream a} r ->{g, Stream (List.Nonempty a)} r +data.Stream.chunk! n = + use List.Nonempty singleton + use Nat == decrement + if n == 0 then + ((bug ("group size must be greater than 0", n)) : + ('{g, Stream a} r ->{g, Stream (List.Nonempty a)} r)) + else + go acc need = cases + { r } -> + emit acc + r + { emit a -> k } -> + if need == 0 then + emit acc + handle k() with go (singleton a) (decrement n) + else handle k() with go (Nonempty.snoc acc a) (decrement need) + thunk -> + (handle thunk() + with cases + { r } -> r + { emit a -> k } -> handle k() with go (singleton a) (decrement n)) + +data.Stream.chunk!.doc : Doc +data.Stream.chunk!.doc = + use Stream chunk rangeClosed toList + {{ + `` chunk! groupSize stream `` transforms a stream of `a` elements into a + stream of [nonempty lists]({type List.Nonempty}) of `a` elements. Each list + in the stream will have a length of exactly `groupSize`, except for the last + emitted list, which may have fewer elements. + + {{ + docCallout + (Some {{ ⚠️ }}) {{ This function will call {bug} if `groupSize` is `0`. }} + }} + + # Examples + + ``` + chunk 3 (rangeClosed 1 10) |> toList + ``` + + ``` + chunk 1 (rangeClosed 1 5) |> toList + ``` + }} + +data.Stream.collate : + '{g1, Stream a} r -> '{g2, Stream a} Void -> '{g1, g2, Stream a} r +data.Stream.collate finiteStream infiniteStream = + do collate! finiteStream infiniteStream + +data.Stream.collate.doc : Doc +data.Stream.collate.doc = + {{ + `data.Stream.intersperse finiteStream infiniteStream` returns a delayed + computation of `data.Stream.intersperse! finiteStream infiniteStream` + }} + +test> data.Stream.collate.test.testIntersperse = test.verify do + use Counter nat! + use Nat increment + use Stream tap + commas = (Stream.repeat do ",") |> (tap do comma!) + nats n = to n |> Stream.map Nat.toText |> (tap do nat!) + expected = "0,1,2,3,4,5" + actual = + handle collate (nats 5) commas |> Stream.toList |> Text.join "" + with + go cs ns = cases + { comma! -> k } -> handle k() with go (increment cs) ns + { nat! -> k } -> handle k() with go cs (increment ns) + { txt } -> + ensureEqual cs 5 + ensureEqual ns 6 + txt + go 0 0 + ensureEqual expected actual + +data.Stream.collate! : + '{g1, Stream a} r -> '{g2, Stream a} Void ->{g1, g2, Stream a} r +data.Stream.collate! finiteStream infiniteStream = + handle finiteStream() with handler3 infiniteStream + +data.Stream.collate!.doc : Doc +data.Stream.collate!.doc = + {{ + `Stream.intersperce! finiteStream infiniteStream` alternates between emitting + elements of the finiteStream and the infiniteStream, The resulting stream + terminates after emitting the final element of the finiteStream. + + ``` + nats = to 5 |> Stream.map Nat.toText + commas = Stream.repeat do "," + combined = do collate! nats commas + combined |> Stream.toList |> Text.join "" + ``` + }} + +data.Stream.collate!.handler1 : + '{g2} Void -> Request (Stream a) r ->{g2, Stream a} r +data.Stream.collate!.handler1 k = cases + { emit a -> s } -> handle k() with data.Stream.collate!.handler2 a s + { done } -> done + +data.Stream.collate!.handler2 : + a -> '{g2} r -> Request (Stream a) Void ->{g2, Stream a} r +data.Stream.collate!.handler2 af k = cases + { emit ai -> s } -> + emit ai + emit af + handle k() with data.Stream.collate!.handler1 s + +data.Stream.collate!.handler3 : + '{g2} Void -> Request (Stream a) r ->{g2, Stream a} r +data.Stream.collate!.handler3 k = cases + { emit a -> s } -> + emit a + handle s() with handler1 k + { done } -> done + +data.Stream.concatMap : (a ->{e} [b]) -> '{e, Stream a} r -> '{e, Stream b} r +data.Stream.concatMap f s _ = concatMap! f s + +data.Stream.concatMap.doc : Doc +data.Stream.concatMap.doc = + {{ + `` concatMap f s `` returns a delayed computation of ``concatMap! f s``. + }} + +data.Stream.concatMap! : (a ->{e} [b]) -> '{e, Stream a} r ->{e, Stream b} r +data.Stream.concatMap! f = flatMap! (a -> fromList! (f a)) + +data.Stream.concatMap!.doc : Doc +data.Stream.concatMap!.doc = + {{ + `` concatMap! f s `` applies `f` to each element emitted by stream `s` in + turn and emits the elements of the resulting lists. + }} + +data.Stream.contains : a -> '{g, Stream a} r ->{g} Boolean +data.Stream.contains x = isSome << Stream.find ((===) x) + +data.Stream.contains.doc : Doc +data.Stream.contains.doc = + use Stream contains + {{ + `` contains x s `` returns `` true `` if the element `x` is found in the + {type Stream} `s`. + + # Examples + + ``` + to 10 |> contains 10 + ``` + + ``` + to 10 |> contains 11 + ``` + + ## See also: + + * {Stream.find} + }} + +test> data.Stream.contains.test = test.verify do + use Stream contains + s = to 10 + ensureEqual (contains 5 s) true + ensureEqual (contains 11 s) false + +data.Stream.doc : Doc +data.Stream.doc = + use Stream ++ + {{ + The {type Stream} ability provides a way to lazily generate a potentially + infinite sequence of values that can use other abilities in their + computation. The ability is parameterized by the type of values the stream + generates. + + {{ + docCallout + (Some {{ ℹ️ }}) + {{ + Operations on {type Stream} generally have a `!` suffix to indicate that + they are eager and will immediately emit values, and the same operation + without the `!` suffix will return a lazy computation that can be forced + later with (e.g. with {force}) to emit values. + }} }} + + # Emitting values onto a stream + + Emit a single value: + + @signature{emit} + + Emit a value and return it as the result: + + @signatures{emitAndReturn!, emitAndReturn} + + Emit a list of values: + + @signatures{fromList!, Stream.fromList} + + Emit all the {type Nat} values in order starting from a given value: + + @signatures{Stream.from!, Stream.from} + + Emit all the {type Nat} values in order starting from 0 and up to a given + value (inclusive): + + @signatures{to!, to} + + Emit all the {type Nat} values in order starting from 0 and up to a given + value (exclusive): + + @signatures{until!, Stream.until} + + Emit all the {type Nat} values in a range (exclusive of the end value): + + @signatures{Stream.range!, Stream.range} + + Emit all the {type Nat} values in a range (inclusive of the end value): + + @signatures{rangeClosed!, Stream.rangeClosed} + + Repeatedly force a computation to produce a stream of values: + + @signature{Stream.repeat} + + Iterate a function over a seed value to produce a stream of values: + + @signatures{iterate!, Stream.iterate} + + Iterate a state transition function over a seed value to produce a stream + of values: + + @signature{Stream.unfold} + + # Consuming values from a stream + + Collect all the values from a stream into a list: + + @signatures{Stream.toList, toDelayedList} + + Collect all the values from a stream into a list and also return the final + result of the {type Stream} computation: + + @signatures{toListWithResult, toDelayedListWithResult} + + Take the first value from a stream and return it together with the + remainder of the stream: + + @signature{Stream.uncons} + + Ignore all the values in the stream, but force the computation to run its + effects: + + @signature{drain} + + Accumulate values from a stream with a function, starting with an initial + value: + + @signatures{Stream.fold, foldDelayed} + + Fold a stream in a balanced way by recursively splitting the stream in half + and folding each half: + + @signature{Stream.foldBalanced} + + Fold a stream, returning both the result of the fold and the result of the + {type Stream} computation: + + @signatures{foldWithResult, foldDelayedWithResult} + + Fold stream with a function, starting with an initial value, and emit + intermediate results: + + @signatures{scan!, scan} + + Apply an effectful function to each value in a stream: + + @signature{Stream.foreach} + + Pipe values from a stream into a computation that uses the {type Ask} + ability to consume them: + + @signatures{pipe!, pipe} + + # Transforming streams + + Take a prefix of a stream: + + @signatures{take!, Stream.take} + + Drop a prefix of a stream: + + @signatures{Stream.drop!, Stream.drop} + + Take a prefix of a stream while a predicate holds: + + @signatures{takeWhile!, Stream.takeWhile} + + Map a function over a stream: + + @signatures{Stream.map!, Stream.map} + + Map a list-valued function over a stream, emitting the values in the list + for each input value: + + @signatures{concatMap!, concatMap} + + Remove values from a stream that do not satisfy a predicate: + + @signatures{filter!, Stream.filter} + + Map a stream-valued function over a stream, emitting the values in the + stream for each input value: + + @signatures{flatMap!, Stream.flatMap} + + Wrap all the values in a stream in a {Some} and emit a {None} at the end: + + @signatures{terminated!, terminated} + + # Combining streams + + Concatenate two streams: + + @signature{++} + + Interleave two streams by alternating between values from each stream: + + @signatures{interleave!, Stream.interleave} + + Run two streams in parallel and emit values from both streams: + + @signatures{zip!, Stream.zip} + + Run two streams in parallel and apply a function to the values from each + stream, emitting the results: + + @signatures{zipWith!, Stream.zipWith} + }} + +data.Stream.drain : '{g, Stream a} r ->{g} r +data.Stream.drain s = at2 (foldWithResult (const const()) () s) + +data.Stream.drain.doc : Doc +data.Stream.drain.doc = + {{ + `` drain s `` consumes all elements from the {type Stream} `s` and discards + them. + }} + +data.Stream.drop : Nat -> '{g, Stream a} r -> '{g, Stream a} r +data.Stream.drop n s _ = Stream.drop! n s + +data.Stream.drop.doc : Doc +data.Stream.drop.doc = + {{ `Stream.drop n s` returns a delayed computation of `Stream.drop! n s`. }} + +test> data.Stream.drop.test = runs 100 do + ns = gen.listOf natInOrder () + lim = gen.natIn 0 (List.size ns) () + expected = (List.drop lim ns, "result") + stream = do + fromList! ns + "result" + actual = toListWithResult (Stream.drop lim stream) + expect (assertEquals actual expected) + +data.Stream.drop! : Nat -> '{g, Stream a} r ->{g, Stream a} r +data.Stream.drop! n s = + go _ = + abilities.repeat n do ask + forever do emit ask + pipe! s go + +data.Stream.drop!.doc : Doc +data.Stream.drop!.doc = + {{ + `Stream.drop! n s` emits all elements of a stream computation `s` after + omitting the first `n` elements emitted by `s`. + }} + +data.Stream.dropWhile : + (a ->{e} Boolean) -> '{f, Stream a} r -> '{e, f, Stream a} r +data.Stream.dropWhile p s = do match Stream.uncons s with + Left r -> r + Right (a, as) -> if p a then data.Stream.dropWhile p as () else s() + +data.Stream.dropWhile.doc : Doc +data.Stream.dropWhile.doc = + {{ + Drops elements from the stream while the given predicate is true. Returns the + rest of the stream. This is the lazy version of {dropWhile!} + + # Example + + ``` + Stream.toList + (Stream.dropWhile Nat.isEven (Stream.fromList [2, 4, 6, 7, 8])) + ``` + }} + +data.Stream.dropWhile! : + (a ->{e} Boolean) -> '{f, Stream a} r ->{e, f, Stream a} r +data.Stream.dropWhile! p s = match Stream.uncons s with + Left r -> r + Right (a, as) -> if p a then data.Stream.dropWhile! p as else s() + +data.Stream.dropWhile!.doc : Doc +data.Stream.dropWhile!.doc = + {{ + Drops elements from the stream while the given predicate is true. Returns the + rest of the stream. This is the strict version of {Stream.dropWhile}. + + # Example + + ``` + Stream.toList do dropWhile! Nat.isEven (Stream.fromList [2, 4, 6, 7, 8]) + ``` + }} + +data.Stream.emit.doc : Doc +data.Stream.emit.doc = + {{ + Puts a value onto the {type Stream}. The value will be received by the + nearest enclosing handler. + + # Example + + ``` + Stream.toList do + emit 1 + emit 2 + emit 3 + ``` + }} + +data.Stream.emitAndReturn : a -> '{Stream a} a +data.Stream.emitAndReturn a _ = emitAndReturn! a + +data.Stream.emitAndReturn.doc : Doc +data.Stream.emitAndReturn.doc = + {{ + `Stream.emitAndReturn a` returns a delayed computation of + `Stream.emitAndReturn! a`. + }} + +data.Stream.emitAndReturn! : a ->{Stream a} a +data.Stream.emitAndReturn! a = + emit a + a + +data.Stream.emitAndReturn!.doc : Doc +data.Stream.emitAndReturn!.doc = + {{ `Stream.emitAndReturn! a` emits `a` and evaluates to `a`. }} + +data.Stream.filter : (a ->{g} Boolean) -> '{g, Stream a} r -> '{g, Stream a} r +data.Stream.filter p s _ = filter! p s + +data.Stream.filter.doc : Doc +data.Stream.filter.doc = + {{ + `Stream.filter p s` returns a delayed computation of `Stream.filter! p s`. + }} + +test> data.Stream.filter.test = runs 10 do + n = natInOrder() + p = Nat.isEven + expected = if p n then [n] else [] + actual = Stream.toList (Stream.filter p do emit n) + expect (assertEquals actual expected) + +data.Stream.filter! : (a ->{g} Boolean) -> '{g, Stream a} r ->{g, Stream a} r +data.Stream.filter! pred = concatMap! (a -> (if pred a then [a] else [])) + +data.Stream.filter!.doc : Doc +data.Stream.filter!.doc = + {{ + `` filter! pred s `` emits the elements emitted by stream `s` for which the + predicate `pred` holds. + }} + +data.Stream.find : (a ->{g} Boolean) -> '{g, Stream a} r ->{g} Optional a +data.Stream.find pred = Stream.head << Stream.filter pred + +data.Stream.find.doc : Doc +data.Stream.find.doc = + use Nat == + use Stream find + {{ + `` find p x `` looks for an element that matches predicate `p` in the + {type Stream} `x`. Returns {type Optional} value. + + # Examples + + ``` + to 10 |> find ((==) 10) + ``` + + ``` + to 10 |> find ((==) 11) + ``` + + ## See also: + + * {Stream.contains} + }} + +test> data.Stream.find.test = test.verify do + use Nat == + use Stream find + s = to 10 + ensureEqual (find ((==) 10) s) (Some 10) + ensureEqual (find ((==) 11) s) None + +data.Stream.flatMap : + (a ->{e, Stream b} any) -> '{e, Stream a} r -> '{e, Stream b} r +data.Stream.flatMap f s _ = flatMap! f s + +data.Stream.flatMap.doc : Doc +data.Stream.flatMap.doc = + {{ + `` Stream.flatMap f s `` returns a delayed computation of ``flatMap! f s``. + }} + +test> data.Stream.flatMap.test = runs 10 do + use Nat + + ns = gen.listOf natInOrder () + expected = List.flatMap (n -> [n, n + 1]) ns + go n = + emit n + emit (n + 1) + actual = Stream.toList (Stream.flatMap go (Stream.fromList ns)) + expect (assertEquals actual expected) + +data.Stream.flatMap! : + (a ->{e, Stream b} any) -> '{e, Stream a} r ->{e, Stream b} r +data.Stream.flatMap! f s = pipe! s do forever do f ask + +data.Stream.flatMap!.doc : Doc +data.Stream.flatMap!.doc = + {{ + `` flatMap! f s `` applies `f` to each element emitted by stream `s` in turn + and concatenates the resulting streams. + }} + +data.Stream.fold : (b ->{g} a ->{g} b) -> b -> '{g, Stream a} r ->{g} b +data.Stream.fold f z s = at1 (handle s() with foldWithResult.handler f z) + +data.Stream.fold.doc : Doc +data.Stream.fold.doc = + {{ + `Stream.fold! op z s` folds over the elements emitted by stream `s`, starting + an accumulation at value `z` and using binary operation `op` to add each + element emitted by `s` into the accumulation. + }} + +test> data.Stream.fold.test = runs 10 do + use Nat + + ns = gen.listOf natInOrder () + expected = List.foldLeft (+) 0 ns + actual = Stream.fold (+) 0 (Stream.fromList ns) + expect (assertEquals actual expected) + +data.Stream.foldBalanced : + (a ->{g1} b) -> b -> (b -> b ->{g2} b) -> '{g3, Stream a} r ->{g1, g2, g3} b +data.Stream.foldBalanced f z op s0 = + use List :+ + use Nat + >= + done = cases + [] -> z + h +: t -> List.foldLeft (acc a -> op acc (at1 a)) (at1 h) t + push stack a n = match stack with + stack :+ (a0, m) | n >= m -> push stack (op a0 a) (n + m) + _ -> stack :+ (a, n) + go stack s = match Stream.uncons s with + Left _ -> done stack + Right (hd, s) -> go (push stack (f hd) 1) s + go [] s0 + +data.Stream.foldBalanced.doc : Doc +data.Stream.foldBalanced.doc = + use Text ++ + {{ + Reduce a {type Stream} using a balanced fold, useful for converting a stream + to some balanced structure, or when the combining operation isn't constant + time. + + ``` + pair t1 t2 = "(" ++ t1 ++ " " ++ t2 ++ ")" + Stream.fromList (List.range 0 8) |> Stream.foldBalanced Nat.toText "" pair + ``` + }} + +test> data.Stream.foldBalanced.test = test.verify do + use Nat * + / == + n = Each.range 1 32 + ns = Stream.range 1 (n + 1) + ensure (Stream.foldBalanced id 0 (+) ns == n * (n + 1) / 2) + +data.Stream.foldDelayed : + (b ->{g} a ->{g} b) -> b -> '{g, Stream a} r -> '{g} b +data.Stream.foldDelayed f z s _ = Stream.fold f z s + +data.Stream.foldDelayed.doc : Doc +data.Stream.foldDelayed.doc = + {{ + `Stream.fold f z s` returns a delayed computation of `Stream.fold! f z s`. + }} + +data.Stream.foldDelayedRight : + (a ->{g} b ->{g} b) -> b -> '{g, Stream a} r -> '{g} b +data.Stream.foldDelayedRight f z s = do Stream.foldRight f z s + +data.Stream.foldDelayedRight.doc : Doc +data.Stream.foldDelayedRight.doc = + use Nat + + {{ + `` foldDelayedRight f z s `` folds the {type Stream} `s` with the function + `f` and the initial value `z`, associating to the right. + + Returns an unevaluated computation that will return the result of the fold + when forced. + + # Example + + ``` + foldDelayedRight (+) 0 (Stream.fromList [1, 2, 3]) () + ``` + + # See also - {Stream.foldRight} for a version of this that forces the result. + - {foldDelayed} associates to the left. - {List.foldRight} folds a + {type List}. + + + }} + +data.Stream.foldDelayedWithResult : + (b ->{g} a ->{g} b) -> b -> '{g, Stream a} r -> '{g} (b, r) +data.Stream.foldDelayedWithResult f z s _ = foldWithResult f z s + +data.Stream.foldDelayedWithResult.doc : Doc +data.Stream.foldDelayedWithResult.doc = + {{ + `Stream.foldWithResult f z s` returns a delayed computation of + `Stream.foldWithResult! f z s`. + }} + +data.Stream.foldRight : (a ->{g} b ->{g} b) -> b -> '{g, Stream a} r ->{g} b +data.Stream.foldRight f z s = Stream.fold (g a b -> g (f a b)) id s z + +data.Stream.foldRight.doc : Doc +data.Stream.foldRight.doc = + use Nat + + {{ + `` Stream.foldRight f z s `` folds the {type Stream} `s` with the function + `f` and the initial value `z`, associating to the right. + + Returns the result of the fold, forcing the computation. + + # Example + + ``` + Stream.foldRight (+) 0 (Stream.fromList [1, 2, 3]) + ``` + + # See also - {foldDelayedRight} for a version of this that returns an + unevaluated computation. - {Stream.fold} associates to the left. - + {List.foldRight} folds a {type List}. + + + }} + +data.Stream.foldWithResult : + (b ->{g} a ->{g} b) -> b -> '{g, Stream a} r ->{g} (b, r) +data.Stream.foldWithResult f z s = handle s() with foldWithResult.handler f z + +data.Stream.foldWithResult.doc : Doc +data.Stream.foldWithResult.doc = + {{ + `Stream.foldWithResult! f z s` folds over the elements emitted by stream `s` + and returns a pair containing both the fold result and the value of the + stream. + + See also: {foldDelayed} + }} + +data.Stream.foldWithResult.handler : + (b ->{g} a ->{g} b) -> b -> Request (Stream a) r ->{g} (b, r) +data.Stream.foldWithResult.handler f z = cases + { emit a -> k } -> + handle k() with data.Stream.foldWithResult.handler f (f z a) + { v } -> (z, v) + +test> data.Stream.foldWithResult.test = runs 100 do + use Nat + + use Stream ++ + ns = gen.listOf natInOrder () + r = alpha() + expected = (List.foldLeft (+) 0 ns, r) + actual = foldWithResult (+) 0 (Stream.fromList ns ++ (do r)) + expect (assertEquals actual expected) + +data.Stream.foreach : (a ->{g} ()) -> '{g, Stream a} r ->{g} r +data.Stream.foreach f s = + h = cases + { emit a -> k } -> handle k (f a) with h + { r } -> r + handle s() with h + +data.Stream.foreach.doc : Doc +data.Stream.foreach.doc = + {{ + `` Stream.foreach f s `` performs `f` on each element of the {type Stream} + `s`, discarding the result. + }} + +data.Stream.from : Nat -> '{Stream Nat} a +data.Stream.from n _ = Stream.from! n + +data.Stream.from.doc : Doc +data.Stream.from.doc = + {{ `Stream.from n` returns a delayed computation of `Stream.from! n`. }} + +data.Stream.from! : Nat ->{Stream Nat} a +data.Stream.from! n = + use Nat + + emit n + data.Stream.from! (n + 1) + +data.Stream.from!.doc : Doc +data.Stream.from!.doc = + {{ + `Stream.from! n` emits all natural numbers starting from `n` and increasing + by ones. + }} + +data.Stream.fromList : [a] -> '{Stream a} () +data.Stream.fromList l _ = fromList! l + +data.Stream.fromList.doc : Doc +data.Stream.fromList.doc = + {{ `Stream.fromList` returns a delayed computation of `Stream.fromList!`. }} + +data.Stream.fromList! : [a] ->{Stream a} () +data.Stream.fromList! l = ignore (List.map emit l) + +data.Stream.fromList!.doc : Doc +data.Stream.fromList!.doc = + {{ `Stream.fromList! l` eagerly emits each element of `List` `l`. }} + +data.Stream.head : '{g, Stream a} r ->{g} Optional a +data.Stream.head stream = match Stream.uncons stream with + Left _ -> None + Right (a, _) -> Some a + +data.Stream.head.doc : Doc +data.Stream.head.doc = + {{ + Returns the first element of a stream, if it exists, or {None} if the stream + is empty. + + # Example + + ``` + Stream.head (Stream.fromList [1, 2, 3]) + ``` + }} + +data.Stream.indexed : '{g, Stream a} r -> '{g, Stream (a, Nat)} r +data.Stream.indexed s = do indexed! s + +data.Stream.indexed.doc : Doc +data.Stream.indexed.doc = + use Stream fromList indexed toList + {{ + Transforms a stream of elements in a stream of pairs of the form + `(element, index)` where `element` is the original element and `index` is the + zero-based index of the element in the stream. + + See {indexed!} for a variant in which the output is not delayed. + + # Examples + + ``` + fromList ["a", "b", "c"] |> indexed |> toList + ``` + + ``` + fromList [] |> indexed |> toList + ``` + }} + +data.Stream.indexed! : '{g, Stream a} r ->{g, Stream (a, Nat)} r +data.Stream.indexed! s = zipWith! Tuple.pair s Nat.all + +data.Stream.indexed!.doc : Doc +data.Stream.indexed!.doc = {{ A non-delayed variant of {Stream.indexed}. }} + +data.Stream.Int.all : '{Stream Int} a +data.Stream.Int.all _ = + emit +0 + go : Int ->{Stream Int} a + go n = + emit n + emit (Int.negate n) + go (Int.increment n) + go +1 + +data.Stream.Int.all.doc : Doc +data.Stream.Int.all.doc = + {{ + `Stream.Int.all` is the (delayed) stream of all integers: +0, +1, -1, +2, -2, + etc. + }} + +data.Stream.Int.from : Int -> '{Stream Int} a +data.Stream.Int.from n _ = Int.from! n + +data.Stream.Int.from.doc : Doc +data.Stream.Int.from.doc = + {{ + `Stream.Int.from n` returns a delayed computation of `Stream.Int.from! n`. + }} + +data.Stream.Int.from! : Int ->{Stream Int} a +data.Stream.Int.from! n = + emit n + data.Stream.Int.from! (Int.increment n) + +data.Stream.Int.from!.doc : Doc +data.Stream.Int.from!.doc = + {{ + `Stream.Int.from! n` eagerly emits all integers starting at `n` and + increasing by ones. + }} + +data.Stream.Int.negatives : '{Stream Int} a +data.Stream.Int.negatives = Stream.map Int.negate (Int.from +1) + +data.Stream.Int.negatives.doc : Doc +data.Stream.Int.negatives.doc = + {{ + `Stream.Int.negatives` is the (delayed) stream of all negative integers. + }} + +data.Stream.Int.positives : '{Stream Int} a +data.Stream.Int.positives = Int.from +1 + +data.Stream.Int.positives.doc : Doc +data.Stream.Int.positives.doc = + {{ + `Stream.Int.positives` is the (delayed) stream of all positive integers. + }} + +data.Stream.Int.range : Int -> Int -> '{Stream Int} () +data.Stream.Int.range a b _ = Int.range! a b + +data.Stream.Int.range.doc : Doc +data.Stream.Int.range.doc = + {{ + `Stream.Int.range n m` is the (delayed) stream of integers from `n` + (inclusive) until `m` (exclusive). + }} + +data.Stream.Int.range! : Int ->{Stream Int} Int ->{Stream Int} () +data.Stream.Int.range! a b = + if Universal.gteq a b then () + else + emit a + data.Stream.Int.range! (Int.increment a) b + +data.Stream.Int.range!.doc : Doc +data.Stream.Int.range!.doc = + {{ + `Stream.Int.range! n m` is the (eager) stream of integers from `n` + (inclusive) until `m` (exclusive). + }} + +data.Stream.interleave : + '{g, Stream a} r -> '{g, Stream a} r -> '{g, Stream a} r +data.Stream.interleave s1 s2 _ = interleave! s1 s2 + +data.Stream.interleave.doc : Doc +data.Stream.interleave.doc = + {{ + `Stream.interleave s1 s2` returns a delayed computation of + `Stream.interleave! s1 s2`. + }} + +data.Stream.interleave! : + '{g, Stream a} r -> '{g, Stream a} r ->{g, Stream a} r +data.Stream.interleave! s1 s2 = handle s1() with interleave!.handler s2 + +data.Stream.interleave!.doc : Doc +data.Stream.interleave!.doc = + {{ + `Stream.interleave! s1 s2` alternates between emitting elements of stream + `s1` and stream `s2`. The resulting stream terminates when the first of the + two streams terminates (and evaluates to the value of that stream). + }} + +data.Stream.interleave!.handler : + '{e} r -> Request (Stream a) r ->{e, Stream a} r +data.Stream.interleave!.handler k = cases + { emit a -> s } -> + emit a + handle k() with data.Stream.interleave!.handler s + { r } -> r + +data.Stream.intersperse : a -> '{g, Stream a} r -> '{g, Stream a} r +data.Stream.intersperse sep stream = do intersperse! sep stream + +data.Stream.intersperse.doc : Doc +data.Stream.intersperse.doc = + use Stream intersperse toList + {{ + Intersperse a separator between the elements of a stream. + + This function takes a separator and a stream and returns a new stream with + the separator interspersed between the elements of the input stream. + + # Example + + ``` + toList (intersperse "🙀" (Stream.fromList ["👻", "💀", "🎃"])) + ``` + + This also works for infinite streams: + + ``` + toList (Stream.take 7 (intersperse "🍎" (Stream.repeat do "🍊"))) + ``` + }} + +data.Stream.intersperse! : a -> '{g, Stream a} r ->{g, Stream a} r +data.Stream.intersperse! sep stream = + use Stream uncons + go s = match uncons s with + Left r -> r + Right (head, tail) -> + emit sep + emit head + go tail + match uncons stream with + Left r -> r + Right (head, tail) -> + emit head + go tail + +data.Stream.intersperse!.doc : Doc +data.Stream.intersperse!.doc = + {{ + A strict version of {Stream.intersperse} that evaluates the stream instead of + returning it. + }} + +data.Stream.iterate : (a ->{g} a) -> a -> '{g, Stream a} Void +data.Stream.iterate f init = do iterate! f init + +data.Stream.iterate.doc : Doc +data.Stream.iterate.doc = + {{ + `` Stream.iterate f x `` Returns a delayed computation of `` iterate! f x `` + }} + +test> data.Stream.iterate.tests.ex1 = + use Nat * + output = Stream.toList (Stream.take 5 <| Stream.iterate (n -> n * 2) 1) + expected = [1, 2, 4, 8, 16] + check (output === expected) + +data.Stream.iterate! : (a ->{g} a) -> a ->{g, Stream a} Void +data.Stream.iterate! f init = + loop x = + emit x + loop (f x) + loop init + +data.Stream.iterate!.doc : Doc +data.Stream.iterate!.doc = + {{ + `` iterate! f x `` returns a infinite Stream consisting of + + `x, f x, f (f x), f (f (f x))...` + + and so on. That is, f applied to x zero times, then once, then twice, etc. + }} + +data.Stream.map : (a ->{g} b) -> '{g, Stream a} r -> '{g, Stream b} r +data.Stream.map f s _ = Stream.map! f s + +data.Stream.map.doc : Doc +data.Stream.map.doc = + {{ `Stream.map f s` returns a delayed computation of `Stream.map! f s`. }} + +test> data.Stream.map.test = runs 10 do + use Nat + + n = natInOrder() + f = (+) 1 + expected = [f n] + actual = Stream.toList (Stream.map f do emit n) + expect (assertEquals actual expected) + +data.Stream.map! : (a ->{g} b) -> '{g, Stream a} r ->{g, Stream b} r +data.Stream.map! f s = pipe! s do forever do f ask |> emitAndReturn! + +data.Stream.map!.doc : Doc +data.Stream.map!.doc = + {{ + `Stream.map! f s` applies a function `f` to each element emitted by a stream + `s` and emits each result. + }} + +data.Stream.Nat.all : '{Stream Nat} a +data.Stream.Nat.all _ = Stream.from! 0 + +data.Stream.Nat.all.doc : Doc +data.Stream.Nat.all.doc = + {{ `Stream.Nat.all` is the (delayed) stream of all natural numbers. }} + +data.Stream.pipe : + '{g, Stream a} r -> '{g, Ask a, Stream b} r -> '{g, Stream b} r +data.Stream.pipe i t _ = pipe! i t + +data.Stream.pipe.doc : Doc +data.Stream.pipe.doc = + {{ `Stream.pipe i t` returns a delayed computation of `Stream.pipe! i t`. }} + +data.Stream.pipe! : + '{g, Stream a} r -> '{g, Ask a, Stream b} r ->{g, Stream b} r +data.Stream.pipe! i t = handle t() with pipe!.handler i + +data.Stream.pipe!.doc : Doc +data.Stream.pipe!.doc = + {{ + `Stream.pipe! i t` pipes an input stream `i` through a transducer `t`. The + transducer may consume any number of elements emitted by the input stream + (using `Ask.ask`) and may emit any number of values (using `Stream.emit`). + }} + +data.Stream.pipe!.handler : + '{g, Stream a} r -> Request {Ask a, Stream b} r ->{g, Stream b} r +data.Stream.pipe!.handler i = cases + { r } -> r + { ask -> resumeC } -> + h : Request {Stream a} r ->{Stream b} r + h = cases + { r } -> r + { emit a -> i' } -> handle resumeC a with data.Stream.pipe!.handler i' + handle i() with h + { emit b -> k } -> + emit b + handle k() with data.Stream.pipe!.handler i + +data.Stream.range : Nat -> Nat -> '{Stream Nat} () +data.Stream.range n m _ = Stream.range! n m + +data.Stream.range.doc : Doc +data.Stream.range.doc = + {{ + `Stream.range n m` returns a delayed computation of `Stream.range! n m`. + }} + +test> data.Stream.range.test.emptyRange = runs 10 do + n = natInOrder() + actual = Stream.toList (Stream.range n n) + expect (assertEquals actual []) + +test> data.Stream.range.test.emptyRange2 = runs 10 do + n = natInOrder() + m = natInOrder() + n' = Universal.max n m + m' = Universal.min n m + actual = Stream.toList (Stream.range n' m') + expect (assertEquals actual []) + +test> data.Stream.range.test.listLike = runs 100 do + n = natInOrder() + m = natInOrder() + expected = List.range n m + actual = Stream.toList (Stream.range n m) + expect (assertEquals actual expected) + +test> data.Stream.range.test.singletonRange = runs 10 do + use Nat + + n = natInOrder() + actual = Stream.toList (Stream.range n (n + 1)) + expect (assertEquals actual [n]) + +data.Stream.range! : Nat -> Nat ->{Stream Nat} () +data.Stream.range! n m = + use Nat + + if Universal.gteq n m then () + else + emit n + data.Stream.range! (n + 1) m + +data.Stream.range!.doc : Doc +data.Stream.range!.doc = + {{ + `Stream.range! n m` emits the natural numbers from `n` (inclusive) until `m` + (exclusive). + }} + +data.Stream.rangeClosed : Nat -> Nat -> '{Stream Nat} () +data.Stream.rangeClosed n m _ = rangeClosed! n m + +data.Stream.rangeClosed.doc : Doc +data.Stream.rangeClosed.doc = + {{ + `Stream.rangeClosed n m` returns a delayed computation of + `Stream.rangeClosed! n m`. + }} + +data.Stream.rangeClosed! : Nat -> Nat ->{Stream Nat} () +data.Stream.rangeClosed! n m = + use Nat + + Stream.range! n (m + 1) + +data.Stream.rangeClosed!.doc : Doc +data.Stream.rangeClosed!.doc = + {{ + `Stream.rangeClosed! n m` emits the natural numbers from `n` to `m` + (inclusive). + }} + +data.Stream.repeat : '{g} a -> '{g, Stream a} Void +data.Stream.repeat a = + loop = do + emit a() + loop() + loop + +data.Stream.repeat.doc : Doc +data.Stream.repeat.doc = + use Stream repeat + {{ + `` repeat x `` builds a {type Stream} that emits `x` forever. + + # Example + + ``` + lcg 0 (toDelayedList (Stream.take 10 (repeat do Random.natIn 0 100))) + ``` + }} + +data.Stream.scan : + (b ->{g} a ->{g} b) -> b -> '{g, Stream a} r -> '{g, Stream b} r +data.Stream.scan f z s _ = scan! f z s + +data.Stream.scan.doc : Doc +data.Stream.scan.doc = + {{ + `Stream.scan f z s` returns a delayed computation of `Stream.scan! f z s`. + }} + +data.Stream.scan! : + (b ->{g} a ->{g} b) -> b -> '{g, Stream a} r ->{g, Stream b} r +data.Stream.scan! f z s = + f' : b ->{g} a ->{g, Stream b} b + f' b a = emitAndReturn! (f b a) + at2 (foldWithResult f' z s) + +data.Stream.scan!.doc : Doc +data.Stream.scan!.doc = + {{ + `Stream.scan! op z s` folds over the elements emitted by stream `s`, starting + an accumulation at value `z` and using binary operation `op` to add each + element emitted by `s` into the accumulation. `Stream.scan!` emits the + cumulative value as each element of `s` is added to the accumulation. + + See also: {scan!} + }} + +data.Stream.somes : '{g, Stream (Optional a)} r -> '{g, Stream a} r +data.Stream.somes s = do somes! s + +data.Stream.somes.doc : Doc +data.Stream.somes.doc = + use Stream fromList somes toList + {{ + Returns a {type Stream} of the {Some} values from a {type Stream} of + {type Optional} values, skipping the {None} values. + + # Examples + + ``` + toList (somes (fromList [Some 1, None, Some 2, None, Some 3])) + ``` + + ``` + toList (somes (fromList [None, Some 1, Some 2, None, Some 3])) + ``` + + ``` + toList (somes (fromList [None, None, None])) + ``` + + # See also + + * {somes!} - a variant that produces the stream immediately, without + creating an unevaluated stream. + }} + +data.Stream.somes! : '{g, Stream (Optional a)} r ->{g, Stream a} r +data.Stream.somes! s = + h = cases + { emit (Some a) -> k } -> + emit a + handle k() with h + { emit None -> k } -> handle k() with h + { r } -> r + handle s() with h + +data.Stream.somes!.doc : Doc +data.Stream.somes!.doc = + use Stream fromList toList + {{ + Produces a {type Stream} of the {Some} values from a {type Stream} of + {type Optional} values, skipping the {None} values. + + # Examples + + ``` + toList do somes! (fromList [Some 1, None, Some 2, None, Some 3]) + ``` + + ``` + toList do somes! (fromList [None, Some 1, Some 2, None, Some 3]) + ``` + + ``` + toList do somes! (fromList [None, None, None]) + ``` + + # See also + + * {Stream.somes} - a variant that returns an unevaluated stream. + }} + +data.Stream.span : + (a ->{g} Boolean) -> '{g, Stream a} r ->{g} ([a], '{g, Stream a} r) +data.Stream.span p thunk = + use List :+ + go : [a] -> Request {Stream a} r ->{g} ([a], '{g, Stream a} r) + go acc = cases + { r } -> (acc, do r) + { emit a -> k } -> + if p a then handle k() with go (acc :+ a) + else + (acc, do + emit a + k()) + handle thunk() with go [] + +data.Stream.span.doc : Doc +data.Stream.span.doc = + {{ + Returns the longest prefix of the input {type Stream} that satisfies the + predicate, together with the rest of the {type Stream}. + + Performs whatever effects are required to produce the prefix tbut does not + perform any effects on the rest of the {type Stream}. + + # Example + + ``` + Tuple.second Stream.toList + <| Stream.span Nat.isOdd (Stream.fromList [1, 1, 2, 3, 1, 1]) + ``` + }} + +data.Stream.splitAt : Nat -> '{g, Stream a} r ->{g} ([a], '{g, Stream a} r) +data.Stream.splitAt n s = + use List :+ + use Nat - == + h : [a] -> Nat -> Request (Stream a) r ->{g} ([a], '{g, Stream a} r) + h acc n = cases + { emit a -> k } -> go (acc :+ a) (n - 1) k + { r } -> (acc, do r) + go : [a] -> Nat -> '{g, Stream a} r ->{g} ([a], '{g, Stream a} r) + go acc n s = if n == 0 then (acc, s) else handle s() with h acc n + go [] n s + +data.Stream.splitAt.doc : Doc +data.Stream.splitAt.doc = + {{ + Returns first `n` elements of a {type Stream}, as a {type List}, together + with the rest of the stream. + + If the {type Stream} has fewer than `n` elements, all the elements are + returned as the first element of the pair, and the second element of the pair + is the empty {type Stream}. + + Performs whatever effects are required to produce the first `n` elements of + the {type Stream}, but does not perform any effects on the rest of the + {type Stream}. + + # Example + + ``` + Tuple.second Stream.toList + <| Stream.splitAt 3 (Stream.fromList [1, 2, 3, 4, 5]) + ``` + }} + +data.Stream.tails : '{g, Stream a} r -> '{g, Stream ('{Stream a, g} r)} r +data.Stream.tails s = do tails! s + +data.Stream.tails.doc : Doc +data.Stream.tails.doc = + use Stream toList + {{ + Returns a {type Stream} of all the suffixes of the input {type Stream}. + + A lazy version of {tails!}. + + # Example + + ``` + toList (Stream.map toList (Stream.tails (Stream.fromList [1, 2, 3]))) + ``` + }} + +data.Stream.tails! : '{g, Stream a} r ->{g, Stream ('{Stream a, g} r)} r +data.Stream.tails! s = match Stream.uncons s with + Left r -> r + Right (x, s) -> (Stream.+:) (x Stream.+: s) (do data.Stream.tails! s) () + +data.Stream.tails!.doc : Doc +data.Stream.tails!.doc = + use Stream toList + {{ + Emits all the suffixes of the input {type Stream} onto a {type Stream}. + + # Example + + ``` + toList (Stream.map toList do tails! (Stream.fromList [1, 2, 3])) + ``` + }} + +data.Stream.take : Nat -> '{g, Stream a} t -> '{g, Stream a} Optional t +data.Stream.take n s = do take! n s + +data.Stream.take.doc : Doc +data.Stream.take.doc = + {{ `Stream.take n s` returns a delayed computation of `Stream.take! n s`. }} + +test> data.Stream.take.tests.completion = + runs 100 do + use Nat + + use Stream ++ + ns = gen.listOf natInOrder () + lim = List.size ns + expected = (List.take lim ns, Some "end") + actual = + toListWithResult + (Stream.take (lim + 1) (Stream.fromList ns ++ (do "end"))) + expect (assertEquals actual expected) + +test> data.Stream.take.tests.earlyTermination = runs 100 do + ns = gen.listOf natInOrder () + lim = gen.natIn 0 (List.size ns) () + expected = (List.take lim ns, None) + actual = toListWithResult (Stream.take lim (Stream.fromList ns)) + expect (assertEquals actual expected) + +test> data.Stream.take.tests.pullMinimal = + runs 100 do + Scope.run do + use Nat == + n = gen.natIn 0 1000 () + ref = Scope.ref 0 + (Stream.repeat do Ref.modify ref Nat.increment) |> Stream.take n |> drain + |> ignore + expect (Ref.read ref == n) + +data.Stream.take! : Nat -> '{g, Stream a} r ->{g, Stream a} Optional r +data.Stream.take! n s = + use Nat - > + h n = cases + { emit a -> k } -> + emit a + if n > 0 then handle k() with h (n - 1) else None + { r } -> Some r + if n > 0 then handle s() with h (n - 1) else None + +data.Stream.take!.doc : Doc +data.Stream.take!.doc = + use Stream fromList take toList + {{ + `Stream.take! n s` emits the first `n` elements of a stream `s`. + + If the stream `s` has fewer than `n` elements, the result has exactly the + elements of `s`, and the result of the computation will be {Some} containing + the result of the computation `s`. Otherwise, the result is a stream of + length `n` with the first `n` elements of `s` and a result of {None}. + + Note that the implementation tries to do as little work as possible in order + to not execute effects unnecessarily. For this reason, once `n` elements have + been emitted, the stream `s` is not consumed further and the result of the + computation is {None}. So even if the stream `s` has exactly 3 elements, + `Stream.take! 3 s` will emit all the elements and return {None}. + + # Examples + + ``` + s = fromList [1, 2, 3, 4, 5] + toList (take 3 s) + ``` + + ``` + s = take 3 (fromList [1, 2, 3]) + (toList s, drain s) + ``` + + ``` + s = take 4 (fromList [1, 2, 3]) + (toList s, drain s) + ``` + }} + +data.Stream.takeWhile : + (a ->{g} Boolean) -> '{g, Stream a} r -> '{g, Stream a} Optional r +data.Stream.takeWhile p s = do takeWhile! p s + +data.Stream.takeWhile.doc : Doc +data.Stream.takeWhile.doc = + use Nat <= + use Stream takeWhile + {{ + `` takeWhile p s `` is a delayed computation of ``takeWhile! p s``. + + # Example + + ``` + Stream.toList (takeWhile (x -> x <= 3) (Stream.from 0)) + ``` + }} + +test> data.Stream.takeWhile.tests.worksLikeList = + test.verify do + use Nat < + Each.repeat 100 + vs = Random.listOf Random.nat do Random.natIn 0 100 + s = Stream.fromList vs + s' = Stream.takeWhile (x -> x < 11529215046068469760) s + ensure + (Stream.toList s' === List.takeWhile (x -> x < 11529215046068469760) vs) + +data.Stream.takeWhile! : + (a ->{g} Boolean) -> '{g, Stream a} r ->{g, Stream a} Optional r +data.Stream.takeWhile! p s = + h = cases + { emit a -> k } -> + if p a then + emit a + handle k() with h + else None + { r } -> Some r + handle s() with h + +data.Stream.takeWhile!.doc : Doc +data.Stream.takeWhile!.doc = + use Nat <= + {{ + `` takeWhile! p s `` emits elements from the {type Stream} `s` as long as `p` + returns `` true `` on those elements. That is, it emits elements until (but + not including) the first element `e` for which `p e` is false. + + # Example + + ``` + Stream.toList do takeWhile! (x -> x <= 3) (Stream.from 0) + ``` + }} + +data.Stream.tap : (x ->{g1} ()) -> '{g2, Stream x} r -> '{g1, g2, Stream x} r +data.Stream.tap f s = do tap! f s + +data.Stream.tap.doc : Doc +data.Stream.tap.doc = + {{ `Stream.tap f s` returns a delayed computation of `Stream.tap! f s`. }} + +data.Stream.tap! : (x ->{g1} ()) -> '{g2, Stream x} r ->{g1, g2, Stream x} r +data.Stream.tap! f s = + handle s() + with cases + { emit x -> k } -> + f x + emit x + data.Stream.tap! f k + { r } -> r + +data.Stream.tap!.doc : Doc +data.Stream.tap!.doc = + {{ + `Stream.tap f s` returns a new stream that runs the function `f` for effects + on each element of the stream `s` before emitting it. + }} + +data.Stream.terminated : '{g, Stream a} r -> '{g, Stream (Optional a)} r +data.Stream.terminated s _ = terminated! s + +data.Stream.terminated.doc : Doc +data.Stream.terminated.doc = + {{ + `Stream.terminated s` returns a delayed computation of `Stream.terminated! + s`. + }} + +test> data.Stream.terminated.test = runs 100 do + use List :+ + ns = gen.listOf natInOrder () + expected = List.map Some ns :+ None + s : '{Stream Nat} () + s = Stream.fromList ns + s2 : '{Stream (Optional Nat)} () + s2 = terminated s + actual = Stream.toList s2 + expect (assertEquals actual expected) + +data.Stream.terminated! : '{g, Stream a} r ->{g, Stream (Optional a)} r +data.Stream.terminated! s = + r = Stream.map! Some s + emit None + r + +data.Stream.terminated!.doc : Doc +data.Stream.terminated!.doc = + {{ + `Stream.terminated! s` emits all elements of a stream `s`, wrapped in a + `Some`, and then emits a final `None` to explicitly signal termination of the + stream. + }} + +test> data.Stream.tests.ex1 = + check ((Stream.toList do Stream.range! 0 5) === [0, 1, 2, 3, 4]) + +data.Stream.to : Nat -> '{Stream Nat} () +data.Stream.to n _ = to! n + +data.Stream.to.doc : Doc +data.Stream.to.doc = + {{ `Stream.to n` returns a delayed computation of `Stream.to! n`. }} + +data.Stream.to! : Nat ->{Stream Nat} () +data.Stream.to! n = rangeClosed! 0 n + +data.Stream.to!.doc : Doc +data.Stream.to!.doc = + {{ `Stream.to! n` emits the natural numbers from 0 to `n` (inclusive). }} + +data.Stream.toDelayedList : '{g, Stream a} r -> '{g} [a] +data.Stream.toDelayedList s _ = Stream.toList s + +data.Stream.toDelayedList.doc : Doc +data.Stream.toDelayedList.doc = + {{ `Stream.toList s` returns a delayed computation of `Stream.toList! s`. }} + +data.Stream.toDelayedList.handler : Request {Stream a} () -> [a] +data.Stream.toDelayedList.handler = + use List ++ + h : [a] -> Request {Stream a} () -> [a] + h acc = cases + { emit e -> resume } -> handle resume() with h (acc ++ [e]) + { u } -> acc + h [] + +test> data.Stream.toDelayedList.test.bidirectional = runs 20 do + l = gen.listOf natInOrder () + expect (assertEquals (Stream.toList (Stream.fromList l)) l) + +data.Stream.toDelayedListWithResult : '{g, Stream a} r -> '{g} ([a], r) +data.Stream.toDelayedListWithResult s _ = toListWithResult s + +data.Stream.toDelayedListWithResult.doc : Doc +data.Stream.toDelayedListWithResult.doc = + {{ + `Stream.toListWithResult s` returns a delayed computation of + `Stream.toListWithResult! s`. + }} + +data.Stream.toList : '{g, Stream a} r ->{g} [a] +data.Stream.toList s = at1 (toListWithResult s) + +data.Stream.toList.doc : Doc +data.Stream.toList.doc = + {{ + `Stream.toList! s` collects all values emitted by a stream `s` in a `List`. + }} + +data.Stream.toListWithResult : '{g, Stream a} r ->{g} ([a], r) +data.Stream.toListWithResult = + use List :+ + foldWithResult (:+) [] + +data.Stream.toListWithResult.doc : Doc +data.Stream.toListWithResult.doc = + {{ + `Stream.toListWithResult! s` collects all values emitted by a stream `s` in a + `List`, returning a pair containing both the list of emitted values and the + value of `s` itself. + }} + +data.Stream.trace : Text -> '{g, Stream a} r ->{g} r +data.Stream.trace msg s = + h = cases + { emit a -> k } -> + Debug.trace msg a + handle k() with h + { r } -> r + handle s() with h + +data.Stream.trace.doc : Doc +data.Stream.trace.doc = + {{ + `` Stream.trace msg s `` prints out the {type Text} `msg` on every element + emitted by the {type Stream} `s`, and also prints out the element. + }} + +data.Stream.uncons : '{g, Stream a} r ->{g} Either r (a, '{g, Stream a} r) +data.Stream.uncons s = + handle s() + with cases + { r } -> Left r + { emit hd -> tl } -> Right (hd, (tl : '{g, Stream a} r)) + +data.Stream.uncons.doc : Doc +data.Stream.uncons.doc = + use Stream fromList uncons + {{ + `` uncons s `` returns the first element of a {type Stream} `s` and the + remaining stream after that element. Returns {Left} if the {type Stream} is + empty. + + ``` + fromList [1, 2, 3] |> uncons |> Either.mapRight at1 + ``` + + ``` + fromList [] |> uncons + ``` + }} + +test> data.Stream.uncons.tests = test.verify do + use List :+ + n = Each.range 0 20 + s = Stream.range 0 n + l = Stream.toList s + go acc s = match Stream.uncons s with + Left _ -> acc + Right (hd, tl) -> go (acc :+ hd) tl + ensure (go [] s === l) + +data.Stream.unfold : s -> (s ->{g} Optional (a, s)) -> '{g, Stream a} () +data.Stream.unfold s f = do + loop = cases + Some (a, s') -> + do + emit a + loop (f s') () + None -> do () + loop (f s) () + +data.Stream.unfold.doc : Doc +data.Stream.unfold.doc = + use Nat + < + use Stream unfold + {{ + `` unfold s f `` builds a {type Stream} from a seed value `s` and a function + `f` that takes the current seed value and returns the next value in the + stream, if any. + + # Example + + ``` + Stream.toList (unfold 0 (i -> (if i < 10 then Some (i, i + 1) else None))) + ``` + }} + +data.Stream.until : Nat -> '{Stream Nat} () +data.Stream.until n _ = until! n + +data.Stream.until.doc : Doc +data.Stream.until.doc = + {{ `Stream.until n` returns a delayed computation of `Stream.until n`. }} + +data.Stream.until! : Nat ->{Stream Nat} () +data.Stream.until! n = Stream.range! 0 n + +data.Stream.until!.doc : Doc +data.Stream.until!.doc = + {{ + `Stream.until! n` emits the natural numbers from 0 (inclusive) until `n` + (exclusive). + }} + +data.Stream.window : Nat -> '{g, Stream a} r -> '{g, Stream [a]} r +data.Stream.window windowSize thunk = do window! windowSize thunk + +data.Stream.window.doc : Doc +data.Stream.window.doc = {{ The delayed version of {window!}. }} + +data.Stream.window! : Nat -> '{g, Stream a} r ->{g, Stream [a]} r +data.Stream.window! windowSize thunk = + use List :+ + use Nat <= == + when (windowSize == 0) do forever do emit [] + go : [a] -> Nat -> Request {Stream a} r ->{Stream [a]} r + go acc need = cases + { r } -> r + { emit a -> k } -> + if need <= 1 then + newAcc = acc :+ a + emit newAcc + handle k() with go (List.drop 1 newAcc) 0 + else handle k() with go (acc :+ a) (Nat.decrement need) + handle thunk() with go [] windowSize + +data.Stream.window!.doc : Doc +data.Stream.window!.doc = + use Stream fromList toList + {{ + Returns a {type Stream} of {type List}s, each containing `n` elements of the + input {type Stream} in a sliding window. + + The first element is the first `n` elements of the input {type Stream}, the + second element has the `n` elements after the first, and so on. + + If the input {type Stream} has fewer than `n` elements, then the output + {type Stream} is empty. + + If `n` is zero, then the output {type Stream} is an infinite {type Stream} of + empty {type List}s. + + # Examples + + ``` + toList <| window 3 (fromList [1, 2, 3, 4, 5]) + ``` + + ``` + toList <| window 3 (fromList [1, 2, 3]) + ``` + + ``` + toList <| window 3 (fromList [1, 2]) + ``` + }} + +data.Stream.zip : '{g, Stream a} r -> '{g, Stream b} r -> '{g, Stream (a, b)} r +data.Stream.zip sa sb _ = zip! sa sb + +data.Stream.zip.doc : Doc +data.Stream.zip.doc = + {{ + `Stream.zip sa sb` returns a delayed computation of `Stream.zip! sa sb`. + }} + +data.Stream.zip! : '{g, Stream a} r -> '{g, Stream b} r ->{g, Stream (a, b)} r +data.Stream.zip! sa sb = zipWith! Tuple.pair sa sb + +data.Stream.zip!.doc : Doc +data.Stream.zip!.doc = + {{ + `Stream.zip! sa sb` emits pairs of elements emitted by stream `sa` and stream + `sb`, respectively, in the order emitted. The computation terminates when + either `sa` or `sb` terminates, returning the value of the first stream to + terminate (or the value of `sa` if both streams terminate after emitting the + same number of elements). + }} + +data.Stream.zipWith : + (a ->{g} b ->{g} c) + -> '{g, Stream a} r + -> '{g, Stream b} r + -> '{g, Stream c} r +data.Stream.zipWith f sa sb _ = zipWith! f sa sb + +data.Stream.zipWith.doc : Doc +data.Stream.zipWith.doc = + {{ + `Stream.zipWith f sa sb` returns a delayed computation of `Stream.zipWith! f + sa sb` + }} + +data.Stream.zipWith! : + (a ->{g} b ->{g} c) -> '{g, Stream a} r -> '{g, Stream b} r ->{g, Stream c} r +data.Stream.zipWith! f sa sb = + readB : a -> '{g, Stream a} r -> Request (Stream b) r ->{g, Stream c} r + readB a sa = cases + { emit b -> sb } -> + emit (f a b) + handle sa() with readA sb + { r } -> r + readA : '{g, Stream b} r -> Request (Stream a) r ->{g, Stream c} r + readA sb = cases + { emit a -> sa } -> handle sb() with readB a sa + { r } -> r + handle sa() with readA sb + +data.Stream.zipWith!.doc : Doc +data.Stream.zipWith!.doc = + {{ + `Stream.zipWith! f sa sb` "zips" two streams `sa` and `sb` by combining + elements pairwise with a binary operation `f`, in the order emitted. The + computation terminates when either `sa` or `sb` terminates, returning the + value of the first stream to terminate (or the value of `sa` if both streams + terminate after emitting the same number of elements). + }} + +data.Trie.doc : Doc +data.Trie.doc = + {{ + A {type Trie} is a [prefix tree](https://en.wikipedia.org/wiki/Trie) that is + essentially a map from sequences of keys to values. + + The {type Trie} type is parameterized by the key type and the value type. + + # Constructing a trie + + The empty trie: + + @signature{Trie.empty} + + A trie with a single value at a given sequence of keys: + + @signature{Trie.singleton} + + A trie can also be constructed from an optional value at the root and a + list of child tries: + + @signature{Trie} + + # Querying a trie + + Get the value at a given sequence of keys: + + @signature{Trie.lookup} + + Get the child tries of a trie: + + @signature{Trie.tail} + + Get the optional value at the root of a trie: + + @signature{Trie.head} + + Get a {type List} of all the values in a trie: + + @signature{Trie.values} + + # Adding elements + + Add a value at a given sequence of keys: + + @signature{Trie.insert} + + Combine two tries by adding all the values from the second trie to the + first trie. If a value is present in both tries, the value from the first + trie is used: + + @signature{Trie.union} + + Union two tries, but if a value is present in both tries, use the given + function to combine the values: + + @signature{Trie.unionWith} + + # Transforming a trie + + Map a function over all the values in a trie: + + @signature{Trie.map} + + Map a function over the keys: + + @signature{Trie.mapKeys} + }} + +data.Trie.empty : Trie k v +data.Trie.empty = Trie None Map.empty + +data.Trie.empty.doc : Doc +data.Trie.empty.doc = {{ The empty {type Trie}. }} + +data.Trie.fromList : [([k], v)] -> Trie k v +data.Trie.fromList kvs = + go : [([k], v)] -> Trie k v -> Trie k v + go = cases + [], t -> t + (ks, v) +: kvs, t -> go kvs (Trie.insert ks v t) + go kvs Trie.empty + +data.Trie.gen : '{Gen} k -> '{Gen} v -> '{Gen} Trie k v +data.Trie.gen k v = do + h = gen.optional v () + g = data.Trie.gen k v + t = tests.mapOf k g () + Trie h t + +data.Trie.gen.doc : Doc +data.Trie.gen.doc = + {{ + A generator of {type Trie} values, given a generator of keys and a generator + of values. + + # Example + + ``` + deprecated.sample 10 (Trie.gen gen.int Char.ascii) + ``` + }} + +data.Trie.head : Trie k v -> Optional v +data.Trie.head = cases Trie head _ -> head + +data.Trie.head.doc : Doc +data.Trie.head.doc = + use Trie fromList head + {{ + Gets the element at the root of a {type Trie}. Returns {None} if the + {type Trie} has no element with an empty key. + + # Examples + + ``` + head (fromList [([], 1), (["a"], 2)]) + ``` + + ``` + head (fromList [(["a"], 1), (["b"], 2)]) + ``` + }} + +data.Trie.head.modify : + (Optional v ->{𝕖} Optional v) -> Trie k v ->{𝕖} Trie k v +data.Trie.head.modify f = cases Trie head tail -> Trie (f head) tail + +data.Trie.head.modify.doc : Doc +data.Trie.head.modify.doc = + use Trie singleton + use head modify + {{ + Modifies the {Trie.head} of the given {type Trie} using the given function. + + # Example + + ``` + modify (Optional.map Nat.increment) (singleton [] 0) + ``` + + ``` + modify (const None) (singleton [] 1) + ``` + }} + +data.Trie.head.set : Optional v -> Trie k v -> Trie k v +data.Trie.head.set head1 = cases Trie _ tail -> Trie head1 tail + +data.Trie.head.set.doc : Doc +data.Trie.head.set.doc = + use head set + use text fromList toList + {{ + Sets or deletes the value at the {Trie.head} of a {type Trie}. If the + provided value is {None}, the value at the head is deleted. Otherwise, the + value at the head is set to the provided value. + + # Examples + + ``` + toList (set (Some 1) (fromList [("ab", 2)])) + ``` + + ``` + toList (set None (fromList [("", 1), ("ab", 2)])) + ``` + }} + +data.Trie.insert : [k] -> v -> Trie k v -> Trie k v +data.Trie.insert path v t = Trie.unionWith const (Trie.singleton path v) t + +data.Trie.insert.doc : Doc +data.Trie.insert.doc = + use Trie insert + {{ + Inserts a value into a {type Trie} at a given key sequence. + + # Example + + ``` + text.toList + (insert + (toCharList "foo") + 1 + (insert (toCharList "bar") 2 (insert (toCharList "baz") 3 Trie.empty))) + ``` + }} + +data.Trie.lookup : [k] -> Trie k v -> Optional v +data.Trie.lookup path t = match path with + [] -> Trie.head t + p +: ps -> Optional.flatMap (data.Trie.lookup ps) (Map.get p (Trie.tail t)) + +data.Trie.lookup.doc : Doc +data.Trie.lookup.doc = + {{ + Looks up a value in a {type Trie} at a given key sequence. + + # Example + + ``` + Trie.lookup + (toCharList "foo") (text.fromList [("foo", 1), ("bar", 2), ("baz", 3)]) + ``` + }} + +data.Trie.map : (v1 ->{e} v2) ->{e} Trie k v1 ->{e} Trie k v2 +data.Trie.map f t = + Trie (Optional.map f (Trie.head t)) (Map.map (data.Trie.map f) (Trie.tail t)) + +data.Trie.map.doc : Doc +data.Trie.map.doc = {{ Apply a function to each value in a {type Trie}. }} + +data.Trie.mapKeys : (k1 ->{e} k2) ->{e} Trie k1 v ->{e} Trie k2 v +data.Trie.mapKeys f t = + Trie + (Trie.head t) (Map.mapKeys f (Map.map (data.Trie.mapKeys f) (Trie.tail t))) + +data.Trie.mapKeys.doc : Doc +data.Trie.mapKeys.doc = + {{ + Transforms the keys of a {type Trie} using the given function. + + # Example + + ``` + Trie.toList + (Trie.mapKeys Char.toUppercase (text.fromList [("bar", 1), ("baz", 2)])) + ``` + }} + +data.Trie.singleton : [k] -> v -> Trie k v +data.Trie.singleton path v = match path with + [] -> Trie (Some v) Map.empty + k +: ks -> Trie None (Map.fromList [(k, data.Trie.singleton ks v)]) + +data.Trie.singleton.doc : Doc +data.Trie.singleton.doc = + {{ + Creates a {type Trie} with a single element. + + # Example + + ``` + Trie.toList (Trie.singleton (toCharList "foo") 1) + ``` + }} + +data.Trie.tail : Trie k v -> Map k (Trie k v) +data.Trie.tail = cases Trie _ tail -> tail + +data.Trie.tail.doc : Doc +data.Trie.tail.doc = + use Trie fromList tail + {{ + Gets a {type Map} that maps the first element of each key in a {type Trie} to + the whole {type Trie} under that key element. Returns {None} if the + {type Trie} is empty or only has an empty key. + + # Examples + + ``` + Map.toList (Map.map Trie.values (tail (fromList [([], 1), (["a"], 2)]))) + ``` + + ``` + Map.values (tail (fromList [(["a"], 1), (["b"], 2)])) + ``` + + ``` + Map.values (tail (fromList [([], 1)])) + ``` + }} + +data.Trie.tail.modify : + (Map k111 (Trie k111 v) ->{𝕖} Map k (Trie k v)) -> Trie k111 v ->{𝕖} Trie k v +data.Trie.tail.modify f = cases Trie head tail -> Trie head (f tail) + +data.Trie.tail.modify.doc : Doc +data.Trie.tail.modify.doc = + use tail modify + use text fromList toList + {{ + Modifies the {Trie.tail} of the given {type Trie} using the given function. + + # Example + + ``` + toList + (modify + (Map.map (Trie.map Nat.increment)) + (fromList [("", 0), ("bar", 1), ("baz", 2)])) + ``` + + ``` + toList + (modify (const Map.empty) (fromList [("", 0), ("bar", 1), ("baz", 2)])) + ``` + }} + +data.Trie.tail.set : Map k (Trie k v) -> Trie k111 v -> Trie k v +data.Trie.tail.set tail1 = cases Trie head _ -> Trie head tail1 + +data.Trie.tail.set.doc : Doc +data.Trie.tail.set.doc = + {{ + Sets the {Trie.tail} of a {type Trie} to the provided {type Map}. + + # Example + + ``` + text.toList + (tail.set + (Map.fromList [(?a, text.fromList [("b", 2)])]) + (text.fromList [("", 0), ("foo", 1)])) + ``` + }} + +data.Trie.text.fromList : [(Text, v)] -> Trie Char v +data.Trie.text.fromList = List.map (first toCharList) >> Trie.fromList + +data.Trie.text.fromList.doc : Doc +data.Trie.text.fromList.doc = + {{ + Creates a {type Trie} from a list of {type Text} keys and values, where each + character of the {type Text} becomes a key in the {type Trie}. + + # Example + + ``` + Trie.toList (text.fromList [("foo", 1), ("bar", 2), ("baz", 3)]) + ``` + }} + +data.Trie.text.insert : Text -> v -> Trie Char v -> Trie Char v +data.Trie.text.insert k = Trie.insert (toCharList k) + +data.Trie.text.insert.doc : Doc +data.Trie.text.insert.doc = + use text insert + {{ + Inserts a value into a {type Trie} using a {type Text} key, where each + character of the {type Text} becomes a key in the {type Trie} + + # Example + + ``` + Trie.toList (insert "foo" 1 (insert "bar" 2 (insert "baz" 3 Trie.empty))) + ``` + }} + +data.Trie.text.lookup : Text -> Trie Char v -> Optional v +data.Trie.text.lookup k = Trie.lookup (toCharList k) + +data.Trie.text.lookup.doc : Doc +data.Trie.text.lookup.doc = + use text insert + {{ + Looks up a value in a {type Trie} using a {type Text} key, where each + character of the {type Text} is a key in the {type Trie}. + + # Example + + ``` + text.lookup + "foo" (insert "foo" 1 (insert "bar" 2 (insert "baz" 3 Trie.empty))) + ``` + }} + +data.Trie.text.toList : Trie Char v -> [(Text, v)] +data.Trie.text.toList = List.map (first fromCharList) << Trie.toList + +data.Trie.text.toList.doc : Doc +data.Trie.text.toList.doc = + {{ + Converts a {type Trie} to a list of {type Text} keys and values, such that + each character in the {type Trie}'s key sequence for a value becomes a + character in corresponding {type Text} in the output list. + + # Example + + ``` + text.toList (text.fromList [("foo", 1), ("bar", 2), ("baz", 3)]) + ``` + }} + +data.Trie.toList : Trie k v -> [([k], v)] +data.Trie.toList = Map.toList << Trie.toMap + +data.Trie.toList.doc : Doc +data.Trie.toList.doc = + {{ + Convert a {type Trie} to a list of key-value pairs. + + # Example + + ``` + Trie.toList (Trie.fromList [([1, 2, 3], "a"), ([2, 3, 4], "b")]) + ``` + }} + +data.Trie.toMap : Trie k v -> Map [k] v +data.Trie.toMap t = + use List :+ + go : Trie k v -> [k] -> Map [k] v -> Map [k] v + go = cases + Trie None m, ks, acc -> go' m ks acc + Trie (Some v) m, ks, acc -> Map.insert ks v (go' m ks acc) + go' : Map k (Trie k v) -> [k] -> Map [k] v -> Map [k] v + go' = cases + internal.Tip, ks, m -> m + internal.Bin _ k v l r, ks, m -> + Map.unions [go' l ks m, go v (ks :+ k) m, go' r ks m] + go t [] Map.empty + +data.Trie.toMap.doc : Doc +data.Trie.toMap.doc = + use Map toList + use Trie fromList toMap + {{ + Converts a {type Trie} to a {type Map} from {type List}s to values. + + # Examples + + ``` + toList (toMap (fromList [([], 1), (["a"], 2)])) + ``` + + ``` + toList (toMap (fromList [(["a"], 1), (["b"], 2)])) + ``` + + ``` + toList (toMap (fromList [([], 1)])) + ``` + }} + +data.Trie.union : Trie k v -> Trie k v -> Trie k v +data.Trie.union = Trie.unionWith const + +data.Trie.union.doc : Doc +data.Trie.union.doc = + use Trie fromList + {{ + Returns the union of two {type Trie}s. If a key is present in both + {type Trie}s, the value from the first {type Trie} is used. + + # Examples + + ``` + Trie.toList + (Trie.union + (fromList [([1, 2, 3], "a"), ([2, 3, 4], "b")]) + (fromList [([2, 3, 4], "c"), ([3, 4, 5], "d")])) + ``` + }} + +test> data.Trie.union.tests.values = runs 100 do + use Trie gen values + t1 = gen natInOrder natInOrder () + t2 = gen natInOrder natInOrder () + t1vs = values t1 + uvs = values (Trie.union t1 t2) + p = List.all (v -> List.contains v uvs) t1vs + expect p + +data.Trie.unionWith : + (v ->{e} v ->{e} v) ->{e} Trie k v ->{e} Trie k v ->{e} Trie k v +data.Trie.unionWith f t1 t2 = + use Optional orElse + use Trie head tail + h1 = head t1 + h2 = head t2 + Trie + (orElse (orElse (Optional.map2 f h1 h2) h1) h2) + (Map.unionWith (data.Trie.unionWith f) (tail t1) (tail t2)) + +data.Trie.unionWith.doc : Doc +data.Trie.unionWith.doc = + use Nat + + use text fromList + {{ + Takes two {type Trie}s and a function, and returns a new {type Trie} that + contains all the keys from both input {type Trie}s. If a key is present in + both input {type Trie}s the function is used to combine the values. + + # Example + + ``` + text.toList + (Trie.unionWith + (+) + (fromList [("foo", 1), ("bar", 2)]) + (fromList [("bar", 3), ("baz", 4)])) + ``` + }} + +data.Trie.values : Trie k v -> [v] +data.Trie.values = cases + Trie ov m -> + match ov with + None -> List.flatMap data.Trie.values (Map.values m) + Some v -> v List.+: List.flatMap data.Trie.values (Map.values m) + +data.Trie.values.doc : Doc +data.Trie.values.doc = + {{ + Returns a list of all values in a {type Trie}. + + # Example + + ``` + Trie.values (text.fromList [("foo", 1), ("bar", 2), ("baz", 3)]) + ``` + }} + +data.Tuple.at1 : Tuple a b -> a +data.Tuple.at1 = cases Cons a _ -> a + +data.Tuple.at1.doc : Doc +data.Tuple.at1.doc = + {{ + Get the first element of a tuple. + + # Examples + + ``` + at1 ("Salt", "Pepper") + ``` + + ``` + at1 ("Achivement", "Effort", "Reward") + ``` + + ``` + at1 (Cons "one" ()) + ``` + + See {Tuple.doc} for more information on tuples. + }} + +test> data.Tuple.at1.tests.ex1 = check (at1 ("A", "B") === "A") + +data.Tuple.at2 : Tuple a (Tuple b c) -> b +data.Tuple.at2 = cases Cons _ (Cons b _) -> b + +data.Tuple.at2.doc : Doc +data.Tuple.at2.doc = + {{ + Get the second element of a tuple. + + # Examples + + ``` + at2 ("Salt", "Pepper") + ``` + + ``` + at2 ("Achivement", "Effort", "Reward") + ``` + + See {Tuple.doc} for more information on tuples. + }} + +test> data.Tuple.at2.tests.ex1 = check (at2 ("A", "B") === "B") + +data.Tuple.at3 : Tuple a (Tuple b (Tuple c d)) -> c +data.Tuple.at3 = cases Cons _ (Cons _ (Cons c _)) -> c + +data.Tuple.at3.doc : Doc +data.Tuple.at3.doc = + {{ + Get the third element of a tuple. + + # Examples + + ``` + at3 (?🔴, ?🟢, ?🔵) + ``` + + ``` + at3 ("John", "Paul", "Ringo", "George") + ``` + + See {Tuple.doc} for more information on tuples. + }} + +test> data.Tuple.at3.tests.ex1 = check (at3 ("A", "B", "C") === "C") + +test> data.Tuple.at3.tests.ex2 = check (at3 ("A", "B", "C", "D") === "C") + +data.Tuple.at4 : Tuple a (Tuple b (Tuple c (Tuple d e))) -> d +data.Tuple.at4 = cases Cons _ (Cons _ (Cons _ (Cons d _))) -> d + +data.Tuple.at4.doc : Doc +data.Tuple.at4.doc = + {{ + Get the fourth element of a tuple. + + # Examples + + ``` + at4 ("Beyoncé", "Kelly", "LaTavia", "LeToya") + ``` + + ``` + at4 ("Ginger", "Baby", "Scary", "Sporty", "Posh") + ``` + + See {Tuple.doc} for more information on tuples. + }} + +test> data.Tuple.at4.tests.ex1 = check (at4 ("A", "B", "C", "D") === "D") + +test> data.Tuple.at4.tests.ex2 = + check (at4 ("A", "B", "C", "D", "E", "F") === "D") + +data.Tuple.bimap : (a ->{g1} b) -> (a, a) ->{g1} (b, b) +data.Tuple.bimap f = cases (a, b) -> (f a, f b) + +data.Tuple.bimap.doc : Doc +data.Tuple.bimap.doc = + use Nat + + {{ + Applies a function to both elements of a pair. + + # Example + + ``` + Tuple.bimap (x -> x + 1) (1, 2) + ``` + }} + +data.Tuple.Cons.doc : Doc +data.Tuple.Cons.doc = {{ Prepends an element to a {type Tuple}. }} + +data.Tuple.doc : Doc +data.Tuple.doc = + use Text reverse size + use Tuple bimap mapLeft mapRight second + {{ + 📚 Language Reference: + [Tuple types](https://www.unison-lang.org/docs/language-reference/tuple-types/) + + A {type Tuple} is a heterogeneous list of typed elements. + + # Constructing tuples + + Tuple literals are constructed with a built-in syntactic shorthand. For + example, `` (1, "Two") `` is a binary tuple (a pair), `` (-4, "Yes", false) + `` is a triple, and so on. + + Behind the scenes, an n-tuple is represented by n applications of the + {Cons} constructor, associated to the right and terminated by ``()``: + + ``` + (Cons _ (Cons b (Cons _ _))) = (1, "Two", +3) + b + ``` + + You can use the same shorthand for pattern matching: + + ``` + swap = cases (x, y) -> (y, x) + swap (1, "2") + ``` + + There is no syntactic shorthand for a 1-element tuple: + + ``` + Cons "One" () + ``` + + You can think of the `` () `` value as a zero-tuple. + + There's no limit on the size of tuples. + + # Indexing tuples + + `` at1 t `` gets the first element of the {type Tuple} `t`. `` at2 t `` + gets the second element, and so on. These functions work on tuples of any + length. + + The Base library provides indexing functions up to {at4}, but you can + access any element of a {type Tuple} using pattern matching: + + ``` + at5 = cases Cons _ (Cons _ (Cons _ (Cons _ (Cons x _)))) -> x + at5 (?a, ?b, ?c, ?d, ?e) + ``` + + # Mapping over tuples + + {mapLeft} applies a function to the left element of a pair: + + ``` + mapLeft size ("Left", "Right") + ``` + + {mapRight} applies a function to the right element of a pair: + + ``` + mapRight size ("Left", "Right") + ``` + + {mapPair} applies one function on the left and another on the right: + + ``` + mapPair (size, reverse) ("Left", "Right") + ``` + + {bimap} applies a single function to both elements of a pair where both + have the same type: + + ``` + bimap size ("Left", "Right") + ``` + + {first} applies a function to the first element of any {type Tuple}: + + ``` + first reverse ("one", 2, +3) + ``` + + {second} applies a function to the second element of any {type Tuple}: + + ``` + second reverse (1, "two", +3) + ``` + }} + +data.Tuple.first : (i ->{g} o) -> Tuple i b ->{g} Tuple o b +data.Tuple.first f = cases Cons a b -> Cons (f a) b + +data.Tuple.first.doc : Doc +data.Tuple.first.doc = + {{ + The expression `` first f t `` constructs a new {type Tuple} the same as `t` + except the first element is modified by the function `f`. + + # Example + + ``` + x = ("Soprano", "Alto", "Tenor", "Bass") + first Text.size x + ``` + }} + +data.Tuple.mapLeft : (a ->{g} b) -> (a, c) ->{g} (b, c) +data.Tuple.mapLeft = first + +data.Tuple.mapLeft.doc : Doc +data.Tuple.mapLeft.doc = + use Tuple mapLeft + {{ + {mapLeft} is {first} specialized to pairs. It modifies the first element of + the pair with the given function. + + # Example + + ``` + x = ("Bread", "Butter") + mapLeft Text.size x + ``` + }} + +data.Tuple.mapPair : ((a ->{g} b), (c ->{g} d)) -> (a, c) ->{g} (b, d) +data.Tuple.mapPair = cases (f, g), (a, c) -> (f a, g c) + +data.Tuple.mapPair.doc : Doc +data.Tuple.mapPair.doc = + {{ + The expression `` mapPair (f, g) (a, b) `` constructs the new pair + `(f a, g b)`. + + # Example + + ``` + x = ("Milk", "Cookies") + mapPair (Text.size, Text.reverse) x + ``` + }} + +data.Tuple.mapRight : (a ->{g} b) -> (c, a) ->{g} (c, b) +data.Tuple.mapRight = Tuple.second + +data.Tuple.mapRight.doc : Doc +data.Tuple.mapRight.doc = + use Tuple mapRight + {{ + {mapRight} is {Tuple.second} specialized to pairs. It modifies the second + element of the pair with the given function. + + # Example + + ``` + x = ("Tea", "Biscuits") + mapRight Text.size x + ``` + }} + +data.Tuple.pair : a -> b -> (a, b) +data.Tuple.pair a b = (a, b) + +data.Tuple.pair.doc : Doc +data.Tuple.pair.doc = + use List zipWith + use Tuple pair + {{ + `` pair x y `` creates the {type Tuple} `(x,y)`. + + # Example + + When you want to create a tuple in a lambda: + + ``` + zipWith (x y -> (x, y)) [1, 2] [5, 10] + ``` + + This can be rewritten using {pair} to be easier to read: + + ``` + zipWith pair [1, 2] [5, 10] + ``` + }} + +data.Tuple.second : + (i ->{g} o) -> Tuple a (Tuple i b) ->{g} Tuple a (Tuple o b) +data.Tuple.second f = cases Cons a (Cons b c) -> Cons a (Cons (f b) c) + +data.Tuple.second.doc : Doc +data.Tuple.second.doc = + use Tuple second + {{ + The expression `` second f t `` constructs a new {type Tuple} the same as `t` + except the second element is modified by the function `f`. + + ``` + x = ("Spring", "Summer", "Autumn", "Winter") + second Text.size x + ``` + }} + +data.Tuple.swap : (a, b) -> (b, a) +data.Tuple.swap = cases (a, b) -> (b, a) + +data.Tuple.swap.doc : Doc +data.Tuple.swap.doc = + {{ + Swaps the elements of a 2-tuple. + + # Example + + ``` + Tuple.swap (1, 2) + ``` + }} + +test> data.Tuple.tests.ex1 = + use Text ++ + check + let + tuple : (Text, Nat, Text) + tuple = ("Hello", 3, "Tuple") + actual = at1 tuple ++ Nat.toText (at2 tuple) ++ at3 tuple + expected = "Hello3Tuple" + actual === expected + +test> data.Tuple.tests.ex2 = + use Nat * + + check + let + pythagoras : (Nat, Nat, Nat) + pythagoras = (3, 4, 5) + square n = n * n + let + (a, b, c) = pythagoras + square a + square b === square c + +Debug.tap : Text -> a -> a +Debug.tap = Function.tap << Debug.trace + +Debug.tap.doc : Doc +Debug.tap.doc = + use Nat == + {{ + `` Debug.tap t v `` evaluates `` Debug.trace t v `` and then returns the + value `v`, allowing you to trace intermediate values in pipelines. + + # Example + + @typecheck ``` + x = + [1, 2, 3] |> List.map Nat.increment |> Debug.tap "incremented" |> Nat.sum + check (x == 9) + ``` + + This will print the following to the console: + + ``` raw + trace: incremented + [2, 3, 4] + ``` + + # See also + + {Function.tap} + }} + +Debug.toDebugText : a -> Text +Debug.toDebugText a = + match toDebugText.impl a with + None -> + "⚠️ (Debug.toText called, but Unison is not being run with tracing support)" + Some e -> Either.fold id id e + +Debug.toDebugText.doc : Doc +Debug.toDebugText.doc = + {{ + Makes a best effort attempt to convert an arbitrary value to {type Text}. + + The representation might not be particularly pretty but it can still be + useful for debugging or if writing introspection tools in pure Unison. + + Also see {Debug.trace}. + }} + +-- builtin Debug.toDebugText.impl : a -> Optional (Either Text Text) + +Debug.toDebugText.impl.doc : Doc +Debug.toDebugText.impl.doc = + {{ + This function converts any value to {type Text}, purely for debugging + purposes. It returns different things depending on the mode Unison is being + run in. + + * {None} indicates Unison is being run with all tracing support switched off. + * {Left} indicates Unison is being run with only low-level tracing support. + The inside {type Text} may not be particularly pretty. + * A {Right} indicates Unison is being run with full tracing support. The + inside {type Text} will be a pretty-printed definition. + }} + +-- builtin Debug.trace : Text -> a -> () + +Debug.trace.doc : Doc +Debug.trace.doc = + use Debug trace + use Nat * + {{ + {trace} prints out a {type Text} and any value and returns `()`. + + For example, this returns `()` after printing `"Calling f"` followed by + `"2468"` to the console: + + ``` + f n = n * 2 + trace "Calling f" (f 1234) + ``` + }} + +-- builtin Debug.watch : Text -> a -> a + +Debug.watch.doc : Doc +Debug.watch.doc = + use Nat * + {{ + `` watch t e `` prints the text `t` to the console after the expression `e` + has been evaluated. Returns the value of `e`. + + For example, this returns `2468` after printing "Calling f" to the console: + + ``` + f n = n * 2 + watch "Calling f" (f 1234) + ``` + }} + +(Doc.++) : Doc -> Doc -> Doc +d1 Doc.++ d2 = match (d1, d2) with + (Doc.Join ds, Doc.Join ds2) -> Doc.Join (ds List.++ ds2) + (Doc.Join ds, _) -> Doc.Join (ds List.:+ d2) + (_, Doc.Join ds) -> Doc.Join (d1 List.+: ds) + _ -> Doc.Join [d1, d2] + +Doc.Anchor.doc : Doc +Doc.Anchor.doc = + {{ + {Anchor} decorates a {type Doc} with an anchor. Anchors are used to identify + a particular {type Doc} in a {type Doc} tree. Anchors are useful for linking + to a particular {type Doc} in a {type Doc} tree. + }} + +Doc.BulletedList.doc : Doc +Doc.BulletedList.doc = + {{ + A {BulletedList} is a {type Doc} that represents a bulleted list of items, + each of which is a {type Doc}. + + You should use {docBulletedList} to construct a {BulletedList} instead of + using this constructor directly. Or use the + [documentation syntax](https://www.unison-lang.org/learn/usage-topics/documentation/). + + # Example + + ``` + docBulletedList [{{ Winter }}, {{ Spring }}, {{ Summer }}, {{ Autumn }}] + ``` + }} + +Doc.Callout.doc : Doc +Doc.Callout.doc = + {{ + {Callout} wraps a {type Doc} in a callout block. Callout blocks are used to + highlight a particular {type Doc} for the reader, visually separating it from + the surrounding {type Doc} with an optional title or symbol. + + You should not use {Callout} directly. Instead use {docCallout}. + }} + +Doc.codeBlock : Text -> Text -> Doc +Doc.codeBlock typ code = CodeBlock typ (Word code) + +Doc.codeBlock.doc : Doc +Doc.codeBlock.doc = + {{ + Returns a {type Doc} code block. The first argument is the language, and the + second argument is the code. + + # Example + + ``` + codeBlock + "unison" "fib x =\n if x < 2 then x else fib (x - 1) + fib (x - 2)" + ``` + }} + +Doc.CodeBlock.doc : Doc +Doc.CodeBlock.doc = + {{ + Embeds a code block in a {type Doc} value. The first argument is the language + of the code block, and the second argument is the code itself. + + You should not use this function directly. Instead, use the {docCodeBlock} + function, or use the documentation syntax as detailed in + [Documenting Unison Code](https://unisonweb.org/docs/documentation). + }} + +Doc.Column.doc : Doc +Doc.Column.doc = + {{ + Takes a {type List} of {type Doc}s and returns a {type Doc} that displays the + {type List} of {type Doc}s in a column with their left edges aligned. + + You should not call this constructor directly. Instead use {docColumn}. + }} + +(Doc.Deprecated.++) : Deprecated -> Deprecated -> Deprecated +d1 Doc.Deprecated.++ d2 = match (d1, d2) with + (Deprecated.Join ds, Deprecated.Join ds2) -> Deprecated.Join (ds List.++ ds2) + (Deprecated.Join ds, _) -> Deprecated.Join (ds List.:+ d2) + (_, Deprecated.Join ds) -> Deprecated.Join (d1 List.+: ds) + _ -> Deprecated.Join [d1, d2] + +Doc.Deprecated.example : Link.Term -> Deprecated +Doc.Deprecated.example e = + source = Deprecated.Source (Link.Term e) + value = Evaluate e + Deprecated.Join [Blob "\n", source, Blob "\n\nOutput: ", value, Blob ""] + +Doc.Deprecated.example.doc : Deprecated +Doc.Deprecated.example.doc = + [: Given a `termRef` reference, constructs an example that shows the source + of that term as well as the result of evaluating it.:] + +Doc.doc : Doc +Doc.doc = + {{ + # Unison Documentation + + A {type Doc} is computable first-class documentation. You are reading a + {type Doc} at this very moment. + + 📚 Tutorial: + [Documenting Unison code](https://www.unison-lang.org/docs/usage-topics/documentation/) + }} + +Doc.EmbedSvg.doc : Doc +Doc.EmbedSvg.doc = + {{ + Used by @inlineSignature{embedSvg} for embedding SVG images into a + {type Doc}. + }} + +Doc.EmbedSvg.embedSvg : Text -> Doc +Doc.EmbedSvg.embedSvg txt = Special (Embed (Any (EmbedSvg txt))) + +Doc.EmbedSvg.embedSvg.doc : Doc +Doc.EmbedSvg.embedSvg.doc = + use embedSvg example + {{ + Embeds an SVG image into a {type Doc}. + + For example, given this code: + + @source{example} + + Here it is rendered: + + {{ example }} + }} + +Doc.EmbedSvg.embedSvg.example : Doc +Doc.EmbedSvg.embedSvg.example = + txt = + """ + + + + """ + embedSvg txt + +Doc.Folded.doc : Doc +Doc.Folded.doc = + {{ + {Folded} decorates a {type Doc} with a folded state represented by a + {type Boolean}. If the {type Boolean} is `` true `` the {type Doc} is folded. + Otherwise the {type Doc} is unfolded. + }} + +Doc.FrontMatter.doc : Doc +Doc.FrontMatter.doc = + {{ + Unison {type Doc} type for embedding FrontMatter in Unison Doc. + + ex: + + ``` unison + myDoc = {{ + + {{ Special (Embed (Any (FrontMatter [("title", "FrontMatter Example"), ("author", "Simon")]))) }} + + # FrontMatter Example + + }} + ``` + + This will result in the following YAML and HTML when used with the + `docs.to-html` ucm command: + + ``` html + --- + title: FrontMatter Example + author: Simon + --- +
+

FrontMatter Example

+

+ ``` + + See {frontMatter} for a more ergonomic helper. + }} + +Doc.FrontMatter.frontMatter : [(Text, Text)] -> Doc +Doc.FrontMatter.frontMatter data = Special (Embed (Any (FrontMatter data))) + +Doc.FrontMatter.frontMatter.doc : Doc +Doc.FrontMatter.frontMatter.doc = + {{ + Helper for embedding FrontMatter in Unison Doc. + + ex: + + ``` unison + myDoc = {{ + + {{ frontMatter [("title", "FrontMatter Example"), ("author", "Simon")] }} + + # FrontMatter Example + + }} + ``` + + This will result in the following YAML and HTML when used with the + `docs.to-html` ucm command: + + ``` html + --- + title: FrontMatter Example + author: Simon + --- +
+

FrontMatter Example

+

+ ``` + }} + +Doc.Join.doc : Doc +Doc.Join.doc = {{ Joins a list of {type Doc} values together. }} + +Doc.LaTeXInline.doc : Doc +Doc.LaTeXInline.doc = + {{ + Unison {type Doc} type for embedding inline + [LaTeX](https://en.wikipedia.org/wiki/LaTeX) in Unison Doc. + + Ex: + + ``` unison + myDoc = {{ + + # LaTeXInline Example + + {{ Special (EmbedInline (Any (LaTeXInline "\\f\\relax{x} = \\int_{-\\infty}^\\infty \\f\\hat\\xi\\,e^{2 \\pi i \\xi x} \\,d\\xi"))) }} + + }} + ``` + + Results in: {{ + Special + (EmbedInline + (Any + (LaTeXInline + "\\f\\relax{x} = \\int_{-\\infty}^\\infty \\f\\hat\\xi\\,e^{2 \\pi i \\xi x} \\,d\\xi"))) + }} + + See {laTeXInline} for a more ergonomic helper. + }} + +Doc.LaTeXInline.laTeXInline : Doc -> Doc +Doc.LaTeXInline.laTeXInline = cases + Paragraph [Code (Word raw)] -> Special (EmbedInline (Any (LaTeXInline raw))) + other -> + bug + ( "I don't know how to handle this, please use backticks for inline LaTeX" + , other + ) + +Doc.LaTeXInline.laTeXInline.doc : Doc +Doc.LaTeXInline.laTeXInline.doc = + {{ + Helper for embedding inline [LaTeX](https://en.wikipedia.org/wiki/LaTeX) in + Unison Doc. + + Ex: + + ``` unison + myDoc = {{ + + # LaTeXInline Example + + {{ laTeXInline {{ `\f\relax{x} = \int_{-\infty}^\infty \f\hat\xi\,e^{2 \pi i \xi x} \,d\xi` }} }} + + }} + ``` + + Results in: {{ + laTeXInline + {{ + `\f\relax{x} = \int_{-\infty}^\infty \f\hat\xi\,e^{2 \pi i \xi x} \,d\xi` + }} }} + }} + +Doc.MediaSource.doc : Doc +Doc.MediaSource.doc = + {{ + Unison {type Doc} type used to create videos source for {type Video}. + Multiple can be used for different formats of the same video. + }} + +Doc.MediaSource.mimeType.doc : Doc +Doc.MediaSource.mimeType.doc = {{ The MIME type of the media source. }} + +Doc.MediaSource.mimeType.modify.doc : Doc +Doc.MediaSource.mimeType.modify.doc = + {{ Modifies the MIME type of a {type MediaSource}. }} + +Doc.MediaSource.mimeType.set.doc : Doc +Doc.MediaSource.mimeType.set.doc = + {{ Sets the MIME type of a {type MediaSource}. }} + +Doc.MediaSource.sourceUrl.doc : Doc +Doc.MediaSource.sourceUrl.doc = {{ The URL of the media source. }} + +Doc.MediaSource.sourceUrl.modify.doc : Doc +Doc.MediaSource.sourceUrl.modify.doc = + {{ Modifies the source URL of a {type MediaSource}. }} + +Doc.MediaSource.sourceUrl.set.doc : Doc +Doc.MediaSource.sourceUrl.set.doc = + {{ Sets the source URL of a {type MediaSource}. }} + +Doc.NumberedList.doc : Doc +Doc.NumberedList.doc = + {{ + A {NumberedList} is a {type Doc} that represents a numbered list of items, + each of which is a {type Doc}. The numbering starts from the given + {type Nat}. + + You should use {docNumberedList} to construct a {NumberedList} instead of + using this constructor directly. Or use the + [documentation syntax](https://www.unison-lang.org/learn/usage-topics/documentation/). + + # Example + + ``` + docNumberedList 0 [{{ John }}, {{ Paul }}, {{ George }}, {{ Ringo }}] + ``` + }} + +Doc.Paragraph.doc : Doc +Doc.Paragraph.doc = + {{ + Styles a {type List} of {type Doc} values as a paragraph. + + You should not use this function directly. Instead, use the {docParagraph} + function, or use the documentation syntax as detailed in + [Documenting Unison Code](https://unisonweb.org/docs/documentation). + }} + +Doc.shareSlug : Text -> Doc +Doc.shareSlug txt = + use Text ++ + docNamedLink + (docCode (docWord txt)) (docWord ("https://share.unison-lang.org/" ++ txt)) + +Doc.shareSlug.doc : Doc +Doc.shareSlug.doc = + {{ + A helper function for making links in a {type Doc} to usernames or projects + on Unison Share. Within your doc, use (for example) + `{{ shareSlug "@unison/base" }}` and it will render nicely as + {{ shareSlug "@unison/base" }}. + + **More examples:** + + {{ + docTable + [ [{{ `shareSlug @runarorama` }}, shareSlug "@runarorama"] + , [{{ `shareSlug @rlmark` }}, shareSlug "@rlmark"] + , [{{ `shareSlug @unison/json` }}, shareSlug "@unison/json"] + , [{{ `shareSlug @unison/cloud` }}, shareSlug "@unison/cloud"] + ] }} + }} + +Doc.Special.doc : Doc +Doc.Special.doc = + {{ + {Special} includes a {type SpecialForm} in a {type Doc}. Special forms + include things like source code, examples, links, images, and videos. + }} + +Doc.SpecialForm.doc : Doc +Doc.SpecialForm.doc = + {{ + A {type SpecialForm} is a part of a {type Doc} and is used to embed things + like type signatures, evaluated terms, or embedded media. + + # Example + + ``` + SpecialForm.Signature [Term.Term (Any (do Set.map))] |> Special + ``` + }} + +Doc.SpecialForm.Link.doc : Doc +Doc.SpecialForm.Link.doc = + use List map + use SpecialForm Link + {{ + Constructs a {type Doc} value that represents a link to {type Either} a + Unison type or term. + + Instead of calling this function, you can use the documentation syntax + detailed in + [Documenting Unison Code](https://unisonweb.org/docs/documentation). + + # Examples + + Links to the {map} function: + + ``` + {{ + {{ Special (Link (Right (term do map))) }} + }} + ``` + + Or equivalently: + + ``` + {{ + {map} + }} + ``` + + Links to the {type Int} type: + + ``` + {{ + {{ Special (Link (Left (typeLink Int))) }} + }} + ``` + + Or equivalently: + + ``` + {{ + {type Int} + }} + ``` + }} + +Doc.Style.doc : Doc +Doc.Style.doc = + {{ + Annotate a {type Doc} with a named style. The style can be used to customize + the rendering of the {type Doc}, for example with a style sheet. + }} + +Doc.term : '{g1} a -> Doc.Term +Doc.term a = Term.Term (Any a) + +Doc.Term.doc : Doc +Doc.Term.doc = + {{ + A {type Doc.Term} wraps a Unison term of {type Any} type. This is used to + embed Unison terms in {type Doc} values. + }} + +Doc.term.doc : Doc +Doc.term.doc = + {{ Embeds a Unison term of {type Any} type in a {type Doc} value. }} + +Doc.Text.doc : Doc +Doc.Text.doc = + {{ + Represents a table in a {type Doc}, as a list of rows, where each row is a + list of cells (and each cell is a {type Doc}). + }} + +Doc.UntitledSection.doc : Doc +Doc.UntitledSection.doc = + {{ + Groups a list of {type Doc} values into a single {type Doc} value that + renders as a section without a title. + + You should not use this constructor directly. Instead, use + {docUntitledSection}. + + # Examples + + ``` + {{ + {{ UntitledSection [{{ This is a section without a title. }}] }} + }} + ``` + }} + +Doc.Video.config.doc : Doc +Doc.Video.config.doc = + {{ + Gets the configuration of a {Video}, which is a list of key-value pairs. The + supported keys are dependent on the rendering target, but the following are + common: + + * `` "width" `` - the width of the video in pixels + * `` "height" `` - the height of the video in pixels + * `` "autoplay" `` - whether the video should start playing automatically + * `` "loop" `` - whether the video should loop when it reaches the end + * `` "muted" `` - whether the video should be muted + * `` "controls" `` - whether the video should have controls + * `` "poster" `` - a URL to an image to show before the video starts playing + * `` "preload" `` - whether the video should be preloaded + * `` "src" `` - the URL of the video + + See e.g. + [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video) + for the list of supported attributes in the HTML rendering target. + }} + +Doc.Video.config.set.doc : Doc +Doc.Video.config.set.doc = + {{ Sets the configuration for the given {type Video} value. }} + +Doc.Video.doc : Doc +Doc.Video.doc = + {{ + Unison Doc type for embedding video. + + ex: + + ``` unison + myDoc = {{ + + # Video Example + + {{ Special (Embed (Any (Video [(MediaSource "test.mp4" None)] [("poster", "test.png")]))) }} + + }} + ``` + + This will result in the following HTML when used with the `docs.to-html` ucm + command: + + ``` html +
+

Video Example

+ + +

+ ``` + + See {video} for a more ergonomic helper to work with videos in Unison Doc. + }} + +Doc.Video.sources.doc : Doc +Doc.Video.sources.doc = + {{ The {type List} of {type MediaSource}s for a {type Video}. }} + +Doc.Video.sources.modify.doc : Doc +Doc.Video.sources.modify.doc = + {{ Modifies the list of video sources with the given function. }} + +Doc.Video.sources.set.doc : Doc +Doc.Video.sources.set.doc = {{ Sets the sources for the given {type Video}. }} + +Doc.Video.video : Text -> Text -> Doc +Doc.Video.video src posterUrl = + Special (Embed (Any (Video [MediaSource src None] [("poster", posterUrl)]))) + +Doc.Video.video.doc : Doc +Doc.Video.video.doc = + {{ + Helper for embedding video in Unison Doc. + + ex: + + ``` unison + myDoc = {{ + + # Video Example + + {{ video "test.mp4" "test.png" }} + + }} + ``` + + This will result in the following HTML when used with the `docs.to-html` ucm + command: + + ``` html +
+

Video Example

+ + +

+ ``` + }} + +Doc.Word.doc : Doc +Doc.Word.doc = + {{ + A {Word} wraps a {type Text} value. This is the most basic form of {type Doc} + value. + }} + +docs.abilitiesTutorialLink : Doc +docs.abilitiesTutorialLink = + {{ + [abilities](https://www.unison-lang.org/docs/fundamentals/abilities/mental-model/) + }} + +docs.arrays : Doc +docs.arrays = + use Array toList + {{ + There are four types of fixed-size arrays in Unison: + + {{ + docTable + [ [{{ }}, {{ **Mutable** }}, {{ **Immutable** }}] + , [{{ **Boxed** }}, {{ {type mutable.Array} }}, {{ {type data.Array} }}] + , [ {{ + **Unboxed** + }} + , {{ + {type mutable.ByteArray} + }} + , {{ + {type data.ByteArray} + }} + ] + ] }} + + # Immutable boxed arrays + + {type data.Array} is an immutable, flat array of elements. It's __boxed__ + in the sense that it stores references to the elements, rather than the + elements themselves. + + For example: + + ``` + toList (data.Array.of 0 10) + ``` + + Use a {type data.Array} when: + + * You need a sequence of elements of the same type. + * You know the number of elements ahead of time. + * You don't need to insert and remove elements. + * You want fast indexing, slicing, and splitting. + + # Mutable boxed arrays + + {type mutable.Array} is a mutable, flat array of elements. It's __boxed__ + in the sense that it stores references to the elements, rather than the + elements themselves. It's __mutable__ because you can alter the contents of + the array using the {type Scope} or {type IO} abilities. + + For example: + + ``` + catch do + Scope.run do + arr = mutable.Array.of "x" 10 + Array.write arr 7 "y" + toList (Array.freeze arr) + ``` + + Use a {type mutable.Array} when: + + * You want to use an array for all the reasons above. + * You want to be able to alter the contents of the array. + + # Byte arrays + + Byte arrays are __unboxed__ in the sense that they store the bytes of the + data directly. They come in two flavors: + + * {type data.ByteArray} is an immutable, flat array of bytes. + * {type mutable.ByteArray} is a mutable, flat array of bytes that you can + mutate using the {type Scope} or {type IO} abilities. + + Use a {type data.ByteArray} when: + + * You want to work with the raw representation of your data directly in + memory. + * You know the number of bytes ahead of time. + * You don't need to insert and remove bytes. + * You want fast indexing and splitting. + + Use a {type mutable.ByteArray} when: + + * You want to use a byte array for all the reasons above. + * You want to be able to alter the contents of the array. + }} + +docs.basicAbilities : Doc +docs.basicAbilities = + {{ + # Basic abilities in the Base library + + The base library provides a number of {{ abilitiesTutorialLink }}. + + ## Abilities that model failure + + 📚 Tutorial: + [Error handling with abilities](https://www.unison-lang.org/docs/fundamentals/abilities/error-handling/) + + ### Aborting a function without returning + + The {type Abort} ability provides a single constructor, {abort} that + lets a program request that execution be aborted. + + ### Throwing errors + + {type Throw} provides the ability to throw an error or any other + value out to an ability handler. It provides a single ability + constructor, {throw}. + + ### Throwing exceptions + + {type Exception} provides the ability to throw a {type Failure} to + an exception handler like {catch}. It provides a single ability + constructor, {Exception.raise}, but has a somewhat rich API for + working with failures. + + ## Dependency injection: asking for a value from the outside + + {type Ask} is the ability to read a value of a particular type. The + single constructor, {ask}, asks an ability handler to provide a value of + that type. + + ## Storing state + + {type Store} is the ability to read and write some state (via + {Store.get} and {Store.put}, respectively) and modify it (via + {Store.modify}). + + ## Mutable state + + {type Scope} is the ability to create and modify mutable references that + are local to some scope. + + ## Lazy data streams and iteration + + {type Stream} is the ability to emit values onto a data stream (via + {emit}). The {type Stream} ability comes with a rich collection of + functions for working with streams. + + {type Each} is the ability for a computation to repeat once for each + value in a {type Stream} or {type List}. This can be used for + nondeterminism, list comprehensions, and logic programming. + + ## Randomness + + {type Random} is the ability to generate pseudorandom values. The + {type Random} ability comes with various functions for generating data. + + ## I/O + + {type IO} is the ability to perform I/O. The {type IO} ability comes + with a rich API for working with files, sockets, and other system + facilities. + }} + +docs.basicDataTypes : Doc +docs.basicDataTypes = + {{ + # Basic data types in the Base library + + ## Optional values + + 📚 Tutorial: + [Error handling with data types](https://www.unison-lang.org/docs/fundamentals/control-flow/exception-handling/). + + The type {type Optional} represents optional values, for functions that + might not return any value, or functions that take optional arguments. + + A value like {{ docExample 1 do x -> (x : Optional Int) }} either has an + {type Int} (e.g. ``Some 4``) or it's ``None``. + + ## Values with one of two types + + 📚 Tutorial: + [Error handling with data types](https://www.unison-lang.org/docs/fundamentals/control-flow/exception-handling/). + + The {type Either} type represents values than be one of two types. For + example, a value {{ docExample 1 do x -> (x : Either Text Int) }} is + either `` Left txt `` or `` Right n `` where `txt` is of type + {type Text} and `n` is of type {type Int}. + + ## A value containing no data + + Unison has a special value called `` () `` (pronounced "unit"), which is + of type {type Unit}. It's the only value of type {type Unit}, and the + type itself can also be written `()`. + + ``` + () : () + ``` + + See the documentation on {type Unit} for more details. + + ## A types with no values + + An even further impoverished type is {type Void}. There are + __no values at all__ of this type. This is used for functions that + should never terminate, functions that should never be called, or cases + that should never come up. See the documentation on {type Void} for more + details. + }} + +docs.collectionTypes : Doc +docs.collectionTypes = + use Stream +: + {{ + # Lists + + {type List} represents finite sequences of elements like: + + * `` [?u, ?u, ?d, ?d, ?l, ?r, ?l, ?r, ?b, ?a] `` + * ``["chocolate", "vanilla", "strawberry"]``. + * `` [] `` + + Use a {type List} when: + + * You want to collect items + * You want to allow duplicates + * You need the items in a particular order + + {type List.Nonempty} is the type of lists that can never be empty, like: + + * `` "cheese" +| ["pepperoni", "pineapple", "licorice", "liver"] `` + * `` "baking" +| ["movie", "picnic", "stroll", "karate fight"] `` + + Use a {type List.Nonempty} when you want a {type List} but you want to + ensure it's never empty. + + # Maps + + {type Map} allows you to associate keys with values, like: + + @typecheck ``` + Map.fromList + [ ("Africa", "Lion") + , ("Asia", "Tiger") + , ("Australia", "Kangaroo") + , ("Europe", "Wolf") + , ("North America", "Grizzly Bear") + , ("South America", "Jaguar") + ] + ``` + + Use a {type Map} when: + + * You want to associate keys with values + * You don't want to allow duplicate keys + * You don't care about the order of the keys + + ## Specialized maps + + {type NatMap} is a {type Map} where the keys are fixed to be + {type Nat}s. This is much more efficient than a {type Map}, so you + should use a {type NatMap} instead when your keys are of type + {type Nat}, or can be easily converted to/from {type Nat}s. + + @typecheck ``` + NatMap.fromList [(1, "one"), (2, "two"), (3, "three")] + ``` + + # Sets + + {type Set} represents unordered collections of distinct elements, like: + + @typecheck ``` + Set.fromList ["Mystery", "Romance", "Nonfiction", "Sci-Fi"] + ``` + + Use a {type Set} when: + + * You want to collect items + * You don't care about the order of the items + * You don't want duplicate items + + ## Specialized sets + + {type NatSet} is a {type Set} where the elements are fixed to be + {type Nat}s. This is much more efficient than a {type Set}, so you + should use a {type NatSet} instead when your elements are of type + {type Nat}, or can be easily converted to/from {type Nat}s. + + @typecheck ``` + NatSet.fromList [1, 2, 3] + ``` + + # Bags + + {type Bag} represents unordered collections of (possibly repeated) + elements, like: + + @typecheck ``` + Bag.fromOccurrenceList [("Apple", 6), ("Orange", 4)] + ``` + + Use a {type Bag} when: + + * You want to collect items + * You don't care about the order of the items + * You want to allow duplicate items + + # Tries + + {type Trie} represents a [trie](https://en.wikipedia.org/wiki/Trie), also + known as a __prefix tree__. This is a collection where each item is + associated with a key which is a {type List}, and you can efficiently look + up items based on a prefix of the key. + + For example: + + @typecheck ``` + text.fromList + [("cat", "meow"), ("dog", "woof"), ("duck", "quack"), ("frog", "ribbit")] + ``` + + Use a {type Trie} when: + + * You need a {type Map}-like collection + * You need to look up items based on a prefix of the key + * You may want to order the items lexicographically by key + + # Arrays + + {{ arrays }} + + # Streams + + {type Stream} is an + [ability](https://www.unison-lang.org/learn/fundamentals/abilities) that + allows you to lazily generate a potentially infinite sequence of values. + + For example, this computation is an infinite sequence of random numbers: + + @typecheck ``` + rng : '{Random, Stream Nat} Void + rng = do (+:) Random.nat! rng () + ``` + + Use a {type Stream} when: + + * You want to lazily generate a potentially infinite sequence of values + * You don't want to store the entire sequence in memory + * You may need to use other abilities to generate the values + }} + +docs.concurrency : Doc +docs.concurrency = + {{ + # Concurrency and multithreading + + `` fork c `` forks the computation `c` onto a new green thread (see + Wikipedia: [green threads](https://en.wikipedia.org/wiki/Green_threads)), + returning the {type ThreadId} of the new thread. + + Basic communication among threads is done with {type Ref} and + {type Promise}. + + {type Ref} is the type of mutable memory cells that can be read and written + atomically. + + A {type Promise} is a synchronization primitive that represents a single + value that may not be available yet. A {type Promise} can be thought of as + a {type Ref} that can only be written once, and threads can block waiting + for the value to be written. + + # Transactional memory + + {type TVar} is the type of shared mutable references that can be modified + with atomic transactions using the {type STM} ability. Atomic transactions + can be controlled with the {STM.atomically} function. + + The {type TMap} type provides concurrent transactional maps, and + {type TQueue} provides transactional queues. + }} + +docs.higherOrderFunctions : Doc +docs.higherOrderFunctions = + use Function flatMap join + use Nat == + use Tuple pair + {{ + # Basic higher-order functions in the Base library + + {<<} is function composition: + + @signature{<<} + + {compose2}, {compose3}, {apply2}, {composeK}, and {flatMap} are various + forms of composition on functions of more than one argument: + + @signature{compose2} @signature{compose3} @signature{apply2} + @signature{composeK} @signature{flatMap} + + {{ SectionBreak }} + + {curry} turns a function that takes a pair into a function that takes two + arguments one at a time: + + @signature{curry} + + {uncurry} goes the other way: + + @signature{uncurry} + + {{ SectionBreak }} + + {flip} flips the argument order of a two-argument function: + + @signature{flip} + + For example: + + ``` + flip pair "One" "Two" + ``` + + {{ SectionBreak }} + + {join} passes the same value to both sides of a two-argument function: + + @signature{join} + + For example: + + ``` + join pair "xoxo" + ``` + + {{ SectionBreak }} + + {on} applies one function to two values, then passes both results to a + second function: + + @signature{on} + + For example, `` "One" `` and `` "Two" `` are equal __on__ their size: + + ``` + on (==) Text.size "One" "Two" + ``` + + {{ SectionBreak }} + + {fix} finds the fixed point of a function: + + @signature{fix} + }} + +docs.inputOutput : Doc +docs.inputOutput = + {{ + # I/O in Unison + + Input/output operations are provided by the {type IO} ability, which is + built into Unison. + + ## Manipulating files + + The {type Handle} type represents file handles. It is built in to + Unison. Operations for opening, closing, reading, and writing files are + detailed in the documentation for {type Handle}. + + The type {type FilePath} represents a path to a file or directory. It + provides functions for manipulating directories and file metadata, + creating temporary directories, etc. + + ## Interacting with the console + + {stdIn}, {stdOut}, and {stdErr} are file handles for standard input, + standard output, and standard error, respectively. + + The functions {readLine} and {printLine} are convenience functions for + reading from {stdIn} and printing to {stdOut}, respectively. + + {{ networkAccess }} + }} + +docs.languageSupport : Doc +docs.languageSupport = + use Code dependencies lookup + {{ + # Unison language constructs in the Base library + + The {type Code} type abstractly represents some Unison type or term. + + ## Serialization and deserialization + + {type Code} can be deserialized from {type Bytes}: + + @signature{Code.deserialize} + + {type Code} can be serialized to {type Bytes}: + + @signature{Code.serialize} + + ## Codebase interaction + + We can look up a {type Link.Term} in the codebase and get back its + {type Code}: + + @signature{lookup} + + For example: + + @typecheck ``` + sinFunction : '{IO} Optional Code + sinFunction = do lookup (termLink sin) + ``` + + We can get the dependencies of a term: + + @signature{dependencies} + + For example: + + @typecheck ``` + listFilterDeps : '{IO, Abort} [Link.Term] + listFilterDeps _ = + dependencies (Optional.toAbort (lookup (termLink List.filter))) + ``` + }} + +docs.networkAccess : Doc +docs.networkAccess = + {{ + # Networking + + This namespace provides networking components, inluding: + + * A simple interface to + [TCP networking](http://en.wikipedia.org/wiki/Transmission_Control_Protocol). + * An implementation of the + [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security) protocol. + + ## TCP networking + + {{ Socket.doc }} + + ## TLS Sockets + + {{ Tls.doc }} + + ## Abstract network connections + + The {type Connection} type is an abstract type that represents a network + connection. It can be backed by either a {type Socket} or a + {type TlsSocket}: + + @signatures{socket, tls.deprecated} + + A {type Connection} provides three functions: + + @signatures{Connection.send, receiver, closer} + }} + +docs.primitiveTypes : Doc +docs.primitiveTypes = + {{ + # Primitive types + + 📚 Language Reference: + [Built-in types](https://www.unison-lang.org/docs/language-reference/built-in-types/) + + 🔎 Tip: follow the links for individual types for detailed documentation. + + {type Nat} is the type of 64-bit unsigned integers, like `` 0 `` and + ``3735928559``. + + {type Int} is the type of 64-bit signed integers, like `` +0 `` and + ``-3735928559``. + + {type Float} is the type of 64-bit ("double precision") floating point + numbers, like `` -1.6777216e7 `` and ``3.141592653589793``. + + {type Boolean} values can only be either `` true `` or ``false``. + + {type Bytes} is the type of raw in-memory data, represented as strings of + bytes, such as ``0xsdeadbeef``. + + {type Text} represents textual strings in Unicode, like `` "Hello, World!" + `` + + {type Char} is the type of individual Unicode characters (more precisely, + single code points) like ``?A``, ``?b``, ``?🌈``, and ``?⭐``. + }} + +docs.tests : Doc +docs.tests = + use Each repeat + use Nat * == + use Random natIn + use Result Fail + use Text ++ head reverse + use arbitrary nats + use test raiseFailure verify + {{ + # Types and functions for testing + + 📚 Tutorial: + [Testing your Unison code](https://www.unison-lang.org/docs/usage-topics/testing/) + + ## Test results + + The Unison Codebase Manager expects a {type List} of {type Result} + values from tests. A {type Result} can be either `` Ok `` or ``Fail``. + + A simple way to generate a result is {check}, which just checks that a + {type Boolean} is ``true``: + + ``` + check (1 == 2) + ``` + + ``` + check (1 == 1) + ``` + + ## Running tests + + UCM will run all tests in the current project when you issue the `test` + command. It'll also run any tests in the scratch file on save. A line + beginning with `test>` will be treated as a test definition. For + example: + + ``` unison + test> myTest = check (1 == 1) + ``` + + ## Property-based testing + + The Base library provides the {verify} function for property-based + testing: + + @signature{verify} + + This function takes a block of code that generates test cases and checks + that the property holds for all of them. For example: + + @typecheck ``` + verify do + repeat 100 + n = Random.nat() + ensureEqual (Nat.fromText (Nat.toText n)) (Some n) + ``` + + This will generate 100 random test cases, each consisting of a random + number, and check that the number is equal to itself when converted to + {type Text} and back. If the property fails for any of the test cases, + the test will return {Fail} containing the failing test case. + + The computation passed to {verify} can use various abilities: + + * {type Random} for generating random test cases. + * {type Each} for generating a range of test cases or repeating a test a + certain number of times. + * {type Exception} for failing a test. + * {type Label} for adding scoped labels to tests that will be displayed + in the results of failing tests. + + ## Failing tests + + {verify} will return a {Fail} if the computation passed to it raises an + {type Exception}. + + You can use {raiseFailure} to fail a test explicitly: + + @typecheck ``` + verify do + b = Random.boolean() + when b do raiseFailure "This test sometimes fails" b + ``` + + The first argument to {raiseFailure} is a message that will be displayed + in the test results, and the second argument is a payload that will be + displayed in the test results if the test fails. + + There are also functions for checking properties that give more detailed + information about the failure: + + * {ensure} - check that a value is true. + * {ensureWith} - check that a value is true, with a custom payload for + the failure. + * {ensureEqual} - check that two values are equal. + * {{ docLink (docEmbedTermLink do ensureNotEqual) }} - check that two + values are not equal. + * {ensureLess} - check that the first value is less than the second. + * {ensureLessOrEqual} - check that the first value is at most the + second. + * {ensureGreater} - check that the first value is greater than the + second. + * {ensureGreaterOrEqual} - check that the first value is at least the + second. + * {ensuring} - check that a given computation returns ``true``. + * {{ docLink (docEmbedTermLink do ensuringWith) }} - check that a given + computation returns ``true``, with a custom payload for the failure + case. + + For example, here is a test that the Base library uses for the {head} + function: + + @source{head.tests} + + ## Generating test cases + + Use a combination of the {type Random} and {type Each} abilities to + generate test cases for use by {verify}. + + Use {repeat} to repeat a test a certain number of times. This is + particularly useful for tests that generate random test cases: + + @typecheck ``` + verify do + repeat 100 + size1 = natIn 0 100 + size2 = natIn 0 100 + text1 = ofChars unicode size1 + text2 = ofChars unicode size2 + ensureEqual + (reverse text1 ++ reverse text2) (reverse (text2 ++ text1)) + ``` + + The above test generates 100 random test cases, each consisting of two + random {type Text} values, and checks that reversing the concatenation + of the two values is equal to the concatenation of the reversed values. + It's using {natIn} for the size of the {type Text} values, and {ofChars} + to generate the {type Text} values themselves. + + We can also use {type Each} to generate test cases that cover a range or + list of values instead of random values. For example, here's a test that + checks that converting a number to base 16 gives the expected result: + + @typecheck ``` + verify do + (n, c) = each (List.zip (Nat.range 10 16) (toCharList "ABCDEF")) + ensureEqual (Nat.toTextBase 16 n) (Some (Char.toText c)) + ``` + + See {type Random} for more information on generating random values and + {type Each} for more on repetition and ranges. + + A few convenience functions are provided for common test case generation + patterns: + + * {arbitrary.ints} - generate a specific number of random integers, + checking corner cases like 0, 1, -1, {maxInt}, and {minInt} first, + then generating random integers. + * {nats} - generate a specific number of random natural numbers, + checking corner cases like 0, 1, and {maxNat} first, then generating + random natural numbers. + * {arbitrary.floats} - generate a specific number of random + floating-point numbers, checking corner cases like 0.0, 1.0, -1.0, + {maxFloat}, {minFloat}, {Infinity}, and {NaN} first, then generating + random floating-point numbers. + * {unspecialFloats} - generate a specific number of random + floating-point numbers that are not NaN or infinity. Checks corner + cases first, then generates random floating-point numbers. + + ## Scoped labels for test failures + + You can use the {type Label} ability to add scoped labels to tests that + will be displayed in the results of failing tests. For example, here is + an erroneous test that fails where the labels help to identify the + problem: + + @typecheck ``` + verify do + labeled "Tests for empty Text" do + labeled "head" do ensureEqual (head "") None + labeled "isEmpty" do ensure (Text.isEmpty "") + labeled "size" do ensureEqual (Text.size "") 1 + ``` + + This test fails with: + + ``` raw + 🚫 FAILED + Tests for empty Text: + size: + elements not equal + (0, 1) + ``` + + Within a test you can use {label} to add a label to the test. The labels + will be displayed in the results of failing tests. For example: + + @typecheck ``` + verify do + labeled "check multiplication" do + x = nats 100 + y = nats 100 + label "x * y should be greater than x" (x, y) + ensureGreater (x * y) x + ``` + + Due to a false assumption in the test, it fails with: + + ``` raw + 🚫 FAILED + check multiplication: + x * y should be greater than x: (0, 0) + 0 is not greater than 0 + ``` + }} + +Either.bimap : (a ->{g} b) -> (c ->{g} d) -> Either a c ->{g} Either b d +Either.bimap f g = cases + Left a -> Left (f a) + Right b -> Right (g b) + +Either.bimap.doc : Doc +Either.bimap.doc = + use Either bimap + use Text toLowercase toUppercase + {{ + Transforms both sides of an {type Either} value using the given functions. + + # Examples + + ``` + bimap toUppercase toLowercase (Right "Hello") + ``` + + ``` + bimap toUppercase toLowercase (Left "Hello") + ``` + }} + +Either.doc : Doc +Either.doc = + use Either fold left + {{ + The {type Either} type is a general-purpose data type that represents values + with two possibilities. + + An {type Either} value is either a {Left} value or a {Right} value. + + The {type Either} type is often used to represent a value that is either + correct or an error; by convention, the {Left} constructor is used to hold an + error value and the {Right} constructor is used to hold a correct one. + + 📚 Guide: + [Error handling with data types](https://www.unison-lang.org/learn/fundamentals/control-flow/exception-handling/#either-success-or-failure) + + # Constructing {type Either} values + + You can construct an {type Either} value with the {Left} and {Right} + constructors: + + ``` + Left 1 + ``` + + ``` + Right 1 + ``` + + # Pattern matching on {type Either} values + + You can pattern match on {type Either} values with the {Left} and {Right} + constructors: + + ``` + match Left 1 with + Left e -> "It's a left!" + Right r -> "It's a right!" + ``` + + ``` + match Right 1 with + Left e -> "It's a left!" + Right r -> "It's a right!" + ``` + + {fold} is a general-purpose function for pattern matching on {type Either} + values, given two functions, one for the {Left} case and one for the + {Right} case: + + @signature{fold} + + Sometimes you just need to check if a value is {Left} or {Right}: + + @signatures{isLeft, isRight} + + # Converting {type Either} values to {type Optional} values + + Convert an {type Either} value to an {type Optional} value: + + ``` + left (Left 1) + ``` + + ``` + Either.right (Right 1) + ``` + + ``` + left (Right 1) + ``` + + # Operations related to error-handling + + Any {type Either} value with a {Left} value can be thrown into the + {type Throw} ability: + + @signature{Either.toThrow} + + Often a function will return an {type Either} value to indicate whether it + succeeded or failed. The {type Either} type provides a number of functions + for working with these values. + + If the {type Either} value is a {type Text} on the {Left}, you can raise an + {type Exception} with that {type Text} as the error message: + + @signature{raiseMessage} + + If the {type Either} value is a {type Failure} on the {Left}, you can raise + it as an {type Exception}: + + @signature{Either.toException} + + Guide: + [Error handling with abilities](https://www.unison-lang.org/learn/fundamentals/abilities/error-handling/) + + # Mapping over {type Either} values + + Transform a {Right} value with a function, if the {type Either} value is a + {Right}: + + @signature{Either.mapRight} + + Transform a {Left} value: + + @signature{Either.mapLeft} + + Map over a {Right} value with an {type Either}-returning function: + + @signature{Either.flatMapRight} + + Flatten a nested {type Either} value: + + @signature{flattenRight} + }} + +Either.flatMapRight : (a ->{g1} Either e b) -> Either e a ->{g1} Either e b +Either.flatMapRight f = cases + Left l -> Left l + Right a -> f a + +Either.flatMapRight.doc : Doc +Either.flatMapRight.doc = + use Either flatMapRight + use Nat + + {{ + Maps an {type Either}-valued function over the right side of an {type Either} + value. + + # Examples + + ``` + flatMapRight (x -> Right (x + 1)) (Right 1) + ``` + + ``` + flatMapRight (x -> Left (x + 1)) (Right 1) + ``` + + ``` + flatMapRight (x -> Right (x + 1)) (Left 1) + ``` + + # See also + + * {Either.mapRight} + * {Either.mapLeft} + }} + +Either.flattenRight : Either e (Either e a) -> Either e a +Either.flattenRight = Either.flatMapRight id + +Either.flattenRight.doc : Doc +Either.flattenRight.doc = + {{ + Flattens an {type Either} value that is nested on the {Right} side, by + removing one {Right} constructor from the outside. + + # Examples + + ``` + flattenRight (Right (Right 1)) + ``` + + ``` + flattenRight (Right (Left 1)) + ``` + + ``` + flattenRight (Left (Right (Left (Right 1)))) + ``` + }} + +Either.fold : (a ->{e} c) -> (b ->{e} c) -> Either a b ->{e} c +Either.fold l r = cases + Right v -> r v + Left v -> l v + +Either.fold.doc : Doc +Either.fold.doc = + {{ + `` Either.fold l r e `` applies either the function `l` or the function `r` + to the {type Either} `e`, depending on whether `e` is a {Left} or a {Right}. + }} + +Either.isLeft : Either a b -> Boolean +Either.isLeft = cases + Left _ -> true + Right _ -> false + +Either.isLeft.doc : Doc +Either.isLeft.doc = + {{ + Returns `` true `` if the {type Either} is a {Left} value, otherwise returns + `` false `` + + # Examples + + ``` + isLeft (Left "hello") + ``` + + ``` + isLeft (Right "world") + ``` + + # See also + + * {isRight} + }} + +Either.isRight : Either a b -> Boolean +Either.isRight = cases + Left _ -> false + Right _ -> true + +Either.isRight.doc : Doc +Either.isRight.doc = + {{ + Returns `` true `` if the {type Either} is a {Right} value, otherwise returns + `` false `` + + # Examples + + ``` + isRight (Right "hello") + ``` + + ``` + isRight (Left "world") + ``` + + # See also + + * {isLeft} + }} + +Either.left : Either l r -> Optional l +Either.left = Either.fold Some (_ -> None) + +Either.left.doc : Doc +Either.left.doc = + {{ + `` Either.left e `` returns `` Some v `` if `e` is ``Left v``, `` None `` + otherwise. + }} + +Either.Left.doc : Doc +Either.Left.doc = + use Nat / == + {{ + {Left} is one of two constructors of the {type Either} type. By convention, + the {Left} constructor is used to represent the failure case of a + computation, and the {Right} constructor is used to represent the success + case. + + # Example + + @typecheck ``` + safeDiv x y = if y == 0 then Left "division by zero" else Right (x / y) + ``` + }} + +Either.mapLeft : (a ->{𝕖} b) -> Either a z ->{𝕖} Either b z +Either.mapLeft f = cases + Left l -> Left (f l) + Right r -> Right r + +Either.mapLeft.doc : Doc +Either.mapLeft.doc = + {{ + Map a function over the value if the {type Either} is a {Left}. If it is + {Right}, change nothing. + }} + +test> Either.mapLeft.tests.ex1 = + check + let + input = [Left 2, Left 5, Right "Hiya"] + actual = List.map (Either.mapLeft Nat.toText) input + expected = [Left "2", Left "5", Right "Hiya"] + assert (actual === expected) ("Not equal!", actual, expected) true + +Either.mapRight : (a ->{𝕖} b) -> Either e a ->{𝕖} Either e b +Either.mapRight f = cases + Left l -> Left l + Right a -> Right (f a) + +Either.mapRight.doc : Doc +Either.mapRight.doc = + {{ + Map a function over the value if the @Either is a @Right, if it is @Left, + change nothing. + }} + +test> Either.mapRight.tests.ex1 = + use Text ++ + check + let + input = [Left 2, Left 5, Right "Hiya", Right "Bye", Left 10] + actual = List.map (Either.mapRight (s -> s ++ "!")) input + expected = [Left 2, Left 5, Right "Hiya!", Right "Bye!", Left 10] + assert (actual === expected) ("Not equal!", actual, expected) true + +Either.raiseMessage : v -> Either Text a ->{Exception} a +Either.raiseMessage v e = + Either.toException (Either.mapLeft (msg -> failure msg v) e) + +Either.raiseMessage.doc : Doc +Either.raiseMessage.doc = + {{ + Raise an {type Exception} if the {type Either} value is {Left}, otherwise + return the {Right} value. + + The first argument is an arbitrary value that will be included in the + {type Exception} if it is raised. + + # Examples + + @typecheck ``` + raiseMessage "Some useful data" (Left "oops") + ``` + + If you don't have any data to include in the {type Exception}, you can pass + in ``()``: + + @typecheck ``` + raiseMessage () (Left "oops") + ``` + }} + +Either.right : Either l r -> Optional r +Either.right = Either.fold (_ -> None) Some + +Either.Right.doc : Doc +Either.Right.doc = + use Nat / == + {{ + {Right} is one of two constructors of the {type Either} type. By convention + it is used to represent the success case of a computation that can fail, and + {Left} is used to represent the failure case. + + # Example + + @typecheck ``` + safeDiv x y = if y == 0 then Left "division by zero" else Right (x / y) + ``` + }} + +Either.right.doc : Doc +Either.right.doc = + {{ + `` Either.right e `` returns `` Some v `` if `e` is ``Right v``, {None} + otherwise. + }} + +Either.toAbort : Either a b ->{Abort} b +Either.toAbort = cases + Left e -> abort + Right x -> x + +Either.toAbort.doc : Doc +Either.toAbort.doc = + use Either toAbort + {{ + Calls {abort} if the {type Either} is {Left}, otherwise returns the {Right} + value. + + # Examples + + ``` + toOptional! do toAbort (Left "error") + ``` + + ``` + toOptional! do toAbort (Right 42) + ``` + }} + +Either.toBug : Either e a -> a +Either.toBug = cases + Left e -> bug e + Right a -> a + +Either.toBug.doc : Doc +Either.toBug.doc = + {{ + Matches on an {type Either} value and calls {bug} with its value if it is a + {Left}. + + # Example + + @typecheck ``` + Either.toBug (Left "oops") + ``` + }} + +Either.toException : Either Failure a ->{Exception} a +Either.toException = cases + Left e -> Exception.raise e + Right a -> a + +Either.toException.doc : Doc +Either.toException.doc = + use Either toException + {{ + {toException} translates an `Either Failure a` value into the + {type Exception} ability. A {Left} enclosing a {type Failure} will trigger a + call to {Exception.raise}, and a `Right a` will return the unwrapped value + `a`. + + @typecheck ``` + toException (Left (failure "failure" ())) + ``` + + @typecheck ``` + toException (Right "success") + ``` + }} + +Either.toOptional : Either a b -> Optional b +Either.toOptional = cases + Left _ -> None + Right b -> Some b + +Either.toOptional.doc : Doc +Either.toOptional.doc = + use Either toOptional + {{ + Converts {Left} to {None} and {Right} to {Some}. + + ``` + [toOptional (Left "️🌸"), toOptional (Right 42)] + ``` + }} + +Either.toThrow : Either e a ->{Throw e} a +Either.toThrow = cases + Left e -> throw e + Right a -> a + +Either.toThrow.doc : Doc +Either.toThrow.doc = + use Nat / == + {{ + Extracts the {Right} value from an {type Either} or throws the {Left} value + using the {type Throw} ability. + + # Example + + @typecheck ``` + safeDiv : Nat -> Nat ->{Throw Text} Nat + safeDiv x y = if y == 0 then throw "Division by zero" else x / y + ``` + }} + +Either.unwrap : Either a a -> a +Either.unwrap = Either.fold id id + +Either.unwrap.doc : Doc +Either.unwrap.doc = + {{ + Given `` Left v `` or ``Right v``, {Either.unwrap} returns the wrapped value + `v` (ignoring the distinction between {Left} and {Right}. + }} + +(Float.!=) : Float -> Float -> Boolean +x Float.!= y = + use Float == + Boolean.not (x == y) + +-- builtin Float.* : Float -> Float -> Float + +-- builtin Float.+ : Float -> Float -> Float + +-- builtin Float.- : Float -> Float -> Float + +-- builtin Float./ : Float -> Float -> Float + +-- builtin Float.< : Float -> Float -> Boolean + +-- builtin Float.<= : Float -> Float -> Boolean + +-- builtin Float.== : Float -> Float -> Boolean + +-- builtin Float.> : Float -> Float -> Boolean + +-- builtin Float.>= : Float -> Float -> Boolean + +-- builtin Float.abs : Float -> Float + +Float.abs.doc : Doc +Float.abs.doc = + use Float abs + {{ + `` abs n `` returns the __absolute value__ or __modulus__ of the number `n`, + which is its distance from ``0.0``. + + ``` + abs 93.0 + ``` + + ``` + abs -4.2 + ``` + }} + +-- builtin Float.acos : Float -> Float + +Float.acos.doc : Doc +Float.acos.doc = + {{ `` acos `` is the arccosine function, the inverse of {cos}. }} + +-- builtin Float.acosh : Float -> Float + +Float.acosh.doc : Doc +Float.acosh.doc = + {{ + `` acosh `` is the inverse hyperbolic cosine function, the inverse of {cosh}. + }} + +-- builtin Float.asin : Float -> Float + +Float.asin.doc : Doc +Float.asin.doc = + {{ `` asin `` is the arcsine function, the inverse of {sin}. }} + +-- builtin Float.asinh : Float -> Float + +Float.asinh.doc : Doc +Float.asinh.doc = + {{ + `` asinh `` is the inverse hyperbolic sine function, the inverse of {sinh}. + }} + +-- builtin Float.atan : Float -> Float + +Float.atan.doc : Doc +Float.atan.doc = + {{ `` atan `` is the arctangent function, the inverse of {tan}. }} + +-- builtin Float.atan2 : Float -> Float -> Float + +Float.atan2.doc : Doc +Float.atan2.doc = + {{ + `` atan2 x y `` is the + [2-argument arctangent function](https://en.wikipedia.org/wiki/Atan2), + defined as the angle in radians between the positive x axis and the ray to + the point `(x,y)`. + }} + +-- builtin Float.atanh : Float -> Float + +Float.atanh.doc : Doc +Float.atanh.doc = + {{ + `` atanh `` is the inverse hyperbolic tangent function, the inverse of + {tanh}. + }} + +Float.ceiling : Float -> Float +Float.ceiling x = + use Float != + <= == >= fromInt + if x >= fromInt maxInt || x <= fromInt minInt || x != x then x + else + n = unsafeToInt x + d = fromInt n + if d == x || x <= 0.0 then d else d + 1.0 + +-- builtin Float.ceiling.deprecated : Float -> Int + +Float.ceiling.doc : Doc +Float.ceiling.doc = + use Float ceiling + {{ + `` ceiling x `` rounds `x` up to the next whole number. That is, it returns + the smallest whole number that is at least as big as `x`. + + ``` + ceiling 0.1 + ``` + + ``` + ceiling -9.9 + ``` + }} + +test> Float.ceiling.tests.adjunction = runs 100 do + use Float <= + x = gen.float() + y = Float.fromInt gen.int() + expect (Float.ceiling x <= y === x <= y) + +Float.clamp : Float -> Float -> Float -> Float +Float.clamp low hi x = Float.min hi (Float.max low x) + +Float.clamp.doc : Doc +Float.clamp.doc = + use Float clamp + {{ + `` clamp lo hi x `` clamps value `x` between `lo` and `hi`. The result is + `lo` if `x` is less than `lo`, `hi` if `x` is greater than `hi`, and `x` + otherwise. + + # Examples + + ``` + clamp 0.0 10.0 1.0 + ``` + + ``` + clamp 0.0 10.0 11.0 + ``` + + ``` + clamp 0.0 10.0 10.0 + ``` + + ``` + clamp 0.0 10.0 -10.0 + ``` + }} + +test> Float.clamp.test = test.verify do + use Float + + _ = Each.range 0 100 + low = 0.0 + hi = 10.0 + x = Random.nat() |> Float.fromNat + result = Float.clamp low hi x + ensureWith result (Float.inRange low (hi + 1.0) result) + +Float.components : Float ->{Throw Text} (Int, Nat) +Float.components f = + use Float < + use Int - + use Nat != == + exp = rawExponent f + man = rawMantissa f + if exp == 2047 then + if man != 0 then throw "NaN" + else if f < 0.0 then throw "-Infinity" else throw "Infinity" + else + if exp != 0 then (Nat.toInt exp - +1023, Nat.or man (Nat.shiftLeft 1 52)) + else (-1022, man) + +Float.components.doc : Doc +Float.components.doc = + use Float * + use Int - + {{ + Extracts the exponent and mantissa from a {type Float} value, as a pair {{ + docExample 1 do p -> (p : (Int, Nat)) }} representing the floats order of + magnitude and significant digits, respectively. + + The {type Float} can be reconstructed from the components as: + + ``` + toEither do + (exponent, mantissa) = components 47.2 + Float.fromNat mantissa * Float.pow 2.0 (Float.fromInt (exponent - +52)) + ``` + + # Examples + + ``` + toEither do components 0.0 + ``` + + ``` + toEither do components 1.6777216e7 + ``` + + ``` + toEither do components (ulp 0.0) + ``` + + ``` + toEither do components pi + ``` + + ``` + toEither do components NaN + ``` + + ``` + toEither do components NegativeInfinity + ``` + + ``` + toEither do components Infinity + ``` + }} + +-- builtin Float.cos : Float -> Float + +Float.cos.doc : Doc +Float.cos.doc = + {{ + `` cos `` is the + [cosine function](https://en.wikipedia.org/wiki/Sine_and_cosine). + }} + +-- builtin Float.cosh : Float -> Float + +Float.cosh.doc : Doc +Float.cosh.doc = + {{ + `` cos `` is the + [hyperbolic cosine function](https://en.wikipedia.org/wiki/Hyperbolic_functions). + }} + +Float.doc : Doc +Float.doc = + use Float * + / < <= == > >= fromInt fromNat fromRepresentation fromText max min pow + {{ + {type Float} is the type of 64-bit floating point numbers, conforming to the + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) standard for + double-precision numbers. {type Float} is built into Unison. + + # Constructing floating point numbers + + ## Literal syntax + + You can construct {type Float} values using literal syntax. For example, + these are valid {type Float} values: + + * `` 3.0e-3 `` + * `` -99.0 `` + * `` 1.6777216e7 `` + + A literal {type Float} consists of an optional sign (either `+` or `-`), + followed by two natural numbers separated by a decimal point (''.''), + followed by an optional exponent (''e'' followed by another natural + number). The whole number part, the decimal point, and the fractional + part are all required (for example `.3` or `4.` are not valid + {type Float} literals). + + {fromRepresentation} constructs a {type Float} from its representation + as a 64-bit word of type {type Nat}: + + ``` + fromRepresentation 4614256656552045848 + ``` + + {fromText} constructs a {type Float} from any valid {type Float} literal + syntax as a {type Text} value: + + ``` + fromText "3.141592653589793e10" + ``` + + {fromNat} and {fromInt} convert a {type Nat} or a {type Int}, + respectively, to {type Float}: + + ``` + fromNat maxNat + ``` + + ``` + fromInt minInt + ``` + + ## Special values + + `` NaN `` is a special {type Float} that is __not a number__, standing + for the result of a floating point calculation that is undefined or + unrepresentable. + + `` Infinity `` is a special {type Float} representing positive infinity. + + `` NegativeInfinity `` is a special {type Float} representing negative + infinity. + + `` maxFloat `` is the largest representable {type Float} that is not + ``Infinity``. + + `` minFloat `` is the smallest representable {type Float} that is not + ``NegativeInfinity``. + + You can test for these special values using {isInfinity}, + {isNegativeInfinity}, and {isNaN} + + # Floating point arithmetic + + You can add, multiply, subtract, and divide {type Float} numbers: + + ``` + (1.0 + 2.0 * 3.0) / 2.0 + ``` + + Note that Unison has no + [order of operations or operator precedence](https://en.wikipedia.org/wiki/Order_of_operations) + rules, so parentheses are necessary. All binary operators associate to the + left: + + ``` + (1.0 + 2.0) * 3.0 / 2.0 + ``` + + ## Exponentiation + + `` pow x y `` raises `x` to the power of `n`: + + ``` + pow 2.0 0.5 + pow 3.0 0.5 + ``` + + # Comparing floating point numbers + + `` a <= b `` is `` true `` if `a` is at most `b`, and `` false `` + otherwise. + + ``` + 2.0 <= 2.1 + ``` + + `` a >= b `` is `` true `` if `a` is at least `b`, and `` false `` + otherwise. + + ``` + 0.0 >= -0.0 + ``` + + `` a > b `` is `` true `` if `a` is strictly above `b`, and `` false `` + otherwise: + + ``` + 1.0 > 0.999 + ``` + + `` a < b `` is `` true `` if `a` is strictly below `b`, and `` false `` + otherwise. + + ``` + 0.999 < 1.0 + ``` + + `` min a b `` returns the smaller of the two numbers `a` and `b`. + + ``` + min -4.2 9.6 + ``` + + `` max a b `` returns the larger of the two numbers `a` and `b`. + + ``` + max -4.2 0.46 + ``` + + ## Equality of floating point numbers + + `` (==) `` can be used to check if two {type Float} values are exactly + equal. + + Note that when comparing the results of two floating point calculations + for equality, `` (==) `` might return `` true `` simply because the two + results coincide due to a rounding error, not necessarily because they + are in fact equal. + + The problem of fitting a fractional number into 64 bits can also lead to + surprising results where things that seem like they should be equal end + up being different. For example, `` 0.1 + 0.2 `` is not equal to + ``0.3``: + + ``` + 0.1 + 0.2 + ``` + + {{ + docCallout + (Some {{ ⚠️ }}) + {{ + Checking whether two floating point numbers are exactly equal is a + common source of bugs. It's often better to use approximate equality + (see below). + }} }} + + You can use `` (===) `` to check whether two {type Float} values have + the same 64-bit representation. Note that this does not coincide with `` + (==) `` in the case of ``NaN``, ``0.0``, and ``-0.0``. + + ### Approximate equality + + {type Float} provides two ways of checking if two numbers are + __nearly equal__: + + 1. Equality up to __epsilon__ + 2. Equality up to __the unit of least precision__ + + #### Equality up to epsilon + + @signature{equalUpTo} + + `` equalUpTo epsilon x y `` is an approximate equality check. + It returns `` true `` if the difference between `x` and `y` is + at most some number `epsilon`. + + For example, we can select a small `epsilon` value to compare + `` 0.2 + 0.1 `` to ``0.3``: + + ``` + equalUpTo 1.0e-3 (0.2 + 0.1) 0.3 + ``` + + There's no one "best" `epsilon` value. The smallest difference + that matters depends on your application. + + ### Unit of least precision + + @signature{withinULPs} + + `` withinULPs slop x y `` is another way of doing approximate + equality checks. It returns `` true `` if the difference between `x` + and `y` is at most `slop` times the __unit of least precision__ (see + {ulp}) at either `x` or `y`. + + For example, `` 0.2 + 0.1 `` doesn't exactly equal `` 0.3 `` + + ``` + withinULPs 0 (0.2 + 0.1) 0.3 + ``` + + But it's within 1 ULP of ``0.3``: + + ``` + withinULPs 1 (0.2 + 0.1) 0.3 + ``` + + # Numerical functions + + ## @signature{Float.abs} + + {{ Float.abs.doc }} + + ## @signature{Float.ceiling} + + {{ Float.ceiling.doc }} + + ## @signature{Float.floor} + + {{ Float.floor.doc }} + + ## @signature{exp} + + {{ exp.doc }} + + ## @signature{log} + + {{ log.doc }} + + ## @signature{logBase} + + {{ logBase.doc }} + + ## @signature{Float.negate} + + {{ Float.negate.doc }} + + ## @signature{Float.round} + + {{ Float.round.doc }} + + ## @signature{sqrt} + + {{ sqrt.doc }} + + ## @signature{truncate} + + {{ truncate.doc }} + + # Trigonometric functions + + ## @signature{acos} + + {{ acos.doc }} + + ## @signature{acosh} + + {{ acosh.doc }} + + ## @signature{asin} + + {{ asin.doc }} + + ## @signature{asinh} + + {{ asinh.doc }} + + ## @signature{atan} + + {{ atan.doc }} + + ## @signature{atan2} + + {{ atan2.doc }} + + ## @signature{atanh} + + {{ atanh.doc }} + + ## @signature{cos} + + {{ cos.doc }} + + ## @signature{cosh} + + {{ cosh.doc }} + + ## @signature{sin} + + {{ sin.doc }} + + ## @signature{sinh} + + {{ sinh.doc }} + + ## @signature{tan} + + {{ tan.doc }} + + ## @signature{tanh} + + {{ tanh.doc }} + }} + +Float.e : Float +Float.e = 2.718281828459045 + +Float.e.doc : Doc +Float.e.doc = + {{ + [Euler's Number (e)](https://en.wikipedia.org/wiki/Euler's_Number), the base + of the natural logarithm. This constant is accurate to about 15 decimal + digits. + }} + +Float.emod : Float -> Float ->{Exception} Float +Float.emod n d = + use Float + mod + d' = Float.abs d + mod (mod n d' + d') d' + +Float.emod.doc : Doc +Float.emod.doc = + use Float - emod + {{ + `` emod x y `` gets the Euclidean modulus of dividing `x` by `y`. The result + is ``Float.abs (truncate (x / y) * y - x)``. + + This modulus operation differs from {Float.mod} in that the result is always + a positive {type Float}. + + # Examples + + ``` + catch do emod 9.0 3.0 + ``` + + ``` + catch do emod 10.0 3.0 + ``` + + ``` + catch do emod -10.0 3.0 + ``` + + ``` + catch do emod 10.0 -3.0 + ``` + + ``` + catch do emod -10.0 -3.0 + ``` + + ``` + catch do emod 10.25 0.5 + ``` + + ``` + catch do emod -10.25 0.5 + ``` + + # Special cases + + ``` + catch do emod 10.0 0.0 + ``` + + ``` + catch do emod NaN 1.0 + ``` + + ``` + catch do emod 10.0 NaN + ``` + + ``` + catch do emod Infinity 1.0 + ``` + + ``` + catch do emod NegativeInfinity 1.0 + ``` + + ``` + catch do emod 10.0 Infinity + ``` + + ``` + catch do emod 10.0 NegativeInfinity + ``` + + ## See also: + + * {Float.safeEmod} - A version of {emod} that returns an {type Optional} + {type Float} instead of throwing an exception. + }} + +test> Float.emod.truncatesTowardsZero = test.verify do + use Float * - / + use Random float + x = float() * 1000.0 + y = float() * 1000.0 + ensure (Float.abs (truncate (x / y) * y - x) === Float.emod x y) + +Float.eq.doc : Doc +Float.eq.doc = + use Float + eq + {{ + Returns `` true `` if the two {type Float} values are equal, and `` false `` + otherwise. + + {{ + docCallout + (Some {{ ⚠️ }}) + {{ + Checking whether two floating point numbers are exactly equal is a common + source of bugs. It's usually better to check for approximate equality using + {equalUpTo} or {withinULPs} instead. + }} }} + + # Examples + + ``` + eq 1.0 1.0 + ``` + + ``` + eq 1.0 2.0 + ``` + + Note that the following example returns `` false `` because floating point + arithmetic is not exact: + + ``` + eq (0.1 + 0.2) 0.3 + ``` + }} + +Float.equalUpTo : Float -> Float -> Float -> Boolean +Float.equalUpTo epsilon x y = + use Float - <= + z = Float.abs (x - y) + z <= epsilon + +Float.equalUpTo.doc : Doc +Float.equalUpTo.doc = + use Float + + {{ + `` equalUpTo epsilon x y `` checks if `x` and `y` are within `epsilon` of + each other. + + Since {type Float} values accumulate rounding errors, comparing them for + precise equality is generally a bad idea. Instead, use `` equalUpTo `` to + check whether the difference between two values is within some error bound + `epsilon`. + + # Example + + These two ways of computing `` 0.3 `` are not exactly equivalent — one of + them has a small rounding error: + + ``` + equalUpTo 0.0 (0.1 + 0.2) (0.15 + 0.15) + ``` + + If we allow for a small error bound, we find that they are close enough to + equal: + + ``` + equalUpTo 1.0e-16 (0.1 + 0.2) (0.15 + 0.15) + ``` + }} + +-- builtin Float.exp : Float -> Float + +Float.exp.doc : Doc +Float.exp.doc = + {{ + `` exp `` is the + [natural exponential function](https://en.wikipedia.org/wiki/Exponential_function) + whose inverse is {log}, the natural logaritm. + }} + +Float.floor : Float -> Float +Float.floor x = + use Float != - <= == >= fromInt + if x >= fromInt maxInt || x <= fromInt minInt || x != x then x + else + n = unsafeToInt x + d = fromInt n + if d == x || x >= 0.0 then d else d - 1.0 + +-- builtin Float.floor.deprecated : Float -> Int + +Float.floor.doc : Doc +Float.floor.doc = + use Float floor + {{ + `` floor x `` rounds `x` down to the nearest whole number. That is, it + returns the largest whole number that is at most `x`. + + ``` + floor 0.1 + ``` + + ``` + floor -9.9 + ``` + }} + +test> Float.floor.tests.adjunction = runs 100 do + use Float <= + x = gen.float() + y = Float.fromInt gen.int() + expect (x <= y === x <= Float.floor y) + +Float.fromHalfPrecision : Nat -> Float +Float.fromHalfPrecision half = + use Nat + - == and or shiftLeft shiftRight + s = and 1 (shiftRight half 15) + e = shiftRight half 10 |> and 31 + m = and half 1023 + let + (e', m') = + if e == 0 then + if m == 0 then (0, 0) + else + lz = Nat.leadingZeros m - 54 + newM = and 1023 (shiftLeft m (lz + 1)) + (1008 - lz, shiftLeft newM 42) + else + if e == 31 then (2047, shiftLeft m 42) else (1008 + e, shiftLeft m 42) + signBit = shiftLeft s 63 + exponentBits = shiftLeft e' 52 + significandBits = m' + double = or signBit (or exponentBits significandBits) + Float.fromRepresentation double + +Float.fromHalfPrecision.doc : Doc +Float.fromHalfPrecision.doc = + {{ + Converts a 16-bit half-precision floating point number encoded in the low 16 + bits of a {type Nat}, to a 64-bit double-precision floating point number. + + # Examples + + ``` + fromHalfPrecision 15360 + ``` + + ``` + fromHalfPrecision 1023 + ``` + + ``` + fromHalfPrecision 31744 + ``` + }} + +test> Float.fromHalfPrecision.tests.largestSubnormal = + use Nat == + check + (Float.toRepresentation (fromHalfPrecision 1023) == 4544123227923808256) + +test> Float.fromHalfPrecision.tests.maxHalf = + use Nat == + check + (Float.toRepresentation (fromHalfPrecision 31743) == 4679235614791434240) + +test> Float.fromHalfPrecision.tests.negativeOne = + use Nat == + check + (Float.toRepresentation (fromHalfPrecision 48128) == 13830554455654793216) + +test> Float.fromHalfPrecision.tests.negativeZero = + use Nat == + check + (Float.toRepresentation (fromHalfPrecision 32768) == 9223372036854775808) + +test> Float.fromHalfPrecision.tests.normal = + use Nat == + check + (Float.toRepresentation (fromHalfPrecision 20902) == 4631556392564555776) + +test> Float.fromHalfPrecision.tests.notANumber = + use Nat == + check + (Float.toRepresentation (fromHalfPrecision 32256) == 9221120237041090560) + +test> Float.fromHalfPrecision.tests.positiveOne = + use Nat == + check + (Float.toRepresentation (fromHalfPrecision 15360) == 4607182418800017408) + +test> Float.fromHalfPrecision.tests.positiveZero = + use Nat == + check (Float.toRepresentation (fromHalfPrecision 0) == 0) + +test> Float.fromHalfPrecision.tests.smallestNormal = + use Nat == + check + (Float.toRepresentation (fromHalfPrecision 1024) == 4544132024016830464) + +test> Float.fromHalfPrecision.tests.subnormal = + use Nat == + check (Float.toRepresentation (fromHalfPrecision 422) == 4538045127645462528) + +-- builtin Float.fromInt : Int -> Float + +Float.fromInt.doc : Doc +Float.fromInt.doc = + use Float fromInt + {{ + Convert an {type Int} to a {type Float}. + + # Examples + + ``` + fromInt +3 + ``` + + ``` + fromInt -3 + ``` + + ``` + fromInt maxInt + ``` + }} + +-- builtin Float.fromNat : Nat -> Float + +Float.fromNat.doc : Doc +Float.fromNat.doc = + use Float fromNat + {{ + Convert a {type Nat} to a {type Float}. + + # Examples + + ``` + fromNat 3 + ``` + + ``` + fromNat maxNat + ``` + }} + +-- builtin Float.fromRepresentation : Nat -> Float + +Float.fromRepresentation.doc : Doc +Float.fromRepresentation.doc = + {{ + Creates a {type Float} from its underlying representation as a 64-bit word of + type {type Nat}. + }} + +Float.fromSinglePrecision : Nat -> Float +Float.fromSinglePrecision n = + use Nat + - == and or shiftLeft shiftRight + s = and 1 (shiftRight n 31) + e = shiftRight n 23 |> and 255 + m = and n 8388607 + let + (e', m') = + if e == 0 then + if m == 0 then (0, 0) + else + lz = Nat.leadingZeros m - 41 + newM = and 8388607 (shiftLeft m (lz + 1)) + (896 - lz, shiftLeft newM 29) + else + if e == 255 then (2047, shiftLeft m 29) else (896 + e, shiftLeft m 29) + signBit = shiftLeft s 63 + exponentBits = shiftLeft e' 52 + significandBits = m' + double = or signBit (or exponentBits significandBits) + Float.fromRepresentation double + +Float.fromSinglePrecision.doc : Doc +Float.fromSinglePrecision.doc = + {{ + Converts a 32-bit single-precision floating point number encoded in the low + 32 bits of a {type Nat}, to a 64-bit double-precision floating point number. + + # Examples + + ``` + fromSinglePrecision 1065353216 + ``` + + ``` + fromSinglePrecision 1056964608 + ``` + + ``` + fromSinglePrecision 1048576000 + ``` + }} + +test> Float.fromSinglePrecision.tests.largestSubnormal = + use Nat == + check + (Float.toRepresentation (fromSinglePrecision 8388607) + == 4039728864677593088) + +test> Float.fromSinglePrecision.tests.maxSingle = + use Nat == + check + (Float.toRepresentation (fromSinglePrecision 2139095039) + == 5183643170566569984) + +test> Float.fromSinglePrecision.tests.negativeOne = + use Nat == + check + (Float.toRepresentation (fromSinglePrecision 3212836864) + == 13830554455654793216) + +test> Float.fromSinglePrecision.tests.negativeZero = + use Nat == + check + (Float.toRepresentation (fromSinglePrecision 2147483648) + == 9223372036854775808) + +test> Float.fromSinglePrecision.tests.normal = + use Nat == + check + (Float.toRepresentation (fromSinglePrecision 1089469440) + == 4620129717972893696) + +test> Float.fromSinglePrecision.tests.notANumber = + use Nat == + check + (Float.toRepresentation (fromSinglePrecision 2139095040) + == 9218868437227405312) + +test> Float.fromSinglePrecision.tests.positiveOne = + use Nat == + check + (Float.toRepresentation (fromSinglePrecision 1065353216) + == 4607182418800017408) + +test> Float.fromSinglePrecision.tests.positiveZero = + use Nat == + check (Float.toRepresentation (fromSinglePrecision 0) == 0) + +test> Float.fromSinglePrecision.tests.smallestNormal = + use Nat == + check + (Float.toRepresentation (fromSinglePrecision 8388608) + == 4039728865751334912) + +test> Float.fromSinglePrecision.tests.subnormal = + use Nat == + check + (Float.toRepresentation (fromSinglePrecision 5119746) + == 4036218951905050624) + +-- builtin Float.fromText : Text -> Optional Float + +Float.fromText.doc : Doc +Float.fromText.doc = + use Float fromText + {{ + Parses a {type Text} as a {type Float}. Returns {None} if the text is not a + valid {type Float}. + + # Example + + ``` + fromText "3.14" + ``` + + ``` + fromText "NaN" + ``` + + ``` + fromText "Infinity" + ``` + + ``` + fromText "-Infinity" + ``` + + ``` + fromText "3.14.15" + ``` + }} + +Float.gt.doc : Doc +Float.gt.doc = + use Float gt + {{ + Returns `` true `` if the first {type Float} value is greater than the + second, and `` false `` otherwise. + + # Examples + + ``` + gt 1.0 2.0 + ``` + + ``` + gt 2.0 1.0 + ``` + + ``` + gt 1.0 1.0 + ``` + }} + +Float.gteq.doc : Doc +Float.gteq.doc = + use Float gteq + {{ + Returns `` true `` if the first {type Float} value is greater than or equal + to the second, and `` false `` otherwise. + + # Examples + + ``` + gteq 1.0 2.0 + ``` + + ``` + gteq 2.0 1.0 + ``` + + ``` + gteq 1.0 1.0 + ``` + }} + +Float.Infinity : Float +Float.Infinity = + use Float / + 1.0 / 0.0 + +Float.Infinity.doc : Doc +Float.Infinity.doc = {{ The positive infinity value. }} + +Float.inRange : Float -> Float -> Float -> Boolean +Float.inRange fromInclusive toExclusive n = + use Float < >= + n >= fromInclusive && n < toExclusive + +Float.inRange.doc : Doc +Float.inRange.doc = + use Float inRange + {{ + `` inRange x y n `` returns `` true `` if `n` is between `x` (inclusive) and + `y` (exclusive). + + # Examples + + ``` + inRange 0.0 1.0 0.5 + ``` + + ``` + inRange NegativeInfinity Infinity 1.0 + ``` + + ``` + inRange -1.0 1.0 2.0 + ``` + }} + +Float.isInfinity : Float -> Boolean +Float.isInfinity f = + rawExponent f === 2047 && rawMantissa f === 0 && signBit f === 0 + +Float.isInfinity.doc : Doc +Float.isInfinity.doc = + {{ + {isInfinity} returns `` true `` if its argument is {Infinity}, and `` false + `` otherwise. + }} + +Float.isNaN : Float -> Boolean +Float.isNaN f = rawExponent f === 2047 && Boolean.not (rawMantissa f === 0) + +Float.isNaN.doc : Doc +Float.isNaN.doc = + {{ + {isNaN} returns `` true `` if its argument is the {NaN} value, and `` false + `` otherwise. + }} + +Float.isNegativeInfinity : Float -> Boolean +Float.isNegativeInfinity f = + rawExponent f === 2047 && rawMantissa f === 0 && signBit f === 1 + +Float.isNegativeInfinity.doc : Doc +Float.isNegativeInfinity.doc = + {{ + {isNegativeInfinity} returns `` true `` if its argument is + {NegativeInfinity}, and `` false `` otherwise. + }} + +-- builtin Float.log : Float -> Float + +Float.log.doc : Doc +Float.log.doc = + {{ + `` log `` is the + [natural logarithm](https://en.wikipedia.org/wiki/Natural_logarithm). + }} + +-- builtin Float.logBase : Float -> Float -> Float + +Float.logBase.doc : Doc +Float.logBase.doc = + {{ + `` logBase n x `` is the logarithm base `n` of `x`, the exponent to which `n` + must be raised to produce `x`. + + ``` + logBase 2.0 256.0 + ``` + }} + +Float.lt.doc : Doc +Float.lt.doc = + use Float lt + {{ + Returns `` true `` if the first {type Float} value is less than the second, + and `` false `` otherwise. + + # Examples + + ``` + lt 1.0 2.0 + ``` + + ``` + lt 2.0 1.0 + ``` + + ``` + lt 1.0 1.0 + ``` + }} + +Float.lteq.doc : Doc +Float.lteq.doc = + use Float lteq + {{ + Returns `` true `` if the first {type Float} value is less than or equal to + the second, and `` false `` otherwise. + + # Examples + + ``` + lteq 1.0 2.0 + ``` + + ``` + lteq 2.0 1.0 + ``` + + ``` + lteq 1.0 1.0 + ``` + }} + +-- builtin Float.max : Float -> Float -> Float + +Float.max.doc : Doc +Float.max.doc = + use Float max + {{ + Returns the maximum of the two {type Float} values. + + # Examples + + ``` + max 1.0 2.0 + ``` + + ``` + max 2.0 1.0 + ``` + + ``` + max 1.0 1.0 + ``` + }} + +Float.maxFloat : Float +Float.maxFloat = 1.7976931348623157e308 + +Float.maxFloat.doc : Doc +Float.maxFloat.doc = + {{ The largest possible value of a {type Float} that isn't `` Infinity `` }} + +-- builtin Float.min : Float -> Float -> Float + +Float.min.doc : Doc +Float.min.doc = + use Float min + {{ + Returns the minimum of the two {type Float} values. + + # Examples + + ``` + min 1.0 2.0 + ``` + + ``` + min 2.0 1.0 + ``` + + ``` + min 1.0 1.0 + ``` + }} + +Float.minFloat : Float +Float.minFloat = -1.7976931348623157e308 + +Float.minFloat.doc : Doc +Float.minFloat.doc = + {{ + The smallest possible value of a {type Float}, that isn't negative `` + Infinity `` + }} + +Float.mod : Float -> Float ->{Exception} Float +Float.mod = cases + n, d + | isNegativeInfinity n || isNegativeInfinity d -> negativeInfinity() + | isInfinity n || isInfinity d -> positiveInfinity() + | isNaN n || isNaN d -> + ArithmeticException.notANumber() + | d Float.== 0.0 -> dividedByZero() + | n Float.== 0.0 -> Float.abs n + | otherwise -> + match n Float.- Float.floor (n Float./ d) Float.* d with + rem + | rem Float.== 0.0 -> Float.abs rem + | otherwise -> rem + +Float.mod.doc : Doc +Float.mod.doc = + use Float * - / mod + {{ + `` mod x y `` gets the modulus of a floored division of `x` by `y`. A formula + for the result is ``x - Float.floor (x / y) * y``. + + The result of this function is not defined if the divisor (the second + arument) is ``0.0``, or if either argument is `` NaN `` or infinite. + + # Examples + + `` -3.0 `` divides `` 10.0 `` three times, with a remainder of ``1.0``: + + ``` + catch do mod 10.0 3.0 + ``` + + The floored division of `` 10.0 `` by `` -3.0 `` is ``-4.0``, and `` -3.0 * + -4.0 `` is ``12.0``, so the remainder is ``-2.0``: + + ``` + catch do mod 10.0 -3.0 + ``` + + The floored division of `` -10.0 `` by `` 3.0 `` is ``-4.0``, and `` 3.0 * + -4.0 `` is ``-12.0``, so the remainder is ``2.0``: + + ``` + catch do mod -10.0 3.0 + ``` + + The floored division of `` -10.0 `` by `` -3.0 `` is ``3.0``, and `` -3.0 * + 3.0 `` is ``-9.0``, so the remainder is ``-1.0``: + + ``` + catch do mod -10.0 -3.0 + ``` + + The floored division of `` 10.25 `` by `` 0.5 `` is ``20.0``, and `` 0.5 * + 20.0 `` is ``10.0``, so the remainder is ``0.25``: + + ``` + catch do mod 10.25 0.5 + ``` + + # Special cases + + ``` + catch do mod 10.0 0.0 + ``` + + ``` + catch do mod NaN 1.0 + ``` + + ``` + catch do mod 10.0 NaN + ``` + + ``` + catch do mod Infinity 1.0 + ``` + + ``` + catch do mod NegativeInfinity 1.0 + ``` + + ``` + catch do mod 10.0 Infinity + ``` + + ``` + catch do mod 10.0 NegativeInfinity + ``` + + ## See also: + + * {Float.safeMod} + }} + +Float.NaN : Float +Float.NaN = + use Float / + 0.0 / 0.0 + +Float.NaN.doc : Doc +Float.NaN.doc = {{ A {type Float} that is not a number. }} + +Float.negate : Float -> Float +Float.negate x = + Float.fromRepresentation + (Nat.xor (Float.toRepresentation x) 9223372036854775808) + +Float.negate.doc : Doc +Float.negate.doc = + use Float negate + {{ + `` negate x `` flips the sign of `x` so if it's positive, it becomes + negative, and vice versa. + + # Examples + + ``` + negate 0.0 + ``` + + ``` + negate -1.0 + ``` + }} + +Float.NegativeInfinity : Float +Float.NegativeInfinity = + use Float / + -1.0 / 0.0 + +Float.NegativeInfinity.doc : Doc +Float.NegativeInfinity.doc = + {{ The negative infinity value of type {type Float}. }} + +Float.neq.doc : Doc +Float.neq.doc = + use Float + neq + {{ + Returns `` true `` if the two {type Float} values are not equal, and `` false + `` otherwise. + + {{ + docCallout + (Some {{ ⚠️ }}) + {{ + Checking whether two floating point numbers are exactly equal is a common + source of bugs. It's usually better to check for approximate equality using + {equalUpTo} or {withinULPs} instead. + }} }} + + # Examples + + ``` + neq 1.0 1.0 + ``` + + ``` + neq 1.0 2.0 + ``` + + Note that the following example returns `` true `` because floating point + arithmetic is not exact: + + ``` + neq (0.1 + 0.2) 0.3 + ``` + }} + +Float.pi : Float +Float.pi = 3.141592653589793 + +Float.pi.doc : Doc +Float.pi.doc = + {{ + The constant [Pi (π)](https://en.wikipedia.org/wiki/Pi), which is the ratio + of a circle's circumference to its diameter. This constant is accurate to + about 16 decimal digits. + }} + +-- builtin Float.pow : Float -> Float -> Float + +Float.pow.doc : Doc +Float.pow.doc = + use Float pow + {{ + `` pow x n `` is the number `x` raised to the power of `n`. + + ``` + pow 2.0 8.0 + ``` + }} + +Float.product : [Float] -> Float +Float.product = + use Float * + List.foldLeft (*) 1.0 + +Float.product.doc : Doc +Float.product.doc = + use Float product + {{ + `` product ns `` returns the product of all the values in `ns`. If `ns` is + empty, returns ``1.0``. + + # Examples + + ``` + product [1.0, 0.2, 30.0, 1.2] + ``` + + ``` + product [12.0] + ``` + + ``` + product [] + ``` + }} + +Float.rawExponent : Float -> Nat +Float.rawExponent f = + Nat.and (Nat.shiftRight (Float.toRepresentation f) 52) 2047 + +Float.rawExponent.doc : Doc +Float.rawExponent.doc = + {{ + Extracts the raw exponent bits from a {type Float} value, as a {type Nat}. + This will be a number between `` 0 `` and ``2047``. + + # Examples + + ``` + rawExponent 0.0 + ``` + + ``` + rawExponent 1.6777216e7 + ``` + + ``` + rawExponent 1.0e-12 + ``` + }} + +Float.rawMantissa : Float -> Nat +Float.rawMantissa f = Nat.and (Float.toRepresentation f) 4503599627370495 + +Float.rawMantissa.doc : Doc +Float.rawMantissa.doc = + {{ + Extracts the raw mantissa bits from a {type Float} value, as a {type Nat}. + This will be a number between `` 0 `` and ``4503599627370495``. + + # Examples + + ``` + rawMantissa 256.0 + ``` + + ``` + rawMantissa pi + ``` + + ``` + rawMantissa 1.0e-30 + ``` + }} + +Float.reciprocal : Float -> Float +Float.reciprocal x = + use Float / + 1.0 / x + +Float.reciprocal.doc : Doc +Float.reciprocal.doc = + use Float / + {{ + Returns the reciprocal of a floating-point number. + + The reciprocal of a number `x` is ``1.0 / x``. + + # Examples + + The reciprocal of `2.0` is `1.0 / 2.0`: + + ``` + reciprocal 2.0 + ``` + }} + +Float.round : Float -> Float +Float.round x = + use Float + - < >= + f = Float.floor x + if x >= 0.0 then if x - f >= 0.5 then f + 1.0 else f + else if f - x < -0.5 then f + 1.0 else f + +-- builtin Float.round.deprecated : Float -> Int + +Float.round.doc : Doc +Float.round.doc = + use Float round + {{ + `` round x `` rounds `x` to the nearest whole number. + + ``` + round 42.1 + ``` + + ``` + round -42.1 + ``` + + ``` + round 42.9 + ``` + + ``` + round -42.9 + ``` + + ``` + round 0.5 + ``` + + ``` + round -0.5 + ``` + }} + +Float.roundTo : Nat -> Float ->{Exception} Float +Float.roundTo = roundToWith Float.round + +Float.roundTo.doc : Doc +Float.roundTo.doc = + {{ + `` roundTo n x `` rounds {type Float} `x` to a number of decimal places `n` + using {Float.round} function. Defined in terms of ``roundToWith round n x``. + + # Examples + + ``` + catch do roundTo 0 0.123456789 + ``` + + ``` + catch do roundTo 10 0.123456789 + ``` + + ``` + catch do roundTo 5 0.123456789 + ``` + + ``` + catch do roundTo 5 0.1234512345 + ``` + + # Special cases + + ``` + catch do roundTo 5 NegativeInfinity + ``` + + ``` + catch do roundTo 5 Infinity + ``` + + ``` + catch do roundTo 5 NaN + ``` + + ## See also: + + * {safeRoundTo} + * {roundToWith} + * {safeRoundToWith} + }} + +Float.roundToWith : (Float -> Float) -> Nat -> Float ->{Exception} Float +Float.roundToWith = cases + _, _, x + | isNegativeInfinity x -> negativeInfinity() + | isInfinity x -> positiveInfinity() + | isNaN x -> ArithmeticException.notANumber() + _, 0, x -> x + f, n, x -> + use Float * / + m = Float.pow 10.0 (Float.fromNat n) + f (x * m) / m + +Float.roundToWith.doc : Doc +Float.roundToWith.doc = + use Float ceiling floor round + {{ + `` roundToWith f n x `` rounds {type Float} `x` to a number of decimal places + `n` using rounding function `f`. + + * If `n` is equal to `0`, the result is `x`. + * If `n` is greater than or equal to the number of decimal places of `x`, the + result is `x`. + + # Examples + + ``` + catch do roundToWith round 0 0.123456789 + ``` + + ``` + catch do roundToWith round 10 0.123456789 + ``` + + ``` + catch do roundToWith round 5 0.123456789 + ``` + + ``` + catch do roundToWith floor 5 0.123456789 + ``` + + ``` + catch do roundToWith ceiling 5 0.123456789 + ``` + + ``` + catch do roundToWith truncate 5 0.123456789 + ``` + + # Special cases + + ``` + catch do roundToWith round 5 NegativeInfinity + ``` + + ``` + catch do roundToWith floor 5 Infinity + ``` + + ``` + catch do roundToWith ceiling 5 NaN + ``` + + ## See also: + + * {safeRoundToWith} + * {roundTo} + * {safeRoundTo} + }} + +Float.safeEmod : Float -> Float -> Optional Float +Float.safeEmod x y = Exception.orElse (do None) do Some (Float.emod x y) + +Float.safeEmod.doc : Doc +Float.safeEmod.doc = + use Float emod safeEmod + {{ + Safe version of {emod}. + + # Examples + + ``` + safeEmod 9.0 3.0 + ``` + + ``` + safeEmod 10.0 3.0 + ``` + + ``` + safeEmod -10.0 3.0 + ``` + + ``` + safeEmod 10.0 -3.0 + ``` + + ``` + safeEmod -10.0 -3.0 + ``` + + # Special cases + + ``` + safeEmod 10.0 0.0 + ``` + + ``` + safeEmod NaN 1.0 + ``` + + ``` + safeEmod 10.0 NaN + ``` + + ``` + safeEmod Infinity 1.0 + ``` + + ``` + safeEmod NegativeInfinity 1.0 + ``` + + ``` + safeEmod 10.0 Infinity + ``` + + ``` + safeEmod 10.0 NegativeInfinity + ``` + + ## See also: + + * {emod} + }} + +test> Float.safeEmod.test = + test.verify do + go = cases + (one, two, result) -> + output = Float.safeEmod one two + ensureWith [output, result] (output === result) + xs = + [ (10.0, -3.0, Some 1.0) + , (-10.0, 3.0, Some 2.0) + , (-10.0, -3.0, Some 2.0) + , (0.9, 0.1, Some 0.0) + , (-0.0, 2.2, Some 0.0) + , (0.0, 2.2, Some 0.0) + , (2.2, 0.0, None) + , (2.2, 1.0e-4, Some 0.0) + , (-2.2, 1.1, Some 0.0) + , (2.2, 1.1, Some 0.0) + , (2.2, -1.1, Some 0.0) + , (NaN, 1.0, None) + , (Infinity, 1.0, None) + , (NegativeInfinity, 1.0, None) + , (2.2, NaN, None) + , (2.2, Infinity, None) + , (2.2, NegativeInfinity, None) + ] + foreach.flipped xs go + +Float.safeMod : Float -> Float -> Optional Float +Float.safeMod x y = Exception.orElse (do None) do Some (Float.mod x y) + +Float.safeMod.doc : Doc +Float.safeMod.doc = + use Float mod safeMod + {{ + Safe version of {mod}. + + # Examples + + ``` + safeMod 9.0 3.0 + ``` + + ``` + safeMod 10.0 3.0 + ``` + + ``` + safeMod -10.0 3.0 + ``` + + ``` + safeMod 10.0 -3.0 + ``` + + # Special cases + + ``` + safeMod 10.0 0.0 + ``` + + ``` + safeMod NaN 1.0 + ``` + + ``` + safeMod 10.0 NaN + ``` + + ``` + safeMod Infinity 1.0 + ``` + + ``` + safeMod NegativeInfinity 1.0 + ``` + + ``` + safeMod 10.0 Infinity + ``` + + ``` + safeMod 10.0 NegativeInfinity + ``` + + ## See also: + + * {mod} + }} + +test> Float.safeMod.test = + test.verify do + go = cases + (one, two, result) -> + output = Float.safeMod one two + ensureWith [output, result] (output === result) + xs = + [ (10.0, -3.0, Some -2.0) + , (-10.0, 3.0, Some 2.0) + , (-10.0, -3.0, Some -1.0) + , (0.9, 0.1, Some 0.0) + , (1.0e-5, 2.2, Some 1.0e-5) + , (-0.0, 2.2, Some 0.0) + , (0.0, 2.2, Some 0.0) + , (2.2, 0.0, None) + , (2.2, 1.0e-4, Some 0.0) + , (-2.2, 1.1, Some 0.0) + , (2.2, 1.1, Some 0.0) + , (2.2, -1.1, Some 0.0) + , (NaN, 1.0, None) + , (Infinity, 1.0, None) + , (NegativeInfinity, 1.0, None) + , (2.2, NaN, None) + , (2.2, Infinity, None) + , (2.2, NegativeInfinity, None) + ] + foreach.flipped xs go + +Float.safeRoundTo : Nat -> Float -> Optional Float +Float.safeRoundTo n x = Exception.orElse (do None) do Some (roundTo n x) + +Float.safeRoundTo.doc : Doc +Float.safeRoundTo.doc = {{ Safe version of {roundTo}. }} + +Float.safeRoundToWith : (Float -> Float) -> Nat -> Float -> Optional Float +Float.safeRoundToWith f n x = + Exception.orElse (do None) do Some (roundToWith f n x) + +Float.safeRoundToWith.doc : Doc +Float.safeRoundToWith.doc = {{ Safe version of {roundToWith}. }} + +test> Float.safeRoundToWith.tests.gen = test.verify do + use Float >= + use Nat <= + _ = Each.range 0 100 + n = Random.natIn 1 6 + x = match Random.float() with + x + | x >= 0.1 -> x + | otherwise -> + use Float + + ignore {{ To avoid `e-N` suffixes when converting to text }} + x + 0.1 + match safeRoundToWith truncate n x with + Some y -> + use Nat + + ignore {{ Prefix is `0.` part of textual representation }} + prefix = 2 + y' = Float.toText y + s = Text.size y' + ensureWith (x, y, y', n) (s <= n + prefix) + None -> ensure false + +test> Float.safeRoundToWith.tests.static = + test.verify do + foreach.flipped + [ (safeRoundToWith Float.round 0 0.123456789, Some 0.123456789) + , (safeRoundToWith Float.round 10 0.123456789, Some 0.123456789) + , (safeRoundToWith Float.round 5 0.444444444, Some 0.44444) + , (safeRoundToWith Float.round 5 0.444455555, Some 0.44446) + , (safeRoundToWith truncate 5 0.999999999, Some 0.99999) + , (safeRoundToWith Float.ceiling 5 0.111111111, Some 0.11112) + , (safeRoundToWith Float.floor 5 0.111111111, Some 0.11111) + , (safeRoundToWith Float.floor 5 0.111111111, Some 0.11111) + ] + cases pair@(lhs, rhs) -> ensureWith pair (lhs === rhs) + +Float.signBit : Float -> Nat +Float.signBit f = Nat.shiftRight (Float.toRepresentation f) 63 + +Float.signBit.doc : Doc +Float.signBit.doc = + {{ + Gets the sign bit of a {type Float} number. Returns `` 1 `` if the number is + negative, otherwise ``0``. + + # Examples + + ``` + signBit pi + ``` + + ``` + signBit -1.2 + ``` + + Zero can be either positive or negative: + + ``` + signBit -0.0 + ``` + + ``` + signBit 0.0 + ``` + }} + +-- builtin Float.sin : Float -> Float + +Float.sin.doc : Doc +Float.sin.doc = + {{ + `` sin `` is the + [sine function](https://en.wikipedia.org/wiki/Sine_and_cosine). + }} + +-- builtin Float.sinh : Float -> Float + +Float.sinh.doc : Doc +Float.sinh.doc = + {{ + `` sinh `` is the + [hyperbolic sine function](https://en.wikipedia.org/wiki/Hyperbolic_functions). + }} + +-- builtin Float.sqrt : Float -> Float + +Float.sqrt.doc : Doc +Float.sqrt.doc = + {{ + `` sqrt `` takes the square root of a number. + + ``` + sqrt 256.0 + ``` + }} + +Float.sum : [Float] -> Float +Float.sum = + use Float + + List.foldLeft (+) 0.0 + +Float.sum.doc : Doc +Float.sum.doc = + use Float sum + {{ + `` sum ns `` returns the sum of all the values in `ns`. If `ns` is empty, + returns ``0.0``. + + # Examples + + ``` + sum [1.0, 0.2, 3.0e-2, 4.0e-3] + ``` + + ``` + sum [12.0] + ``` + + ``` + sum [] + ``` + }} + +-- builtin Float.tan : Float -> Float + +Float.tan.doc : Doc +Float.tan.doc = + {{ + `` tan `` is the + [tangent function](https://en.wikipedia.org/wiki/Trigonometric_functions). + }} + +-- builtin Float.tanh : Float -> Float + +Float.tanh.doc : Doc +Float.tanh.doc = + {{ + `` tanh `` is the + [hyperbolic tangent function](https://en.wikipedia.org/wiki/Hyperbolic_functions#Tanh). + }} + +Float.toHalfPrecision : Float -> Nat +Float.toHalfPrecision n = + use Int - < > abs + use Nat != + == and or shiftLeft shiftRight + double = Float.toRepresentation n + sign = shiftRight double 63 + exp = and (shiftRight double 52) 2047 + fraction = and double 4503599627370495 + let + (halfExp, halfFraction) = + if exp == 0 && fraction == 0 then (0, 0) + else + if exp == 2047 then + (shiftLeft 31 10, and (shiftRight fraction 42) 1023) + else + normExp = Nat.toInt exp - +1008 + if normExp > +30 then (shiftLeft 31 10, 0) + else + if normExp < -10 then (0, 0) + else + if normExp < +1 then + shift = abs (+1 - normExp) + ( 0 + , shiftRight + (or 1024 (and (shiftRight fraction 42) 1023)) shift + ) + else + (shiftLeft (abs normExp) 10, and (shiftRight fraction 42) 1023) + roundingBit = isSetBit 41 fraction + lastMantissa = isSetBit 42 fraction + restOfMantissa = dropBits 23 fraction != 0 + mantissa = + if (restOfMantissa || lastMantissa) && roundingBit then halfFraction + 1 + else halfFraction + halfSign = shiftLeft sign 15 + or halfSign (or halfExp mantissa) + +Float.toHalfPrecision.doc : Doc +Float.toHalfPrecision.doc = + {{ + Converts a 64-bit double-precision floating point number to a 16-bit + half-precision floating point number encoded in the low 16 bits of a + {type Nat}. + + # Examples + + ``` + toHalfPrecision 1.0 + ``` + + ``` + toHalfPrecision 0.5 + ``` + + ``` + toHalfPrecision 0.25 + ``` + }} + +test> Float.toHalfPrecision.monotonic = test.verify do + use Float <= + use Random float + Each.repeat 1000 + n = float() + m = float() + halfn = toHalfPrecision n + halfm = toHalfPrecision m + doublen = fromHalfPrecision halfn + doublem = fromHalfPrecision halfm + ensure (iff (n <= m) (doublen <= doublem)) + +test> Float.toHalfPrecision.tests.largestSubnormal = + use Nat == + check + (toHalfPrecision (Float.fromRepresentation 4544123227923808256) == 1023) + +test> Float.toHalfPrecision.tests.maxHalf = + use Nat == + check + (toHalfPrecision (Float.fromRepresentation 4679235614791434240) == 31743) + +test> Float.toHalfPrecision.tests.negativeOne = + use Nat == + check + (toHalfPrecision (Float.fromRepresentation 13830554455654793216) == 48128) + +test> Float.toHalfPrecision.tests.negativeZero = + use Nat == + check + (toHalfPrecision (Float.fromRepresentation 9223372036854775808) == 32768) + +test> Float.toHalfPrecision.tests.normal = + use Nat == + check + (toHalfPrecision (Float.fromRepresentation 4631556392564555776) == 20902) + +test> Float.toHalfPrecision.tests.notANumber = + use Nat == + check + (toHalfPrecision (Float.fromRepresentation 9221120237041090560) == 32256) + +test> Float.toHalfPrecision.tests.positiveOne = + use Nat == + check + (toHalfPrecision (Float.fromRepresentation 4607182418800017408) == 15360) + +test> Float.toHalfPrecision.tests.positiveZero = + use Nat == + check (toHalfPrecision (Float.fromRepresentation 0) == 0) + +test> Float.toHalfPrecision.tests.smallestNormal = + use Nat == + check + (toHalfPrecision (Float.fromRepresentation 4544132024016830464) == 1024) + +test> Float.toHalfPrecision.tests.subnormal = + use Nat == + check (toHalfPrecision (Float.fromRepresentation 4538045127645462528) == 422) + +Float.toInt : Float -> Optional Int +Float.toInt n = + use Float fromInt + if Float.inRange (fromInt minInt) (fromInt maxInt) n then + Some (unsafeToInt n) + else None + +Float.toInt.doc : Doc +Float.toInt.doc = + use Float + toInt + {{ + Converts a {type Float} to an {type Int} by dropping any fractional amount. + + # Examples + + ``` + toInt 1.0 + ``` + + ``` + toInt 3.99 + ``` + + If the {type Float} value is `` NaN `` or the integer portion is outside + the range of {type Int}, then this returns ``None``: + + ``` + toInt (Float.fromInt maxInt + 1.0) + ``` + }} + +Float.toNat : Float -> Optional Nat +Float.toNat f = + use Float < <= >= + use Nat != + - == > shiftRight + if f >= 0.0 && f <= Float.fromNat maxNat then + if f < 1.0 then Some 0 + else + Some + <| let + r = Float.toRepresentation f + pow' = shiftRight r 52 + pow = pow' - 1023 + significand = + dropBits 12 r + (if pow' != 0 then Nat.pow 2 52 else 0) + if pow == 52 then significand + else + if pow > 52 then Nat.shiftLeft significand (pow - 52) + else shiftRight significand (52 - pow) + else None + +Float.toNat.doc : Doc +Float.toNat.doc = + use Float toNat + {{ + Converts a {type Float} to a {type Nat} by dropping any fractional amount and + ignoring negative numbers. + + # Examples + + ``` + toNat 1.0 + ``` + + ``` + toNat 3.99 + ``` + + If the {type Float} value is `` NaN `` or the integer portion is outside + the range of {type Nat}, then this returns ``None``. + }} + +test> Float.toNat.tests.cornerCase = + use Nat pow + check (Float.toNat (Float.fromNat (pow 2 52)) === Some (pow 2 52)) + +test> Float.toNat.tests.roundtrip = runs 1000 do + x = natInOrder() + f = Float.fromNat x + y = Float.toNat f + if y === Some x then expect true else bug (x, f, y) + +test> Float.toNat.tests.small = + runs 1000 do + use Float / fromNat + use Nat - + x = nonzeroNat() + y = nonzeroNat() + expect + (Float.toNat (fromNat (Nat.min x y - 1) / fromNat (Nat.max x y)) + === Some 0) + +-- builtin Float.toRepresentation : Float -> Nat + +Float.toRepresentation.doc : Doc +Float.toRepresentation.doc = + use Float toRepresentation + use Nat toTextBase + {{ + Converts a {type Float} to its binary64 representation as a 64-bit word. + (i.e. a {type Nat}). The + [IEEE 754](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) + standard specifies the format: + + * The first bit is the sign bit. + * The next 11 bits are the exponent. + * The last 52 bits are the significand (a.k.a. mantissa). + + # Examples + + ``` + toTextBase 16 (toRepresentation 1.0) + ``` + + ``` + toTextBase 16 (toRepresentation 1.0000000000000002) + ``` + + ``` + toTextBase 16 (toRepresentation 1.0000000000000004) + ``` + + ``` + toTextBase 16 (toRepresentation 2.0) + ``` + + ``` + toTextBase 16 (toRepresentation -2.0) + ``` + }} + +Float.toSinglePrecision : Float -> Nat +Float.toSinglePrecision n = + use Int - < > abs + use Nat != == and or shiftLeft shiftRight + double = Float.toRepresentation n + sign = shiftRight double 63 + exp = and (shiftRight double 52) 2047 + fraction = and double 4503599627370495 + let + (singleExp, singleFraction) = + if exp == 0 && fraction == 0 then (0, 0) + else + if exp == 2047 then + (shiftLeft 255 23, and (shiftRight fraction 29) 8388607) + else + use Int + + unbiasedExp = Nat.toInt exp - +1023 + normExp = unbiasedExp + +127 + if unbiasedExp > +127 then (shiftLeft 255 23, 0) + else + if unbiasedExp < -149 then (0, 0) + else + if unbiasedExp < -126 then + shift = abs (+1 - normExp) + ( 0 + , shiftRight + (or 8388608 (and (shiftRight fraction 29) 8388607)) shift + ) + else + ( shiftLeft (abs normExp) 23 + , and (shiftRight fraction 29) 8388607 + ) + use Nat + + roundingBit = isSetBit 28 fraction + lastMantissa = isSetBit 29 fraction + restOfMantissa = dropBits 38 fraction != 0 + mantissa = + if (restOfMantissa || lastMantissa) && roundingBit then + singleFraction + 1 + else singleFraction + singleSign = shiftLeft sign 31 + or singleSign (or singleExp mantissa) + +Float.toSinglePrecision.doc : Doc +Float.toSinglePrecision.doc = + {{ + Converts a 64-bit double-precision floating point number to a 32-bit + single-precision floating point number encoded in the low 32 bits of a + {type Nat}. + + # Examples + + ``` + toSinglePrecision 1.0 + ``` + + ``` + toSinglePrecision 0.5 + ``` + + ``` + toSinglePrecision 0.25 + ``` + }} + +test> Float.toSinglePrecision.monotonic = test.verify do + use Float <= + use Random float + Each.repeat 1000 + n = float() + m = float() + singlen = toSinglePrecision n + singlem = toSinglePrecision m + doublen = fromSinglePrecision singlen + doublem = fromSinglePrecision singlem + ensure (iff (n <= m) (doublen <= doublem)) + +test> Float.toSinglePrecision.tests.largestSubnormal = + use Nat == + check + (toSinglePrecision (Float.fromRepresentation 4039728864677593088) + == 8388607) + +test> Float.toSinglePrecision.tests.maxSingle = + use Nat == + check + (toSinglePrecision (Float.fromRepresentation 5183643170566569984) + == 2139095039) + +test> Float.toSinglePrecision.tests.negativeOne = + use Nat == + check + (toSinglePrecision (Float.fromRepresentation 13830554455654793216) + == 3212836864) + +test> Float.toSinglePrecision.tests.negativeZero = + use Nat == + check + (toSinglePrecision (Float.fromRepresentation 9223372036854775808) + == 2147483648) + +test> Float.toSinglePrecision.tests.normal = + use Nat == + check + (toSinglePrecision (Float.fromRepresentation 4620129717972893696) + == 1089469440) + +test> Float.toSinglePrecision.tests.notANumber = + use Nat == + check + (toSinglePrecision (Float.fromRepresentation 9218868437227405312) + == 2139095040) + +test> Float.toSinglePrecision.tests.positiveOne = + use Nat == + check + (toSinglePrecision (Float.fromRepresentation 4607182418800017408) + == 1065353216) + +test> Float.toSinglePrecision.tests.positiveZero = + use Nat == + check (toSinglePrecision (Float.fromRepresentation 0) == 0) + +test> Float.toSinglePrecision.tests.smallestNormal = + use Nat == + check + (toSinglePrecision (Float.fromRepresentation 4039728865751334912) + == 8388608) + +test> Float.toSinglePrecision.tests.subnormal = + use Nat == + check + (toSinglePrecision (Float.fromRepresentation 4036218951905050624) + == 5119746) + +-- builtin Float.toText : Float -> Text + +Float.toText.doc : Doc +Float.toText.doc = + use Float toText + {{ + Converts a {type Float} to its textual representation: + + ``` + toText 3.1415926535897 + ``` + + ``` + toText 1.0e-13 + ``` + + ``` + toText 9.999999999999e12 + ``` + }} + +Float.truncate : Float -> Float +Float.truncate x = + use Float != <= >= fromInt + if x >= fromInt maxInt || x <= fromInt minInt || x != x then x + else + n = unsafeToInt x + fromInt n + +Float.truncate.doc : Doc +Float.truncate.doc = + {{ + `` truncate x `` returns the whole number obtained by dropping any fractional + portion of `x`. That is, it truncates any digits after the decimal point. + + ``` + truncate 4.1 + ``` + + ``` + truncate 4.9 + ``` + }} + +Float.ulp : Float -> Float +Float.ulp x = + use Float - + use Int + + next = + Float.fromRepresentation + (Int.toRepresentation + (Int.fromRepresentation (Float.toRepresentation x) + +1)) + next - x + +Float.ulp.doc : Doc +Float.ulp.doc = + {{ + `` ulp x `` returns the size of the __unit of least precision__ at `x`, which + is the positive distance between `x` and the next larger {type Float} value. + This is the smallest representable difference between adjacent {type Float} + values. + + # Examples + + The ULP near zero is very small: + + ``` + ulp 0.0 + ``` + + As numbers get big, so does the ULP: + + ``` + ulp 1.0e16 + ``` + }} + +Float.ulpDiff : Float -> Float -> Nat +Float.ulpDiff a b = + use Float toRepresentation + use Int abs fromRepresentation + a' = abs (fromRepresentation (toRepresentation a)) + b' = abs (fromRepresentation (toRepresentation b)) + Nat.diff a' b' + +Float.ulpDiff.doc : Doc +Float.ulpDiff.doc = + use Float + + {{ + `` ulpDiff x y `` returns the difference between `x` and `y` in + __units of least precision__. That is, it returns the distance, in + consecutive 64-bit floating point numbers, between the numbers `x` and `y`. + + # Examples + + These two ways of computing `` 0.5 `` are equivalent: + + ``` + ulpDiff (0.2 + 0.3) (0.25 + 0.25) + ``` + + These two ways of computing `` 0.3 `` differ by the smallest possible + amount: + + ``` + ulpDiff (0.1 + 0.2) (0.15 + 0.15) + ``` + + Zero and negative zero differ by quite a lot: + + ``` + ulpDiff 0.0 -0.0 + ``` + }} + +-- builtin Float.unsafeToInt : Float -> Int + +Float.unsafeToInt.doc : Doc +Float.unsafeToInt.doc = + use Float toInt + {{ + Converts a {type Float} to an {type Int} by dropping any fractional amount. + + ``` + unsafeToInt 4.9 + ``` + + ``` + unsafeToInt 4.1 + ``` + + {{ + docCallout + (Some {{ ⚠️ }}) + {{ + If the {type Float} is `` NaN `` or outside the range of {type Int}, this + function returns ``+0``: + + ``` + (unsafeToInt maxFloat) + ``` + + For safe conversion to {type Int}, use {toInt} instead: + + @signature{toInt} + }} }} + }} + +Float.withinULPs : Nat -> Float -> Float -> Boolean +Float.withinULPs precision x y = + use Float * - <= fromNat + z = Float.abs (x - y) + z <= ulp x * fromNat precision || z <= ulp y * fromNat precision + +Float.withinULPs.doc : Doc +Float.withinULPs.doc = + use Float + + {{ + `` withinULPs slop x y `` checks if `x` and `y` are closer to each other than + either number's __unit of least precision__ (see {ulp}) times the `slop` + argument. + + A `slop` of `` 0 `` results in a check for precise equality. Note that + {type Float} values are generally only precisely equal if they're computed + the exact same way. + + A higher number for `slop` results in a less precise check. + + # Examples + + These two ways of computing `` 0.3 `` are nearly equal but not exactly, as + one of them has a small rounding error: + + ``` + withinULPs 0 (0.1 + 0.2) (0.15 + 0.15) + ``` + + Since these are simple calculations on simple numbers, the error is within + one ULP: + + ``` + withinULPs 1 (0.1 + 0.2) (0.15 + 0.15) + ``` + }} + +(Function.&&&) : (a ->{g} b) -> (a ->{h} c) -> a ->{g, h} (b, c) +(Function.&&&) f g x = (f x, g x) + +Function.&&&.doc : Doc +Function.&&&.doc = + use Function both + use Nat * + use Text reverse size + {{ + Combines two functions into a function that returns a tuple of their results. + + # Examples + + ``` + both size reverse "hello" + ``` + + ``` + size &&& reverse <| "hello" + ``` + + ``` + List.map (both Nat.increment (x -> x * 2)) [1, 2, 3] + ``` + }} + +(Function.<<) : (b ->{𝕖} c) -> (a ->{𝕖} b) -> a ->{𝕖} c +(Function.<<) f g x = f (g x) + +Function.<<.doc : Doc +Function.<<.doc = + use Int complement increment isEven range + use List filter map + {{ + `` f << g `` is the same as `x -> f (g x)`. It is useful for avoiding + parentheses when composing functions, or avoiding the need for named + arguments or lambdas. + + # Example + + ``` + filter isEven << map (increment << complement) <| range -5 +5 + ``` + + As opposed to: + + ``` + filter isEven (map (x -> increment (complement x)) (range -5 +5)) + ``` + }} + +(Function.<|) : (a ->{𝕖} b) -> a ->{𝕖} b +f Function.<| a = f a + +Function.<|.doc : Doc +Function.<|.doc = + use List contains + use Nat range + {{ + `` f <| x `` is the same as `f x`. It is useful for avoiding parentheses when + applying a function to a composite expression. + + # Example + + ``` + contains 4 <| range 0 4 + ``` + + As opposed to: + + ``` + contains 4 (range 0 4) + ``` + }} + +(Function.<||) : (r ->{g} a) -> (r ->{e} a ->{f} b) -> r ->{e, f, g} b +(Function.<||) g f r = f r (g r) + +Function.<||.doc : Doc +Function.<||.doc = + use Int toText + use Nat increment + use Universal ordering + {{ + Takes an input, applies a transformation to it, and then uses both the + original input and the transformed value as arguments to a binary function. + + `` g <|| f <| x `` is equivalent to `f x (g x)`. + + # Examples + + This multiplies `` 5 `` by ``increment 5``: + + ``` + increment Function.<|| (Nat.*) <| 5 + ``` + + This concatenates a list with its own reverse: + + ``` + List.reverse Function.<|| (List.++) <| [1, 2, 3] + ``` + + {Function.<||} can be used to thread a common argument through any number + of functions: + + ``` + use Int * + use Text ++ + y b = match ordering b +0 with + Less -> "negative" + Greater -> "positive" + Equal -> "zero" + z c = c * c + f a b c = toText a ++ " is " ++ b ++ " and its square is " ++ toText c + z Function.<|| (y Function.<|| f) <| +10 + ``` + + Combine with {<<} or {>>} to apply an n-ary function to the results of n + functions that all take a common argument: + + ``` + use Int * + use Text ++ + y b = match ordering b +0 with + Less -> "negative" + Greater -> "positive" + Equal -> "zero" + z c = c * c + f a b c = a ++ " is " ++ b ++ " and its square is " ++ toText c + z Function.<|| (y Function.<|| (toText >> f)) <| +10 + ``` + + # See also + + * {||>} - applies the functions in the opposite order. + * {type Ask} - a more flexible way to thread a common argument through a + whole program. + }} + +(Function.>>) : (a ->{𝕖} b) -> (b ->{𝕖} c) -> a ->{𝕖} c +(Function.>>) f g x = g (f x) + +Function.>>.doc : Doc +Function.>>.doc = + use Int complement increment isEven range + use List filter map + {{ + `` f >> g `` is the same as `x -> g (f x)`. It is useful for avoiding + parentheses when composing functions or avoiding the need for named arguments + or lambdas. + + # Example + + ``` + map (increment >> complement) >> filter isEven <| range -5 +5 + ``` + + As opposed to: + + ``` + filter isEven (map (x -> complement (increment x)) (range -5 +5)) + ``` + }} + +Function.apply2 : + (a ->{e} b ->{e} c) -> (r ->{e} a) -> (r ->{e} b) -> r ->{e} c +Function.apply2 f a b r = f (a r) (b r) + +Function.apply2.doc : Doc +Function.apply2.doc = + use Nat / + {{ + Passes one argument to two functions, and then passes their outputs to a + third function. + + # Example + + ``` + divMod x y = apply2 Tuple.pair (flip (/) y) (flip Nat.mod y) x + divMod 17 3 + ``` + }} + +Function.applyAll : [a ->{e} b] -> a ->{e} [b] +Function.applyAll fs x = List.map (f -> f x) fs + +Function.applyAll.doc : Doc +Function.applyAll.doc = + use Nat * + + {{ + Applies a list of functions to a single argument, returning a list of + results. + + # Examples + + ``` + applyAll [(+) 1, (*) 2] 3 + ``` + + # See also + + * {(<|)} and {(|>)} for applying a single function to an argument. + * {List.apply} for applying a list of functions to a list of arguments. + * {List.map} for applying a function to each element of a list. + }} + +Function.both : (a ->{g} b) -> (a ->{h} c) -> a ->{g, h} (b, c) +Function.both = (&&&) + +Function.compose2 : (c ->{𝕖} d) -> (a ->{𝕖} b ->{𝕖} c) -> a -> b ->{𝕖} d +Function.compose2 f g x y = f (g x y) + +Function.compose2.doc : Doc +Function.compose2.doc = + use List ++ + use Nat + + {{ + `` compose2 f g x y `` is equivalent to `f (g x y)`. It takes a unary + function, a binary function, and two arguments, and returns the result of + applying the binary function to the two arguments and then applying the unary + function to the result. + + # Examples + + ``` + compose2 Nat.increment (+) + ``` + + ``` + compose2 List.size (++) + ``` + + ``` + compose2 Text.toUppercase (leftPad 10) "👋" " hello" + ``` + }} + +Function.compose3 : + (d ->{f} e) -> (a ->{g} b ->{h} c ->{i} d) -> a -> b -> c ->{f, g, h, i} e +Function.compose3 f g x y z = f (g x y z) + +Function.compose3.doc : Doc +Function.compose3.doc = + use Nat + + {{ + `` compose3 f g x y z `` is equivalent to `f (g x y z)`. It takes a unary + function, a ternary function, and three arguments, and returns the result of + applying the ternary function to the three arguments and then applying the + unary function to the result. + + # Examples + + ``` + compose3 Boolean.not Nat.inRange + ``` + + ``` + compose3 Nat.increment List.foldRight (+) 0 [1, 2, 3] + ``` + + ``` + compose3 Text.toUppercase leftPad 10 "👋" " hello" + ``` + }} + +Function.composeK : + (b ->{e} r ->{e} c) -> (a ->{e} r ->{e} b) -> a -> r ->{e} c +Function.composeK f g a r = f (g a r) r + +Function.composeK.doc : Doc +Function.composeK.doc = + use List ++ + {{ + Composes two functions that take an argument in common. + + # Example + + ``` + f b r = b ++ [at1 r] + g a r = List.fill (at2 r) a + composeK f g "na" ("Batman!", 16) + ``` + }} + +Function.const : a -> b -> a +Function.const a _ = a + +Function.const.doc : Doc +Function.const.doc = + {{ + A binary function that always returns its first argument. + + # Example + + ``` + const 42 0 + ``` + }} + +Function.curry : ((a, b) ->{e} c) -> a -> b ->{e} c +Function.curry f a b = f (a, b) + +Function.curry.doc : Doc +Function.curry.doc = + {{ + Turns a function that takes a pair into one that can be partially applied by + taking its arguments one at a time. + }} + +Function.delay : (a ->{g} b) -> a -> '{g} b +Function.delay f a = do f a + +Function.delay.doc : Doc +Function.delay.doc = + {{ + `` delay f `` turns an effectful function into one that returns a computation + (a thunk) that can be forced later. + + `` delay f x `` is the same as {{ docExample 2 do f x -> do f x }}. + + `` delay f x () `` is the same as {{ docExample 2 do f x -> f x }}. + + Language documentation: + [Delayed Computations](https://www.unison-lang.org/learn/fundamentals/values-and-functions/delayed-computations/) + }} + +Function.fix : ('{e} a ->{e} a) ->{e} a +Function.fix f = + x : '{e} a + x = do f x + x() + +Function.fix.doc : Doc +Function.fix.doc = + use Nat * <= decrement + {{ + Finds the + [least fixed point](https://en.wikipedia.org/wiki/Fixed-point_combinator) of + a function. + + # Examples + + We can write the factorial function using explicit recursion: + + ``` + fac : Nat -> Nat + fac n = if n <= 1 then 1 else n * fac (decrement n) + fac 7 + ``` + + Or we can write it using {fix}: + + ``` + fac : Nat -> Nat + fac = fix (rec n -> (if n <= 1 then 1 else n * rec () (decrement n))) + fac 7 + ``` + }} + +Function.fix.examples.factorial.explicitRecursion : Nat +Function.fix.examples.factorial.explicitRecursion = + use Nat * + fac n = if Universal.lteq n 1 then 1 else n * fac (Nat.decrement n) + fac 7 + +Function.fix.examples.factorial.fixpoint : Nat +Function.fix.examples.factorial.fixpoint = + use Nat * + fix + (rec n -> (if Universal.lteq n 1 then 1 else n * rec () (Nat.decrement n))) + 7 + +Function.flatMap : (a -> r ->{e} b) -> (r ->{e} a) -> r ->{e} b +Function.flatMap f x r = f (x r) r + +Function.flatMap.doc : Doc +Function.flatMap.doc = + {{ + Monadic bind for a reader monad. + + `` Function.flatMap f g r `` sequentially composes `f` and `g` so that the + output of `g` is passed as input to `f`, with `r` passed to both `f` and `g`. + }} + +Function.flip : (a ->{e} b ->{e} c) -> b -> a ->{e} c +Function.flip f b a = f a b + +Function.flip.doc : Doc +Function.flip.doc = {{ Flips the argument order of a binary function. }} + +Function.id : a -> a +Function.id a = a + +Function.id.doc : Doc +Function.id.doc = + {{ + The identity function. `` id x `` is the same as `x`. It is useful as a base + case for composing functions. + + # Example + + ``` + id 3 + ``` + + Fold a list of functions from left to right and compose them, starting with + the identity function: + + ``` + List.foldRight + (<<) id [List.map Nat.increment, List.filter Nat.isEven, List.reverse] + <| Nat.range 0 10 + ``` + }} + +Function.join : (i ->{g} i ->{h} o) -> i ->{g, h} o +Function.join f r = f r r + +Function.join.doc : Doc +Function.join.doc = + {{ + Passes the same argument to both sides of a binary function. + + # Example + + ``` + Function.join Tuple.pair "xoxo" + ``` + }} + +Function.loopWhile : (a ->{e} (a, Boolean)) -> a ->{e} a +Function.loopWhile f x = match f x with + (y, true) -> Function.loopWhile f y + (_, false) -> x + +Function.loopWhile.doc : Doc +Function.loopWhile.doc = + use Nat + < + {{ + Repeatedly applies a function to an argument until the function returns + ``false``. Returns the argument that caused the function to return ``false``. + + # Examples + + ``` + loopWhile (x -> (x + 1, x < 10)) 0 + ``` + + # See also + + * {List.unfold} for a version that returns all intermediate results as a + {type List}. + * {Stream.unfold} for a version that returns all intermediate results as a + {type Stream}. + * {while} evaluates a thunk repeatedly until a predicate returns ``false``. + }} + +Function.on : (b ->{e} b ->{e} c) -> (a ->{e} b) -> a -> a ->{e} c +Function.on b u x y = b (u x) (u y) + +Function.on.doc : Doc +Function.on.doc = + use Nat == + {{ + Transforms two inputs with a unary function and combines the outputs with a + binary function. + + # Example + + These two lists are equal __on__ their sizes: + + ``` + on (==) List.size [1, 2, 3] [4, 5, 6] + ``` + }} + +Function.on.examples.equalOn : Boolean +Function.on.examples.equalOn = on (===) List.size [1, 2, 3] [5, 5, 5] + +Function.tap : (a ->{g} ()) -> a ->{g} a +Function.tap tappedFunction param = + tappedFunction param + param + +Function.tap.doc : Doc +Function.tap.doc = + use Nat == + {{ + `` Function.tap tappedFunction param `` applies `param` to `tappedFunction`, + discarding the result. It then returns `param`. + + This can be used to insert an action into a pipeline without impacting result + of that pipeline. + + # Example + + @typecheck ``` + x = + [1, 2, 3] |> List.map Nat.increment + |> Function.tap (Debug.trace "incremented") + |> Nat.sum + check (x == 9) + ``` + + This will print the following to the console: + + ``` raw + trace: incremented + [2, 3, 4] + ``` + + # See also + + {Debug.tap} + }} + +test> Function.tap.tests.t1 = + use Nat + + use Store put + check + ((99, 100) === (withInitialValue 0 do + put 0 + tapper n = put (n + 1) + result = Function.tap tapper 99 + (result, Store.get))) + +Function.times : (a ->{e} a) -> Nat -> a ->{e} a +Function.times f n x = + use Nat - == + if n == 0 then x else Function.times f (n - 1) (f x) + +Function.times.doc : Doc +Function.times.doc = + use Nat + + {{ + Applies a function to an argument `n` times. + + # Examples + + `` times f 3 x `` is equivalent to `f (f (f x))`: + + ``` + times (x -> x + 1) 3 0 + ``` + }} + +Function.uncurry : (a ->{e} b ->{e} c) -> (a, b) ->{e} c +Function.uncurry f = cases (a, b) -> f a b + +Function.uncurry.doc : Doc +Function.uncurry.doc = + {{ + The inverse of {curry} Turns a binary function that takes its arguments one + at a time into one that takes a pair all at once. + }} + +(Function.|>) : a -> (a ->{𝕖} b) ->{𝕖} b +a Function.|> f = f a + +Function.|>.doc : Doc +Function.|>.doc = + use List contains + use Nat range + {{ + `` x |> f `` is the same as `f x`. It is useful for avoiding parentheses when + applying a function to a composite expression. + + # Example + + ``` + range 0 4 |> contains 4 + ``` + + As opposed to: + + ``` + contains 4 (range 0 4) + ``` + }} + +(Function.||>) : (r ->{e} a ->{f} b) -> (r ->{g} a) -> r ->{e, f, g} b +(Function.||>) f g r = f r (g r) + +Function.||>.doc : Doc +Function.||>.doc = + use Int toText + use Nat increment + use Universal ordering + {{ + Takes an input, applies a transformation to it, and then uses both the + original input and the transformed value as arguments to a binary function. + + `` f ||> g <| x `` is equivalent to `f x (g x)`. + + # Examples + + This multiplies `` 5 `` by ``increment 5``: + + ``` + (Nat.*) Function.||> increment <| 5 + ``` + + This concatenates a list with its own reverse: + + ``` + (List.++) Function.||> List.reverse <| [1, 2, 3] + ``` + + {Function.||>} can be used to thread a common argument through any number + of functions: + + ``` + use Int * + use Text ++ + y b = match ordering b +0 with + Less -> "negative" + Greater -> "positive" + Equal -> "zero" + z c = c * c + f a b c = toText a ++ " is " ++ b ++ " and its square is " ++ toText c + f Function.||> y Function.||> z <| +10 + ``` + + Combine with {<<} to apply an n-ary function to the results of n functions + that all take a common argument: + + ``` + use Int * + use Text ++ + x = toText + y b = match ordering b +0 with + Less -> "negative" + Greater -> "positive" + Equal -> "zero" + z c = c * c + f a b c = a ++ " is " ++ b ++ " and its square is " ++ toText c + f << x Function.||> y Function.||> z <| +10 + ``` + + This last example is often pronounced as `f` over `x`, `y`, and `z`, + applied to `+10`. + + # See also + + * {<||} - applies the functions in the opposite order. + * {type Ask} - a more flexible way to thread a common argument through a + whole program. + }} + +GUID.doc : Doc +GUID.doc = + use GUID new toBase16 toBytes + {{ + A {type GUID} is a globally unique identifier. + + Create a random {type GUID} using {new}. This generates Version 4 (random) + GUIDs: + + ``` + splitmix 1 new + ``` + + Convert a {type GUID} to {type Text} using {toBase16}: + + ``` + splitmix 1 do toBase16 new() + ``` + + Convert {type Text} to a {type GUID} using {fromBase16.impl}: + + ``` + catch do GUID (fromBase16 (Text.toUtf8 "6ba7b8109dad11d180b400c04fd430c8")) + ``` + + Convert a {type GUID} to {type Bytes} using {toBytes}: + + ``` + toBytes (GUID 0xs6ba7b8109dad11d180b400c04fd430c8) + ``` + + Convert {type Bytes} to a {type GUID} using {GUID}: + + ``` + GUID 0xs6ba7b8109dad11d180b400c04fd430c8 + ``` + }} + +GUID.new : '{Random} GUID +GUID.new = + do + unsafeRun! do + "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx" + |> (Text.map cases + ?x -> hex() + ?y -> + Random.natIn 0 16 |> Nat.and 3 |> Nat.or 8 |> Nat.toTextBase 16 + |> Optional.flatMap Text.head + |> Optional.getOrElse ?8 + v -> v) + |> Bytes.fromHex + |> GUID + +GUID.new.deprecated : Text -> Text -> GUID +GUID.new.deprecated domain name = + use Bytes ++ + use Text toUtf8 + GUID (hash Sha1 (toUtf8 domain ++ toUtf8 name)) + +GUID.new.deprecated.doc : Doc +GUID.new.deprecated.doc = + {{ + Create a new {type GUID} from a domain and a name. The domain and name are + hashed together to create the {type GUID}. + + This implements the + [UUID v5](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_5_(SHA-1_name-based)) + + # Examples + + ``` + new.deprecated "unison-lang.org" "unison" + ``` + }} + +GUID.toBase16 : GUID -> Text +GUID.toBase16 = cases GUID bs -> Bytes.toHex bs + +GUID.toBase16.doc : Doc +GUID.toBase16.doc = + {{ + Converts a {type GUID} to {type Text} representing that {type GUID} as a + string of base-16 digits. + + # Example + + ``` + GUID.toBase16 (GUID 0xsdeadbeef) + ``` + }} + +GUID.toBytes : GUID -> Bytes +GUID.toBytes = cases GUID bytes -> bytes + +GUID.toBytes.doc : Doc +GUID.toBytes.doc = + {{ + Convert a {type GUID} to its {type Bytes} value. + + # Examples + + ``` + GUID.toBytes (new.deprecated "unison-lang.org" "unison") + ``` + }} + +Hash.Murmur.add : Nat -> Nat -> Nat +Hash.Murmur.add k h = + use Nat * xor + k1 = k * murmur_m + k2 = xor k1 (Nat.shiftRight k1 murmur_r) + k3 = k2 * murmur_m + h1 = h * murmur_m + h2 = xor h1 k3 + h2 + +Hash.Murmur.add.doc : Doc +Hash.Murmur.add.doc = + use Murmur add + {{ + `` add n currentHash `` mixes `n` into the existing hash value and returns + the new hash value. + + # Examples + + ``` + curHash = 329482934 + finish (add 1 curHash |> add 2) + ``` + }} + +Hash.Murmur.finish : Nat -> Nat +Hash.Murmur.finish h = + use Nat * shiftRight xor + h1 = xor h (shiftRight h murmur_r) + h2 = h1 * murmur_m + h3 = xor h2 (shiftRight h2 murmur_r) + h3 + +Hash.Murmur.finish.doc : Doc +Hash.Murmur.finish.doc = + use Murmur add + {{ + `` finish h `` finalizes the hash to produce a {type Nat}. + + ``` + curHash = 230498 + finish (add 207176 curHash |> add 3 |> add 39482) + ``` + }} + +Hash.Murmur.impl.murmur_m : Nat +Hash.Murmur.impl.murmur_m = 14313749767032793493 + +Hash.Murmur.impl.murmur_r : Nat +Hash.Murmur.impl.murmur_r = 47 + +Hash.Murmur.initialSeed : Nat +Hash.Murmur.initialSeed = 2240047973121 + +Hash.Murmur.initialSeed.doc : Doc +Hash.Murmur.initialSeed.doc = + {{ + An initial seed to use for the Murmur hash functions {Murmur.add} and + {finish}. + }} + +Hash.Murmur.Readme : Doc +Hash.Murmur.Readme = + use Murmur add + {{ + This namespace has a + [64-bit murmur hash](https://en.wikipedia.org/wiki/MurmurHash) + implementation. Use {add} to mix a {type Nat} into the hash, and {finish} to + finalize the hash, producing a 64-bit {type Nat}. + + ``` + add 2 initialSeed |> add 329048 |> finish + ``` + + You can use any value to initialize the hash. The above example uses + {initialSeed}. + + # Credits + + Implementation is a port of + [this Haskell library](https://hackage.haskell.org/package/murmur-hash-0.1.0.9/docs/src/Data-Digest-Murmur64.html#hash64AddWord64) + by Thomas Schilling, which is BSD-licensed. + }} + +ignore : a -> () +ignore _ = () + +ignore.doc : Doc +ignore.doc = {{ `` ignore a `` ignores the argument `a` and returns ``()``. }} + +(Int.!=) : Int -> Int -> Boolean +a Int.!= b = + use Int == + Boolean.not (a == b) + +Int.!=.doc : Doc +Int.!=.doc = + use Int != * + {{ + `` a != b `` returns `` true `` if `a` and `b` have the same {type Int} + value. + + # Examples + + ``` + +3 * +4 != +2 * +6 + ``` + + ``` + +2 * -6 != +12 + ``` + }} + +-- builtin Int.* : Int -> Int -> Int + +Int.*.doc : Doc +Int.*.doc = + use Int * + {{ + Multiply two {type Int}s. + + # Examples + + ``` + +3 * +3 + ``` + + ``` + +3 * -3 + ``` + + ``` + maxInt * -1 + ``` + + If the value of the product is too large to fit in an {type Int}, then the + result wraps around: + + ``` + maxInt * +2 + ``` + }} + +-- builtin Int.+ : Int -> Int -> Int + +Int.+.doc : Doc +Int.+.doc = + use Int + + {{ + Add two {type Int}s. + + # Examples + + ``` + +3 + +3 + ``` + + ``` + +4 + -3 + ``` + + If the value of the sum is too large to fit in an {type Int}, then the + result wraps around: + + ``` + maxInt + +1 + ``` + }} + +-- builtin Int.- : Int -> Int -> Int + +Int.-.doc : Doc +Int.-.doc = + use Int - + {{ + Subtract one {type Int} from another. + + # Examples + + ``` + +3 - +3 + ``` + + ``` + +4 - -3 + ``` + + If the value of the difference is too low to fit in an {type Int}, then the + result wraps around: + + ``` + minInt - +1 + ``` + }} + +(Int./) : Int -> Int -> Int +x Int./ y = + use Int == + if x == minInt && y == -1 then minInt else div.impl x y + +Int./.doc : Doc +Int./.doc = + use Int - / + {{ + `` m / n `` returns the integer quotient of `m` and `n`. This operation + rounds down (truncates towards negative infinity). + + # Properties + + * `` m / n `` is the greatest integer that's no larger than the fraction + m/n. + * `` m - Int.div m n * n `` is always between `0` and `n`. + + For Euclidean integer division, see {ediv}. {/} and {ediv} are equivalent + unless the divisor (the second argument) is negative. + + # Warning: division by zero + + Passing a zero as the divisor (the second argument), as in ``x / +0``, will + halt your program with a runtime error. Use this function only if you + happen to know that the divisor will never be zero. Otherwise use + {safeDiv}. + + # Examples + + ``` + +17 / +5 + ``` + + ``` + +17 / -5 + ``` + + ``` + -17 / +5 + ``` + + ``` + -17 / -5 + ``` + }} + +-- builtin Int.< : Int -> Int -> Boolean + +Int.<.doc : Doc +Int.<.doc = + use Int * < + {{ + `` a < b `` checks if `a` is less than `b`. + + # Examples + + ``` + +3 * +3 < +10 + ``` + + ``` + +3 * +3 < +9 + ``` + + ``` + +3 * +3 < +8 + ``` + }} + +-- builtin Int.<= : Int -> Int -> Boolean + +Int.<=.doc : Doc +Int.<=.doc = + use Int * <= + {{ + `` a <= b `` checks if `a` is less than or equal to `b`. + + # Examples + + ``` + +3 * +3 <= +10 + ``` + + ``` + +3 * +3 <= +9 + ``` + + ``` + +3 * +3 <= +8 + ``` + }} + +-- builtin Int.== : Int -> Int -> Boolean + +Int.==.doc : Doc +Int.==.doc = + use Int * + == + {{ + Equality on integers. `` x == y `` is `` true `` if `x` and `y` have the same + {type Int} value: + + ``` + +2 + +2 == +5 + ``` + + ``` + +12 * +3 == +36 + ``` + }} + +-- builtin Int.> : Int -> Int -> Boolean + +Int.>.doc : Doc +Int.>.doc = + use Int * > + {{ + `` a > b `` checks if `a` is greater than `b`. + + # Examples + + ``` + +3 * +3 > +10 + ``` + + ``` + +3 * +3 > +9 + ``` + + ``` + +3 * +3 > +8 + ``` + }} + +-- builtin Int.>= : Int -> Int -> Boolean + +Int.>=.doc : Doc +Int.>=.doc = + use Int * >= + {{ + `` a >= b `` checks if `a` is greater than or equal to `b`. + + # Examples + + ``` + +3 * +3 >= +10 + ``` + + ``` + +3 * +3 >= +9 + ``` + + ``` + +3 * +3 >= +8 + ``` + }} + +Int.abs : Int -> Nat +Int.abs x = + use Int < + Int.toRepresentation (if x < +0 then Int.negate x else x) + +Int.abs.doc : Doc +Int.abs.doc = + use Int abs + {{ + `` abs x `` returns the absolute value of `x` (in other words, how far `x` is + from zero). + + # Examples + + ``` + abs -42 + ``` + + ``` + abs +0 + ``` + + ``` + abs +47 + ``` + }} + +-- builtin Int.and : Int -> Int -> Int + +Int.and.doc : Doc +Int.and.doc = + use Int and + {{ + `` and x y `` is the bitwise AND operation on corresponding bits in `x` and + `y`. If both bits in a given position are ``1``, the bit at that position in + the result will be ``1``, otherwise ``0``. + + # Examples + + ``` + and +5 +3 + ``` + + ``` + and +0 -1 + ``` + + ``` + and -1 +1720 + ``` + }} + +Int.clamp : Int -> Int -> Int -> Int +Int.clamp low hi x = Int.min hi (Int.max low x) + +Int.clamp.doc : Doc +Int.clamp.doc = + use Int clamp + {{ + `` clamp lo hi x `` clamps value `x` between `lo` and `hi`. The result is + `lo` if `x` is less than `lo`, `hi` if `x` is greater than `hi`, and `x` + otherwise. + + # Examples + + ``` + clamp +0 +10 +1 + ``` + + ``` + clamp +0 +10 +11 + ``` + + ``` + clamp +0 +10 +10 + ``` + + ``` + clamp +0 +10 -10 + ``` + }} + +test> Int.clamp.test = test.verify do + _ = Each.range 0 100 + low = +0 + hi = +10 + x = Random.int() + result = Int.clamp low hi x + ensureWith result (Int.inRange low (Int.increment hi) result) + +-- builtin Int.complement : Int -> Int + +Int.complement.doc : Doc +Int.complement.doc = + use Int complement + {{ + `` complement `` flips all the bits of an {type Int}, performing a logical + NOT operation. + + # Examples + + ``` + complement +0 + ``` + + ``` + complement +4294967295 + ``` + }} + +Int.decrement : Int -> Int +Int.decrement n = + use Int - + n - +1 + +Int.decrement.doc : Doc +Int.decrement.doc = + use Int decrement + {{ + Decrements an {type Int} by one. + + # Examples + + ``` + decrement +0 + ``` + + Decrementing {minInt} wraps around and results in {maxInt}: + + ``` + decrement minInt + ``` + }} + +test> Int.decrement.test = + use Int + + deprecated.forAll 100 Domain.ints (n -> Int.decrement n + +1 === n) + +Int.diff : Int -> Int -> Nat +Int.diff x y = + use Int - + Int.abs (x - y) + +Int.diff.doc : Doc +Int.diff.doc = + use Int - abs diff + {{ + Returns the absolute distance between two {type Int} values as a {type Nat}. + This is the same as ``abs (x - y)``. + + # Examples + + ``` + diff +3 +5 + ``` + + ``` + diff +3 -5 + ``` + + ``` + diff -3 +5 + ``` + + ``` + diff -3 -5 + ``` + + # See also + + * {abs} for the absolute value of an {type Int}. + * {-} for the difference of two {type Int} values. + }} + +-- builtin Int.div.impl : Int -> Int -> Int + +test> Int.div.tests.galois = + runs 100 do + use Float / <= + use Int == div toFloat + use gen int + x = int() + m = int() + n = int() + p = n == +0 || toFloat (div m n) <= toFloat m / toFloat n + if p then expect p + else + bug + ( x + , m + , n + , div m n + , toFloat m + , toFloat n + , unsafeToInt (toFloat m / toFloat n) + ) + +test> Int.div.tests.range = runs 100 do + use Int != * - <= >= div + use Test ok + use gen int + m = int() + n = int() + if n != +0 then + x = m - div m n * n + p = n >= +0 && +0 <= x && x <= n || +0 >= x && x >= n + if p then ok else bug (m, n, div m n, x) + else ok + +Int.doc : Doc +Int.doc = + use Int != * + - / < <= == > >= abs and complement decrement emod fromRepresentation fromText inRange increment isEven isOdd leadingZeros max min mod negate or popCount pow range rangeClosed safeEmod safeMod shiftLeft shiftRight toFloat toRepresentation toText trailingZeros xor + {{ + {type Int} is the type of 64-bit signed integers. This type is built into + Unison. + + Values range from @eval{ minInt } to @eval{maxInt}. + + If you don't need negative numbers, use {type Nat} instead. + + # Constructing integers + + ## Literal syntax + + You can construct {type Int} values using literal syntax. For example, + these are valid {type Int} values: + + * `+0` + * `-0` + * `+9001` + * `-16777216` + * `+0xdeadbeef` (hexadecimal notation) + * `-0o5446` (octal notation) + + A literal {type Int} consists of a sign (either `+` or `-`), followed by + a number in decimal, hexadecimal (starting with `0x`), or octal notation + (starting with `0o`). If you omit the sign, Unison will parse the + literal as a {type Nat} instead. + + {Nat.toInt} or {fromRepresentation} construct a {type Int} from its + representation as a 64-bit word of type {type Nat}: + + ``` + fromRepresentation 18446744073709551615 + ``` + + {fromText} constructs a {type Int} from any valid {type Int} literal + syntax as a {type Text} value: + + ``` + fromText "+0xff" + ``` + + # Integer arithmetic + + You can add, multiply, and subtract {type Int} values: + + ``` + +1 + -2 * +3 - +4 + ``` + + Note that Unison has no + [order of operations or operator precedence](https://en.wikipedia.org/wiki/Order_of_operations) + rules, so parentheses are necessary. All binary operators associate to the + left: + + ``` + +1 + +2 + +3 + ``` + + {pow} is exponentiation: + + @signature{pow} + + ``` + pow +2 24 + ``` + + {increment} increments an {type Int} by one: + + ``` + increment +1 + ``` + + {decrement} decrements an {type Int} by one: + + ``` + decrement +1 + ``` + + Arithmetic overflow is handled by wrapping around. For example incrementing + {maxInt} results in {minInt}: + + ``` + increment maxInt + ``` + + Similarly, decrementing {minInt} results in {maxInt}: + + ``` + decrement minInt + ``` + + ## Integer division + + {/} is flooring integer division. That is, `` m / n `` is the greatest + {type Int} that's no larger than the fraction m/n: + + ``` + +17 / +5 + ``` + + ``` + +17 / -5 + ``` + + {mod} gets the modulus of such division: + + ``` + mod +17 +5 + ``` + + ``` + mod +17 -5 + ``` + + {ediv} is Euclidean integer division. `` ediv x y `` is the greatest + {type Int} which when multiplied by `x` yields at most `y`: + + ``` + ediv +17 +5 + ``` + + ``` + ediv +17 -5 + ``` + + {emod} gets the modulus of such division. {emod} has the property that + the modulus is always positive: + + ``` + emod +17 +5 + ``` + + ``` + emod +17 -5 + ``` + + If you're not sure which integer division to use, note that {/} and + {ediv} agree unless the divisor is negative. + + Both {/} and {ediv} are undefined if the divisor is zero and will throw + a runtime error in that case. If you can't be sure the divisor is never + zero, use {safeDiv} and {safeEdiv} instead (for flooring and Euclidean + division, respectively): + + @signature{safeDiv} + + @signature{safeEdiv} + + ``` + safeDiv +2 +0 + ``` + + {safeMod} and {safeEmod} get the modulus of division while avoiding + division by zero: + + ``` + safeMod +2 +0 + ``` + + ``` + safeEmod +2 +0 + ``` + + # Comparing integers + + `` a == b `` checks if `a` and `b` are equal: + + ``` + +3 * +4 == +2 * +6 + ``` + + `` a != b `` checks if `a` and `b` are __not__ equal: + + ``` + +3 * +4 != +2 * +6 + ``` + + `` a <= b `` checks if `a` is at most `b`: + + ``` + +3 * +3 <= +10 + ``` + + `` a >= b `` checks if `a` is at least `b`: + + ``` + +2 * +5 >= +10 + ``` + + `` a > b `` checks if `a` is strictly above `b`: + + ``` + +2 * +5 > +10 + ``` + + `` a < b `` checks if `a` is strictly below `b`: + + ``` + +2 * +5 < +10 + ``` + + `` min a b `` returns the lesser of the two numbers `a` and `b`: + + ``` + min +1 +2 + ``` + + `` max a b `` returns the greater of the two numbers `a` and `b`: + + ``` + max -1 +2 + ``` + + # Sign and parity + + `` abs x `` discards the sign of `x` and returns its absolute value, as a + {type Nat}: + + ``` + abs -10 + ``` + + `` negate x `` flips the sign of `x`: + + ``` + negate +1 + ``` + + ``` + negate -1 + ``` + + `` signum x `` gets the sign of `x`, returning `` -1 `` if it's negative, + `` +1 `` if it's positive, and `` +0 `` if `x` is zero (being both positive + and negative, or neither, depending on convention). + + `` isEven `` and `` isOdd `` check if an {type Int} is even or odd, + respectively: + + ``` + isEven +2 + ``` + + ``` + isOdd +2 + ``` + + # Integer ranges + + `` inRange x y n `` checks if `n` is between `x` (inclusive) and `y` + (exclusive). That is, whether `n` is at least `x` and strictly below `y`: + + ``` + inRange +1 +3 +2 + ``` + + ``` + inRange +1 +3 +3 + ``` + + ``` + inRange +1 +3 +1 + ``` + + `` range x y `` returns all the numbers between `x` (inclusive) and `y` + (exclusive) as a {type List}: + + ``` + range +1 +10 + ``` + + `` rangeClosed x y `` returns all the numbers between `x` (inclusive) and + `y` (also inclusive) as a {type List}: + + ``` + rangeClosed +1 +10 + ``` + + # Bitwise operations + + `` complement `` flips all the bits of an {type Int} (logical NOT): + + ``` + complement +123 + ``` + + `` and x y `` is the bitwise AND operation on corresponding bits in `x` and + `y`: + + ``` + and +5 +3 + ``` + + `` or `` is bitwise OR: + + ``` + or +5 +3 + ``` + + `` xor `` is bitwise exclusive-OR: + + ``` + xor +5 +3 + ``` + + `` leadingZeros `` counts the number of zero bits at the front (i.e. left) + of an {type Int}: + + ``` + leadingZeros +1 + ``` + + `` trailingZeros `` counts the number of zero bits at the end (i.e. right) + of an {type Int}: + + ``` + trailingZeros +256 + ``` + + `` popCount `` counts the number of `` 1 `` bits in an {type Int}: + + ``` + popCount +3735928559 + ``` + + `` shiftLeft x n `` performs a __left shift__ of `x` by `n` bits: + + ``` + shiftLeft +256 2 + ``` + + `` shiftRight x n `` performs a __right arithmetic shift__ of `x` by `n` + bits: + + ``` + shiftRight -256 2 + ``` + + `` shiftRightL x n `` performs a __right logical shift__ of `x` by `n` + bits: + + ``` + shiftRightL -1 40 + ``` + + # Conversion to other types + + `` toText `` gives the textual representation of an {type Int}. For + positive numbers, the `+` sign is omitted: + + ``` + toText +1720 + ``` + + ``` + toText -1720 + ``` + + `` truncate0 `` converts to {type Nat}, mapping any negative integers to + ``0``: + + ``` + truncate0 +123 + ``` + + ``` + truncate0 -123 + ``` + + `` toRepresentation `` casts the 64-bit representation of an {type Int} as + a {type Nat}. + + ``` + toRepresentation -1 + ``` + + `` toFloat `` converts to {type Float}: + + ``` + toFloat +2116 + ``` + }} + +Int.ediv : Int -> Int -> Int +Int.ediv x y = + use Int * / == + signum y + * (if y == minInt then if signum x == -1 then -1 else +0 + else x / Nat.toInt (Int.abs y)) + +Int.ediv.doc : Doc +Int.ediv.doc = + use Int / <= + {{ + Euclidean integer division. `` ediv x y `` is obtained by finding the largest + multiple of `y` not exceeding `x` and dividing that by `y`. + + If `y` is positive, `` ediv x y `` is equivalent to ``x / y``. + + # Warning: division by zero + + Passing a zero as the divisor (the second argument), as in ``ediv x +0``, + will halt your program with a runtime error. Use this function only if you + happen to know that the divisor will never be zero. Otherwise use + {safeEdiv}. + + # Examples + + ``` + ediv +17 +5 + ``` + + ``` + ediv +17 -5 + ``` + + ``` + ediv -17 +5 + ``` + + ``` + ediv -17 -5 + ``` + + # Mathematical properties + + `` ediv x y `` is defined as the largest {type Int} `q` such that + ``q * x <= y``. + }} + +test> Int.ediv.tests.adjunction = runs 100 do + use Int != * / <= == >= + use gen int + x = int() + y = int() + p = x * y + _ = "Guard against overflow and div-by-zero" + if y != +0 && x != +0 && p / y == x && p / x == y then + a = ediv x y * y <= x + b = ediv (x * y) y >= x + expect (a && b) + else Test.ok + +Int.emod : Int -> Int -> Nat +Int.emod x y = + use Int + mod + y' = Int.fromRepresentation (Int.abs y) + Int.toRepresentation (mod (mod x y' + y') y') + +Int.emod.doc : Doc +Int.emod.doc = + use Int * + == emod + {{ + `` emod x y `` gets the modulus of dividing `x` by `y`, which is the + {type Nat} `r` such that ``ediv x y * y + r == x``. + + This modulus operation differs from {Int.mod} in that the result is always a + natural number. + + # Examples + + ``` + emod +9 +3 + ``` + + ``` + emod +10 +3 + ``` + + ``` + emod -10 -3 + ``` + + ``` + emod +10 -3 + ``` + + ``` + emod -10 +3 + ``` + }} + +-- builtin Int.fromRepresentation : Nat -> Int + +Int.fromRepresentation.doc : Doc +Int.fromRepresentation.doc = + use Int fromRepresentation + {{ + Constructs an {type Int} from its representation as a 64-bit word of type + {type Nat}. + + # Examples + + ``` + fromRepresentation 1 + ``` + + ``` + fromRepresentation 18446744073709551615 + ``` + }} + +-- builtin Int.fromText : Text -> Optional Int + +Int.fromText.doc : Doc +Int.fromText.doc = + use Int fromText + {{ + Constructs an {type Int} from any valid {type Int} literal syntax as a + {type Text} value. + + # Examples + + ``` + fromText "-0xff" + ``` + + The `+` sign is optional for positive integers: + + ``` + fromText "42" + ``` + + If the input is not a valid {type Int} literal syntax, then the result is + {None}: + + ``` + fromText "hello" + ``` + + Also, if the input is a valid {type Int} literal syntax, but the value is + outside the range of a {type Int}, then the result is {None}: + + ``` + fromText "0x8000000000000000" + ``` + }} + +Int.gcd : Int -> Int ->{Abort} Nat +Int.gcd x y = + use Int == + go x y = match y with + +0 -> Int.abs x + _ -> go y (Int.mod x y) + if x == +0 || y == +0 then abort else go x y + +Int.gcd.doc : Doc +Int.gcd.doc = + use Abort toOptional + use Int gcd + {{ + `` gcd x y `` returns the greatest common divisor of `x` and `y`, or calls + {abort} if either `x` or `y` are zero. The result is never negative, so this + function returns {type Nat}. + + # Examples + + ``` + toOptional (do gcd +8 +12) () + ``` + + ``` + toOptional (do gcd -52 +24) () + ``` + + ``` + toOptional (do gcd +1 -900) () + ``` + + ``` + toOptional (do gcd -15 -18) () + ``` + }} + +test> Int.gcd.tests.commonDivisor = runs 100 do + use Int emod gcd + use Nat toInt + x = positiveInt() + y = positiveInt() + toDefault! (do Test.fail) do + dividesX = emod x (toInt (gcd x y)) === 0 + dividesY = emod y (toInt (gcd x y)) === 0 + expect (dividesX && dividesY) + +test> Int.gcd.tests.multipleOfAnyCD = + runs 100 do + use Int maybeMultiply + x = positiveInt() + y = positiveInt() + z = positiveInt() + a = + toDefault! (do true) do + Int.emod + (Nat.toInt (Int.gcd (maybeMultiply x z) (maybeMultiply y z))) z + === 0 + expect a + +-- builtin Int.increment : Int -> Int + +Int.increment.doc : Doc +Int.increment.doc = + use Int increment + {{ + Increments an {type Int} by one: + + # Examples + + ``` + increment +2 + ``` + + Incrementing `` maxInt `` wraps around and returns ``minInt``: + + ``` + increment maxInt + ``` + }} + +Int.inRange : Int -> Int -> Int -> Boolean +Int.inRange fromInclusive toExclusive x = + Universal.gteq x fromInclusive && Universal.lt x toExclusive + +Int.inRange.doc : Doc +Int.inRange.doc = + use Int inRange + {{ + `` inRange x y n `` returns true if `n` is between `x` (inclusive) and `y` + (exclusive). That is, if `n` is at least `x` and strictly below `y`. + + # Examples + + ``` + inRange +1 +3 +2 + ``` + + ``` + inRange +1 +3 +3 + ``` + + ``` + inRange +1 +3 +1 + ``` + }} + +test> Int.inRange.test = runs 100 do + use gen int + x = int() + y = int() + z = int() + match List.sort [x, y, z] with + [x, y, z] -> expect (Int.inRange x z y || z === y) + _ -> expect true + +-- builtin Int.isEven : Int -> Boolean + +Int.isEven.doc : Doc +Int.isEven.doc = + use Int isEven + {{ + Checks if a number is even. + + # Examples + + ``` + isEven +2 + ``` + + ``` + isEven -9 + ``` + }} + +Int.isNegative : Int -> Boolean +Int.isNegative i = + use Int < + i < +0 + +Int.isNegative.doc : Doc +Int.isNegative.doc = + use Int isNegative + {{ + `` isNegative n `` returns `` true `` if the {type Int} `n` is strictly less + than ``+0``. + + # Examples + + ``` + isNegative -1 + ``` + + ``` + isNegative +0 + ``` + + ``` + isNegative +1 + ``` + }} + +-- builtin Int.isOdd : Int -> Boolean + +Int.isOdd.doc : Doc +Int.isOdd.doc = + use Int isOdd + {{ + Checks if a number is odd. + + # Examples + + ``` + isOdd +2 + ``` + + ``` + isOdd -9 + ``` + }} + +Int.lcm : Int -> Int ->{Abort} Int +Int.lcm a b = + use Int * / + use Nat toInt + toInt (Int.abs (a * b)) / toInt (Int.gcd a b) + +Int.lcm.doc : Doc +Int.lcm.doc = + use Int lcm + {{ + Returns the least common multiple of two {type Int}s, or calls {abort} if + either argument is zero. + + # Example + + ``` + toOptional! do lcm +4 +6 + ``` + + ``` + toOptional! do lcm +4 -6 + ``` + + ``` + toOptional! do lcm -4 -6 + ``` + + ``` + toOptional! do lcm +0 +0 + ``` + }} + +-- builtin Int.leadingZeros : Int -> Nat + +Int.leadingZeros.doc : Doc +Int.leadingZeros.doc = + use Int leadingZeros + {{ + Counts the number of zero bits at the front (i.e. the left) of an {type Int}. + + # Examples + + ``` + leadingZeros +0 + ``` + + ``` + leadingZeros +16777216 + ``` + + ``` + leadingZeros -1 + ``` + + ``` + leadingZeros maxInt + ``` + }} + +Int.max : Int -> Int -> Int +Int.max a b = + use Int < + if a < b then b else a + +Int.max.doc : Doc +Int.max.doc = + use Int max + {{ + `` max a b `` returns the greater of the two numbers `a` and `b`. + + # Examples + + ``` + max +1 +2 + ``` + }} + +Int.maxInt : Int +Int.maxInt = +9223372036854775807 + +Int.maxInt.doc : Doc +Int.maxInt.doc = {{ The maximum value of an {type Int}, 2^63 - 1. }} + +Int.maxValue.doc : Doc +Int.maxValue.doc = {{ The largest value representable by an {type Int}. }} + +Int.maybeMultiply : Int -> Int ->{Abort} Int +Int.maybeMultiply x y = + use Int * / == + p = x * y + if p / x == y then p else abort + +Int.maybeMultiply.doc : Doc +Int.maybeMultiply.doc = + use Int maybeMultiply + {{ + Multiplies two {type Int} values. Calls {abort} if the result overflows the + size of {type Int}. + + # Examples + + ``` + toOptional! do maybeMultiply +3 +3 + ``` + + ``` + toOptional! do maybeMultiply maxInt +2 + ``` + }} + +Int.min : Int -> Int -> Int +Int.min a b = + use Int > + if a > b then b else a + +Int.min.doc : Doc +Int.min.doc = + use Int min + {{ + `` min a b `` returns the lesser of the two numbers `a` and `b`. + + # Examples + + ``` + min +1 +2 + ``` + }} + +Int.minInt : Int +Int.minInt = -9223372036854775808 + +Int.minInt.doc : Doc +Int.minInt.doc = {{ The minimum value of an {type Int}, -2^63. }} + +-- builtin Int.mod : Int -> Int -> Int + +Int.mod.doc : Doc +Int.mod.doc = + use Int * + / == mod + {{ + `` mod x y `` gets the modulus of dividing `x` by `y`, which is the number + `r` such that ``x / y * y + r == x``. + + This uses flooring division (truncating towards negative infinity — see {/}). + This coincides with {Int.emod} when the divisor (the second argument) is + positive. + + # Examples + + ``` + mod +9 +3 + ``` + + ``` + mod +10 +3 + ``` + + ``` + mod -10 -3 + ``` + + ``` + mod +10 -3 + ``` + + ``` + mod -10 +3 + ``` + }} + +test> Int.mod.test = runs 1000 do + use Int * - / == + use gen int + a = int() + n = int() + expect (n == +0 || a == minInt && n == -1 || Int.mod a n == a - n * a / n) + +-- builtin Int.negate : Int -> Int + +Int.negate.doc : Doc +Int.negate.doc = + use Int negate + {{ + Negate an {type Int}. + + # Examples + + ``` + negate +0 + ``` + + ``` + negate +1 + ``` + + ``` + negate -1 + ``` + + ``` + negate maxInt + ``` + + Note that the negative of the minimum value of an {type Int} is itself, + because the positive value is too large to fit in an {type Int}: + + ``` + negate minInt + ``` + }} + +-- builtin Int.or : Int -> Int -> Int + +Int.or.doc : Doc +Int.or.doc = + use Int or + {{ + `` or x y `` is the bitwise OR operation on corresponding bits in `x` and + `y`. If both bits in a given position are ``0``, the bit at that position in + the result will be ``0``, otherwise ``1``. + + # Examples + + ``` + or +5 +3 + ``` + + ``` + or +2 +8 + ``` + }} + +-- builtin Int.popCount : Int -> Nat + +Int.popCount.doc : Doc +Int.popCount.doc = + use Int popCount + {{ + Counts the number of `1` bits in an {type Int}. + + ``` + popCount +16777215 + ``` + + ``` + popCount +0 + ``` + + ``` + popCount -1 + ``` + + ``` + popCount minInt + ``` + }} + +-- builtin Int.pow : Int -> Nat -> Int + +Int.pow.doc : Doc +Int.pow.doc = + use Int pow + {{ + `` pow x y `` raises `x` to the power of `y`. Note that the power is a + {type Nat}. + + ``` + pow +2 24 + ``` + }} + +Int.product : [Int] -> Int +Int.product = + use Int * + List.foldLeft (*) +1 + +Int.product.doc : Doc +Int.product.doc = + use Int product + {{ + `` product ns `` returns the product of all the values in `ns`. If `ns` is + empty, returns ``1``. + + # Examples + + ``` + product [-1, +2, -3, +4] + ``` + + ``` + product [+12] + ``` + + ``` + product [] + ``` + }} + +Int.range : Int -> Int -> [Int] +Int.range start stopExclusive = + use Int - + Int.rangeClosed start (stopExclusive - +1) + +Int.range.doc : Doc +Int.range.doc = + use Int range + {{ + `` range x y `` returns all the numbers between `x` (inclusive) and `y` + (exclusive) as a {type List}. + + # Examples + + ``` + range +1 +5 + ``` + + ``` + range -3 +3 + ``` + }} + +Int.range.examples.invalid.descFromNeg : [Int] +Int.range.examples.invalid.descFromNeg = Int.range +0 -1 + +Int.range.examples.invalid.descFromPos : [Int] +Int.range.examples.invalid.descFromPos = Int.range +1 +0 + +Int.range.examples.valid.ascFromNeg : [Int] +Int.range.examples.valid.ascFromNeg = Int.range -1 +0 + +Int.range.examples.valid.ascFromNeg2 : [Int] +Int.range.examples.valid.ascFromNeg2 = Int.range -1 +1 + +Int.range.examples.valid.ascFromPos : [Int] +Int.range.examples.valid.ascFromPos = Int.range +0 +1 + +test> Int.range.tests.invalid.descFromNeg = + check (examples.invalid.descFromNeg === []) + +test> Int.range.tests.invalid.descFromPos = + check (examples.invalid.descFromPos === []) + +test> Int.range.tests.valid.ascFromNeg = + check (examples.valid.ascFromNeg === [-1]) + +test> Int.range.tests.valid.ascFromNeg2 = + check (examples.valid.ascFromNeg2 === [-1, +0]) + +test> Int.range.tests.valid.ascFromPos = + check (examples.valid.ascFromPos === [+0]) + +Int.rangeClosed : Int -> Int -> [Int] +Int.rangeClosed start stop = + use Int + + f : Int -> Optional (Int, Int) + f i = if Universal.lteq i stop then Some (i, i + +1) else None + List.unfold start f + +Int.rangeClosed.doc : Doc +Int.rangeClosed.doc = + use Int rangeClosed + {{ + `` rangeClosed x y `` returns all the numbers between `x` (inclusive) and `y` + (inclusive) as a {type List}. + + # Examples + + ``` + rangeClosed +1 +5 + ``` + + ``` + rangeClosed -3 +3 + ``` + }} + +Int.safeDiv : Int -> Int -> Optional Int +Int.safeDiv x y = + use Int / == + if y == +0 then None else Some (x / y) + +Int.safeDiv.doc : Doc +Int.safeDiv.doc = + {{ + Safe (flooring) integer division. Returns {None} if the divisor (the second + argument) is zero, otherwise `` safeDiv x y `` is equal to `` + Some (Int.div x y) `` + }} + +Int.safeEdiv : Int -> Int -> Optional Int +Int.safeEdiv x y = + use Int == + if y == +0 then None else Some (ediv x y) + +Int.safeEdiv.doc : Doc +Int.safeEdiv.doc = + {{ + Safe integer division. Returns {None} if the divisor (the second argument) is + zero, otherwise `` safeEdiv x y `` is equal to ``Some (ediv x y)``. + }} + +Int.safeEmod : Int -> Int -> Optional Nat +Int.safeEmod m n = + use Int == + if n == +0 then None else Some (Int.emod m n) + +Int.safeEmod.doc : Doc +Int.safeEmod.doc = + use Int safeEmod + {{ + Returns the remainder of the division of the first argument by the second, or + {None} if the second argument is zero. Otherwise the result is the same as + {Int.emod}. + + # Example + + ``` + safeEmod +12 +5 + ``` + + ``` + safeEmod +5 +0 + ``` + }} + +Int.safeMod : Int -> Int -> Optional Int +Int.safeMod m n = + use Int == + if n == +0 then None else Some (Int.mod m n) + +Int.safeMod.doc : Doc +Int.safeMod.doc = + use Int safeMod + {{ + The remainder of dividing one {type Int} by another, or {None} if the divisor + is zero. Otherwise the same as {Int.mod}. + + This uses flooring division (truncating towards negative infinity), so the + result has the same sign as the divisor. + + # Examples + + ``` + safeMod +17 +5 + ``` + + ``` + safeMod +17 +0 + ``` + + ``` + safeMod +17 -5 + ``` + + ``` + safeMod -17 +5 + ``` + }} + +-- builtin Int.shiftLeft : Int -> Nat -> Int + +Int.shiftLeft.doc : Doc +Int.shiftLeft.doc = + use Int * == pow shiftLeft + {{ + `` shiftLeft x n `` performs a __left shift__ of `x` by `n` bits. Discards + any bits shifted off to the left, and adds `n` unset bits on the right side. + + This is equivalent to multiplying `x` by `` 2 `` raised to the power of `n`. + + # Examples + + ``` + shiftLeft +1 8 + ``` + + ``` + shiftLeft +256 2 + ``` + + ``` + shiftLeft minInt 1 + ``` + + ``` + shiftLeft +4 7 == +4 * pow +2 7 + ``` + + ``` + shiftLeft maxInt 7 == maxInt * pow +2 7 + ``` + }} + +-- builtin Int.shiftRight : Int -> Nat -> Int + +Int.shiftRight.doc : Doc +Int.shiftRight.doc = + use Int / == pow shiftRight + {{ + `` shiftRight x n `` performs a __right arithmetic shift__ of `x` by `n` + bits. Discards any bits shifted off to the right, and shifts in `n` copies of + the leftmost bit. + + This is equivalent to dividing `x` by `` 2 `` raised to the power of `n`. + + # Examples + + ``` + shiftRight +256 8 + ``` + + ``` + shiftRight +256 2 + ``` + + ``` + shiftRight minInt 56 + ``` + + ``` + shiftRight +9999999 10 == +9999999 / pow +2 10 + ``` + + ``` + shiftRight maxInt 7 == maxInt / pow +2 7 + ``` + }} + +Int.shiftRightL : Int -> Nat -> Int +Int.shiftRightL x = + Int.fromRepresentation << Nat.shiftRight (Int.toRepresentation x) + +Int.shiftRightL.doc : Doc +Int.shiftRightL.doc = + {{ + Shifts the given {type Int} right by the given number of bits, filling the + leftmost bits with zeros. + + # Example + + ``` + shiftRightL -1 56 + ``` + }} + +-- builtin Int.signum : Int -> Int + +Int.signum.doc : Doc +Int.signum.doc = + {{ + `` signum x `` gets the sign of `x` by returning `` -1 `` if it's negative, + `` +1 `` if it's positive, and `` +0 `` if it's zero. + + # Examples + + ``` + signum +0 + ``` + + ``` + signum maxInt + ``` + + ``` + signum minInt + ``` + }} + +Int.sum : [Int] -> Int +Int.sum = + use Int + + List.foldLeft (+) +0 + +Int.sum.doc : Doc +Int.sum.doc = + use Int sum + {{ + `` sum ns `` returns the sum of all the values in `ns`. If `ns` is empty, + returns ``0``. + + # Examples + + ``` + sum [+1, +2, +3, -4] + ``` + + ``` + sum [+12] + ``` + + ``` + sum [] + ``` + }} + +-- builtin Int.toRepresentation : Int -> Nat + +Int.toRepresentation.doc : Doc +Int.toRepresentation.doc = + use Int toRepresentation + {{ + Casts an {type Int} to an unsigned integer, returning the {type Nat} + represented by the same bits as the {type Int}. + + If the {type Int} is positive, the result is the same value as the {type Int} + itself. If the {type Int} is negative, the result is the two's complement of + the absolute value of the {type Int}. + + # Examples + + ``` + toRepresentation -1 + ``` + + ``` + toRepresentation +0 + ``` + }} + +-- builtin Int.toText : Int -> Text + +Int.toText.doc : Doc +Int.toText.doc = + use Int toText + {{ + Convert an {type Int} to a {type Text}. + + # Examples + + ``` + toText +0 + ``` + + ``` + toText +1 + ``` + + ``` + toText -1 + ``` + + ``` + toText maxInt + ``` + + ``` + toText minInt + ``` + }} + +Int.toTextBase : Nat -> Int -> Optional Text +Int.toTextBase n x = + use Int < + use Text ++ + sign = if x < +0 then "-" else "" + Optional.map + (t -> sign ++ t) (Natural.toText n (Natural.fromNat (Int.abs x))) + +Int.toTextBase.doc : Doc +Int.toTextBase.doc = + use Int toTextBase + {{ + `` toTextBase radix n `` renders the {type Int} `x` into {type Text} in the + specified radix. + + # Examples + + ``` + toTextBase 10 +16777216 + ``` + + ``` + toTextBase 16 +16777216 + ``` + + ``` + toTextBase 16 +3735928559 + ``` + + ``` + toTextBase 2 -9 + ``` + }} + +-- builtin Int.trailingZeros : Int -> Nat + +Int.trailingZeros.doc : Doc +Int.trailingZeros.doc = + use Int trailingZeros + {{ + Counts the number of zero bits at the end (i.e. the right) of an {type Int}. + + # Examples + + ``` + trailingZeros +0 + ``` + + ``` + trailingZeros +16777216 + ``` + + ``` + trailingZeros +1 + ``` + + ``` + trailingZeros (Int.complement maxInt) + ``` + }} + +-- builtin Int.truncate0 : Int -> Nat + +Int.truncate0.doc : Doc +Int.truncate0.doc = + {{ + Converts an {type Int} to a {type Nat} by mapping all negative values to + ``0``. + + # Examples + + ``` + truncate0 -1 + ``` + + ``` + truncate0 +42 + ``` + }} + +-- builtin Int.xor : Int -> Int -> Int + +Int.xor.doc : Doc +Int.xor.doc = + use Int xor + {{ + `` xor x y `` is the bitwise exclusive-OR operation on corresponding bits in + `x` and `y`. The bit at a given position in the result will be `` 1 `` if + that bit differs in the inputs, and `` 0 `` if they are the same. + + # Examples + + ``` + xor +5 +3 + ``` + + ``` + xor +2 +10 + ``` + + ``` + xor -1 -1 + ``` + }} + +IO.arrayOf : a -> Nat ->{IO} mutable.Array {IO} a +IO.arrayOf v l = MArr 0 l (IO.Raw.arrayOf v l) + +IO.arrayOf.doc : Doc +IO.arrayOf.doc = + {{ Creates a new array of the given size, filled with the given value. }} + +IO.byteArray : Nat ->{IO} mutable.ByteArray {IO} +IO.byteArray l = MBArr 0 l (IO.Raw.byteArray l) + +IO.byteArray.doc : Doc +IO.byteArray.doc = + {{ + Creates a new byte array with the given size. The contents are unspecified. + }} + +IO.byteArrayOf : Nat -> Nat ->{IO} mutable.ByteArray {IO} +IO.byteArrayOf b l = MBArr 0 l (IO.Raw.byteArrayOf b l) + +IO.byteArrayOf.doc : Doc +IO.byteArrayOf.doc = + {{ + Creates a new {type mutable.ByteArray} in {type IO}. The array has a length + given by the first argument, and is filled with the low byte of the second + argument. + }} + +IO.byteArrayOf.test : '{IO, Exception} [Result] +IO.byteArrayOf.test = do + b = IO.byteArrayOf 1 10 + z = ByteArray.freeze! b + check (ByteArray.toList z === [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) + +IO.catchAll : '{IO, Exception} a ->{IO} Either Failure a +IO.catchAll thunk = + handle tryEval.impl do catch thunk + with cases + { x } -> x + { Exception.raise f -> _ } -> Left f + +IO.catchAll.doc : Doc +IO.catchAll.doc = + {{ + Run a delayed computation, catching both runtime failures and failures raised + by {Exception.raise}. + + This is similar to {catch} but it also catches runtime failures such as calls + to {bug} or pattern match failures. + + See also {tryEval} + }} + +IO.catchAll.tests.catchArithmeticFailure : '{IO, Exception} [Result] +IO.catchAll.tests.catchArithmeticFailure = + do + verifyAndIgnore do + use Nat / + result = catchAll do 3 / 0 + expected = + Left (Failure (typeLink ArithmeticFailure) "divide by zero" (Any ())) + ensureEqual expected result + +IO.catchAll.tests.catchBug : '{IO, Exception} [Result] +IO.catchAll.tests.catchBug = + do + verifyAndIgnore do + result = catchAll do bug "this bug should be caught" + expected = + Left + (Failure + (typeLink RuntimeFailure) + "builtin.bug" + (Any "this bug should be caught")) + ensureEqual expected result + +-- builtin IO.concurrent.fork : '{IO} a ->{IO} IO.concurrent.ThreadId + +IO.concurrent.fork.doc : Doc +IO.concurrent.fork.doc = + {{ + Forks a computation into a new lightweight thread, returning the + {type ThreadId} of the new thread. The new thread will be terminated when the + computation completes, when the main thread terminates, or when explicitly + killed with {concurrent.kill}. + }} + +IO.concurrent.fork_ : '{IO} () ->{IO} () +IO.concurrent.fork_ = fork >> ignore + +IO.concurrent.fork_.doc : Doc +IO.concurrent.fork_.doc = + {{ + A variant of {fork} that does not return the {type ThreadId} of the forked + thread. + }} + +IO.concurrent.kill : ThreadId ->{IO, Exception} () +IO.concurrent.kill = Either.toException << kill.impl + +IO.concurrent.kill.doc : Doc +IO.concurrent.kill.doc = + {{ + Kills the thread with the given {type ThreadId}, causing it to terminate + immediately with a runtime error. If the thread has already terminated, this + function has no effect. + }} + +-- builtin IO.concurrent.kill.impl : +-- IO.concurrent.ThreadId ->{IO} Either Failure () + +IO.concurrent.MVar.doc : Doc +IO.concurrent.MVar.doc = + use MVar put take + {{ + An {type MVar} is a potentially empty mutable reference cell guarded by a + mutex. They serve as a basis for synchronizing multiple concurrent threads of + execution. + + The primary means of synchronizing with an {type MVar} is to use it like a + one-element channel. The {put} function attempts to fill the cell, and blocks + the thread until this is possible. Meanwhile the {take} function tries to get + the contents of a cell, blocking if the cell is empty. Multiple threads can + {put} or {take} simultaneously, allowing multiple producers and consumers to + coordinate so long as the blocking behavior is appropriate (i.e. producers + potentially blocking until a consumer is available to process their result, + or at least free the single buffer spot). + + However, there are also non-blocking operations like {MVar.tryTake} that + allow either side to continue doing something else if the {type MVar} is not + in the appropriate state for an operation. + }} + +-- builtin IO.concurrent.MVar.isEmpty : IO.concurrent.MVar a ->{IO} Boolean + +IO.concurrent.MVar.isEmpty.doc : Doc +IO.concurrent.MVar.isEmpty.doc = + {{ + Determines whether the {type MVar} contains a value that may be + [taken]({MVar.take}). + }} + +IO.concurrent.MVar.modify : + MVar a -> (a ->{IO, Exception} (a, b)) ->{IO, Exception} b +IO.concurrent.MVar.modify mvar f = + use MVar take tryPut + a = take mvar + attemptLoop a = do + (a', b) = f a + success = tryPut mvar a' + if success then b else attemptLoop (take mvar) () + match catchAll (attemptLoop a) with + Left e -> + _ = tryPut mvar a + Exception.raise e + Right b -> b + +IO.concurrent.MVar.modify.doc : Doc +IO.concurrent.MVar.modify.doc = + use MVar modify + {{ + `` modify v f `` modifies the {type MVar} `v` by applying the function `f` to + its current value. If the {type MVar} is empty, the current thread blocks + until the {type MVar} is filled. If the {type MVar} is modified by another + thread between the time the current thread reads the value and attempts to + modify it, then the function is retried on the modified value until it + succeeds. If the function throws an {type Exception}, the {type MVar} is + restored to its original value and the exception is rethrown. + + The function `f` is passed the current value of the {type MVar} and returns a + pair of the new value and a result value. The result value is returned by the + {modify} function. + }} + +-- builtin IO.concurrent.MVar.new : a ->{IO} IO.concurrent.MVar a + +IO.concurrent.MVar.new.doc : Doc +IO.concurrent.MVar.new.doc = + {{ Creates a new {type MVar} containing a given initial value. }} + +-- builtin IO.concurrent.MVar.newEmpty : '{IO} IO.concurrent.MVar a + +IO.concurrent.MVar.newEmpty.doc : Doc +IO.concurrent.MVar.newEmpty.doc = + {{ Creates a new {type MVar} with no initial contained value. }} + +IO.concurrent.MVar.put : MVar a -> a ->{IO, Exception} () +IO.concurrent.MVar.put = compose2 Either.toException put.impl + +IO.concurrent.MVar.put.doc : Doc +IO.concurrent.MVar.put.doc = + {{ + Puts the given value into the {type MVar}. If the var already contains a + value, this function will block until another thread takes the value and + allows this operation to complete. + }} + +-- builtin IO.concurrent.MVar.put.impl : +-- IO.concurrent.MVar a -> a ->{IO} Either Failure () + +IO.concurrent.MVar.read : MVar a ->{IO, Exception} a +IO.concurrent.MVar.read = Either.toException << read.impl + +IO.concurrent.MVar.read.doc : Doc +IO.concurrent.MVar.read.doc = + {{ + Reads the contents of an {type MVar}, but does not remove the value from the + var. If the var is empty, this operation blocks until another thread fills + the var. + }} + +-- builtin IO.concurrent.MVar.read.impl : +-- IO.concurrent.MVar a ->{IO} Either Failure a + +IO.concurrent.MVar.swap : MVar a -> a ->{IO, Exception} a +IO.concurrent.MVar.swap = compose2 Either.toException swap.impl + +IO.concurrent.MVar.swap.doc : Doc +IO.concurrent.MVar.swap.doc = + {{ + Takes the value of an {type MVar} and replaces it with the given value. If + the var is initially empty, this operation will block. This operation is not + atomic unless there are no other writers for this var. + }} + +-- builtin IO.concurrent.MVar.swap.impl : +-- IO.concurrent.MVar a -> a ->{IO} Either Failure a + +IO.concurrent.MVar.take : MVar a ->{IO, Exception} a +IO.concurrent.MVar.take = Either.toException << take.impl + +IO.concurrent.MVar.take.doc : Doc +IO.concurrent.MVar.take.doc = + {{ + Reads the contents of an {type MVar}, and leaves it empty. If the var is + empty already, this operation will block until a value can be obtained. + }} + +-- builtin IO.concurrent.MVar.take.impl : +-- IO.concurrent.MVar a ->{IO} Either Failure a + +IO.concurrent.MVar.tryModify : + MVar a -> (a ->{IO, Exception} (a, b)) ->{IO, Exception} Optional b +IO.concurrent.MVar.tryModify mvar f = + use MVar tryPut + a = MVar.take mvar + let + (a', b) = + match catchAll do f a with + Left e -> + _ = tryPut mvar a + Exception.raise e + Right x -> x + success = tryPut mvar a' + if success then Some b else None + +IO.concurrent.MVar.tryModify.doc : Doc +IO.concurrent.MVar.tryModify.doc = + {{ + `` tryModify v f `` attempts to modify the {type MVar} `v` by applying the + function `f` to its current value. If the {type MVar} is empty, the current + thread blocks until the {type MVar} is filled. If the {type MVar} is modified + by another thread between the time the current thread reads the value and + attempts to modify it, then {tryModify} returns {None}. If the function + throws an {type Exception}, the {type MVar} is restored to its original value + and the exception is rethrown. + + The function `f` is passed the current value of the {type MVar} and returns a + pair of the new value and a result value. The result value is returned by the + {MVar.modify} function if it succeeds in modifying the {type MVar}. + Otherwise, it returns {None}. + }} + +IO.concurrent.MVar.tryPut : MVar a -> a ->{IO, Exception} Boolean +IO.concurrent.MVar.tryPut mv a = Either.toException (tryPut.impl mv a) + +IO.concurrent.MVar.tryPut.doc : Doc +IO.concurrent.MVar.tryPut.doc = + {{ + Tries to put a value into an {type MVar}. If the var already contains + something, this operation doesn't modify it. Yields + }} + +-- builtin IO.concurrent.MVar.tryPut.impl : +-- IO.concurrent.MVar a -> a ->{IO} Either Failure Boolean + +IO.concurrent.MVar.tryRead : MVar a ->{IO, Exception} Optional a +IO.concurrent.MVar.tryRead mv = Either.toException (tryRead.impl mv) + +IO.concurrent.MVar.tryRead.doc : Doc +IO.concurrent.MVar.tryRead.doc = + {{ + Tries to read a value from an {type MVar}. If the var is empty, yields + nothing. + }} + +-- builtin IO.concurrent.MVar.tryRead.impl : +-- IO.concurrent.MVar a ->{IO} Either Failure (Optional a) + +-- builtin IO.concurrent.MVar.tryTake : IO.concurrent.MVar a ->{IO} Optional a + +IO.concurrent.MVar.tryTake.doc : Doc +IO.concurrent.MVar.tryTake.doc = + {{ + Tries to take the value of an {type MVar} and leave it empty. If the var is + empty, yields nothing. + }} + +IO.concurrent.Promise.doc : Doc +IO.concurrent.Promise.doc = + {{ + A synchronisation primitive that represents a single value which may not yet + be available. + + When created, a {type Promise} is empty. It can then be written to exactly + once, and never be made empty again. + + {{ Promise.read.doc }} + + {{ Promise.write.doc }} + + Albeit simple, {type Promise} can be used in conjunction with {type Ref} and + {cas} to build complex concurrent behaviour and data structures like queues + and semaphores. + }} + +-- builtin IO.concurrent.Promise.new : '{IO} IO.concurrent.Promise a + +IO.concurrent.Promise.new.doc : Doc +IO.concurrent.Promise.new.doc = {{ Creates an empty {type Promise}. }} + +-- builtin IO.concurrent.Promise.read : IO.concurrent.Promise a ->{IO} a + +IO.concurrent.Promise.read.doc : Doc +IO.concurrent.Promise.read.doc = + use Promise read + {{ + {read} on an empty {type Promise} will block until the {type Promise} is + completed. {read} on a written {type Promise} will always immediately return + its content. + }} + +-- builtin IO.concurrent.Promise.tryRead : +-- IO.concurrent.Promise a ->{IO} Optional a + +IO.concurrent.Promise.tryRead.doc : Doc +IO.concurrent.Promise.tryRead.doc = + {{ + Like {Promise.read}, but doesn't block on an empty {type Promise}, and + returns `` None `` immediately instead. + }} + +-- builtin IO.concurrent.Promise.write : +-- IO.concurrent.Promise a -> a ->{IO} Boolean + +IO.concurrent.Promise.write.doc : Doc +IO.concurrent.Promise.write.doc = + use Promise write + {{ + `` write a `` on an empty {type Promise} will set it to `a`, notify any and + all readers currently blocked on a call to {Promise.read}, and return + ``true``. `` write a `` on a written {type Promise} will not modify its + content, and return ``false``. + }} + +IO.concurrent.Promise.write_ : Promise a -> a ->{IO} () +IO.concurrent.Promise.write_ p a = ignore (Promise.write p a) + +IO.concurrent.Promise.write_.doc : Doc +IO.concurrent.Promise.write_.doc = + {{ Like {Promise.write}, but discards the {type Boolean} result }} + +IO.concurrent.sleep : Duration ->{IO, Exception} () +IO.concurrent.sleep d = sleepMicroseconds (truncate0 (countMicroseconds d)) + +IO.concurrent.sleep.doc : Doc +IO.concurrent.sleep.doc = + {{ + `` sleep d `` suspends the execution of the current thread by an amount of + time specified by the {type Duration} `d`. + + If the {type Duration} is negative, it's interpreted as {Duration.zero}. + + # Example + + A one second delay: + + @typecheck ``` + printLine "Before delay" + sleep Duration.second + printLine "After delay" + ``` + }} + +IO.concurrent.sleepMicroseconds : Nat ->{IO, Exception} () +IO.concurrent.sleepMicroseconds = Either.toException << sleepMicroseconds.impl + +IO.concurrent.sleepMicroseconds.doc : Doc +IO.concurrent.sleepMicroseconds.doc = + {{ + `` sleepMicroseconds n `` delays the next IO operation by `n` microseconds. + + # Example + + A one-second delay: + + @typecheck ``` + do + printLine "Before delay" + sleepMicroseconds 1000000 + printLine "After delay" + ``` + }} + +IO.concurrent.sleepMicroseconds.examples.ex1 : '{IO, Exception} () +IO.concurrent.sleepMicroseconds.examples.ex1 = do + printLine "Before delay" + sleepMicroseconds 1000000 + printLine "After delay" + +-- builtin IO.concurrent.sleepMicroseconds.impl : Nat ->{IO} Either Failure () + +-- builtin IO.concurrent.STM.atomically : '{IO.concurrent.STM} a ->{IO} a + +IO.concurrent.STM.atomically.doc : Doc +IO.concurrent.STM.atomically.doc = + {{ + Executes a transaction as an atomic unit. Within an atomic transaction, all + accesses to {type TVar} values are ensured to be consistent. + + Generally it is best to make transactions as small as possible. Atomicity is + ensured by executing the operations, checking if they were consistent, and + retrying if they were not. Thus, short transactions could continuously + invalidate longer ones if they modify common resources. + + # Example + + The following example shows two threads using {STM.atomically} to increment + a shared counter {type TVar} and communicate their completion to the main + thread using a {type TMVar}. The first thread increments the counter by 1, + and the second thread increments the counter by 2. The final value of the + counter is 3. + + @source{atomically.doc.example} + }} + +IO.concurrent.STM.atomically.doc.example : '{IO} Nat +IO.concurrent.STM.atomically.doc.example = do + use Nat + + use STM atomically + use TMVar newEmpty put take + use TVar modify + counter = atomically do TVar.new 0 + done1 = atomically newEmpty + done2 = atomically newEmpty + thread1 = fork do + atomically do + modify counter Nat.increment + put done1 () + thread2 = fork do + atomically do + modify counter (x -> x + 2) + put done2 () + atomically do + take done1 + take done2 + TVar.read counter + +IO.concurrent.STM.doc : Doc +IO.concurrent.STM.doc = + {{ + This ability is used to represent atomic transactions that may access shared + mutable memory. + + The fundamental unit of shared memory is the {type TVar}, which is a single + mutable reference. Transactions are built by [reading]({TVar.read}) and + [writing]({TVar.write}) these references to produce a computation requiring + the {type STM} ability. Then such a computation can be performed + {STM.atomically} using the {type IO} ability. During such an atomic + transaction, all reference access is ensured to be consistent. + + Transactions are ensured to be atomic by checking that their shared memory + access has been consistent for the duration of the transaction. Thus, short + transactions can invalidate longer ones by repeatedly modifying resources + that they share. Thus, it is best to keep transactions short to avoid + starvation. + }} + +IO.concurrent.STM.docs.glossary.FIFO : Doc +IO.concurrent.STM.docs.glossary.FIFO = {{ [FIFO]({content}) }} + +IO.concurrent.STM.docs.glossary.FIFO.content : Doc +IO.concurrent.STM.docs.glossary.FIFO.content = + {{ + First In, First Out (or "FIFO") means processing in first-come, first-serve + order, where the item which has been in the queue longest will be processed + first. See + [the Wikipedia page](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)) + for more information. + }} + +-- builtin IO.concurrent.STM.retry : '{IO.concurrent.STM} a + +IO.concurrent.STM.retry.doc : Doc +IO.concurrent.STM.retry.doc = + {{ + Forces a transaction to abort and retry. + + This can be used to abort a transaction as the result of testing the value of + shared memory. If a transaction is aborted due to this operation, it will not + actually retry until one of the accessed [TVars]({type TVar}) is modified by + some other thread, since the transaction would presumably just end up + retrying again. + + # Example + + The following example shows a transaction that will retry if the value of + the {type TVar} is not 0. + + @source{retry.doc.example} + }} + +IO.concurrent.STM.retry.doc.example : '{IO} Nat +IO.concurrent.STM.retry.doc.example = do + use Nat != > + use STM atomically + use TVar read + counter = atomically do TVar.new 10 + thread = fork do + while (c -> c > 0) do + atomically do + TVar.modify counter Nat.decrement + read counter + atomically do + x = read counter + if x != 0 then retry() else x + +IO.concurrent.STM.STMFailure.doc : Doc +IO.concurrent.STM.STMFailure.doc = + {{ A type of {type Failure} that is raised when an STM transaction fails. }} + +IO.concurrent.STM.TMap.contains : Bytes -> TMap a ->{STM} Boolean +IO.concurrent.STM.TMap.contains b m = isSome (TMap.lookup b m) + +IO.concurrent.STM.TMap.contains.doc : Doc +IO.concurrent.STM.TMap.contains.doc = + use TMap contains insert + use fromList impl + {{ + `` contains key t `` returns `` true `` if the key is found and `` false `` + otherwise. + + @typecheck ``` + t = TMap.empty() + insert 0xs0000 "🎩" t + contains 0xs0000 t + ``` + + An entry inserted by {insert} will + }} + +IO.concurrent.STM.TMap.delete : Bytes -> TMap a ->{STM} () +IO.concurrent.STM.TMap.delete b tm = + use F Empty + use Nat + + use TVar read write + go i b m = + match (Bytes.at i b, m) with + (None, TMap tv _) -> write tv None + (Some h, TMap _ children) -> + child = List.unsafeAt h children + match read child with + One b2 a0 + | b === b2 -> write child Empty + | otherwise -> + c = TMap.empty() + write child (Many c) + _ = insert.impl (i + 1) b2 c + go (i + 1) b c + Many m@(TMap tv children) -> + go (i + 1) b m + isEmpty c = match read c with + Empty -> true + _ -> false + if List.all isEmpty children && isNone (read tv) then + write child Empty + else () + Empty -> () + go 0 b tm + +IO.concurrent.STM.TMap.delete.doc : Doc +IO.concurrent.STM.TMap.delete.doc = + use TMap delete + use fromList impl + {{ + `` delete key tm `` deletes a key from `tm`. + + @typecheck ``` + t = TMap.empty() + TMap.insert 0xs0000 "🪐" t + delete 0xs0000 t + ``` + + This will shorten the height of the tree if this key is the last remaining + key at one of the levels of the tree. + }} + +IO.concurrent.STM.TMap.doc : Doc +IO.concurrent.STM.TMap.doc = + use TMap empty insert lookup + use fromList impl + {{ + A transactional, very low contention concurrent mutable map, keyed by + {type Bytes}. If there are many concurrent writes, this will generally be + more performant than sticking a {type Map} in a {type TVar}, since distinct + keys can be updated without contention. + + @signatures{empty, lookup, insert, TMap.delete, TMap.contains} + + @typecheck ``` + t = empty() + insert 0xs0012 "🌻" t + insert 0xs9212 "🌹" t + lookup 0xs9212 t + ``` + + # Implementation notes + + The implementation is based on byte tries. At each level of the trie, there + is a {type TVar} for the current value, and 256 children tries (one for + each byte value). So to {insert}, we have to {TVar.read} the spine, and + then do one {TVar.write} once we reach the leaf that needs to be altered. + }} + +IO.concurrent.STM.TMap.empty : '{STM} TMap a +IO.concurrent.STM.TMap.empty _ = + use F Empty + use TVar new + TMap + (new None) + [ new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + , new Empty + ] + +IO.concurrent.STM.TMap.empty.doc : Doc +IO.concurrent.STM.TMap.empty.doc = {{ Creates an empty {type TMap}. }} + +IO.concurrent.STM.TMap.impl.F.doc : Doc +IO.concurrent.STM.TMap.impl.F.doc = {{ Implementation detail of {type TMap}. }} + +IO.concurrent.STM.TMap.insert : Bytes -> a -> TMap a ->{STM} () +IO.concurrent.STM.TMap.insert b a m = insert.impl 0 b a m + +IO.concurrent.STM.TMap.insert.doc : Doc +IO.concurrent.STM.TMap.insert.doc = + use TMap insert + use fromList impl + {{ + `` insert key v t `` inserts an entry in the map, replacing any existing + entry that may exist for `key`. + + @typecheck ``` + t = TMap.empty() + insert 0xs0000 "🎩" t + insert 0xsabb8 "👒" t + insert 0xs9191 "🧢" t + ``` + }} + +IO.concurrent.STM.TMap.insert.impl : Nat -> Bytes -> a -> TMap a ->{STM} () +IO.concurrent.STM.TMap.insert.impl i b a m = match (Bytes.at i b, m) with + (None, TMap tv _) -> TVar.write tv (Some a) + (Some h, TMap _ children) -> + use IO.concurrent.STM.TMap.insert impl + use Nat + + use TVar write + child = List.unsafeAt h children + match TVar.read child with + One b2 a0 + | b === b2 -> write child (One b2 a) + | otherwise -> + c = TMap.empty() + write child (Many c) + impl (i + 1) b2 a0 c + impl (i + 1) b a c + Many m -> impl (i + 1) b a m + F.Empty -> write child (One b a) + +IO.concurrent.STM.TMap.lookup : Bytes -> TMap a ->{STM} Optional a +IO.concurrent.STM.TMap.lookup b m = + use Nat + + use TVar read + go i m = match (Bytes.at i b, m) with + (None, TMap tv _) -> read tv + (Some h, TMap _ children) -> + match read (List.unsafeAt h children) with + One b2 a | b === b2 -> Some a + Many m -> go (i + 1) m + _ -> None + go 0 m + +IO.concurrent.STM.TMap.lookup.doc : Doc +IO.concurrent.STM.TMap.lookup.doc = + use TMap lookup + use fromList impl + {{ + `` lookup key t `` returns {Some} if the key is found and {None} otherwise. + + @typecheck ``` + t = TMap.empty() + TMap.insert 0xs0000 "🎩" t + lookup 0xs0000 t + ``` + }} + +IO.concurrent.STM.TMap.tests.insertsAndDeletes : '{IO} [Result] +IO.concurrent.STM.TMap.tests.insertsAndDeletes _ = STM.atomically do + use List all foreach + t = TMap.empty() + keys = Nat.range 0 100 |> List.map encodeNat64be |> List.indexed + foreach (cases (k, i) -> TMap.insert k i t) keys + inserted = all (cases (k, i) -> TMap.lookup k t === Some i) keys + foreach (k -> TMap.delete (at1 k) t) keys + deleted = all (k -> Boolean.not (TMap.contains (at1 k) t)) keys + if inserted && deleted then [Ok "Passed"] else [Result.Fail "❌"] + +IO.concurrent.STM.TMap.tests.insertsAndDeletes.doc : Doc +IO.concurrent.STM.TMap.tests.insertsAndDeletes.doc = + {{ Basic unit test of insertion and deletion. }} + +IO.concurrent.STM.TQueue.boundedEnqueue : Nat -> a -> TQueue a ->{STM} () +IO.concurrent.STM.TQueue.boundedEnqueue maxSize a = cases + TQueue elems _ -> + use List +: + use Nat >= + es = TVar.read elems + if List.size es >= maxSize then retry() else TVar.write elems (a +: es) + +IO.concurrent.STM.TQueue.boundedEnqueue.doc : Doc +IO.concurrent.STM.TQueue.boundedEnqueue.doc = + {{ + `` boundedEnqueue maxSize a tq `` enqueues `a` to `tq`, blocking until the + resulting queue size does not exceed `maxSize`. + + Also see {tryBoundedEnqueue} for a version that does not block. + }} + +IO.concurrent.STM.TQueue.dequeue : TQueue a ->{STM} a +IO.concurrent.STM.TQueue.dequeue tq = match tryDequeue tq with + None -> retry() + Some a -> a + +IO.concurrent.STM.TQueue.dequeue.doc : Doc +IO.concurrent.STM.TQueue.dequeue.doc = + use Nat == + {{ + Removes and returns the front item in the queue (the oldest {enqueue}'d item) + in {{ FIFO }} order. + + # Example + + @typecheck ``` + q = TQueue.empty() + enqueue 1 q + enqueue 2 q + dequeue q == 1 + ``` + }} + +IO.concurrent.STM.TQueue.dequeueNonce : TQueue a ->{STM} Nat +IO.concurrent.STM.TQueue.dequeueNonce = cases TQueue _ nonce -> TVar.read nonce + +IO.concurrent.STM.TQueue.dequeueNonce.doc : Doc +IO.concurrent.STM.TQueue.dequeueNonce.doc = + {{ + Returns a {type Nat} which is incremented on each {dequeue}. This can be + useful for applications that wish to detect when a consumer is no longer + actively pulling values from the {type TQueue}. + }} + +IO.concurrent.STM.TQueue.doc : Doc +IO.concurrent.STM.TQueue.doc = + {{ + A mutable queue, suitable for use by multiple concurrent threads. Implemented + using {type STM}. + + # Constructing a queue + + Construct a new empty queue: + + @signature{TQueue.empty} + + Construct a queue from a list of elements: + + @signature{TQueue.fromList} + + # Adding to the queue + + Enqueue an element: + + @signature{enqueue} + + Enqueue a list of elements: + + @signature{enqueueAll} + + Put an element at the front of the queue: + + @signature{pushback} + + # Bounded enqueue operations + + Enqueue an element, waiting for space to become available if the queue is + beyond a certain size: + + @signature{boundedEnqueue} + + Enqueue an element or return `` false `` if the queue is beyond a certain + size: + + @signature{tryBoundedEnqueue} + + Enqueue a list of elements or return `` false `` if the queue is beyond a + certain size: + + @signature{tryBoundedEnqueueAll} + + # Taking from the queue + + Dequeue an element, waiting for an element to become available if the queue + is empty: + + @signature{dequeue} + + Dequeue an element or return `` None `` if the queue is empty: + + @signature{tryDequeue} + + Get the next element in the queue without removing it: + + @signature{peek} + + Get the element at the front of the queue or return `` None `` if the queue + is empty: + + @signature{tryPeek} + + # Querying the queue + + Get the number of elements in the queue: + + @signature{TQueue.size} + + Get the number of elements that have been dequeued: + + @signature{dequeueNonce} + + Get a {type List} of all the elements currently in the queue: + + @signature{TQueue.elements} + }} + +IO.concurrent.STM.TQueue.elements : TQueue a ->{STM} [a] +IO.concurrent.STM.TQueue.elements = cases TQueue elems _ -> TVar.read elems + +IO.concurrent.STM.TQueue.elements.doc : Doc +IO.concurrent.STM.TQueue.elements.doc = + {{ + `` TQueue.elements tq `` returns all the elements of the queue. The next + element to be {dequeue}'d will be the last element of the returned + {type List} and the first element of the {type List} is the most recently + {enqueue}'d. + }} + +IO.concurrent.STM.TQueue.empty : '{STM} TQueue a +IO.concurrent.STM.TQueue.empty = do TQueue (TVar.new []) (TVar.new 0) + +IO.concurrent.STM.TQueue.empty.doc : Doc +IO.concurrent.STM.TQueue.empty.doc = + {{ + Creates a {type TQueue} with no elements, so `` elements TQueue.empty === [] + `` + }} + +IO.concurrent.STM.TQueue.enqueue : a -> TQueue a ->{STM} () +IO.concurrent.STM.TQueue.enqueue a = cases + TQueue elems _ -> TVar.modify elems (es -> a List.+: es) + +IO.concurrent.STM.TQueue.enqueue.doc : Doc +IO.concurrent.STM.TQueue.enqueue.doc = + {{ + Adds an element to the back of the queue. It will be {dequeue}'d in {{ FIFO + }} order, after elements currently in the {type TQueue}. + }} + +IO.concurrent.STM.TQueue.enqueueAll : [a] -> TQueue a ->{STM} () +IO.concurrent.STM.TQueue.enqueueAll as = cases + TQueue elems _ -> TVar.modify elems (es -> as List.++ es) + +IO.concurrent.STM.TQueue.enqueueAll.doc : Doc +IO.concurrent.STM.TQueue.enqueueAll.doc = + use Nat == + {{ + `` enqueueAll xs tq `` enqueues multiple elements. The last element of `xs` + will be the first to {dequeue}. + + # Example + + @typecheck ``` + q = TQueue.empty() + enqueueAll [1, 2, 3] q + dequeue q == 3 + ``` + }} + +IO.concurrent.STM.TQueue.fromList : [a] ->{STM} TQueue a +IO.concurrent.STM.TQueue.fromList elems = + use TVar new + TQueue (new elems) (new 0) + +IO.concurrent.STM.TQueue.fromList.doc : Doc +IO.concurrent.STM.TQueue.fromList.doc = + use Nat == + {{ + Creates a new {type TQueue} from a list of elements. The last element of the + list will be the first {TQueue.dequeue}'d, so for example, + ``dequeue (fromList [1, 2, 3]) == 3``. + }} + +IO.concurrent.STM.TQueue.peek : TQueue a ->{STM} a +IO.concurrent.STM.TQueue.peek = cases + TQueue elems _ -> + match TVar.read elems with + [] -> retry() + init :+ last -> last + +IO.concurrent.STM.TQueue.peek.doc : Doc +IO.concurrent.STM.TQueue.peek.doc = + {{ + Read the next item that would be returned by {dequeue}, without removing it + from the queue. + }} + +IO.concurrent.STM.TQueue.pushback : a -> TQueue a ->{STM} () +IO.concurrent.STM.TQueue.pushback a = cases + TQueue elems _ -> TVar.modify elems (es -> es List.:+ a) + +IO.concurrent.STM.TQueue.pushback.doc : Doc +IO.concurrent.STM.TQueue.pushback.doc = + use Nat == + {{ + `` pushback a tq `` puts `a` onto the front of the queue, so it is the next + element to {dequeue}. + + # Example + + @typecheck ``` + q = TQueue.fromList [4, 3, 2, 1] + pushback 99 q + dequeue q == 99 + ``` + }} + +IO.concurrent.STM.TQueue.size : TQueue a ->{STM} Nat +IO.concurrent.STM.TQueue.size tq = List.size (TQueue.elements tq) + +IO.concurrent.STM.TQueue.size.doc : Doc +IO.concurrent.STM.TQueue.size.doc = + use Nat == + {{ + Returns the current number of elements in the {type TQueue}. For instance `` + size (fromList [1, 2, 3]) == 3 `` + }} + +IO.concurrent.STM.TQueue.tests.ex1 : '{IO} [Result] +IO.concurrent.STM.TQueue.tests.ex1 _ = STM.atomically do + use Boolean not + use Nat == + use TQueue elements size + assert b = if b then () else bug "test failed" + q = TQueue.empty() + assert (tryPeek q === None) + enqueue 1 q + enqueue 2 q + assert (peek q == 1) + assert (size q == 2) + assert (dequeue q == 1) + assert (dequeue q == 2) + assert (size q == 0) + assert (dequeueNonce q == 2) + enqueueAll [1, 2, 3] q + assert (dequeue q == 3) + assert (dequeue q == 2) + assert (dequeue q == 1) + assert (dequeueNonce q == 5) + assert (size q == 0) + enqueueAll [1, 2, 3, 4] q + assert (not (tryBoundedEnqueue 3 99 q)) + assert (not (tryBoundedEnqueue 3 99 q)) + enqueue 0 q + assert (elements q === [0, 1, 2, 3, 4]) + pushback 5 q + assert (elements q === [0, 1, 2, 3, 4, 5]) + [Ok "Passed"] + +IO.concurrent.STM.TQueue.tryBoundedEnqueue : + Nat -> a -> TQueue a ->{STM} Boolean +IO.concurrent.STM.TQueue.tryBoundedEnqueue maxSize a = cases + TQueue elems _ -> + use List +: + use Nat >= + es = TVar.read elems + if List.size es >= maxSize then false + else + TVar.write elems (a +: es) + true + +IO.concurrent.STM.TQueue.tryBoundedEnqueue.doc : Doc +IO.concurrent.STM.TQueue.tryBoundedEnqueue.doc = + {{ + `` tryBoundedEnqueue maxSize a tq `` enqueues `a` to `tq`, or does nothing + and returns `` false `` if the enqueuing would cause the queue size to exceed + `maxSize`. + + Also see {boundedEnqueue} for a version that blocks until the element can be + enqueued and not exceed the provided `maxSize`. + }} + +IO.concurrent.STM.TQueue.tryBoundedEnqueueAll : + Nat -> [a] -> TQueue a ->{STM} Boolean +IO.concurrent.STM.TQueue.tryBoundedEnqueueAll maxSize as = cases + TQueue elems _ -> + use List ++ size + use Nat + > + es = TVar.read elems + if size es + size as > maxSize then false + else + TVar.write elems (as ++ es) + true + +IO.concurrent.STM.TQueue.tryBoundedEnqueueAll.doc : Doc +IO.concurrent.STM.TQueue.tryBoundedEnqueueAll.doc = + {{ + `` tryBoundedEnqueueAll maxSize as tq `` enqueues multiple elements as long + as the size of the resulting {type TQueue} does not exceed `maxSize`. + + Returns `` false `` if unsuccessful, `` true `` otherwise. + }} + +IO.concurrent.STM.TQueue.tryDequeue : TQueue a ->{STM} Optional a +IO.concurrent.STM.TQueue.tryDequeue = cases + TQueue elems nonce -> + match TVar.read elems with + [] -> None + init :+ last -> + use Nat + + TVar.write elems init + TVar.modify nonce (n -> n + 1) + Some last + +IO.concurrent.STM.TQueue.tryDequeue.doc : Doc +IO.concurrent.STM.TQueue.tryDequeue.doc = + {{ + Tries to dequeue an element from the given {type TQueue}. + + Returns {None} if the queue is empty. + }} + +IO.concurrent.STM.TQueue.tryPeek : TQueue a ->{STM} Optional a +IO.concurrent.STM.TQueue.tryPeek tq = List.last (TQueue.elements tq) + +IO.concurrent.STM.TQueue.tryPeek.doc : Doc +IO.concurrent.STM.TQueue.tryPeek.doc = + {{ + Read the next item that would be returned by {dequeue}, without removing it + from the queue, and return {None} if the queue is empty. + }} + +IO.concurrent.ThreadId.doc : Doc +IO.concurrent.ThreadId.doc = + {{ A unique identifier for a thread created with {fork}. }} + +-- builtin IO.concurrent.ThreadId.toText : IO.concurrent.ThreadId -> Text + +IO.concurrent.ThreadId.toText.doc : Doc +IO.concurrent.ThreadId.toText.doc = + {{ Converts a {type ThreadId} to its {type Text} representation. }} + +IO.concurrent.TMVar.doc : Doc +IO.concurrent.TMVar.doc = + {{ + {type TMVar} implements a posibly empty memory cell similar to {type MVar}, + but using the transactional memory of {type TVar}. Essentially, this is a + lock implemented using transactions. + + It's important to note that when using the {type STM} versions of functions, + everything in a transaction still happens atomically. So, when taking two + [TMVars]({type TMVar}) in sequence, if the second is unavailable the entire + transaction will reset, and the first will not remain held. + + One possible advantage of this is that if multiple 'locks' are needed for + some operation, they can all be reserved atomically or not at all. Usually + with mutexes, care needs to be taken to always take multiple locks in a + single globally defined order to avoid deadlock. [TMVars]({type TMVar}) + shouldn't require this as long as all locks are taken together atomically. + }} + +IO.concurrent.TMVar.isEmpty : TMVar a ->{STM} Boolean +IO.concurrent.TMVar.isEmpty = cases + TMVar v -> + match TVar.read v with + Some _ -> true + None -> false + +IO.concurrent.TMVar.isEmpty.doc : Doc +IO.concurrent.TMVar.isEmpty.doc = + {{ + Determines if the {type TMVar} is empty. + + Note that unlike an {type MVar}, it's possible to atomically test if a + {type TMVar} is empty and act correctly on that knowledge, without other + threads invalidating the logic. However, keeping transactions short is always + recommended. + }} + +IO.concurrent.TMVar.new : a ->{STM} TMVar a +IO.concurrent.TMVar.new x = TMVar (TVar.new (Some x)) + +IO.concurrent.TMVar.new.doc : Doc +IO.concurrent.TMVar.new.doc = + {{ Creates a new {type TMVar} containing the given value. }} + +IO.concurrent.TMVar.newEmpty : '{STM} TMVar a +IO.concurrent.TMVar.newEmpty = do TMVar (TVar.new None) + +IO.concurrent.TMVar.newEmpty.doc : Doc +IO.concurrent.TMVar.newEmpty.doc = + {{ Creates a new {type TMVar} with no contents. }} + +IO.concurrent.TMVar.newEmptyIO : '{IO} TMVar a +IO.concurrent.TMVar.newEmptyIO = do TMVar (TVar.newIO None) + +IO.concurrent.TMVar.newEmptyIO.doc : Doc +IO.concurrent.TMVar.newEmptyIO.doc = + {{ + Creates a new {type TMVar} with no contents. + + This executes directly in the {type IO} ability to avoid needing + {STM.atomically} + }} + +IO.concurrent.TMVar.newIO : a ->{IO} TMVar a +IO.concurrent.TMVar.newIO x = TMVar (TVar.newIO (Some x)) + +IO.concurrent.TMVar.newIO.doc : Doc +IO.concurrent.TMVar.newIO.doc = + {{ + Creates a new {type TMVar} containing the given value. + + This executes directly in the {type IO} ability to avoid needing + {STM.atomically}. + }} + +IO.concurrent.TMVar.put : TMVar a -> a ->{STM} () +IO.concurrent.TMVar.put mv x = + (TMVar v) = mv + match TVar.read v with + None -> TVar.write v (Some x) + Some _ -> retry() + +IO.concurrent.TMVar.put.doc : Doc +IO.concurrent.TMVar.put.doc = + {{ + Fills the {type TMVar} if it is empty. + + If it is not empty, the transaction retries and waits until another thread + empties the {type TMVar}. + }} + +IO.concurrent.TMVar.read : TMVar a ->{STM} a +IO.concurrent.TMVar.read = cases + TMVar v -> + match TVar.read v with + Some x -> x + _ -> retry() + +IO.concurrent.TMVar.read.doc : Doc +IO.concurrent.TMVar.read.doc = + {{ + Reads the contents of the {type TMVar} without reserving them. + + If the cell is empty, the transaction retries and waits until another thread + modifies the {type TMVar}. + }} + +IO.concurrent.TMVar.swap : TMVar a -> a ->{STM} a +IO.concurrent.TMVar.swap mv x = + (TMVar v) = mv + match TVar.read v with + None -> retry() + Some y -> + TVar.write v (Some x) + y + +IO.concurrent.TMVar.swap.doc : Doc +IO.concurrent.TMVar.swap.doc = + {{ + Swaps the contents of the {type TMVar} with the given value. + + If the {type TMVar} is empty, the transaction retries and waits until the + swap can be completed. + }} + +IO.concurrent.TMVar.take : TMVar a ->{STM} a +IO.concurrent.TMVar.take = cases + TMVar v -> + match TVar.read v with + None -> retry() + Some x -> + TVar.write v None + x + +IO.concurrent.TMVar.take.doc : Doc +IO.concurrent.TMVar.take.doc = + {{ + Takes the contents of a {type TMVar}, leaving it empty. + + If the {type TMVar} is already empty, the transaction will retry and wait + until the variable has been changed elsewhere. + }} + +IO.concurrent.TMVar.tryPut : TMVar a -> a ->{STM} Boolean +IO.concurrent.TMVar.tryPut mv x = + (TMVar v) = mv + match TVar.read v with + Some _ -> false + None -> + TVar.write v (Some x) + true + +IO.concurrent.TMVar.tryPut.doc : Doc +IO.concurrent.TMVar.tryPut.doc = + {{ + Tries to fill the {type TMVar} if it is empty. + + Returns an indication of whether the operation was successful, rather than + waiting if the {type TMVar} is already filled. + }} + +IO.concurrent.TMVar.tryRead : TMVar a ->{STM} Optional a +IO.concurrent.TMVar.tryRead = cases TMVar v -> TVar.read v + +IO.concurrent.TMVar.tryRead.doc : Doc +IO.concurrent.TMVar.tryRead.doc = + {{ Tries to read the contents of the {type TMVar} without reserving them. }} + +IO.concurrent.TMVar.tryTake : TMVar a ->{STM} Optional a +IO.concurrent.TMVar.tryTake = cases + TMVar v -> + match TVar.read v with + None -> None + Some x -> + TVar.write v None + Some x + +IO.concurrent.TMVar.tryTake.doc : Doc +IO.concurrent.TMVar.tryTake.doc = + {{ + Tries to take the contents of a {type TMVar}, leaving it empty if it is not + already. If the {type TMVar} is empty, this function simply returns {None} + rather than retrying. + }} + +IO.concurrent.TVar.doc : Doc +IO.concurrent.TVar.doc = + {{ + A {type TVar} is a mutable reference cell that may be accessed during atomic + transactions. It may be [read]({TVar.read}) and [written]({TVar.write}) + during a transaction, and these reads/writes are guaranteed to be consistent + with one another. + + The atomicity is controlled by the {STM.atomically} function. + }} + +IO.concurrent.TVar.modify : TVar a -> (a ->{STM} a) ->{STM} () +IO.concurrent.TVar.modify tv f = TVar.write tv (f (TVar.read tv)) + +IO.concurrent.TVar.modify.doc : Doc +IO.concurrent.TVar.modify.doc = + {{ + Modifies the contents of a {type TVar} according to the provided function. + }} + +-- builtin IO.concurrent.TVar.new : +-- a ->{IO.concurrent.STM} IO.concurrent.TVar a + +IO.concurrent.TVar.new.doc : Doc +IO.concurrent.TVar.new.doc = + {{ Creates a new {type TVar} with the given contents. }} + +-- builtin IO.concurrent.TVar.newIO : a ->{IO} IO.concurrent.TVar a + +IO.concurrent.TVar.newIO.doc : Doc +IO.concurrent.TVar.newIO.doc = + {{ + Creates a new {type TVar} with the given contents. This is an isolated + operation. To combine into a larger transaction, use {TVar.new}. + }} + +-- builtin IO.concurrent.TVar.read : +-- IO.concurrent.TVar a ->{IO.concurrent.STM} a + +IO.concurrent.TVar.read.doc : Doc +IO.concurrent.TVar.read.doc = {{ Gets the contents of a {type TVar}. }} + +-- builtin IO.concurrent.TVar.readIO : IO.concurrent.TVar a ->{IO} a + +IO.concurrent.TVar.readIO.doc : Doc +IO.concurrent.TVar.readIO.doc = + {{ + Gets the contents of a {type TVar}. This is an isolated operation. To combine + into a larger transaction, use {TVar.read}. + }} + +IO.concurrent.TVar.state : TVar s -> (s ->{e} (a, s)) ->{e, STM} a +IO.concurrent.TVar.state tv f = + (r, s) = f (TVar.read tv) + TVar.write tv s + r + +IO.concurrent.TVar.state.doc : Doc +IO.concurrent.TVar.state.doc = + {{ + Runs a state function on the contents of a {type TVar}, updating the var with + the new state and returning the result. + }} + +-- builtin IO.concurrent.TVar.swap : +-- IO.concurrent.TVar a -> a ->{IO.concurrent.STM} a + +IO.concurrent.TVar.swap.doc : Doc +IO.concurrent.TVar.swap.doc = + {{ Swaps the contents of a {type TVar} with the provided value. }} + +-- builtin IO.concurrent.TVar.write : +-- IO.concurrent.TVar a -> a ->{IO.concurrent.STM} () + +IO.concurrent.TVar.write.doc : Doc +IO.concurrent.TVar.write.doc = {{ Sets the contents of a {type TVar}. }} + +IO.console.printLine : Text ->{IO, Exception} () +IO.console.printLine t = + putText stdOut t + putText stdOut "\n" + +IO.console.printLine.doc : Doc +IO.console.printLine.doc = + {{ + `` printLine "myText" `` prints the given {type Text} value to the console. + + {printLine} performs {type IO} and in rare circumstances may raise an + {type Exception}, for example, if {stdOut} has been closed. + + @typecheck ``` + myProgram : '{IO, Exception} () + myProgram _ = printLine "Hello World" + ``` + + ``` ucm + .> run myProgram + Hello World + ``` + }} + +IO.console.readLine : '{IO, Exception} Text +IO.console.readLine _ = getLine stdIn + +IO.console.readLine.doc : Doc +IO.console.readLine.doc = + use Text ++ + {{ + Reads a line from standard input. + + # Example + + @typecheck ``` + printLine "Important question: what's your favorite type of pie?" + printLine "> " + pie = readLine() + printLine (pie ++ "!? No way, me too!!! 🎉") + ``` + }} + +IO.deprecated.EpochTime.doc : Doc +IO.deprecated.EpochTime.doc = + {{ + A time value in seconds since the Unix epoch. This type is deprecated. Use + {type Instant} instead. + }} + +IO.deprecated.systemTime : '{IO, Exception} EpochTime +IO.deprecated.systemTime = do EpochTime (Either.toException systemTime.impl()) + +IO.deprecated.systemTime.doc : Doc +IO.deprecated.systemTime.doc = + {{ + Returns the time on the system clock, represented as a number of seconds + since midnight on January 1st 1970. This function is deprecated. Use + {realtime} instead. + }} + +-- builtin IO.deprecated.systemTime.impl : '{IO} Either Failure Nat + +IO.deprecated.systemTimeMicroseconds : '{IO} Int +IO.deprecated.systemTimeMicroseconds = systemTimeMicroseconds.impl + +IO.deprecated.systemTimeMicroseconds.doc : Doc +IO.deprecated.systemTimeMicroseconds.doc = + {{ + Returns the time on the system clock, represented as a number of microseconds + since midnight on January 1st 1970. This function is deprecated. Use + {realtime} instead. + }} + +-- builtin IO.deprecated.systemTimeMicroseconds.impl : '{IO} Int + +IO.doc : Doc +IO.doc = + {{ + The {type IO} ability is built into Unison, and provides operations for + input/output and interacting with the operating system — terminals, files, + directories, threads, shared mutable state, networking, command line + arguments, environment variables, and system clocks. + + You cannot `handle` {type IO} with an ability handler. It's provided by the + Unison runtime when it calls into the main function of your program. For + example, given this program: + + @typecheck ``` + myProgram : '{IO, Exception} () + myProgram _ = printLine "Hello World" + ``` + + The {type IO} ability is supplied to the program when `run` from the UCM + console: + + ``` ucm + .> run myProgram + Hello World + ``` + + 📚 Tutorial: + [Running Unison Programs](https://www.unison-lang.org/docs/usage-topics/running-programs/) + + # Files and handles + + {type FilePath} is the type of file names and directory names. You can use + {open} to get a {type Handle} from a {type FilePath}. The {type FilePath} + type itself provides a number of operations for working with directories + and interacting with the file system. + + {type Handle} is the type of file handles. It provides operations for + reading and writing file contents, and closing files. + + # Standard handles + + Three standard file handles are provided for interacting with the terminal: + {stdIn}, {stdOut}, and {stdErr}. + + # Command line arguments and environment variables + + `` getArgs `` gets the list of the running program's command line + arguments. + + `` getEnv var `` gets the value of the environment variable named `var`. + + # System clocks + + A number of functions are provided for getting the current time, and + converting between different time representations. + + {realtime} gets the current real-world time according to the system clock, + as an {type Instant}. + + {Clock.timeSinceEpoch} gets the current time since the Unix epoch, as a + {type Duration}. + + {Clock.monotonic} gets the current time according to a monotonic clock, as + a {type Duration}. This clock is unaffected by changes to the system clock, + and is guaranteed to be monotonically increasing. + + {processCPUTime} gets the current time according to a clock that measures + the CPU time used by the current process, as a {type Duration}. + + {threadCPUTime} gets the current time according to a clock that measures + the CPU time used by the current thread, as a {type Duration}. + + {{ concurrency }} + + # Networking + + The {type Socket} type provides access to network sockets. + + {type Tls} implements Transport Layer Security (TLS), providing + authentication and encryption. + + # System processes + + The {type Process} type provides access to operating system processes, + allowing you to start, wait for, terminate, and interact with processes. + }} + +IO.Error.EOF.doc : Doc +IO.Error.EOF.doc = + {{ + An error that indicates that an operation could not be completed because the + end of a file was reached. + }} + +IO.Failure.ArithmeticFailure.doc : Doc +IO.Failure.ArithmeticFailure.doc = + {{ + A type of {type Failure} that is raised when an arithmetic operation fails. + }} + +IO.Failure.doc : Doc +IO.Failure.doc = + use Exception raise + {{ + {type Failure} is a data type used to capture information about errors. It's + commonly found as an argument to the request constructor, {raise}, of the + {type Exception} ability. + + The {type Failure} type has three fields: + + * {field typeLink} - a marker {type Type} that describes the type of error. + This is used in e.g. {catchOnly} and {catchMany} to match on the type of + error. + * {field message} - a {type Text} that describes the error. + * {field payload} - an {type Any} that contains additional information about + the error. + + @typecheck ``` + raise (Failure (typeLink Generic) "A user error occurred" (Any 42)) + ``` + + # See also + + * [Generic.failure]({failure}) for creating a {type Failure} with a generic + type. + * {type Generic} - A marker type for generic errors if you don't need to + define a custom error type. + }} + +IO.Failure.failureType : Failure -> Type +IO.Failure.failureType = cases Failure t _ _ -> t + +IO.Failure.failureType.doc : Doc +IO.Failure.failureType.doc = + {{ Extracts the {type Type} of failure from a {type Failure}. }} + +IO.Failure.message : Failure -> Text +IO.Failure.message = cases Failure _ msg _ -> msg + +IO.Failure.message.doc : Doc +IO.Failure.message.doc = + {{ Extracts the message {type Text} from a {type Failure}. }} + +IO.Failure.payload : Failure -> Any +IO.Failure.payload = cases Failure _ _ a -> a + +IO.Failure.payload.doc : Doc +IO.Failure.payload.doc = + {{ + Extracts the payload from a {type Failure}. This will have type {type Any}. + }} + +IO.Failure.RuntimeFailure.doc : Doc +IO.Failure.RuntimeFailure.doc = + {{ + A runtime failure, which is a {type Failure} that occurs when a computation + fails due to e.g. a pattern match failure or a call to {bug}. + + See {catchAll} and {tryEval} for a way to catch runtime failures. + }} + +(IO.FilePath./) : FilePath -> Text -> FilePath +(IO.FilePath./) = cases + FilePath base, child -> + if endsWith "/" base then FilePath (base Text.++ child) + else FilePath (base Text.++ "/" Text.++ child) + +IO.FilePath./.doc : Doc +IO.FilePath./.doc = + use FilePath / + {{ + `` baseDir / child `` returns a new {type FilePath} with the `child` segment + appended to `baseDir`. + + # Examples + + ``` + homeDir = FilePath "/home/user" + homeDir / ".local" / "share" + ``` + + ``` + homeDir = FilePath "/home/user/" + homeDir / ".local" / "share" + ``` + + ``` + baseDir = FilePath "/" + baseDir / "tmp" + ``` + }} + +IO.FilePath.appendFile : FilePath -> Bytes ->{IO, Exception} () +IO.FilePath.appendFile path contents = + bracket + (do open path FileMode.Append) + (h -> Handle.close h) + (h -> putBytes h contents) + +IO.FilePath.appendFile.doc : Doc +IO.FilePath.appendFile.doc = + {{ + Appends the given {type Bytes} to the file at the provided {type FilePath}. + Creates the file if it doesn't exist. + + # Example + + @typecheck ``` + appendFile (FilePath "notes.txt") 0xs554e49534f4e + ``` + + # See also + + * {appendFileUtf8} to append {type Text} to a file as UTF-8 bytes. + }} + +IO.FilePath.appendFileUtf8 : FilePath -> Text ->{IO, Exception} () +IO.FilePath.appendFileUtf8 path contents = + appendFile path (Text.toUtf8 contents) + +IO.FilePath.appendFileUtf8.doc : Doc +IO.FilePath.appendFileUtf8.doc = + {{ + Appends the given {type Text} to the file at the provided {type FilePath}. + Creates the file if it doesn't exist. The file encoding is assumed to be + UTF-8. + + # Example + + @typecheck ``` + appendFileUtf8 (FilePath "notes.txt") "Unison" + ``` + + # See also + + * {appendFile} to append {type Bytes} instead of {type Text}. + }} + +IO.FilePath.createDirectory : FilePath ->{IO, Exception} () +IO.FilePath.createDirectory = + Either.toException << createDirectory.impl << FilePath.toText + +IO.FilePath.createDirectory.deprecated : Text ->{IO, Exception} () +IO.FilePath.createDirectory.deprecated = + Either.toException << createDirectory.impl + +IO.FilePath.createDirectory.doc : Doc +IO.FilePath.createDirectory.doc = + {{ + Creates a directory at the given path. Raises an {type Exception} if the + directory could not be created. + }} + +-- builtin IO.FilePath.createDirectory.impl : Text ->{IO} Either Failure () + +IO.FilePath.createTempDirectory : FilePath ->{IO, Exception} FilePath +IO.FilePath.createTempDirectory = + FilePath << Either.toException << createTempDirectory.impl << FilePath.toText + +IO.FilePath.createTempDirectory.doc : Doc +IO.FilePath.createTempDirectory.doc = + {{ + Creates a temporary directory in the system's temporary directory. The + directory is created with the given prefix. Raises an {type Exception} if the + directory could not be created. + }} + +-- builtin IO.FilePath.createTempDirectory.impl : +-- Text ->{IO} Either Failure Text + +IO.FilePath.directoryContents : FilePath ->{IO, Exception} [FilePath] +IO.FilePath.directoryContents = List.map FilePath << texts + +IO.FilePath.directoryContents.doc : Doc +IO.FilePath.directoryContents.doc = + {{ + Gets the contents of a directory as a {type List} of {type FilePath}s. Raises + an {type Exception} if the path does not exist or is not a directory. + }} + +-- builtin IO.FilePath.directoryContents.impl : +-- Text ->{IO} Either Failure [Text] + +IO.FilePath.directoryContents.texts : FilePath ->{IO, Exception} [Text] +IO.FilePath.directoryContents.texts = + Either.toException << directoryContents.impl << FilePath.toText + +IO.FilePath.doc : Doc +IO.FilePath.doc = + {{ + The {type FilePath} type represents a file name or directory name. + + # Creating a {type FilePath} + + A {type FilePath} is a wrapper around a {type Text}. It's constructed as + ``FilePath "my/path/to/file.txt"``. + + Constructing a value of {type FilePath} does not perform any validation + that the file exists at the given location. + + # Reading and writing files + + A few convenience functions are provided on {type FilePath} to quickly read + and write file contents at that path. + + Append some {type Bytes} to a file: + + @signature{appendFile} + + Append some {type Text} to a file: + + @signature{appendFileUtf8} + + Read all the contents of a file as {type Bytes}: + + @signature{readFile} + + Read all the contents of a file as {type Text}: + + @signature{readFileUtf8} + + Overwrite a file with some {type Bytes}: + + @signature{writeFile} + + Overwrite a file with some {type Text}: + + @signature{writeFileUtf8} + + # Working with directories + + Create a directory at the path: + + @signature{createDirectory} + + Create a temporary directory and return the path to it: + + @signature{createTempDirectory} + + Delete the directory at the path: + + @signature{removeDirectory} + + Check if a directory exists at the path: + + @signature{isDirectory} + + Get the path to the current working directory: + + @signature{getCurrentDirectory} + + Get a directory's contents: + + @signature{directoryContents} + + Get the path to the user's temporary directory: + + @signature{getTempDirectory} + + Set the current working directory: + + @signature{setCurrentDirectory} + + Move/rename a directory: + + @signature{renameDirectory} + + # Working with files + + Check if a file exists at the path: + + @signature{FilePath.exists} + + Get the size of a file: + + @signature{getSize} + + Get the timestamp of a file: + + @signature{getTimestamp} + + Open a file for reading or writing: + + @signature{open} + + Move/rename a file: + + @signature{renameFile} + + Delete a file: + + @signature{removeFile} + + # See also + + * {type Handle} for working with open files and pipes. + * {type Socket} for working with network sockets. + }} + +IO.FilePath.exists : FilePath ->{IO, Exception} Boolean +IO.FilePath.exists d = Either.toException (exists.impl (FilePath.toText d)) + +IO.FilePath.exists.deprecated : Text ->{IO, Exception} Boolean +IO.FilePath.exists.deprecated d = Either.toException (exists.impl d) + +IO.FilePath.exists.doc : Doc +IO.FilePath.exists.doc = + {{ + Returns `` true `` if the given {type FilePath} represents a file that + exists, and `` false `` otherwise. + }} + +-- builtin IO.FilePath.exists.impl : Text ->{IO} Either Failure Boolean + +IO.FilePath.FileMode.doc : Doc +IO.FilePath.FileMode.doc = + {{ + A {type FileMode} represents the mode in which a {type Handle} is opened. It + is used by the {open} function. + + * {Read} - Open the file for reading. + * {Write} - Open the file for writing. If the file already exists, it will be + truncated to zero length. + * {FileMode.Append} - Open the file for writing. If the file already exists, + the new data will be appended to the end of the file. + * {ReadWrite} - Open the file for reading and writing. If the file already + exists, the {type Handle} will be positioned at the beginning of the file + and any writes (with e.g. {putBytes} or {putText}) will overwrite the + existing data. + }} + +IO.FilePath.getCurrentDirectory : '{IO, Exception} FilePath +IO.FilePath.getCurrentDirectory = + FilePath << Either.toException << getCurrentDirectory.impl + +IO.FilePath.getCurrentDirectory.deprecated : '{IO, Exception} Text +IO.FilePath.getCurrentDirectory.deprecated = + Either.toException << getCurrentDirectory.impl + +IO.FilePath.getCurrentDirectory.doc : Doc +IO.FilePath.getCurrentDirectory.doc = + {{ + Gets the current directory as a {type FilePath}. Raises an {type Exception} + if the current directory could not be determined. + }} + +-- builtin IO.FilePath.getCurrentDirectory.impl : '{IO} Either Failure Text + +IO.FilePath.getSize : FilePath ->{IO, Exception} Nat +IO.FilePath.getSize d = Either.toException (getSize.impl (FilePath.toText d)) + +IO.FilePath.getSize.deprecated : Text ->{IO, Exception} Nat +IO.FilePath.getSize.deprecated d = Either.toException (getSize.impl d) + +IO.FilePath.getSize.doc : Doc +IO.FilePath.getSize.doc = {{ Gets the size of a file in bytes. }} + +-- builtin IO.FilePath.getSize.impl : Text ->{IO} Either Failure Nat + +IO.FilePath.getTempDirectory : '{IO, Exception} FilePath +IO.FilePath.getTempDirectory = + FilePath << Either.toException << getTempDirectory.impl + +IO.FilePath.getTempDirectory.deprecated : '{IO, Exception} Text +IO.FilePath.getTempDirectory.deprecated = + Either.toException << getTempDirectory.impl + +IO.FilePath.getTempDirectory.doc : Doc +IO.FilePath.getTempDirectory.doc = + {{ Gets the path to the system's temporary directory. }} + +-- builtin IO.FilePath.getTempDirectory.impl : '{IO} Either Failure Text + +IO.FilePath.getTimestamp : FilePath ->{IO, Exception} Instant +IO.FilePath.getTimestamp = + fromEpochSeconds << Int.fromRepresentation << Either.toException + << getTimestamp.impl + << FilePath.toText + +IO.FilePath.getTimestamp.deprecated.v1 : Text ->{IO, Exception} Nat +IO.FilePath.getTimestamp.deprecated.v1 = + Either.toException << getTimestamp.impl + +IO.FilePath.getTimestamp.deprecated.v1.doc : Doc +IO.FilePath.getTimestamp.deprecated.v1.doc = + {{ + Get the timestamp of a file or directory. This function is deprecated. Use + {getTimestamp} instead. + }} + +IO.FilePath.getTimestamp.deprecated.v2 : FilePath ->{IO, Exception} EpochTime +IO.FilePath.getTimestamp.deprecated.v2 = + EpochTime << Either.toException << getTimestamp.impl << FilePath.toText + +IO.FilePath.getTimeStamp.doc : Doc +IO.FilePath.getTimeStamp.doc = + {{ Gets the time stamp of the given file path, if it exists. }} + +-- builtin IO.FilePath.getTimestamp.impl : Text ->{IO} Either Failure Nat + +IO.FilePath.isDirectory : FilePath ->{IO, Exception} Boolean +IO.FilePath.isDirectory = + Either.toException << isDirectory.impl << FilePath.toText + +IO.FilePath.isDirectory.deprecated : Text ->{IO, Exception} Boolean +IO.FilePath.isDirectory.deprecated = Either.toException << isDirectory.impl + +IO.FilePath.isDirectory.doc : Doc +IO.FilePath.isDirectory.doc = + {{ + Returns `` true `` if the given path points to a directory, `` false `` + otherwise. + }} + +-- builtin IO.FilePath.isDirectory.impl : Text ->{IO} Either Failure Boolean + +IO.FilePath.open : FilePath -> FileMode ->{IO, Exception} Handle +IO.FilePath.open = compose2 Either.toException open.impl << FilePath.toText + +IO.FilePath.open.deprecated : Text -> FileMode ->{IO, Exception} Handle +IO.FilePath.open.deprecated = compose2 Either.toException open.impl + +IO.FilePath.open.doc : Doc +IO.FilePath.open.doc = + use FileMode Append + {{ + Opens a file for reading or writing, returning a {type Handle} that can be + used to read or write to the file, according to the given {type FileMode}. + + The behavior of this function depends on the given {type FileMode} and + whether the file already exists. + + * If the file does not exist, this function: + * Creates the file if the {type FileMode} is {Write}, {Append}, or + {ReadWrite}. + * Raises an {type Exception} if the {type FileMode} is {Read}. + * If the file exists, this function: + * Truncates the file to zero bytes if the file mode is {Write}. + * Seeks to the end of the file if the file mode is {Append}. + * Seeks to the beginning of the file if the file mode is {Read} or + {ReadWrite}. + }} + +-- builtin IO.FilePath.open.impl : +-- Text -> FileMode ->{IO} Either Failure IO.Handle + +IO.FilePath.readFile : FilePath ->{IO, Exception} Bytes +IO.FilePath.readFile path = + bracket + (do open path Read) (h -> Handle.close h) (h -> getBytes h (getSize path)) + +IO.FilePath.readFile.doc : Doc +IO.FilePath.readFile.doc = + {{ + Reads all {type Bytes} from the file at the provided {type FilePath}. + + # Example + + @typecheck ``` + readFile (FilePath "notes.txt") + ``` + + # See also + + * {readFileUtf8} to read {type Text} from a text file encoded as UTF-8. + }} + +IO.FilePath.readFileUtf8 : FilePath ->{IO, Exception} Text +IO.FilePath.readFileUtf8 path = fromUtf8 (readFile path) + +IO.FilePath.readFileUtf8.doc : Doc +IO.FilePath.readFileUtf8.doc = + {{ + Reads all {type Text} from the file at the provided {type FilePath}. Assumes + the file has a UTF-8 encoding. + + # Example + + @typecheck ``` + readFileUtf8 (FilePath "notes.txt") + ``` + + # See also + + * {readFile} to read {type Bytes} intstead of {type Text}. + }} + +IO.FilePath.removeDirectory : FilePath ->{IO, Exception} () +IO.FilePath.removeDirectory d = + Either.toException (removeDirectory.impl (FilePath.toText d)) + +IO.FilePath.removeDirectory.deprecated : Text ->{IO, Exception} () +IO.FilePath.removeDirectory.deprecated d = + Either.toException (removeDirectory.impl d) + +IO.FilePath.removeDirectory.doc : Doc +IO.FilePath.removeDirectory.doc = + {{ + Removes a directory at the given path. Raises an {type Exception} if the + directory could not be removed. + }} + +-- builtin IO.FilePath.removeDirectory.impl : Text ->{IO} Either Failure () + +IO.FilePath.removeFile : FilePath ->{IO, Exception} () +IO.FilePath.removeFile = + Either.toException << removeFile.impl << FilePath.toText + +IO.FilePath.removeFile.deprecated : Text ->{IO, Exception} () +IO.FilePath.removeFile.deprecated = Either.toException << removeFile.impl + +IO.FilePath.removeFile.doc : Doc +IO.FilePath.removeFile.doc = {{ Removes the file at the given path. }} + +-- builtin IO.FilePath.removeFile.impl : Text ->{IO} Either Failure () + +IO.FilePath.renameDirectory : FilePath -> FilePath ->{IO, Exception} () +IO.FilePath.renameDirectory from to = + use FilePath toText + Either.toException (renameDirectory.impl (toText from) (toText to)) + +IO.FilePath.renameDirectory.deprecated : Text -> Text ->{IO, Exception} () +IO.FilePath.renameDirectory.deprecated from to = + Either.toException (renameDirectory.impl from to) + +IO.FilePath.renameDirectory.doc : Doc +IO.FilePath.renameDirectory.doc = + {{ + Renames a directory at the given path. Raises an {type Exception} if the + directory could not be renamed. + }} + +-- builtin IO.FilePath.renameDirectory.impl : +-- Text -> Text ->{IO} Either Failure () + +IO.FilePath.renameFile : FilePath -> FilePath ->{IO, Exception} () +IO.FilePath.renameFile = + compose2 Either.toException (on renameFile.impl FilePath.toText) + +IO.FilePath.renameFile.deprecated : Text -> Text ->{IO, Exception} () +IO.FilePath.renameFile.deprecated = compose2 Either.toException renameFile.impl + +IO.FilePath.renameFile.doc : Doc +IO.FilePath.renameFile.doc = + {{ Renames the file at the first path to the second path. }} + +-- builtin IO.FilePath.renameFile.impl : Text -> Text ->{IO} Either Failure () + +IO.FilePath.setCurrentDirectory : FilePath ->{IO, Exception} () +IO.FilePath.setCurrentDirectory d = + Either.toException (setCurrentDirectory.impl (FilePath.toText d)) + +IO.FilePath.setCurrentDirectory.deprecated : Text ->{IO, Exception} () +IO.FilePath.setCurrentDirectory.deprecated d = + Either.toException (setCurrentDirectory.impl d) + +IO.FilePath.setCurrentDirectory.doc : Doc +IO.FilePath.setCurrentDirectory.doc = + {{ + Sets the current directory to the given {type FilePath}. Raises an + {type Exception} if the current directory could not be set. + }} + +-- builtin IO.FilePath.setCurrentDirectory.impl : Text ->{IO} Either Failure () + +IO.FilePath.toText : FilePath -> Text +IO.FilePath.toText = cases FilePath f -> f + +IO.FilePath.toText.doc : Doc +IO.FilePath.toText.doc = + {{ + Converts a {type FilePath} to a {type Text} representation. + + # Example + + ``` + FilePath.toText (FilePath "/home/user/file.txt") + ``` + }} + +IO.FilePath.writeFile : FilePath -> Bytes ->{IO, Exception} () +IO.FilePath.writeFile path contents = + bracket (do open path Write) (h -> Handle.close h) (h -> putBytes h contents) + +IO.FilePath.writeFile.doc : Doc +IO.FilePath.writeFile.doc = + {{ + Overwrite the file at the provided {type FilePath} with the provided + {type Bytes}. The file will be created if it doesn't exist. + + # Example + + @typecheck ``` + writeFile (FilePath "notes.txt") 0xs554e49534f4e + ``` + + # See also + + * {writeFileUtf8} to write {type Text} instead of {type Bytes}. + }} + +IO.FilePath.writeFileUtf8 : FilePath -> Text ->{IO, Exception} () +IO.FilePath.writeFileUtf8 path contents = writeFile path (Text.toUtf8 contents) + +IO.FilePath.writeFileUtf8.doc : Doc +IO.FilePath.writeFileUtf8.doc = + {{ + Overwrite the file at the provided {type FilePath} with the provided + {type Text}. The file will be created if it doesn't exist. + + # Example + + @typecheck ``` + writeFileUtf8 (FilePath "notes.txt") "Unison" + ``` + + # See also + + * {writeFile} to write {type Bytes} instead of {type Text}. + }} + +IO.getArgs : '{IO, Exception} [Text] +IO.getArgs _ = Either.toException getArgs.impl() + +IO.getArgs.doc : Doc +IO.getArgs.doc = + {{ + Get a list of the current command line arguments, not including the program + name. + }} + +-- builtin IO.getArgs.impl : '{IO} Either Failure [Text] + +IO.getEnv : Text ->{IO, Exception} Text +IO.getEnv name = Either.toException (getEnv.impl name) + +IO.getEnv.doc : Doc +IO.getEnv.doc = + {{ + `` getEnv var `` returns the value of the environment variable named `var`. + + This function will throw an {type Exception} if the specified environment + variable doesn't exist. + }} + +-- builtin IO.getEnv.impl : Text ->{IO} Either Failure Text + +IO.Handle.BufferMode.doc : Doc +IO.Handle.BufferMode.doc = + {{ + A {type BufferMode} represents the buffering mode of a {type Handle}. + + * {NoBuffering} - No buffering. Each write to the {type Handle} will be + immediately flushed to the underlying file. + * {LineBuffering} - Line buffering. The {type Handle} will be flushed when a + newline is written to it, when the buffer is full, or when the + {type Handle} is closed. + * {BlockBuffering} - Block buffering. Each write to the {type Handle} will be + buffered until the buffer is full, or the {type Handle} is closed. + * {SizedBlockBuffering} - Same as {BlockBuffering}, but the buffer size is + specified by the user. + + The default buffering mode when a {type Handle} is opened (e.g. with {open}) + depends on the operating system and the type of file being opened. Usually + the default for a file is {BlockBuffering}. For terminal devices like + {stdOut} and {stdIn}, the default is usually {LineBuffering}. + + # See also + + * {setBuffering} + * {getBuffering} + }} + +IO.Handle.close : Handle ->{IO, Exception} () +IO.Handle.close = Either.toException << Handle.close.impl + +IO.Handle.close.doc : Doc +IO.Handle.close.doc = + {{ + Closes the given {type Handle}. This is a no-op if the handle is already + closed. Before closing the handle, any buffered data is flushed to the + underlying resource. Any further operations on the handle will fail (except + for {Handle.close}). + }} + +-- builtin IO.Handle.close.impl : IO.Handle ->{IO} Either Failure () + +IO.Handle.doc : Doc +IO.Handle.doc = + use Handle close + {{ + The {type Handle} type represents a + [file handle](https://en.wikipedia.org/wiki/File_descriptor). It provides + operations for reading and writing file contents, and closing files. + + # Obtaining a handle + + A value of this type is never constructed directly. It's returned as the + result of functions like {open}: + + @signature{open} + + Values of type {type Handle} reflect system resources that should be closed + with {close} once the desired operations on the file have been performed: + + @signature{close} + + Failure to close file handles can lead to + [resource leaks](https://en.wikipedia.org/wiki/Resource_leak). + + The handles for {Handle.stdIn}, {stdOut}, and {stdErr} are provided by the + runtime. These handles are never closed, and should not be closed by the + user. + + # Buffering + + Unison's file {type Handle}s are buffered by default. This means that when + you read or write to or from a freshly opened {type Handle}, the + {type Bytes} you read or write will be buffered in memory before being + written to or read from the underlying resource. This is much more + efficient than reading or writing {type Bytes} one at a time, but it can + lead to some unexpected behavior. For example, if you attempt to read a + single byte from {Handle.stdIn} you would find that the call never returns + until the user presses Enter. + + If you want bytes to be made available to readers immediately, you can + disable buffering: + + `` setBuffering stdIn NoBuffering `` + + In general, you can set the buffering mode for a {type Handle}: + + @signature{setBuffering} + + Get the buffering mode for a {type Handle}: + + @signature{getBuffering} + + See {type BufferMode} for the possible values of the buffering mode and + their meanings. + + # Seeking + + A {type Handle} has a __file pointer__ that can be positioned at a + particular byte offset in the file. This is called __seeking__. Reading and + writing operations are performed relative to the file pointer. + + Not all {type Handle}s support seeking. For example, {Handle.stdIn} and + {stdOut} do not support seeking. + + Check if a {type Handle} supports seeking: + + @signature{isSeekable} + + Set the position of the file pointer: + + @signature{seek} + + Get the position of the file pointer: + + @signature{position} + + # Reading + + Check if a {type Handle} has data available to read: + + @signature{ready} + + Read a single character from a {type Handle}: + + @signature{getChar} + + Read a single line from a {type Handle}: + + @signature{getLine} + + Read a number of bytes from a {type Handle}, blocking until the requested + number of bytes are available or the end of the stream is reached: + + @signature{getBytes} + + Read a number of bytes from a {type Handle}, returning immediately with + whatever bytes are available, blocking until there's at least one byte: + + @signature{getSomeBytes} + + Read all the bytes from a {type Handle}, blocking until the end of the + stream is reached: + + @signature{getAllBytes} + + Read all the {type Text} from a {type Handle}, blocking until the end of + the stream is reached: + + @signature{getAllText} + + # Writing + + Write {type Text} to a {type Handle}: + + @signature{putText} + + Write a number of bytes to a {type Handle}: + + @signature{putBytes} + + # Echo + + Get the [echo mode](https://en.wikipedia.org/wiki/Echo_%28computing%29) of + a {type Handle} connected to a terminal: + + @signature{getEcho} + + Set the [echo mode](https://en.wikipedia.org/wiki/Echo_%28computing%29) of + a {type Handle} connected to a terminal: + + @signature{setEcho} + + # Querying the status of a handle + + Check if a {type Handle} is open: + + @signature{isOpen} + + Check if a {type Handle} has reached the end of the stream: + + @signature{isEOF} + }} + +IO.Handle.getAllBytes : Handle ->{IO, Exception} Bytes +IO.Handle.getAllBytes h = + go bs = + use Bytes ++ + buf = getSomeBytes h 4096 + if isEOF h then bs ++ buf else go (bs ++ buf) + go 0xs + +IO.Handle.getAllBytes.doc : Doc +IO.Handle.getAllBytes.doc = + {{ + Reads all the bytes from a {type Handle}, blocking until the end of the + stream is reached. + + # See also + + * {getAllText} - reads all bytes from a {type Handle} as UTF-8 text. + * {readFile} - reads all the bytes from a file at a given path. + }} + +IO.Handle.getAllText : Handle ->{IO, Exception} Text +IO.Handle.getAllText h = fromUtf8 (getAllBytes h) + +IO.Handle.getAllText.doc : Doc +IO.Handle.getAllText.doc = + {{ + Reads a {type Handle} until the end of the stream is reached, decoding the + bytes as UTF-8 {type Text}. + + # See also + + * {getAllBytes} - reads all bytes from a {type Handle}. + * {readFileUtf8} - reads all the text from a file at a given path. + }} + +IO.Handle.getBuffering : Handle ->{IO, Exception} BufferMode +IO.Handle.getBuffering h = Either.toException (getBuffering.impl h) + +IO.Handle.getBuffering.doc : Doc +IO.Handle.getBuffering.doc = + {{ Gets the {type BufferMode} of the given {type Handle}. }} + +-- builtin IO.Handle.getBuffering.impl : +-- IO.Handle ->{IO} Either Failure BufferMode + +IO.Handle.getBytes : Handle -> Nat ->{IO, Exception} Bytes +IO.Handle.getBytes = compose2 Either.toException getBytes.impl + +IO.Handle.getBytes.doc : Doc +IO.Handle.getBytes.doc = + {{ + `` getBytes h n `` reads up to `n` bytes from {type Handle} `h`. + + {getBytes} blocks until either `n` bytes are read or `EOF` is reached. + }} + +-- builtin IO.Handle.getBytes.impl : +-- IO.Handle -> Nat ->{IO} Either Failure Bytes + +IO.Handle.getChar : Handle ->{IO, Exception} Char +IO.Handle.getChar = Either.toException << getChar.impl + +IO.Handle.getChar.doc : Doc +IO.Handle.getChar.doc = + {{ + Reads a single character from a {type Handle}. + + # Example + + @typecheck ``` + getChar stdIn + ``` + }} + +-- builtin IO.Handle.getChar.impl : IO.Handle ->{IO} Either Failure Char + +IO.Handle.getContents : Handle ->{IO, Exception} Bytes +IO.Handle.getContents h = + use Bytes ++ + go : Bytes ->{IO, Exception} Bytes + go acc = if isEOF h then acc else go (acc ++ getBytes h 4096) + go Bytes.empty + +IO.Handle.getContents.doc : Doc +IO.Handle.getContents.doc = + {{ + Read all the remaining contents of a {type Handle} as a {type Bytes} value. + + # Examples + + @typecheck ``` + do + (stdin, stdout, stderr, p) = start "cat" [] + putText stdin "hello world" + Handle.close stdin + (getContents stdout, Process.wait p) + ``` + }} + +IO.Handle.getEcho : Handle ->{IO, Exception} Boolean +IO.Handle.getEcho = Either.toException << getEcho.impl + +IO.Handle.getEcho.doc : Doc +IO.Handle.getEcho.doc = + {{ + Gets the echo mode of a {type Handle} connected to a terminal. + + # Example + + @typecheck ``` + getEcho stdOut + ``` + }} + +-- builtin IO.Handle.getEcho.impl : IO.Handle ->{IO} Either Failure Boolean + +IO.Handle.getLine : Handle ->{IO, Exception} Text +IO.Handle.getLine h = Either.toException (getLine.impl h) + +IO.Handle.getLine.doc : Doc +IO.Handle.getLine.doc = + {{ + Reads a line from the given handle, assuming the default system encoding. + + 🚧 {type Handle} will soon support setting the encoding explicitly. + + # Example + + @typecheck ``` + f = open (FilePath "/path/to/file.csv") Read + finally (do Handle.close f) do + header = getLine f + printLine header + ``` + }} + +-- builtin IO.Handle.getLine.impl : IO.Handle ->{IO} Either Failure Text + +IO.Handle.getPosition : Handle ->{IO, Exception} Nat +IO.Handle.getPosition = Either.toException << getPosition.impl + +IO.Handle.getPosition.doc : Doc +IO.Handle.getPosition.doc = + {{ Returns the current position of the given {type Handle}. }} + +-- builtin IO.Handle.getPosition.impl : IO.Handle ->{IO} Either Failure Nat + +IO.Handle.getSomeBytes : Handle -> Nat ->{IO, Exception} Bytes +IO.Handle.getSomeBytes = compose2 Either.toException getSomeBytes.impl + +IO.Handle.getSomeBytes.doc : Doc +IO.Handle.getSomeBytes.doc = + {{ + `` getSomeBytes h n `` reads up to `n` bytes from {type Handle} `h`. + + {getSomeBytes} will only block if there is nothing to be read from `h` and + `EOF` has not been reached. + }} + +-- builtin IO.Handle.getSomeBytes.impl : +-- IO.Handle -> Nat ->{IO} Either Failure Bytes + +IO.Handle.getText : Handle ->{IO, Exception} Text +IO.Handle.getText h = fromUtf8 (getContents h) + +IO.Handle.getText.doc : Doc +IO.Handle.getText.doc = + {{ + Read all the remaining {type Text} from a {type Handle}, assuming it is + encoded as UTF-8. + + # Examples + + @typecheck ``` + do + (stdin, stdout, stderr, p) = start "cat" [] + putText stdin "hello world" + putText stdin "goodbye world" + Handle.close stdin + (getText stdout, Process.wait p) + ``` + }} + +IO.Handle.isEOF : Handle ->{IO, Exception} Boolean +IO.Handle.isEOF = Either.toException << isEOF.impl + +IO.Handle.isEOF.doc : Doc +IO.Handle.isEOF.doc = + use Text ++ + {{ + Returns `` true `` if the next read from the {type Handle} would result in an + EOF error. This can happen if: + + * The handle represents a file and the end of the file has been reached (i.e. + the file pointer position is equal to the file size). + * The handle represents a socket and the remote end has closed the + connection. + * The handle represents a pipe and the other end of the pipe has been closed. + * The handle represents a terminal and️ the user has sent an end-of-file + character (`Ctrl-D` on POSIX systems) at the beginning of a line. + + Returns `` false `` otherwise. + + This function throws an {type Exception} if the {type Handle} is not open in + a readable {type FileMode} (e.g. if it is a write-only handle, or if it's + closed). It may block until the handle is ready for reading. + + {{ + docCallout + (Some {{ ⚠ }}) + {{ + This function blocks until it can determine whether the {type Handle} is at + EOF or not, which it does by attempting to read from the {type Handle}. If + you want to check if a {type Handle} has more data without blocking, use + {ready} or {readyAndAble} instead. + }} }} + + # Example + + This program reads lines of text from {stdIn} until the user sends an + end-of-file signal (Ctrl-D): + + @typecheck ``` + printLine "Enter some text (press Ctrl-D to finish):" + Boolean.until (do isEOF stdIn) do printLine ("You said: " ++ readLine()) + printLine "EOF reached" + ``` + + # See also + + * {readyAndAble} checks if a {type Handle} is open, has not reached EOF, + and has at least one byte available to read. + * {ready} checks if a {type Handle} is ready for reading or writing, and + throws an {type Exception} if the {type Handle} is at EOF or is closed. + * {isReadable} checks if a {type Handle} is open in a readable + {type FileMode}, regardless of whether it has data available to read. + }} + +-- builtin IO.Handle.isEOF.impl : IO.Handle ->{IO} Either Failure Boolean + +IO.Handle.isOpen : Handle ->{IO, Exception} Boolean +IO.Handle.isOpen = Either.toException << isOpen.impl + +IO.Handle.isOpen.doc : Doc +IO.Handle.isOpen.doc = + {{ + Returns {true} if the given {type Handle} is open for reading or writing, and + {false} otherwise. + }} + +-- builtin IO.Handle.isOpen.impl : IO.Handle ->{IO} Either Failure Boolean + +IO.Handle.isReadable : Handle ->{IO} Boolean +IO.Handle.isReadable h = isRight (catch do ready h) + +IO.Handle.isReadable.doc : Doc +IO.Handle.isReadable.doc = + {{ + Returns `` true `` if the {type Handle} is open in a readable {type FileMode} + and has not already reached EOF. Returns `` false `` otherwise. This function + does not block and is useful for checking if a {type Handle} is ready for + reading without actually reading from it. It never throws an + {type Exception}. + + This function differs from {readyAndAble} in that it does not check if there + is data available to read from the {type Handle}. It only checks if the + {type Handle} is open and has not reached EOF. + + {{ + docCallout + (Some {{ ⚠️ }}) + {{ + Use with caution. Even though this function never throws an + {type Exception}, it may return `` false `` even if the next read from the + {type Handle} would result in an EOF error. Particularly with pipes, + sockets, and terminals, it's possible for the handle's state to change + between the time this function is called and the time the next read is + attempted, as the other end of the pipe or socket may close or the terminal + may send an EOF signal. Use {isEOF} to reliably check if the {type Handle} + is at EOF, blocking if necessary. + }} }} + + # See also + + * {isEOF} checks if a {type Handle} is at EOF, regardless of whether it has + data available to read. Note that {isEOF} may block. + * {readyAndAble} checks if a {type Handle} is open, has not reached EOF, + and has at least one byte available to read. + * {ready} checks if a {type Handle} has data available to read. Throws an + {type Exception} if the end of the stream has been reached or if the + {type Handle} is closed. + }} + +IO.Handle.isSeekable : Handle ->{IO, Exception} Boolean +IO.Handle.isSeekable h = Either.toException (isSeekable.impl h) + +IO.Handle.isSeekable.doc : Doc +IO.Handle.isSeekable.doc = + {{ + Returns `` true `` if the given {type Handle} is seekable, which means that + it represents a file whose position can be changed with {seek}. Returns `` + false `` if the {type Handle} is not seekable, for examople if it represents + {stdIn} or {stdOut}, or if it is a pipe, FIFO object, or {type Socket}. + }} + +-- builtin IO.Handle.isSeekable.impl : IO.Handle ->{IO} Either Failure Boolean + +IO.Handle.position : Handle ->{IO, Exception} Nat +IO.Handle.position h = Either.toException (getPosition.impl h) + +IO.Handle.position.doc : Doc +IO.Handle.position.doc = + {{ + Returns the current position in the given {type Handle}. The position is + measured in bytes from the beginning of the file. + + The position is updated by e.g. {getBytes}, {getSomeBytes}, {putBytes}, + {getLine}, {putText}, and {seek}. + }} + +IO.Handle.putBytes : Handle -> Bytes ->{IO, Exception} () +IO.Handle.putBytes = compose2 Either.toException putBytes.impl + +IO.Handle.putBytes.doc : Doc +IO.Handle.putBytes.doc = + {{ + Writes the given {type Bytes} to current {position} of the given + {type Handle}. + }} + +-- builtin IO.Handle.putBytes.impl : +-- IO.Handle -> Bytes ->{IO} Either Failure () + +IO.Handle.putLine : Handle -> Text ->{IO, Exception} () +IO.Handle.putLine h t = + use Text ++ + putText h (t ++ "\n") + +IO.Handle.putLine.doc : Doc +IO.Handle.putLine.doc = + {{ + Write a {type Text} value to a {type Handle}, followed by a newline + character. + + # Examples + + @typecheck ``` + do + (stdin, stdout, stderr, p) = start "cat" [] + putLine stdin "hello world" + Handle.close stdin + (getLine stdout, Process.wait p) + ``` + }} + +IO.Handle.putText : Handle -> Text ->{IO, Exception} () +IO.Handle.putText h = putBytes h << Text.toUtf8 + +IO.Handle.putText.doc : Doc +IO.Handle.putText.doc = + {{ Prints text to the given file handle by converting it to UTF-8. }} + +IO.Handle.ready : Handle ->{IO, Exception} Boolean +IO.Handle.ready = Either.toException << ready.impl + +IO.Handle.ready.doc : Doc +IO.Handle.ready.doc = + {{ + Checks if a {type Handle} has data available to read. Throws an + {type Exception} if the end of the stream has been reached or if the + {type Handle} is closed. + + # Example + + This program reads lines from {stdIn} and prints them. It will rudely + interrupt the user every 3 seconds: + + @typecheck ``` + main = + do + use Text ++ + printLine "Enter some text (press Ctrl-D to exit):" + readLoop = + do + match catch do ready stdIn with + Left _ -> printLine "All done!" + Right isReady -> + if isReady then + line = readLine() + printLine ("You said: " ++ line) + readLoop() + else + printLine + "I'm waiting for you to type something. I'll check again in a sec." + sleep (seconds +1) + readLoop() + readLoop() + ``` + + This is using {ready} to check if there is any data in the input buffer + before attempting to read from it. + + # See also + + * {readyAndAble} checks if a {type Handle} is open, has not reached EOF, + and has at least one byte available to read. Similar to {ready} but + doesn't throw an {type Exception} if the {type Handle} is at EOF. + * {isEOF} checks if a {type Handle} is at EOF, regardless of whether it has + data available to read. Note that {isEOF} may block. + * {isReadable} checks if a {type Handle} is open in a readable + {type FileMode} and has not reached EOF. + }} + +-- builtin IO.Handle.ready.impl : IO.Handle ->{IO} Either Failure Boolean + +IO.Handle.readyAndAble : Handle ->{IO} Boolean +IO.Handle.readyAndAble h = match ready.impl h with + Left _ -> false + Right false -> false + Right true -> true + +IO.Handle.readyAndAble.doc : Doc +IO.Handle.readyAndAble.doc = + {{ + Returns `` true `` if the {type Handle} is open in a readable + {type FileMode}, has not reached EOF, and has at least one byte available to + read. Returns `` false `` otherwise. It never throws an {type Exception}. + + This function does not block and is useful for checking if a {type Handle} is + ready for reading without actually reading from it. + + # Example + + This program checks {stdIn} on a regular cadence to see if there is any + data available to read, and prints it if there is: + + @typecheck ``` + readLoop = + do + use Text ++ + sleep (seconds +1) + if readyAndAble stdIn then + printLine ("You said: " ++ readLine()) + readLoop() + else + printLine + "I'm waiting for you to type something. I'll check again in a sec." + readLoop() + readLoop() + ``` + + Note that this program totally ignores the end-of-file signal (Ctrl-D) and + will keep checking for input even after the user has sent it. + + # See also + + * {isEOF} checks if a {type Handle} is at EOF, regardless of whether it has + data available to read. Note that {isEOF} may block. + * {ready} checks if a {type Handle} is ready for reading or writing, and + throws an {type Exception} if the {type Handle} is at EOF or is closed. + * {isReadable} checks if a {type Handle} is open in a readable + {type FileMode} and has not reached EOF. + }} + +IO.Handle.seek : Handle -> SeekMode -> Int ->{IO, Exception} () +IO.Handle.seek h mode n = Either.toException (seek.impl h mode n) + +IO.Handle.seek.doc : Doc +IO.Handle.seek.doc = + {{ + Seeks to a position in a file, given an offset and a {type SeekMode}. + Subsequent reads and writes will occur at the new position. + + The offset is a {type Nat} number of bytes. If the offset would take the + position outside the file, the position is clamped to the beginning or end of + the file. + + The available {type SeekMode}s are: + + * {AbsoluteSeek} - seek from the beginning of the file. The offset is + expected to be in the range 0 to the file size (inclusive). + * {RelativeSeek} - seek relative to the current position. If the offset is + negative, the file handle seeks backwards. If the offset is positive, the + file handle seeks forwards. + * {SeekFromEnd} - seek from the end of the file. If the offset is negative, + the file handle seeks backwards. If the offset is positive, the file handle + seeks to the end of the file. + }} + +-- builtin IO.Handle.seek.impl : +-- IO.Handle -> SeekMode -> Int ->{IO} Either Failure () + +IO.Handle.SeekMode.doc : Doc +IO.Handle.SeekMode.doc = + {{ + The type {type SeekMode} represents the different ways in which {seek} can be + used to move the current position in a {type Handle}. + + * {AbsoluteSeek} seeks to an absolute position in the file. + * {RelativeSeek} seeks to a position relative to the current position. + * {SeekFromEnd} seeks to a position relative to the end of the file. + }} + +IO.Handle.setBuffering : Handle -> BufferMode ->{IO, Exception} () +IO.Handle.setBuffering h bm = Either.toException (setBuffering.impl h bm) + +IO.Handle.setBuffering.doc : Doc +IO.Handle.setBuffering.doc = + {{ + Sets the buffering mode of a {type Handle}. + + # Example + + @typecheck ``` + setBuffering stdIn NoBuffering + ``` + + See {type BufferMode} for more information on buffering modes and their + meanings. + }} + +-- builtin IO.Handle.setBuffering.impl : +-- IO.Handle -> BufferMode ->{IO} Either Failure () + +IO.Handle.setEcho : Handle -> Boolean ->{IO, Exception} () +IO.Handle.setEcho on = Either.toException << setEcho.impl on + +IO.Handle.setEcho.doc : Doc +IO.Handle.setEcho.doc = + {{ + Sets the echo mode of a {type Handle} connected to a terminal. + + # Example + + Turn off echo for {stdIn}: + + @typecheck ``` + setEcho stdIn false + ``` + }} + +-- builtin IO.Handle.setEcho.impl : +-- IO.Handle -> Boolean ->{IO} Either Failure () + +-- builtin IO.Handle.std : Std -> IO.Handle + +IO.Handle.std.doc : Doc +IO.Handle.std.doc = + {{ + Constructs a {type Handle} value from a {type Std} value representing a + standard file handle; the standard input, output, or error stream. + }} + +IO.Handle.Std.doc : Doc +IO.Handle.Std.doc = + {{ + An enumeration for standard file handles: the standard input, output, or + error stream. + + # Example + + @source{stdOut} + }} + +IO.Handle.Std.StdErr.doc : Doc +IO.Handle.Std.StdErr.doc = + {{ The {type Std} value for the standard error handle. }} + +IO.Handle.Std.StdIn.doc : Doc +IO.Handle.Std.StdIn.doc = + {{ The {type Std} value for the standard input handle. }} + +IO.Handle.Std.StdOut.doc : Doc +IO.Handle.Std.StdOut.doc = + {{ The {type Std} value for the standard output handle. }} + +IO.Handle.stdErr : Handle +IO.Handle.stdErr = std StdErr + +IO.Handle.stdErr.doc : Doc +IO.Handle.stdErr.doc = + {{ A file handle to the operating environment's standard error stream. }} + +IO.Handle.stdIn : Handle +IO.Handle.stdIn = std StdIn + +IO.Handle.stdIn.doc : Doc +IO.Handle.stdIn.doc = + {{ + A file handle to the operating environment's standard input stream. + + {{ setBuffering.doc }} + }} + +IO.Handle.stdOut : Handle +IO.Handle.stdOut = std StdOut + +IO.Handle.stdOut.doc : Doc +IO.Handle.stdOut.doc = + {{ A file handle to the operating environment's standard output stream. }} + +-- builtin IO.Handle.toText : IO.Handle -> Text + +IO.Handle.toText.doc : Doc +IO.Handle.toText.doc = + {{ + Renders the given {type Handle} as a {type Text}. For a file handle, this + contains the path to the file in a platform-specific format. For a standard + handle like {stdOut}, this contains the name of the standard handle. + }} + +IO.IOError.doc : Doc +IO.IOError.doc = + {{ + An {type IOError} represents an error that can occur when performing an + {type IO} action. + + * {AlreadyExists} - The file or directory already exists. + * {EOF} - The end of the file has been reached. + * {IllegalOperation} - The operation is not legal for the object being + operated on. + * {NoSuchThing} - The file or directory does not exist. + * {PermissionDenied} - The user does not have permission to perform the + operation. + * {ResourceBusy} - The requested resource is locked or in use. + * {ResourceExhausted} - No more resources of the requested type are + available. + * {UserError} - A user-defined error. + }} + +IO.IOFailure.doc : Doc +IO.IOFailure.doc = + {{ + A type of {type Failure} raised by {type IO} operations, for example when a + file cannot be opened. + }} + +IO.net.Connection.accept : ListeningServerSocket ->{IO, Exception} Connection +IO.net.Connection.accept = Socket.accept >> socket + +IO.net.Connection.accept.doc : Doc +IO.net.Connection.accept.doc = + {{ + Accept a connection on a listening TCP socket. Returns a new + {type Connection} object representing the connection. + }} + +IO.net.Connection.client : HostName -> Port ->{IO, Exception} Connection +IO.net.Connection.client host port = socket (Socket.client host port) + +IO.net.Connection.client.doc : Doc +IO.net.Connection.client.doc = + {{ + Connect to a TCP server. Returns a new {type Connection} object representing + the connection. + }} + +IO.net.Connection.close : Connection ->{IO, Exception} () +IO.net.Connection.close c = closer c () + +IO.net.Connection.close.doc : Doc +IO.net.Connection.close.doc = + {{ + Close the {type Connection}. If the connection is already closed, this is a + no-op. + }} + +IO.net.Connection.closer : Connection -> '{IO, Exception} () +IO.net.Connection.closer = cases Connection _ _ close -> close + +IO.net.Connection.closer.doc : Doc +IO.net.Connection.closer.doc = {{ Closes the connection. }} + +IO.net.Connection.closer.modify : + ('{IO, Exception} () ->{g} '{IO, Exception} ()) + -> Connection + ->{g} Connection +IO.net.Connection.closer.modify f = cases + Connection send receive close -> Connection send receive (f close) + +IO.net.Connection.closer.set : '{IO, Exception} () -> Connection -> Connection +IO.net.Connection.closer.set close1 = cases + Connection send receive _ -> Connection send receive close1 + +IO.net.Connection.doc : Doc +IO.net.Connection.doc = + use Connection accept close receive send + use Text toUtf8 + {{ + An abstract type representing a network connection. {send} sends bytes to the + connection. {receive} receives bytes from the connection. {close} closes the + connection. + + # Creating a connection + + You can use the {Connection.socket} helper function to create a connection + from a {type Socket}: + + @typecheck ``` + socket = Socket.client (HostName "example.com") (Port "80") + conn = Connection.socket socket + send conn (toUtf8 "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") + ``` + + The {Connection.client} function creates a connection to a server at the + given host and port: + + @typecheck ``` + conn = Connection.client (HostName "example.com") (Port "80") + send conn (toUtf8 "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") + ``` + + A connection can also be created for a server using {accept}, which blocks + until a client connects: + + @typecheck ``` + server = Socket.server None (Port "8080") + conn = accept (listen server) + send conn (toUtf8 "Hello, client!") + ``` + + ## Secure connections + + A TLS-secured {type Connection} can be created using the {tls} function: + + @typecheck ``` + conn = tls (HostName "example.com") (Port "443") + send conn (toUtf8 "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") + ``` + + See also {withConfig}, {fromSocket}, and {fromSocketWithConfig} for more + options. + + ## Sized connections + + Use {sizedSocket} to create a connection that sets an upper limit on the + number of bytes that can be received in a single call to {receive}: + + @typecheck ``` + socket = Socket.client (HostName "example.com") (Port "80") + conn = sizedSocket 1024 socket + send conn (toUtf8 "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") + atMost1k = receive conn + printLine (fromUtf8 atMost1k) + ``` + + ## Custom connections + + You can create a {type Connection} using the {Connection} constructor, + passing it functions to send and receive data over the connection. For + example, this is the implementation of {Connection.socket}: + + @source{Connection.socket} + + Note that the constructor takes a {receiver} and {closer}, thunks that + are used to implement the {receive} and {close} functions, respectively. + }} + +IO.net.Connection.receive : Connection ->{IO, Exception} Bytes +IO.net.Connection.receive c = receiver c () + +IO.net.Connection.receive.doc : Doc +IO.net.Connection.receive.doc = + {{ + Receive {type Bytes} from the {type Connection}. If the connection is closed, + an exception is raised. + + The number of bytes you get depends on how the {type Connection} was + constructed. See {socket} and {tls.deprecated} for example. + }} + +IO.net.Connection.receiver : Connection -> '{IO, Exception} Bytes +IO.net.Connection.receiver = cases Connection _ receive _ -> receive + +IO.net.Connection.receiver.doc : Doc +IO.net.Connection.receiver.doc = + {{ Receives the given number of bytes over the connection. }} + +IO.net.Connection.receiver.modify : + ('{IO, Exception} Bytes ->{g} '{IO, Exception} Bytes) + -> Connection + ->{g} Connection +IO.net.Connection.receiver.modify f = cases + Connection send receive close -> Connection send (f receive) close + +IO.net.Connection.receiver.modify.doc : Doc +IO.net.Connection.receiver.modify.doc = + {{ + Modifies the receive function of a {type Connection}. + + # Example + + This example modifies the receive function of a {type Connection} to print + the received bytes to the console: + + @typecheck ``` + observeReceiving c = + f receive = do + x = receive() + printLine (toHex.deprecated x) + x + receiver.modify f c + ``` + }} + +IO.net.Connection.receiver.set : + '{g, IO, Exception} Bytes -> Connection -> Connection +IO.net.Connection.receiver.set receive1 = cases + Connection send _ close -> Connection send receive1 close + +IO.net.Connection.receiver.set.doc : Doc +IO.net.Connection.receiver.set.doc = + {{ Sets the receive function of a {type Connection}. }} + +IO.net.Connection.send : Connection -> Bytes ->{IO, Exception} () +IO.net.Connection.send = cases Connection send _ _ -> send + +IO.net.Connection.send.doc : Doc +IO.net.Connection.send.doc = {{ Sends the given bytes over the connection. }} + +IO.net.Connection.send.modify : + ((Bytes ->{IO, Exception} ()) ->{g} Bytes ->{IO, Exception} ()) + -> Connection + ->{g} Connection +IO.net.Connection.send.modify f = cases + Connection send receive close -> Connection (f send) receive close + +IO.net.Connection.send.modify.doc : Doc +IO.net.Connection.send.modify.doc = + {{ + Modifies the send function of a {type Connection}. + + # Example + + This example modifies the send function of a {type Connection} to print the + message before sending it. + + @typecheck ``` + observeSending c = + f send x = + printLine (toHex.deprecated x) + send x + send.modify f c + ``` + }} + +IO.net.Connection.send.set : + (Bytes ->{IO, Exception} ()) -> Connection -> Connection +IO.net.Connection.send.set send1 = cases + Connection _ receive close -> Connection send1 receive close + +IO.net.Connection.send.set.doc : Doc +IO.net.Connection.send.set.doc = + {{ + Sets the send function of a {type Connection}. + + # Example + + @typecheck ``` + ttyConnection = + send.set + (putBytes stdOut) (Connection (do ()) (do getBytes stdIn 4096) do ()) + ``` + }} + +IO.net.Connection.sizedSocket : Nat -> Socket ->{IO, Exception} Connection +IO.net.Connection.sizedSocket n s = + Connection (Socket.send s) (do receiveAtMost s n) do Socket.close s + +IO.net.Connection.sizedSocket.doc : Doc +IO.net.Connection.sizedSocket.doc = + {{ + Create a {type Connection} from a {type Socket}, with a specified maximum + receive size. This is the maximum number of bytes you will receive from a + single call to {Connection.receive}. + + When the connection is closed, the socket is closed as well. + }} + +IO.net.Connection.socket : Socket -> Connection +IO.net.Connection.socket sock = + send = Socket.send sock + receive = do Socket.receive sock + close = do Socket.close sock + Connection send receive close + +IO.net.Connection.socket.doc : Doc +IO.net.Connection.socket.doc = + {{ + Wrap a TCP {type Socket} in an abstract {type Connection} interface. + + The maximum receive size is set to 4096. See {sizedSocket} if you need a + different maximum receive size. + + When the connection is closed, the socket is closed as well. + }} + +IO.net.Connection.tls : HostName -> Port ->{IO, Exception} Connection +IO.net.Connection.tls host port = + withConfig host port (ClientConfig.default host "") + +IO.net.Connection.tls.deprecated : TlsSocket -> Connection +IO.net.Connection.tls.deprecated sock = + send = TlsSocket.send sock + receive = do TlsSocket.receive sock + close = do TlsSocket.terminate sock + Connection send receive close + +IO.net.Connection.tls.deprecated.doc : Doc +IO.net.Connection.tls.deprecated.doc = + {{ + Wrap a {type TlsSocket} in an abstract {type Connection} interface. + + # Deprecated + + This function is deprecated because it is unable to properly handle the + implementation of {Connection.close}. Use {tls} instead. + }} + +IO.net.Connection.tls.doc : Doc +IO.net.Connection.tls.doc = + {{ + Creates a new TLS client connection from a {type HostName} and a {type Port} + for the server. Uses the default client configuration {ClientConfig.default}. + + This function opens a socket, performs the TLS handshake and returns a + {type Connection} that can be used to send and receive data over the secure + socket. + + # Example + + @typecheck ``` + host = HostName "example.com" + port = Port "443" + conn = tls host port + Connection.send + conn (Text.toUtf8 "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") + ``` + + # See also + + * {withConfig} - Allows you to specify a custom client configuration. + * {fromSocket} - Uses the default client configuration but requires you to + create the socket yourself. + * {fromSocketWithConfig} - Creates the socket for you, but allows you to + specify a custom client configuration. + }} + +IO.net.Connection.tls.fromSocket : + Socket -> HostName ->{IO, Exception} Connection +IO.net.Connection.tls.fromSocket socket host = + fromSocketWithConfig (ClientConfig.default host "") socket + +IO.net.Connection.tls.fromSocket.doc : Doc +IO.net.Connection.tls.fromSocket.doc = + {{ + Creates a new TLS client connection from a {type Socket} and a + {type HostName} for the server. Uses the default client configuration + {ClientConfig.default}. + + This function performs the TLS handshake and returns a {type Connection} that + can be used to send and receive data over the secure socket. + + # Example + + @typecheck ``` + host = HostName "example.com" + port = Port "443" + socket = Socket.client host port + conn = fromSocket socket host + Connection.send + conn (Text.toUtf8 "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") + ``` + + # See also + + * {withConfig} - Allows you to specify a custom client configuration. + * {tls} - a simpler function that uses the default client configuration and + opens a socket for you. + * {withConfig} - Also creates the socket for you, but allows you to specify + a custom client configuration. + }} + +IO.net.Connection.tls.fromSocketWithConfig : + ClientConfig -> Socket ->{IO, Exception} Connection +IO.net.Connection.tls.fromSocketWithConfig config socket = + tls = newClient config socket + tlsSock = handshake tls + send = TlsSocket.send tlsSock + recv = do TlsSocket.receive tlsSock + close = do + TlsSocket.terminate tlsSock + Socket.close socket + Connection send recv close + +IO.net.Connection.tls.fromSocketWithConfig.doc : Doc +IO.net.Connection.tls.fromSocketWithConfig.doc = + {{ + Creates a new TLS client connection from a {type ClientConfig} and a + {type Socket}. + + This function performs the TLS handshake and returns a {type Connection} that + can be used to send and receive data over the secure socket. + + # Example + + @typecheck ``` + host = HostName "example.com" + port = Port "443" + config = ClientConfig.default host "" + socket = Socket.client host port + conn = fromSocketWithConfig config socket + Connection.send + conn (Text.toUtf8 "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") + ``` + + # See also + + * {tls} - a simpler function that uses the default client configuration and + opens a socket for you. + * {withConfig} - Also creates the socket for you, but allows you to specify + a custom client configuration. + * {fromSocket} - Uses the default client configuration but requires you to + create the socket yourself. + }} + +IO.net.Connection.tls.withConfig : + HostName -> Port -> ClientConfig ->{IO, Exception} Connection +IO.net.Connection.tls.withConfig host port config = + socket = Socket.client host port + fromSocketWithConfig config socket + +IO.net.Connection.tls.withConfig.doc : Doc +IO.net.Connection.tls.withConfig.doc = + {{ + Creates a new TLS client connection from a {type HostName}, a {type Port}, + and a {type ClientConfig}. + + This function opens a socket, performs the TLS handshake and returns a + {type Connection} that can be used to send and receive data over the secure + socket. + + # Example + + @typecheck ``` + host = HostName "example.com" + port = Port "443" + config = ClientConfig.default host "" + conn = withConfig host port config + Connection.send + conn (Text.toUtf8 "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") + ``` + + # See also + + * {tls} - a simpler function that uses the default client configuration and + opens a socket for you. + * {fromSocket} - Uses the default client configuration but requires you to + create the socket yourself. + * {fromSocketWithConfig} - Creates the socket for you, but allows you to + specify a custom client configuration. + }} + +IO.net.HostName.doc : Doc +IO.net.HostName.doc = + {{ + Either a host name, like ``"unison-lang.org"``, an IPv4 address like + ``"192.168.0.1"``, or an IPv6 address like ``"2604:a880:4:1d0::d2:8000"``. + + # Construction + + You can construct a {type HostName} from a {type Text} value: + + ``` + HostName "unison-lang.org" + ``` + + # Conversion to Text + + You can convert a {type HostName} to a {type Text} value: + + ``` + HostName.toText (HostName "unison-lang.org") + ``` + + # See also + + * {type Port} + }} + +IO.net.HostName.toText : HostName -> Text +IO.net.HostName.toText = cases HostName host -> host + +IO.net.HostName.toText.doc : Doc +IO.net.HostName.toText.doc = {{ Converts a {type HostName} to a {type Text}. }} + +IO.net.Port.doc : Doc +IO.net.Port.doc = + {{ Either a service name like "http" or a port number like "8080". }} + +IO.net.Port.number : Nat -> Port +IO.net.Port.number n = Port (Nat.toText n) + +IO.net.Port.number.doc : Doc +IO.net.Port.number.doc = + {{ + Converts a {type Nat} to a {type Port}. + + # Example + + ``` + Port.number 8080 + ``` + }} + +IO.net.Port.toText : Port -> Text +IO.net.Port.toText = cases Port port -> port + +IO.net.Port.toText.doc : Doc +IO.net.Port.toText.doc = + {{ + Converts a {type Port} to a {type Text} representation. + + # Example + + ``` + Port.toText (Port "8080") + ``` + }} + +IO.net.Socket.accept : ListeningServerSocket ->{IO, Exception} Socket +IO.net.Socket.accept = cases ListeningServerSocket sock -> accept.raw sock + +IO.net.Socket.accept.doc : Doc +IO.net.Socket.accept.doc = + {{ + Accept an incoming connection on the given {type ListeningServerSocket}. The + socket will become a {type Socket} which can be used to send and receive data + via {Socket.send} and {Socket.receive}. + }} + +-- builtin IO.net.Socket.accept.impl : +-- IO.net.Socket ->{IO} Either Failure IO.net.Socket + +IO.net.Socket.accept.raw : Socket ->{IO, Exception} Socket +IO.net.Socket.accept.raw = Either.toException << accept.impl + +IO.net.Socket.accept.raw.doc : Doc +IO.net.Socket.accept.raw.doc = + {{ + Accepts a connection on the given {type Socket}, returning a new + {type Socket} that can be used to {Socket.send} and {Socket.receive} data. + + This is a low-level function that accepts a raw socket and may fail if the + socket is not in the correct state. Use {Socket.accept} instead to ensure the + socket is listening for connections. + }} + +IO.net.Socket.BoundServerSocket.doc : Doc +IO.net.Socket.BoundServerSocket.doc = + {{ + A {type BoundServerSocket} is a local TCP socket that is ready to start + listening for incoming connections via {listen}, at which time it will become + a {type ListeningServerSocket}. + }} + +IO.net.Socket.client : HostName -> Port ->{IO, Exception} Socket +IO.net.Socket.client = cases + HostName host, Port port -> Either.toException (client.impl host port) + +IO.net.Socket.client.deprecated : Text -> Text ->{IO, Exception} Socket +IO.net.Socket.client.deprecated = compose2 Either.toException client.impl + +IO.net.Socket.client.doc : Doc +IO.net.Socket.client.doc = + {{ + `` Socket.client host port `` obtains a {type Socket} connected to the given + {type HostName} `host` and TCP service with the {type Port} `port`. + + You should close the socket with {Socket.close} when you're finished with it. + + The socket will have the `TCP_NODELAY` and `TCP_KEEPALIVE` options set. + }} + +-- builtin IO.net.Socket.client.impl : +-- Text -> Text ->{IO} Either Failure IO.net.Socket + +IO.net.Socket.close : Socket ->{IO, Exception} () +IO.net.Socket.close = Either.toException << Socket.close.impl + +IO.net.Socket.close.doc : Doc +IO.net.Socket.close.doc = {{ Closes a {type Socket}. }} + +-- builtin IO.net.Socket.close.impl : IO.net.Socket ->{IO} Either Failure () + +IO.net.Socket.doc : Doc +IO.net.Socket.doc = + use Socket accept client close receive send + use Text toUtf8 + {{ + The {type Socket} type represents a TCP socket. It can be either a listening + socket created via {server}, or a connection to a server via {client}. + + # Client sockets + + A client socket is created via {client}. + + @signature{client} + + The {type HostName} can be either a domain name like `` + "www.unison-lang.org" `` or an IP address like ``"10.0.0.1"``. The + {type Port} is either a number like `` "8080" `` or a service name like + ``"http"``. + + Once a client socket is created, you can use {send} to send {type Bytes} to + the server, and {receive} to receive {type Bytes} from the server: + + @signatures{send, receive} + + When you're done with the socket, you should close it with {close}: + + @signature{close} + + ## Examples + + Connect to a server on localhost, port 80: + + @typecheck ``` + socket = client (HostName "localhost") (Port "80") + send socket (toUtf8 "GET / HTTP/1.0\r\n\r\n") + response = receive socket + close socket + response + ``` + + Connect to a remote server on the HTTP service: + + @typecheck ``` + socket = client (HostName "www.google.com") (Port "http") + send socket (toUtf8 "GET / HTTP/1.0\r\n\r\n") + response = receive socket + close socket + response + ``` + + # Server sockets + + A server socket is created via {server}: + + @signature{server} + + If {None} is provided as the host name, the socket will be bound to any + available interface. + + The returned {type BoundServerSocket} is a socket that has been bound to a + specific port on a specific interface. It can be converted to a + {type ListeningServerSocket} via {listen}: + + @signature{listen} + + To accept a connection on a listening socket, use {accept}: + + @signature{accept} + + The resulting {type Socket} can be used to send and receive {type Bytes} + via {send} and {receive}: + + @signatures{send, receive} + + When you're done with the socket, you should close it with {close}: + + @signature{close} + + ## Examples + + A service bound to port 80 on any available interface: + + @typecheck ``` + socket = server None (Port "80") |> listen + connection = accept socket + bytes = receive connection + if startsWith "GET /" (fromUtf8 bytes) then + send connection (toUtf8 "HTTP/1.0 200 OK\r\n\r\nHello, world!") + else send connection (toUtf8 "HTTP/1.0 400 Bad Request\r\n\r\n") + close + ``` + }} + +IO.net.Socket.listen : + BoundServerSocket ->{IO, Exception} ListeningServerSocket +IO.net.Socket.listen = cases + BoundServerSocket sock -> + listen.raw sock + ListeningServerSocket sock + +IO.net.Socket.listen.doc : Doc +IO.net.Socket.listen.doc = + {{ + Start listening for incoming connections on the given + {type BoundServerSocket}. The socket will become a + {type ListeningServerSocket} which can be used to accept incoming connections + via {Socket.accept}. + }} + +-- builtin IO.net.Socket.listen.impl : IO.net.Socket ->{IO} Either Failure () + +IO.net.Socket.listen.raw : Socket ->{IO, Exception} () +IO.net.Socket.listen.raw = Either.toException << listen.impl + +IO.net.Socket.listen.raw.doc : Doc +IO.net.Socket.listen.raw.doc = + {{ + Listens for connections on the given {type Socket}. + + This is a low-level function that accepts a raw socket and may fail if the + socket is not in the correct state. Use {listen} instead to ensure the socket + is bound to an address. + }} + +IO.net.Socket.ListeningServerSocket.doc : Doc +IO.net.Socket.ListeningServerSocket.doc = + {{ + A {type ListeningServerSocket} is a local TCP socket that is listening for + incoming connections and can be used to accept them via {Socket.accept} which + will return a {type Socket} which can be used to send and receive data via + {Socket.send} and {Socket.receive}. + }} + +IO.net.Socket.port : Socket ->{IO, Exception} Port +IO.net.Socket.port = Port << Nat.toText << Either.toException << port.impl + +IO.net.Socket.port.doc : Doc +IO.net.Socket.port.doc = + {{ Returns the {type Port} that a {type Socket} is connected to. }} + +-- builtin IO.net.Socket.port.impl : IO.net.Socket ->{IO} Either Failure Nat + +IO.net.Socket.portNumber : Socket ->{IO, Exception} Nat +IO.net.Socket.portNumber = Either.toException << port.impl + +IO.net.Socket.portNumber.doc : Doc +IO.net.Socket.portNumber.doc = + {{ + Returns the port number that a {type Socket} is connected to, as a {type Nat} + }} + +IO.net.Socket.receive : Socket ->{IO, Exception} Bytes +IO.net.Socket.receive s = receiveAtMost s 4096 + +IO.net.Socket.receive.doc : Doc +IO.net.Socket.receive.doc = + {{ + Receives at most 4096 {type Bytes} from the given {type Socket}. The returned + {type Bytes} may be empty if the socket is closed. + }} + +IO.net.Socket.receiveAtMost : Socket -> Nat ->{IO, Exception} Bytes +IO.net.Socket.receiveAtMost = compose2 Either.toException receiveAtMost.impl + +IO.net.Socket.receiveAtMost.doc : Doc +IO.net.Socket.receiveAtMost.doc = + {{ + Receives up to the given number of {type Bytes} from the socket. Raises an + {type Exception} if the socket could not be read from. + }} + +-- builtin IO.net.Socket.receiveAtMost.impl : +-- IO.net.Socket -> Nat ->{IO} Either Failure Bytes + +IO.net.Socket.send : Socket -> Bytes ->{IO, Exception} () +IO.net.Socket.send s bs = Either.toException (Socket.send.impl s bs) + +IO.net.Socket.send.doc : Doc +IO.net.Socket.send.doc = {{ Sends {type Bytes} to a {type Socket}. }} + +-- builtin IO.net.Socket.send.impl : +-- IO.net.Socket -> Bytes ->{IO} Either Failure () + +IO.net.Socket.server : + Optional HostName -> Port ->{IO, Exception} BoundServerSocket +IO.net.Socket.server host port = BoundServerSocket (server.raw host port) + +IO.net.Socket.server.deprecated : + Optional Text -> Text ->{IO, Exception} Socket +IO.net.Socket.server.deprecated = compose2 Either.toException server.impl + +IO.net.Socket.server.doc : Doc +IO.net.Socket.server.doc = + {{ + Create a {type BoundServerSocket} that can be used to listen for incoming + connections. The socket will be bound to the given hostname and port. If the + hostname is set to {None}, the socket will be bound to any available local + address. + }} + +-- builtin IO.net.Socket.server.impl : +-- Optional Text -> Text ->{IO} Either Failure IO.net.Socket + +IO.net.Socket.server.raw : Optional HostName -> Port ->{IO, Exception} Socket +IO.net.Socket.server.raw host port = + Either.toException + (server.impl (Optional.map HostName.toText host) (Port.toText port)) + +IO.net.Socket.server.raw.doc : Doc +IO.net.Socket.server.raw.doc = + {{ + Creates a {type Socket} bound to the given {type HostName} and {type Port} + that can be used to listen for connections. + + This is a low-level function that returns a raw socket which may fail if the + socket state is not correctly managed. Use {server} instead to ensure you get + a socket that is bound to an address. + }} + +-- builtin IO.net.Socket.toText : IO.net.Socket -> Text + +IO.net.Socket.toText.doc : Doc +IO.net.Socket.toText.doc = + {{ + Returns a {type Text} representation of the given {type Socket}, which is + operating system dependent. + }} + +IO.net.Socket.UnboundServerSocket.doc : Doc +IO.net.Socket.UnboundServerSocket.doc = + {{ + An {type UnboundServerSocket} is a local TCP socket that cannot be used to + accept connections until it is bound to a local address. + }} + +IO.net.Tls.Cipher.doc : Doc +IO.net.Tls.Cipher.doc = {{ A cipher algorithm. }} + +-- builtin IO.net.Tls.ClientConfig.certificates.set : +-- [IO.net.Tls.SignedCert] +-- -> IO.net.Tls.ClientConfig +-- -> IO.net.Tls.ClientConfig + +IO.net.Tls.ClientConfig.certificates.set.doc : Doc +IO.net.Tls.ClientConfig.certificates.set.doc = + {{ Sets the list of certificates of a {type ClientConfig}. }} + +IO.net.Tls.ClientConfig.default : HostName -> Text -> ClientConfig +IO.net.Tls.ClientConfig.default = cases + HostName hostname, suffix -> default.impl hostname (Text.toUtf8 suffix) + +IO.net.Tls.ClientConfig.default.doc : Doc +IO.net.Tls.ClientConfig.default.doc = + {{ + Create a default TLS client configuration for the given hostname and + certificate suffix. + + The suffix is used to identify the certificate per service on the host, if + the host has multiple services with different certificates. + + For a host with only one service running, the suffix is usually empty, but + for a host with multiple services with different certificates, the suffix + could be the service name (e.g. `` ":https" `` or ``":smtp"``), or the port + (e.g. `` ":443" `` or ``":995"``). + }} + +-- builtin IO.net.Tls.ClientConfig.default.impl : +-- Text -> Bytes -> IO.net.Tls.ClientConfig + +IO.net.Tls.ClientConfig.doc : Doc +IO.net.Tls.ClientConfig.doc = + use ClientConfig default + use ClientConfig.certificates set + {{ + A {type ClientConfig} is created with {default}: + + @signature{default} + + The {type HostName} argument is the hostname of the server you're connecting + to, and the {type Text} argument is the certificate suffix. For a host with + only one service running, the suffix should usually be ``""``. If present, + the suffix is used to identify the certificate per service on the host, if + the host has multiple services with different certificates (see {default}). + + If you need to assign your own certificates to the {type ClientConfig}, you + can use {set}: + + @signature{set} + + A {type SignedCert} can be loaded from {type Bytes} with {decodeCert}: + + @signature{decodeCert} + }} + +-- builtin IO.net.Tls.decodeCert : +-- Bytes -> Either Failure IO.net.Tls.SignedCert + +IO.net.Tls.decodeCert.doc : Doc +IO.net.Tls.decodeCert.doc = + {{ + Decodes a {type SignedCert} from {type Bytes} in the X.509 signed certificate + format. + }} + +-- builtin IO.net.Tls.decodePrivateKey : Bytes -> [IO.net.Tls.PrivateKey] + +IO.net.Tls.decodePrivateKey.doc : Doc +IO.net.Tls.decodePrivateKey.doc = + {{ + Decodes [PEM-encoded](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) + {type Bytes} into a {type List} of {type Tls.PrivateKey}s. + }} + +IO.net.Tls.doc : Doc +IO.net.Tls.doc = + use Socket close + use TlsSocket receive send terminate + {{ + The {type Tls} type implements Transport Layer Security (TLS), providing + authentication and encryption. It can be either a server created via + {newServer}, or a client created via {newClient}. Either way requires a + {type Socket}. + + # TLS clients + + A TLS client is created via {newClient}: + + @signature{newClient} + + The {type Socket} argument is the underlying socket to use for the TLS + connection. The {type ClientConfig} contains TLS-specific information, + including the certificate authority to use for authentication. + + ## TLS Client configuration + + {{ ClientConfig.doc }} + + ## Connecting to a TLS server + + Once a client socket is created, you need to perform the TLS handshake: + + @signature{handshake} + + This returns a {type TlsSocket} which can be used to send and receive + {type Bytes}. + + Once the handshake is complete, you can use {send} to send {type Bytes} + to the server, and {receive} to receive {type Bytes} from the + {type TlsSocket}: + + @signatures{send, receive} + + When you're done with the {type TlsSocket}, you should call {terminate}. + Note that this does not close the underlying {type Socket}: + + @signature{terminate} + + Then close the underlying socket with {close}: + + @signature{close} + + ## Example + + Connect to a TLS server: + + @typecheck ``` + socket = Socket.client (HostName "google.com") (Port "443") + config = ClientConfig.default (HostName "google.com") "" + tls = newClient config socket + conn = handshake tls + send conn (Text.toUtf8 "GET / HTTP/1.0\r\n\r\n") + response = receive conn + terminate conn + close socket + fromUtf8 response + ``` + + # TLS servers + + A TLS server is created via {newServer}: + + @signature{newServer} + + The {type Socket} argument is the underlying server socket to use for the + TLS connection. Normally this would be created via {Socket.accept}. The + {type ServerConfig} contains TLS-specific information, including the + certificate and private key to use for authentication. + + ## TLS Server configuration + + A {type ServerConfig} is created with {ServerConfig.default}: + + @signature{ServerConfig.default} + + You should pass your own list of {type SignedCert}s and the + {type Tls.PrivateKey} of the service. These can be loaded from + {type Bytes} with {decodeCert} and {decodePrivateKey}, respectively: + + @signatures{decodeCert, decodePrivateKey} + + ## Starting a TLS server + + Once the {type Tls} context is created, you need to perform the TLS + handshake: + + @signature{handshake} + + Once the handshake is complete, you can use {send} to send {type Bytes} + to the client, and {receive} to receive {type Bytes} from the resulting + {type TlsSocket}: + + @signatures{send, receive} + + When you're done with the {type TlsSocket}, you should call {terminate}: + + @signature{terminate} + + Then close the underlying socket: + + @signature{close} + }} + +-- builtin IO.net.Tls.encodeCert : IO.net.Tls.SignedCert -> Bytes + +IO.net.Tls.encodeCert.doc : Doc +IO.net.Tls.encodeCert.doc = + {{ + Encodes a {type SignedCert} to {type Bytes} in the X.509 signed certificate + format. + }} + +-- builtin IO.net.Tls.encodePrivateKey : IO.net.Tls.PrivateKey -> Bytes + +IO.net.Tls.encodePrivateKey.doc : Doc +IO.net.Tls.encodePrivateKey.doc = + {{ + Encodes a {type Tls.PrivateKey}s into + [PEM-encoded](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) + {type Bytes}. + }} + +IO.net.Tls.handshake : Tls ->{IO, Exception} TlsSocket +IO.net.Tls.handshake tls = + Either.toException (handshake.impl tls) + TlsSocket tls + +IO.net.Tls.handshake.doc : Doc +IO.net.Tls.handshake.doc = + {{ + Perform the TLS handshake on the given TLS client or server. The socket can't + be used until the handshake has been completed. The socket will become a + {type TlsSocket} which can be used to send and receive data via + {TlsSocket.send} and {TlsSocket.receive}. + }} + +-- builtin IO.net.Tls.handshake.impl : IO.net.Tls ->{IO} Either Failure () + +IO.net.Tls.newClient : ClientConfig -> Socket ->{IO, Exception} Tls +IO.net.Tls.newClient config = Either.toException << newClient.impl config + +IO.net.Tls.newClient.doc : Doc +IO.net.Tls.newClient.doc = + {{ + Create a new TLS client with the given configuration. The socket can't be + used until the TLS handshake has been completed via {handshake}. + }} + +-- builtin IO.net.Tls.newClient.impl : +-- IO.net.Tls.ClientConfig -> IO.net.Socket ->{IO} Either Failure IO.net.Tls + +IO.net.Tls.newServer : ServerConfig -> Socket ->{IO, Exception} Tls +IO.net.Tls.newServer config = Either.toException << newServer.impl config + +IO.net.Tls.newServer.doc : Doc +IO.net.Tls.newServer.doc = + {{ + Create a new TLS server with the given configuration. The socket can't be + used until the TLS handshake has been completed via {handshake}. + }} + +-- builtin IO.net.Tls.newServer.impl : +-- IO.net.Tls.ServerConfig -> IO.net.Socket ->{IO} Either Failure IO.net.Tls + +IO.net.Tls.PrivateKey.doc : Doc +IO.net.Tls.PrivateKey.doc = + {{ + A private key for use with {type Tls}. + + You can decode a private key from a + [PEM-encoded](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) + {type Bytes} using {decodePrivateKey}, and you can encode a private key to a + [PEM-encoded](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) + {type Bytes} using {encodePrivateKey}. + + You can create a {type ServerConfig} with a {type Tls.PrivateKey} via + {ServerConfig.default}. + + See {type Tls} for more information. + }} + +-- builtin IO.net.Tls.ServerConfig.certificates.set : +-- [IO.net.Tls.SignedCert] +-- -> IO.net.Tls.ServerConfig +-- -> IO.net.Tls.ServerConfig + +IO.net.Tls.ServerConfig.certificates.set.doc : Doc +IO.net.Tls.ServerConfig.certificates.set.doc = + {{ Sets the list of certificates of a {type ServerConfig}. }} + +-- builtin IO.net.Tls.ServerConfig.ciphers.set : +-- [IO.net.Tls.Cipher] -> IO.net.Tls.ServerConfig -> IO.net.Tls.ServerConfig + +IO.net.Tls.ServerConfig.ciphers.set.doc : Doc +IO.net.Tls.ServerConfig.ciphers.set.doc = + {{ Sets the list of ciphers of a {type ServerConfig}. }} + +-- builtin IO.net.Tls.ServerConfig.default : +-- [IO.net.Tls.SignedCert] +-- -> IO.net.Tls.PrivateKey +-- -> IO.net.Tls.ServerConfig + +IO.net.Tls.ServerConfig.default.doc : Doc +IO.net.Tls.ServerConfig.default.doc = + {{ The default {type ServerConfig} for TLS. }} + +IO.net.Tls.ServerConfig.doc : Doc +IO.net.Tls.ServerConfig.doc = + {{ + A TLS server configuration that specifies the certificate, private key, and + other settings for a TLS server. + + The default configuration is created with {ServerConfig.default}. + }} + +-- builtin IO.net.Tls.ServerConfig.versions.set : +-- [IO.net.Tls.Version] -> IO.net.Tls.ServerConfig -> IO.net.Tls.ServerConfig + +IO.net.Tls.ServerConfig.versions.set.doc : Doc +IO.net.Tls.ServerConfig.versions.set.doc = + {{ Sets the list of TLS versions of a {type ServerConfig}. }} + +IO.net.Tls.SignedCert.doc : Doc +IO.net.Tls.SignedCert.doc = + {{ + A signed certificate for use with {type Tls}. + + You can decode a signed certificate from a {type Bytes} using {decodeCert}, + and you can encode a signed certificate to {type Bytes} using {encodeCert}. + + You can create a {type ServerConfig} with a {type List} of {type SignedCert}s + via {ServerConfig.default}, and change the list of certificates accepted by a + {type ServerConfig} via {ServerConfig.certificates.set}. + + You can set the list of certificates accepted by a {type ClientConfig} via + {ClientConfig.certificates.set}. + + See {type Tls} for more information. + }} + +IO.net.Tls.terminate : Tls ->{IO, Exception} () +IO.net.Tls.terminate tls = Either.toException (terminate.impl tls) + +IO.net.Tls.terminate.doc : Doc +IO.net.Tls.terminate.doc = + {{ + Notifies the TLS context that this side of the connection wants to close the + connection. This function should be called before closing the underlying + socket with {Socket.close}, and it does not actually close the socket. + }} + +-- builtin IO.net.Tls.terminate.impl : IO.net.Tls ->{IO} Either Failure () + +IO.net.Tls.TlsFailure.doc : Doc +IO.net.Tls.TlsFailure.doc = + {{ + A type of {type Failure} raised by {type IO} operations that use {type Tls}, + for example when a TLS connection cannot be established. + }} + +IO.net.Tls.TlsSocket.doc : Doc +IO.net.Tls.TlsSocket.doc = + {{ + A {type TlsSocket} is a network connection that uses TLS to encrypt the data + sent and received. It wraps a {type Socket} and can be used to send and + receive data via {TlsSocket.send} and {TlsSocket.receive}. + }} + +IO.net.Tls.TlsSocket.receive : TlsSocket ->{IO, Exception} Bytes +IO.net.Tls.TlsSocket.receive = cases + TlsSocket tls -> Either.toException (receive.impl tls) + +IO.net.Tls.TlsSocket.receive.doc : Doc +IO.net.Tls.TlsSocket.receive.doc = + {{ Receive bytes from the given TLS socket. }} + +-- builtin IO.net.Tls.TlsSocket.receive.impl : +-- IO.net.Tls ->{IO} Either Failure Bytes + +IO.net.Tls.TlsSocket.send : TlsSocket -> Bytes ->{IO, Exception} () +IO.net.Tls.TlsSocket.send = cases + TlsSocket tls -> Either.toException << TlsSocket.send.impl tls + +IO.net.Tls.TlsSocket.send.doc : Doc +IO.net.Tls.TlsSocket.send.doc = + {{ Send the given bytes over the given TLS socket. }} + +-- builtin IO.net.Tls.TlsSocket.send.impl : +-- IO.net.Tls -> Bytes ->{IO} Either Failure () + +IO.net.Tls.TlsSocket.terminate : TlsSocket ->{IO, Exception} () +IO.net.Tls.TlsSocket.terminate = cases + TlsSocket tls -> Either.toException (terminate.impl tls) + +IO.net.Tls.TlsSocket.terminate.doc : Doc +IO.net.Tls.TlsSocket.terminate.doc = + {{ + Notifies the TLS context that the connection on this side of the interface + wants to close. This function should be called prior to closing the + underlying {type Socket} with {Socket.close}. Note that calling + {TlsSocket.terminate} does not actually close the socket. + + See {terminate_} for a version that won't raise an {type Exception}. + }} + +IO.net.Tls.TlsSocket.terminate_ : TlsSocket ->{IO} () +IO.net.Tls.TlsSocket.terminate_ = cases + TlsSocket tls -> ignore (terminate.impl tls) + +IO.net.Tls.TlsSocket.terminate_.doc : Doc +IO.net.Tls.TlsSocket.terminate_.doc = + {{ + Similar to {TlsSocket.terminate} but ignores exceptions (such as the socket + already being closed). This can be useful in a finalizer if you want to + ensure that TLS termination happens if possible. + }} + +IO.net.Tls.Version.doc : Doc +IO.net.Tls.Version.doc = + {{ + Represents the version of the TLS protocol used to secure a {type Tls} + {type Socket}. + }} + +IO.net.URI.authority : URI -> Optional Authority +IO.net.URI.authority = cases URI _ authority _ _ _ -> authority + +IO.net.URI.Authority.doc : Doc +IO.net.URI.Authority.doc = + {{ + A URI authority as described by + [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.2). + + An authority contains optional {Authority.userInfo}, a {Authority.host}, and + an optional {Authority.port}. The port can be excluded if it is the default + for the requested {type Scheme}. + }} + +IO.net.URI.Authority.fromHost : HostName -> Authority +IO.net.URI.Authority.fromHost host = Authority None host None + +IO.net.URI.authority.modify : + (Optional Authority ->{g} Optional Authority) -> URI ->{g} URI +IO.net.URI.authority.modify f = cases + URI scheme authority path query fragment -> + URI scheme (f authority) path query fragment + +IO.net.URI.authority.set : Optional Authority -> URI -> URI +IO.net.URI.authority.set authority1 = cases + URI scheme _ path query fragment -> URI scheme authority1 path query fragment + +IO.net.URI.Authority.toText : Authority -> Text +IO.net.URI.Authority.toText = cases + Authority userInfoMaybe (HostName host) portMaybe -> + use Optional fold + use Text ++ + userInfo = fold (do "") (cases UserInfo u -> u ++ "@") userInfoMaybe + port = fold (do "") (cases Port p -> ":" ++ p) portMaybe + userInfo ++ host ++ port + +test> IO.net.URI.Authority.toText.tests = + test.verify do + (expected, uri) = + each + [ ("unison-lang.org", parseOrBug "https://unison-lang.org") + , ("www.unison-lang.org", parseOrBug "https://www.unison-lang.org") + , ( "www.unison-lang.org:443" + , parseOrBug "https://www.unison-lang.org:443" + ) + , ( "alice:secret@www.unison-lang.org:443" + , parseOrBug "https://alice:secret@www.unison-lang.org:443" + ) + ] + actual = Optional.map Authority.toText (URI.authority uri) + ensureEqual (Some expected) actual + +IO.net.URI.doc : Doc +IO.net.URI.doc = + use Path / + use Query empty + use URI parse + {{ + # URI + + The {type URI} type represents a URI as defined by + [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986). + + A URI is comprised of the following: + + * a {type Scheme}, such as `http` or `https` + * a possibly empty {type Authority}, such as `www.unison-lang.org:80` + * a possibly empty {type Path}, such as `/docs/quickstart` + * a possibly empty {type RawQuery}, such as `?version=1.0` + * a possibly empty {type Fragment}, such as `#install-unison` + + ## Constructing a URI + + The easiest way to construct a URI is to parse one from Text using the + {{ docLink (docEmbedTermLink do parse) }} or {{ + docLink (docEmbedTermLink do parseOptional) }} functions. + + ``` + parseOptional "http://google.com/search?q=unison+programming+language" + ``` + + ``` + catch do parse "http://google.com/search?q=unison+programming+language" + ``` + + URI's can also be built up programmatically: + + ``` + query = empty & ("q", "unison+programming+language") & ("lang", "en") + path = root / "search" + fromPath path |> URI.https |> withQuery (fromQuery query) + ``` + + ## Query Parameters + + There are two types for representing query parameters. {type RawQuery} + represents the raw query string, and doesn't try to parse the contents + in any way. {type Query} is a type which represents a parsing of a query + string that splits it into a list of key-value pairs. + + ``` + fromRawQuery (RawQuery "foo=bar&baz=quux") + ``` + + ``` + fromQuery (empty & ("foo", "bar") & ("baz", "quux")) + ``` + }} + +IO.net.URI.encode.percent.char.special : Char -> Text +IO.net.URI.encode.percent.char.special c = + use Text ++ + go acc = cases + [x, y] ++ tail -> go (acc ++ fromCharList [?%, x, y]) tail + [x] -> bug "URI.encode.percent.char.special" + [] -> acc + hexBytes = + Bytes.toBase16 (Text.toUtf8 (Char.toText c)) |> fromUtf8.impl + |> Either.toOptional + |> getOrBug "bug in char.special" + |> toCharList + go "" hexBytes + +IO.net.URI.escape : Text -> Text +IO.net.URI.escape txt = + Text.join "" (List.map escapeQueryChar (toCharList txt)) + +IO.net.URI.escapeChar : Char -> Text +IO.net.URI.escapeChar = cases + ?? -> "%3F" + ?# -> "%23" + ?[ -> "%5B" + ?] -> "%5D" + ?@ -> "%40" + c -> Char.toText c + +IO.net.URI.escapeQueryChar : Char -> Text +IO.net.URI.escapeQueryChar = cases + ?? -> "%3F" + ?# -> "%23" + ?[ -> "%5B" + ?] -> "%5D" + ?@ -> "%40" + ?& -> "%26" + ?= -> "%3D" + ?+ -> "%2B" + ?$ -> "%24" + ?, -> "%2C" + ?\s -> "+" + c -> Char.toText c + +IO.net.URI.forceHostAndPort : URI -> Authority +IO.net.URI.forceHostAndPort = cases + URI _ (Some a@(Authority _ _ (Some _))) _ _ _ -> a + u@(URI _ (Some (Authority userInfo host None)) _ _ _) -> + Authority userInfo host (Some (URI.port u)) + u@(URI _ None _ _ _) -> Authority None (URI.host u) (Some (URI.port u)) + +IO.net.URI.fragment : URI -> Fragment +IO.net.URI.fragment = cases URI _ _ _ _ frag -> frag + +IO.net.URI.fragment.doc : Doc +IO.net.URI.fragment.doc = + {{ + Gets the fragment from the given {type URI} (the part after the `#` symbol). + }} + +IO.net.URI.Fragment.empty : Fragment +IO.net.URI.Fragment.empty = Fragment "" + +IO.net.URI.Fragment.toText : Fragment -> Text +IO.net.URI.Fragment.toText = cases Fragment t -> t + +IO.net.URI.fragmentText : URI -> Text +IO.net.URI.fragmentText = URI.fragment >> Fragment.toText + +IO.net.URI.fragmentText.doc : Doc +IO.net.URI.fragmentText.doc = + {{ + Gets the fragment from the given {type URI} (the part after the `#` symbol), + as {type Text}. + }} + +IO.net.URI.fromPath : Path -> URI +IO.net.URI.fromPath path = + URI Scheme.empty None path RawQuery.empty Fragment.empty + +IO.net.URI.host : URI -> HostName +IO.net.URI.host = cases + URI _ (Some (Authority _ host _)) _ _ _ -> host + _ -> HostName "localhost" + +IO.net.URI.http : URI -> URI +IO.net.URI.http = cases + URI scheme authority path query fragment -> + URI (Scheme "http") authority path query fragment + +IO.net.URI.https : URI -> URI +IO.net.URI.https = cases + URI scheme authority path query fragment -> + URI Scheme.https authority path query fragment + +IO.net.URI.parse : Text ->{Exception} URI +IO.net.URI.parse txt = + match IPattern.run uri txt with + None -> Exception.raiseFailure (typeLink ParseError) "Invalid URI" txt + Some (_, t)| Text.size t !== 0 -> + Exception.raiseFailure (typeLink ParseError) "Invalid URI" txt + Some ([scheme, userInfo, host, port, path, query, fragment], _) -> + use Abort toException + use Text ++ + authority' : {Exception} (Optional Authority) + authority' = + toException + (do parseAuthority userInfo host port) + (typeLink ParseError) + ("Invalid authority: " ++ txt) + path' = + toException + (do parsePath path) (typeLink ParseError) ("Invalid path: " ++ txt) + fragment' = + toException + (do parseFragment fragment) + (typeLink ParseError) + ("Invalid fragment:" ++ txt) + URI (Scheme scheme) authority' path' (RawQuery query) fragment' + Some _ -> + Exception.raiseFailure + (typeLink ParseError) "wrong number of URI parts captures" txt + +IO.net.URI.parse._internal.parseAuthority : + Text -> Text -> Text ->{Abort} Optional Authority +IO.net.URI.parse._internal.parseAuthority userInfo host port = + userInfo' = parseUserInfo userInfo + port' = parsePort port + match (userInfo', host, port') with + (None, "", None) -> None + (u, h, p) -> Some (Authority u (HostName h) p) + +IO.net.URI.parse._internal.parseFragment : Text ->{Abort} Fragment +IO.net.URI.parse._internal.parseFragment txt = + Fragment (renderPercentEncoded txt) + +IO.net.URI.parse._internal.parsePath : Text ->{Abort} Path +IO.net.URI.parse._internal.parsePath txt = + use List :+ + if Text.size txt === 0 || txt === "/" then Path [] + else + Path match Text.split ?/ (renderPercentEncoded txt) with + ["", ""] -> [""] + "" +: rest :+ "" -> rest :+ "" + "" +: rest -> rest + rest :+ "" -> rest :+ "" + rest -> rest + +IO.net.URI.parse._internal.parsePort : Text ->{Abort} Optional Port +IO.net.URI.parse._internal.parsePort txt = + if Text.size txt === 0 then None else Some (Port (renderPercentEncoded txt)) + +IO.net.URI.parse._internal.parseQuery : Text ->{Abort} Query +IO.net.URI.parse._internal.parseQuery txt' = + use Char == + use List ++ + use Text split + Query + let + txt = + renderPercentEncoded + (Text.map (c -> (if c == ?+ then ?\s else c)) txt') + makePair = cases + [] -> ("", [""]) + k +: vs -> (k, [Text.join "=" vs]) + split ?& txt |> List.map (makePair << split ?=) + |> List.foldRight + (cases + (k, v) -> if Text.size k === 0 then id else Map.putWith (++) k v) + Map.empty + +test> IO.net.URI.parse._internal.parseQuery.tests.percentPlusIsPlus = + test.verify do + ensureEqual + (toOptional! do parseQuery "a%2bb=c%2bd") + (Some (Query (Map.fromList [("a+b", ["c+d"])]))) + +test> IO.net.URI.parse._internal.parseQuery.tests.plusIsSpace = + test.verify do + ensureEqual + (toOptional! do parseQuery "a+b=c+d") + (Some (Query (Map.fromList [("a b", ["c d"])]))) + +IO.net.URI.parse._internal.parseUserInfo : Text ->{Abort} Optional UserInfo +IO.net.URI.parse._internal.parseUserInfo txt = + if Text.size txt === 0 then None + else Some (UserInfo (renderPercentEncoded txt)) + +IO.net.URI.parse._internal.renderPercentEncoded : Text ->{Abort} Text +IO.net.URI.parse._internal.renderPercentEncoded txt = + use Nat > + use Optional toAbort + pipe : '{Abort, Ask (Either Nat Text), Stream Text} () + pipe = + do + use List :+ + go : [Nat] ->{Abort, Ask (Either Nat Text), Stream Text} () + go bytes = + match ask with + Left byte -> go (bytes :+ byte) + Right text -> + match bytes with + [] -> () + bytes -> + decoded = + fromList.impl bytes |> fromUtf8.impl |> Either.toOptional + |> toAbort + emit decoded + emit text + go [] + go [] + tokens : '{Abort, Stream (Either Nat Text)} () + tokens = do match Text.split ?% txt with + [] -> () + h +: t -> + emit (Right h) + foreach.flipped + t (t -> let + (a, b) = Text.splitAt 2 t + emit (Left (Nat.fromHex a |> toAbort)) + if Text.size b > 0 then emit (Right b) else ()) + filtered = Stream.pipe (tokens Stream.++ (do emit (Right ""))) pipe + t : Text + t = Stream.fold (Text.++) "" filtered + t + +IO.net.URI.parseOptional : Text -> Optional URI +IO.net.URI.parseOptional txt = + match IPattern.run uri txt with + None -> None + Some (_, t)| Text.size t !== 0 -> None + Some ([scheme, userInfo, host, port, path, query, fragment], _) -> + parsed : '{Abort} URI + parsed = do + authority' = parseAuthority userInfo host port + path' = parsePath path + fragment' = parseFragment fragment + URI (Scheme scheme) authority' path' (RawQuery query) fragment' + Abort.toOptional parsed () + Some _ -> bug "wrong number of URI parts captures" + +IO.net.URI.parseOrBug : Text -> URI +IO.net.URI.parseOrBug uriText = match parseOptional uriText with + Some uri -> uri + None -> bug ("failed to parse URI", uriText) + +IO.net.URI.path : URI -> Path +IO.net.URI.path = cases URI _ _ path _ _ -> path + +(IO.net.URI.Path.++) : Path -> Path -> Path +basePath IO.net.URI.Path.++ relativePath = + use List ++ + Path (segments basePath ++ segments relativePath) + +(IO.net.URI.Path./) : Path -> Text -> Path +current IO.net.URI.Path./ child = + use List :+ + segments.modify (p -> p :+ child) current + +IO.net.URI.Path./.doc : Doc +IO.net.URI.Path./.doc = + use Path / + {{ + Appends a path segment to a {type Path}. + + Sometimes this can lead to more concise and clear {type Path} expressions + than manually manipulating the path segments list. This is particularly + useful if you have a base {type Path} and need to define several different + subpaths based on it. + + Example: + + ``` + root / "downloads" / "v1" + ``` + }} + +IO.net.URI.Path.doc : Doc +IO.net.URI.Path.doc = + use Path fromText + {{ + Parses a {type Text} into a {type Path}, or raises an exception if the + {type Text} cannot be parsed into a path. A valid path is a sequence of + segments separated by slashes (''/''). + + This function expands percent-encoded characters and throws an exception if + the path contains any invalid percent encoding. + + # Examples + + ``` + catch do fromText "/foo/bar/baz" + ``` + + ``` + catch do fromText "/foo/%20bar/baz" + ``` + + ``` + catch do fromText "/foo/%2Fbar/baz" + ``` + + ``` + catch do fromText "/foo/%bar/baz" + ``` + }} + +IO.net.URI.Path.encode : Path -> Bytes +IO.net.URI.Path.encode p = + use Bytes ++ + use fromList impl + foldDelimited (++) encode.segment 0xs2f 0xs2f Bytes.empty (segments p) + +IO.net.URI.Path.encode.char : Char -> Text +IO.net.URI.Path.encode.char = cases + c + | isAlphaNum c + || List.contains + c [?-, ?., ?_, ?~, ?:, ?@, ?!, ?$, ?&, ?\', ?(, ?), ?*, ?+, ?,, ?;, ?=] -> + fromCharList [c] + | otherwise -> + special c + +test> IO.net.URI.Path.encode.char.test = + inputToExpected = + [ (?\s, "%20") + , (?+, "+") + , (?:, ":") + , (?@, "@") + , (?-, "-") + , (?., ".") + , (?_, "_") + , (?~, "~") + , (?!, "!") + , (?$, "$") + , (?&, "&") + , (?\', "'") + , (?(, "(") + , (?), ")") + , (?*, "*") + , (?+, "+") + , (?,, ",") + , (?;, ";") + , (?=, "=") + , (?0, "0") + , (?9, "9") + , (?a, "a") + , (?z, "z") + , (?A, "A") + , (?Z, "Z") + , (??, "%3f") + , (?/, "%2f") + , (?🙃, "%f0%9f%99%83") + , (?ü, "%c3%bc") + , (?ÿ, "%c3%bf") + , (?\0, "%00") + , (?\", "%22") + ] + check = cases + (in, expected) -> + encoded = Path.encode.char in + expect (assertEquals encoded expected) + deprecated.run (Test.tests (List.map check inputToExpected)) + +IO.net.URI.Path.encode.segment : Text -> Bytes +IO.net.URI.Path.encode.segment t = + use Bytes ++ + go : Bytes -> Char -> Bytes + go acc c = acc ++ Text.toUtf8 (Path.encode.char c) + List.foldLeft go Bytes.empty (toCharList t) + +IO.net.URI.Path.fromText : Text ->{Exception} Path +IO.net.URI.Path.fromText s = + use Text ++ + handle parsePath s + with cases + { r } -> r + { abort -> _ } -> + Exception.raise (failure ("Invalid URI Path: " ++ s) (Any (s : Text))) + +IO.net.URI.path.modify : (Path ->{g} Path) -> URI ->{g} URI +IO.net.URI.path.modify f = cases + URI scheme authority path query fragment -> + URI scheme authority (f path) query fragment + +IO.net.URI.Path.root : Path +IO.net.URI.Path.root = Path [] + +IO.net.URI.path.set : Path -> URI -> URI +IO.net.URI.path.set path1 = cases + URI scheme authority _ query fragment -> + URI scheme authority path1 query fragment + +test> IO.net.URI.Path.slash.tests = + use Path / + expectedSegments = + [ (root, []) + , (root / "foo", ["foo"]) + , (root / "foo" / "bar", ["foo", "bar"]) + , (root / "", [""]) + , (root / "foo" / "", ["foo", ""]) + , (root / "?" / "%", ["?", "%"]) + ] + checkSegments = cases + (path, expectedSegments) -> + expect (assertEquals (segments path) expectedSegments) + deprecated.run (Test.tests (List.map checkSegments expectedSegments)) + +IO.net.URI.Path.toText : Path -> Text +IO.net.URI.Path.toText path = + handle path |> Path.encode |> fromUtf8 + with cases + { x } -> x + { Exception.raise e -> _ } -> + bug ("Path contains invalid characters", path, e) + +IO.net.URI.Path.toUnescapedText : Path -> Text +IO.net.URI.Path.toUnescapedText path = + use Text ++ + foldDelimited (++) id "/" "/" "" (segments path) + +IO.net.URI.Path.toUnescapedText.doc : Doc +IO.net.URI.Path.toUnescapedText.doc = + use Path fromText toText + {{ + Convert a {type Path} to a {type Text} value, without percent-encoding the + path segments. Note that this is not suitable for use in a URI, as it may + contain characters that are not allowed. + + # Example + + ``` + catch do Path.toUnescapedText (fromText "/foo%20bar/baz%25quux") + ``` + + As opposed to {toText}, which percent-encodes the path segments: + + ``` + catch do toText (fromText "/foo%20bar/baz%25quux") + ``` + + # See also + + * {toText} - Convert a {type Path} to a {type Text} value, percent-encoding + the path segments. This is suitable for use in a URI. + }} + +IO.net.URI.pattern.alphaNum : Pattern Text +IO.net.URI.pattern.alphaNum = Pattern.or patterns.letter patterns.digit + +IO.net.URI.pattern.authority : + IPattern (And (And Capture Capture) Capture) Text +IO.net.URI.pattern.authority = + use IPattern ++ <|> capture + pattern.userInfo ++ pattern.host ++ pattern.port + <|> (capture (literal "") ++ capture (literal "") ++ capture (literal "")) + +IO.net.URI.pattern.dash : Pattern Text +IO.net.URI.pattern.dash = literal "-" + +IO.net.URI.pattern.decOctet : Pattern Text +IO.net.URI.pattern.decOctet = + use Pattern + join + use patterns digit + join [literal "25", charRange ?0 ?5] + + join [literal "2", charRange ?0 ?4, digit] + + join [literal "1", digit, digit] + + join [digit, digit] + + digit + +test> IO.net.URI.pattern.decOctet.tests = test.verify do + use Pattern capture run + ensureEqual (run (capture decOctet) "0") (Some (["0"], "")) + ensureEqual (run (capture decOctet) "1") (Some (["1"], "")) + ensureEqual (run (capture decOctet) "2") (Some (["2"], "")) + ensureEqual (run (capture decOctet) "3") (Some (["3"], "")) + ensureEqual (run (capture decOctet) "127") (Some (["127"], "")) + ensureEqual (run (capture decOctet) "255") (Some (["255"], "")) + ensureEqual (run (capture decOctet) "256") (Some (["25"], "6")) + +IO.net.URI.pattern.fragment : IPattern Capture Text +IO.net.URI.pattern.fragment = + use IPattern <|> capture + use Pattern or + capture + (Pattern.join + [literal "#", many (or pchar (or (literal "/") (literal "?")))]) + <|> capture (literal "") + +IO.net.URI.pattern.h16 : Pattern Text +IO.net.URI.pattern.h16 = Pattern.replicate 1 4 patterns.hexDigit + +IO.net.URI.pattern.heirPart : + IPattern (And (And (And Capture Capture) Capture) Capture) Text +IO.net.URI.pattern.heirPart = + use IPattern ++ +: <|> capture + use Pattern or + literal "//" +: (pattern.authority ++ capture pathAbEmpty) + <|> (capture (literal "") ++ capture (literal "") ++ capture (literal "") + ++ capture (or pathAbsolute (or pathRootless pathEmpty))) + +IO.net.URI.pattern.host : IPattern Capture Text +IO.net.URI.pattern.host = + use Pattern or + IPattern.capture (or ipLiteral (or ipv4Address regName)) + +IO.net.URI.pattern.ipLiteral : Pattern Text +IO.net.URI.pattern.ipLiteral = + Pattern.join [literal "[", Pattern.or ipv6Address ipvFuture, literal "]"] + +IO.net.URI.pattern.ipv4Address : Pattern Text +IO.net.URI.pattern.ipv4Address = + use Pattern join + join [decOctet, Pattern.replicate 3 3 (join [literal ".", decOctet])] + +test> IO.net.URI.pattern.ipv4Address.tests = + test.verify do + use Pattern capture run + ensureEqual + (run (capture ipv4Address) "127.0.0.1") (Some (["127.0.0.1"], "")) + ensureEqual + (run (capture ipv4Address) "10.10.10.10") (Some (["10.10.10.10"], "")) + ensureEqual (run (capture ipv4Address) "125.260.123.1") None + +IO.net.URI.pattern.ipv6Address : Pattern Text +IO.net.URI.pattern.ipv6Address = + use Pattern join + join [Pattern.replicate 6 6 (join [h16, literal ":"]), ls32] + +IO.net.URI.pattern.ipvFuture : Pattern Text +IO.net.URI.pattern.ipvFuture = + use Pattern or + Pattern.join + [ literal "v" + , some (or patterns.hexDigit period) + , literal "." + , some (or unreserved (or subDelims (literal ":"))) + ] + +IO.net.URI.pattern.ls32 : Pattern Text +IO.net.URI.pattern.ls32 = + Pattern.or (Pattern.join [h16, literal ":", h16]) ipv4Address + +IO.net.URI.pattern.path : Pattern Text +IO.net.URI.pattern.path = + use Pattern or + or + pathAbEmpty (or pathAbsolute (or pathNoscheme (or pathRootless pathEmpty))) + +IO.net.URI.pattern.pathAbEmpty : Pattern Text +IO.net.URI.pattern.pathAbEmpty = + many (Pattern.join [literal "/", pattern.segment]) + +IO.net.URI.pattern.pathAbsolute : Pattern Text +IO.net.URI.pattern.pathAbsolute = + use Pattern join + join + [ literal "/" + , Pattern.optional + (join [segmentNz, many (join [literal "/", pattern.segment])]) + ] + +IO.net.URI.pattern.pathEmpty : Pattern Text +IO.net.URI.pattern.pathEmpty = literal "" + +IO.net.URI.pattern.pathNoscheme : Pattern Text +IO.net.URI.pattern.pathNoscheme = + use Pattern join + join [segmentNzNc, many (join [literal "/", pattern.segment])] + +IO.net.URI.pattern.pathRootless : Pattern Text +IO.net.URI.pattern.pathRootless = + use Pattern join + join [segmentNz, many (join [literal "/", pattern.segment])] + +IO.net.URI.pattern.pchar : Pattern Text +IO.net.URI.pattern.pchar = + use Pattern or + or + unreserved + (or percentEncoded (or subDelims (or (literal ":") (literal "@")))) + +IO.net.URI.pattern.percentEncoded : Pattern Text +IO.net.URI.pattern.percentEncoded = + use patterns hexDigit + Pattern.join [literal "%", hexDigit, hexDigit] + +IO.net.URI.pattern.period : Pattern Text +IO.net.URI.pattern.period = literal "." + +IO.net.URI.pattern.port : IPattern Capture Text +IO.net.URI.pattern.port = + use IPattern +: <|> capture + literal ":" +: capture (some patterns.digit) <|> capture (literal "") + +IO.net.URI.pattern.query : IPattern Capture Text +IO.net.URI.pattern.query = + use IPattern +: <|> capture + use Pattern or + literal "?" +: capture (many (or pchar (or (literal "/") (literal "?")))) + <|> capture (literal "") + +IO.net.URI.pattern.regName : Pattern Text +IO.net.URI.pattern.regName = + use Pattern or + many (or unreserved (or percentEncoded subDelims)) + +IO.net.URI.pattern.scheme : IPattern Capture Text +IO.net.URI.pattern.scheme = + use IPattern :+ <|> capture + capture (some patterns.letter) :+ literal ":" <|> capture (literal "") + +IO.net.URI.pattern.segment : Pattern Text +IO.net.URI.pattern.segment = many pchar + +IO.net.URI.pattern.segmentNz : Pattern Text +IO.net.URI.pattern.segmentNz = some pchar + +IO.net.URI.pattern.segmentNzNc : Pattern Text +IO.net.URI.pattern.segmentNzNc = + use Pattern or + Pattern.join + [some (or unreserved (or percentEncoded (or subDelims (literal "@"))))] + +IO.net.URI.pattern.subDelims : Pattern Text +IO.net.URI.pattern.subDelims = + use Pattern or + or + (literal "!") + (or + (literal "$") + (or + (literal "&") + (or + (literal "'") + (or + (literal "(") + (or + (literal ")") + (or + (literal "*") + (or + (literal "+") + (or (literal ",") (or (literal ";") (literal "=")))))))))) + +IO.net.URI.pattern.tilde : Pattern Text +IO.net.URI.pattern.tilde = literal "~" + +IO.net.URI.pattern.underscore : Pattern Text +IO.net.URI.pattern.underscore = literal "_" + +IO.net.URI.pattern.unreserved : Pattern Text +IO.net.URI.pattern.unreserved = + use Pattern or + or alphaNum (or dash (or period (or underscore tilde))) + +IO.net.URI.pattern.uri : + IPattern + (And + (And + (And Capture (And (And (And Capture Capture) Capture) Capture)) Capture) + Capture) + Text +IO.net.URI.pattern.uri = + use IPattern ++ + pattern.scheme ++ heirPart ++ pattern.query ++ pattern.fragment + +IO.net.URI.pattern.userInfo : IPattern Capture Text +IO.net.URI.pattern.userInfo = + use IPattern :+ <|> capture + use Pattern or + capture + (some (or unreserved (or percentEncoded (or subDelims (literal ":"))))) + :+ literal "@" + <|> capture (literal "") + +IO.net.URI.port : URI -> Port +IO.net.URI.port = cases + URI _ (Some (Authority _ _ (Some port))) _ _ _ -> port + URI (Scheme "https") (Some (Authority _ _ None)) _ _ _ -> Port "443" + URI (Scheme "ftp") (Some (Authority _ _ None)) _ _ _ -> Port "21" + URI (Scheme "gopher") (Some (Authority _ _ None)) _ _ _ -> Port "701" + _ -> Port "80" + +IO.net.URI.query : URI -> RawQuery +IO.net.URI.query = cases URI _ _ _ query _ -> query + +(IO.net.URI.Query.&) : Query -> (Text, Text) -> Query +(IO.net.URI.Query.&) query = cases (key, value) -> addParam key value query + +IO.net.URI.Query.addParam : Text -> Text -> Query -> Query +IO.net.URI.Query.addParam key value = cases + Query query -> Query (Map.putWith (a b -> b List.++ a) key [value] query) + +IO.net.URI.Query.doc : Doc +IO.net.URI.Query.doc = + {{ + use Query & A URI query component such as `?version=1.0&redirect=true`, as + defined by + [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986/#section-3.4) + + The keys and values in a {type Query} data structure should be in their raw + form; not URI-encoded. The URI encoding will happen when a URI is written + into an HTTP request. For example, a value of `#unison` will be encoded as + `%23unison`. + + Example: + + ``` + Query.empty & ("version", "1.0") & ("hashtag", "#unison") |> toRawQuery + |> RawQuery.encode + ``` + }} + +IO.net.URI.Query.empty : Query +IO.net.URI.Query.empty = Query Map.empty + +IO.net.URI.Query.encode.char : Text -> Text +IO.net.URI.Query.encode.char t = + use Text ++ + go : Text -> Char -> Text + go acc c = acc ++ charX c + List.foldLeft go "" (toCharList t) + +IO.net.URI.Query.encode.charX : Char -> Text +IO.net.URI.Query.encode.charX = cases + c| isAlphaNum c || List.contains c [?-, ?., ?_, ?~, ?/, ??] -> + fromCharList [c] + ?\s -> "+" + c -> special c + +IO.net.URI.Query.encode.impl : Boolean -> Query -> Text +IO.net.URI.Query.encode.impl prependQuestionMark query = + use Query.encode char + use Text ++ + qs = + (Query params) = query + encodeKv = cases (k, v) -> char k ++ "=" ++ char v + flattenKvs = cases (k, vs) -> List.map (v -> (k, v)) vs + kvs = List.flatMapRight flattenKvs (Map.toList params) + foldDelimited (++) encodeKv "" "&" Text.empty kvs + start = if prependQuestionMark then "?" else "" + start ++ qs + +test> IO.net.URI.Query.encode.test = + test.verify do + ensureEqual "? " (RawQuery.encode (RawQuery " ")) + ensureEqual + "baz=%25&foo=bar" + (Query.toText (Query.empty & ("foo", "bar") & ("baz", "%"))) + +test> IO.net.URI.Query.encode.test.double.simple = test.verify do + q = Query (Map.fromList [("key1", ["value1"]), ("key2", ["value2"])]) + b = Query.toText q + ensureEqual "key1=value1&key2=value2" b + +test> IO.net.URI.Query.encode.test.empty = + q = Query.empty + b = Query.toText q + check (assertEquals b "") + +test> IO.net.URI.Query.encode.test.single.simple = + q = Query.singleton "key" "value" + b = Query.toText q + check (assertEquals b "key=value") + +IO.net.URI.Query.example.exampleQuery : Query +IO.net.URI.Query.example.exampleQuery = + Query.empty & ("q", "unison+programming+language") & ("lang", "en") + +IO.net.URI.Query.fromRawQuery : RawQuery -> Optional Query +IO.net.URI.Query.fromRawQuery = cases + RawQuery text -> Abort.toOptional (do parseQuery text) () + +IO.net.URI.Query.getParam : Text -> Query -> [Text] +IO.net.URI.Query.getParam key = cases + Query query -> Map.get key query |> Optional.getOrElse [] + +IO.net.URI.Query.singleton : Text -> Text -> Query +IO.net.URI.Query.singleton k v = Query (Map.singleton k [v]) + +test> IO.net.URI.Query.tests.addParamOrder = + test.verify do + ensureEqual + "a=b1&a=b2" (Query.toText (Query.empty & ("a", "b1") & ("a", "b2"))) + +IO.net.URI.Query.toMap : Query -> Map Text [Text] +IO.net.URI.Query.toMap = cases Query map -> map + +IO.net.URI.Query.toMap.doc : Doc +IO.net.URI.Query.toMap.doc = + use Optional toAbort + {{ + Gets the multimap of query parameter names to their values in the given + {type Query}. + + # Example + + ``` + toOptional! do + x = + toAbort + (parseOptional "http://example.com/foo?param1=p1value¶m2=p2value") + q = toAbort (fromRawQuery (URI.query x)) + Map.toList (Query.toMap q) + ``` + }} + +IO.net.URI.Query.toText : Query -> Text +IO.net.URI.Query.toText query = + handle encode.impl false query + with cases + { x } -> x + { Exception.raise e -> _ } -> + bug ("Query contains invalid characters", query, e) + +IO.net.URI.Query.union : Query -> Query -> Query +IO.net.URI.Query.union = cases + Query q1, Query q2 -> Query (Map.unionWith (List.++) q1 q2) + +IO.net.URI.RawQuery.doc : Doc +IO.net.URI.RawQuery.doc = + {{ + A URI query component such as `?version=1.0&redirect=true`, as defined by + [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986/#section-3.4) + }} + +IO.net.URI.RawQuery.empty : RawQuery +IO.net.URI.RawQuery.empty = RawQuery "" + +IO.net.URI.RawQuery.empty.doc : Doc +IO.net.URI.RawQuery.empty.doc = + {{ + An empty {type RawQuery}; one with no keys or values. When encoded in a URI, + this will be rendered as a blank string with no `?` or `&`. + }} + +IO.net.URI.RawQuery.encode : RawQuery -> Text +IO.net.URI.RawQuery.encode = cases + RawQuery "" -> "" + RawQuery query -> "?" Text.++ query + +test> IO.net.URI.RawQuery.encode.tests = + test.verify do + ensureEqual + "?baz=%25&foo=bar" + (RawQuery.encode + (fromQuery (Query.empty & ("foo", "bar") & ("baz", "%")))) + +test> IO.net.URI.RawQuery.encode.tests.double.simple = + test.verify do + q = + Query (Map.fromList [("key1", ["value1"]), ("key2", ["value2"])]) + |> fromQuery + b = RawQuery.encode q + ensureEqual "?key1=value1&key2=value2" b + +IO.net.URI.RawQuery.encodePairs : [Char] -> Text +IO.net.URI.RawQuery.encodePairs chars = + use List ++ + go acc = cases + a +: (b +: rest) -> go (acc ++ [?&, a, b]) rest + _ -> acc + go [] chars |> fromCharList + +IO.net.URI.RawQuery.encodeRaw.char : Char -> Text +IO.net.URI.RawQuery.encodeRaw.char = cases + c + | c === ?: || c === ?# || c === ?[ || c === ?] -> special c + | otherwise -> + if Char.toNat c Nat.> 127 then + fromCharList [c] |> Text.toUtf8 |> Bytes.toBase16 |> fromUtf8.impl + |> Either.toOptional + |> getOrBug "bug in encodeRaw.char" + |> toCharList + |> encodePairs + else fromCharList [c] + +IO.net.URI.RawQuery.encodeRaw.text : Text -> Text +IO.net.URI.RawQuery.encodeRaw.text t = + use Text ++ + go : Text -> Char -> Text + go acc c = acc ++ encodeRaw.char c + List.foldLeft go "" (toCharList t) + +IO.net.URI.RawQuery.fromQuery : Query -> RawQuery +IO.net.URI.RawQuery.fromQuery = RawQuery << Query.toText + +IO.net.URI.RawQuery.modify : (RawQuery ->{g} RawQuery) -> URI ->{g} URI +IO.net.URI.RawQuery.modify f = cases + URI scheme authority path query fragment -> + URI scheme authority path (f query) fragment + +IO.net.URI.scheme : URI -> Scheme +IO.net.URI.scheme = cases URI scheme _ _ _ _ -> scheme + +IO.net.URI.Scheme.defaultPort : Scheme ->{Throw Text} Nat +IO.net.URI.Scheme.defaultPort = cases + Scheme "http" -> 80 + Scheme "https" -> 443 + Scheme x -> throw ("Don't know what port to use for scheme: " Text.++ x) + +IO.net.URI.Scheme.empty : Scheme +IO.net.URI.Scheme.empty = Scheme "" + +IO.net.URI.Scheme.http : Scheme +IO.net.URI.Scheme.http = Scheme "http" + +IO.net.URI.Scheme.http.doc : Doc +IO.net.URI.Scheme.http.doc = + {{ The Hypertext Transfer Protocol (HTTP) protocol. }} + +IO.net.URI.Scheme.https : Scheme +IO.net.URI.Scheme.https = Scheme "https" + +IO.net.URI.Scheme.https.doc : Doc +IO.net.URI.Scheme.https.doc = + {{ + The Hypertext Transfer Protocol Secure (HTTPS) protocol, which extends + {Scheme.http} with encryption via Transport Layer Security. + }} + +IO.net.URI.scheme.modify : (Scheme ->{g} Scheme) -> URI ->{g} URI +IO.net.URI.scheme.modify f = cases + URI scheme authority path query fragment -> + URI (f scheme) authority path query fragment + +IO.net.URI.scheme.set : Scheme -> URI -> URI +IO.net.URI.scheme.set scheme1 = cases + URI _ authority path query fragment -> + URI scheme1 authority path query fragment + +IO.net.URI.Scheme.toText : Scheme -> Text +IO.net.URI.Scheme.toText = cases Scheme s -> s + +test> IO.net.URI.Scheme.toText.tests = + test.verify do + (expected, scheme) = + each + [ ("http", Scheme.http) + , ("https", Scheme.https) + , ("file", Scheme "file") + , ("ssh", Scheme "ssh") + ] + actual = Scheme.toText scheme + ensureEqual expected actual + +test> IO.net.URI.tests.testEmptyFragment = + test.verify do + use Map fromList + use Path / + use Scheme http + ensureEqual + (parseOptional "http://example.com/hi/there?foo=bar#") + (Some + (URI + http + (Some (Authority None (HostName "example.com") None)) + (root / "hi" / "there") + (fromQuery (Query (fromList [("foo", ["bar"])]))) + (Fragment "#"))) + ensureEqual + (parseOptional "#") + (Some (URI Scheme.empty None root (RawQuery "") (Fragment "#"))) + ensureEqual + (parseOptional "http://example.com/hi/there?foo=bar") + (Some + (URI + http + (Some (Authority None (HostName "example.com") None)) + (root / "hi" / "there") + (fromQuery (Query (fromList [("foo", ["bar"])]))) + (Fragment ""))) + +test> IO.net.URI.tests.testPathDecode = test.verify do + use test raiseFailure + roundTrip path = match Pattern.run (Pattern.capture pattern.path) path with + None -> raiseFailure "coludn't parse path" path + Some ([p], r) -> + ensureEqual (Text.size r) 0 + ensureEqual path p + Some _ -> raiseFailure "path parse returned multiple results" path + roundTrip "/" + roundTrip "/hi" + roundTrip "/hi/" + roundTrip "/a/b/c/d/e/f/g" + roundTrip "/a=/b" + +test> IO.net.URI.tests.testPathToText = test.verify do + use Path toText + ensureEqual "/" (root |> toText) + ensureEqual "/" (Path [] |> toText) + ensureEqual "/" (Path [""] |> toText) + ensureEqual "/hi" (Path ["hi"] |> toText) + ensureEqual "/hi/" (Path ["hi", ""] |> toText) + ensureEqual "/hi/there" (Path ["hi", "there"] |> toText) + +test> IO.net.URI.tests.testQueryParams = + test.verify do + use test raiseFailure + go = + do + use Map fromList toList + ensureParamsEqual : Query -> Query ->{Exception} () + ensureParamsEqual = cases + Query m1, Query m2 -> + m1' = toList m1 |> (sortBy cases (k, _) -> k) + m2' = toList m2 |> (sortBy cases (k, _) -> k) + if m1' === m2' then () + else raiseFailure "elements not equals" (m1, m2) + ensureParamsEqual + (Query (fromList [("baz", ["qux"]), ("foo", ["bar"])])) + (parseQuery "foo=bar&baz=qux") + ensureParamsEqual + (Query (fromList [("baz", ["qux"]), ("foo", ["bar"])])) + (parseQuery "foo=bar&baz=qux&") + ensureParamsEqual + (Query (fromList [("foo", ["bar", "bar"]), ("baz", ["qux"])])) + (parseQuery "foo=bar&baz=qux&foo=bar") + ensureParamsEqual + (Query + (fromList [("baz", ["qux1", "qux2"]), ("foo", ["bar", "bar"])])) + (parseQuery "foo=bar&baz=qux1&foo=bar&baz=qux2") + ensureParamsEqual + (Query (fromList [("baz", ["qux", "qux"]), ("foo", ["bar", "bar"])])) + (parseQuery "foo=bar&baz=qux&foo=bar&baz=qux") + handle go() + with cases + { abort -> _ } -> raiseFailure "failed to parse" () + { a } -> () + +test> IO.net.URI.tests.testUriRoundTrip = + test.verify do + use Path / + use Scheme http + use URI parse + roundTrip uri = + decoded = parse uri + ensureEqual uri (URI.toText decoded) + ensureEqual + (URI + http + (Some (Authority None (HostName "example.com") None)) + root + RawQuery.empty + Fragment.empty) + (parse "http://example.com") + roundTrip "http://example.com/" + roundTrip "http://example.com/hi" + roundTrip "http://example.com/hi/" + roundTrip "http://example.com/hi/there" + roundTrip "http://example.com/hi/there/" + roundTrip "http://example.com/hi/there?foo=bar" + ensureEqual + (URI + http + (Some (Authority None (HostName "example.com") None)) + (root / "hi" / "there") + (RawQuery "foo=bar&baz=qux") + Fragment.empty) + (parse "http://example.com/hi/there?foo=bar&baz=qux") + ensureEqual + (URI + http + (Some (Authority None (HostName "example.com") None)) + (root / "hi" / "there") + (RawQuery "foo=bar&") + (Fragment "#fragment")) + (parse "http://example.com/hi/there?foo=bar&#fragment") + roundTrip "http://example.com/hi/there?foo=bar#fragment" + roundTrip "http://example.com/hi/there?foo=bar#fragment/with/path" + roundTrip + "http://example.com/hi/there?foo=bar#fragment/with/path?and=query" + roundTrip + "http://example.com/hi/there?foo=bar#fragment/with/path?and=query&and=more" + +IO.net.URI.toText : URI -> Text +IO.net.URI.toText = cases + URI scheme authorityMaybe path query fragment -> + use Text ++ + schemeText = match Scheme.toText scheme with + "" -> "" + s -> s ++ "://" + schemeText ++ Optional.fold (do "") Authority.toText authorityMaybe + ++ Path.toText path + ++ RawQuery.encode query + ++ Fragment.toText fragment + +test> IO.net.URI.toText.tests.complicatedUri = + test.verify do + use Path / + authority = + Authority + (Some (UserInfo "user:password")) + (HostName "www.unison-lang.org") + (Some (Port "6667")) + path = root / "docs" / "search" + query = Query.empty & ("term", "Boolean.&&") & ("version", "1.0") + uri = + URI + (Scheme "gopher") + (Some authority) + path + (fromQuery query) + Fragment.empty + result = URI.toText uri + expected = + "gopher://user:password@www.unison-lang.org:6667/docs/search?term=Boolean.%26%26&version=1.0" + ensureEqual expected result + +test> IO.net.URI.toText.tests.unisonDocs = test.verify do + expected = "https://www.unison-lang.org/learn/" + result = URI.toText unisonDocs.unisonDocs + ensureEqual expected result + +IO.net.URI.toText.tests.unisonDocs.unisonDocs : URI +IO.net.URI.toText.tests.unisonDocs.unisonDocs = + use Path / + URI + Scheme.https + (Some (Authority None (HostName "www.unison-lang.org") None)) + (root / "learn" / "") + RawQuery.empty + Fragment.empty + +IO.net.URI.toUnescapedText : URI -> Text +IO.net.URI.toUnescapedText = cases + URI scheme authorityMaybe path query fragment -> + use Text ++ + schemeText = match Scheme.toText scheme with + "" -> "" + s -> s ++ "://" + schemeText ++ Optional.fold (do "") Authority.toText authorityMaybe + ++ Path.toUnescapedText path + ++ RawQuery.encode query + ++ Fragment.toText fragment + +IO.net.URI.toUnescapedText.doc : Doc +IO.net.URI.toUnescapedText.doc = + use URI parse toText + {{ + Convert a {type URI} to a {type Text} value, without percent-encoding the + path segments. Note that the result is not a valid URI, as it may contain + characters that are not allowed. + + # Example + + ``` + catch do + URI.toUnescapedText (parse "https://example.com/foo%20bar/baz%25quux") + ``` + + As opposed to {toText}, which percent-encodes the path segments: + + ``` + catch do toText (parse "https://example.com/foo%20bar/baz%25quux") + ``` + }} + +IO.net.URI.withHost : Text -> URI -> URI +IO.net.URI.withHost host = cases + URI scheme authority path query fragment -> + URI scheme (Some (fromHost (HostName host))) path query fragment + +IO.net.URI.withQuery : RawQuery -> URI -> URI +IO.net.URI.withQuery query = cases + URI scheme authority path _ fragment -> + URI scheme authority path query fragment + +-- builtin IO.Process.call : Text -> [Text] ->{IO} Nat + +IO.Process.call.doc : Doc +IO.Process.call.doc = + use Nat == + {{ + Starts a process using the given command and arguments, waits for it to + terminate, and returns its exit code. + + # Example + + @typecheck ``` + do + exitCode = call "echo" ["hello", "world"] + if exitCode == 0 then printLine "success" else printLine "failure" + ``` + + # See also + + * {start} for a more flexible way to start a process. + }} + +IO.Process.doc : Doc +IO.Process.doc = + use Handle close + use Nat == + use Process kill wait + {{ + A {type Process} is a handle to a running operating system process. + + # Starting a process + + The simple way to start a process is to use {call}: + + @typecheck ``` + do + exitCode = call "echo" ["hello", "world"] + if exitCode == 0 then printLine "success" else printLine "failure" + ``` + + This will call the `echo` command with the arguments `hello` and `world`, + and then print `success` if the command exits with a zero exit code, or + `failure` otherwise. + + For a more flexible way to start a process, use {start}: + + @typecheck ``` + do + (stdin, stdout, stderr, p) = start "echo" ["hello", "world"] + wait p + ``` + + This starts a process running the `echo` command, passing it the arguments + `hello` and `world`. The `echo` command will print those arguments to + standard output, which is captured by the `stdout` handle. The `stderr` and + `stdin` handles are not used in this example. + + The {start} function returns {type Handle} values for the `stdin`, + `stdout`, and `stderr` file handles, and a {type Process} handle for the + process itself. The {type Handle} values can be used to read from and write + to the process. The {type Process} value can be used to wait for or + terminate the process (see below). + + Use {close} to close a handle. If you don't close these handles, they will + be closed automatically if/when the process terminates. + + # Interacting with a process + + You can use the file handles returned by {start} to write to the process's + standard input and read from its standard output and standard error: + + @typecheck ``` + do + (stdin, stdout, stderr, p) = start "cat" [] + putText stdin "hello world" + close stdin + (getLine stdout, wait p) + ``` + + # Terminating a process + + {kill} attempts to terminate a process. The implementation of this function + is platform-dependent, and may not be supported on all platforms. On a UNIX + system this will send a `SIGTERM` signal to the process. On a Windows + system it calls `TerminateProcess` or `TerminateJobObject` depending on the + process type. + + @typecheck ``` + do + (stdin, stdout, stderr, p) = start "sleep" ["10"] + kill p + wait p + ``` + + You can check whether a {type Process} has terminated using + {Process.exitCode}. + + # Waiting for a process + + {wait} waits for a process to terminate, and returns its exit code. If the + process has already terminated, {wait} returns immediately. + + @typecheck ``` + do + (stdin, stdout, stderr, p) = start "sleep" ["10"] + wait p + ``` + + # Getting a process's exit code + + {Process.exitCode} returns a process's exit code, if it has already + terminated. If the process has not yet terminated, it returns {None}. + + @typecheck ``` + do + (stdin, stdout, stderr, p) = start "sleep" ["10"] + Process.exitCode p + ``` + }} + +-- builtin IO.Process.exitCode : IO.Process ->{IO} Optional Nat + +IO.Process.exitCode.doc : Doc +IO.Process.exitCode.doc = + {{ + Returns the exit code of a process, if it has terminated. Otherwise, returns + {None}. Does not wait for the process to terminate. + + # Example + + @typecheck ``` + do + (stdin, stdout, stderr, p) = start "echo" ["hello", "world"] + exitCode p + ``` + + # See also + + * {Process.wait} for a blocking version of this function. + * {start} to start a process. + * {Process.kill} to terminate a running process. + * {type Process} for more information about working with processes. + }} + +-- builtin IO.Process.kill : IO.Process ->{IO} () + +IO.Process.kill.doc : Doc +IO.Process.kill.doc = + use Process wait + {{ + Attempts to terminate a process. The implementation of this function is + platform-dependent, and may not be supported on all platforms. On a UNIX + system this will send a `SIGTERM` signal to the process. On a Windows system + it calls `TerminateProcess` or `TerminateJobObject` depending on the process + type. + + # Example + + @typecheck ``` + do + (stdin, stdout, stderr, p) = start "sleep" ["10"] + Process.kill p + wait p + ``` + + # See also + + * {start} to start a process. + * {wait} to wait for a process to terminate. + * {exitCode} to check whether a process has terminated. + * {type Process} for more information about working with processes. + }} + +-- builtin IO.Process.start : +-- Text -> [Text] ->{IO} (IO.Handle, IO.Handle, IO.Handle, IO.Process) + +IO.Process.start.doc : Doc +IO.Process.start.doc = + use Process kill wait + {{ + Starts a process using the given command and arguments, and returns a tuple + containing the process's standard input, standard output, standard error, and + process handles. + + # Example + + @typecheck ``` + do + (stdin, stdout, stderr, p) = start "echo" ["hello", "world"] + wait p + ``` + + This starts a process running the `echo` command, passing it the arguments + `hello` and `world`. The `echo` command will print those arguments to + standard output, which is captured by the `stdout` handle. The `stderr` and + `stdin` handles are not used in this example. + + The {start} function returns {type Handle} values for the `stdin`, + `stdout`, and `stderr` file handles, and a {type Process} handle for the + process itself. Use the {type Handle} values to read from and write to the + process. Use the {type Process} value to wait for or terminate the process + with {wait} and {kill}, respectively. + + Use {Handle.close} to close a handle. If you don't close these handles, + they will be closed automatically if/when the process terminates. + + # See also + + * {call} for a simpler way to start a process, if you don't need to + interact with it. + * {wait} to wait for a process to terminate. + * {kill} to terminate a process. + * {exitCode} to check whether a process has terminated. + * {type Handle} for more information about file handles. + * {type Process} for more information about process handles. + }} + +-- builtin IO.Process.wait : IO.Process ->{IO} Nat + +IO.Process.wait.doc : Doc +IO.Process.wait.doc = + {{ + Waits for a process to terminate, and returns its exit code. + + # Example + + @typecheck ``` + do + (stdin, stdout, stderr, p) = start "echo" ["hello", "world"] + Process.wait p + ``` + + # See also + + * {exitCode} for a non-blocking version of this function. + * {start} to start a process. + * {Process.kill} to terminate a running process. + * {type Process} for more information about working with processes. + }} + +-- builtin IO.randomBytes : Nat ->{IO} Bytes + +IO.randomBytes.doc : Doc +IO.randomBytes.doc = + {{ + `` randomBytes n `` generates a random {type Bytes} value of length `n` using + a cryptographically secure source of randomness. + }} + +IO.randomNat : '{IO} Nat +IO.randomNat = do match decodeNat64be (randomBytes 8) with + Some (n, _) -> n + None -> bug "requested 8 random bytes, but did not receive enough" + +IO.randomNat.doc : Doc +IO.randomNat.doc = + {{ + Returns a uniformly-distributed {type Nat} derived from {randomBytes}. + + This can be useful for generating a random seed to be used for deterministic + {type Random} generation, such as by {splitmix}. + + # Examples + + @typecheck ``` + do + seed = randomNat() + splitmix seed do Random.natIn 0 100 + ``` + }} + +-- builtin IO.Raw.array : Nat ->{IO} mutable.Array.Raw {IO} a + +IO.Raw.array.doc : Doc +IO.Raw.array.doc = + {{ + Creates a new array of the specified size. Contents are unspecified. + + Currently no attempt is made to ensure that it is sensible to access the + initial values of an array created this way. Every position in the array + should be overwritten (with {Raw.write}, {mutable.Array.Raw.copyTo!} or + similar) before any are read. + }} + +-- builtin IO.Raw.arrayOf : a -> Nat ->{IO} mutable.Array.Raw {IO} a + +IO.Raw.arrayOf.doc : Doc +IO.Raw.arrayOf.doc = {{ Creates a new array filled with the specified value. }} + +-- builtin IO.Raw.byteArray : Nat ->{IO} mutable.ByteArray.Raw {IO} + +IO.Raw.byteArray.doc : Doc +IO.Raw.byteArray.doc = + {{ + Uses the {type IO} ability to create a new low-level byte array of type + {type mutable.ByteArray.Raw} with the given number of bytes. The contents of + the array are undefined. + }} + +-- builtin IO.Raw.byteArrayOf : Nat -> Nat ->{IO} mutable.ByteArray.Raw {IO} + +IO.Raw.byteArrayOf.doc : Doc +IO.Raw.byteArrayOf.doc = + {{ + `` IO.Raw.byteArrayOf b len `` creates a {type mutable.ByteArray} of length + `len`, in {type IO}, filling it with the byte value of `b` (which must be in + the range 0 to 255). + }} + +-- builtin IO.ref : a ->{IO} mutable.Ref {IO} a + +IO.ref.doc : Doc +IO.ref.doc = + use Ref read write + {{ + Constructs a new mutable reference of type {type Ref} with the given initial + value, using {type IO}. The reference can be read and written to using {read} + and {write}: + + @signatures{read, write} + + See also {Scope.ref} for a version of this function that works in the + {type Scope} ability and doesn't need access to {type IO}. + }} + +IO.thawArray : data.Array a ->{IO} mutable.Array {IO} a +IO.thawArray = cases + Arr off len src -> + dst = IO.Raw.array len + handle data.Array.Raw.copyTo! dst 0 src off len with impossible + MArr 0 len dst + +IO.thawArray.doc : Doc +IO.thawArray.doc = + {{ + Creates a new {type mutable.Array} initialized to the contents of the given + array. + }} + +IO.thawByteArray : data.ByteArray ->{IO} mutable.ByteArray {IO} +IO.thawByteArray = cases + BArr off len src -> + dst = IO.Raw.byteArray len + handle data.ByteArray.Raw.copyTo! dst 0 src off len with impossible + MBArr 0 len dst + +IO.thawByteArray.doc : Doc +IO.thawByteArray.doc = + {{ + Creates a new {type mutable.ByteArray} initialized to the contents of the + given array. + }} + +IO.tryEval : '{IO, Exception} a ->{IO, Exception} a +IO.tryEval = Either.toException << catchAll + +IO.tryEval.doc : Doc +IO.tryEval.doc = + {{ + Run a delayed computation, translating runtime failures (such as calls to + {bug} or pattern match failures) into the {type Exception} ability via + {Exception.raise}. + + See also {catchAll}. + }} + +-- builtin IO.tryEval.impl : '{IO} a ->{IO, Exception} a + +(IPattern.++) : IPattern n a -> IPattern m a -> IPattern (And n m) a +(IPattern.++) = cases + IPattern p1, IPattern p2 -> IPattern (Pattern.join [p1, p2]) + +(IPattern.+:) : Pattern a -> IPattern n a -> IPattern n a +(IPattern.+:) = cases p1, IPattern p2 -> IPattern (Pattern.join [p1, p2]) + +(IPattern.:+) : IPattern n a -> Pattern a -> IPattern n a +(IPattern.:+) = cases IPattern p1, p2 -> IPattern (Pattern.join [p1, p2]) + +(IPattern.<|>) : IPattern n a -> IPattern n a -> IPattern n a +(IPattern.<|>) = cases IPattern p1, IPattern p2 -> IPattern (Pattern.or p1 p2) + +IPattern.And.doc : Doc +IPattern.And.doc = + use IPattern ++ + {{ + {type And} is a Phantom type resulting in calling {++} which records how many + times the pattern captures on the `l` and `r` sides of the `++`. + }} + +IPattern.capture : Pattern a -> IPattern Capture a +IPattern.capture p = IPattern (Pattern.capture p) + +IPattern.capture.doc : Doc +IPattern.capture.doc = + {{ + The {IPattern.capture} function lets you capture a value and records in the + return type that the resulting Pattern captures one value. It is meant as a + replacement for {Pattern.capture}. + }} + +IPattern.Capture.doc : Doc +IPattern.Capture.doc = + {{ + {type Capture} is a Phantom type which denotes a call to {IPattern.capture} + }} + +IPattern.colonplusdoc : Doc +IPattern.colonplusdoc = + use IPattern :+ + {{ + The {:+} operator lets you add a {type Pattern} to the right of an + {IPattern}, and records in the return type that the resulting Pattern + captures the same number of values as the original {type IPattern}. + }} + +IPattern.doc : Doc +IPattern.doc = + use Pattern or + {{ + # Overview + + {type IPattern} is a typesafe wrapper around {type Pattern} which lets you + ensure that your Pattern always captures the same number of values by + tracking what things have been captured. + + # Motivation + + {type Pattern} is a great way to parse text quickly, but it's easy to make + mistakes when using {or} if the two alternatives are complex, because in + this case the type system can't help you ensure that the two alternatives + capture the same number of values. + + For example, consider the following code: + + ``` + use Pattern capture run + pattern = + or + (capture (literal "a")) + (Pattern.join [capture (literal "b"), capture (literal "c")]) + (run pattern "a", run pattern "bc") + ``` + + Here we have a Pattern that when run sometimes captures one value and + sometimes captures 2 values. In many cases we might want to ensure that we + always capture the same number of values so that we can rely on knowing + positionally which capture is which in the array of captures returned by + {Pattern.run}. + + The {type IPattern} functions use two phantom types to track the number of + captures, {type And} and {type Capture}. The number of times you see + {type Capture} in the type signature of an {IPattern} is the number of + values it captures. So `IPattern Capture Text` captures a single value of + type {type Text}, and `IPattern (And Capture Capture) Text` captures two. + `IPattern (And Capture (And Capture Capture)) Text` captures three, and so + on. + + # Functions + + * {{ IPattern.capture.doc }} + * {{ IPattern.run.doc }} + * {{ plusplusdoc }} + * {{ pluscolondoc }} + * {{ colonplusdoc }} + * {{ ordoc }} + + # Example + + Here is a function which parses address which are a hostname followed by an + optional port number. So valid strings are "localhost" and "localhost:8080" + but not "localhost:8080:8080". + + The three helper functions `port`, `hostname` and `address` are all + `IPattern`s which capture 1, 1 and 2 values respectively. + + ``` + parseAddress : Text -> Optional (HostName, Optional Port) + parseAddress txt = + use IPattern ++ +: <|> capture + use Nat > + port : IPattern Capture Text + port = + literal ":" +: capture (some patterns.digit) <|> capture (literal "") + hostname : IPattern Capture Text + hostname = capture (some (notCharIn [?:])) + address : IPattern (And Capture Capture) Text + address = hostname ++ port + match IPattern.run address txt with + None -> None + Some (_, x) | Text.size x > 0 -> None + Some ([host, ""], _) -> Some (HostName host, None) + Some ([host, port], _) -> Some (HostName host, Some (Port port)) + Some _ -> bug "bug in parseAddress" + parseAddress "localhost:8080" + ``` + }} + +IPattern.ordoc : Doc +IPattern.ordoc = + use IPattern <|> + {{ + The {<|>} is meant to replace {Pattern.or}, but ensures that the two + alternative patterns capture the same number of values. + }} + +IPattern.pluscolondoc : Doc +IPattern.pluscolondoc = + use IPattern +: + {{ + The {+:} operator lets you add a {type Pattern} to the left of an {IPattern}, + and records in the return type that the resulting Pattern captures the same + number of values as the original {type IPattern}. + }} + +IPattern.plusplusdoc : Doc +IPattern.plusplusdoc = + use IPattern ++ + {{ + The {++} operator lets you combine two {IPattern}s, and records in the return + type as `And n m` that the resulting Pattern captures `n` values from the + left and `m` values from the right. In order for this to be successful, You + should avoid calling {Pattern.join} directly,. + }} + +IPattern.run : IPattern n a -> a -> Optional ([a], a) +IPattern.run = cases IPattern p, a -> Pattern.run p a + +IPattern.run.doc : Doc +IPattern.run.doc = {{ Pattern.run is just a call-through to {Pattern.run}. }} + +LICENSE : License +LICENSE = License [unisoncomputing] [Year 2023] mit + +LICENSE.doc : Doc +LICENSE.doc = License.toDoc LICENSE + +LocalDate.ordering : LocalDate -> LocalDate -> Ordering +LocalDate.ordering x y = match Universal.ordering (year x) (year y) with + Equal -> + match Universal.ordering (month x) (month y) with + Equal -> Universal.ordering (LocalDate.day x) (LocalDate.day y) + o -> o + o -> o + +LocalDate.ordering.doc : Doc +LocalDate.ordering.doc = + use LocalDate ordering + {{ + `` ordering x y `` returns the ordering of the two dates `x` and `y`. The + ordering is based on the year, month, and day of the two dates. If the years + are equal, the months are compared, and if the months are equal, the days are + compared. + + Returns {Less} if `x` is before `y`, {Equal} if `x` is the same as `y`, and + {Greater} if `x` is after `y`. + + # Examples + + ``` + ordering (LocalDate +2022 1 1) (LocalDate +2022 1 2) + ``` + + ``` + ordering (LocalDate +2022 1 1) (LocalDate +2022 1 1) + ``` + + ``` + ordering (LocalDate +2022 1 2) (LocalDate +2022 1 1) + ``` + }} + +LocalDateTime.ordering : LocalDateTime -> LocalDateTime -> Ordering +LocalDateTime.ordering x y = + match LocalDate.ordering (LocalDateTime.date x) (LocalDateTime.date y) with + Equal -> LocalTime.ordering (LocalDateTime.time x) (LocalDateTime.time y) + o -> o + +LocalDateTime.ordering.doc : Doc +LocalDateTime.ordering.doc = + use LocalDateTime ordering + {{ + `` ordering x y `` returns the ordering of the two date-time values `x` and + `y`. The ordering is based on the date and time of the two values. If the + dates are equal, the times are compared. + + Returns {Less} if `x` is before `y`, {Equal} if `x` is the same as `y`, and + {Greater} if `x` is after `y`. + + # Examples + + ``` + ordering + (LocalDateTime (LocalDate +2022 1 1) (LocalTime 12 0 0 0)) + (LocalDateTime (LocalDate +2022 1 1) (LocalTime 12 0 1 0)) + ``` + + ``` + ordering + (LocalDateTime (LocalDate +2022 1 1) (LocalTime 12 0 0 0)) + (LocalDateTime (LocalDate +2022 1 1) (LocalTime 12 0 0 0)) + ``` + + ``` + ordering + (LocalDateTime (LocalDate +2022 1 1) (LocalTime 12 0 1 0)) + (LocalDateTime (LocalDate +2022 1 1) (LocalTime 12 0 0 0)) + ``` + }} + +LocalTime.ordering : LocalTime -> LocalTime -> Ordering +LocalTime.ordering x y = + match Universal.ordering (LocalTime.hour x) (LocalTime.hour y) with + Equal -> + match Universal.ordering (LocalTime.minute x) (LocalTime.minute y) with + Equal -> + match Universal.ordering (LocalTime.second x) (LocalTime.second y) with + Equal -> + Universal.ordering + (LocalTime.nanosecond x) (LocalTime.nanosecond y) + o -> o + o -> o + o -> o + +LocalTime.ordering.doc : Doc +LocalTime.ordering.doc = + use LocalTime ordering + {{ + `` ordering x y `` returns the ordering of the two time values `x` and `y`. + The ordering is based on the hour, minute, second, and nanosecond of the two + values. If the hours are equal, the minutes are compared, and so on. + + Returns {Less} if `x` is before `y`, {Equal} if `x` is the same as `y`, and + {Greater} if `x` is after `y`. + + # Examples + + ``` + ordering (LocalTime 12 0 0 0) (LocalTime 12 0 1 0) + ``` + + ``` + ordering (LocalTime 12 0 0 0) (LocalTime 12 0 0 0) + ``` + + ``` + ordering (LocalTime 12 0 1 0) (LocalTime 12 0 0 0) + ``` + }} + +math.ArithmeticException.dividedByZero : '{Exception} r +math.ArithmeticException.dividedByZero = + do + Exception.raise + (Failure + (typeLink ArithmeticException) "Division by zero" (Any DividedByZero)) + +math.ArithmeticException.dividedByZero.doc : Doc +math.ArithmeticException.dividedByZero.doc = + {{ + Raises a {type ArithmeticException} with a {DividedByZero} error. + + # Example + + ``` + catch dividedByZero + ``` + }} + +math.ArithmeticException.doc : Doc +math.ArithmeticException.doc = + {{ + A marker type associated with {type Failure}s thrown by arithmetic functions. + }} + +math.ArithmeticException.negativeInfinity : '{Exception} r +math.ArithmeticException.negativeInfinity = + do + Exception.raise + (Failure + (typeLink ArithmeticException) + "Negative infinity" + (Any NegativeInfinityNotAllowed)) + +math.ArithmeticException.negativeInfinity.doc : Doc +math.ArithmeticException.negativeInfinity.doc = + {{ + Raises a {type ArithmeticException} with a {NegativeInfinityNotAllowed} + error. + + # Example + + ``` + catch negativeInfinity + ``` + }} + +math.ArithmeticException.notANumber : '{Exception} r +math.ArithmeticException.notANumber = + do Exception.raise (Failure (typeLink ArithmeticException) "NaN" (Any NaN)) + +math.ArithmeticException.notANumber.doc : Doc +math.ArithmeticException.notANumber.doc = + {{ + Raises a {type ArithmeticException} with a {NotANumber} error. + + # Example + + ``` + catch ArithmeticException.notANumber + ``` + }} + +math.ArithmeticException.overflow : '{Exception} r +math.ArithmeticException.overflow = + do + Exception.raise + (Failure (typeLink ArithmeticException) "Overflow" (Any Overflow)) + +math.ArithmeticException.overflow.doc : Doc +math.ArithmeticException.overflow.doc = + {{ + Raises a {type ArithmeticException} with an {Overflow} error. + + # Example + + ``` + catch overflow + ``` + }} + +math.ArithmeticException.positiveInfinity : '{Exception} r +math.ArithmeticException.positiveInfinity = + do + Exception.raise + (Failure + (typeLink ArithmeticException) + "Positive infinity" + (Any PositiveInfinityNotAllowed)) + +math.ArithmeticException.positiveInfinity.doc : Doc +math.ArithmeticException.positiveInfinity.doc = + {{ + Raises a {type ArithmeticException} with a {PositiveInfinityNotAllowed} + error. + + # Example + + ``` + catch positiveInfinity + ``` + }} + +math.ArithmeticException.underflow : '{Exception} r +math.ArithmeticException.underflow = + do + Exception.raise + (Failure (typeLink ArithmeticException) "Underflow" (Any Underflow)) + +math.ArithmeticException.underflow.doc : Doc +math.ArithmeticException.underflow.doc = + {{ + Raises a {type ArithmeticException} with an {Underflow} error. + + # Example + + ``` + catch underflow + ``` + }} + +(math.Natural.*) : Natural -> Natural -> Natural +u math.Natural.* v = + use List dropRightWhile fill size unsafeAt + use List.Nonempty toList + use Nat + < == > + b = radix + us = toList (digits u) + vs = toList (digits v) + m = size us + n = size vs + m6 j ws = if j < n then m2 j ws else dropRightWhile (x -> x == 0) ws + m2 j ws = + use List :+ + vj = if n > j then unsafeAt j vs else 0 + if vj == 0 then m6 (j + 1) (ws :+ 0) else m4 0 j 0 vj ws + m4 i j k vj ws = + use List ++ + use Nat * - / + ui = if m > i then unsafeAt i us else 0 + t = ui * vj + (if size ws > i + j then unsafeAt (i + j) ws else 0) + k + ws' = + replace + (i + j) + (Nat.mod t b) + (if size ws > i + j then ws else ws ++ fill (size ws - i + j) 0) + k' = t / b + i' = i + 1 + if i' < m then m4 i' j k' vj ws' + else + ws'' = + replace + (m + j) + k' + (if size ws' > m + j then ws' else ws' ++ fill (size ws' - m + j) 0) + m6 (j + 1) ws'' + mkNatural (dropRightWhile (x -> x == 0) (m2 0 (fill m 0))) + +(math.Natural.+) : Natural -> Natural -> Natural +u math.Natural.+ v = + use List :+ size unsafeAt + use List.Nonempty toList + use Nat + == > >= + b = radix + us = toList (digits u) + vs = toList (digits v) + uz = size us + vz = size vs + n = if uz > vz then uz else vz + ignore "j runs through digit positions" + ignore "k keeps track of carries at each step" + go j k ws = + if j >= n then if k >= 0 then ws :+ k else ws + else + uj = if uz > j then unsafeAt j us else 0 + vj = if vz > j then unsafeAt j vs else 0 + ignore "Add the digits at position j, plus carry" + a = uj + vj + k + ignore "The new digit at j is the remainder of dividing by the radix" + wj = Nat.mod a b + ignore "We carry if necessary" + k' = if a >= b then 1 else 0 + ignore "Go to the next digit" + go (j + 1) k' (ws :+ wj) + mkNatural (List.dropRightWhile (x -> x == 0) (go 0 0 [])) + +(math.Natural.-) : Natural -> Natural -> Natural +u math.Natural.- v = + use Int / + use List :+ size unsafeAt + use List.Nonempty toList + use Nat < >= + b = Nat.toInt radix + us = toList (digits u) + vs = toList (digits v) + uz = size us + vz = size vs + n = Nat.max uz vz + go j k ws = + if j >= n then if k Int.== +0 then ws else [] + else + uj = if j < uz then unsafeAt j us else 0 + vj = if j < vz then unsafeAt j vs else 0 + a = subtractToInt uj vj Int.+ k + go (j Nat.+ 1) (a / b) (ws :+ truncate0 (Int.mod a b)) + mkNatural (List.dropRightWhile (x -> x Nat.== 0) (go 0 +0 [])) + +(math.Natural./) : Natural -> Natural ->{Exception} Natural +a math.Natural./ b = at1 (divMod a b) + +(math.Natural.<) : Natural -> Natural -> Boolean +a math.Natural.< b = + use Natural <= == + a <= b && Boolean.not (a == b) + +(math.Natural.<=) : Natural -> Natural -> Boolean +a math.Natural.<= b = + use Natural - + a - b === Natural.zero + +(math.Natural.==) : Natural -> Natural -> Boolean +a math.Natural.== b = (a : Natural) === (b : Natural) + +(math.Natural.>) : Natural -> Natural -> Boolean +a math.Natural.> b = + use Natural <= + Boolean.not (a <= b) + +(math.Natural.>=) : Natural -> Natural -> Boolean +a math.Natural.>= b = + use Natural < + Boolean.not (a < b) + +math.Natural.div.aborting : Natural -> Natural ->{Abort} Natural +math.Natural.div.aborting a b = at1 (divMod.aborting a b) + +math.Natural.div.aborting.doc : Doc +math.Natural.div.aborting.doc = + use Natural fromNat + use div aborting + {{ + Divides one {type Natural} by another {type Natural}, aborting if the divisor + is zero. + + # Example + + ``` + toOptional! do aborting (fromNat 10) (fromNat 2) + ``` + + ``` + toOptional! do aborting (fromNat 10) (fromNat 0) + ``` + }} + +math.Natural.divMod : Natural -> Natural ->{Exception} (Natural, Natural) +math.Natural.divMod a b = toDefault dividedByZero (do divImpl a b) () + +math.Natural.divMod.aborting : Natural -> Natural ->{Abort} (Natural, Natural) +math.Natural.divMod.aborting = divImpl + +math.Natural.divMod.aborting.doc : Doc +math.Natural.divMod.aborting.doc = + use Natural fromNat + use divMod aborting + {{ + Divides two {type Natural} numbers and returns the quotient and remainder. + Calls {abort} if the divisor is zero. + + # Example + + ``` + toOptional! do aborting (fromNat 10) (fromNat 3) + ``` + + ``` + toOptional! do aborting (fromNat 10) (fromNat 0) + ``` + }} + +math.Natural.divMod.doc : Doc +math.Natural.divMod.doc = + use Function join + use Natural / fromNat + {{ + Takes two {type Natural} values and returns a pair of {type Natural} values. + The first value is the quotient of the division of the first argument by the + second argument. The second value is the remainder. Throws an + {type ArithmeticException} if the second argument is zero. + + # Examples + + ``` + f = join (curry mapPair) toDecimalText + catch do f (divMod (fromNat 10) (fromNat 3)) + ``` + + ``` + f = join (curry mapPair) toDecimalText + catch do f (divMod (fromNat 10) (fromNat 0)) + ``` + + ``` + f = join (curry mapPair) toDecimalText + catch do f (divMod (fromNat 10) (fromNat 1)) + ``` + + # See also + + * {/} + * {Natural.mod} + }} + +math.Natural.doc : Doc +math.Natural.doc = + use Natural.docs constructing converting + {{ + The {type Natural} type provides natural numbers (unsigned integers) of + arbitrary size, starting at 0. Whereas the {type Nat} type represents 64-bit + unsigned machine integers with a maximum value of {maxNat}, this type has no + such limit, at the cost of being considerably slower. + + This document provides a high-level overview of the main operations on + {type Natural}, organized into the following categories: + + * [Construction]({constructing}) + * [Basic arithmetic]({arithmetic}) + * [Comparison]({relations}) + * [Conversion to and from other types]({converting}) + + {{ constructing }} + + {{ arithmetic }} + + {{ relations }} + + {{ converting }} + }} + +math.Natural.docs.arithmetic : Doc +math.Natural.docs.arithmetic = + use Natural * + - <= fromNat one zero + {{ + # Basic arithmetic on natural numbers + + {+} adds two {type Natural} numbers together: + + ``` + toDecimalText (one + one) + ``` + + {-} subtracts one {type Natural} from another, but returns at least 0. + Since {type Natural} does not represent negative numbers, `` x - y `` + returns 0 if ``x <= y``: + + ``` + toDecimalText (fromNat 12 - fromNat 4) + ``` + + ``` + toDecimalText (fromNat 4 - fromNat 12) + ``` + + {*} is multiplication on {type Natural} numbers: + + ``` + toDecimalText (fromNat 32 * fromNat 128) + ``` + + {maybeDiv} is safe division on {type Natural} numbers. It returns `None` if + the divisor is equal to {zero}: + + ``` + Optional.map toDecimalText (maybeDiv (fromNat 128) (fromNat 32)) + ``` + + ``` + maybeDiv (fromNat 3) (fromNat 0) + ``` + + {div.aborting} is the same as {maybeDiv} except it calls {abort} when the + divisor is equal to {zero}. + + `` x ^ y `` raises `x` to the power of `y`, where `x` is a {type Natural} + number, but `y` is a {type Nat}: + + ``` + toDecimalText (fromNat maxNat ^ 3) + ``` + }} + +math.Natural.docs.constructing : Doc +math.Natural.docs.constructing = + use Natural + fromNat parse + use Optional map + {{ + # Constructing numbers + + {Natural.zero} is the number 0. + + {Natural.one} is the number 1. + + {fromNat} converts a {type Nat} into a {type Natural}: + + ``` + toDecimalText (fromNat 42) + ``` + + ``` + toDecimalText (fromNat maxNat + fromNat 2) + ``` + + {parse} takes a radix, and a {type Text} consisting of digits in that + radix, and turns it into a {type Natural} number (or {None} if the + {type Text} can't be parsed): + + ``` + map toDecimalText (parse 10 "123") + ``` + + ``` + map toDecimalText (parse 16 "DEADBEEF") + ``` + + ``` + map toDecimalText (parse 10 "not a number") + ``` + + {parse!} does the same thing as {parse} except it calls {abort} on failure, + instead of returning {None}. + }} + +math.Natural.docs.converting : Doc +math.Natural.docs.converting = + use Natural + fromNat one toHex + use toText deprecated + {{ + # Conversions to other types + + {toDecimalText} converts a {type Natural} number to a {type Text} string of + decimal digits (base-10): + + ``` + toDecimalText (fromNat maxNat) + ``` + + {toHex} converts a {type Natural} number to a {type Text} string of + hexadecimal digits (base-16): + + ``` + toHex (fromNat maxNat) + ``` + + {deprecated} converts a {type Natural} number to a {type Text} string using + digits in the given radix (at least base-2 and at most base-36). Returns + {None} if the radix is outside that range: + + ``` + deprecated (fromNat 3735928559) 2 + ``` + + ``` + deprecated (fromNat maxNat) 36 + ``` + + ``` + deprecated one 0 + ``` + + {toMaybeNat} converts a {type Natural} to a {type Nat}, if it's small + enough. Returns {None} if the argument is larger than {maxNat}: + + ``` + toMaybeNat one + ``` + + ``` + toMaybeNat (fromNat maxNat + one) + ``` + }} + +math.Natural.docs.relations : Doc +math.Natural.docs.relations = + use Natural < <= == > >= one zero + {{ + # Comparing natural numbers + + {==} is equality on {type Natural} numbers. Returns `` true `` if both + arguments evaluate to the same number, or `` false `` otherwise: + + ``` + one == one + ``` + + ``` + zero == one + ``` + + {Natural.isZero} returns `` true `` if its argument evaluates to {zero}, or + `` false `` otherwise. + + `` a <= b `` is `` true `` if `a` is at most `b`, or `` false `` otherwise. + + `` a < b `` is `` true `` if `a` is strictly less than `b`, or `` false `` + otherwise. + + `` a >= b `` is `` true `` if `a` is at least `b`, or `` false `` + otherwise. + + `` a > b `` is `` true `` if `a` is strictly greater than `b`, or `` false + `` otherwise. + }} + +math.Natural.eq.doc : Doc +math.Natural.eq.doc = + use Natural eq fromNat + {{ + Returns `` true `` if the two {type Natural}s are equal, `` false `` + otherwise. + + # Examples + + ``` + eq (fromNat 1) (fromNat 1) + ``` + + ``` + eq (fromNat 1) (fromNat 2) + ``` + }} + +math.Natural.fromNat : Nat -> Natural +math.Natural.fromNat u = + b = radix + go n ws = + use List :+ + use Nat / == + x = n / b + ws' = ws :+ Nat.mod n b + if x == 0 then ws' else go (n / b) ws' + mkNatural (go u []) + +math.Natural.fromNat.doc : Doc +math.Natural.fromNat.doc = + use Natural fromNat + {{ + Converts a {type Nat} into a {type Natural}. + + # Examples + + ``` + toDecimalText (fromNat 0) + ``` + + ``` + toDecimalText (fromNat maxNat) + ``` + }} + +math.Natural.gt.doc : Doc +math.Natural.gt.doc = + use Natural fromNat gt + {{ + Returns `` true `` if the first {type Natural} is greater than the second, `` + false `` otherwise. + + # Examples + + ``` + gt (fromNat 2) (fromNat 1) + ``` + + ``` + gt (fromNat 1) (fromNat 2) + ``` + }} + +math.Natural.gte.doc.doc : Doc +math.Natural.gte.doc.doc = + use Natural >= fromNat + {{ + Returns `` true `` if the first {type Natural} is greater than or equal to + the second, `` false `` otherwise. + + # Examples + + ``` + fromNat 2 >= fromNat 1 + ``` + + ``` + fromNat 1 >= fromNat 2 + ``` + + ``` + fromNat 1 >= fromNat 1 + ``` + }} + +math.Natural.internal.bitMask : Nat +math.Natural.internal.bitMask = + use Nat - + radix - 1 + +math.Natural.internal.bitWidth : Nat +math.Natural.internal.bitWidth = 32 + +math.Natural.internal.digits : Natural -> List.Nonempty Nat +math.Natural.internal.digits = cases Natural ns -> ns + +math.Natural.internal.divImpl : Natural -> Natural ->{Abort} (Natural, Natural) +math.Natural.internal.divImpl u v = + use List +: + use List.Nonempty size toList + use Nat + / < == mod + use Natural fromNat + use Nonempty last + atOr0 k xs = Optional.getOrElse 0 (List.at k xs) + us = digits u + vs = digits v + m = size us + n = size vs + ignore "Simplified division if `v` is a small number:" + shortDivision v r ws ujs = match List.unsnoc ujs with + Some (ujs', uj) -> + use Nat * + ignore "r is the previous remainder" + rb = r * radix + ignore "w is the next digit in the result" + w = (rb + uj) / v + ignore "r' is the new remainder" + r' = mod (rb + uj) v + shortDivision v r' (w +: ws) ujs' + None -> (fromNats ws, fromNat r) + abortWhen (Natural.isZero v) + if m < n then (Natural.zero, u) + else + if n == 1 then shortDivision (Nonempty.head vs) 0 [] (toList us) + else + ignore "Scale both sides such that the divisor's high bit is set" + s = 32 Nat.- Nat.leadingZeros (last vs) + d = Nat.shiftLeft 1 s + ignore "un and vn are the normalized dividend and divisor, respectively" + un = u Natural.* fromNat d + vn = v Natural.* fromNat d + uns = toList (digits un) + vns = toList (digits vn) + ignore "main loop" + loop j' qds uns = + use Nat * - > >= shiftRight + j = j' - 1 + if j' >= 1 then + x = atOr0 (j + n) uns * radix + atOr0 (j + n - 1) uns + vmsd = last (digits vn) + ignore "estimate a digit in the quotient and remainder" + estimate qh rh = + if qh >= radix + || qh * atOr0 (n - 2) vns > radix * rh + atOr0 (j + n - 2) uns then + qh' = qh - 1 + rh' = rh + vmsd + if rh' < radix then estimate qh' rh' else (qh', rh') + else (qh, rh) + let + (qhat, rhat) = estimate (x / vmsd) (mod x vmsd) + multiply k i uns = + if i < n then + p = qhat * atOr0 i vns + t = atOr0 (i + j) uns - k - Nat.and p bitMask + uns' = replace (i + j) t uns + multiply + (shiftRight p bitWidth - shiftRight t bitWidth) (i + 1) uns' + else (k, uns) + let + (k, unstmp) = multiply 0 0 uns + uns' = updateAt (x -> x - k) (j + n) unstmp + let + (qd, uns'') = + if k > atOr0 (j + n) uns then + addBack i k uns' = + if i < n then + t = atOr0 (i + j) uns + atOr0 i vns + k + uns'' = replace (i + j) t uns' + addBack (i + 1) (shiftRight t bitWidth) uns'' + else (k, uns') + let + (k, uns'') = addBack 0 0 uns' + (qhat - 1, updateAt (x -> x + k) (j + n) uns'') + else (qhat, uns') + loop (j' - 1) (qd +: qds) uns'' + else qds + qds = loop (m Nat.- n + 1) [] uns + q = fromNats qds + r = u Natural.- q Natural.* v + (q, r) + +math.Natural.internal.fromNats : [Nat] -> Natural +math.Natural.internal.fromNats nats = + Natural.internal.normalize (mkNatural nats) + +math.Natural.internal.mkNatural : [Nat] -> Natural +math.Natural.internal.mkNatural nats = match mayNonempty nats with + None -> Natural (List.Nonempty.singleton 0) + Some ns -> Natural ns + +math.Natural.internal.normalize : Natural -> Natural +math.Natural.internal.normalize = + use List :+ + use Nat == + lmask = bitMask + hmask = Nat.complement lmask + cases + Natural ns -> + rec rem done carry = match rem with + [] -> + done' = List.dropRightWhile (x -> x == 0) done + if carry == 0 then mkNatural done' else mkNatural (done' :+ carry) + x +: xs -> go xs x done carry + go rem next done carry = + use Nat + <= and + newNext = next + carry + if newNext <= lmask then rec rem (done :+ newNext) 0 + else + newNewNext = and lmask newNext + newCarry = Nat.shiftRight (and hmask newNext) bitWidth + rec rem (done :+ newNewNext) newCarry + go (Nonempty.tail ns) (Nonempty.head ns) [] 0 + +math.Natural.internal.radix : Nat +math.Natural.internal.radix = Nat.pow 2 bitWidth + +math.Natural.isZero : Natural -> Boolean +math.Natural.isZero n = + use Nat == + d = digits n + List.Nonempty.size d == 1 && Nonempty.head d == 0 + +math.Natural.isZero.doc : Doc +math.Natural.isZero.doc = + {{ Returns true if the given {type Natural} is zero. }} + +math.Natural.lt.doc : Doc +math.Natural.lt.doc = + use Natural fromNat lt + {{ + Returns `` true `` if the first {type Natural} is less than the second, `` + false `` otherwise. + + # Examples + + ``` + lt (fromNat 1) (fromNat 2) + ``` + + ``` + lt (fromNat 2) (fromNat 1) + ``` + }} + +math.Natural.lte.doc : Doc +math.Natural.lte.doc = + use Natural <= fromNat + {{ + Returns `` true `` if the first {type Natural} is less than or equal to the + second, `` false `` otherwise. + + # Examples + + ``` + fromNat 1 <= fromNat 2 + ``` + + ``` + fromNat 2 <= fromNat 1 + ``` + + ``` + fromNat 1 <= fromNat 1 + ``` + }} + +math.Natural.maybeDiv : Natural -> Natural -> Optional Natural +math.Natural.maybeDiv a b = Optional.map at1 (maybeDivMod a b) + +math.Natural.maybeDiv.doc : Doc +math.Natural.maybeDiv.doc = + use Natural fromNat + {{ + Divides one {type Natural} by another, returning {None} if the divisor is + zero. + + # Example + + ``` + maybeDiv (fromNat 10) (fromNat 2) + ``` + + ``` + maybeDiv (fromNat 10) (fromNat 0) + ``` + }} + +math.Natural.maybeDivMod : Natural -> Natural -> Optional (Natural, Natural) +math.Natural.maybeDivMod a b = toOptional! do divImpl a b + +math.Natural.maybeDivMod.doc : Doc +math.Natural.maybeDivMod.doc = + use Natural fromNat + {{ + Divides one {type Natural} by another {type Natural}, returning {None} if the + divisor is zero, or {Some} of the quotient and remainder otherwise. + + # Example + + ``` + maybeDivMod (fromNat 10) (fromNat 2) + ``` + + ``` + maybeDivMod (fromNat 10) (fromNat 0) + ``` + }} + +math.Natural.maybeMod : Natural -> Natural -> Optional Natural +math.Natural.maybeMod a b = Optional.map at2 (maybeDivMod a b) + +math.Natural.maybeMod.doc : Doc +math.Natural.maybeMod.doc = + use Natural fromNat + {{ + Computes the remainder of one {type Natural} divided by another, returning + {None} if the divisor is zero. + + # Example + + ``` + maybeMod (fromNat 10) (fromNat 3) + ``` + + ``` + maybeMod (fromNat 10) (fromNat 0) + ``` + }} + +math.Natural.mod : Natural -> Natural ->{Exception} Natural +math.Natural.mod a b = at2 (divMod a b) + +math.Natural.mod.aborting : Natural -> Natural ->{Abort} Natural +math.Natural.mod.aborting a b = at2 (divMod.aborting a b) + +math.Natural.mod.aborting.doc : Doc +math.Natural.mod.aborting.doc = + use Natural fromNat + use mod aborting + {{ + Computes the remainder of one {type Natural} divided by another + {type Natural}, calling {abort} if the divisor is zero. + + # Example + + ``` + toOptional! do aborting (fromNat 10) (fromNat 2) + ``` + + ``` + toOptional! do aborting (fromNat 10) (fromNat 0) + ``` + }} + +math.Natural.mod.doc : Doc +math.Natural.mod.doc = + use Abort toOptional + use Natural fromNat mod zero + {{ + `` mod x y `` is the remainder of dividing `x` by `y` (see {maybeDiv}). + Throws {DividedByZero} if `y` is equal to {zero}. + + # Examples + + ``` + toOptional do toDecimalText (mod (fromNat 11) (fromNat 2)) + ``` + + ``` + toOptional do toDecimalText (mod (fromNat 10000) (fromNat 10)) + ``` + + ``` + Either.mapLeft message (catch do toDecimalText (mod Natural.one zero)) + ``` + }} + +math.Natural.one : Natural +math.Natural.one = Natural.fromNat 1 + +math.Natural.one.doc : Doc +math.Natural.one.doc = {{ The {type Natural} number 1. }} + +math.Natural.parse : Nat -> Text -> Optional Natural +math.Natural.parse radix t = + use Natural * + >= fromNat + use Optional map + use Text uncons + r' = fromNat radix + go acc = cases + None -> acc + Some (d, t) -> + a = map fromNat (toBase36Digit d) + if map (x -> x >= r') a === Some true then None + else + match acc with + None -> go a (uncons t) + Some b -> go (map (x -> x + b * r') a) (uncons t) + go None (uncons t) + +math.Natural.parse.deprecated : Text -> Nat -> Optional Natural +math.Natural.parse.deprecated t radix = + use Natural * + >= fromNat + use Optional map + use Text uncons + r' = fromNat radix + go acc = cases + None -> acc + Some (d, t) -> + a = map fromNat (toBase36Digit d) + if map (x -> x >= r') a === Some true then None + else + match acc with + None -> go a (uncons t) + Some b -> go (map (x -> x + b * r') a) (uncons t) + go None (uncons t) + +math.Natural.parse.deprecated.doc : Doc +math.Natural.parse.deprecated.doc = + {{ Deprecated in favor of {Natural.parse}, which flips the argument order. }} + +math.Natural.parse.doc : Doc +math.Natural.parse.doc = + use Natural parse + use Optional map + {{ + `` parse radix str `` attempts to parse a {type Natural} from the text `str`, + which is expected to consist of digits in the radix `radix`. Returns {None} + if this is not the case, or if the `radix` given is outside the permitted + range. + + The smallest supported radix is `2`, where a digit must be either `0` or `1`. + The largest supported radix is `36`, where a digit is expected to be either a + numeric character in the range `0` through `9` or a Latin character in the + range `A` (which represents ten) through `Z` (which represents thirty-five). + This function is not case-sensitive. + + # Examples: + + ``` + map toDecimalText (parse 10 "42") + ``` + + ``` + map toDecimalText (parse 2 "1001") + ``` + + ``` + map toDecimalText (parse 16 "DEADBEEF") + ``` + }} + +test> math.Natural.parse.tests.roundtrip = runs 1000 do + x = gen.natural() + expect (Natural.parse 10 (toDecimalText x) === Some x) + +math.Natural.parse! : Nat -> Text ->{Abort} Natural +math.Natural.parse! radix t = Optional.toAbort (Natural.parse radix t) + +math.Natural.parse!.deprecated : Text -> Nat ->{Abort} Natural +math.Natural.parse!.deprecated t radix = + Optional.toAbort (parse.deprecated t radix) + +math.Natural.parse!.deprecated.doc : Doc +math.Natural.parse!.deprecated.doc = + {{ Deprecated in favor of {parse!}, which flips the argument order. }} + +math.Natural.parse!.doc : Doc +math.Natural.parse!.doc = + use Natural + + {{ + `` parse! radix str `` attempts to parse a {type Natural} from the text + `str`, which is expected to consist of digits in the radix `radix`. Calls + {abort} if this is not the case, or if the `radix` given is outside the + permitted range. + + The smallest supported radix is `2`, where a digit must be either `0` or `1`. + The largest supported radix is `36`, where a digit is expected to be either a + numeric character in the range `0` through `9` or a Latin character in the + range `A` (which represents ten) through `Z` (which represents thirty-five). + This function is not case-sensitive. + + # Examples: + + ``` + toOptional! do toDecimalText (parse! 10 "42") + ``` + + ``` + toOptional! do + toDecimalText (parse! 2 "01111110011" + parse! 16 "DeadBeef") + ``` + }} + +test> math.Natural.parse!.tests.roundtrip = runs 1000 do + x = gen.natural() + expect ((toOptional! do parse! 10 (toDecimalText x)) === Some x) + +math.Natural.pow.doc : Doc +math.Natural.pow.doc = + {{ + Computes the power of a natural number. + + # Example + + ``` + Natural.pow (Natural.fromNat 2) 3 + ``` + }} + +test> math.Natural.pow.tests.powerOfOne = runs 1000 do + use Natural == + x = gen.natural() + expect (Natural.pow x 1 == x) + +test> math.Natural.pow.tests.powerOfPower = runs 1000 do + use Nat * + use Natural == pow + use gen natIn + x = gen.natural() + a = natIn 0 100 () + b = natIn 0 100 () + expect (pow (pow x a) b == pow x (a * b)) + +test> math.Natural.pow.tests.powerOfProduct = runs 1000 do + use Natural * == pow + use gen natural + x = natural() + y = natural() + a = gen.natIn 0 100 () + expect (pow (x * y) a == pow x a * pow y a) + +test> math.Natural.pow.tests.powerOfZero = runs 1000 do + use Natural == + x = gen.natural() + expect (Natural.pow x 0 == Natural.one) + +test> math.Natural.pow.tests.product = runs 1000 do + use Nat + + use Natural * == pow + use gen natIn + x = gen.natural() + a = natIn 0 1000 () + b = natIn 0 1000 () + expect (pow x a * pow x b == pow x (a + b)) + +test> math.Natural.pow.tests.quotient = runs 1000 do + Optional.getOrElse + Test.fail (Abort.toOptional + (do + use Nat - + use Natural + == pow + use gen natIn + x = gen.natural() + Natural.one + b = natIn 1 1000 () + a = natIn b 1000 () + xa = pow x a + xb = pow x b + lhs = div.aborting xa xb + rhs = pow x (a - b) + if rhs == lhs then Test.ok else bug (x, a, b, xa, xb, lhs, rhs)) ()) + +math.Natural.shiftLeft : Natural -> Nat -> Natural +math.Natural.shiftLeft x y = + use List :+ + use Nat / > + go words bits in out = + if words > 0 then go 0 bits in (List.fill words 0) + else + f s n = + use Nat - + nout = Nat.and bitMask (Nat.shiftLeft n bits) + carry = Nat.shiftRight n (32 - bits) + let + (carried, outs) = s + (carry, outs :+ Nat.or nout carried) + let + (c, r) = List.foldLeft f (0, out) in + fromNats (if c > 0 then r :+ c else r) + d = y / 32 + m = Nat.mod y 32 + go d m (List.Nonempty.toList (digits x)) [] + +math.Natural.shiftLeft.doc : Doc +math.Natural.shiftLeft.doc = + use Natural * + {{ + `` Natural.shiftLeft n k `` Returns a {type Natural} whose value is + ``n * Natural.fromNat (Nat.pow 2 k)``. + }} + +test> math.Natural.tests.additionAssociative = + runs 100 do laws.associative gen.natural (Natural.+) + +test> math.Natural.tests.additionCommutative = + runs 100 do laws.commutative gen.natural (Natural.+) + +test> math.Natural.tests.additionZero = runs 100 do + use Natural + fromNat + a = gen.natural() + expect (a + fromNat 0 === a && fromNat 0 + a === a) + +test> math.Natural.tests.distributiveLaw = + runs 1000 do laws.distributive gen.natural (Natural.*) (Natural.+) + +math.Natural.tests.gen.natural : '{Gen} Natural +math.Natural.tests.gen.natural = + do + use Natural + fromNat + use Weighted <|> + naturals n r = + yield n + <|> (weight 1 do + yield r + <|> (weight 1 do naturals (n + fromNat 1) (r + fromNat 1))) + Gen.sample (naturals (fromNat 0) (fromNat bitMask)) + +test> math.Natural.tests.multiplicationAssociative = + runs 1000 do laws.associative gen.natural (Natural.*) + +test> math.Natural.tests.multiplicationCommutative = + runs 100 do laws.commutative gen.natural (Natural.*) + +test> math.Natural.tests.multiplicationZero = runs 100 do + use Natural * fromNat + n = gen.natural() + expect (n * fromNat 0 === fromNat 0) + +test> math.Natural.tests.subtraction = runs 1000 do + use Natural + - zero + use gen natural + x = natural() + y = natural() + z = natural() + a = x + y - x === y + x - y + b = x - y - z === x - y + z + c = x - x === zero + d = zero - x === zero + expect (a && b && c && d) + +test> math.Natural.tests.subtractLeftAdjoint = + runs 1000 do adjoint gen.natural (Natural.<=) (Natural.-) (Natural.+) + +test> math.Natural.tests.zeroDivisors = runs 1000 do + use Natural * zero + use gen natural + a = natural() + b = natural() + expect (implies (a * b === zero) (a === zero || b === zero)) + +math.Natural.toDecimalText : Natural -> Text +math.Natural.toDecimalText n = match toText.deprecated n 10 with + None -> bug "Natural.toDecimalText aborted" + Some a -> a + +math.Natural.toDecimalText.doc : Doc +math.Natural.toDecimalText.doc = + {{ + Renders a {type Natural} into {type Text} in base-10. + + # Examples + + ``` + toOptional! do toDecimalText (parse! 10 "16777216") + ``` + + ``` + toOptional! do toDecimalText (parse! 16 "DEADBEEF") + ``` + }} + +math.Natural.toHex : Natural -> Text +math.Natural.toHex n = match toText.deprecated n 16 with + None -> bug "Natural.toText aborted" + Some n -> n + +math.Natural.toHex.doc : Doc +math.Natural.toHex.doc = + use Natural toHex + {{ + Converts a {type Natural} to a {type Text} containing its hexadecimal + representation. + + # Examples + + ``` + toHex (Natural.fromNat 0) + ``` + + ``` + toHex (factorial 48) + ``` + }} + +math.Natural.toMaybeNat : Natural -> Optional Nat +math.Natural.toMaybeNat n = match digits n with + Nonempty.Nonempty a [] -> Some a + Nonempty.Nonempty a [b] -> Some (a Nat.* Nat.shiftLeft b 32) + _ -> None + +math.Natural.toMaybeNat.doc : Doc +math.Natural.toMaybeNat.doc = + use Natural + fromNat + {{ + Converts a {type Natural} to a {type Nat}. If the {type Natural} is outside + the range of {type Nat}, returns {None}. + + # Example + + ``` + toMaybeNat Natural.zero + ``` + + ``` + toMaybeNat (fromNat maxNat + fromNat 1) + ``` + }} + +math.Natural.toNat : Natural ->{Exception} Nat +math.Natural.toNat n = match digits n with + Nonempty.Nonempty a [] -> a + Nonempty.Nonempty a [b] -> a Nat.* Nat.shiftLeft b 32 + _ -> overflow() + +math.Natural.toNat.doc : Doc +math.Natural.toNat.doc = + use Natural + one + {{ + Converts a {type Natural} to a {type Nat}, if it's small enough. Returns + {None} if the argument is larger than {maxNat}. + + # Examples + + ``` + toMaybeNat one + ``` + + ``` + toMaybeNat (Natural.fromNat maxNat + one) + ``` + }} + +test> math.Natural.toNat.tests.roundtrip = runs 1000 do + x = natInOrder() + expect (toMaybeNat (Natural.fromNat x) === Some x) + +math.Natural.toText : Nat -> Natural -> Optional Text +math.Natural.toText radix n = + use Nat < + use Natural >= + use Text ++ + radix' = Natural.fromNat radix + go n acc = match toOptional! do divMod.aborting n radix' with + Some (quot, rem) -> + if rem >= radix' then None + else + match fromBase36Digit (Nonempty.head (digits rem)) with + Some b -> + use Natural > + d = Char.toText b + if quot > Natural.zero then go quot (d ++ acc) else Some (d ++ acc) + None -> None + None -> None + if radix < 2 || radix Nat.> 36 then None else go n "" + +math.Natural.toText.deprecated : Natural -> Nat -> Optional Text +math.Natural.toText.deprecated n radix = + use Nat < + use Natural >= + use Text ++ + radix' = Natural.fromNat radix + go n acc = match toOptional! do divMod.aborting n radix' with + Some (quot, rem) -> + if rem >= radix' then None + else + match fromBase36Digit (Nonempty.head (digits rem)) with + Some b -> + use Natural > + d = Char.toText b + if quot > Natural.zero then go quot (d ++ acc) else Some (d ++ acc) + None -> None + None -> None + if radix < 2 || radix Nat.> 36 then None else go n "" + +math.Natural.toText.deprecated.doc : Doc +math.Natural.toText.deprecated.doc = + {{ + Deprecated in favor of {Natural.toText}, which flips the argument order. + }} + +math.Natural.toText.doc : Doc +math.Natural.toText.doc = + use Natural parse toText + use Optional flatMap + {{ + Renders a {type Natural} into {type Text} in the specified radix. + + The smallest allowed radix is ``2``, and the largest is ``36``. This function + returns {None} if the requested radix is not in that range. + + # Examples + + ``` + flatMap (toText 10) (parse 10 "16777216") + ``` + + ``` + flatMap (toText 16) (parse 16 "DEADBEEF") + ``` + + ``` + flatMap (toText 2) (parse 16 "DEADBEEF") + ``` + }} + +math.Natural.toText! : Nat -> Natural ->{Abort} Text +math.Natural.toText! radix n = Optional.toAbort (Natural.toText radix n) + +math.Natural.toText!.deprecated : Natural -> Nat ->{Abort} Text +math.Natural.toText!.deprecated n radix = + Optional.toAbort (toText.deprecated n radix) + +math.Natural.toText!.deprecated.doc : Doc +math.Natural.toText!.deprecated.doc = + {{ Deprecated in favor of {toText!}, which flips the argument order. }} + +math.Natural.toText!.doc : Doc +math.Natural.toText!.doc = + {{ + Renders a {type Natural} into {type Text} in the specified radix. + + The smallest allowed radix is ``2``, and the largest is ``36``. This function + calls {abort} if the requested radix is not in that range. + + # Examples + + ``` + toOptional! do toText! 10 (parse! 10 "16777216") + ``` + + ``` + toOptional! do toText! 16 (parse! 16 "DEADBEEF") + ``` + + ``` + toOptional! do toText! 2 (parse! 16 "DEADBEEF") + ``` + }} + +math.Natural.zero : Natural +math.Natural.zero = Natural.fromNat 0 + +math.Natural.zero.doc : Doc +math.Natural.zero.doc = {{ The {type Natural} number 0. }} + +(math.Natural.^) : Natural -> Nat -> Natural +b math.Natural.^ e = + use Nat / == > + use Natural * + r = Natural.one + go b e r = + if e > 0 then + rem = Nat.mod e 2 + quot = e / 2 + go (b * b) quot (if rem == 1 then r * b else r) + else r + Abort.toBug do go b e r + +metadata.Author.doc : Doc +metadata.Author.doc = + {{ + Represents the author of a definition. An {type Author} consists of a + {type GUID} and a {type Text} name. + + 📚 Guide: + [Setting the default author](https://www.unison-lang.org/learn/tooling/configuration/) + }} + +metadata.Author.guid : Author -> GUID +metadata.Author.guid = cases Author guid _ -> guid + +metadata.Author.guid.doc : Doc +metadata.Author.guid.doc = {{ The unique identifier of the author. }} + +metadata.Author.guid.modify : (GUID ->{𝕖} GUID) -> Author ->{𝕖} Author +metadata.Author.guid.modify f = cases Author guid name -> Author (f guid) name + +metadata.Author.guid.modify.doc : Doc +metadata.Author.guid.modify.doc = + {{ Modifies the {type GUID} of a {type Author}. }} + +metadata.Author.guid.set : GUID -> Author -> Author +metadata.Author.guid.set guid1 = cases Author _ name -> Author guid1 name + +metadata.Author.guid.set.doc : Doc +metadata.Author.guid.set.doc = {{ Sets the GUID of a {type Author}. }} + +metadata.Author.name : Author -> Text +metadata.Author.name = cases Author _ name -> name + +metadata.Author.name.doc : Doc +metadata.Author.name.doc = {{ The name of the author. }} + +metadata.Author.name.modify : (Text ->{𝕖} Text) -> Author ->{𝕖} Author +metadata.Author.name.modify f = cases Author guid name -> Author guid (f name) + +metadata.Author.name.modify.doc : Doc +metadata.Author.name.modify.doc = + {{ + Modifies the name of a {type Author}. + + # Example + + ``` + Author.name.modify Text.toUppercase (Author (GUID 0xs1234) "Alice") + ``` + }} + +metadata.Author.name.set : Text -> Author -> Author +metadata.Author.name.set name1 = cases Author guid _ -> Author guid name1 + +metadata.Author.name.set.doc : Doc +metadata.Author.name.set.doc = {{ Sets the name of a {type Author}. }} + +metadata.Author.toCopyrightHolder : Author -> CopyrightHolder +metadata.Author.toCopyrightHolder = cases + Author guid name -> CopyrightHolder guid name + +metadata.Author.toCopyrightHolder.doc : Doc +metadata.Author.toCopyrightHolder.doc = + {{ Converts a {type Author} to a {type CopyrightHolder}. }} + +metadata.authors.andriypalamarchuk : Author +metadata.authors.andriypalamarchuk = + Author andriypalamarchuk.guid "Andriy Palamarchuk" + +metadata.authors.andriypalamarchuk.guid : GUID +metadata.authors.andriypalamarchuk.guid = + GUID 0xs5adc5efa6ccad3952bdca2a6bb9ef8c6f23c740248f75eefc6c62efcbdd39117 + +metadata.authors.anovstrup : Author +metadata.authors.anovstrup = Author anovstrup.guid "Aaron Novstrup" + +metadata.authors.anovstrup.guid : GUID +metadata.authors.anovstrup.guid = + GUID 0xsd9ac1a05c39c416669b5491c7d8bd43b540bd2fd640668746d88d72ffca652ad + +metadata.authors.aryairani : Author +metadata.authors.aryairani = Author aryairani.guid "Arya Irani" + +metadata.authors.aryairani.guid : GUID +metadata.authors.aryairani.guid = + GUID 0xsae849a2722f0138ac80d7d1abb2da095eb55471fa560dc94a637cfe51c4b6d61 + +metadata.authors.atacratic : Author +metadata.authors.atacratic = Author atacratic.guid "Chris Gibbs" + +metadata.authors.atacratic.guid : GUID +metadata.authors.atacratic.guid = + GUID 0xscdcb28403d14f0706ba238a7cd56b57341d94c1de379cd0d9649ef4e8c2aa9cb + +metadata.authors.ceedubs : Author +metadata.authors.ceedubs = Author ceedubs.guid "Cody Allen" + +metadata.authors.ceedubs.guid : GUID +metadata.authors.ceedubs.guid = + GUID 0xs033486fb6ece1d72a03bf80517eb8aa19501ebe4f8923cd74c28589bd968958e + +metadata.authors.chiroptical : Author +metadata.authors.chiroptical = Author chiroptical.guid "Barry Moore" + +metadata.authors.chiroptical.guid : GUID +metadata.authors.chiroptical.guid = + GUID 0xs6d941a759d78fa72c90bb71c1ba2b08cb9b09526a8f3e6835f91efebbb2d00f6 + +metadata.authors.chrispenner : Author +metadata.authors.chrispenner = Author chrispenner.guid "Chris Penner" + +metadata.authors.chrispenner.guid : GUID +metadata.authors.chrispenner.guid = + GUID 0xs4ab15704caa2d270f00d53d8fa721f5145b1b38aff4d38f21a2c0d916df56c77 + +metadata.authors.daanleijen : Author +metadata.authors.daanleijen = Author daanleijen.guid "Daan Leijen" + +metadata.authors.daanleijen.guid : GUID +metadata.authors.daanleijen.guid = + GUID 0xs5638bf4454691fdf9acffdd81a3ad445600d474b18f22400ccd95bec395db250 + +metadata.authors.dariooddenino : Author +metadata.authors.dariooddenino = Author dariooddenino.guid "Dario Oddenino" + +metadata.authors.dariooddenino.guid : GUID +metadata.authors.dariooddenino.guid = + GUID 0xs479ed25ff7d935798b990fa8ca702600f66a7fc3eb8e22cf38e11dff03619ad7 + +metadata.authors.dolio : Author +metadata.authors.dolio = Author dolio.guid "Dan Doel" + +metadata.authors.dolio.guid : GUID +metadata.authors.dolio.guid = + GUID 0xs27844d0708ccf6981d6a5a4ffecb997642a0e420030c4b3628ce21c48031de39 + +metadata.authors.emiflake : Author +metadata.authors.emiflake = Author emiflake.guid "Emily Martins" + +metadata.authors.emiflake.guid : GUID +metadata.authors.emiflake.guid = + GUID 0xsea9be535efaf182c8ab9291a67ff94f5ea5b5d2c183a9af22a618ebf7e457694 + +metadata.authors.fabianböller : Author +metadata.authors.fabianböller = Author fabianböller.guid "Fabian Böller" + +metadata.authors.fabianböller.guid : GUID +metadata.authors.fabianböller.guid = + GUID 0xs91f6643aa683ecca32cf4280fd367f26a79caa1d38c9edf9e711d77e4e25d6a4 + +metadata.authors.gerardfinol : Author +metadata.authors.gerardfinol = Author gerardfinol.guid "Gerard Finol" + +metadata.authors.gerardfinol.guid : GUID +metadata.authors.gerardfinol.guid = + GUID 0xs55c2f1ddfecfa8af6a996d710538af73e0ddd914d87384bf868245164f2bfca6 + +metadata.authors.hagl : Author +metadata.authors.hagl = Author hagl.guid "Harald Gliebe" + +metadata.authors.hagl.guid : GUID +metadata.authors.hagl.guid = + GUID 0xsaa9d6507fe8e523c297753846c4644840123408d5ebc58e8847a9f7930277119 + +metadata.authors.heathermiller : Author +metadata.authors.heathermiller = Author heathermiller.guid "Heather Miller" + +metadata.authors.heathermiller.guid : GUID +metadata.authors.heathermiller.guid = + GUID 0xs1621fe08889eb6e1ea02f989d5d3e23cfc5aae3e695d9b31a7b4af22764f7dac + +metadata.authors.iamevn : Author +metadata.authors.iamevn = Author iamevn.guid "Evan Minsk" + +metadata.authors.iamevn.guid : GUID +metadata.authors.iamevn.guid = + GUID 0xsef40f5340a6662f6ab31b21ad0dcead1cd2aae6d246dcc988052d829ab79e111 + +metadata.authors.jamessully : Author +metadata.authors.jamessully = Author jamessully.guid "James Sully" + +metadata.authors.jamessully.guid : GUID +metadata.authors.jamessully.guid = + GUID 0xs37dbf3fcf61385ccfccf1503c7120f83ed6af275d9e03691562dc85b25aaa8e4 + +metadata.authors.johanwinther : Author +metadata.authors.johanwinther = Author johanwinther.guid "Johan Winther" + +metadata.authors.johanwinther.guid : GUID +metadata.authors.johanwinther.guid = + GUID 0xs284726f325f3c4597b94e5616041500789e1f5689d317fdc7de75bf6df49f986 + +metadata.authors.jskripsky : Author +metadata.authors.jskripsky = Author jskripsky.guid "Juraj Skripsky" + +metadata.authors.jskripsky.guid : GUID +metadata.authors.jskripsky.guid = + GUID 0xs659268e71c50efacbab817fd331deea0f08bd83e339ae7ad16a4fac1611975d0 + +metadata.authors.justjoheinz : Author +metadata.authors.justjoheinz = Author justjoheinz.guid "Markus Klink" + +metadata.authors.justjoheinz.guid : GUID +metadata.authors.justjoheinz.guid = + GUID 0xs354dec58bdd018171b941941a5640db97cd4bd27172c16fa7166400dc3395b19 + +metadata.authors.katefulton : Author +metadata.authors.katefulton = Author katefulton.guid "Kate Fulton" + +metadata.authors.katefulton.guid : GUID +metadata.authors.katefulton.guid = + GUID 0xs4be8485723d46b2e0fc81259b25e81e453003b37d2f2e1d70895a0860785648b + +metadata.authors.loewenheim : Author +metadata.authors.loewenheim = Author loewenheim.guid "Sebastian Zivota" + +metadata.authors.loewenheim.guid : GUID +metadata.authors.loewenheim.guid = + GUID 0xsa562626e7aef0a3512e7bde22ba5ba4da7021f418a1c24b5d1433a5465d539fa + +metadata.authors.oleggrenrus : Author +metadata.authors.oleggrenrus = Author oleggrenrus.guid "Oleg Grenrus" + +metadata.authors.oleggrenrus.guid : GUID +metadata.authors.oleggrenrus.guid = + GUID 0xs4494856904fcd5b76313595772308a8f8c978b92736ca4da810c6946fe53b90d + +metadata.authors.pchiusano : Author +metadata.authors.pchiusano = Author pchiusano.guid "Paul Chiusano" + +metadata.authors.pchiusano.guid : GUID +metadata.authors.pchiusano.guid = + GUID 0xsbab8fb8f91d3a892c6e81972c5f79766280f5a2c76fe41cae0401da3ee248306 + +metadata.authors.pete_ts : Author +metadata.authors.pete_ts = Author pete_ts.guid "Pete Tsamouris" + +metadata.authors.pete_ts.guid : GUID +metadata.authors.pete_ts.guid = + GUID 0xsb88c22ed2076e72ea4b37763b6996f36bdda51844d32f1f8f638a21e315ce3c1 + +metadata.authors.pragdave : Author +metadata.authors.pragdave = Author pragdave.guid "Dave Thomas" + +metadata.authors.pragdave.guid : GUID +metadata.authors.pragdave.guid = + GUID 0xsfc73ee4cc7f1e68bb1d678858f6284d05d9a3d0959a092078d0e0c6348432f85 + +metadata.authors.rlmark : Author +metadata.authors.rlmark = Author rlmark.guid "Rebecca Mark" + +metadata.authors.rlmark.guid : GUID +metadata.authors.rlmark.guid = + GUID 0xs7e5286918ea1930c9c1f2a9f52b31d72d6c14556a5e0e0041ce6bf1f3eaded6c + +metadata.authors.runarorama : Author +metadata.authors.runarorama = Author runarorama.guid "Rúnar Bjarnason" + +metadata.authors.runarorama.doc : Doc +metadata.authors.runarorama.doc = + deets = + {{ + # Rúnar Óli Bjarnason + + ## Cofounder, Unison Computing + + runar.bjarnason@unison.cloud + + Twitter: [@runarorama](https://twitter.com/runarorama) + + Slack: [@Rúnar](https://unisonlanguage.slack.com/team/ULMRDML5V) + }} + {{ + {{ + docTable + [ [ Image + {{ + Rúnar + }} + {{ + https://avatars.githubusercontent.com/u/130916?s=400&u=4f5ebe83e2ec4377a3a78efa49698574dbec0d34&v=4 + }} + None + , deets + ] + ] }} + }} + +metadata.authors.runarorama.guid : GUID +metadata.authors.runarorama.guid = + GUID 0xs85c3e4d205549d6acced79c1d6db2f2bc2debd3cd4337b6ff499ac3a80b1c7a4 + +metadata.authors.simonhøjberg : Author +metadata.authors.simonhøjberg = Author simonhøjberg.guid "Simon Højberg" + +metadata.authors.simonhøjberg.guid : GUID +metadata.authors.simonhøjberg.guid = + GUID 0xsbb7feca7d29d8f71d9b9221e860f4716a83cbe344139d38ffe1c22135a6c80bc + +metadata.authors.stew : Author +metadata.authors.stew = Author stew.guid "Stew O'Connor" + +metadata.authors.stew.guid : GUID +metadata.authors.stew.guid = + GUID 0xsed13062781ab5fe4830d10a5cad9295642e848bd48be4c0e5842bd74145a42c0 + +metadata.authors.systemfw : Author +metadata.authors.systemfw = Author systemfw.guid "Fabio Labella" + +metadata.authors.systemfw.guid : GUID +metadata.authors.systemfw.guid = + GUID 0xsd494e6c5baf7a4c1cc511348d515828fadcc5c5d29bad0e24ea1a9aa938883b6 + +metadata.authors.thomasschilling : Author +metadata.authors.thomasschilling = + Author thomasschilling.guid "Thomas Schilling" + +metadata.authors.thomasschilling.guid : GUID +metadata.authors.thomasschilling.guid = + GUID 0xs3b3121bed967e91e1eb2ca6669f249b2cd948f795a414c3caf43f5b2d2647528 + +metadata.authors.unisoncomputing.guid : GUID +metadata.authors.unisoncomputing.guid = + GUID 0xse17e77da9b7a7fe7081157e2215cbce387a16c7498b43d10f6d04d17bf49b252 + +metadata.authors.universityofglasgow : Author +metadata.authors.universityofglasgow = + Author + universityofglasgow.guid + "The University Court of the University of Glasgow" + +metadata.authors.universityofglasgow.guid : GUID +metadata.authors.universityofglasgow.guid = + GUID 0xs213dbb64277ccec7d59cdc7ca0bb2d612d94aa84ab5ab1527ee0023f02c7ba4a + +metadata.authors.vanev : Author +metadata.authors.vanev = Author vanev.guid "Evan Siegel" + +metadata.authors.vanev.guid : GUID +metadata.authors.vanev.guid = + GUID 0xs47117b6467ff7795f8b15dbaa549488d5beb1328eadc1d3a56b56208def1ecc5 + +metadata.authors.zenhack : Author +metadata.authors.zenhack = Author zenhack.guid "Ian DenHardt" + +metadata.authors.zenhack.guid : GUID +metadata.authors.zenhack.guid = + GUID 0xs57fe457d5a5bee52ec8504dfa1929c6b9984e2ca1291dc87b4bcc29c8e77d104 + +metadata.CopyrightHolder.doc : Doc +metadata.CopyrightHolder.doc = + {{ + The holder of a copyright, represented by a {type GUID} and a {type Text} + name. + }} + +metadata.CopyrightHolder.guid : CopyrightHolder -> GUID +metadata.CopyrightHolder.guid = cases CopyrightHolder guid _ -> guid + +metadata.CopyrightHolder.guid.doc : Doc +metadata.CopyrightHolder.guid.doc = + {{ The {type GUID} of a {type CopyrightHolder}. }} + +metadata.CopyrightHolder.guid.modify : + (GUID ->{𝕖} GUID) -> CopyrightHolder ->{𝕖} CopyrightHolder +metadata.CopyrightHolder.guid.modify f = cases + CopyrightHolder guid name -> CopyrightHolder (f guid) name + +metadata.CopyrightHolder.guid.modify.doc : Doc +metadata.CopyrightHolder.guid.modify.doc = + {{ Modifies the GUID of a {type CopyrightHolder}. }} + +metadata.CopyrightHolder.guid.set : GUID -> CopyrightHolder -> CopyrightHolder +metadata.CopyrightHolder.guid.set guid1 = cases + CopyrightHolder _ name -> CopyrightHolder guid1 name + +metadata.CopyrightHolder.guid.set.doc : Doc +metadata.CopyrightHolder.guid.set.doc = + {{ Sets the GUID of a {type CopyrightHolder}. }} + +metadata.CopyrightHolder.name : CopyrightHolder -> Text +metadata.CopyrightHolder.name = cases CopyrightHolder _ name -> name + +metadata.CopyrightHolder.name.doc : Doc +metadata.CopyrightHolder.name.doc = {{ The name of a {type CopyrightHolder}. }} + +metadata.CopyrightHolder.name.modify : + (Text ->{𝕖} Text) -> CopyrightHolder ->{𝕖} CopyrightHolder +metadata.CopyrightHolder.name.modify f = cases + CopyrightHolder guid name -> CopyrightHolder guid (f name) + +metadata.CopyrightHolder.name.modify.doc : Doc +metadata.CopyrightHolder.name.modify.doc = + {{ Modifies the name of a {type CopyrightHolder}. }} + +metadata.CopyrightHolder.name.set : Text -> CopyrightHolder -> CopyrightHolder +metadata.CopyrightHolder.name.set name1 = cases + CopyrightHolder guid _ -> CopyrightHolder guid name1 + +metadata.CopyrightHolder.name.set.doc : Doc +metadata.CopyrightHolder.name.set.doc = + {{ Sets the name of a {type CopyrightHolder}. }} + +metadata.copyrightHolders.andriypalamarchuk : CopyrightHolder +metadata.copyrightHolders.andriypalamarchuk = + CopyrightHolder andriypalamarchuk.guid "Andriy Palamarchuk" + +metadata.copyrightHolders.anovstrup : CopyrightHolder +metadata.copyrightHolders.anovstrup = + CopyrightHolder anovstrup.guid "Aaron Novstrup" + +metadata.copyrightHolders.atacratic : CopyrightHolder +metadata.copyrightHolders.atacratic = + CopyrightHolder atacratic.guid "Chris Gibbs" + +metadata.copyrightHolders.ceedubs : CopyrightHolder +metadata.copyrightHolders.ceedubs = CopyrightHolder ceedubs.guid "Cody Allen" + +metadata.copyrightHolders.chiroptical : CopyrightHolder +metadata.copyrightHolders.chiroptical = + CopyrightHolder chiroptical.guid "Barry Moore" + +metadata.copyrightHolders.chrispenner : CopyrightHolder +metadata.copyrightHolders.chrispenner = + CopyrightHolder chrispenner.guid "Chris Penner" + +metadata.copyrightHolders.daanleijen : CopyrightHolder +metadata.copyrightHolders.daanleijen = + CopyrightHolder daanleijen.guid "Daan Leijen" + +metadata.copyrightHolders.dariooddenino : CopyrightHolder +metadata.copyrightHolders.dariooddenino = + CopyrightHolder dariooddenino.guid "Dario Oddenino" + +metadata.copyrightHolders.emiflake : CopyrightHolder +metadata.copyrightHolders.emiflake = + CopyrightHolder emiflake.guid "Emily Martins" + +metadata.copyrightHolders.fabianböller : CopyrightHolder +metadata.copyrightHolders.fabianböller = + CopyrightHolder fabianböller.guid "Fabian Böller" + +metadata.copyrightHolders.gerardfinol : CopyrightHolder +metadata.copyrightHolders.gerardfinol = + CopyrightHolder gerardfinol.guid "Gerard Finol" + +metadata.copyrightHolders.hagl : CopyrightHolder +metadata.copyrightHolders.hagl = CopyrightHolder hagl.guid "Harald Gliebe" + +metadata.copyrightHolders.iamevn : CopyrightHolder +metadata.copyrightHolders.iamevn = CopyrightHolder iamevn.guid "Evan Minsk" + +metadata.copyrightHolders.jamessully : CopyrightHolder +metadata.copyrightHolders.jamessully = + CopyrightHolder jamessully.guid "James Sully" + +metadata.copyrightHolders.johanwinther : CopyrightHolder +metadata.copyrightHolders.johanwinther = + CopyrightHolder johanwinther.guid "Johan Winther" + +metadata.copyrightHolders.jskripsky : CopyrightHolder +metadata.copyrightHolders.jskripsky = + CopyrightHolder jskripsky.guid "Juraj Skripsky" + +metadata.copyrightHolders.justjoheinz : CopyrightHolder +metadata.copyrightHolders.justjoheinz = + CopyrightHolder justjoheinz.guid "Markus Klink" + +metadata.copyrightHolders.katefulton : CopyrightHolder +metadata.copyrightHolders.katefulton = + CopyrightHolder katefulton.guid "Kate Fulton" + +metadata.copyrightHolders.pete_ts : CopyrightHolder +metadata.copyrightHolders.pete_ts = + CopyrightHolder pete_ts.guid "Pete Tsamouris" + +metadata.copyrightHolders.pragdave : CopyrightHolder +metadata.copyrightHolders.pragdave = + CopyrightHolder pragdave.guid "Dave Thomas" + +metadata.copyrightHolders.stew : CopyrightHolder +metadata.copyrightHolders.stew = CopyrightHolder stew.guid "Stew O'Connor" + +metadata.copyrightHolders.systemfw : CopyrightHolder +metadata.copyrightHolders.systemfw = + CopyrightHolder systemfw.guid "Fabio Labella" + +metadata.copyrightHolders.thomasschilling : CopyrightHolder +metadata.copyrightHolders.thomasschilling = + CopyrightHolder thomasschilling.guid "Thomas Schilling" + +metadata.copyrightHolders.unisoncomputing : CopyrightHolder +metadata.copyrightHolders.unisoncomputing = + CopyrightHolder unisoncomputing.guid "Unison Computing, public benefit corp" + +metadata.copyrightHolders.universityofglasgow : CopyrightHolder +metadata.copyrightHolders.universityofglasgow = + CopyrightHolder + universityofglasgow.guid + "The University Court of the University of Glasgow" + +metadata.copyrightHolders.vanev : CopyrightHolder +metadata.copyrightHolders.vanev = CopyrightHolder vanev.guid "Evan Siegel" + +metadata.copyrightHolders.zenhack : CopyrightHolder +metadata.copyrightHolders.zenhack = CopyrightHolder zenhack.guid "Ian DenHardt" + +metadata.isPropagated : IsPropagated +metadata.isPropagated = IsPropagated + +metadata.IsPropagated.doc : Doc +metadata.IsPropagated.doc = + {{ + This type is marker metadata for definitions in the codebase that where + updated due to being a transitive dependent of a replacement in a UCM patch. + + See + [Upgrading Libraries](https://www.unison-lang.org/learn/tooling/codebase-organization/#upgrading-libraries-to-the-latest-version) + for more details. + }} + +metadata.isTest : IsTest +metadata.isTest = IsTest + +metadata.License.copyrightHolders : License -> [CopyrightHolder] +metadata.License.copyrightHolders = cases + License copyrightHolders _ _ -> copyrightHolders + +metadata.License.copyrightHolders.doc : Doc +metadata.License.copyrightHolders.doc = + {{ The list of {type CopyrightHolder}s of a {type License}. }} + +metadata.License.copyrightHolders.modify : + ([CopyrightHolder] ->{𝕖} [CopyrightHolder]) -> License ->{𝕖} License +metadata.License.copyrightHolders.modify f = cases + License copyrightHolders years licenseType -> + License (f copyrightHolders) years licenseType + +metadata.License.copyrightHolders.modify.doc : Doc +metadata.License.copyrightHolders.modify.doc = + {{ Modifies the list of {type CopyrightHolder}s of a {type License}. }} + +metadata.License.copyrightHolders.set : [CopyrightHolder] -> License -> License +metadata.License.copyrightHolders.set copyrightHolders1 = cases + License _ years licenseType -> License copyrightHolders1 years licenseType + +metadata.License.copyrightHolders.set.doc : Doc +metadata.License.copyrightHolders.set.doc = + {{ Sets the list of {type CopyrightHolder}s of a {type License}. }} + +metadata.License.doc : Doc +metadata.License.doc = + {{ + A {type License} consists of a {type List} of {type CopyrightHolder}s, a list + of {type Year}s, and a {type LicenseType} describing the license. + + # Example + + The license of this document: + + @source{unisoncomputing2022} + }} + +metadata.License.licenseType : License -> LicenseType +metadata.License.licenseType = cases License _ _ licenseType -> licenseType + +metadata.License.licenseType.doc : Doc +metadata.License.licenseType.doc = + {{ The {type LicenseType} of a {type License}. }} + +metadata.License.licenseType.modify : + (LicenseType ->{𝕖} LicenseType) -> License ->{𝕖} License +metadata.License.licenseType.modify f = cases + License copyrightHolders years licenseType -> + License copyrightHolders years (f licenseType) + +metadata.License.licenseType.modify.doc : Doc +metadata.License.licenseType.modify.doc = + {{ Modifies the license type of a {type License}. }} + +metadata.License.licenseType.set : LicenseType -> License -> License +metadata.License.licenseType.set licenseType1 = cases + License copyrightHolders years _ -> + License copyrightHolders years licenseType1 + +metadata.License.licenseType.set.doc : Doc +metadata.License.licenseType.set.doc = + {{ Sets the license type of a {type License}. }} + +metadata.License.toDoc : License -> Doc +metadata.License.toDoc = cases + License authors years (LicenseType license) -> + use List map + use Text ++ join + authorsDoc = docWord (join ", " (map CopyrightHolder.name authors) ++ ".") + yearsDoc = docWord (join ", " (map Year.toText years)) + copyrightStatement = Doc.Join [{{ Copyright © }}, yearsDoc, authorsDoc] + {{ + {{ copyrightStatement }} + + {{ license }} + }} + +metadata.License.toDoc.doc : Doc +metadata.License.toDoc.doc = + {{ + Converts a {type License} to a {type Doc} containing the copyright statement + and license text. + + # Example + + ``` + License.toDoc unisoncomputing2022 + ``` + }} + +metadata.License.years : License -> [Year] +metadata.License.years = cases License _ years _ -> years + +metadata.License.years.doc : Doc +metadata.License.years.doc = + {{ The years in which the license was issued or renewed. }} + +metadata.License.years.modify : ([Year] ->{𝕖} [Year]) -> License ->{𝕖} License +metadata.License.years.modify f = cases + License copyrightHolders years licenseType -> + License copyrightHolders (f years) licenseType + +metadata.License.years.modify.doc : Doc +metadata.License.years.modify.doc = + {{ Modifies the years of a {type License}. }} + +metadata.License.years.set : [Year] -> License -> License +metadata.License.years.set years1 = cases + License copyrightHolders _ licenseType -> + License copyrightHolders years1 licenseType + +metadata.License.years.set.doc : Doc +metadata.License.years.set.doc = + {{ + Sets the years component of a {type License}. + + # Example + + ``` + years.set [Year 2023] unisoncomputing2022 + ``` + }} + +metadata.licenses.anovstrup.bsd2_2020 : License +metadata.licenses.anovstrup.bsd2_2020 = + License [copyrightHolders.anovstrup] [Year 2020] bsd2 + +metadata.licenses.anovstrup.bsd2_2020.doc : Doc +metadata.licenses.anovstrup.bsd2_2020.doc = License.toDoc anovstrup.bsd2_2020 + +metadata.licenses.anovstrup.bsd3_2020 : License +metadata.licenses.anovstrup.bsd3_2020 = + License [copyrightHolders.anovstrup] [Year 2020] bsd3 + +metadata.licenses.anovstrup.bsd3_2020.doc : Doc +metadata.licenses.anovstrup.bsd3_2020.doc = License.toDoc anovstrup.bsd3_2020 + +metadata.licenses.anovstrup.mit_2020 : License +metadata.licenses.anovstrup.mit_2020 = + License [copyrightHolders.anovstrup] [Year 2020] mit + +metadata.licenses.anovstrup.mit_2020.doc : Doc +metadata.licenses.anovstrup.mit_2020.doc = License.toDoc anovstrup.mit_2020 + +metadata.licenses.atacratic.mit_2020 : License +metadata.licenses.atacratic.mit_2020 = + License [copyrightHolders.atacratic] [Year 2020] mit + +metadata.licenses.ceedubs2020 : License +metadata.licenses.ceedubs2020 = + License [copyrightHolders.ceedubs] [Year 2020] mit + +metadata.licenses.ceedubs2020.doc : Doc +metadata.licenses.ceedubs2020.doc = License.toDoc ceedubs2020 + +metadata.licenses.chiroptical.mit_2022 : License +metadata.licenses.chiroptical.mit_2022 = + License [copyrightHolders.chiroptical] [Year 2022] mit + +metadata.licenses.chiroptical.mit_2022.doc : Doc +metadata.licenses.chiroptical.mit_2022.doc = License.toDoc mit_2022 + +metadata.licenses.chrispenner : License +metadata.licenses.chrispenner = + License [copyrightHolders.chrispenner] [Year 2021] mit + +metadata.licenses.chrispenner.doc : Doc +metadata.licenses.chrispenner.doc = License.toDoc licenses.chrispenner + +metadata.licenses.dariooddenino.dariooddenino2020 : License +metadata.licenses.dariooddenino.dariooddenino2020 = + License [copyrightHolders.dariooddenino] [Year 2020] mit + +metadata.licenses.dariooddenino.dariooddenino2020.doc : Doc +metadata.licenses.dariooddenino.dariooddenino2020.doc = + License.toDoc dariooddenino2020 + +metadata.licenses.datamapinternal : License +metadata.licenses.datamapinternal = + License + [ copyrightHolders.daanleijen + , copyrightHolders.universityofglasgow + , copyrightHolders.andriypalamarchuk + , unisoncomputing + ] + [Year 2002, Year 2004, Year 2008, Year 2020] + mit + +metadata.licenses.datamapinternal.doc : Doc +metadata.licenses.datamapinternal.doc = License.toDoc datamapinternal + +metadata.licenses.emiflake_mit_2020 : License +metadata.licenses.emiflake_mit_2020 = + License [copyrightHolders.emiflake] [Year 2020] mit + +metadata.licenses.emiflake_mit_2020.doc : Doc +metadata.licenses.emiflake_mit_2020.doc = License.toDoc emiflake_mit_2020 + +metadata.licenses.fböller2020 : License +metadata.licenses.fböller2020 = + License [copyrightHolders.fabianböller] [Year 2020] mit + +metadata.licenses.fböller2020.doc : Doc +metadata.licenses.fböller2020.doc = License.toDoc fböller2020 + +metadata.licenses.hagl2021 : License +metadata.licenses.hagl2021 = License [copyrightHolders.hagl] [Year 2021] mit + +metadata.licenses.hagl2021.doc : Doc +metadata.licenses.hagl2021.doc = License.toDoc hagl2021 + +metadata.licenses.iamevn : License +metadata.licenses.iamevn = + License [copyrightHolders.iamevn] [Year 2021, Year 2022] mit + +metadata.licenses.iamevn.doc : Doc +metadata.licenses.iamevn.doc = License.toDoc licenses.iamevn + +metadata.licenses.jamessully : License +metadata.licenses.jamessully = + License [copyrightHolders.jamessully] [Year 2021] mit + +metadata.licenses.jamessully.doc : Doc +metadata.licenses.jamessully.doc = License.toDoc licenses.jamessully + +metadata.licenses.johanwinther2023 : License +metadata.licenses.johanwinther2023 = + License [copyrightHolders.johanwinther] [Year 2023] mit + +metadata.licenses.jskripsky2023 : License +metadata.licenses.jskripsky2023 = + License [copyrightHolders.jskripsky] [Year 2023] mit + +metadata.licenses.justjoheinz : License +metadata.licenses.justjoheinz = + License [copyrightHolders.justjoheinz] [Year 2023] mit + +metadata.licenses.katefulton2021 : License +metadata.licenses.katefulton2021 = + License [copyrightHolders.katefulton] [Year 2021] bsd3 + +metadata.licenses.katefulton2021.doc : Doc +metadata.licenses.katefulton2021.doc = License.toDoc katefulton2021 + +metadata.licenses.pete_ts.bsd2_2020 : License +metadata.licenses.pete_ts.bsd2_2020 = + License [copyrightHolders.pete_ts] [Year 2020] bsd2 + +metadata.licenses.pete_ts.bsd2_2020.doc : Doc +metadata.licenses.pete_ts.bsd2_2020.doc = License.toDoc pete_ts.bsd2_2020 + +metadata.licenses.pete_ts.bsd3_2020 : License +metadata.licenses.pete_ts.bsd3_2020 = + License [copyrightHolders.pete_ts] [Year 2020] bsd3 + +metadata.licenses.pete_ts.bsd3_2020.doc : Doc +metadata.licenses.pete_ts.bsd3_2020.doc = License.toDoc pete_ts.bsd3_2020 + +metadata.licenses.pete_ts.mit_2020 : License +metadata.licenses.pete_ts.mit_2020 = + License [copyrightHolders.pete_ts] [Year 2020] mit + +metadata.licenses.pete_ts.mit_2020.doc : Doc +metadata.licenses.pete_ts.mit_2020.doc = License.toDoc pete_ts.mit_2020 + +metadata.licenses.unisoncomputing2020 : License +metadata.licenses.unisoncomputing2020 = + License [unisoncomputing] [Year 2020] mit + +metadata.licenses.unisoncomputing2020.doc : Doc +metadata.licenses.unisoncomputing2020.doc = License.toDoc unisoncomputing2020 + +metadata.licenses.unisoncomputing2021 : License +metadata.licenses.unisoncomputing2021 = + License [unisoncomputing] [Year 2021] mit + +metadata.licenses.unisoncomputing2021.doc : Doc +metadata.licenses.unisoncomputing2021.doc = License.toDoc unisoncomputing2021 + +metadata.licenses.unisoncomputing2022 : License +metadata.licenses.unisoncomputing2022 = + License [unisoncomputing] [Year 2022] mit + +metadata.licenses.unisoncomputing2022.doc : Doc +metadata.licenses.unisoncomputing2022.doc = License.toDoc unisoncomputing2022 + +metadata.licenses.zenhack2020 : License +metadata.licenses.zenhack2020 = + License [copyrightHolders.zenhack] [Year 2020] mit + +metadata.licenses.zenhack2020.doc : Doc +metadata.licenses.zenhack2020.doc = License.toDoc zenhack2020 + +metadata.LicenseType.doc : Doc +metadata.LicenseType.doc = + {{ Represents a type of license, such as {mit} or {bsd3}. }} + +metadata.licenseTypes.allRightsReserved : LicenseType +metadata.licenseTypes.allRightsReserved = + LicenseType {{ All rights reserved. }} + +metadata.licenseTypes.allRightsReserved.doc : Doc +metadata.licenseTypes.allRightsReserved.doc = + {{ + Used to indicate that the copyright holder reserves all rights to their work + and grants no additional permissions to users. + }} + +metadata.licenseTypes.bsd2 : LicenseType +metadata.licenseTypes.bsd2 = + LicenseType + {{ + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this + + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, + + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + }} + +metadata.licenseTypes.bsd2.doc : Doc +metadata.licenseTypes.bsd2.doc = + {{ + The [BSD 2-Clause](https://opensource.org/licenses/BSD-2-Clause) license. + + # License text + + {{ (LicenseType d) = bsd2 d }} + }} + +metadata.licenseTypes.bsd3 : LicenseType +metadata.licenseTypes.bsd3 = + LicenseType + {{ + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this + + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, + + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors + + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + }} + +metadata.licenseTypes.bsd3.doc : Doc +metadata.licenseTypes.bsd3.doc = + {{ + The [BSD 3-Clause](https://opensource.org/licenses/BSD-3-Clause) license. + + # License text + + {{ (LicenseType d) = bsd3 d }} + }} + +metadata.licenseTypes.cc0 : LicenseType +metadata.licenseTypes.cc0 = LicenseType cc0.text + +metadata.licenseTypes.cc0.doc : Doc +metadata.licenseTypes.cc0.doc = + {{ + The + [Creative Commons CC0 license](https://creativecommons.org/publicdomain/zero/1.0/). + Here's the summary, copied verbatim from the website: + + {{ + docCallout + None + {{ + The person who associated a work with this deed has dedicated the work to + the public domain by waiving all of his or her rights to the work worldwide + under copyright law, including all related and neighboring rights, to the + extent allowed by law. + + You can copy, modify, distribute and perform the work, even for commercial + purposes, all without asking permission. See Other Information below. + + **Other information** + + * In no way are the patent or trademark rights of any person affected by + CC0, nor are the rights that other persons may have in the work or in how + the work is used, such as publicity or privacy rights. + * Unless expressly stated otherwise, the person who associated a work with + this deed makes no warranties about the work, and disclaims liability for + all uses of the work, to the fullest extent permitted by applicable law. + * When using or citing the work, you should not imply endorsement by the + author or the affirmer. + }} }} + + See the full license text for details. + }} + +metadata.licenseTypes.cc0.text : Doc +metadata.licenseTypes.cc0.text = + {{ + **Statement of Purpose** + + The laws of most jurisdictions throughout the world automatically confer + exclusive Copyright and Related Rights (defined below) upon the creator and + subsequent owner(s) (each and all, an "owner") of an original work of + authorship and/or a database (each, a "Work"). + + Certain owners wish to permanently relinquish those rights to a Work for the + purpose of contributing to a commons of creative, cultural and scientific + works ("Commons") that the public can reliably and without fear of later + claims of infringement build upon, modify, incorporate in other works, reuse + and redistribute as freely as possible in any form whatsoever and for any + purposes, including without limitation commercial purposes. These owners may + contribute to the Commons to promote the ideal of a free culture and the + further production of creative, cultural and scientific works, or to gain + reputation or greater distribution for their Work in part through the use and + efforts of others. + + For these and/or other purposes and motivations, and without any expectation + of additional consideration or compensation, the person associating CC0 with + a Work (the "Affirmer"), to the extent that he or she is an owner of + Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to + the Work and publicly distribute the Work under its terms, with knowledge of + his or her Copyright and Related Rights in the Work and the meaning and + intended legal effect of CC0 on those rights. + + **1. Copyright and Related Rights.** A Work made available under CC0 may be + protected by copyright and related or neighboring rights ("Copyright and + Related Rights"). Copyright and Related Rights include, but are not limited + to, the following: + + 1. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + 2. moral rights retained by the original author(s) and/or performer(s); + 3. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + 4. rights protecting against unfair competition in regards to a Work, subject + to the limitations in paragraph 4(a), below; + 5. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + 6. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + 7. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations + thereof. + + **2. Waiver.** To the greatest extent permitted by, but not in contravention + of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably + and unconditionally waives, abandons, and surrenders all of Affirmer's + Copyright and Related Rights and associated claims and causes of action, + whether now known or unknown (including existing as well as future claims and + causes of action), in the Work (i) in all territories worldwide, (ii) for the + maximum duration provided by applicable law or treaty (including future time + extensions), (iii) in any current or future medium and for any number of + copies, and (iv) for any purpose whatsoever, including without limitation + commercial, advertising or promotional purposes (the "Waiver"). Affirmer + makes the Waiver for the benefit of each member of the public at large and to + the detriment of Affirmer's heirs and successors, fully intending that such + Waiver shall not be subject to revocation, rescission, cancellation, + termination, or any other legal or equitable action to disrupt the quiet + enjoyment of the Work by the public as contemplated by Affirmer's express + Statement of Purpose. + + **3. Public License Fallback.** Should any part of the Waiver for any reason + be judged legally invalid or ineffective under applicable law, then the + Waiver shall be preserved to the maximum extent permitted taking into account + Affirmer's express Statement of Purpose. In addition, to the extent the + Waiver is so judged Affirmer hereby grants to each affected person a + royalty-free, non transferable, non sublicensable, non exclusive, irrevocable + and unconditional license to exercise Affirmer's Copyright and Related Rights + in the Work (i) in all territories worldwide, (ii) for the maximum duration + provided by applicable law or treaty (including future time extensions), + (iii) in any current or future medium and for any number of copies, and (iv) + for any purpose whatsoever, including without limitation commercial, + advertising or promotional purposes (the "License"). The License shall be + deemed effective as of the date CC0 was applied by Affirmer to the Work. + Should any part of the License for any reason be judged legally invalid or + ineffective under applicable law, such partial invalidity or ineffectiveness + shall not invalidate the remainder of the License, and in such case Affirmer + hereby affirms that he or she will not (i) exercise any of his or her + remaining Copyright and Related Rights in the Work or (ii) assert any + associated claims and causes of action with respect to the Work, in either + case contrary to Affirmer's express Statement of Purpose. + + **4. Limitations and Disclaimers.** + + 1. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + 2. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or + not discoverable, all to the greatest extent permissible under applicable + law. + 3. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. Further, + Affirmer disclaims responsibility for obtaining any necessary consents, + permissions or other rights required for any use of the Work. + 4. Affirmer understands and acknowledges that Creative Commons is not a party + to this document and has no duty or obligation with respect to this CC0 or + use of the Work. + }} + +metadata.licenseTypes.mit : LicenseType +metadata.licenseTypes.mit = + LicenseType + {{ + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + }} + +metadata.licenseTypes.mit.doc : Doc +metadata.licenseTypes.mit.doc = + {{ + The [MIT](https://opensource.org/licenses/MIT) license. + + # License text + + {{ (LicenseType d) = mit d }} + }} + +metadata.unassignedGuids.guid3 : GUID +metadata.unassignedGuids.guid3 = + GUID 0xs70b404bb278d975f2afe9f211443d319e22871d844fbca00f5936edc9bc0c5a5 + +metadata.unassignedGuids.guid4 : GUID +metadata.unassignedGuids.guid4 = + GUID 0xs6b8aea1fdd5dbe62894bc886f491d192e50ab07cc750a9020d3718e6ecab9648 + +metadata.unassignedGuids.guid5 : GUID +metadata.unassignedGuids.guid5 = + GUID 0xsc20778e872a839792b2c034eae3e0cd50fdb7a9e3adfaaf91725cd3f75c94ce9 + +metadata.unassignedGuids.guid6 : GUID +metadata.unassignedGuids.guid6 = + GUID 0xsbc587e4013cc095bb2c4959aeb7f4f605661a7551ba3082690cf852359b17380 + +metadata.unassignedGuids.guid8 : GUID +metadata.unassignedGuids.guid8 = + GUID 0xsba4a468ef31d423de16c8f989d0133bf6781830ee0122d16fa1594c6f2afa08a + +metadata.Year.doc : Doc +metadata.Year.doc = {{ Represents a year in the Gregorian calendar. }} + +metadata.Year.toText : Year -> Text +metadata.Year.toText = cases Year n -> Nat.toText n + +metadata.Year.toText.doc : Doc +metadata.Year.toText.doc = + {{ + Converts a {type Year} to a {type Text}. + + # Example + + ``` + Year.toText (Year 2022) + ``` + }} + +mutable.Array.doc : Doc +mutable.Array.doc = + {{ + A {type mutable.Array} is a fixed size, flat array. It is able to store any + type, because the underlying array stores pointers to heap objects, so any + type can be represented uniformly, although this is less efficient for fixed + size types like {type Nat}. + + The values also store an offset and length relative to the underlying array, + allowing fast slicing operations. These slicing operations are unsafe, + however, because they share the underlying mutable storage with the original + array. + }} + +mutable.Array.drop! : Nat -> mutable.Array g a ->{Exception} mutable.Array g a +mutable.Array.drop! o = cases + MArr off len arr| o Nat.<= len -> MArr (off Nat.+ o) (len Nat.- o) arr + _ -> ArrayFailure.raise "mutable.Array.drop!: not enough elements" o + +mutable.Array.drop!.doc : Doc +mutable.Array.drop!.doc = + {{ + {Array.drop!} returns a new {type mutable.Array} with the specified number of + elements removed from the front. + + The underlying storage of the new array is shared with the old array, so + mutations of each can affect the other. + }} + +mutable.Array.dropEnd! : + Nat -> mutable.Array g a ->{Exception} mutable.Array g a +mutable.Array.dropEnd! o = cases + MArr off len arr| o Nat.<= len -> MArr off (len Nat.- o) arr + _ -> ArrayFailure.raise "mutable.Array.dropEnd!: not enough elements" o + +mutable.Array.dropEnd!.doc : Doc +mutable.Array.dropEnd!.doc = + {{ + {Array.dropEnd!} returns a new {type mutable.Array} with the specified number + of elements removed from the end. + + The underlying storage of the new array is shared with the old array, so + mutations of each can affect the other. + }} + +mutable.Array.fill : Nat -> a ->{Scope s} mutable.Array {Scope s} a +mutable.Array.fill = flip mutable.Array.of + +mutable.Array.fill.doc : Doc +mutable.Array.fill.doc = + use mutable.Array fill + {{ + `` fill sz x `` creates a new {type mutable.Array} of size `sz`, filled with + the value `x`. + + # Example + + ``` + Scope.run do Array.toList (Array.freeze (fill 4 0)) + ``` + + # See also + + * {mutable.Array.of} for a version of this that takes the arguments in the + opposite order. + * {IO.arrayOf} for a version of this that takes the arguments in the + opposite order and works in the {type IO} ability. + }} + +mutable.Array.freeze : mutable.Array g a ->{g} data.Array a +mutable.Array.freeze = cases + MArr off len arr -> Arr 0 len (Array.Raw.freeze arr off len) + +mutable.Array.freeze.doc : Doc +mutable.Array.freeze.doc = + {{ + Freezes an array, yielding an immutable array with the same contents. + + This function makes a copy of the relevant portion of the underlying mutable + array, so it is safe to continue mutating the original array after freezing. + }} + +mutable.Array.freeze! : mutable.Array g a ->{g} data.Array a +mutable.Array.freeze! = cases + MArr off len arr -> Arr off len (Array.Raw.freeze! arr) + +mutable.Array.freeze!.doc : Doc +mutable.Array.freeze!.doc = + {{ + Freezes an array, yielding an immutable array with the same contents. + + Although this operation returns a new value, it causes the original array + (and any other arrays sharing the same underlying storage) to be marked as + immutable. Any attempt to write to these values after this operation occurs + will cause exceptions to be thrown. + }} + +mutable.Array.MArr.doc : Doc +mutable.Array.MArr.doc = + {{ + The constructor for {type mutable.Array} values. This constructor is not + meant to be used directly. See {type mutable.Array} for how to create + {type mutable.Array} values. + }} + +mutable.Array.of : x -> Nat ->{Scope s} mutable.Array {Scope s} x +mutable.Array.of x sz = Scope.arrayOf x sz + +mutable.Array.of.doc : Doc +mutable.Array.of.doc = + {{ + `` mutable.Array.of x sz `` constructs a new mutable array of size `sz` + filled with the value `x`. + }} + +-- builtin mutable.Array.Raw.copyTo! : +-- mutable.Array.Raw g a +-- -> Nat +-- -> mutable.Array.Raw g a +-- -> Nat +-- -> Nat +-- ->{g, Exception} () + +mutable.Array.Raw.doc : Doc +mutable.Array.Raw.doc = + {{ + A {type mutable.Array.Raw} is a fixed size, flat array. It is able to store + any type, because it effectively stores pointers to heap objects, so any type + can be represented uniformly, although this is less efficient for fixed size + types like {type Nat}. + + This type is the raw builtin, offering a fixed amount of storage, and not + supporting any resizing operations. The representation does store its length, + although in the case of a wrapper supporting fast slicing, a separate length + will likely need to be tracked (otherwise slicing off the end cannot be + supported). + }} + +-- builtin mutable.Array.Raw.freeze : +-- mutable.Array.Raw g a -> Nat -> Nat ->{g} data.Array.Raw a + +mutable.Array.Raw.freeze.doc : Doc +mutable.Array.Raw.freeze.doc = + {{ + Creates an immutable copy of a portion of a mutable array. The mutable array + is still safe to be modified afterwards. + }} + +-- builtin mutable.Array.Raw.freeze! : +-- mutable.Array.Raw g a ->{g} data.Array.Raw a + +mutable.Array.Raw.freeze!.doc : Doc +mutable.Array.Raw.freeze!.doc = + {{ + Freezes a mutable array in-place. Although an immutable array value is + returned from this function, the original mutable array is marked frozen as + well, and may no longer be modified. This function is primarily for creating + immutable arrays: to do so, create a mutable array, set its contents, then + freeze to get the immutable array. + }} + +-- builtin mutable.Array.Raw.read : +-- mutable.Array.Raw g a -> Nat ->{g, Exception} a + +mutable.Array.Raw.read.doc : Doc +mutable.Array.Raw.read.doc = + {{ Reads a value from the given raw array at the given index. }} + +-- builtin mutable.Array.Raw.size : mutable.Array.Raw g a -> Nat + +mutable.Array.Raw.size.doc : Doc +mutable.Array.Raw.size.doc = {{ Gets the size of the given raw array. }} + +-- builtin mutable.Array.Raw.write : +-- mutable.Array.Raw g a -> Nat -> a ->{g, Exception} () + +mutable.Array.Raw.write.doc : Doc +mutable.Array.Raw.write.doc = + {{ Writes a value to the given raw array at the given index. }} + +mutable.Array.read : mutable.Array g a -> Nat ->{g, Exception} a +mutable.Array.read arr i = + match arr with + MArr off len arr| i Nat.< len -> mutable.Array.Raw.read arr (off Nat.+ i) + _ -> ArrayFailure.raise "mutable.Array.read: index out of bounds" i + +mutable.Array.read.doc : Doc +mutable.Array.read.doc = + {{ + Returns the value at the specified, 0-based position in the array. + + An exception is thrown if the index is out of bounds. + + ``` + catch do + Scope.run do + dst = Scope.arrayOf "hello" 5 + mutable.Array.read dst 3 + ``` + }} + +mutable.Array.size : mutable.Array g a -> Nat +mutable.Array.size = cases MArr _ length _ -> length + +mutable.Array.slice! : + Nat -> Nat -> mutable.Array g a ->{Exception} mutable.Array g a +mutable.Array.slice! o l = cases + MArr off len arr| o Nat.+ l Nat.<= len -> MArr (off Nat.+ o) l arr + _ -> + ArrayFailure.raise "mutable.Array.slice!: not enough elements" (o Nat.+ l) + +mutable.Array.slice!.doc : Doc +mutable.Array.slice!.doc = + {{ + {Array.slice!} returns a new {type mutable.Array} representing a portion of + the original. + + The first argument specifies how many values to drop from the beginning of + the original. The second argument specifies how many values to retain. The + original array must have a length at least as long as the offset plus the new + length. + + The underlying storage of the new array is shared with the old array, so + mutations of each can affect the other. + }} + +mutable.Array.write : mutable.Array g a -> Nat -> a ->{g, Exception} () +mutable.Array.write arr i v = + match arr with + MArr off len arr -> + if i Nat.< len then Raw.write arr (off Nat.+ i) v + else ArrayFailure.raise "mutable.Array.write: index out of bound" i + +mutable.Array.write.doc : Doc +mutable.Array.write.doc = + {{ + Sets the value at the specified, 0-based position in the array. + + An exception is thrown if the index is out of bounds. + + ``` + catch do + Scope.run do + arr = Scope.arrayOf "hello" 5 + Array.write arr 3 "goodbye" + mutable.Array.read arr 3 + ``` + }} + +mutable.ByteArray.doc : Doc +mutable.ByteArray.doc = + {{ + A {type mutable.ByteArray} is a fixed size, flat array of bytes. These bytes + are stored directly, allowing for more space-efficient storage than would be + possible using {type mutable.Array}, but the type to be stored must be + encoded somehow as raw bytes. + + This array type tracks an offset and length relative to the underlying array, + allowing for fast slicing operations. These operations are unsafe, however, + because they share the underlying mutable storage with the original array. + }} + +mutable.ByteArray.drop! : + Nat -> mutable.ByteArray g ->{Exception} mutable.ByteArray g +mutable.ByteArray.drop! o = cases + MBArr off len arr| o Nat.<= len -> MBArr (off Nat.+ o) (len Nat.- o) arr + _ -> ArrayFailure.raise "mutable.ByteArray.drop!: not enough elements" o + +mutable.ByteArray.drop!.doc : Doc +mutable.ByteArray.drop!.doc = + {{ + {ByteArray.drop!} returns a new {type mutable.ByteArray} with the specified + number of bytes removed from the front. + + The underlying storage of the new array is shared with the old array, so + mutations of each can affect the other. + }} + +mutable.ByteArray.dropEnd! : + Nat -> mutable.ByteArray g ->{Exception} mutable.ByteArray g +mutable.ByteArray.dropEnd! o = cases + MBArr off len arr| o Nat.<= len -> MBArr off (len Nat.- o) arr + _ -> ArrayFailure.raise "mutable.ByteArray.dropEnd!: not enough elements" o + +mutable.ByteArray.dropEnd!.doc : Doc +mutable.ByteArray.dropEnd!.doc = + {{ + {ByteArray.dropEnd!} returns a new {type mutable.ByteArray} with the + specified number of bytes removed from the end. + + The underlying storage of the new array is shared with the old array, so + mutations of each can affect the other. + }} + +mutable.ByteArray.freeze : mutable.ByteArray g ->{g} data.ByteArray +mutable.ByteArray.freeze = cases + MBArr off len arr -> BArr 0 len (ByteArray.Raw.freeze arr off len) + +mutable.ByteArray.freeze.doc : Doc +mutable.ByteArray.freeze.doc = + use data.ByteArray read8 + use mutable.ByteArray write8 + {{ + Freezes a {type mutable.ByteArray} into an immutable {type data.ByteArray} + with the same contents. + + The resulting {type data.ByteArray} is a copy, so it is safe to modify the + original {type mutable.ByteArray} after freezing it. + + # Example + + ``` + catch do + a = Scope.run do + ma = Scope.byteArray 3 + write8 ma 0 2 + write8 ma 1 4 + write8 ma 2 8 + ByteArray.freeze ma + (read8 a 0, read8 a 1, read8 a 2) + ``` + + # See also + + Contrast with {ByteArray.freeze!} which does not make a copy and renders + the original {type mutable.ByteArray} unmodifiable as well. + }} + +mutable.ByteArray.freeze! : mutable.ByteArray g ->{g} data.ByteArray +mutable.ByteArray.freeze! = cases + MBArr off len arr -> BArr off len (ByteArray.Raw.freeze! arr) + +mutable.ByteArray.freeze!.doc : Doc +mutable.ByteArray.freeze!.doc = + use ByteArray freeze! + use data.ByteArray read8 + use mutable.ByteArray write8 + {{ + Freezes a {type mutable.ByteArray} into an immutable {type data.ByteArray} + with the same contents by sharing the original array's storage. + + The resulting {type data.ByteArray} is a view of the original + {type mutable.ByteArray}, so it is not safe to modify the original + {type mutable.ByteArray} after freezing it with {freeze!}. Doing so will + result in a runtime exception being thrown. + + This is intended mainly as the mechanism for creating new immutable arrays, + like so: + + ``` + catch do + a = Scope.run do + ma = Scope.byteArray 3 + write8 ma 0 2 + write8 ma 1 4 + write8 ma 2 8 + freeze! ma + (read8 a 0, read8 a 1, read8 a 2) + ``` + + # See also + + Contrast with {ByteArray.freeze} which makes a copy and leaves the original + {type mutable.ByteArray} modifiable. + }} + +-- builtin mutable.ByteArray.Raw.copyTo! : +-- mutable.ByteArray.Raw g +-- -> Nat +-- -> mutable.ByteArray.Raw g +-- -> Nat +-- -> Nat +-- ->{g, Exception} () + +mutable.ByteArray.Raw.copyTo!.doc : Doc +mutable.ByteArray.Raw.copyTo!.doc = + {{ + `` mutable.ByteArray.Raw.copyTo! dst doff src soff len `` copies `len` bytes + from `src` to `dst`. `doff` and `soff` are byte offsets into the + corresponding arrays. + + Note that the destination array/offset comes first. + }} + +mutable.ByteArray.Raw.doc : Doc +mutable.ByteArray.Raw.doc = + {{ + A raw fixed-size unboxed array of bytes. This is a low-level type that is + used by the {type mutable.ByteArray} type and represents a contiguous region + of memory managed by the Unison runtime. + + This type is not in general intended to be used directly by Unison programs. + It offers a fixed amount of storage and no resize operations. Its main use + case is to provide a low-level interface for implementing + {type mutable.ByteArray}: + + @source{type mutable.ByteArray} + + # Constructing raw byte arrays + + Mutable arrays are parameterized by the ability that they require in order + to perform operations on them. At the moment there are two abilities that + can be used to construct mutable arrays: {type IO} and {type Scope}. + + ## IO arrays + + Construct a raw byte array of a fixed size in {type IO}. The contents of + the array are undefined: + + @signature{IO.Raw.byteArray} + + Construct a raw byte array of a fixed size in {type IO} and initialize + it with the given byte value. The first argument is the byte value to + initialize the array with and the second argument is the size: + + @signature{IO.Raw.byteArrayOf} + + ## Pure arrays + + Construct a raw byte array of a fixed size in the current {type Scope}. + The contents of the array are undefined: + + @signature{Scope.Raw.byteArray} + + Construct a raw byte array of a fixed size in the current {type Scope} + and initialize it with the given byte value. The first argument is the + byte value to initialize the array with and the second argument is the + size: + + @signature{Scope.Raw.byteArrayOf} + + # Reading and writing bytes + + Read a byte from the given raw byte array at the given index: + + @signature{mutable.ByteArray.Raw.read8} + + Write a byte to the given raw byte array at the given index: + + @signature{Raw.write8} + + Read two bytes from the given raw byte array at the given index in + big-endian order: + + @signature{mutable.ByteArray.Raw.read16be} + + Write two bytes to the given raw byte array at the given index in + big-endian order: + + @signature{Raw.write16be} + + Read three bytes from the given raw byte array at the given index in + big-endian order: + + @signature{mutable.ByteArray.Raw.read24be} + + Read four bytes from the given raw byte array at the given index in + big-endian order: + + @signature{mutable.ByteArray.Raw.read32be} + + Write four bytes to the given raw byte array at the given index in + big-endian order: + + @signature{Raw.write32be} + + Read five bytes from the given raw byte array at the given index in + big-endian order: + + @signature{mutable.ByteArray.Raw.read40be} + + Read eight bytes from the given raw byte array at the given index in + big-endian order: + + @signature{mutable.ByteArray.Raw.read64be} + + Write eight bytes to the given raw byte array at the given index in + big-endian order: + + @signature{Raw.write64be} + + # Copying bytes + + Copy bytes from one raw byte array to another. The first argument is the + source array, the second argument is the source index, the third argument + is the destination array, the fourth argument is the destination index, and + the fifth argument is the number of bytes to copy: + + @signature{mutable.ByteArray.Raw.copyTo!} + + # Querying the size of a raw byte array + + Get the size of the given raw byte array: + + @signature{mutable.ByteArray.Raw.size} + + # Converting to immutable byte arrays + + Convert a raw byte array to an immutable byte array: + + @signature{ByteArray.Raw.freeze!} + + Create an immutable copy of a portion of a raw byte array: + + @signature{ByteArray.Raw.freeze} + }} + +-- builtin mutable.ByteArray.Raw.freeze : +-- mutable.ByteArray.Raw g -> Nat -> Nat ->{g} data.ByteArray.Raw + +mutable.ByteArray.Raw.freeze.doc : Doc +mutable.ByteArray.Raw.freeze.doc = + {{ + Creates an immutable copy of a portion of a mutable byte array. The mutable + array is still safe to be modified afterwards. + }} + +-- builtin mutable.ByteArray.Raw.freeze! : +-- mutable.ByteArray.Raw g ->{g} data.ByteArray.Raw + +mutable.ByteArray.Raw.freeze!.doc : Doc +mutable.ByteArray.Raw.freeze!.doc = + {{ + Freezes a mutable byte array in-place. Although an immutable byte array value + is returned from this function, the original mutable array is marked frozen + as well, and may no longer be modified. This function is primarily for + creating immutable arrays: to do so, create a mutable array, set its + contents, then freeze to get the immutable array. + }} + +-- builtin mutable.ByteArray.Raw.read16be : +-- mutable.ByteArray.Raw g -> Nat ->{g, Exception} Nat + +mutable.ByteArray.Raw.read16be.doc : Doc +mutable.ByteArray.Raw.read16be.doc = + {{ + `` mutable.ByteArray.Raw.read16be arr ix `` reads a 16-bit big endian value + starting at __byte__ offset `ix` in the array. + }} + +-- builtin mutable.ByteArray.Raw.read24be : +-- mutable.ByteArray.Raw g -> Nat ->{g, Exception} Nat + +mutable.ByteArray.Raw.read24be.doc : Doc +mutable.ByteArray.Raw.read24be.doc = + {{ + `` mutable.ByteArray.Raw.read24be arr ix `` reads a 24-bit big endian value + starting at __byte__ offset `ix` in the array. + }} + +-- builtin mutable.ByteArray.Raw.read32be : +-- mutable.ByteArray.Raw g -> Nat ->{g, Exception} Nat + +mutable.ByteArray.Raw.read32be.doc : Doc +mutable.ByteArray.Raw.read32be.doc = + {{ + `` mutable.ByteArray.Raw.read32be arr ix `` reads a 32-bit big endian value + starting at __byte__ offset `ix` in the array. + }} + +-- builtin mutable.ByteArray.Raw.read40be : +-- mutable.ByteArray.Raw g -> Nat ->{g, Exception} Nat + +mutable.ByteArray.Raw.read40be.doc : Doc +mutable.ByteArray.Raw.read40be.doc = + {{ + `` mutable.ByteArray.Raw.read24be arr ix `` reads a 40-bit big endian value + starting at __byte__ offset `ix` in the array. + }} + +-- builtin mutable.ByteArray.Raw.read64be : +-- mutable.ByteArray.Raw g -> Nat ->{g, Exception} Nat + +mutable.ByteArray.Raw.read64be.doc : Doc +mutable.ByteArray.Raw.read64be.doc = + {{ + `` mutable.ByteArray.Raw.read64be arr ix `` reads a 64-bit big endian value + starting at __byte__ offset `ix` in the array. + }} + +-- builtin mutable.ByteArray.Raw.read8 : +-- mutable.ByteArray.Raw g -> Nat ->{g, Exception} Nat + +mutable.ByteArray.Raw.read8.doc : Doc +mutable.ByteArray.Raw.read8.doc = + {{ + Reads a 8-bit unsigned integer from a {type data.ByteArray.Raw} at the given + offset. Raises an {type Exception} if the offset is out of bounds. + }} + +-- builtin mutable.ByteArray.Raw.size : mutable.ByteArray.Raw g -> Nat + +mutable.ByteArray.Raw.size.doc : Doc +mutable.ByteArray.Raw.size.doc = + {{ + The size of a {type mutable.ByteArray.Raw} in bytes. + + # Example + + ``` + catch do + Scope.run do mutable.ByteArray.Raw.size (Scope.Raw.byteArrayOf 10 0) + ``` + }} + +-- builtin mutable.ByteArray.Raw.write16be : +-- mutable.ByteArray.Raw g -> Nat -> Nat ->{g, Exception} () + +mutable.ByteArray.Raw.write16be.doc : Doc +mutable.ByteArray.Raw.write16be.doc = + {{ + `` Raw.write16be arr o v `` writes the low 16 bits of the {type Nat} `v` to + the array `arr` starting at __byte__ offset `o`, in big-endian byte order. + }} + +-- builtin mutable.ByteArray.Raw.write32be : +-- mutable.ByteArray.Raw g -> Nat -> Nat ->{g, Exception} () + +mutable.ByteArray.Raw.write32be.doc : Doc +mutable.ByteArray.Raw.write32be.doc = + {{ + `` Raw.write32be arr o v `` writes the low 32 bits of the {type Nat} `v` to + the array `arr` starting at __byte__ offset `o`, in big-endian byte order. + }} + +-- builtin mutable.ByteArray.Raw.write64be : +-- mutable.ByteArray.Raw g -> Nat -> Nat ->{g, Exception} () + +mutable.ByteArray.Raw.write64be.doc : Doc +mutable.ByteArray.Raw.write64be.doc = + {{ + `` Raw.write64be arr o v `` writes all 8 bytes of the {type Nat} `v` to the + array `arr` starting at __byte__ offset `o`, in big-endian byte order. + }} + +-- builtin mutable.ByteArray.Raw.write8 : +-- mutable.ByteArray.Raw g -> Nat -> Nat ->{g, Exception} () + +mutable.ByteArray.Raw.write8.doc : Doc +mutable.ByteArray.Raw.write8.doc = + {{ + `` Raw.write8 arr o v `` writes the low 8 bits of the {type Nat} `v` to the + array `arr` starting at __byte__ offset `o`. + }} + +mutable.ByteArray.read8 : mutable.ByteArray g -> Nat ->{g, Exception} Nat +mutable.ByteArray.read8 arr i = + match arr with + MBArr off len src| i Nat.< len -> + mutable.ByteArray.Raw.read8 src (off Nat.+ i) + _ -> ArrayFailure.raise "mutable.ByteArray.read8: index out of bounds" i + +mutable.ByteArray.read8.doc : Doc +mutable.ByteArray.read8.doc = + {{ + Reads a single byte at the given index from an array. + + Throws an exception if the index is out of bounds. + + ``` + catch do + Scope.run do + arr = Scope.byteArrayOf 5 6 + mutable.ByteArray.read8 arr 3 + ``` + }} + +mutable.ByteArray.size : mutable.ByteArray g -> Nat +mutable.ByteArray.size = cases MBArr _ length _ -> length + +mutable.ByteArray.size.doc : Doc +mutable.ByteArray.size.doc = + {{ + Gets the size of a {type mutable.ByteArray}, in bytes. + + # Example + + ``` + Scope.run do + bs = ByteArray.fromBytes 0xs0102030405 + mutable.ByteArray.size (Scope.thawByteArray bs) + ``` + }} + +mutable.ByteArray.slice! : + Nat -> Nat -> mutable.ByteArray g ->{Exception} mutable.ByteArray g +mutable.ByteArray.slice! o l = cases + MBArr off len arr| o Nat.+ l Nat.<= len -> MBArr (off Nat.+ o) l arr + _ -> + ArrayFailure.raise + "mutable.ByteArray.slice!: not enough elements" (o Nat.+ l) + +mutable.ByteArray.slice!.doc : Doc +mutable.ByteArray.slice!.doc = + {{ + {ByteArray.slice!} returns a new {type mutable.ByteArray} representing a + portion of the original. + + The first argument specifies how many bytes to drop from the beginning. The + second argument specifies how many bytes to retain. The original array must + have a length at least as long as the offset plus the new length. + + The underlying storage of the new array is shared with the old array, so + mutations of each can affect the other. + }} + +mutable.ByteArray.write16be : + mutable.ByteArray g -> Nat -> Nat ->{g, Exception} () +mutable.ByteArray.write16be = cases + ba@(MBArr o0 l a), o1, v + | o1 Nat.+ 1 Nat.< l -> Raw.write16be a (o0 Nat.+ o1) v + | otherwise -> ArrayFailure.raise "write16be" (ba, o1) + +mutable.ByteArray.write16be.doc : Doc +mutable.ByteArray.write16be.doc = + {{ + `` ByteArray.write16be arr o v `` writes the low 16 bits of the {type Nat} + `v` to the {type mutable.ByteArray} `arr`, in big-endian byte order, starting + at the **byte** offset `o`. + + Throws an exception if there is insufficient space in the array, or if the + offset is out of bounds. + }} + +mutable.ByteArray.write32be : + mutable.ByteArray g -> Nat -> Nat ->{g, Exception} () +mutable.ByteArray.write32be = cases + ba@(MBArr o0 l a), o1, v + | o1 Nat.+ 3 Nat.< l -> Raw.write32be a (o0 Nat.+ o1) v + | otherwise -> ArrayFailure.raise "write32be" (ba, o1) + +mutable.ByteArray.write32be.doc : Doc +mutable.ByteArray.write32be.doc = + {{ + `` ByteArray.write32be arr o v `` writes the low 32 bits of the {type Nat} + `v` to the {type mutable.ByteArray} `arr`, in big-endian byte order, starting + at the **byte** offset `o`. + + Throws an exception if there is insufficient space in the array, or if the + offset is out of bounds. + }} + +mutable.ByteArray.write64be : + mutable.ByteArray g -> Nat -> Nat ->{g, Exception} () +mutable.ByteArray.write64be = cases + ba@(MBArr o0 l a), o1, v + | o1 Nat.+ 7 Nat.< l -> Raw.write64be a (o0 Nat.+ o1) v + | otherwise -> ArrayFailure.raise "write64be" (ba, o1) + +mutable.ByteArray.write64be.doc : Doc +mutable.ByteArray.write64be.doc = + {{ + `` ByteArray.write64be arr o v `` writes all 8 bytes of the {type Nat} `v` to + the {type mutable.ByteArray} `arr`, in big-endian byte order, starting at the + **byte** offset `o`. + + Throws an exception if there is insufficient space in the array, or if the + offset is out of bounds. + }} + +mutable.ByteArray.write8 : + mutable.ByteArray g -> Nat -> Nat ->{g, Exception} () +mutable.ByteArray.write8 arr i v = + match arr with + MBArr off len src| i Nat.< len -> Raw.write8 src (off Nat.+ i) v + _ -> ArrayFailure.raise "mutable.ByteArray.write8: index out of bounds" i + +mutable.ByteArray.write8.doc : Doc +mutable.ByteArray.write8.doc = + use mutable.ByteArray read8 write8 + {{ + `` write8 arr o v `` writes the low 8 bits of the {type Nat} `v` to the + {type mutable.ByteArray} `arr`, starting at the **byte** offset `o`. + + Throws an exception if the index is out of bounds. + + ``` + catch do + Scope.run do + arr = Scope.byteArrayOf 5 6 + x = read8 arr 3 + write8 arr 3 7 + y = read8 arr 3 + (x, y) + ``` + }} + +mutable.Ref.atomically : Ref {IO} a -> (a -> (a, b)) ->{IO} b +mutable.Ref.atomically ref f = + ticket = readForCas ref + let + (newState, output) = f (Ticket.read ticket) + if cas ref ticket newState then output else mutable.Ref.atomically ref f + +mutable.Ref.atomically.doc : Doc +mutable.Ref.atomically.doc = + {{ + Atomically modifies a {type Ref}. + + In addition to the {type Ref} itself, this function takes a function that + receives the current value of the {type Ref} and returns a new value + togtether with a result. If the {type Ref} is modified by another thread + between the time the current value is read and the new value is written, the + function is retried. The result of the function is returned as the result of + the {Ref.atomically} call once the new value has been written successfully. + + # Examples + + The following function updates a counter atomically: + + @source{getAndIncrement2} + + Note that the function passed to {Ref.atomically} cannot have any + abilities, because it might be retried nondeterministically depending on + how many threads are trying to modify the {type Ref} concurrently (see + {cas} for more details). + + You can use effects with atomic modifications by returning a suspended + computation from {Ref.atomically}, which gets run once the modification of + the {type Ref} completes. For example, this code prints "Done!" after + incrementing the counter: + + @source{printAndIncrement} + + This pattern can model arbitrary concurrent state machines that run actions + on state transitions. Together with {type Promise} (a write-once + reference), this can be used to implement concurrent queues, channels, and + other concurrency primitives. + + # See also + + * {type Ref} for more information about mutable references. + * {cas} for a more general function that gives you more control over the + behavior of concurrent modifications. + * {type Promise} for more information about write-once references. + * {STM.atomically} for an alternative implementation of atomic + modifications that is based on software transactional memory. + }} + +-- builtin mutable.Ref.cas : +-- mutable.Ref {IO} a -> mutable.Ref.Ticket a -> a ->{IO} Boolean + +mutable.Ref.cas.doc : Doc +mutable.Ref.cas.doc = + use Ref atomically + {{ + Implements a + [compare-and-swap](https://en.wikipedia.org/wiki/Compare-and-swap) operation + on a mutable reference. + + `` cas ref ticket value `` sets the contents of `ref` to `value` if and only + if `ticket` is still valid, i.e. no one has modified the {type Ref} since the + {type Ticket} was read with {readForCas}. It's an atomic operation, and + returns `` true `` if `value` was successfully written, and `` false `` + otherwise. + + {cas} can be used to implement atomic modifications of a {type Ref} using the + **CAS loop** pattern, which is as follows: + + 1. Acquire a ticket, with {readForCas}. + 2. Compute the new value, based on the old value. + 3. Attempt to write the new value, with {cas}. + 4. If {cas} returns ``false``, go back to step 1. + + CAS loops can be very efficient because they don't involve any locking. + + # Examples + + A CAS loop to update a counter atomically: + + @source{getAndIncrement} + + Note that in many cases it's possible to use {atomically} instead, which + implements a CAS loop for you. For example, the above function can be + implemented as: + + @source{getAndIncrement2} + + # See also + + * {readForCas} to acquire a ticket. + * {atomically} for a simpler way to implement atomic modifications. + * {Ref.modify} to modify a {type Ref} without regard to concurrency. + }} + +mutable.Ref.cas.examples.getAndIncrement : Ref {IO} Nat ->{IO} Nat +mutable.Ref.cas.examples.getAndIncrement ref = + use Nat + + ticket = readForCas ref + value = Ticket.read ticket + newValue = value + 1 + if cas ref ticket newValue then value + else mutable.Ref.cas.examples.getAndIncrement ref + +mutable.Ref.cas.examples.getAndIncrement2 : Ref {IO} Nat ->{IO} Nat +mutable.Ref.cas.examples.getAndIncrement2 ref = + use Nat + + Ref.atomically ref (value -> (value + 1, value)) + +mutable.Ref.cas.examples.printAndIncrement : Ref {IO} Nat ->{IO, Exception} () +mutable.Ref.cas.examples.printAndIncrement ref = + use Nat + + Ref.atomically ref (value -> (value + 1, do printLine "Done!")) () + +mutable.Ref.doc : Doc +mutable.Ref.doc = + {{ + The {type Ref} type represents a mutable reference to a value. It can be used + to store a value that can be updated and read multiple times. + + The {type Ref} type is parameterized by two types: + + 1. The ability that is required to read and write the reference. + 2. The type of the value stored in the reference. + + # Creating a reference + + Create a reference that can be mutated and read with {type IO} actions, and + initialize it to a value: + + @signature{IO.ref} + + Create a mutable reference that can be updated and read within a + {type Scope}: + + @signature{Scope.ref} + + # Reading and writing a reference + + Read the value from a reference: + + @signature{Ref.read} + + Write a value to a reference: + + @signature{Ref.write} + + Modify the value in a reference by applying a function to it: + + @signature{Ref.modify} + + # Compare-and-swap + + [Compare-and-swap](https://en.wikipedia.org/wiki/Compare-and-swap) is an + operation to atomically update a mutable reference in a multi-threaded + environment without the need for locking. It is implemented by {type Ref} + with: + + @signature{cas} + + Before updating the {type Ref} with {cas}, you must acquire a {type Ticket} + from the reference: + + @signature{readForCas} + + See the {cas} documentation for details. + }} + +mutable.Ref.modify : Ref g a -> (a ->{h} a) ->{g, h} () +mutable.Ref.modify r f = + x = Ref.read r + Ref.write r (f x) + +mutable.Ref.modify.doc : Doc +mutable.Ref.modify.doc = + {{ + `` Ref.modify r f `` applies the function `f` to the value in the {type Ref} + `r` and writes the result to the {type Ref}. + }} + +-- builtin mutable.Ref.read : mutable.Ref g a ->{g} a + +mutable.Ref.read.doc : Doc +mutable.Ref.read.doc = + {{ + Reads the value of a {type Ref}. + + # Example + + ``` + Scope.run do Ref.read (Scope.ref 1) + ``` + }} + +-- builtin mutable.Ref.readForCas : +-- mutable.Ref {IO} a ->{IO} mutable.Ref.Ticket a + +mutable.Ref.readForCas.doc : Doc +mutable.Ref.readForCas.doc = + {{ + Reads a {type Ticket} from a {type Ref}, which can be used to modify the Ref + atomically via a CAS loop. See {cas} for more details. If you're only + interested in reading the Ref without modifying it, see {Ref.read}. + }} + +mutable.Ref.Ticket.doc : Doc +mutable.Ref.Ticket.doc = + {{ + A {type Ticket} holds the current value of a {type Ref}, and can be passed to + {cas} to modify the {type Ref} atomically. You can obtain a {type Ticket} by + calling {readForCas}, and extract its value with {Ticket.read}. + }} + +-- builtin mutable.Ref.Ticket.read : mutable.Ref.Ticket a -> a + +mutable.Ref.Ticket.read.doc : Doc +mutable.Ref.Ticket.read.doc = + {{ + Extracts the value of a {type Ticket}, which reflects the value of a + {type Ref} at the time the {type Ticket} was read with {readForCas}. + }} + +-- builtin mutable.Ref.write : mutable.Ref g a -> a ->{g} () + +mutable.Ref.write.doc : Doc +mutable.Ref.write.doc = + {{ + Writes a value to a {type Ref}. + + # Example + + ``` + Scope.run do + r = Scope.ref "" + Ref.write r "hello" + Ref.read r + ``` + }} + +mutable.Scope.arrayOf : a -> Nat ->{Scope s} mutable.Array {Scope s} a +mutable.Scope.arrayOf v l = MArr 0 l (Scope.Raw.arrayOf v l) + +mutable.Scope.byteArray : Nat ->{Scope s} mutable.ByteArray {Scope s} +mutable.Scope.byteArray l = MBArr 0 l (Scope.Raw.byteArray l) + +mutable.Scope.byteArrayOf : Nat -> Nat ->{Scope s} mutable.ByteArray {Scope s} +mutable.Scope.byteArrayOf b l = MBArr 0 l (Scope.Raw.byteArrayOf b l) + +mutable.Scope.byteArrayOf.doc : Doc +mutable.Scope.byteArrayOf.doc = + {{ + Creates a new {type mutable.ByteArray} in the current {type Scope}. The array + is filled with the low byte of the first argument, and has a length given by + the second argument. + + # Example + + ``` + Scope.run do + a = Scope.byteArrayOf 0 10 + ByteArray.toList (ByteArray.freeze! a) + ``` + }} + +test> mutable.Scope.byteArrayOf.test = Scope.run do + b = Scope.byteArrayOf 1 10 + z = ByteArray.freeze! b + check (ByteArray.toList z === [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) + +mutable.Scope.doc : Doc +mutable.Scope.doc = + use Ref modify read write + {{ + The {type Scope} ability allows you to create mutable references and arrays + in a local scope that are safe to read and write as they can only be accessed + from within the scope. + + The {type Scope} type is parameterized with a phantom type `s`. This + parameter is never used, but each scope has its own unique type, so it is + impossible to read or write a reference from one scope in another scope. + + # Running a scope + + The {type Scope} ability has a single handler: + + `Scope.run : (∀ s. '{g, Scope s} r) ->{g} r` + + Note that the argument to the handler must be polymorphic in the phantom + type `s`, so the caller can never know what the phantom type actually is. + This means only the handler can use the mutable references and arrays that + it creates. It cannot return them to the caller. Consequently, the + underlying mutable memory is private to (and managed by) the handler. + + # Mutable references + + The {type Ref} is a mutable reference to a value of some type. You can + create a new mutable reference in {type Scope} using {Scope.ref}: + + @signature{Scope.ref} + + For example: + + ``` + Scope.run do + ref = Scope.ref 42 + write ref 43 + modify ref Nat.increment + read ref + ``` + + Write to the reference using {write} and read from it using {read}. You can + also use {modify} to modify the value in the reference using a function. + + # Mutable arrays + + Mutable arrays come in two flavors: + + * {type mutable.Array} - a boxed array of values of some type. + * {type mutable.ByteArray} - an unboxed array of bytes. + + 📚 Guide: {arrays} + + ## Boxed arrays + + Create a new boxed array in {type Scope}, of a given length, and fill it + with a default value: + + @signature{Scope.arrayOf} + + Get new {type mutable.Array} initialized to the contents of an immutable + {type data.Array}. + + @signature{Scope.thawArray} + + See {type mutable.Array} for more operations on boxed arrays. + + ## Unboxed arrays + + Create a new unboxed byte array in {type Scope}, of a given length, and + fill it with a default value: + + @signature{Scope.byteArrayOf} + + Create a new unboxed byte array in {type Scope}, of a given length, with + unspeficied initial values: + + @signature{Scope.byteArray} + + Get new {type mutable.ByteArray} initialized to the contents of an + immutable {type data.ByteArray}: + + @signature{Scope.thawByteArray} + }} + +-- builtin mutable.Scope.Raw.array : +-- Nat ->{mutable.Scope s} mutable.Array.Raw (mutable.Scope s) a + +-- builtin mutable.Scope.Raw.arrayOf : +-- a -> Nat ->{mutable.Scope s} mutable.Array.Raw (mutable.Scope s) a + +-- builtin mutable.Scope.Raw.byteArray : +-- Nat ->{mutable.Scope s} mutable.ByteArray.Raw (mutable.Scope s) + +mutable.Scope.Raw.byteArray.doc : Doc +mutable.Scope.Raw.byteArray.doc = + {{ + Constructs a {type mutable.ByteArray.Raw} in the current {type Scope}. The + contents of the array are undefined. + }} + +-- builtin mutable.Scope.Raw.byteArrayOf : +-- Nat -> Nat ->{mutable.Scope s} mutable.ByteArray.Raw (mutable.Scope s) + +mutable.Scope.Raw.bytearrayOf.doc : Doc +mutable.Scope.Raw.bytearrayOf.doc = + {{ + Creates a {type mutable.ByteArray.Raw} in the current {type Scope} of the + given size and fills it with the given byte value. + }} + +-- builtin mutable.Scope.ref : +-- a ->{mutable.Scope s} mutable.Ref {mutable.Scope s} a + +mutable.Scope.ref.doc : Doc +mutable.Scope.ref.doc = + use Ref read write + {{ + Creates a new mutable {type Ref} in the current {type Scope}. The {type Ref} + is initialized to the given value. The {type Ref} is only visible within the + {type Scope} in which it was created, and can be read and written to using + {read}, {write}, and {Ref.modify}. + + # Example + + ``` + Scope.run do + r = Scope.ref "" + write r "hello" + read r + ``` + }} + +-- builtin mutable.Scope.run : (∀ s. '{g, mutable.Scope s} r) ->{g} r + +mutable.Scope.run.doc : Doc +mutable.Scope.run.doc = + {{ + Runs a computation in a new {type Scope}, returning the result of the + computation. + + # Example + + ``` + Scope.run do + r = Scope.ref "" + Ref.write r "hello" + Ref.read r + ``` + }} + +mutable.Scope.thawArray : data.Array a ->{Scope s} mutable.Array {Scope s} a +mutable.Scope.thawArray = cases + Arr off len src -> + dst = Scope.Raw.array len + handle data.Array.Raw.copyTo! dst 0 src off len with impossible + MArr 0 len dst + +mutable.Scope.thawByteArray : + data.ByteArray ->{Scope s} mutable.ByteArray {Scope s} +mutable.Scope.thawByteArray = cases + BArr off len src -> + dst = Scope.Raw.byteArray len + handle data.ByteArray.Raw.copyTo! dst 0 src off len with impossible + MBArr 0 len dst + +(Nat.!=) : Nat -> Nat -> Boolean +a Nat.!= b = + use Nat == + Boolean.not (a == b) + +Nat.!=.doc : Doc +Nat.!=.doc = + use Nat != + + {{ + Non-equality on {type Nat}. `` x != y `` is `` true `` if `x` and `y` are + __not__ the same {type Nat} value. + + ``` + 2 + 2 != 5 + ``` + + ``` + 12 + 3 != 36 + ``` + }} + +-- builtin Nat.* : Nat -> Nat -> Nat + +Nat.*.doc : Doc +Nat.*.doc = + use Nat * + + {{ + {*} is the multiplication operator for the {type Nat} type. + + ``` + 1 * 2 + ``` + + Operators in Unison are left-associative, so parentheses may need to be added + to arithmetic expressions to group sub-expressions differently. + + ``` + 1 * 2 + 4 + ``` + + ``` + 1 * (2 + 4) + ``` + + Integer overflow is handled by wrapping around to the minimum {type Nat} + value of ``0``. + + ``` + maxNat * maxNat + ``` + }} + +-- builtin Nat.+ : Nat -> Nat -> Nat + +Nat.+.doc : Doc +Nat.+.doc = + use Nat * + + {{ + {+} is the addition operator for the {type Nat} type. + + ``` + 1 + 2 + ``` + + Operators in Unison are left-associative, so parentheses may need to be added + to arithmetic expressions to group sub-expressions differently. + + ``` + (1 + 2) * 4 + ``` + + ``` + 1 + 2 * 4 + ``` + + Integer overflow is handled by wrapping around to the minimum {type Nat} + value of ``0``. + + ``` + 1 + maxNat + ``` + }} + +-- builtin Nat.- : Nat -> Nat -> Nat + +Nat.-.doc : Doc +Nat.-.doc = + use Nat - + {{ + Truncated subtraction on natural numbers, which saturates to 0. + + # Examples + + ``` + 5 - 3 + ``` + + ``` + 1 - 4 + ``` + + ``` + 2 - 2 + ``` + }} + +test> Nat.-.tests.leftAdjoint = runs 100 do + use Nat + - <= >= + x = natInOrder() + y = natInOrder() + r = x - y + y >= x && x + y - y <= x + if Boolean.not r then bug (x, y) else expect r + +-- builtin Nat./ : Nat -> Nat -> Nat + +Nat./.doc : Doc +Nat./.doc = + use Nat / + {{ + Returns the integer quotient of the two {type Nat} arguments. + + # Examples + + ``` + 16 / 2 + ``` + + ``` + 16 / 3 + ``` + + ``` + 16 / 16 + ``` + }} + +-- builtin Nat.< : Nat -> Nat -> Boolean + +Nat.<.doc : Doc +Nat.<.doc = + use Nat < + {{ + Returns `` true `` if the first argument is less than the second, and `` + false `` otherwise. + + # Examples + + ``` + 3 < 2 + ``` + + ``` + 2 < 3 + ``` + + ``` + 2 < 2 + ``` + }} + +-- builtin Nat.<= : Nat -> Nat -> Boolean + +Nat.<=.doc : Doc +Nat.<=.doc = + use Nat <= + {{ + Returns `` true `` if the first argument is less than or equal to the second, + and `` false `` otherwise. + + # Examples + + ``` + 3 <= 2 + ``` + + ``` + 2 <= 3 + ``` + + ``` + 2 <= 2 + ``` + }} + +-- builtin Nat.== : Nat -> Nat -> Boolean + +Nat.==.doc : Doc +Nat.==.doc = + use Nat + == + {{ + Equality on {type Nat}. `` x == y `` is `` true `` if `x` and `y` are the + same {type Nat} value. + + ``` + 2 + 2 == 5 + ``` + + ``` + 12 + 3 == 36 + ``` + }} + +-- builtin Nat.> : Nat -> Nat -> Boolean + +Nat.>.doc : Doc +Nat.>.doc = + use Nat > + {{ + Returns `` true `` if the first argument is greater than the second, and `` + false `` otherwise. + + # Examples + + ``` + 3 > 2 + ``` + + ``` + 2 > 3 + ``` + + ``` + 2 > 2 + ``` + }} + +-- builtin Nat.>= : Nat -> Nat -> Boolean + +Nat.>=.doc : Doc +Nat.>=.doc = + use Nat >= + {{ + Returns `` true `` if the first argument is greater than or equal to the + second, and `` false `` otherwise. + + # Examples + + ``` + 3 >= 2 + ``` + + ``` + 2 >= 3 + ``` + + ``` + 2 >= 2 + ``` + }} + +-- builtin Nat.and : Nat -> Nat -> Nat + +Nat.and.doc : Doc +Nat.and.doc = + use Nat and + {{ + Returns the bitwise AND of the two arguments. + + # Examples + + ``` + and 3 2 + ``` + + ``` + and 207 243 + ``` + + ``` + and 2 2 + ``` + }} + +Nat.bit : Nat -> Nat -> Nat +Nat.bit which n = Nat.and (Nat.shiftRight n which) 1 + +Nat.bit.doc : Doc +Nat.bit.doc = + {{ + `` bit n x `` returns `` 1 `` if the `n`-th bit of the {type Nat} `x` is set, + or `` 0 `` otherwise. + + # Examples + + ``` + bit 0 0 + ``` + + ``` + bit 8 256 + ``` + + ``` + bit 63 maxNat + ``` + + If the bit offset is higher than ``63``, this function always returns + ``0``: + + ``` + bit 64 maxNat + ``` + }} + +Nat.changeBit : Nat -> Boolean -> Nat -> Nat +Nat.changeBit which b n = + use Nat xor + x = if b then maxNat else 0 + xor n (Nat.and (xor x n) (Nat.shiftLeft 1 which)) + +Nat.changeBit.doc : Doc +Nat.changeBit.doc = + {{ + `` changeBit n bool x `` changes the `n`-th bit of `x` to be either set or + cleared, depending on the value of `bool`. If `bool` is `` false `` then the + bit is unset (changed to `0`). If `bool` is `` true `` then the bit is set. + + # Examples + + ``` + changeBit 8 true 0 + ``` + + ``` + changeBit 1 false maxNat + ``` + }} + +test> Nat.changeBit.tests = test.verify do + Each.repeat 500 + n0 = Random.nat! + i = Random.natIn 0 70 + b = Random.boolean() + n = n0 |> changeBit i b + n' = n |> changeBit i (Boolean.not b) |> changeBit i b + ensure (n' === n) + +Nat.choose : Nat -> Nat -> Nat +Nat.choose n k = + use Nat * + - / + k' = Universal.min k (n - k) + nk = n - k' + go a i = a * (nk + i) / i + if Universal.gt k n then 0 else List.foldLeft go 1 (Nat.range 1 (k' + 1)) + +Nat.choose.doc : Doc +Nat.choose.doc = + {{ + `` choose n k `` computes the binomial coefficient, which is the number of + ways to choose `k` unordered elements from `n` possibilities. + + # Examples + + There are 10 ways to choose 3 values from a set of 5: + + ``` + choose 5 3 + ``` + + There is no way to choose 5 values from a set of 3: + + ``` + choose 3 5 + ``` + + There's one way to choose zero values from any set: + + ``` + choose 10 0 + ``` + }} + +test> Nat.choose.tests.test1 = check (choose 7 3 === 35) + +Nat.clamp : Nat -> Nat -> Nat -> Nat +Nat.clamp low hi x = Nat.min hi (Nat.max low x) + +Nat.clamp.doc : Doc +Nat.clamp.doc = + use Nat clamp + {{ + `` clamp lo hi x `` clamps value `x` between `lo` and `hi`. The result is + `lo` if `x` is less than `lo`, `hi` if `x` is greater than `hi`, and `x` + otherwise. + + # Examples + + ``` + clamp 0 10 1 + ``` + + ``` + clamp 0 10 11 + ``` + + ``` + clamp 0 10 10 + ``` + }} + +test> Nat.clamp.test = test.verify do + _ = Each.range 0 100 + low = 0 + hi = 10 + x = Random.nat() + result = Nat.clamp low hi x + ensureWith result (Nat.inRange low (Nat.increment hi) result) + +Nat.clearBit : Nat -> Nat -> Nat +Nat.clearBit which n = Nat.and n (Nat.complement (Nat.shiftLeft 1 which)) + +Nat.clearBit.doc : Doc +Nat.clearBit.doc = + use Nat == + {{ + `` clearBit n x `` changes the `n`-th bit of `x` to ``0``. + + # Examples + + ``` + clearBit 0 1 + ``` + + ``` + clearBit 1 7 + ``` + + If the bit offset is higher than ``63``, or if the bit is already unset, + this function does nothing: + + ``` + clearBit 64 maxNat == maxNat + ``` + + ``` + clearBit 0 0 + ``` + }} + +-- builtin Nat.complement : Nat -> Nat + +Nat.complement.doc : Doc +Nat.complement.doc = + use Nat complement + {{ + Flips all the bits of a {type Nat}. This is equivalent to ``Nat.xor maxNat``. + + # Example + + ``` + complement Nat.maxNat + ``` + + ``` + Nat.toTextBase 16 (complement 77129852519530768) + ``` + }} + +Nat.decrement : Nat -> Nat +Nat.decrement n = + use Nat - + n - 1 + +Nat.decrement.doc : Doc +Nat.decrement.doc = + {{ If the given number is higher than 0, decrement it by 1. }} + +test> Nat.decrement.test = + use Nat + + deprecated.forAll 100 Domain.nats (n -> Nat.decrement (n + 1) === n) + +Nat.deprecated.findLowestZero : (Nat ->{e} Int) -> Nat -> Nat ->{e} Nat +Nat.deprecated.findLowestZero hit bot top = + use Nat + / + use Nat.deprecated findLowestZero + if Universal.gteq bot top then top + else + mid = (bot + top) / 2 + match Universal.ordering (hit mid) +0 with + Equal -> mid + Less -> findLowestZero hit bot mid + Greater -> findLowestZero hit (mid + 1) top + +Nat.deprecated.findLowestZero.doc : Doc +Nat.deprecated.findLowestZero.doc = + use Universal compare + {{ + `` findLowestZero p lo hi `` uses a binary search algorithm to find the + {type Nat} between `lo` (inclusive) and `hi` (exclusive) for which `p` + returns ``0``. {findLowestZero} returns `lo` if no such {type Nat} is found. + + `p` should return `` -1 `` for {type Nat}s below the search value, and `` +1 + `` for {type Nat}s above the search value. + + See also: {search} for a version of this that uses {type Optional}, returning + {None} instead of `lo` when the element is not found. + + # Examples + + ``` + findLowestZero (compare 7) 5 10 + ``` + + ``` + e = Some 7 + s = [3, 5, 7, 8] + findLowestZero (i -> compare e (List.at i s)) 0 (List.size s) + ``` + + ``` + findLowestZero (compare 0) 1 5 + ``` + }} + +Nat.diff : Nat -> Nat -> Nat +Nat.diff x y = + use Nat - < + if x < y then y - x else x - y + +Nat.diff.doc : Doc +Nat.diff.doc = + use Nat diff + {{ + `` diff x y `` returns the distance between the numbers `x` and `y`. + + # Examples + + ``` + diff 10 4 + ``` + + ``` + diff 4 5 + ``` + + ``` + diff 4 4 + ``` + }} + +Nat.doc : Doc +Nat.doc = + use Int fromRepresentation toRepresentation + use Nat != * + - / < <= == > >= and complement decrement fromText inRange increment isEven isOdd isPrefixOf isSuffixOf leadingZeros max min mod or popCount pow range rangeClosed shiftLeft shiftRight toFloat toInt toText trailingZeros xor + {{ + {type Nat} is the type of 64-bit unsigned integers. This type is built into + Unison. + + Values range from `` 0 `` to @eval{maxNat}. + + Negative numbers can be represented with the {type Int} type instead. + + # Constructing natural numbers + + ## Literal syntax + + You can construct {type Nat} values using literal syntax. For example, + these are valid {type Nat} values: + + * `` 0 `` + * `` 12 `` + * `0xdeadbeef` (hexadecimal notation) + * `0o5446` (octal notation) + + A literal {type Nat} consists of a number in decimal, hexadecimal + (starting with `0x`), or octal notation (starting with `0o`). + + # Arithmetic + + You can add and multiply {type Nat} values: + + ``` + (1 + 2 * 3) * 4 + ``` + + Note that Unison has no + [order of operations or operator precedence](https://en.wikipedia.org/wiki/Order_of_operations) + rules, so parentheses are necessary. All binary operators associate to the + left: + + ``` + (1 + 2) * 3 * 4 + ``` + + Natural subtraction is supported with {-}. Note that this subtraction is + clamped to ``0``, so it doesn't overflow or return negative numbers: + + ``` + 1 - 2 + ``` + + ``` + 2 - 1 + ``` + + If you want negative results, {subtractToInt} returns an {type Int}: + + ``` + subtractToInt 4 8 + ``` + + {pow} is exponentiation: + + @signature{pow} + + ``` + pow 2 8 + ``` + + {increment} increments a {type Nat} by one: + + ``` + increment 1 + ``` + + {decrement} decrements a {type Nat} by one: + + ``` + decrement 1 + ``` + + Arithmetic overflow in the positive direction is handled by wrapping + around. For example incrementing {maxNat} results in ``0``: + + ``` + increment maxNat + ``` + + But decrementing `` 0 `` results in ``0``: + + ``` + decrement 0 + ``` + + ## {type Nat} division + + {/} is natural number division. `` m / n `` is the greatest {type Nat} + that's no larger than the fraction m/n: + + ``` + 17 / 5 + ``` + + {mod} gets the modulus (remainder) of such division: + + ``` + mod 17 5 + ``` + + # Comparing {type Nat}s + + `` a == b `` checks if `a` and `b` are equal: + + ``` + 3 * 4 == 2 * 6 + ``` + + `` a != b `` checks if `a` and `b` are __not__ equal: + + ``` + 3 * 4 != 2 * 6 + ``` + + `` a <= b `` checks if `a` is at most `b`: + + ``` + 3 * 3 <= 10 + ``` + + `` a >= b `` checks if `a` is at least `b`: + + ``` + 2 * 5 >= 10 + ``` + + `` a > b `` checks if `a` is strictly above `b`: + + ``` + 2 * 5 > 10 + ``` + + `` a < b `` checks if `a` is strictly below `b`: + + ``` + 2 * 5 < 10 + ``` + + `` min a b `` returns the lesser of the two numbers `a` and `b`: + + ``` + min 1 2 + ``` + + `` max a b `` returns the greater of the two numbers `a` and `b`: + + ``` + max 1 2 + ``` + + `` isEven `` and `` isOdd `` check if a {type Nat} is even or odd, + respectively: + + ``` + isEven 2 + ``` + + ``` + isOdd 2 + ``` + + # {type Nat} ranges + + `` inRange x y n `` checks if `n` is between `x` (inclusive) and `y` + (exclusive). That is, whether `n` is at least `x` and strictly below `y`: + + ``` + inRange 1 3 2 + ``` + + ``` + inRange 1 3 3 + ``` + + ``` + inRange 1 3 1 + ``` + + `` range x y `` returns all the numbers between `x` (inclusive) and `y` + (exclusive) as a {type List}: + + ``` + range 1 10 + ``` + + `` rangeClosed x y `` returns all the numbers between `x` (inclusive) and + `y` (also inclusive) as a {type List}: + + ``` + rangeClosed 1 10 + ``` + + # Bitwise operations + + `` bit n x `` gets the `n`th bit of `x`: + + ``` + bit 1 3 + ``` + + `` changeBit n b x `` changes the `n`th bit of `x` to 1 if `b` is ``true``, + or 0 otherwise: + + ``` + changeBit 0 true 2 + ``` + + `` clearBit n x `` zeroes the `n`th bit of `x`: + + ``` + clearBit 1 7 + ``` + + `` complement `` flips all the bits of a {type Nat} (logical NOT): + + ``` + complement 123 + ``` + + `` and x y `` is the bitwise AND operation on corresponding bits in `x` and + `y`: + + ``` + and 5 3 + ``` + + `` or `` is bitwise OR: + + ``` + or 5 3 + ``` + + `` xor `` is bitwise exclusive-OR: + + ``` + xor 5 3 + ``` + + `` dropBits k x `` clears the high `k` bits of `x`: + + ``` + dropBits 56 maxNat + ``` + + `` flipBit k x `` flips the `k`th bit of `x`: + + ``` + flipBit 0 7 + ``` + + `` flipEndian x `` reverses the byte order of `x`: + + ``` + Nat.toTextBase 16 (flipEndian 255) + ``` + + `` isPrefixOf x y `` checks if `x` is a bitwise prefix of `y`: + + ``` + isPrefixOf 256 511 + ``` + + `` isSuffixOf x y `` checks if `x` is a bitwise suffix of `y`: + + ``` + isSuffixOf 255 maxNat + ``` + + `` isSetBit k x `` checks if the `k`th bit of `x` is set: + + ``` + isSetBit 0 7 + ``` + + `` leadingZeros `` counts the number of zero bits at the front (i.e. left) + of a {type Nat}: + + ``` + leadingZeros 1 + ``` + + `` trailingZeros `` counts the number of zero bits at the end (i.e. right) + of a {type Nat}: + + ``` + trailingZeros 256 + ``` + + `` msb x `` returns the position of the most significant bit of `x`: + + ``` + msb 256 + ``` + + `` onesComplementSum n x y `` is the `n`-bit ones' complement sum of the + low-order `n` bits of `x` and `y`: + + ``` + onesComplementSum 16 65535 1 + ``` + + `` popCount `` counts the number of `` 1 `` bits in a {type Nat}: + + ``` + popCount 3735928559 + ``` + + {reverseBits} reverses the bits of a {type Nat}: + + ``` + reverseBits 3735928559 + ``` + + `` setBit k n `` sets the `k`th bit of `n` to 1: + + ``` + setBit 0 4 + ``` + + `` shiftLeft x n `` performs a __left shift__ of `x` by `n` bits: + + ``` + shiftLeft 256 2 + ``` + + `` shiftRight x n `` performs a __right shift__ of `x` by `n` bits: + + ``` + shiftRight 256 2 + ``` + + `` takeLeftBits k n `` clears all but the high `k` bits of `n`: + + ``` + takeLeftBits 56 511 + ``` + + `` twosComplement n `` returns the two's complement of `n`: + + ``` + twosComplement maxNat + ``` + + # Conversion from other types + + {truncate0} will create a {type Nat} from its {type Int} representation, + truncating all negative integers to ``0``. While {toRepresentation} will + construct a {type Nat} from its representation as a 64-bit word of type + {type Int}, translating all negative numbers to the value of {maxNat}. + + ``` + truncate0 +300 + ``` + + ``` + truncate0 -10 + ``` + + ``` + toRepresentation +12 + ``` + + ``` + toRepresentation -42 + ``` + + {fromText} constructs a {type Nat} from any valid {type Nat} literal syntax + as a {type Text} value: + + ``` + fromText "34" + ``` + + # Conversion to other types + + `` toText `` gives the textual representation of a {type Nat}: + + ``` + toText 1720 + ``` + + `` toInt `` converts a {type Nat} to {type Int}: + + ``` + toInt 123 + ``` + + `` fromRepresentation `` casts the 64-bit representation of a {type Nat} as + an {type Int}. + + ``` + fromRepresentation 1 + ``` + + `` toFloat `` converts to {type Float}: + + ``` + toFloat 2116 + ``` + }} + +Nat.dropBits : Nat -> Nat -> Nat +Nat.dropBits k n = Nat.and n <| Nat.shiftRight maxNat k + +Nat.dropBits.doc : Doc +Nat.dropBits.doc = + {{ + `` dropBits k n `` returns a {type Nat} with the same bits as `n` except the + high `k` bits are set to ``0``. + + # Examples + + ``` + dropBits 56 maxNat + ``` + + ``` + dropBits 60 3735928559 + ``` + + ``` + dropBits 63 255 + ``` + }} + +Nat.factorial : Nat -> Natural +Nat.factorial n = + use Nat - / < == + use Natural * one + partialProduct : Nat -> Natural -> (Natural, Natural) + partialProduct len j = + use Natural + fromNat + two = fromNat 2 + four = fromNat 4 + half = len / 2 + if half == 0 then (j + two, j + two) + else + if len == 2 then ((j + two) * (j + four), j + four) + else + (ql, j') = partialProduct (len - half) j + (qr, j'') = partialProduct half j' + (ql * qr, j'') + go k lo s hi j p r = + use Nat + > + go' b k lo s hi j p r = + if b then Natural.shiftLeft r s else go k lo s hi j p r + lo' = Nat.shiftRight n k + hi' = Nat.or (Nat.decrement lo') 1 + len = (hi' - hi) / 2 + if hi' > hi then + (q, j') = partialProduct len j + p' = p * q + r' = r * p' + go' (k == 0) (k - 1) lo' (s + lo) hi' j' p' r' + else go' (k == 0) (k - 1) lo' (s + lo) hi' j p r + if n < 2 then one + else go (Optional.fold (do 0) Nat.increment (msb n)) 0 0 1 one one one + +Nat.factorial.doc : Doc +Nat.factorial.doc = + {{ + Computes the factorial of a natural number. Since factorials can get quite + large, this returns a {type Natural} rather than a {type Nat}. + }} + +test> Nat.factorial.tests.test1 = + use Natural == + check (factorial 4 == Natural.fromNat 24) + +Nat.flipBit : Nat -> Nat -> Nat +Nat.flipBit which n = Nat.xor n (Nat.shiftLeft 1 which) + +Nat.flipBit.doc : Doc +Nat.flipBit.doc = + {{ + `` flipBit n x `` flips the `n`-th bit of `x`. + + # Examples + + ``` + flipBit 0 0 + ``` + + ``` + flipBit 0 1 + ``` + + ``` + flipBit 24 1 + ``` + + ``` + flipBit 24 16777217 + ``` + + If the bit offset is higher than ``63``, this function does nothing: + + ``` + flipBit 64 0 + ``` + }} + +Nat.flipEndian : Nat -> Nat +Nat.flipEndian n = + use Nat and or shiftLeft shiftRight + a = shiftRight n 56 + b = and 255 (shiftRight n 48) + c = and 255 (shiftRight n 40) + d = and 255 (shiftRight n 32) + e = and 255 (shiftRight n 24) + f = and 255 (shiftRight n 16) + g = and 255 (shiftRight n 8) + h = and 255 n + or + a + (or + (shiftLeft b 8) + (or + (shiftLeft c 16) + (or + (shiftLeft d 24) + (or + (shiftLeft e 32) + (or (shiftLeft f 40) (or (shiftLeft g 48) (shiftLeft h 56))))))) + +Nat.flipEndian.doc : Doc +Nat.flipEndian.doc = + use Nat toTextBase + {{ + Flips the [endianness](https://en.wikipedia.org/wiki/Endianness) of a + {type Nat}. In other words, reverses the order of the 8 bytes in the + {type Nat}. + + # Examples + + ``` + toTextBase 16 (flipEndian 1) + ``` + + ``` + Optional.flatMap + (toTextBase 16 << flipEndian) (Nat.fromHex "feedfacecafebeef") + ``` + }} + +Nat.fromBytesBigEndian : Bytes ->{Abort} Nat +Nat.fromBytesBigEndian bs = match decodeNat64be bs with + Some (n, _) -> n + _ -> abort + +Nat.fromBytesBigEndian.doc : Doc +Nat.fromBytesBigEndian.doc = + use fromList impl + {{ + Reads a {type Nat} from a {type Bytes}, assuming most significant bytes come + first. + + Calls {abort} if the input has fewer than 8 bytes. + + # Examples + + ``` + toOptional! do fromBytesBigEndian 0xs00000000000000ff + ``` + + This example has some junk bytes after the {type Nat}, which are ignored: + + ``` + toOptional! do fromBytesBigEndian 0xs00000000deadbeef00ff00ff + ``` + + In this example, the function is only given 1 byte, so it does {abort}: + + ``` + toOptional! do fromBytesBigEndian 0xs00 + ``` + }} + +Nat.fromBytesLittleEndian : Bytes ->{Abort} Nat +Nat.fromBytesLittleEndian bs = match decodeNat64le bs with + Some (n, _) -> n + _ -> abort + +Nat.fromBytesLittleEndian.doc : Doc +Nat.fromBytesLittleEndian.doc = + use fromList impl + {{ + Reads a {type Nat} from a {type Bytes}, assuming least significant bytes come + first. + + Calls {abort} if the input has fewer than 8 bytes. + + # Examples + + ``` + toOptional! do fromBytesLittleEndian 0xsff00000000000000 + ``` + + This example has some junk bytes after the {type Nat}, which are ignored: + + ``` + toOptional! do fromBytesLittleEndian 0xsefbeadde00000000ffff0000 + ``` + + In this example, the function is only given 1 byte, so it does {abort}: + + ``` + toOptional! do fromBytesLittleEndian 0xs00 + ``` + }} + +Nat.fromHex : Text -> Optional Nat +Nat.fromHex txt = + toOptional! do + use Either toAbort + use Nat - <= == > + use fromHex impl + padded = if Nat.isEven (Text.size txt) then txt else "0" Text.++ txt + match toNat64sbe (toAbort (impl padded)) with + ([n], bs)| Bytes.size bs == 0 -> n + ([], bs)| Bytes.size bs > 0 && Bytes.size bs <= 8 -> + match decodeNat64be + (toAbort (impl (Text.repeat (8 - Bytes.size bs) "00")) Bytes.++ bs) with + Some (n, bs) | Bytes.size bs == 0 -> n + _ -> abort + _ -> abort + +Nat.fromHex.doc : Doc +Nat.fromHex.doc = + use Nat fromHex + {{ + Decodes a {type Nat} from {type Text} using the + [hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal) encoding. Returns + {None} if the {type Text} contains any characters that are not valid + hexadecimal digits, or if the {type Text} contains more than 16 digits. + + # Examples + + ``` + fromHex "100" + ``` + + ``` + fromHex "feedfacecafebeef" + ``` + + ``` + fromHex "" + ``` + + ``` + fromHex "1x" + ``` + }} + +Nat.fromInt : Int -> Optional Nat +Nat.fromInt n = + use Int >= + if n >= +0 then Some (truncate0 n) else None + +Nat.fromInt.doc : Doc +Nat.fromInt.doc = + {{ + Converts an {type Int} to a {type Nat}, or {None} if the {type Int} is less + than zero. + }} + +-- builtin Nat.fromText : Text -> Optional Nat + +Nat.fromText.doc : Doc +Nat.fromText.doc = + use Nat fromText + {{ + Parses a {type Text} value to a {type Nat} value. + + # Examples + + ``` + fromText "123" + ``` + + ``` + fromText "0x123" + ``` + + If the {type Text} is not a decimal or hexadecimal representation of an + unsigned integer, the result is {None}. + + ``` + fromText "123.45" + ``` + + ``` + fromText "-1" + ``` + + If the {type Text} represents a number that is too large to fit in a + {type Nat}, the result is {None}: + + ``` + fromText "18446744073709551620" + ``` + }} + +Nat.fromTextOrFail : Text ->{Exception} Nat +Nat.fromTextOrFail text = match Nat.fromText text with + None -> Exception.raise (failure "Invalid decimal" text) + Some v -> v + +Nat.fromTextOrFail.doc : Doc +Nat.fromTextOrFail.doc = + {{ + {fromTextOrFail} converts {type Text} to {type Nat} by assuming each + character is a decimal digit. Throws an {type Exception} if this is not the + case. + }} + +Nat.gcd : Nat -> Nat ->{Abort} Nat +Nat.gcd x y = + use Nat == + go x y = match y with + 0 -> x + _ -> go y (Nat.mod x y) + if x == 0 || y == 0 then abort else go x y + +Nat.gcd.doc : Doc +Nat.gcd.doc = + use Abort toOptional + use Nat gcd + {{ + `` gcd x y `` returns the greatest common divisor of `x` and `y`. + + # Examples + + ``` + toOptional (do gcd 8 12) () + ``` + + ``` + toOptional (do gcd 52 24) () + ``` + + ``` + toOptional (do gcd 1 900) () + ``` + }} + +test> Nat.gcd.tests.commonDivisor = runs 100 do + use Nat gcd mod + x = nonzeroNat() + y = nonzeroNat() + toDefault! (do Test.fail) do + dividesX = mod x (gcd x y) === 0 + dividesY = mod y (gcd x y) === 0 + expect (dividesX && dividesY) + +test> Nat.gcd.tests.multipleOfAnyCD = runs 100 do + use Nat * + x = nonzeroNat() + y = nonzeroNat() + z = nonzeroNat() + expect (toDefault! (do false) do Nat.mod (Nat.gcd (x * z) (y * z)) z === 0) + +-- builtin Nat.increment : Nat -> Nat + +Nat.increment.doc : Doc +Nat.increment.doc = + use Nat increment + {{ + Increment a {type Nat} value by one. + + # Examples + + ``` + increment 0 + ``` + + ``` + increment 1 + ``` + + If the argument is the maximum value, the result wraps around: + + ``` + increment maxNat + ``` + }} + +Nat.inRange : Nat -> Nat -> Nat -> Boolean +Nat.inRange fromInclusive toExclusive x = + Universal.gteq x fromInclusive && Universal.lt x toExclusive + +Nat.inRange.doc : Doc +Nat.inRange.doc = + use Nat <= inRange + {{ + `` inRange from to x `` returns true if `x` is in the range `from` + (inclusive) to `to` (exclusive). + + Always returns false if ``to <= from``. + + # Examples + + ``` + inRange 1 10 0 + ``` + + ``` + inRange 1 10 1 + ``` + + ``` + inRange 1 10 5 + ``` + + ``` + inRange 1 10 10 + ``` + + ``` + inRange 100 0 50 + ``` + }} + +test> Nat.inRange.test = runs 100 do + x = natInOrder() + y = natInOrder() + z = natInOrder() + match List.sort [x, y, z] with + [x, y, z] -> expect (Nat.inRange x z y || z === y) + _ -> expect true + +-- builtin Nat.isEven : Nat -> Boolean + +Nat.isEven.doc : Doc +Nat.isEven.doc = + use Nat isEven + {{ + Returns `` true `` if the given {type Nat} is divisible by 2, `` false `` + otherwise. + + # Examples + + ``` + isEven 1 + ``` + + ``` + isEven 2 + ``` + }} + +-- builtin Nat.isOdd : Nat -> Boolean + +Nat.isOdd.doc : Doc +Nat.isOdd.doc = + use Nat isOdd + {{ + Returns `` false `` if the given {type Nat} is divisible by 2, `` true `` + otherwise. + + # Examples + + ``` + isOdd 1 + ``` + + ``` + isOdd 2 + ``` + }} + +Nat.isPrefixOf : Nat -> Nat -> Boolean +Nat.isPrefixOf a b = + use Nat - == + takeLeftBits (64 - Nat.trailingZeros a) b == a + +Nat.isPrefixOf.doc : Doc +Nat.isPrefixOf.doc = + use Nat isPrefixOf + {{ + `` isPrefixOf a b `` is `` true `` if `a` and `b` are both ``0``, or both + have a 1 bit in the position of the least significant set bit of `a` and they + agree on all bits that are more significant than that. + + # Examples + + ``` + isPrefixOf 0 0 + ``` + + ``` + isPrefixOf 256 511 + ``` + + ``` + isPrefixOf 17293822569102704640 18446744073709551615 + ``` + + ``` + isPrefixOf 256 16 + ``` + + # See also + + * {Nat.isSuffixOf} checks if one number is a bitwise suffix of another. + }} + +Nat.isSetBit : Nat -> Nat -> Boolean +Nat.isSetBit which n = + use Nat == + bit which n == 1 + +Nat.isSetBit.doc : Doc +Nat.isSetBit.doc = + {{ + `` isSetBit n x `` returns `` true `` if the `n`-th bit of the {type Nat} `x` + is set, and `` false `` otherwise. + + # Examples + + ``` + isSetBit 8 256 + ``` + + ``` + isSetBit 7 256 + ``` + + ``` + isSetBit 63 maxNat + ``` + + This function always returns `` false `` if the bit offset is higher than + ``63``: + + ``` + isSetBit 64 maxNat + ``` + }} + +Nat.isSuffixOf : Nat -> Nat -> Boolean +Nat.isSuffixOf a b = + use Nat == + dropBits (Nat.leadingZeros a) b == a + +Nat.isSuffixOf.doc : Doc +Nat.isSuffixOf.doc = + use Nat isSuffixOf + {{ + `` isSuffixOf a b `` is `` true `` if `a` and `b` are both ``0``, or both + have a 1 bit in the position of the most significant set bit of `a` and they + agree on all bits that are less significant than that. + + # Examples + + ``` + isSuffixOf 0 0 + ``` + + ``` + isSuffixOf 511 256 + ``` + + ``` + isSuffixOf 255 4095 + ``` + + ``` + isSuffixOf 1 19923 + ``` + + # See also + + * {Nat.isPrefixOf} checks if one number is a bitwise prefix of another. + }} + +Nat.lcm : Nat -> Nat ->{Abort} Nat +Nat.lcm a b = + use Nat * / + a * b / Nat.gcd a b + +Nat.lcm.doc : Doc +Nat.lcm.doc = + use Abort toOptional + use Nat lcm + {{ + Computes the least common multiple of two natural numbers. Calls {abort} if + either argument is ``0``. + + # Examples + + ``` + toOptional (do lcm 4 6) () + ``` + + ``` + toOptional (do lcm 0 0) () + ``` + }} + +Nat.leadingOnes : Nat -> Nat +Nat.leadingOnes = Nat.xor maxNat >> Nat.leadingZeros + +Nat.leadingOnes.doc : Doc +Nat.leadingOnes.doc = + use Nat - + {{ + Counts the number of one bits at the front (i.e. the left) of the 64-bit + representation of a {type Nat}. + + # Examples + + ``` + leadingOnes maxNat + ``` + + ``` + leadingOnes 0 + ``` + + ``` + leadingOnes (maxNat - 2048) + ``` + }} + +test> Nat.leadingOnes.tests = test.verify do + use Nat + == > + ensuring do leadingOnes 0 == 0 + ensuring do leadingOnes maxNat == 64 + Each.run do + Each.repeat 100 + n = Random.nat! + lz = Nat.leadingZeros n + lo = leadingOnes n + ensuring do lo + lz > 0 + ensuring do lo + lz == Nat.max lo lz + +-- builtin Nat.leadingZeros : Nat -> Nat + +Nat.leadingZeros.doc : Doc +Nat.leadingZeros.doc = + use Nat leadingZeros + {{ + Counts the number of zero bits at the front (i.e. the left) of a {type Nat}. + + # Examples + + ``` + leadingZeros 0 + ``` + + ``` + leadingZeros 16777216 + ``` + + ``` + leadingZeros maxNat + ``` + }} + +Nat.lsfr : Nat -> Nat +Nat.lsfr n = + use Nat shiftRight + if Nat.isOdd n then Nat.xor (shiftRight n 1) 9223372036854775821 + else shiftRight n 1 + +Nat.lsfr.doc : Doc +Nat.lsfr.doc = + {{ + Implements a maximal-period + [linear feedback shift register](https://en.wikipedia.org/wiki/Linear-feedback_shift_register). + }} + +Nat.max : Nat -> Nat -> Nat +Nat.max a b = + use Nat < + if a < b then b else a + +Nat.max.doc : Doc +Nat.max.doc = + use Nat max + {{ + Returns the maximum of the two arguments. + + # Examples + + ``` + max 3 2 + ``` + + ``` + max 2 3 + ``` + + ``` + max 2 2 + ``` + }} + +Nat.maxNat : Nat +Nat.maxNat = 18446744073709551615 + +Nat.maxNat.doc : Doc +Nat.maxNat.doc = {{ The maximum value of a {type Nat}, 2^64 - 1. }} + +Nat.maybeMultiply : Nat -> Nat ->{Abort} Nat +Nat.maybeMultiply x y = + use Nat * / == + p = x * y + if p / x == y then p else abort + +Nat.maybeMultiply.doc : Doc +Nat.maybeMultiply.doc = + use Nat maybeMultiply + {{ + Multiplies two {type Nat} values. Calls {abort} if the result overflows the + size of {type Nat}. + + # Examples + + ``` + toOptional! do maybeMultiply 3 3 + ``` + + ``` + toOptional! do maybeMultiply maxNat 2 + ``` + }} + +Nat.min : Nat -> Nat -> Nat +Nat.min a b = + use Nat > + if a > b then b else a + +Nat.min.doc : Doc +Nat.min.doc = + use Nat min + {{ + Returns the minimum of the two arguments. + + # Examples + + ``` + min 3 2 + ``` + + ``` + min 2 3 + ``` + + ``` + min 2 2 + ``` + }} + +-- builtin Nat.mod : Nat -> Nat -> Nat + +Nat.mod.doc : Doc +Nat.mod.doc = + use Nat == mod + {{ + `` mod x y `` gets the modulus of dividing `x` by `y`, which is the remainder + `r` such that ``x / y * y + r == x``. + + The result of this function is not defined if the divisor (the second + arument) is ``0``. In that case it throws a runtime error. + + # Examples + + ``` + mod 9 3 + ``` + + ``` + mod 10 3 + ``` + }} + +Nat.modExp : Nat -> Nat -> Nat -> Nat +Nat.modExp base exponent m = + use Nat * / mod + go b e acc = + match e with + 0 -> acc + e -> + go + (mod (b * b) m) + (e / 2) + (if Nat.isEven e then acc else mod (b * acc) m) + match m with + 1 -> 0 + _ -> go (mod base m) exponent 1 + +Nat.modExp.doc : Doc +Nat.modExp.doc = + use Nat pow + {{ + `` modExp base k m `` gives the same results as {{ + docExample 3 do base k m -> Nat.mod (pow base k) m }} but does so without + computing `` pow base k `` first. Useful when `` pow base k `` is a huge + number that could overflow a {type Nat}. + + It is implemented using the "right to left" binary method of + [modular exponentiation](https://en.wikipedia.org/wiki/Modular_exponentiation) + which does `lg k` multiplications. + }} + +test> Nat.modExp.tests = + test.verify do + use Each range + base = range 0 10 + exponent = range 0 20 + modulus = range 1 1024 + ensureEqual + (modExp base exponent modulus) (Nat.mod (Nat.pow base exponent) modulus) + +Nat.msb : Nat -> Optional Nat +Nat.msb n = + use Nat - == + if n == 0 then None else Some (63 - Nat.leadingZeros n) + +Nat.msb.doc : Doc +Nat.msb.doc = + {{ + Returns the position of the highest order set bit in the 64-bit {type Nat}, + also known as the Most Significant Bit (MSB). If no bits are set, this + returns {None}. + + # Examples + + ``` + msb 0 + ``` + + ``` + msb 1 + ``` + + ``` + msb maxNat + ``` + + ``` + msb 256 + ``` + + ``` + msb 3735928559 + ``` + }} + +test> Nat.msb.test = + runs 10 do + use Nat + - + bits = List.map (do gen.oneOf [0, 1] ()) (Nat.range 0 64) + shift b acc = Nat.shiftLeft acc 1 + b + expect + (assertEquals + (msb (List.foldRight shift 0 bits)) + (Optional.map + ((-) (List.size bits - 1)) (List.firstIndexOf 1 (List.reverse bits)))) + +Nat.onesComplementSum : Nat -> Nat -> Nat -> Nat +Nat.onesComplementSum width a b = + use Nat + - + sum = a + b + dropBits (64 - width) (if isSetBit width sum then sum + 1 else sum) + +Nat.onesComplementSum.doc : Doc +Nat.onesComplementSum.doc = + {{ + `` onesComplementSum n x y `` is the `n`-bit ones' complement sum of the + low-order `n` bits of `x` and `y`. + + # Examples + + ``` + onesComplementSum 16 4660 22136 + ``` + + ``` + onesComplementSum 16 65535 1 + ``` + }} + +-- builtin Nat.or : Nat -> Nat -> Nat + +Nat.or.doc : Doc +Nat.or.doc = + use Nat or + {{ + Returns the bitwise OR of the two arguments. + + # Examples + + ``` + or 3 2 + ``` + + ``` + or 2 3 + ``` + + ``` + or 2 2 + ``` + }} + +-- builtin Nat.popCount : Nat -> Nat + +Nat.popCount.doc : Doc +Nat.popCount.doc = + use Nat popCount + {{ + Counts the number of set bits in the underlying 64-bit word. Known as the + __Hamming weight__ or __population count__. + + # Examples + + ``` + popCount 0 + ``` + + ``` + popCount maxNat + ``` + + ``` + popCount 256 + ``` + + ``` + popCount 255 + ``` + }} + +test> Nat.popCount.test = + runs 100 do + use Nat + + bits = List.map (do gen.oneOf [0, 1] ()) (Nat.range 0 64) + shift b acc = Nat.shiftLeft acc 1 + b + expect + (Nat.popCount (List.foldRight shift 0 bits) + === List.size (List.filter (x -> Universal.gt x 0) bits)) + +-- builtin Nat.pow : Nat -> Nat -> Nat + +Nat.pow.doc : Doc +Nat.pow.doc = + use Nat pow + {{ + Returns the first argument raised to the power of the second argument. + + # Examples + + ``` + pow 3 2 + ``` + + ``` + pow 2 3 + ``` + + ``` + pow 2 2 + ``` + }} + +Nat.product : [Nat] -> Nat +Nat.product = + use Nat * + List.foldLeft (*) 1 + +Nat.product.doc : Doc +Nat.product.doc = + use Nat product + {{ + @signature{product} {Nat.product as} returns the product of all the + values in `as`. If `as` is empty, returns `1`. + + # Examples + + ``` + product [1, 2, 3, 4] + ``` + + ``` + product [12] + ``` + + ``` + product [] + ``` + }} + +Nat.range : Nat -> Nat -> [Nat] +Nat.range start stopExclusive = + use Nat + - + initialize (stopExclusive - start) (x -> x + start) + +Nat.range.doc : Doc +Nat.range.doc = + use Nat range + {{ + Returns a {type List} of {type Nat}s from the given start (inclusive) to the + given end (exclusive). The size of the list is the difference between the + start and end. + + # Examples + + ``` + range 0 3 + ``` + + ``` + range 1 1 + ``` + + ``` + range 10 9 + ``` + }} + +Nat.rangeClosed : Nat -> Nat -> [Nat] +Nat.rangeClosed start stop = + use Nat + - + initialize (Nat.increment stop - start) (x -> x + start) + +Nat.rangeClosed.doc : Doc +Nat.rangeClosed.doc = + {{ + {{ docExample 2 do min max -> Nat.rangeClosed min max }} creates a list of + {type Nat} values from `min` to `max` (inclusive). + + Returns the empty list if the `min` value is lower than the `max` value. + + # Examples + + ``` + Nat.rangeClosed 1 10 + ``` + + ``` + Nat.rangeClosed 0 0 + ``` + + ``` + Nat.rangeClosed 1 0 + ``` + + ## See also + + {Nat.range} generates a range exclusive of the upper limit. + + {Int.rangeClosed} and {Int.range} create ranges on {type Int}. + }} + +Nat.reverseBits : Nat -> Nat +Nat.reverseBits x = + use Nat and or shiftLeft shiftRight + x1 = + or + (and (shiftRight x 1) 6148914691236517205) + (shiftLeft (and x 6148914691236517205) 1) + x2 = + or + (and (shiftRight x1 2) 3689348814741910323) + (shiftLeft (and x1 3689348814741910323) 2) + x3 = + or + (and (shiftRight x2 4) 1085102592571150095) + (shiftLeft (and x2 1085102592571150095) 4) + x4 = + or + (and (shiftRight x3 8) 71777214294589695) + (shiftLeft (and x3 71777214294589695) 8) + x5 = + or + (and (shiftRight x4 16) 281470681808895) + (shiftLeft (and x4 281470681808895) 16) + or (shiftRight x5 32) (shiftLeft x5 32) + +Nat.reverseBits.doc : Doc +Nat.reverseBits.doc = + {{ + `` reverseBits x `` reverses the bits in the binary representation of `x`. + + # Example + + ``` + Optional.flatMap + (Nat.toTextBase 16 << reverseBits) (Nat.fromHex "0123456789ABCDEF") + ``` + }} + +test> Nat.reverseBits.test.hom = test.verify do + use Nat and or + use Random nat + _ = Each.range 0 1000 + x = and nat() 4294967295 + y = and nat() 18446744069414584320 + ensureEqual (reverseBits (or x y)) (or (reverseBits x) (reverseBits y)) + +test> Nat.reverseBits.test.iso = test.verify do + _ = Each.range 0 1000 + x = Random.nat() + ensureEqual (reverseBits (reverseBits x)) x + +Nat.search : (Nat ->{e} Int) -> Nat -> Nat ->{e} Optional Nat +Nat.search hit bot top = + use Nat + / search + if Universal.gteq bot top then None + else + mid = (bot + top) / 2 + match Universal.ordering (hit mid) +0 with + Equal -> Some mid + Less -> search hit bot mid + Greater -> search hit (mid + 1) top + +Nat.search.doc : Doc +Nat.search.doc = + use Universal compare + {{ + `` search p lo hi `` uses a binary search algorithm to find the {type Nat} + between `lo` (inclusive) and `hi` (exclusive) for which `p` returns ``0``. + {search} returns {None} if no such {type Nat} is found. + + `p` should return `` -1 `` for {type Nat}s below the search value, and `` +1 + `` for {type Nat}s above the search value. + + See also: {findLowestZero} for a version of this that doesn't use + {type Optional}, returning `lo` instead of {None} when the element is not + found. + + # Examples + + ``` + search (compare 7) 5 10 + ``` + + ``` + e = Some 7 + s = [3, 5, 7, 8] + search (i -> compare e (List.at i s)) 0 (List.size s) + ``` + + ``` + search (compare 0) 5 10 + ``` + }} + +Nat.setBit : Nat -> Nat -> Nat +Nat.setBit which n = Nat.or n (Nat.shiftLeft 1 which) + +Nat.setBit.doc : Doc +Nat.setBit.doc = + {{ + `` setBit n x `` sets the `n`-th bit of the {type Nat} `x` to ``1``. + + # Examples + + ``` + setBit 0 0 + ``` + + ``` + setBit 2 1 + ``` + + ``` + setBit 24 0 + ``` + + If the bit offset is higher than ``63``, or if the bit is already set, this + function does nothing: + + ``` + setBit 64 0 + ``` + + ``` + setBit 0 1 + ``` + }} + +-- builtin Nat.shiftLeft : Nat -> Nat -> Nat + +Nat.shiftLeft.doc : Doc +Nat.shiftLeft.doc = + use Nat * shiftLeft + {{ + `` shiftLeft n k `` shifts the bits in `n` to the left by `k` positions. It + is equivalent to ``n * pow 2 k``. + + The bits shifted out of the left side are lost, and zero bits are shifted in + on the right. + + # Examples + + ``` + shiftLeft 1 8 + ``` + + ``` + shiftLeft 1 64 + ``` + }} + +-- builtin Nat.shiftRight : Nat -> Nat -> Nat + +Nat.shiftRight.doc : Doc +Nat.shiftRight.doc = + use Nat / shiftRight + {{ + `` shiftRight n k `` shifts the bits in `n` to the right by `k` positions. It + is equivalent to ``n / pow 2 k``. + + The bits shifted out of the right side are lost, and zero bits are shifted in + on the left. + + # Examples + + ``` + shiftRight 256 2 + ``` + + ``` + shiftRight 255 9 + ``` + }} + +Nat.subtractToInt : Nat -> Nat -> Int +Nat.subtractToInt = subtractToInt.impl + +Nat.subtractToInt.doc : Doc +Nat.subtractToInt.doc = + {{ + Subtracts one {type Nat} from another, returning an {type Int}. + + # Examples + + ``` + subtractToInt 3 2 + ``` + + If the second argument is greater than the first, the result is negative: + + ``` + subtractToInt 2 3 + ``` + + If the result would be greater than {maxInt}, or less than {minInt}, the + result wraps around: + + ``` + subtractToInt maxNat 1 + ``` + + ``` + subtractToInt 1000 maxNat + ``` + }} + +-- builtin Nat.subtractToInt.impl : Nat -> Nat -> Int + +Nat.sum : [Nat] -> Nat +Nat.sum = + use Nat + + List.foldLeft (+) 0 + +Nat.sum.doc : Doc +Nat.sum.doc = + use Nat sum + {{ + `Nat.sum ns` returns the sum of all the values in `ns`. If `ns` is empty, + returns ``0``. + + # Examples + + ``` + sum [1, 2, 3, 4] + ``` + + ``` + sum [12] + ``` + + ``` + sum [] + ``` + }} + +Nat.takeLeftBits : Nat -> Nat -> Nat +Nat.takeLeftBits k n = + use Nat - + Nat.and n <| Nat.shiftLeft maxNat (64 - k) + +Nat.takeLeftBits.doc : Doc +Nat.takeLeftBits.doc = + {{ + `` takeLeftBits k n `` returns a {type Nat} with only the high (left-most) + `k` bits of `n`. + + # Examples + + ``` + takeLeftBits 56 511 + ``` + + ``` + takeLeftBits 60 255 + ``` + + ``` + takeLeftBits 63 255 + ``` + }} + +test> Nat.takeLeftBits.test = test.verify do + use Nat == + n = Random.nat! + k = Each.range 0 64 + left = takeLeftBits k n + right = reverseBits n |> takeRightBits k |> reverseBits + ensure (left == right) + +Nat.takeRightBits : Nat -> Nat -> Nat +Nat.takeRightBits k n = + use Nat - + Nat.and n <| Nat.shiftRight maxNat (64 - k) + +Nat.takeRightBits.doc : Doc +Nat.takeRightBits.doc = + {{ + `` takeRightBits k n `` returns a {type Nat} with only the low (right-most) + `k` bits of `n`. + + # Examples + + ``` + takeRightBits 1 maxNat + ``` + + ``` + takeRightBits 2 maxNat + ``` + + ``` + takeRightBits 3 maxNat + ``` + + ``` + takeRightBits 64 maxNat + ``` + }} + +test> Nat.takeRightBits.test = test.verify do + use Nat == + n = Random.nat! + k = Each.range 0 64 + left = takeRightBits k n + right = reverseBits n |> takeLeftBits k |> reverseBits + ensure (left == right) + +Nat.toBytesBigEndian : Nat -> Bytes +Nat.toBytesBigEndian = encodeNat64be + +Nat.toBytesBigEndian.doc : Doc +Nat.toBytesBigEndian.doc = + {{ + Convert a {type Nat} to its {type Bytes}, in + [big endian order](https://en.wikipedia.org/wiki/Endianness). + + The resulting {type Bytes} will always have size 8. + + # Examples + + ``` + toBytesBigEndian 1 + ``` + + ``` + toBytesBigEndian (Nat.shiftLeft 8 60) + ``` + + ``` + toBytesBigEndian maxNat + ``` + + # See also + + * {fromBytesBigEndian} which converts in the other direction + }} + +test> Nat.toBytesBigEndian.test.ex1 = + use fromList impl + check + (toBytesBigEndian 255 === 0xs00000000000000ff + && toBytesBigEndian 65305 === 0xs000000000000ff19 + && toBytesBigEndian 3735928559 === 0xs00000000deadbeef) + +Nat.toBytesLittleEndian : Nat -> Bytes +Nat.toBytesLittleEndian = encodeNat64le + +Nat.toBytesLittleEndian.doc : Doc +Nat.toBytesLittleEndian.doc = + {{ + Convert a {type Nat} to its {type Bytes}, in + [little endian order](https://en.wikipedia.org/wiki/Endianness). + + The resulting {type Bytes} will always have size 8. + + # Examples + + ``` + toBytesLittleEndian 1 + ``` + + ``` + toBytesLittleEndian (Nat.shiftLeft 8 60) + ``` + + ``` + toBytesLittleEndian 18446744073709551615 + ``` + + # See also + + * {fromBytesLittleEndian} which converts in the other direction + }} + +test> Nat.toBytesLittleEndian.test.ex1 = + use fromList impl + check + (toBytesLittleEndian 255 === 0xsff00000000000000 + && toBytesLittleEndian 65305 === 0xs19ff000000000000 + && toBytesLittleEndian 3735928559 === 0xsefbeadde00000000) + +test> Nat.toBytesLittleEndian.test.prop = + check + let + nats = + [ 0 + , 1 + , 2 + , 3 + , 4 + , 5 + , 7 + , 13 + , 2347 + , 12320489 + , 38382 + , 230498230498 + , 9292733712 + , 802302393 + , maxNat + ] + roundTrip : Nat -> Boolean + roundTrip n = + (toOptional! do fromBytesLittleEndian (toBytesLittleEndian n)) + === Some n + List.all roundTrip nats + +-- builtin Nat.toInt : Nat -> Int + +Nat.toInt.doc : Doc +Nat.toInt.doc = + use Nat - pow toInt + {{ + Casts a {type Nat} to an {type Int} by interpreting the 64-bit unsigned + integer as a 64-bit signed integer. + + If the {type Nat} is greater than or equal to 2^63, the result will be + negative. + + # Examples + + ``` + toInt 0 + ``` + + ``` + toInt (pow 2 63 - 1) + ``` + + ``` + toInt (pow 2 63) + ``` + + ``` + toInt maxNat + ``` + }} + +-- builtin Nat.toText : Nat -> Text + +Nat.toText.doc : Doc +Nat.toText.doc = + use Nat toText + {{ + Convert a {type Nat} to a {type Text}. + + # Examples + + ``` + toText 0 + ``` + + ``` + toText 1 + ``` + + ``` + toText maxNat + ``` + }} + +Nat.toTextBase : Nat -> Nat -> Optional Text +Nat.toTextBase n x = Natural.toText n (Natural.fromNat x) + +Nat.toTextBase.doc : Doc +Nat.toTextBase.doc = + use Nat toTextBase + {{ + `` toTextBase radix n `` renders the {type Nat} `x` into {type Text} in the + specified radix. + + # Examples + + ``` + toTextBase 10 16777216 + ``` + + ``` + toTextBase 16 16777216 + ``` + + ``` + toTextBase 16 3735928559 + ``` + + ``` + toTextBase 2 9 + ``` + }} + +-- builtin Nat.trailingZeros : Nat -> Nat + +Nat.trailingZeros.doc : Doc +Nat.trailingZeros.doc = + use Nat trailingZeros + {{ + Returns the number of trailing zero bits in a {type Nat}. This is the number + of times that the {type Nat} can be divided by 2 before the remainder is no + longer zero. + + # Examples + + ``` + trailingZeros 0 + ``` + + ``` + trailingZeros maxNat + ``` + + ``` + trailingZeros 256 + ``` + }} + +Nat.twosComplement : Nat -> Nat +Nat.twosComplement n = + use Nat + + Nat.complement n + 1 + +Nat.twosComplement.doc : Doc +Nat.twosComplement.doc = + {{ + Returns the + [two's complement](https://en.wikipedia.org/wiki/Two%27s_complement) of a + {type Nat}. + + This results in the same bit pattern as the negative of the number, if the + {type Nat} is interpreted as a signed {type Int}. + + # Examples + + ``` + twosComplement 0 + ``` + + ``` + twosComplement maxNat + ``` + + ``` + twosComplement 18369614221190033421 + ``` + + # See also + + * {Nat.complement} to flip all the bits of a {type Nat}. + * {Int.negate} to get the negative of an {type Int}. + }} + +-- builtin Nat.xor : Nat -> Nat -> Nat + +Nat.xor.doc : Doc +Nat.xor.doc = + use Nat xor + {{ + Returns the bitwise XOR of the two arguments. + + # Examples + + ``` + xor 3 2 + ``` + + ``` + xor 207 243 + ``` + + ``` + xor 2 2 + ``` + }} + +(Optional.<*>) : Optional (a ->{g} b) -> Optional a ->{g} Optional b +(Optional.<*>) = cases + Some f, Some a -> Some (f a) + _, _ -> None + +Optional.<*>.doc : Doc +Optional.<*>.doc = + use Nat + toText + use Optional map toAbort + {{ + Applies an {type Optional} function to an {type Optional} value, returning an + {type Optional} result. + + # Examples + + ``` + Some toText <*> Some 5 + ``` + + ``` + Some toText <*> None + ``` + + This can be combined with {map} to apply an n-ary function across n + {type Optional} values: + + ``` + map (+) (Some 1) <*> Some 2 + ``` + + The above is equivalent to: + + ``` + toOptional! do + x = toAbort (Some 1) + y = toAbort (Some 2) + x + y + ``` + }} + +Optional.compareBy : + (a ->{g2} a ->{g1} Ordering) -> Optional a -> Optional a ->{g2, g1} Ordering +Optional.compareBy f = cases + None, None -> Equal + None, _ -> Less + _, None -> Greater + Some a, Some a2 -> f a a2 + +Optional.compareBy.doc : Doc +Optional.compareBy.doc = + use Optional compareBy + use Universal ordering + {{ + `` compareBy f x y `` compares `x` and `y` using the {type Ordering} function + `f`. + + # Examples + + ``` + compareBy ordering None None + ``` + + ``` + compareBy ordering None (Some 4) + ``` + + ``` + compareBy ordering (Some 4) None + ``` + + ``` + compareBy ordering (Some 2) (Some 4) + ``` + }} + +Optional.contains : a -> Optional a -> Boolean +Optional.contains elem = cases + Some a | elem === a -> true + _ -> false + +Optional.contains.doc : Doc +Optional.contains.doc = + use Optional contains + {{ + {contains} takes a target element and an optional value and tests whether the + optional value contains the target element. If the optional value is defined + and equivalent, returns true, otherwise returns false. + + Uses {===} from the builtin library to establish equality. + + Examples: + + ``` + contains 0 (Some 0) + ``` + + ``` + contains 1 (Some 0) + ``` + + ``` + contains 0 None + ``` + }} + +Optional.deprecated.mapOptional : (a ->{g} Optional b) -> [a] ->{g} [b] +Optional.deprecated.mapOptional f as = + use List +: + List.foldRight (a acc -> Optional.fold (do acc) (b -> b +: acc) (f a)) [] as + +Optional.deprecated.mapOptional.doc : Doc +Optional.deprecated.mapOptional.doc = + use deprecated mapOptional + {{ + # Deprecated + + This function is deprecated and may be removed in the future. Use + {List.filterMap} instead. + + # Description + + {mapOptional} applies a function which returns an optional value to each + item in a list of elements. Only the values which contain `` Some `` + transformed value are included in the list. + + Examples: + + ``` + mapOptional + (a -> (if Nat.isEven a then Some a else None)) [1, 2, 3, 4, 5, 6] + ``` + + ``` + mapOptional (_ -> None) [1, 2, 3, 4, 5, 6] + ``` + }} + +Optional.doc : Doc +Optional.doc = + use Optional flatMap fold getOrElse map orElse toAbort + {{ + An {type Optional} value is either {None} or it's {Some} containing a value + of some other type. + + # Accessing the value inside an {type Optional} + + To access the value inside an {type Optional}, use {map} to apply a + function to the value, if present. If the {type Optional} is {None}, the + function will not be applied. + + @signature{map} + + If the function you want to apply to the value inside itself returns an + {type Optional}, you can use {flatMap}: + + @signature{flatMap} + + Get the value inside an {type Optional} or a default value if the + {type Optional} is {None} using {getOrElse}: + + @signature{getOrElse} + + If the default value is expensive to compute or has effects, use + {getOrElse'} instead: + + @signature{getOrElse'} + + Use {getOrBug} to get the value inside an {type Optional} or crash with a + bug if the {type Optional} is {None}: + + @signature{getOrBug} + + {toAbort} will {abort} if the {type Optional} is {None}: + + @signature{toAbort} + + {note} throws an error with the {type Throw} ability if the {type Optional} + is {None}: + + @signature{note} + + Provide both a function and a default value to {fold} to apply the function + to the value inside the {type Optional} if it's {Some}, or return the + result of a default computation if it's {None}: + + @signature{fold} + + # Querying an {type Optional} + + Check if an {type Optional} is {Some}: + + @signature{isSome} + + Check if an {type Optional} is {None}: + + @signature{isNone} + + Check if an {type Optional} is {Some} and contains a value that satisfies a + predicate: + + @signature{Optional.exists} + + Check if an {type Optional} is {Some} and contains a particular value + (using {===}): + + @signature{Optional.contains} + + Check if an {type Optional} is {None} or contains a value that satisfies a + predicate: + + @signature{Optional.forAll} + + Return {None} if an {type Optional} is {None} or contains a value that does + not satisfy a predicate: + + @signature{Optional.filter} + + # Combining {type Optional}s + + {orElse} returns the first {type Optional} if it's {Some}, otherwise it + returns the second {type Optional}: + + @signature{orElse} + + Flatten an {type Optional} of {type Optional}: + + @signature{Optional.flatten} + + Combine two {type Optional}s using a function: + + @signature{Optional.map2} + + Combine two {type Optional}s and if both are {Some} put the values into a + tuple: + + @signature{Optional.zip} + + # Conversions to other types + + Convert an {type Optional} to a {type List}: + + @signature{Optional.toList} + + Convert an {type Optional} of a pair to a pair of {type Optional}s: + + @signature{Optional.unzip} + }} + +Optional.exists : (a ->{g} Boolean) -> Optional a ->{g} Boolean +Optional.exists p = cases + Some a -> p a + None -> false + +Optional.exists.doc : Doc +Optional.exists.doc = + use Nat isOdd + use Optional exists + {{ + {exists} returns false if the optional value is `` None `` or returns the + result of applying the predicate to `` Some `` optional value + + Examples: + + ``` + exists (n -> isOdd n) (Some 3) + ``` + + ``` + exists (n -> isOdd n) None + ``` + }} + +Optional.filter : (a ->{g} Boolean) -> Optional a ->{g} Optional a +Optional.filter p = cases + s@(Some a) | p a -> s + _ -> None + +Optional.filter.doc : Doc +Optional.filter.doc = + use Nat isEven + use Optional filter + {{ + {filter} returns `` Some `` value if the optional value is present and the + result of applying the predicate is true, otherwise it returns `` None `` + + Examples: + + ``` + filter (n -> isEven n) (Some 2) + ``` + + ``` + filter (n -> isEven n) (Some 3) + ``` + + ``` + filter (n -> isEven n) None + ``` + }} + +Optional.flatMap : (a ->{𝕖} Optional b) -> Optional a ->{𝕖} Optional b +Optional.flatMap f = cases + None -> None + Some a -> f a + +Optional.flatten : Optional (Optional a) -> Optional a +Optional.flatten = cases + Some (Some x) -> Some x + _ -> None + +Optional.flatten.doc : Doc +Optional.flatten.doc = + use Optional flatten + {{ + Returns the inner {type Optional} value if the outer {type Optional} value is + {Some}, and {None} otherwise. + + # Examples + + ``` + flatten (Some (Some 1)) + ``` + + ``` + flatten (Some None) + ``` + + ``` + flatten None + ``` + }} + +Optional.fold : '{g} b -> (a ->{g} b) -> Optional a ->{g} b +Optional.fold onError f = cases + Some a -> f a + None -> onError() + +Optional.fold.doc : Doc +Optional.fold.doc = + use Nat + + use Optional fold + {{ + {fold} applies the given function if the optional value is present or returns + the supplied default value if the optional value is ``None``. + + The default value parameter is a delayed computation to avoid uneccesary + evaluation. + + Examples: + + ``` + fold (do 0) (a -> a + 1) (Some 1) + ``` + + ``` + fold (do 0) (a -> a + 1) None + ``` + }} + +Optional.forAll : (a ->{g} Boolean) -> Optional a ->{g} Boolean +Optional.forAll p = cases + Some a -> p a + None -> true + +Optional.forAll.doc : Doc +Optional.forAll.doc = + use Nat isEven + use Optional forAll + {{ + {forAll} returns true if the optional value is `` None `` or returns the + result of applying the predicate to `` Some `` optional value + + Examples: + + ``` + forAll (n -> isEven n) (Some 2) + ``` + + ``` + forAll (n -> isEven n) (Some 3) + ``` + + ``` + forAll (n -> isEven n) None + ``` + }} + +Optional.foreach : (a ->{g} ()) -> Optional a ->{g} () +Optional.foreach f = cases + Some a -> f a + None -> () + +Optional.foreach.doc : Doc +Optional.foreach.doc = + {{ + `` Optional.foreach f optional `` performs the side effect `f` on the + contents of `optional` if `optional` is a {Some}. If `optional` is {None}, + then no action is performed. + }} + +Optional.getOrBug : msg -> Optional a -> a +Optional.getOrBug msg = cases + None -> bug msg + Some a -> a + +Optional.getOrBug.doc : Doc +Optional.getOrBug.doc = + {{ + `` getOrBug msg o `` calls {bug} if `o` is {None}, and returns the value + inside the {Some} otherwise. + + ``` + getOrBug "🎉" (Some 99) + ``` + + ``` + getOrBug "oh noes!" None + ``` + }} + +Optional.getOrElse : a -> Optional a -> a +Optional.getOrElse default = cases + Some a -> a + None -> default + +Optional.getOrElse.doc : Doc +Optional.getOrElse.doc = + use Optional getOrElse + {{ + If the optional value is present, {getOrElse} returns that unwrapped value, + otherwise returns the supplied default value if the optional value is `` None + `` + + Examples: + + ``` + getOrElse "not found" (Some "a") + ``` + + ``` + getOrElse "not found" None + ``` + }} + +Optional.getOrElse' : '{e} a -> Optional a ->{e} a +Optional.getOrElse' default = cases + Some a -> a + _ -> default() + +Optional.getOrElse'.doc : Doc +Optional.getOrElse'.doc = + {{ + If the optional value is present, {getOrElse'} returns that unwrapped value; + otherwise it returns the result of evaluating the supplied default + computation if the optional value is ``None``. + + Examples: + + ``` + getOrElse' (do "not found") (Some "a") + ``` + + ``` + getOrElse' (do "not found") None + ``` + }} + +Optional.isNone : Optional a -> Boolean +Optional.isNone = cases + None -> true + _ -> false + +Optional.isNone.doc : Doc +Optional.isNone.doc = + {{ + Returns `` true `` if the {type Optional} argument is {None}, otherwise + ``false``. + }} + +Optional.isSome : Optional a -> Boolean +Optional.isSome = cases + Some a -> true + None -> false + +Optional.isSome.doc : Doc +Optional.isSome.doc = + {{ + Returns `` false `` if the {type Optional} argument is {None}, otherwise + ``true``. + }} + +Optional.map : (a ->{𝕖} b) -> Optional a ->{𝕖} Optional b +Optional.map f = cases + None -> None + Some a -> Some (f a) + +Optional.map.doc : Doc +Optional.map.doc = + use Nat increment + use Optional map + {{ + Apply a function to the value in a {type Optional} value if it is {Some}. + Otherwise, return {None}. + + # Examples + + ``` + map increment (Some 2) + ``` + + ``` + map increment None + ``` + }} + +Optional.map2 : + (a ->{𝕖} b ->{𝕖} c) -> Optional a -> Optional b ->{𝕖} Optional c +Optional.map2 f oa ob = Optional.flatMap (a -> Optional.map (f a) ob) oa + +Optional.map2.doc : Doc +Optional.map2.doc = + use Nat + + use Optional map2 + {{ + Apply a function to two {type Optional} values, returning {None} if either + argument is {None}, or {Some} of the result otherwise. + + # Examples + + ``` + map2 (+) (Some 2) (Some 3) + ``` + + ``` + map2 (+) (Some 2) None + ``` + + ``` + map2 (+) None (Some 3) + ``` + + ``` + map2 (+) None None + ``` + }} + +Optional.None.doc : Doc +Optional.None.doc = + {{ The {type Optional} value that represents the absence of a value. }} + +Optional.note : e -> Optional a ->{Throw e} a +Optional.note e = cases + None -> throw e + Some a -> a + +Optional.note.doc : Doc +Optional.note.doc = + {{ + Turns an {type Optional} into a computation that throws the given error in + case that {type Optional} is {None}. + + # Example + + ``` + toEither do note "Oops!" None + ``` + }} + +Optional.orElse : Optional a -> Optional a -> Optional a +Optional.orElse a b = match a with + None -> b + Some _ -> a + +Optional.orElse.doc : Doc +Optional.orElse.doc = + use Optional orElse + {{ + Returns the first {type Optional} if it is {Some}, otherwise returns the + second {type Optional}. + + # Examples + + ``` + orElse (Some 1) (Some 2) + ``` + + ``` + orElse None (Some 2) + ``` + + ``` + orElse (Some 1) None + ``` + + ``` + orElse None None + ``` + }} + +Optional.Some.doc : Doc +Optional.Some.doc = + {{ The {type Optional} value that represents the presence of a value. }} + +Optional.toAbort : Optional a ->{Abort} a +Optional.toAbort = cases + None -> abort + Some a -> a + +Optional.toException : Text -> Type -> Optional a ->{Exception} a +Optional.toException msg t = cases + None -> Exception.raise (Failure t msg (Any ())) + Some a -> a + +Optional.toException.doc : Doc +Optional.toException.doc = + {{ + Converts an {type Optional} to an {type Exception}. + + If the {type Optional} is {None}, then an {type Exception} is raised with the + given {type Text} message and {type Type}. The content of the {type Failure} + is set to ``()``. + + If the {type Optional} is {Some}, then the value is returned. + + # Example + + ``` + catch do Optional.toException "The value is missing!" (typeLink Void) None + ``` + }} + +Optional.toGenericException : Optional a ->{Exception} a +Optional.toGenericException = cases + Some a -> a + None -> Exception.raise (failure "None" None) + +Optional.toGenericException.doc : Doc +Optional.toGenericException.doc = + {{ + Converts an {type Optional} value to an {type Exception} value by raising an + uninformative exception if the value is {None}. The message in the + {type Failure} exception is the string "None" and the type is {type Generic}. + + # Example + + ``` + catch do Optional.toGenericException None + ``` + + # See also + + * {Optional.toException} to provide a custom message and type. + }} + +Optional.toGenericExceptionWith : Text -> b -> Optional a ->{Exception} a +Optional.toGenericExceptionWith msg b = cases + Some a -> a + None -> raiseGeneric msg b + +Optional.toGenericExceptionWith.doc : Doc +Optional.toGenericExceptionWith.doc = + {{ + Extracts an {type Optional} value or throws a {type Generic} exception with + the given message and contents if the {type Optional} value is {None}. + + # Example + + ``` + catch do toGenericExceptionWith "No value" "hello" None + ``` + }} + +Optional.toList : Optional a -> [a] +Optional.toList = cases + None -> [] + Some x -> [x] + +Optional.toList.doc : Doc +Optional.toList.doc = + use Optional toList + {{ + {toList} turns an optional value into a list. If the optional value contains + `` Some `` value, returns the value in a list, if the optional value is `` + None `` returns an empty list + + Examples: + + ``` + toList (Some "a") + ``` + + ``` + toList None + ``` + }} + +test> Optional.toList.tests.roundtrip = runs 100 do + n = natInOrder() + expect (Optional.toList (List.head [n]) === [n]) + +Optional.unzip : Optional (a, b) -> (Optional a, Optional b) +Optional.unzip = cases + Some (a, b) -> (Some a, Some b) + None -> (None, None) + +Optional.unzip.doc : Doc +Optional.unzip.doc = + use Optional unzip + {{ + {unzip} takes an optional tuple as its parameter and returns a tuple of + optional values + + Examples: + + ``` + unzip (Some ("a", "b")) + ``` + + ``` + unzip None + ``` + }} + +Optional.zip : Optional a -> Optional b -> Optional (a, b) +Optional.zip optA optB = match (optA, optB) with + (Some a, Some b) -> Some (a, b) + _ -> None + +Optional.zip.doc : Doc +Optional.zip.doc = + use Optional zip + {{ + {zip} takes two optional arguments, if both contain `` Some `` value, it + returns a tuple of the two values wrapped in ``Some``, in all other cases, + returns `` None `` + + Examples: + + ``` + zip (Some "a") (Some "b") + ``` + + ``` + zip None (Some "b") + ``` + + ``` + zip None None + ``` + }} + +Ordering.andThen : Ordering -> Ordering -> Ordering +Ordering.andThen x y = match x with + Equal -> y + x -> x + +Ordering.andThen.doc : Doc +Ordering.andThen.doc = + {{ + Combine two orderings into one. Useful for comparing two objects on one + property and then if they are equal with regard to that property, compare + them on another. + + # Example + + ``` + x = [5, 6, 7] + y = [5, 9, 7, 6] + Ordering.andThen (compareOn List.head x y) (compareOn List.last x y) + ``` + }} + +Ordering.andThen.example.ex1 : Ordering +Ordering.andThen.example.ex1 = + x = [5, 6, 7] + y = [5, 9, 7, 6] + Ordering.andThen (compareOn List.head x y) (compareOn List.last x y) + +Ordering.doc : Doc +Ordering.doc = + use Universal ordering + {{ + Represents a comparison between two objects according to some order. {Less} + means the first object is below the second, {Equal} means they are the same, + and {Greater} means the second object is below the first. + + # Examples + + The {ordering} function is an example of a function that returns a value of + type {type Ordering}: + + ``` + ordering 1 2 + ``` + + ``` + ordering 2 1 + ``` + + ``` + ordering 2 2 + ``` + }} + +Ordering.eqBy : (a ->{f} b ->{g} Ordering) -> a -> b ->{f, g} Boolean +Ordering.eqBy ord a b = ord a b === Equal + +Ordering.eqBy.doc : Doc +Ordering.eqBy.doc = + use Universal ordering + {{ + Compares two values for equality. The given function is used to compare the + values. + + # Examples + + ``` + eqBy ordering 1 1 + ``` + + ``` + eqBy ordering 1 2 + ``` + }} + +Ordering.Equal.doc : Doc +Ordering.Equal.doc = + {{ + The {type Ordering} value that indicates that two values are equal. + + # Example + + ``` + Universal.ordering 1 1 + ``` + }} + +Ordering.gtBy : (a ->{g2} a ->{g1} Ordering) -> a -> a ->{g2, g1} Boolean +Ordering.gtBy o a a2 = match o a a2 with + Greater -> true + _ -> false + +Ordering.gtBy.doc : Doc +Ordering.gtBy.doc = + use Universal ordering + {{ + Compare two values using a given comparison function, returning `` true `` if + the first argument is greater than the second, and `` false `` otherwise. + + # Examples + + ``` + gtBy ordering [1, 2, 3] [1, 2, 4] + ``` + + ``` + gtBy ordering [1, 2, 3] [1, 2, 3] + ``` + + ``` + gtBy ordering [2] [1, 2, 3] + ``` + + ``` + gtBy ordering [1, 2, 3] [1, 2] + ``` + }} + +Ordering.gteqBy : (a ->{g2} a ->{g1} Ordering) -> a -> a ->{g2, g1} Boolean +Ordering.gteqBy o a a2 = match o a a2 with + Less -> false + _ -> true + +Ordering.gteqBy.doc : Doc +Ordering.gteqBy.doc = + use Universal ordering + {{ + Returns `` true `` if the first argument is greater than or equal to the + second argument according to the given comparison function. + + # Examples + + ``` + gteqBy ordering 2 1 + ``` + + ``` + gteqBy (compose2 Ordering.inverse ordering) 2 1 + ``` + }} + +Ordering.inverse : Ordering -> Ordering +Ordering.inverse = cases + Less -> Greater + Equal -> Equal + Greater -> Less + +Ordering.inverse.doc : Doc +Ordering.inverse.doc = + use Ordering inverse + {{ + Returns the inverse of the given {type Ordering}. + + # Examples + + ``` + inverse Less + ``` + + ``` + inverse Equal + ``` + + ``` + inverse Greater + ``` + }} + +Ordering.list.orderingBy : (a -> a ->{g} Ordering) -> [a] -> [a] ->{g} Ordering +Ordering.list.orderingBy f = cases + [], [] -> Equal + [], _ -> Less + _, [] -> Greater + x +: xs, y +: ys -> + match f x y with + Less -> Less + Greater -> Greater + Equal -> Ordering.list.orderingBy f xs ys + +Ordering.list.orderingBy.doc : Doc +Ordering.list.orderingBy.doc = + use Universal ordering + use list orderingBy + {{ + Lexicographical ordering over {type List}, using a custom ordering function. + If the first element of each input tests {Equal}, the next index is + consulted, and so on. + + ``` + a1 = [10, 20] + a2 = [10, 2, 3] + orderingBy (flip ordering) a1 a2 + ``` + + If one input is shorter than the other but the elements are all {Equal} up to + the end of the shorter {type List}, the ordering puts the shorter {type List} + first: + + ``` + a1 = [10, 20] + a2 = [10] + orderingBy (flip ordering) a1 a2 + ``` + + Note that this is the same behavior as {ordering} for lists, but with a + custom {type Ordering} function for the elements of the {type List}. + }} + +test> Ordering.list.orderingBy.tests = test.verify do + use List replicate + use Random natIn + use Universal ordering + Each.repeat 100 + p1 = replicate (natIn 0 10) do natIn 0 5 + p2 = replicate (natIn 0 10) do natIn 0 5 + list.orderingBy ordering p1 p2 |> ensureEqual (ordering p1 p2) + +Ordering.ltBy : (a ->{g2} a ->{g1} Ordering) -> a -> a ->{g2, g1} Boolean +Ordering.ltBy o a a2 = match o a a2 with + Less -> true + _ -> false + +Ordering.ltBy.doc : Doc +Ordering.ltBy.doc = + use Universal ordering + {{ + Compare two values using a given comparison function, returning `` true `` if + the first argument is less than the second, and `` false `` otherwise. + + # Examples + + ``` + ltBy ordering [1, 2, 3] [1, 2, 4] + ``` + + ``` + ltBy ordering [1, 2, 3] [1, 2, 3] + ``` + + ``` + ltBy ordering [2] [1, 2, 3] + ``` + + ``` + ltBy ordering [1, 2, 3] [1, 2] + ``` + }} + +Ordering.lteqBy : (a ->{g2} a ->{g1} Ordering) -> a -> a ->{g2, g1} Boolean +Ordering.lteqBy o a a2 = match o a a2 with + Greater -> false + _ -> true + +Ordering.lteqBy.doc : Doc +Ordering.lteqBy.doc = + use Universal ordering + {{ + Returns `` true `` if the first argument is less than or equal to the second + argument according to the given comparison function. + + # Examples + + ``` + lteqBy ordering 1 2 + ``` + + ``` + lteqBy (compose2 Ordering.inverse ordering) 1 2 + ``` + }} + +Ordering.maxBy : (a ->{g2} a ->{g1} Ordering) -> a -> a ->{g2, g1} a +Ordering.maxBy o a a2 = match o a a2 with + Less -> a2 + _ -> a + +Ordering.maxBy.doc : Doc +Ordering.maxBy.doc = + use Universal ordering + {{ + Returns the larger of two values, using a comparison function that defines an + {type Ordering}. + + # Example + + ``` + maxBy ordering 1 2 + ``` + + ``` + maxBy ordering 2 1 + ``` + + ``` + maxBy ordering 1 1 + ``` + }} + +Ordering.medianOf3By : (a ->{g2} a ->{g1} Ordering) -> a -> a -> a ->{g2, g1} a +Ordering.medianOf3By o a a2 a3 = + go a a2 a3 = if gtBy o a a2 then go2 a2 a a3 else go2 a a2 a3 + go2 a a2 a3 = if gtBy o a2 a3 then go3 a a3 a2 else go3 a a2 a3 + go3 a a2 a3 = if ltBy o a2 a then a else a2 + go a a2 a3 + +Ordering.medianOf3By.doc : Doc +Ordering.medianOf3By.doc = + use Universal ordering + {{ + `` medianOf3By o a1 a2 a3 `` computes the median of 3 values according to `o` + using 3 calls to `o` (this is the minimum). + + ``` + medianOf3By ordering 2 1 3 + ``` + + ``` + medianOf3By ordering 3 1 2 + ``` + }} + +test> Ordering.medianOf3By.test = + use Nat == + use Universal ordering + check + (medianOf3By ordering 1 2 3 == 2 && medianOf3By ordering 1 3 2 == 2 + && medianOf3By ordering 2 1 3 == 2 + && medianOf3By ordering 2 3 1 == 2 + && medianOf3By ordering 3 2 1 == 2 + && medianOf3By ordering 3 1 2 == 2) + +Ordering.minBy : (a ->{g2} a ->{g1} Ordering) -> a -> a ->{g2, g1} a +Ordering.minBy o a a2 = match o a a2 with + Greater -> a2 + _ -> a + +Ordering.minBy.doc : Doc +Ordering.minBy.doc = + use Universal ordering + {{ + Returns the smaller of two values, using a comparison function that defines + an {type Ordering}. + + # Example + + ``` + minBy ordering 1 2 + ``` + + ``` + minBy ordering 2 1 + ``` + + ``` + minBy ordering 1 1 + ``` + }} + +Ordering.pair.orderingBy : + (a -> a ->{g2} Ordering) + -> (b -> b ->{g} Ordering) + -> (a, b) + -> (a, b) + ->{g, g2} Ordering +Ordering.pair.orderingBy o1 o2 = cases + (a1, b1), (a2, b2) -> + match o1 a1 a2 with + Less -> Less + Greater -> Greater + Equal -> o2 b1 b2 + +Ordering.pair.orderingBy.doc : Doc +Ordering.pair.orderingBy.doc = + use Universal ordering + {{ + A custom ordering function over pairs, given a way or ordering the two types + in the pair. + + ``` + o = pair.orderingBy (flip ordering) ordering + [o (10, 0) (9, 1), o (10, 10) (10, 9)] + ``` + }} + +test> Ordering.pair.orderingBy.tests = test.verify do + use Random natIn + use Universal ordering + Each.repeat 100 + p1 = (natIn 0 5, natIn 0 5) + p2 = (natIn 0 5, natIn 0 5) + pair.orderingBy ordering ordering p1 p2 |> ensureEqual (ordering p1 p2) + +(Pattern.+) : Pattern a -> Pattern a -> Pattern a +(Pattern.+) = Pattern.or + +Pattern.+.doc : Doc +Pattern.+.doc = + use Pattern or run + {{ + `` or p1 p2 `` matches `p1`, or if that fails, matches `p2`. Thus, `p2` is + not consulted if `p1` succeeds. + + ``` + run (or (literal "❤️") (literal "💙")) "💙" + ``` + + This example shows the left-biasing. Even though the second argument would be + a match, it's not consulted: + + ``` + run (or (literal "abra") (literal "abracadabra")) "abracadabra" + ``` + }} + +-- builtin Pattern.capture : Pattern a -> Pattern a + +Pattern.capture.doc : Doc +Pattern.capture.doc = + use Pattern capture join run + use patterns digit letter + {{ + `` capture p `` captures the segment of the input matched by `p`, or nothing + if `p` fails. + + ``` + run + (join [capture (some letter), literal ", ", capture (some digit)]) + "abcd, 123" + ``` + + If the argument to this function includes subcaptures, those are discarded: + + ``` + run (capture (join [capture letter, capture digit])) "a2" + ``` + + Notice that only the outer pattern capture is included in the output. + }} + +-- builtin Pattern.captureAs : a -> Pattern a -> Pattern a + +Pattern.captureAs.doc : Doc +Pattern.captureAs.doc = + use Pattern capture + {{ + Behaves like {capture} but returns its first argument as the capture. + + # Example + + This example pattern consumes any amount of whitespace and returns a single + space: + + ``` + sp = captureAs " " (some (patterns.char whitespace)) + captures + (sepMany sp (capture (some patterns.digit))) "1 2\t2\n 3\r\n 46 89" + ``` + }} + +Pattern.captures : Pattern t -> t -> [t] +Pattern.captures p t = match Pattern.run p t with + Some (cs, _) -> cs + None -> [] + +Pattern.captures.doc : Doc +Pattern.captures.doc = + {{ + Returns a {type List} of all the captures of a {type Pattern} from a value. + + # Example + + ``` + captures + (sepMany space (Pattern.capture (some wordChar))) "The quick brown fox" + ``` + + # See also + + * {Pattern.run} - also returns the unmatched remainder of the input + * {Pattern.drop} - only returns the unmatched remainder of the input + }} + +Pattern.doc : Doc +Pattern.doc = + use Pattern + capture join replicate run + use patterns digit letter + {{ + {type Pattern} is a type used for regular expression matching. A {{ + docExample 1 do p -> (p : Pattern Text) }} is a pattern that matches + {type Text} input, and a {{ docExample 1 do p -> (p : Pattern Bytes) }} is a + pattern that matches {type Bytes} + input.{{ + docAside + {{ + Currently, only {type Text} patterns are supported. A future release will + add {type Bytes} patterns. + }} + }} + + ``` + run (many digit) "1123456abc" + ``` + + The `` [] `` in the above output is a list of __captures__, and the `` "abc" + `` is the remainder of the input after the {{ docExample 0 do many digit }} + successfully matches. + + A {type Pattern} is built up using functions like {many}, {literal}, + {charRange}, and so on, and run with either {run} or {isMatch}: + + @signatures{run, isMatch} + + # Tutorial + + Let's start with a few simple examples: + + ``` + run (many digit) "1123456abc" + ``` + + ``` + run + (join [many (charRange ?a ?z), capture (many anyChar)]) "abracadabra123" + ``` + + ``` + run (many (chars "🍎🍏")) "🍏🍎🍏🍎🍎🍏123" + ``` + + ``` + run (capture (many (notChars " \n\r\t"))) "abc 123" + ``` + + The {run} function returns a list of captures and the remainder. If you + want the pattern to match the full {type Text}, add {eof} to the end: + + ``` + isMatch (join [many digit, eof]) "1123456abc" + ``` + + This fails, since there's more text after the digits. + + Note that `` many p `` will match 0 or more of `p`, so this succeeds + (without consuming any of the input): + + ``` + run (many digit) "aaa" + ``` + + If you want at least one match of the pattern, you can do the following: + + ``` + some p = join [p, many p] + isMatch (some digit) "abc" + ``` + + ## Capturing + + Use {capture} to capture portions of the input: + + ``` + run (join [many space, capture (many letter)]) " abc" + ``` + + You can capture as many things as you want: + + ``` + letters = capture (many letter) + run (join [letters, many digit, letters]) "abc1123456yay" + ``` + + You can also use {captureAs} to capture a specific value instead of the + matched text: + + ``` + sp = captureAs " " (Pattern.some (patterns.char whitespace)) + captures + (sepMany sp (capture (Pattern.some digit))) + "1 2\t2\n 3\r\n 46 89" + ``` + + ## Choice + + You can use {+} (also called {Pattern.or}) to choose (left-biased) + between two different patterns: + + ``` + run (many (literal "if" + literal "then")) "ifthenifthenabc" + ``` + + ## Repetition + + In addition to {many} (0 or more repetitions) or {{ + docLink (docEmbedTermLink do Pattern.some) }} (1 or more repetitions), + you can use {replicate} to repeat a pattern a range of times (inclusive + on both ends): + + ``` + run (replicate 3 3 (literal "hi")) "hihihithere" + ``` + + ``` + run (replicate 0 10 (literal "hi")) "hihihitherethere" + ``` + + ## Primitive patterns + + In addition, there's a variety of simple patterns you can use: + + * @inlineSignature{anyChar} matches any single character. + * @inlineSignature{literal} matches a {type Text}. + * @inlineSignature{chars} matches a single {type Char} which is present + in the given {type Text}. + * @inlineSignature{notChars} matches a single {type Char} which is + __not__ in the given {type Text}. + * @inlineSignature{charRange} matches a single {type Char} which is in + the given range (inclusive on both sides). + * @inlineSignature{notCharRange} matches a single {type Char} which is + __not__ in the given range (inclusive on both sides). + * @inlineSignature{letter} matches a single letter. + * @inlineSignature{digit} matches a single digit. + * @inlineSignature{patterns.punctuation} matches a single punctuation + character. + * @inlineSignature{space} matches a single whitespace character. + + ## Other functions + + * @inlineSignature{join} sequences 0 or more patterns. + * @inlineSignature{sepMany} matches 0 or more repetitions of a + {type Pattern}, separated by the given delimiter. + * {{ docSignatureInline (docEmbedSignatureLink do sepSome) }} matches 1 + or more repetitions of a {type Pattern}, separated by the given + delimiter. + }} + +Pattern.drop : Pattern t -> t -> t +Pattern.drop p t = match Pattern.run p t with + Some (_, r) -> r + None -> t + +Pattern.drop.doc : Doc +Pattern.drop.doc = + {{ + Drops the first match of a {type Pattern} from a value. + + # Examples + + ``` + Pattern.drop (some (chars "abr")) "abracadabra" + ``` + + # See also + + * {Pattern.run} - also returns any capture groups captured by the pattern + * {captures} - only returns the capture groups + }} + +Pattern.empty : Pattern a +Pattern.empty = Pattern.join [] + +Pattern.empty.doc : Doc +Pattern.empty.doc = + {{ + Succeeds without consuming any of the input. + + ``` + Pattern.run Pattern.empty "hello!" + ``` + }} + +-- builtin Pattern.isMatch : Pattern a -> a -> Boolean + +Pattern.isMatch.doc : Doc +Pattern.isMatch.doc = + {{ + `` isMatch p input `` returns `` true `` if the pattern `p` matches any + prefix of `input`. + + ``` + isMatch patterns.letter "a1" + ``` + + ``` + isMatch patterns.digit "a99" + ``` + + Use {isFullMatch} to ensure that the pattern matches the full input. + }} + +-- builtin Pattern.join : [Pattern a] -> Pattern a + +Pattern.join.doc : Doc +Pattern.join.doc = + use Pattern join + {{ + `` join ps `` sequences a list of patterns. The second pattern in the list is + run on the remainder of the first, and so on, until all patterns are matched + or one of the patterns fails. + + ``` + Pattern.run + (join [many (literal "hi"), Pattern.capture (many patterns.letter)]) + "hihihibob" + ``` + + ``` + isMatch (join [literal "ab", literal "cd"]) "abxy" + ``` + }} + +-- builtin Pattern.many : Pattern a -> Pattern a + +Pattern.many.doc : Doc +Pattern.many.doc = + use Pattern capture run + use patterns letter + {{ + `` many p `` matches 0 or more repetitions of `p`. + + ``` + run (many letter) "abracadabra123" + ``` + + Notice this doesn't capture anything. It can be combined with {capture} if + desired: + + ``` + run (capture (many letter)) "abracadabra123" + ``` + + Use {{ docLink (docEmbedTermLink do some) }} to match 1 or more repetitions, + or {Pattern.replicate} to match a specific range of repetitions. + }} + +Pattern.oneOf : List.Nonempty (Pattern a) -> Pattern a +Pattern.oneOf = reduceRight Pattern.or + +Pattern.oneOf.doc : Doc +Pattern.oneOf.doc = + {{ + Matches if any of the given {type Pattern}s match. + + # Example + + ``` + Pattern.run + (Pattern.capture (Pattern.oneOf (literal "foo" +| [literal "bar"]))) + "food" + ``` + }} + +Pattern.optional : Pattern a -> Pattern a +Pattern.optional p = Pattern.replicate 0 1 p + +Pattern.optional.doc : Doc +Pattern.optional.doc = + use Pattern optional run + {{ + `` optional p `` matches 0 or 1 instances of `p`. + + ``` + run (optional patterns.letter) "123" + ``` + + ``` + run (optional patterns.digit) "123" + ``` + }} + +-- builtin Pattern.or : Pattern a -> Pattern a -> Pattern a + +-- builtin Pattern.replicate : Nat -> Nat -> Pattern a -> Pattern a + +Pattern.replicate.doc : Doc +Pattern.replicate.doc = + use Pattern capture replicate run + use patterns letter + {{ + `` replicate n m p `` matches between `n` and `m` repetitions of `p`. + + It tries to consume as many repetitions as possible while still being less + than or equal to `m`. + + ``` + run (replicate 0 1 letter) "123" + ``` + + ``` + run (capture (replicate 5 7 patterns.digit)) "1234567,abc" + ``` + + ``` + run (capture (replicate 4 4 letter)) "aaaab123" + ``` + + Also see {Pattern.optional}, equivalent to ``replicate 0 1 p``. + }} + +-- builtin Pattern.run : Pattern a -> a -> Optional ([a], a) + +Pattern.run.doc : Doc +Pattern.run.doc = + {{ + Run a {type Pattern} on a value and return a result which is either {None} if + the pattern did not match, or {Some} if it did. The result is a pair: + + ``` + Pattern.run (Pattern.capture (many (notChars " "))) "abc 123" + ``` + + The first element of the pair is a list of capture groups, and the second + element is the remainder of the input string after the pattern successfully + matches. + + See {type Pattern} for more information on patterns. + }} + +Pattern.sepMany : Pattern a -> Pattern a -> Pattern a +Pattern.sepMany by p = Pattern.or (sepSome by p) Pattern.empty + +Pattern.sepMany.doc : Doc +Pattern.sepMany.doc = + use Pattern capture run + {{ + `` sepMany by p `` consumes 0 or more repetitions of `p`, separated by the + delimiter pattern `by`. + + ``` + p = sepMany (literal ",") (capture (some patterns.digit)) + run p "123,456,789" + ``` + + ``` + p = + sepMany + (many space) + (capture (some (Pattern.or patterns.letter patterns.punctuation))) + run p "once. upon. a. time." + ``` + + Also see {{ docLink (docEmbedTermLink do sepSome) }}. + }} + +Pattern.sepSome : Pattern a -> Pattern a -> Pattern a +Pattern.sepSome by p = + use Pattern join + join [p, many (join [by, p])] + +Pattern.sepSome.doc : Doc +Pattern.sepSome.doc = + use Pattern capture run + {{ + {{ docExample 2 do by p -> sepSome by p }} consumes 1 or more repetitions of + `p`, separated by the delimiter pattern `by`. + + ``` + p = sepSome (literal ",") (capture (many patterns.digit)) + run p "123,456,789" + ``` + + ``` + p = + sepSome + (many space) + (capture (many (Pattern.or patterns.letter patterns.punctuation))) + run p "good day, dear programmer!" + ``` + + Also see {sepMany}. + }} + +Pattern.some : Pattern a -> Pattern a +Pattern.some a = Pattern.join [a, many a] + +Pattern.some.doc : Doc +Pattern.some.doc = + use Pattern run + {{ + {{ docExample 1 do p -> some p }} matches 1 or more repetitions of `p`. Use + `` many p `` if you want 0 or more repetitions. + + ``` + run (some (literal "a")) "aaabc" + ``` + + ``` + run (some (literal "x")) "abc" + ``` + }} + +Pretty.Annotated.doc : Doc +Pretty.Annotated.doc = + {{ + The type {type Annotated} represents some annotated text type. It is used by + the {type Pretty} type to represent annotations on text. + + The {type Annotated} type is parameterized by the type of the annotation and + the type of the text. It has the following constructors: + + * {Annotated.Append} - the annotated concatenation of zero or more + {type Annotated} values + * {Annotated.Empty} - the empty {type Annotated} value + * {Annotated.Group} - creates a logical annotated group whose contents are + either all rendered or none of them are rendered + * {Indent} - creates an indentation level. The first argument is the prefix + to render before the first line of the annotated value, the second argument + is the prefix for subsequent lines, and the third argument is the annotated + value to indent. + * {Lit} - a literal annotated value + * {OrElse} - represents a choice of two annotated values. A renderer should + try to render the first value, and if it cannot (for example if there's no + space), try to render the second value. + * {Annotated.Table} - an annotated table of {type Annotated} values. The + first argument is the annotation and the second argument is the {type List} + of {type List}s of {type Annotated} values. The outer {type List} + represents the rows of the table, and the inner {type List} represents the + columns of the table. + * {Wrap} - Adds word wrapping to the annotated value, allowing the renderer + to break the annotated value into multiple lines. + }} + +Pretty.append : Pretty txt -> Pretty txt -> Pretty txt +Pretty.append p1 p2 = + match (Pretty.get p1, Pretty.get p2) with + (_, Annotated.Empty) -> p1 + (Annotated.Empty, _) -> p2 + (Annotated.Append _ ps1, Annotated.Append _ ps2) -> + Pretty (Annotated.Append () (ps1 List.++ ps2)) + (Annotated.Append _ ps1, p2) -> + Pretty (Annotated.Append () (ps1 List.:+ p2)) + (p1, Annotated.Append _ ps2) -> + Pretty (Annotated.Append () (p1 List.+: ps2)) + (p1, p2) -> Pretty (Annotated.Append () [p1, p2]) + +Pretty.append.doc : Doc +Pretty.append.doc = {{ Append two {type Pretty} values. }} + +Pretty.doc : Doc +Pretty.doc = + {{ + The {type Pretty} type is used to represent pretty-printed text. It is a + wrapper around some underlying text type (e.g. {type Text}), and provides + functionality for formatting, line wrapping, and other pretty-printing + concerns. + + The {type Pretty} type is used by UCM internally to display {type Doc} values + in the terminal. + + # Constructing {type Pretty} values + + The empty {type Pretty} value: + + @signature{Pretty.empty} + + Construct a {type Pretty} from an unadorned value of the underlying text + type: + + # Combining {type Pretty} values + + Append two {type Pretty} values: + + @signature{Pretty.append} + + Combine a list of {type Pretty} values into one: + + @signature{Pretty.join} + + Combine a list of {type Pretty} values, separated by a given separator: + + @signature{sepBy} + + Format a list of rows into a table: + + @signature{table} + + Choose the first {type Pretty} value if there's enough space to print it, + otherwise choose the second: + + @signature{Pretty.orElse} + + # Formatting + + Indent by prefixing each line with a given string: + + @signature{indent} + + Indent, but with a different string for the first line: + + @signature{indent'} + + Add word-wrapping: + + @signature{wrap} + + Construct a logical grouping. Layout algorithms will try to keep the group + together, but will break it if necessary: + + @signature{Pretty.group} + + # Transforming {type Pretty} values + + Transform a {type Pretty} value with a function: + + @signature{Pretty.map} + + # Conversions and applications + + Render a {type Doc} value to a {type Pretty} containing {type ConsoleText}: + + @signature{docFormatConsole} + + Get the underlying {type Annotated} text: + + @signature{Pretty.get} + }} + +Pretty.empty : Pretty txt +Pretty.empty = Pretty Annotated.Empty + +Pretty.empty.doc : Doc +Pretty.empty.doc = {{ The empty {type Pretty} value. }} + +Pretty.get : Pretty txt -> Annotated () txt +Pretty.get = cases Pretty p -> p + +Pretty.get.doc : Doc +Pretty.get.doc = + {{ Get the underlying {type Annotated} text from a {type Pretty} value. }} + +Pretty.group : Pretty txt -> Pretty txt +Pretty.group p = Pretty (Annotated.Group () (Pretty.get p)) + +Pretty.group.doc : Doc +Pretty.group.doc = + {{ + Group a {type Pretty} value so that it will be rendered without breaking if + possible. + }} + +Pretty.indent : Pretty txt -> Pretty txt -> Pretty txt +Pretty.indent by p = + use Pretty get + Pretty (Indent () (get by) (get by) (get p)) + +Pretty.indent.doc : Doc +Pretty.indent.doc = + {{ + `` indent i p `` indents the {type Pretty} value `p` by prefixing each line + with the {type Pretty} value `i`. + }} + +Pretty.indent' : Pretty txt -> Pretty txt -> Pretty txt -> Pretty txt +Pretty.indent' initialIndent indentAfterNewline p = + use Pretty get + Pretty (Indent () (get initialIndent) (get indentAfterNewline) (get p)) + +Pretty.indent'.doc : Doc +Pretty.indent'.doc = + {{ + `` indent' first rest `` indents the first line of the pretty-printed text by + prepending `first` to it, and prepends `rest` to all subsequent lines. + }} + +Pretty.join : [Pretty txt] -> Pretty txt +Pretty.join = + go acc = cases + [] -> acc + h +: t -> go (Pretty.append acc h) t + go Pretty.empty + +Pretty.join.doc : Doc +Pretty.join.doc = {{ Concatenate a list of {type Pretty} values together. }} + +Pretty.lit : txt -> Pretty txt +Pretty.lit txt = Pretty (Lit () txt) + +Pretty.lit.doc : Doc +Pretty.lit.doc = + {{ Create a {type Pretty} value from a value of the underlying text type. }} + +Pretty.map : (txt ->{g} txt2) -> Pretty txt ->{g} Pretty txt2 +Pretty.map f p = + use Annotated Append Empty Group Table + use List map + go = cases + Empty -> Empty + Group _ p -> Group () (go p) + Lit _ t -> Lit () (f t) + Wrap _ p -> Wrap () (go p) + OrElse _ p1 p2 -> OrElse () (go p1) (go p2) + Indent _ i0 iN p -> Indent () (go i0) (go iN) (go p) + Append _ ps -> Append () (map go ps) + Table _ ps -> Table () (map (map go) ps) + Pretty (go (Pretty.get p)) + +Pretty.map.doc : Doc +Pretty.map.doc = + {{ Map a function over the underlying text type of a {type Pretty} value. }} + +Pretty.orElse : Pretty txt -> Pretty txt -> Pretty txt +Pretty.orElse p1 p2 = + use Pretty get + Pretty (OrElse () (get p1) (get p2)) + +Pretty.orElse.doc : Doc +Pretty.orElse.doc = + {{ + `` Pretty.orElse p1 p2 `` tries to render `p1`, and if that doesn't fit on + the current line, then it tries to render `p2`. + }} + +Pretty.sepBy : Pretty txt -> [Pretty txt] -> Pretty txt +Pretty.sepBy sep ps = + use Pretty append + go acc insertSep = cases + [] -> acc + ps | insertSep -> go (append acc sep) false ps + h +: t -> go (append acc h) true t + go Pretty.empty false ps + +Pretty.sepBy.doc : Doc +Pretty.sepBy.doc = + {{ + Render a {type List} of {type Pretty} values separated by a given + {type Pretty} value. + }} + +Pretty.table : [[Pretty txt]] -> Pretty txt +Pretty.table rows = + use List map + Pretty (Annotated.Table () (map (map Pretty.get) rows)) + +Pretty.table.doc : Doc +Pretty.table.doc = + {{ Render a {type List} of {type List} of {type Pretty} values as a table. }} + +Pretty.wrap : Pretty txt -> Pretty txt +Pretty.wrap p = Pretty (Wrap () (Pretty.get p)) + +Pretty.wrap.doc : Doc +Pretty.wrap.doc = + {{ + Turn word wrapping on for a {type Pretty} value. Wrapping is off by default. + When wrapping is on, the pretty-printer will attempt to break lines at + whitespace characters. + }} + +README : Doc +README = + {{ + # The Unison Base Library + + This library provides essential Unison functionality, data types, and + shared abstractions used by most other libraries and applications, as well + as by Unison itself. + + ## What's here? + + This document is organized into a number of sections, each giving a + high-level overview of some region of the Base library. You can find + more detailed documentation by following the links. In general, the docs + for a type provide an overview of operations on that type, and the docs + for a specific function give more detail and more examples. + + ## Contents + + * [Primitive types]({primitiveTypes}) — {type Nat}, {type Int}, + {type Float}, {type Boolean}, {type Bytes}, {type Text}, and + {type Char}, as well as operations on these types. + * [Collections]({collectionTypes}) like {type List}, {type Set}, + {type Map}, {type data.Array} and others. + * [Tuples]({Tuple.doc}) – the {type Tuple} type and associated + functions. + * [Basic data types]({basicDataTypes}) – {type Unit}, {type Void}, + {type Optional}, and {type Either}. + * [Basic abilities]({basicAbilities}) such as {type Abort}, {type Ask}, + {type Random}, {type Store}, {type Stream}, {type Each}, {type Throw}, + and {type Exception}. + * [Input and output]({IO.doc}) – the {type IO} ability and basic I/O + functions. + * [Basic networking]({networkAccess}) – networking primitives like + {type Socket} and {type Tls}. + * [Concurrency]({concurrency}) – threads, mutable memory cells, and + software-transactional memory. + * [Higher-order functions]({higherOrderFunctions}) – functions that + operate on functions. + * [Tests]({docs.tests}) – tests and test-case generation. + * [Unison language support]({languageSupport}) – types and functions for + the Unison language itself. + * [First-class documentation]({Doc.doc}) – the {type Doc} type for + writing Unison docs, like this very document! + * [Very large numbers]({Natural.doc}) based on the {type Natural} type. + * [Date and time]({time.README}) – real-time clocks, date and time + types, and supporting functions. + }} + +-- builtin reflection.Code.cache_ : +-- [(reflection.Link.Term, reflection.Code)] ->{IO} [reflection.Link.Term] + +reflection.Code.cache_.doc : Doc +reflection.Code.cache_.doc = + {{ + Dynamically load definitions into the local codebase cache, returning a + non-empty {type List} if some dependencies of the loaded definitions are + missing (we cannot load definitions into the code cache until their + dependencies already exist). If the returned list is empty, then the + definitions were successfully loaded into the code cache. + + Unlike the namespace of human-readable definitions managed by UCM, the + codebase cache is a concept that exists only at runtime. It's initially + populated with the __compiled__ form of all the transitive dependencies + needed by your program, and can be extended dynamically by calling this + function {cache_}. In conjunction with the various functions on {type Value}, + this can be used to build servers that accept computations to run over the + network. + }} + +-- builtin reflection.Code.dependencies : +-- reflection.Code -> [reflection.Link.Term] + +reflection.Code.dependencies.doc : Doc +reflection.Code.dependencies.doc = + use Code dependencies + {{ + `` dependencies c `` lists the immediate (not transitive) dependencies of a + {type Code}. If `c` is a recursive function, the resulting list can include + the code for `c` itself. + + @typecheck ``` + code : Code + code = getOrBug "not found" (Code.lookup (termLink List.filter)) + deps : [Link.Term] + deps = dependencies code + ``` + }} + +reflection.Code.deserialize : Bytes ->{Exception} Code +reflection.Code.deserialize bs = match Code.deserialize.impl bs with + Left e -> raiseGeneric e bs + Right c -> c + +reflection.Code.deserialize.doc : Doc +reflection.Code.deserialize.doc = + use Code serialize + {{ + Reads a {type Code} from a {type Bytes} produced by {serialize}, or raises an + {type Exception} if the bytes are not a valid. + + **See also:** {serialize} + }} + +-- builtin reflection.Code.deserialize.impl : +-- Bytes -> Either Text reflection.Code + +reflection.Code.deserialize.impl.doc : Doc +reflection.Code.deserialize.impl.doc = + use Code serialize + {{ + `` Code.deserialize.impl bytes `` reads a {type Code} from a {type Bytes} + produced by {serialize}, and returns {Left} with an error message otherwise. + + **See also:** {serialize} + }} + +reflection.Code.doc : Doc +reflection.Code.doc = + use Code dependencies lookup + {{ + {type Code} is a serializable representation of a compiled {{ unisonTerm }}. + + A {type Code} can be created from a {type Link.Term} using {lookup}: + + @signature{lookup} + + Once created, a {type Code} can be serialized to and from {type Bytes}: + + @signatures{Code.serialize, Code.deserialize} + + We can also dynamically load definitions into the current codebase using + {cache_}: + + @signature{cache_} + + This returns {Left} with {{ + docExample 1 do missing -> (missing : [Link.Term]) }} for all the missing + dependencies of the input {type Code}. You can list the dependencies of a + {type Code} using {dependencies}: + + @signature{dependencies} + + Missing dependencies can happen when a {type Code} is serialized and sent to + another location which has a different set of definitions in its codebase + cache. See {cache_} for details. + + # {type Code} vs {type Value} + + Any runtime value (even temporary values or values defined local to a + function), can be converted to a {type Value} using {value}, but + {type Code} can only be created from definitions that exist in your + codebase and which can be referenced via {type Link.Term}, such as + ``termLink printLine``. + }} + +reflection.Code.doc.snippets.unisonTerm : Doc +reflection.Code.doc.snippets.unisonTerm = + {{ + [Unison term](https://www.unison-lang.org/learn/language-reference/term-definition/) + }} + +-- builtin reflection.Code.isMissing : reflection.Link.Term ->{IO} Boolean + +reflection.Code.isMissing.doc : Doc +reflection.Code.isMissing.doc = + {{ + Returns `` true `` if the referent of the given {type Link.Term} is missing + from the codebase, and `` false `` otherwise. + }} + +-- builtin reflection.Code.lookup : +-- reflection.Link.Term ->{IO} Optional reflection.Code + +reflection.Code.lookup.doc : Doc +reflection.Code.lookup.doc = + {{ + `` Code.lookup tm `` converts a {type Link.Term} to {type Code}, returning + {None} if the {type Link.Term} doesn't exist in the [code cache]({cache_}). + }} + +-- builtin reflection.Code.serialize : reflection.Code -> Bytes + +reflection.Code.serialize.doc : Doc +reflection.Code.serialize.doc = + {{ + `` Code.serialize c `` converts a {type Code} to {type Bytes}, using a simple + binary format. + + The format doesn't attempt to remove redundancy but it compresses well with + {zlib.compress} or {gzip.compress}. + + Though old serialization formats may eventually be retired, formats will not + change. When new serialized formats for {type Code} are introduced in future + versions of Unison, they'll be given new hashes that differ from this + function. + + **See also:** {Code.deserialize} + }} + +-- builtin reflection.Code.validateLinks : +-- [(reflection.Link.Term, reflection.Code)] +-- ->{Exception} Either [reflection.Link.Term] [reflection.Link.Term] + +reflection.Code.validateLinks.doc : Doc +reflection.Code.validateLinks.doc = + {{ + Checks that code to be loaded is well-formed, and that the given hashes are + valid for the given code. + + Takes a list of pairs of {type Link.Term} and {type Code}. The + {type Link.Term} is a term reference (a hash), and the {type Code} is the + code that should be present at that hash. + + This function checks that the {type Code} is complete (not missing any + bindings in a mutually dependent binding group), and that the hashes of the + {type Code} match the given {type Link.Term}s. If the {type Code} is + well-formed, but the hashes don't match, then this returns a {Right} + containing a {type List} of the mismatched {type Link.Term}s that were given. + If the {type Code} is missing some bindings, then this returns a {Left} + containing a {type List} of the missing {type Link.Term}s. + + Raises a {type MiscFailure} in {type Exception} if any of the given + {type Link.Term}s could not possibly be valid for the code, e.g. if they are + references to builtins or data constructors. + }} + +reflection.Link.doc : Doc +reflection.Link.doc = + {{ + A link to a definition in the Unison codebase. A {type Link} is either a + {type Type} or a {type Link.Term}. + }} + +reflection.Link.Term.doc : Doc +reflection.Link.Term.doc = + {{ + A {type Link.Term} is a reference to a Unison term in the codebase. + + It's used internally in {type Doc} to represent term links like: + + ``` + {{ A link to the {List.map} function }} + ``` + + It's also used for reflection. For example: + + @signatures{Code.lookup, Code.dependencies, Value.dependencies, load} + }} + +-- builtin reflection.Link.Term.toText : reflection.Link.Term -> Text + +reflection.Link.Term.toText.doc : Doc +reflection.Link.Term.toText.doc = + {{ + Converts a {type Link.Term} to its {type Text} representation which is its + full hash in [base32hex](https://en.wikipedia.org/wiki/Base32#base32hex) + encoding, with the `#` prefix. If the term is a builtin, the built-in name is + returned instead. + + # Example + + ``` + Term.toText (termLink List.map) + ``` + }} + +reflection.Link.Type.doc : Doc +reflection.Link.Type.doc = + {{ + A {type Type} is a reference to a Unison type in the codebase. + + It's used internally in {type Doc} to represent type links like: + + ``` + {{ A link to the {type List} type }} + ``` + }} + +reflection.Rewrites.doc : Doc +reflection.Rewrites.doc = + use List map + use Nat * + increment + {{ + The {type Rewrites} type is used by Unison's structural find and replace + feature. + + # Tutorial + + Let's start with a simple rule which replaces instances of `` x + 1 `` with + ``increment x``, where `x` may be any expression: + + @source{useIncrement} + + The `@rewrite` block has a type that reflects the replacements it makes. + + In `ucm`, use + [`sfind useIncrement`](https://www.unison-lang.org/learn/ucm-commands/edit/) + to look for definitions matching `useIncrement`. These can then be brought + into your scratch file using + [`edit`](https://www.unison-lang.org/learn/ucm-commands/edit/). + + Once definitions are in your scratch file, we can apply a rewrite rule to + definitions there using: + + ``` ucm + myproj/main> rewrite useIncrement + ``` + + This will rewrite any instances of `` x + 1 `` in the file, no matter where + they appear, with ``increment x``. For example: + + ``` unison + 92 * 8 + 1 ==> Nat.increment (92 * 8) + ``` + + {{ + docCallout + None + {{ + Notice that the `x` in the rule's left-hand side (LHS) `` ( x + 1 ) `` + matches any subexpression. In this case, the rule matches with `x` bound + to ``(92 * 8)``, and that same `` ( 92 * 8 ) `` expresison is referenced + in the right hand side of the rule. + }} }} + + The rewritten scratch file's definitions will be copied and written to the + top of your scratch file, above a new + [fold](https://www.unison-lang.org/learn/language-reference/comments/). + + A bit of syntax and terminology: + + * The keyword `@rewrite` introduces a block of one or more rules. + * Each rule has a __left-hand side__ (LHS). Above, the LHS is ``x + 1``. + * Each rule has a __right-hand side__ (RHS). Above, the RHS is + ``increment x``. + * Each rule has a __target__, which can be one of: + * `term` (which means the rule rewrites subexpressions of terms, except + for patterns in `match` cases) + * `case` (which means the rule rewrites the left-hand side of pattern + match cases) + * `signature` (which means the rule rewrites type signatures on terms) + + Let's look at some more examples. + + ## `@rewrite` blocks can have multiple rules + + A `@rewrite` block can have multiple rules, which are applied in + sequence. For instance, given: + + @typecheck ``` + incrementAndEtaReduce x f = + @rewrite + term x + 1 ==> increment x + term arg -> f arg ==> f + map (a -> a + 1) [1, 2, 3, 4] + ``` + + ... the `ucm` command `rewrite incrementAndEtaReduce` will produce: + + @typecheck ``` + map increment [1, 2, 3, 4] + ``` + + The first rule replaces `` a -> a + 1 `` with ``a -> increment a``, and + the second rule further simplifies this to just {increment}. + + ## Rewriting cases and type signatures + + A rule can target cases of pattern matching and type signatures. For + instance, this `@rewrite` block replaces usage of {type Either} with + {type Optional}: + + @typecheck ``` + eitherToOptional e a = + @rewrite + term Left e ==> None + term Right a ==> Some a + case Left e ==> None + case Right a ==> Some a + signature e a . Either e a ==> Optional a + eitherToOptional + ``` + + This `@rewrite` does several replacements, replacing usage of {Left} and + {Right} with {None} and {Some} in subexpressions and in also cases of + pattern matching. Lastly, it replaces usage of the type {type Either} + with {type Optional} in type signatures. + + {{ + docCallout + (Some {{ 📚 }}) + {{ + A `signature` target for a rule can introduce variables that can be + used in the rule's LHS and RHS. Here, we the `signature e a .` + introduces `e` and `a` type variables which we refer to in the rule. + }} }} + + **See:** {examples.eitherToOptional} and {optionalToEither}. + }} + +reflection.Rewrites.examples.eitherToOptional : + e1 + -> a1 + -> Rewrites + ( RewriteTerm (Either e1 b1) (Optional a5), + RewriteTerm (Either a4 a1) (Optional a1), + RewriteCase (Either e1 b) (Optional a3), + RewriteCase (Either a2 a1) (Optional a1), + RewriteSignature (Either e a) (Optional a)) +reflection.Rewrites.examples.eitherToOptional e a = + @rewrite + term Left e ==> None + term Right a ==> Some a + case Left e ==> None + case Right a ==> Some a + signature e a . Either e a ==> Optional a + +reflection.Rewrites.examples.eitherToOptional.doc : Doc +reflection.Rewrites.examples.eitherToOptional.doc = + {{ + A collection of rules which rewrites code written in terms of {type Either} + to instead work in terms of {type Optional}. + + **Also see:** {optionalToEither} + }} + +reflection.Rewrites.examples.etaReduce : + (i ->{g} o) -> Rewrites (Tuple (RewriteTerm (i ->{g, g1} o) (i ->{g} o)) ()) +reflection.Rewrites.examples.etaReduce f = @rewrite term x -> f x ==> f + +reflection.Rewrites.examples.etaReduce.doc : Doc +reflection.Rewrites.examples.etaReduce.doc = + use Nat increment + {{ + A rule that performs eta reduction, for instance, replacing the function `` + x -> increment x `` with {increment}. + }} + +reflection.Rewrites.examples.optionalToEither : + ∀ _e a1 a5 b a4 a3 a2 a e. + _e + -> a1 + -> Rewrites + ( RewriteTerm (Optional a5) (Either _e b), + RewriteTerm (Optional a1) (Either a4 a1), + RewriteCase (Optional a1) (Either a3 a1), + RewriteCase (Either a2 a1) (Optional a1), + RewriteSignature (Optional a) (Either e a)) +reflection.Rewrites.examples.optionalToEither _e a = + @rewrite + term None ==> Left _e + term Some a ==> Right a + case Some a ==> Right a + case Right a ==> Some a + signature e a . Optional a ==> Either e a + +reflection.Rewrites.examples.optionalToEither.doc : Doc +reflection.Rewrites.examples.optionalToEither.doc = + {{ + A collection of rules which rewrites code written in terms of {type Optional} + to instead work in terms of {type Either}. Usages of {None} will become `` + Left _e `` where `_e` is a new free variable (which you'll have to fill in). + }} + +reflection.Rewrites.examples.useIncrement : + Nat -> Rewrites (Tuple (RewriteTerm Nat Nat) ()) +reflection.Rewrites.examples.useIncrement x = + use Nat + + @rewrite term x + 1 ==> Nat.increment x + +reflection.Rewrites.examples.useIncrement.doc : Doc +reflection.Rewrites.examples.useIncrement.doc = + use Nat + + {{ A rule that replaces `` x + 1 `` with ``Nat.increment x``. }} + +-- builtin reflection.validateSandboxed.deprecated : +-- [reflection.Link.Term] -> a -> Boolean + +reflection.validateSandboxed.deprecated.doc : Doc +reflection.validateSandboxed.deprecated.doc = + use validateSandboxed deprecated + {{ + Validates that the closure given as the second argument is properly + sandboxed, i.e. that it does not use any I/O or foreign functions that are + not explicitly allowed by the first argument. Returns `` true `` if the + closure is sandboxed, and `` false `` otherwise. + + {{ + docCallout + (Some {{ ⚠️ }}) + {{ + {validateSandboxed} should generally be preferred, for a few reasons: + + * {validateSandboxed} doesn't require [loading]({load}) a value before + performing sandbox validation. + * {validateSandboxed} returns the list of violating terms in the case of a + sandbox violation. + * {validateSandboxed} works better with the JIT runtime. + }} }} + + # Example + + ``` + deprecated [] do open (FilePath "foo.txt") + ``` + + ``` + sandbox = [termLink open.impl, termLink Handle.close.impl] + deprecated sandbox do open (FilePath "foo.txt") + ``` + }} + +-- builtin reflection.Value.dependencies : +-- reflection.Value -> [reflection.Link.Term] + +reflection.Value.dependencies.doc : Doc +reflection.Value.dependencies.doc = + use Value dependencies + {{ + `` dependencies v `` returns the {type Link.Term} dependencies of a + {type Value}. + + ``` + dependencies (value (List.all (const true))) + ``` + }} + +reflection.Value.deserialize : Bytes ->{Exception} Value +reflection.Value.deserialize bs = match Value.deserialize.impl bs with + Left e -> raiseGeneric e bs + Right v -> v + +reflection.Value.deserialize.doc : Doc +reflection.Value.deserialize.doc = + use Value dependencies serialize + {{ + Converts {type Bytes} to a {type Value}, assuming it matches the format + produced by {serialize}, and returning {Left} with an error message + otherwise. + + # Example + + ``` + test.verify do + v = value (List.all (const true)) + ds1 = dependencies v + bs = serialize v + ds2 = dependencies (Value.deserialize bs) + ensureEqual ds1 ds2 + ``` + }} + +-- builtin reflection.Value.deserialize.impl : +-- Bytes -> Either Text reflection.Value + +reflection.Value.deserialize.impl.doc : Doc +reflection.Value.deserialize.impl.doc = + use Value dependencies serialize + use Value.deserialize impl + {{ + `` impl bytes `` converts {type Bytes} to a {type Value}, assuming it matches + the format produced by {serialize}, and returning {Left} with an error + message otherwise. + + ``` + v = value (List.all (const true)) + ds1 = dependencies v + bs = serialize v + ds2 = match impl bs with + Left e -> bug e + Right v2 -> dependencies v2 + ds1 === ds2 + ``` + }} + +reflection.Value.doc : Doc +reflection.Value.doc = + use Value deserialize serialize + {{ + {type Value} is a serializable representation of a Unison runtime value. It's + mainly used for implementing new storage layers or distributed execution + protocols. + + @signatures{value, Value.dependencies, serialize, deserialize, load} + + `` value a `` converts any `a` to a serializable {type Value}. That can then + be serialized to {type Bytes} using {serialize}, and deserialized using + {deserialize}. + + {load} will turn a {type Value} into an ordinary Unison value that can + computed with. For example: + + @typecheck ``` + roundtrip : x ->{IO, Exception} Either [Link.Term] x + roundtrip x = + v : Value + v = value x + bs : Bytes + bs = serialize v + load (deserialize bs) + roundtrip [1, 2, 3] + ``` + + On {load}, a {Left} tells us that some term dependencies are missing and + should be loaded using {cache_} before trying to {load} again. In the above + example, because we're calling {load} at the same location where we created + the {type Value}, it will always succeed, but if Alice were sending a + {type Value} over the network to another location, Bob, then Bob may report + {Left} to indicate he's missing some dependencies of the {type Value} and + that Alice should send them. + + **See also:** {type Code}, {type Link.Term} + }} + +-- builtin reflection.Value.load : +-- reflection.Value ->{IO} Either [reflection.Link.Term] a + +reflection.Value.load.doc : Doc +reflection.Value.load.doc = + {{ + `` load v `` converts a {{ docExample 1 do v -> (v : Value) }} to an ordinary + Unison value rather than its serializable runtime representation. + + This function returns {Left} with any missing dependencies, which should be + loaded using {cache_} before calling this function. + }} + +-- builtin reflection.Value.serialize : reflection.Value -> Bytes + +reflection.Value.serialize.doc : Doc +reflection.Value.serialize.doc = + use Value serialize + {{ + `` serialize v `` converts `v` to {type Bytes}, using a simple binary format. + {Value.deserialize} reverses the process. + + The format should compress well with any compression function, such as + {zlib.compress} or {gzip.compress}. + + ``` + v = value (List.fill 50 "🌸") + zlib.compress (serialize v) + ``` + + Though old serialization formats may be retired, the serialization format + will not change between releases of Unison. If new serialization formats for + {type Value} are introduced in future versions of Unison, they will have a + different hash than this function. + }} + +-- builtin reflection.Value.validateSandboxed : +-- [reflection.Link.Term] +-- -> reflection.Value +-- ->{IO} Either [reflection.Link.Term] [reflection.Link.Term] + +reflection.Value.validateSandboxed.doc : Doc +reflection.Value.validateSandboxed.doc = + {{ + `` validateSandboxed allowedTerms value `` validates that `value` don't use + any tracked (i.e. IO) operations other than those specified in + `allowedTerms`. + + The result is: + + * {Right} `[]` then validation has succeeded; the value does not violate the + sandbox. + * {Right} `nonEmptyList` then validation has failed; the terms in + `nonEmptyList` violate the sandbox. + * {Left} `requiredDependencies` then the code for `requireDependencies` is + needed to complete the sandbox validation. You will need to fetch the + {type Code} for the dependencies and load it with {cache_}. + `requiredDependencies` should never be an empty list (and if it is, it + should be reported as a runtime bug). + + # Example + + Check that a value doesn't use any tracked operations other than putting + bytes to a {type Handle} and getting a reference to the stdout handle: + + @source{validateSandboxed.example} + }} + +reflection.Value.validateSandboxed.example : + '{IO} Either [Link.Term] [Link.Term] +reflection.Value.validateSandboxed.example = do + v = value do printLine "hi" + validateSandboxed [termLink putBytes.impl, termLink std] v + +-- builtin reflection.Value.value : a -> reflection.Value + +reflection.Value.value.doc : Doc +reflection.Value.value.doc = + use Value serialize + {{ + `` value a `` converts any value, `a` to a {type Value}, which can then be + serialized using {serialize}. + + ``` + v = value (List.fill 50 "🌹") + zlib.compress (serialize v) + ``` + + **Also see:** {Value.deserialize.impl}, {load} + }} + +ReleaseNotes : Doc +ReleaseNotes = + {{ + Changes since 3.18.1: + + * Added {terminate_}, by {{ shareSlug "@ceedubs" }}. + }} + +syntax.docAside : Doc -> Doc +syntax.docAside = Aside + +syntax.docBlockquote : Doc -> Doc +syntax.docBlockquote = Blockquote + +syntax.docBold : Doc -> Doc +syntax.docBold = Doc.Bold + +syntax.docBulletedList : [Doc] -> Doc +syntax.docBulletedList = BulletedList + +syntax.docBulletedList.doc : Doc +syntax.docBulletedList.doc = + {{ + Creates a bulleted list from a list of {type Doc} elements. + + Instead of calling this function directly, you can use the documentation + syntax as detailed in + [Documenting Unison Code](https://unisonweb.org/docs/documentation). + + # Example + + ``` + docBulletedList [{{ One }}, {{ Two }}, {{ Three }}] + ``` + + Or equivalently: + + ``` + {{ + * One + * Two + * Three + }} + ``` + }} + +syntax.docCallout : Optional Doc -> Doc -> Doc +syntax.docCallout = Callout + +syntax.docCallout.doc : Doc +syntax.docCallout.doc = + {{ + A callout is a block of text that is visually distinct from the surrounding + text. It is used to draw attention to important information. + + # Examples + + ``` + docCallout None {{ This is a callout }} + ``` + + ``` + docCallout (Some {{ 👋 }}) {{ This is a callout with an emoji }} + ``` + + ``` + docCallout + (Some + {{ + # Title + + + }}) {{ This is a callout with a title }} + ``` + }} + +syntax.docCode : Doc -> Doc +syntax.docCode c = Code c + +syntax.docCodeBlock : Text -> Text -> Doc +syntax.docCodeBlock typ c = CodeBlock typ (docWord c) + +syntax.docCodeBlock.doc : Doc +syntax.docCodeBlock.doc = + {{ + A {type Doc} value that represents a code block. The first argument is the + language of the code block, and the second argument is the code itself. + + Instead of calling this function, you can use the documentation syntax + detailed in + [Documenting Unison Code](https://unisonweb.org/docs/documentation). + + # Examples + + ``` + docCodeBlock "unison" "foo = 1" + ``` + + ``` + docCodeBlock "javascript" "console.log('hello world')" + ``` + }} + +syntax.docColumn : [Doc] -> Doc +syntax.docColumn = cases + [d] -> d + ds -> Column ds + +syntax.docColumn.doc : Doc +syntax.docColumn.doc = + {{ + Groups a list of {type Doc} values into a column with each {type Doc} on its + own line. + + # Example + + ``` + docColumn + [ {{ + This is a doc + }} + , {{ + This is another doc + }} + , {{ + This is yet another doc + }} + ] + ``` + }} + +syntax.docEmbedAnnotation : tm -> Doc.Term +syntax.docEmbedAnnotation tm = + guid = "8546106e53c88996c8d3eb785a2fca80df9c7b3b" + Term.Term (Any tm) + +syntax.docEmbedAnnotations : tms -> tms +syntax.docEmbedAnnotations tms = + guid = "11f21dc3bcb37652d8058d655e757560ac38f7b3" + tms + +syntax.docEmbedSignatureLink : '{g1} t -> Doc.Term +syntax.docEmbedSignatureLink tm = + guid = "d9a4fb87e34569319591130bf3ec6e24" + term tm + +syntax.docEmbedTermLink : '{g1} t -> Either a Doc.Term +syntax.docEmbedTermLink tm = + guid = "9d3927033a9589dda2d10406840af7ef3b4bf21e" + Right (term tm) + +syntax.docEmbedTermLink.doc : Doc +syntax.docEmbedTermLink.doc = + use List map + {{ + Creates a link to a term in the documentation. + + Instead of calling this function directly, you can use the documentation + syntax as detailed in + [Documenting Unison Code](https://unisonweb.org/docs/documentation). + + # Example + + ``` + docLink (docEmbedTermLink do map) + ``` + + Or equivalently: + + ``` + {{ + {map} + }} + ``` + }} + +syntax.docEmbedTypeLink : typ -> Either typ b +syntax.docEmbedTypeLink typ = + guid = "f9e80035f8c21ac80c98b6c2cc06fe004ae2eb2c" + Left typ + +syntax.docEmbedTypeLink.doc : Doc +syntax.docEmbedTypeLink.doc = + {{ + Creates a link to a type in the documentation. + + Instead of calling this function directly, you can use the documentation + syntax as detailed in + [Documenting Unison Code](https://unisonweb.org/docs/documentation). + + # Example + + ``` + docLink (docEmbedTypeLink (typeLink Int)) + ``` + + Or equivalently: + + ``` + {{ + {type Int} + }} + ``` + }} + +syntax.docEval : 'a -> Doc +syntax.docEval d = Special (Eval (term d)) + +syntax.docEval.doc : Doc +syntax.docEval.doc = + use Nat + + {{ + Constructs a {type Doc} that evaluates to the given computation in a block + element and displays the result. + + # Examples + + ``` + docEval do 1 + 1 + ``` + + ``` + (docEval do 1 + 1) |> docBlockquote + ``` + }} + +syntax.docEvalInline : 'a -> Doc +syntax.docEvalInline a = Special (EvalInline (term a)) + +syntax.docEvalInline.doc : Doc +syntax.docEvalInline.doc = + use Nat + + {{ + Constructs a {type Doc} that evaluates the given computation and displays the + result inline. + + # Example + + ``` + {{ + The result of `` 1 + 1 `` is {{ docEvalInline do 1 + 1 }} + }} + ``` + + Or equivalently: + + ``` + {{ + The result of `` 1 + 1 `` is @eval{ 1 + 1 } + }} + ``` + }} + +syntax.docExample : Nat -> '{g1} t -> Doc +syntax.docExample n a = Special (Example n (term a)) + +syntax.docExample.doc : Doc +syntax.docExample.doc = + use List ++ + use Nat + + {{ + Constructs an inline code element that is typechecked. The first argument to + this function is the number of variables to introduce into the scope of the + example. The second argument is the example itself, which is a computation + returning a function that takes at least that many arguments. + + # Examples + + Demonstrate the use of the {+} function: + + ``` + docExample 2 do x y -> x + y + ``` + + Explain how list concatenation works: + + ``` + docExample 3 do xs ys zs -> (xs ++ (ys ++ zs)) === (xs ++ ys ++ zs) + ``` + }} + +syntax.docExampleBlock : Nat -> '{g1} t -> Doc +syntax.docExampleBlock n a = Special (ExampleBlock n (term a)) + +syntax.docExampleBlock.doc : Doc +syntax.docExampleBlock.doc = + use List map + {{ + Creates a code block containing the code of a given Unison computation. The + {type Nat} argument specifies the number of variables to quantify over. If + the computation contains a function, that many arguments to the function will + be removed and replaced with free variables. + + # Example + + ``` + docExampleBlock 2 do f x -> map f x === x + ``` + + ``` + docExampleBlock 0 do map Nat.increment [1, 2, 3] === [2, 3, 4] + ``` + + ``` + docExampleBlock 0 do f x y -> f y x + ``` + + ``` + docExampleBlock 3 do f x y z -> f y x z + ``` + }} + +syntax.docFoldedSource : [(Either Type Doc.Term, [Doc.Term])] -> Doc +syntax.docFoldedSource t = Special (FoldedSource t) + +syntax.docFoldedSource.doc : Doc +syntax.docFoldedSource.doc = + {{ + Creates a code block containing the code of types and/or terms. The block + will be folded by default, and can be unfolded to reveal the code. + + Instead of calling this function directly, you can use the documentation + syntax as detailed in + [Documenting Unison Code](https://unisonweb.org/docs/documentation). + + # Example + + ``` + docFoldedSource [(Left (typeLink Bag), [])] + ``` + + Or equivalently: + + ``` + {{ + @foldedSource{type Bag} + }} + ``` + }} + +syntax.docFormatConsole : Doc -> Pretty (Either SpecialForm ConsoleText) +syntax.docFormatConsole d = + use List +: :+ + use Nat + toText + use Pretty group join + use Text ++ + lit t = Pretty.lit (Right (Plain t)) + p1 <> p2 = Pretty.append p1 p2 + nl = lit "\n" + map f p = Pretty.map (Either.mapRight f) p + go = cases + Word t -> lit t + Code d -> group (lit "`" <> go d <> lit "`") + CodeBlock typ d -> + group (lit "``` " <> group (lit typ) <> nl <> go d <> nl <> lit "```") + Italic (Paragraph (([l] ++ mid) ++ [r])) -> + group (lit "*" <> go l) <> join (List.map go mid) + <> group (go r <> lit "*") + Italic d -> group (lit "*" <> go d <> lit "*") + Strikethrough (Paragraph (([l] ++ mid) ++ [r])) -> + group (lit "~~" <> go l) <> join (List.map go mid) + <> group (go r <> lit "~~") + Strikethrough d -> group (lit "~~" <> go d <> lit "~~") + Doc.Bold d -> map ConsoleText.Bold (go d) + Style _ d -> go d + Anchor _ d -> go d + Blockquote d -> group (Pretty.indent (lit "> ") (go d)) + Blankline -> group (lit "\n\n") + Linebreak -> group (lit "\n") + SectionBreak -> lit "܍" + Tooltip inner _ -> go inner + Aside d -> map (Foreground BrightBlack) (lit "(" <> go d <> lit ")") + Callout None d -> group (Pretty.indent (lit " | ") (go d)) + Callout (Some icon) d -> + group + (Pretty.indent + (lit " | ") + (sepBy nl [map ConsoleText.Bold (go icon), lit "", go d])) + Doc.Table rows -> table (List.map (List.map go) rows) + Folded _ summary details -> go summary <> go details + Paragraph ds -> wrap (join (List.map go ds)) + BulletedList ds -> + item d = indent' (lit "* ") (lit " ") (go d) + items = List.map item ds + group (sepBy nl items) + NumberedList n ds -> + dot = ". " + w = Text.size (toText (n + List.size ds)) + Text.size dot + num n = lit (alignRightWith w ?\s (toText n ++ dot)) + indent = lit (Text.repeat w " ") + item : Nat -> Doc -> Pretty (Either SpecialForm ConsoleText) + item n d = indent' (num n) indent (go d) + items n acc = cases + [] -> acc + d +: ds -> items (n + 1) (acc :+ item n d) ds + group (sepBy nl (items n [] ds)) + Section title ds -> + ggo d = group (go d) + t = indent' (lit "# ") (lit " ") (ggo (Doc.Bold title)) + subs = List.map (d -> Pretty.indent (lit " ") (ggo d)) ds + group (sepBy (nl <> nl) (t +: subs)) + UntitledSection ds -> + ggo d = group (go d) + group (sepBy (nl <> nl) (List.map ggo ds)) + Doc.Join ds -> join (List.map go ds) + Column ds -> sepBy nl (List.map go ds) + Doc.Group d -> group (go d) + NamedLink name _ -> map Underline (go name) + Image alt _ (Some caption) -> sepBy nl [go alt, go (Italic caption)] + Image alt _ None -> go alt + Special sf -> Pretty.lit (Left sf) + go d + +syntax.docGroup : Doc -> Doc +syntax.docGroup = Doc.Group + +syntax.docItalic : Doc -> Doc +syntax.docItalic = Italic + +syntax.docJoin : [Doc] -> Doc +syntax.docJoin = cases + [d] -> d + ds -> Doc.Join ds + +syntax.docJoin.doc : Doc +syntax.docJoin.doc = + {{ + Joins a {type List} of {type Doc}s into a single {type Doc}. + + # Example + + ``` + docJoin [{{ butter }}, {{ flies }}] + ``` + }} + +syntax.docLink : Either Type Doc.Term -> Doc +syntax.docLink t = Special (SpecialForm.Link t) + +syntax.docLink.doc : Doc +syntax.docLink.doc = + {{ + Constructs a {type Doc} that links to the given {type Type} or + {type Doc.Term}. Supports the `{foo}` or `{type foo}` linking syntax in + {type Doc} literals. + + # Examples + + Link to a type: + + ``` + docLink (Left (typeLink Nat)) + ``` + + or equvalently: + + ``` + {{ + {type Nat} + }} + ``` + + Link to a term: + + ``` + docLink (Right (Term.Term (Any (do factorial)))) + ``` + + or equvalently: + + ``` + {{ + {factorial} + }} + ``` + }} + +syntax.docNamedLink : Doc -> Doc -> Doc +syntax.docNamedLink = NamedLink + +syntax.docNumberedList : Nat -> [Doc] -> Doc +syntax.docNumberedList = NumberedList + +syntax.docNumberedList.doc : Doc +syntax.docNumberedList.doc = + {{ + Creates a numbered list from a list of {type Doc} elements. + + Instead of calling this function directly, you can use the documentation + syntax as detailed in + [Documenting Unison Code](https://unisonweb.org/docs/documentation). + + # Example + + ``` + docNumberedList 1 [{{ One }}, {{ Two }}, {{ Three }}] + ``` + + Or equivalently: + + ``` + {{ + 1. One + 2. Two + 3. Three + }} + ``` + }} + +syntax.docParagraph : [Doc] -> Doc +syntax.docParagraph = Paragraph + +syntax.docParagraph.doc : Doc +syntax.docParagraph.doc = + {{ + Groups a list of {type Doc} values into a single {type Doc} value that + represents a paragraph. For each {type Doc} after the first, if there is + space left on the current line, it will be added to the current line. + Otherwise, it will be added to the next line. + + Instead of calling this function, you can use the documentation syntax + detailed in + [Documenting Unison Code](https://unisonweb.org/docs/documentation). + }} + +syntax.docSection : Doc -> [Doc] -> Doc +syntax.docSection = Section + +syntax.docSignature : [Doc.Term] -> Doc +syntax.docSignature ts = Special (SpecialForm.Signature ts) + +syntax.docSignature.doc : Doc +syntax.docSignature.doc = + {{ + A {type Doc} value that displays type signatures. Takes a list of + {type Doc.Term} values, and the renderer will display the types of those + terms. + + Instead of calling this function, you can use the documentation syntax + detailed in + [Documenting Unison Code](https://unisonweb.org/docs/documentation). + + # Examples + + ``` + docSignature [term do Bag.map] + ``` + + ``` + docSignature [term do docSignature] + ``` + }} + +syntax.docSignatureInline : Doc.Term -> Doc +syntax.docSignatureInline t = Special (SignatureInline t) + +syntax.docSource : [(Either Type Doc.Term, [Doc.Term])] -> Doc +syntax.docSource t = Special (SpecialForm.Source t) + +syntax.docSource.doc : Doc +syntax.docSource.doc = + {{ + Embeds the source code of a {type List} of Unison terms or types into a + {type Doc}. + + Instead of using this function directly, you should use the `@source` syntax + as detailed in + [Documenting Unison Code](https://unison-lang.org/docs/documentation). + + # Example + + ``` + docSource [(Left (typeLink Optional), [])] + ``` + + Or equivalently: + + ``` + {{ + @source{type Optional} + }} + ``` + }} + +syntax.docSourceElement : link -> annotations -> (link, annotations) +syntax.docSourceElement link annotations = + guid = "e56ece7785c34c1cc9a441b11da81cfa98d05985" + (link, annotations) + +syntax.docStrikethrough : Doc -> Doc +syntax.docStrikethrough = Strikethrough + +syntax.docTable : [[Doc]] -> Doc +syntax.docTable = Doc.Table + +syntax.docTable.doc : Doc +syntax.docTable.doc = + {{ + Constructs a {type Doc} table from a {type List} of {type List}s of + {type Doc}s. + + # Example + + The truth table for locical AND: + + ``` + docTable + [ [{{ `a` }}, {{ `b` }}, {{ `a && b` }}] + , [{{ `` true `` }}, {{ `` true `` }}, {{ @eval{ true && true } }}] + , [{{ `` true `` }}, {{ `` false `` }}, {{ @eval{ true && false } }}] + , [{{ `` false `` }}, {{ `` true `` }}, {{ @eval{ false && true } }}] + , [{{ `` false `` }}, {{ `` false `` }}, {{ @eval{ false && false } }}] + ] + ``` + }} + +syntax.docTooltip : Doc -> Doc -> Doc +syntax.docTooltip = Tooltip + +syntax.docTransclude : d -> d +syntax.docTransclude d = + guid = "b7a4fb87e34569319591130bf3ec6e24" + d + +syntax.docTransclude.doc : Doc +syntax.docTransclude.doc = + use docJoin doc + {{ + [Transcludes](https://en.wikipedia.org/wiki/Transclusion) the given + {type Doc} into the current {type Doc}. + + # Example + + Transcludes the {type Doc} for the {docJoin} function into another doc: + + ``` + {{ + The doc for {docJoin} follows: + + {{ docTransclude doc }} + + And now we're back to the original doc. + }} + ``` + + Or equivalently: + + ``` + {{ + The doc for {docJoin} follows: + + {{ doc }} + + And now we're back to the original doc. + }} + ``` + }} + +syntax.docUntitledSection : [Doc] -> Doc +syntax.docUntitledSection = cases + [d] -> d + ds -> UntitledSection ds + +syntax.docUntitledSection.doc : Doc +syntax.docUntitledSection.doc = + {{ + Groups a list of {type Doc}s into a section with no title. The section will + be omitted if the list is empty or contains only one element. + + Instead of using this function directly, you can use the documentation syntax + detailed in + [the documentation guide](https://unisonweb.org/docs/documentation), where + any group of two or more paragraphs without a title will be treated as an + untitled section. + }} + +syntax.docVerbatim : Doc -> Doc +syntax.docVerbatim c = CodeBlock "raw" c + +syntax.docVerbatim.doc : Doc +syntax.docVerbatim.doc = + {{ + Constructs a {type Doc} that puts the contents of another {type Doc} in a + `raw` code block (i.e. without any syntax highlighting). + + # Example + + ``` + docVerbatim {{ This is a raw code block. }} + ``` + }} + +syntax.docWord : Text -> Doc +syntax.docWord = Word + +syntax.docWord.doc : Doc +syntax.docWord.doc = + {{ + The basic building block of a {type Doc}. Constructs a {type Doc} that + displays the given {type Text}. + + # Examples + + ``` + docWord "foo" + ``` + + Or equivalently: + + ``` + {{ + foo + }} + ``` + + ``` + docWord "foo" |> docCode + ``` + + or equivalently: + + ``` + {{ + `foo` + }} + ``` + }} + +system.ANSI.Color.doc : Doc +system.ANSI.Color.doc = + {{ + The type {type Color} is the set of + [ANSI colors](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors). that + can be used to colorize text in the terminal. + + This type is used by {type ConsoleText} to colorize text, in particular by + {docFormatConsole} to colorize the rendering of {type Doc} values to the UCM + terminal. + }} + +system.ConsoleText.doc : Doc +system.ConsoleText.doc = + {{ + A {type ConsoleText} value is a {type Text} value that can be printed to the + console with {type Color} and other formatting. + + # Examples + + ``` + ConsoleText.Bold (Foreground Red (Plain "This will be red and bold")) + ``` + + ``` + Foreground + Red + (Background Yellow (Plain "This will be red text on a yellow background")) + ``` + }} + +test.arbitrary.floats : Nat ->{Each, Random} Float +test.arbitrary.floats n = + limit n do + Each.append + (do + each + [ 0.0 + , -0.0 + , -1.0 + , 1.0 + , -0.5 + , 0.5 + , minFloat + , maxFloat + , NaN + , NegativeInfinity + , Infinity + ]) + do + repeatForever() + Float.fromRepresentation Random.nat! + +test.arbitrary.floats.doc : Doc +test.arbitrary.floats.doc = + {{ + Generates a sequence of floating-point numbers for testing, checking edge + cases like 0.0, -0.0, {minFloat}, {maxFloat}, {NaN}, {NegativeInfinity}, and + {Infinity} first, then proceeding with random numbers. + + # Example + + ``` + splitmix 1 do Each.toList do arbitrary.floats 20 + ``` + }} + +test.arbitrary.ints : Nat ->{Each, Random} Int +test.arbitrary.ints n = limit n do + Each.append (do each [+0, -1, +1, minInt, maxInt, -2, +2]) do + repeatForever() + Random.int() + +test.arbitrary.ints.doc : Doc +test.arbitrary.ints.doc = + use Int + + use arbitrary ints + {{ + Generates a sequence of integers for testing, checking edge cases like 0, 1, + {minInt}, and {maxInt} first, then proceeding with random numbers. + + # Example + + @typecheck ``` + test.verify do + x = ints 10 + y = ints 10 + label "x" x + label "y" y + ensureGreaterOrEqual (x + y) x + ``` + + This test checks that the sum of two integers is greater than or equal to + the first integer. This is true for all integers except {maxInt}, which + will overflow and wrap around to {minInt}. This test fails with: + + ``` raw + 🚫 FAILED + x: 1 + y: 9223372036854775807 + -9223372036854775808 is not greater than or equal to 1 + (-2147483648, 1) + ``` + + The test fails because the sum of 1 and {maxInt} is {minInt}, which is not + greater than or equal to 1. A seemingly simple test like this can reveal + assumptions about the behavior of functions that are not always true. + }} + +test.arbitrary.nats : Nat ->{Each, Random} Nat +test.arbitrary.nats n = limit n do + Each.append (do each [0, 1, maxNat, 2]) do + repeatForever() + Random.nat! + +test.arbitrary.nats.doc : Doc +test.arbitrary.nats.doc = + use Nat + + use arbitrary nats + {{ + Generates a sequence of natural numbers for testing, checking edge cases like + 0, 1, and {maxNat} first, then proceeding with random numbers. + + # Example + + @typecheck ``` + test.verify do + x = nats 10 + y = nats 10 + label "x" x + label "y" y + ensureGreaterOrEqual (x + y) x + ``` + + A seemingly simple test like this can reveal assumptions about the behavior + of functions that are not always true. In this case, the test checks that + the sum of two natural numbers is greater than or equal to the first + number. This is true for all natural numbers except {maxNat}, which will + overflow and wrap around to 0. This test fails with: + + ``` raw + 🚫 FAILED + x: 1 + y: 18446744073709551615 + 0 is not greater than or equal to 1 + (0, 1) + ``` + }} + +test.arbitrary.unspecialFloats : Nat ->{Each, Random} Float +test.arbitrary.unspecialFloats n = + limit n do + Each.append + (do each [0.0, -0.0, -1.0, 1.0, -0.5, 0.5, minFloat, maxFloat]) + do + repeatForever() + x = Float.fromRepresentation Random.nat! + guard (Float.inRange minFloat maxFloat x) + x + +test.arbitrary.unspecialFloats.doc : Doc +test.arbitrary.unspecialFloats.doc = + {{ + Generates a sequence of floating-point numbers for testing, without special + values like {NaN}, {NegativeInfinity}, and {Infinity}. The sequence includes + edge cases like 0.0, -0.0, {minFloat}, and {maxFloat} first, then proceeds + with random numbers. + + # Example + + ``` + splitmix 1 do Each.toList do unspecialFloats 20 + ``` + }} + +test.assert : Boolean -> e -> a -> a +test.assert b e a = if b then a else bug e + +test.assert.doc : Doc +test.assert.doc = + {{ + `` assert b e a `` halts with the error `` bug e `` if the given + {type Boolean} expression `b` is false. Otherwise it returns the value of the + expression `a`. + }} + +test.assertEquals : a -> a -> Boolean +test.assertEquals actual expected = + assert + (actual === expected) + ("The values being compared are not equal", actual, expected) + true + +test.assertEquals.doc : Doc +test.assertEquals.doc = + {{ + {assertEquals} is a helper function aimed to allow debug printing during + testing. It is a wrapper around an equality check with {===}. If the two + arguments are not equal, {assertEquals} calls {bug} with an error message + showing the values being compared. + + # Examples + + ``` + assertEquals 1 1 + ``` + + @typecheck ``` + assertEquals 1 2 + ``` + + The latter example halts with the following error message: + + ``` ucm + 💔💥 + I've encountered a call to builtin.bug with the following value: + ("The values being compared are not equal", 1, 2) + ``` + }} + +test.check : Boolean -> [Result] +test.check b = if b then [Ok "Passed"] else [Result.Fail "Failed"] + +test.deprecated.check' : Boolean -> Test +test.deprecated.check' b = if b then proved else Test.fail + +test.deprecated.Domain.boolean : Domain Boolean +test.deprecated.Domain.boolean = Small [false, true] + +test.deprecated.Domain.ints : Domain Int +test.deprecated.Domain.ints = + use Int negate + use Weighted <|> + go n = + yield n + <|> (weight 1 do + go + (if Universal.gt n +0 then negate n + else Int.increment (negate n))) + Large + (List.foldLeft + (a n -> a <|> yield n) Weighted.Fail [+0, +1, -1, maxInt, minInt] + <|> go +2) + +test.deprecated.Domain.lift2 : + (a -> b -> c) -> Domain a -> Domain b -> Domain c +test.deprecated.Domain.lift2 f da db = + use Domain weighted + use List size + use Nat + + wa = weighted da + wb = weighted db + wc = Weighted.mergeWith (a1 a2 -> f a1 a2) wa wb + match (da, db) with + (Small as, Small bs)| Universal.lt (size as + size bs) smallSize -> + Small (Weighted.sample smallSize wc) + _ -> Large wc + +test.deprecated.Domain.lists : Domain [()] +test.deprecated.Domain.lists = listsOf (Small [()]) + +test.deprecated.Domain.listsOf : Domain a -> Domain [a] +test.deprecated.Domain.listsOf d = + Large + (Weighted.lists match d with + Small as -> Weighted.fromList as + Large w -> w) + +test.deprecated.Domain.map : (a -> b) -> Domain a -> Domain b +test.deprecated.Domain.map f = cases + Large w -> Large (Weighted.map f w) + Small as -> Small (List.map f as) + +test.deprecated.Domain.nats : Domain Nat +test.deprecated.Domain.nats = Large natsInOrder + +test.deprecated.Domain.pairs : Domain a -> Domain (a, a) +test.deprecated.Domain.pairs d = lift2 (a b -> (a, b)) d d + +test.deprecated.Domain.sample : Nat -> Domain a -> [a] +test.deprecated.Domain.sample n = cases + Large w -> Weighted.sample n w + Small xs -> List.take n xs + +test.deprecated.Domain.smallSize : Nat +test.deprecated.Domain.smallSize = 10000 + +test.deprecated.Domain.tuples : Domain a -> Domain b -> Domain (Tuple a b) +test.deprecated.Domain.tuples = lift2 (a b -> Cons a b) + +test.deprecated.Domain.weighted : Domain a -> Weighted a +test.deprecated.Domain.weighted = cases + Small as -> Weighted.fromList as + Large w -> w + +test.deprecated.forAll : Nat -> Domain a -> (a ->{e} Boolean) ->{e} [Result] +test.deprecated.forAll n d p = deprecated.run (forAll' n d p) + +test.deprecated.Gen.append : '{Gen} a -> '{Gen} a -> '{Gen} a +test.deprecated.Gen.append g1 g2 = + w1 = toWeighted g1 + w2 = toWeighted g2 + w3 = Weighted.append w1 w2 + do Gen.sample w3 + +test.deprecated.Gen.append.doc : Doc +test.deprecated.Gen.append.doc = + use Gen append + use deprecated sample + use gen natIn + {{ + `` append g1 g2 `` generates all values in `g1`, then `g2`. For example: + + ``` + sample 10 <| append (natIn 0 3) (natIn 3 5) + ``` + + When sampling from the resulting generator, if the number of samples is less + than the number of samples needed to exhaust the first generator, the second + generator is not consulted, for example: + + ``` + sample 3 <| append (natIn 0 4) (natIn 100 130) + ``` + }} + +test.deprecated.Gen.append.examples.ex1 : [Nat] +test.deprecated.Gen.append.examples.ex1 = + use gen natIn + deprecated.sample 10 <| Gen.append (natIn 0 3) (natIn 3 5) + +test.deprecated.Gen.append.examples.ex2 : [Nat] +test.deprecated.Gen.append.examples.ex2 = + use gen natIn + deprecated.sample 3 <| Gen.append (natIn 0 4) (natIn 100 130) + +test.deprecated.gen.atLeastOne : '{Gen} a -> '{Gen} List.Nonempty a +test.deprecated.gen.atLeastOne g = do + a = g() + as = gen.listOf g () + Nonempty.Nonempty a as + +test.deprecated.gen.atLeastOne.doc : Doc +test.deprecated.gen.atLeastOne.doc = + {{ + Given the ability to generate a value of some type, generate at least one + value of that type, as a {type List.Nonempty}. + }} + +test.deprecated.gen.atLeastOneDistinct : '{Gen} a -> '{Gen} List.Nonempty a +test.deprecated.gen.atLeastOneDistinct gen = + do + use Nat + + size = natInOrder() + gen() + +| (deprecated.sample (size + 1) do + Gen.sample (Weighted.drop 1 (toWeighted gen))) + +test.deprecated.gen.atLeastOneDistinct.doc : Doc +test.deprecated.gen.atLeastOneDistinct.doc = + {{ + Generates a nonempty list of distinct elements using the given generator. + }} + +test.deprecated.gen.boolean : '{Gen} Boolean +test.deprecated.gen.boolean = do Gen.sample (Weighted.fromList [false, true]) + +test.deprecated.gen.boolean.doc : Doc +test.deprecated.gen.boolean.doc = + {{ A generator of {type Boolean} values `` true `` and ``false``. }} + +test.deprecated.gen.Char.alpha : '{Gen} Char +test.deprecated.gen.Char.alpha = Gen.append Char.lower Char.upper + +test.deprecated.gen.Char.alpha.doc : Doc +test.deprecated.gen.Char.alpha.doc = + {{ + {alpha} generates {type Char} values of the lowercase and uppercase letter + ranges 'a'-'z' and 'A'-'Z'. + + # Example + + ``` + deprecated.sample 52 alpha + ``` + }} + +test.deprecated.gen.Char.alpha.sampled : [Char] +test.deprecated.gen.Char.alpha.sampled = deprecated.sample 52 do alpha() + +test.deprecated.gen.Char.ascii : '{Gen} Char +test.deprecated.gen.Char.ascii = + pick [Char.asciiPrintable, cost 5 asciiNonPrintable] + +test.deprecated.gen.Char.ascii.doc : Doc +test.deprecated.gen.Char.ascii.doc = + use Char ascii + {{ + {ascii} generates {type Char} values in the complete ASCII range starting + with printable characters, then non-printable ones. + + # Example + + ``` + deprecated.sample 16 ascii + ``` + }} + +test.deprecated.gen.Char.ascii.sampled : [Char] +test.deprecated.gen.Char.ascii.sampled = deprecated.sample 20 do Char.ascii() + +test.deprecated.gen.Char.asciiNonPrintable : '{Gen} Char +test.deprecated.gen.Char.asciiNonPrintable = + do fromNat.impl (gen.natIn 0 32 ()) + +test.deprecated.gen.Char.asciiNonPrintable.doc : Doc +test.deprecated.gen.Char.asciiNonPrintable.doc = + {{ + {asciiNonPrintable} generates {type Char} values in the non-printable part of + the ASCII range. + + # Example + + ``` + deprecated.sample 16 asciiNonPrintable + ``` + }} + +test.deprecated.gen.Char.asciiNonPrintable.sampled : [Char] +test.deprecated.gen.Char.asciiNonPrintable.sampled = + deprecated.sample 32 do asciiNonPrintable() + +test.deprecated.gen.Char.asciiPrintable : '{Gen} Char +test.deprecated.gen.Char.asciiPrintable = do fromNat.impl (gen.natIn 32 127 ()) + +test.deprecated.gen.Char.asciiPrintable.doc : Doc +test.deprecated.gen.Char.asciiPrintable.doc = + use Char asciiPrintable + {{ + {asciiPrintable} generates {type Char} values in the printable part of the + ASCII range. + + # Example + + ``` + deprecated.sample 16 asciiPrintable + ``` + }} + +test.deprecated.gen.Char.asciiPrintable.sampled : [Char] +test.deprecated.gen.Char.asciiPrintable.sampled = + deprecated.sample (127 Nat.- 32) do Char.asciiPrintable() + +test.deprecated.gen.Char.digit : '{Gen} Char +test.deprecated.gen.Char.digit = do fromNat.impl (gen.natIn 48 58 ()) + +test.deprecated.gen.Char.digit.doc : Doc +test.deprecated.gen.Char.digit.doc = + use Char digit + {{ + {digit} generates {type Char} values of the digits 0 through 9. + + # Example + + ``` + deprecated.sample 10 digit + ``` + }} + +test.deprecated.gen.Char.digit.sampled : [Char] +test.deprecated.gen.Char.digit.sampled = + deprecated.sample (58 Nat.- 48) do Char.digit() + +test.deprecated.gen.Char.hexDigit : '{Gen} Char +test.deprecated.gen.Char.hexDigit = + aTof : '{Gen} Char + aTof = do fromNat.impl (gen.natIn 97 103 ()) + pick [cost 10 aTof, Char.digit] + +test.deprecated.gen.Char.hexDigit.doc : Doc +test.deprecated.gen.Char.hexDigit.doc = + use Char hexDigit + {{ + {hexDigit} generates {type Char} values that are valid hexadecimal digits + ('a'-'z' and 0-9). + + # Example + + ``` + deprecated.sample 16 hexDigit + ``` + }} + +test.deprecated.gen.Char.hexDigit.sampled : [Char] +test.deprecated.gen.Char.hexDigit.sampled = + deprecated.sample (113 Nat.- 97) do Char.hexDigit() + +test.deprecated.gen.Char.lower : '{Gen} Char +test.deprecated.gen.Char.lower = do fromNat.impl (gen.natIn 97 123 ()) + +test.deprecated.gen.Char.lower.doc : Doc +test.deprecated.gen.Char.lower.doc = + use Char lower + {{ + {lower} generates {type Char} values in the range of the lowercase letters + 'a'-'z'. + + # Example + + ``` + deprecated.sample 16 lower + ``` + }} + +test.deprecated.gen.Char.lower.sampled : [Char] +test.deprecated.gen.Char.lower.sampled = + deprecated.sample (123 Nat.- 97) do Char.lower() + +test.deprecated.gen.Char.upper : '{Gen} Char +test.deprecated.gen.Char.upper = do fromNat.impl (gen.natIn 65 91 ()) + +test.deprecated.gen.Char.upper.doc : Doc +test.deprecated.gen.Char.upper.doc = + use Char upper + {{ + {upper} generates {type Char} values in the range of of the uppercase letters + 'A'-'Z'. + + # Example + + ``` + deprecated.sample 16 upper + ``` + }} + +test.deprecated.gen.Char.upper.sampled : [Char] +test.deprecated.gen.Char.upper.sampled = + deprecated.sample (91 Nat.- 65) do Char.upper() + +test.deprecated.Gen.cost : Nat -> '{Gen} a -> '{Gen} a +test.deprecated.Gen.cost n g _ = (weight n do toWeighted g) |> Gen.sample + +test.deprecated.Gen.cost.doc : Doc +test.deprecated.Gen.cost.doc = + {{ + Increase the cost of a generator. This makes a generator less likely to be + chosen when generating values. + + # Example + + This generator will choose printable ASCII characters 5 times more often + than non-printable characters: + + ``` + pick [Char.asciiPrintable, cost 5 asciiNonPrintable] + ``` + }} + +test.deprecated.gen.distinctListOf : '{Gen} a -> '{Gen} [a] +test.deprecated.gen.distinctListOf gen = do + size = natInOrder() + deprecated.sample size gen + +test.deprecated.gen.distinctListOf.doc : Doc +test.deprecated.gen.distinctListOf.doc = + {{ Generates a list of distinct elements using the given generator. }} + +test.deprecated.Gen.doc : Doc +test.deprecated.Gen.doc = + {{ + The {type Gen} ability provides a way to generate arbitrary values of a given + type. It is used in testing to generate test data. + + The values generated are __not__ random, but are instead generated + deterministically. This means that the same test data will be generated every + time the test is run, which is useful for debugging. + + A generator may iterate over a finite or infinite set of values. If it is + finite, it will eventually run out of values and a property test over its + domain can be considered **proved**. + + 📚 Guide: + [Testing Unison code](https://www.unison-lang.org/learn/usage-topics/testing/) + + # Example usage + + This tests that {List.contains} returns `` true `` for an element that is + in the list, for 100 different combinations of list and element: + + {{ + docSource + [docSourceElement (docEmbedTermLink do contains.tests.positive) []] }} + + # Basic generators + + Generate the {type Boolean}s. This generates `` true `` and ``false``, then + stops: + + @signature{gen.boolean} + + Generate the {type Nat}s, in order starting from ``0``: + + @signature{natInOrder} + + Generate the {type Nat}s in pseudo-random order: + + @signature{gen.nat} + + Generate the {type Nat}s in a given range, in order: + + @signature{gen.natIn} + + Generate nonzero {type Nat}s, in order starting from ``1``: + + @signature{nonzeroNat} + + Generate the {type Int}s, alternating between positive and negative values: + + @signature{gen.int} + + Generate the strictly positive {type Int}s: + + @signature{positiveInt} + + Generate {type Float}s: + + @signature{gen.float} + + Generate ASCII {type Char}s: + + @signature{Char.ascii} + + Generate ASCII {type Text} of increasing length: + + @signature{Text.ascii} + + # Generating structures + + Generate {type List}s of elements from another generator: + + @signature{gen.listOf} + + Generate {type Optional} values from another generator: + + @signature{gen.optional} + + Generate pairs from two generators: + + @signature{pairOf} + + Generate {type Set}s of elements from another generator: + + @signature{setOf} + + Generate {type Map}s from two generators, one for keys and one for values: + + @signature{gen.mapOf} + + Generate {type List.Nonempty} lists from another generator: + + @signature{atLeastOne} + + Generate {type Either} values from two generators, one for the {Left} and + one for the {Right}: + + @signature{gen.either} + + # Generator combinators + + The empty generator generates no values: + + @signature{gen.empty} + + A generator that selects values from a list: + + @signature{gen.oneOf} + + A generator that generates values from a list of generators: + + @signature{pick} + + # Generators of functions + + Generate a unary {type Boolean} function: + + @signature{yesNo} + + Generate a binary {type Boolean} function: + + @signature{logic} + + Generate a function on {type Optional} from an underlying function: + + @signature{someOrNone} + + # Running generators + + Sample a generator, returning at most a number of values: + + @signature{deprecated.sample} + + Run a generator that returns a {type Test}, with a given number of samples: + + @signature{runs} + }} + +test.deprecated.gen.either : '{Gen} a -> '{Gen} b -> '{Gen} Either a b +test.deprecated.gen.either a b = pick [do Left a(), do Right b()] + +test.deprecated.gen.either.doc : Doc +test.deprecated.gen.either.doc = + {{ + Given the ability to generate values of two types, generate {type Either} + values alternating between {Left} and {Right}. + + # Example + + ``` + deprecated.sample 10 (gen.either natInOrder Char.lower) + ``` + }} + +test> test.deprecated.gen.either.test = + use Set fromList + g = gen.either gen.boolean (gen.oneOf [0, 1]) + actual = fromList (deprecated.sample 10 g) + expected = fromList [Left false, Right 0, Left true, Right 1] + check (expected === actual) + +test.deprecated.gen.empty : '{Gen} a +test.deprecated.gen.empty = do Gen.sample Weighted.Fail + +test.deprecated.gen.empty.doc : Doc +test.deprecated.gen.empty.doc = + {{ + A generator that never produces any values. + + # Example + + ``` + deprecated.sample 10 gen.empty + ``` + }} + +test.deprecated.gen.float : '{Gen} Float +test.deprecated.gen.float = do Gen.sample Weighted.floats + +test.deprecated.gen.float.doc : Doc +test.deprecated.gen.float.doc = + {{ + A generator that produces {type Float}s. + + # Examples + + ``` + deprecated.sample 10 gen.float + ``` + }} + +test.deprecated.gen.functions.logic : '{Gen} (Boolean -> Boolean -> Boolean) +test.deprecated.gen.functions.logic = + use Boolean != < > + gen.oneOf + [Boolean.and, given, implies, Boolean.or, (!=), nand, (<), (>), nor, (!=)] + +test.deprecated.gen.functions.logic.doc : Doc +test.deprecated.gen.functions.logic.doc = + {{ + Generates one of the binary logical operators {Boolean.and}, {Boolean.or}, + {given}, {implies}, {iff}, and their complements {nand}, {nor}, etc. + }} + +test.deprecated.gen.functions.someOrNone : + (a ->{g} b) -> b -> '{Gen} (Optional a ->{g} Optional b) +test.deprecated.gen.functions.someOrNone f v = + gen.oneOf + [Optional.map f, cases + None -> Some v + Some _ -> None, cases + None -> Some v + Some x -> Some (f x), const None, const (Some v)] + +test.deprecated.gen.functions.someOrNone.doc : Doc +test.deprecated.gen.functions.someOrNone.doc = + {{ + `` someOrNone f b `` returns a generator of functions that are either `` + Optional.map f `` or functions that return `` Some b `` or {None} regardless + of their inputs. + + # Example + + ``` + deprecated.sample 5 (someOrNone Nat.isEven true) + ``` + }} + +test.deprecated.gen.functions.yesNo : '{Gen} (Boolean -> Boolean) +test.deprecated.gen.functions.yesNo = + gen.oneOf [id, Boolean.not, const true, const false] + +test.deprecated.gen.functions.yesNo.doc : Doc +test.deprecated.gen.functions.yesNo.doc = + {{ Generates either {Boolean.not} or {id}. }} + +test.deprecated.gen.int : '{Gen} Int +test.deprecated.gen.int = do Gen.sample Weighted.ints + +test.deprecated.gen.int.doc : Doc +test.deprecated.gen.int.doc = + use Int + == + use gen int + {{ + Generates {type Int} values. + + # Example + + Checks commutativity of addition using 100 different pairs of {type Int} + values: + + ``` + runs 100 do + x = int() + y = int() + expect (x + y == y + x) + ``` + }} + +test.deprecated.gen.listOf : '{Gen} a -> '{Gen} [a] +test.deprecated.gen.listOf g = do + size = natInOrder() + List.map (_ -> g()) (Nat.range 0 size) + +test.deprecated.gen.listOf.doc : Doc +test.deprecated.gen.listOf.doc = + use Nat <= + {{ + Generates a {type List} of values of the given type, using the given + {type Gen} to generate the elements of the list. + + # Example + + ``` + runs 100 do + xs = gen.listOf natInOrder () + sorted = sortWith (<=) xs + expect (isSortedBy (<=) sorted) + ``` + }} + +test.deprecated.gen.mapOf : '{Gen} k -> '{Gen} v -> '{Gen} Map k v +test.deprecated.gen.mapOf k v = do Map.fromList (gen.listOf (pairOf k v) ()) + +test.deprecated.gen.mapOf.doc : Doc +test.deprecated.gen.mapOf.doc = + {{ + Constructs a generator of {type Map} given a generator for keys and a + generator for values. + }} + +test.deprecated.gen.nat : '{Gen} Nat +test.deprecated.gen.nat = do Gen.sample Weighted.nats + +test.deprecated.gen.nat.doc : Doc +test.deprecated.gen.nat.doc = + use Nat + == + {{ + A generator that generates {type Nat}s in a pseudo-random order, but always + starting with `` 0 `` and ``maxNat``. + + # Example + + Checks commutativity of addition using the first 100 unique pairs of Nat + values: + + ``` + runs 100 do + x = natInOrder() + y = natInOrder() + expect (x + y == y + x) + ``` + }} + +test.deprecated.gen.natIn : Nat -> Nat -> '{Gen} Nat +test.deprecated.gen.natIn start stopExclusive _ = + use Nat + + use Weighted <|> + go n = + if Universal.lt n stopExclusive then yield n <|> (weight 1 do go (n + 1)) + else Weighted.Fail + Gen.sample (go start) + +test.deprecated.gen.natIn.doc : Doc +test.deprecated.gen.natIn.doc = + {{ + A generator that produces all the {type Nat}s in the given range in order, + exclusive of the upper bound. + + # Example + + ``` + deprecated.sample 10 (gen.natIn 0 10) + ``` + }} + +test.deprecated.gen.natInOrder : '{Gen} Nat +test.deprecated.gen.natInOrder = do Gen.sample natsInOrder + +test.deprecated.gen.natInOrder.doc : Doc +test.deprecated.gen.natInOrder.doc = + use Nat + == + {{ + Generates {type Nat} values. The values are generated in order, starting with + ``0``. + + # Example + + Checks commutativity of addition using the first 100 unique pairs of + {type Nat} values: + + ``` + runs 100 do + x = natInOrder() + y = natInOrder() + expect (x + y == y + x) + ``` + }} + +test.deprecated.gen.nonzeroNat : '{Gen} Nat +test.deprecated.gen.nonzeroNat = + do Gen.sample (Weighted.filter (x -> Universal.gt x 0) natsInOrder) + +test.deprecated.gen.nonzeroNat.doc : Doc +test.deprecated.gen.nonzeroNat.doc = + {{ Generates a {type Nat} greater than 0. }} + +test.deprecated.gen.normalFloat : '{Gen} Float +test.deprecated.gen.normalFloat = do Gen.sample normalFloats + +test.deprecated.gen.normalFloat.doc : Doc +test.deprecated.gen.normalFloat.doc = + {{ + Generates a pseudo-random {type Float} in the range `` 0.0 `` to ``1.0``. + + # Example + + ``` + deprecated.sample 12 normalFloat + ``` + }} + +test.deprecated.gen.oneOf : [a] -> '{Gen} a +test.deprecated.gen.oneOf as = do Gen.sample (Weighted.fromList as) + +test.deprecated.gen.oneOf.doc : Doc +test.deprecated.gen.oneOf.doc = + {{ A generator of elements from the given {type List}. }} + +test.deprecated.gen.optional : '{Gen} a -> '{Gen} Optional a +test.deprecated.gen.optional a = + Gen.append (do Gen.sample (yield None)) do Some a() + +test.deprecated.gen.optional.doc : Doc +test.deprecated.gen.optional.doc = + {{ + Given the ability to generate values of some type, generate {type Optional} + values of that type. + }} + +test> test.deprecated.gen.optional.test = + use Set fromList + actual = fromList (deprecated.sample 10 (gen.optional gen.boolean)) + expected = fromList [None, Some false, Some true] + check (expected === actual) + +test.deprecated.gen.pairOf : '{Gen} a -> '{Gen} b -> '{Gen} (a, b) +test.deprecated.gen.pairOf a b = do (a(), b()) + +test.deprecated.gen.pairOf.doc : Doc +test.deprecated.gen.pairOf.doc = + {{ Generates a pair given a generator for each of the elements. }} + +test.deprecated.gen.pick : ['{Gen} a] -> '{Gen} a +test.deprecated.gen.pick gs = + use Weighted <|> + merge : '{Gen} a -> '{Gen} a -> '{Gen} a + merge g1 g2 _ = toWeighted g1 <|> toWeighted g2 |> Gen.sample + List.foldBalanced id merge gen.empty gs + +test.deprecated.gen.pick.doc : Doc +test.deprecated.gen.pick.doc = + {{ + Create a new generator that picks values from a list of generators. + + # Example + + ``` + fromCharList + (deprecated.sample 10 (pick [Char.upper, Char.lower, Char.digit])) + ``` + }} + +test.deprecated.gen.positiveInt : '{Gen} Int +test.deprecated.gen.positiveInt = + do Gen.sample (Weighted.filter (x -> x Int.> +0) Weighted.ints) + +test.deprecated.gen.positiveInt.doc : Doc +test.deprecated.gen.positiveInt.doc = + {{ + Generates an {type Int} that is greater than zero. + + # Example + + ``` + deprecated.sample 10 positiveInt + ``` + }} + +test.deprecated.Gen.runGen : '{g, Gen} a ->{g, Abort} a +test.deprecated.Gen.runGen gen = + h = cases + { Gen.sample w -> k } -> + match Weighted.sample 1 w with + [a] -> handle k a with h + _ -> abort + { a } -> a + handle gen() with h + +test.deprecated.Gen.runGen.doc : Doc +test.deprecated.Gen.runGen.doc = + {{ + Runs a {type Gen} and returns the result, or aborts if the {type Gen} runs + out of values to generate before returning a result. + }} + +test.deprecated.Gen.sample.doc : Doc +test.deprecated.Gen.sample.doc = + {{ + The {type Gen} ability allows you to generate arbitrary values of a given + type, for use in testing. Its one method, {Gen.sample}, requests a value from + the given {type Weighted}. + + # Example + + @source{runGen} + }} + +test.deprecated.gen.setOf : '{Gen} a -> '{Gen} Set a +test.deprecated.gen.setOf a = do Set.fromList (gen.listOf a ()) + +test.deprecated.gen.setOf.doc : Doc +test.deprecated.gen.setOf.doc = + {{ Generates a {type Set}, given a generator of the elements. }} + +test.deprecated.Gen.take : Nat -> '{e, Gen} t -> '{e, Gen} t +test.deprecated.Gen.take n g = do Gen.sample (Weighted.take n (toWeighted g)) + +test.deprecated.Gen.take.doc : Doc +test.deprecated.Gen.take.doc = + {{ + Construct a new generator that yields the first `n` elements from the given + generator and then stops generating values. + + # Examples + + ``` + deprecated.sample 10 (Gen.take 5 natInOrder) + ``` + }} + +test.deprecated.gen.Text.ascii : '{Gen} Text +test.deprecated.gen.Text.ascii = do fromCharList (gen.listOf Char.ascii ()) + +test.deprecated.gen.Text.ascii.doc : Doc +test.deprecated.gen.Text.ascii.doc = + {{ + Generates {type Text} constisting of printable and nonprintable ASCII + characters. + }} + +test.deprecated.gen.Text.asciiPrintable : '{Gen} Text +test.deprecated.gen.Text.asciiPrintable = + do fromCharList (gen.listOf Char.asciiPrintable ()) + +test.deprecated.gen.Text.asciiPrintable.doc : Doc +test.deprecated.gen.Text.asciiPrintable.doc = + {{ Generates {type Text} constisting of printable ASCII characters. }} + +test.deprecated.Gen.toWeighted : '{e, Gen} a ->{e} Weighted a +test.deprecated.Gen.toWeighted g = + go = cases + { a } -> yield a + { Gen.sample w -> k } -> Weighted.flatMap (a -> (handle k a with go)) w + handle g() with go + +test.deprecated.Gen.toWeighted.doc : Doc +test.deprecated.Gen.toWeighted.doc = + {{ + Takes computation that generates values via the {type Gen} ability and + returns a {type Weighted} that yields the same elements with the same + weights. + + # Example + + ``` + Weighted.sample 10 (toWeighted natInOrder) + ``` + }} + +test.deprecated.internals.v1.foldReport : + (Trie Text Status ->{e} r) -> Report ->{e} r +test.deprecated.internals.v1.foldReport k = cases Report t -> k t + +test.deprecated.internals.v1.foldScope : ([Text] ->{𝕖} r) -> Labels ->{𝕖} r +test.deprecated.internals.v1.foldScope k = cases Labels ss -> k ss + +test.deprecated.internals.v1.foldStatus : + r -> (Success ->{𝕖} r) -> (Success ->{𝕖} r) -> r -> Status ->{𝕖} r +test.deprecated.internals.v1.foldStatus failed expected unexpected pending = cases + Failed -> failed + Expected s -> expected s + Unexpected s -> unexpected s + Pending -> pending + +test.deprecated.internals.v1.foldSuccess : + (Nat ->{𝕖} r) -> r -> Success ->{𝕖} r +test.deprecated.internals.v1.foldSuccess passed proved = cases + Passed n -> passed n + Proved -> proved + +test.deprecated.internals.v1.Scope.cons : Text -> Labels -> Labels +test.deprecated.internals.v1.Scope.cons n = + use List +: + foldScope (Labels << (+:) n) + +test.deprecated.internals.v1.Status.combine : Status -> Status -> Status +test.deprecated.internals.v1.Status.combine = cases + _, Pending -> Pending + Pending, _ -> Pending + Failed, _ -> Failed + _, Failed -> Failed + Unexpected a, Unexpected b -> Unexpected (Success.combine a b) + Unexpected a, _ -> Unexpected a + _, Unexpected b -> Unexpected b + Expected a, Expected b -> Expected (Success.combine a b) + +test.deprecated.internals.v1.Status.pending : Status -> Status +test.deprecated.internals.v1.Status.pending = cases + Failed -> Pending + Expected s -> Unexpected s + Unexpected s -> Pending + Pending -> Pending + +test.deprecated.internals.v1.Success.combine : Success -> Success -> Success +test.deprecated.internals.v1.Success.combine = cases + Passed n, Passed m -> Passed (n Nat.+ m) + Passed n, Proved -> Passed n + Proved, Passed n -> Passed n + Proved, Proved -> Proved + +test.deprecated.internals.v1.Test.finished : Status -> Test +test.deprecated.internals.v1.Test.finished st = + Test (Report << foldScope (sc -> Trie.singleton sc st)) + +test.deprecated.internals.v1.Test.forAll' : + Nat -> Domain a -> (a ->{e} Boolean) ->{e} Test +test.deprecated.internals.v1.Test.forAll' maxSize domain property = + use Text ++ + check xs s = + List.map + (cases + (c, i) -> + if property c then finished (Expected s) + else + v1.Test.label.deprecated + ("test case " ++ Nat.toText i) (finished Failed)) + (List.indexed xs) + List.foldBalanced id Test.both proved + <| (match domain with + Small xs -> check (List.take maxSize xs) Proved + Large _ -> check (Domain.sample maxSize domain) (Passed 1)) + +test.deprecated.internals.v1.Test.modifyScope : + (Labels -> Labels) -> Test -> Test +test.deprecated.internals.v1.Test.modifyScope f = cases Test k -> Test (k << f) + +test.deprecated.internals.v1.Test.modifyStatus : + (Status -> Status) -> Test -> Test +test.deprecated.internals.v1.Test.modifyStatus f = cases + Test k -> Test (foldReport (Report << Trie.map f) << k) + +test.deprecated.internals.v1.Test.pending : Test -> Test +test.deprecated.internals.v1.Test.pending = modifyStatus Status.pending + +test.deprecated.internals.v1.Test.report : Test -> Report +test.deprecated.internals.v1.Test.report = cases Test k -> k (Labels []) + +test.deprecated.internals.v1.Test.Report.combine : Report -> Report -> Report +test.deprecated.internals.v1.Test.Report.combine = cases + Report t1, Report t2 -> Report <| Trie.unionWith Status.combine t1 t2 + +test.deprecated.internals.v1.Test.Report.empty : Report +test.deprecated.internals.v1.Test.Report.empty = Report Trie.empty + +test.deprecated.internals.v1.Test.Report.toCLIResult : Report -> [Result] +test.deprecated.internals.v1.Test.Report.toCLIResult r = + use Nat toText + use Result Fail + use Text != ++ + descend scope = cases + (k, t) -> go ((if scope != "" then scope ++ "." else "") ++ k) t + convert : Text -> Status -> Result + convert scope = cases + Failed -> Fail scope + Expected (Passed n) -> Ok (scope ++ " : Passed " ++ toText n ++ " tests.") + Expected Proved -> Ok (scope ++ " : Proved.") + Unexpected (Passed n) -> + Fail (scope ++ " : Passed " ++ toText n ++ " tests.") + Unexpected Proved -> Fail (scope ++ " : Proved.") + Pending -> Ok (scope ++ " : Pending.") + go : Text -> Trie Text Status -> [Result] + go scope t = + use List +: + rest = List.flatMapRight (descend scope) (Map.toList (Trie.tail t)) + match Trie.head t with + Some status -> convert scope status +: rest + None -> rest + let + (Report t) = r + go "" t + +test.deprecated.laws.absorption : + '{Gen} a -> (a ->{e} a ->{e} a) -> (a ->{e} a ->{e} a) ->{e, Gen} Test +test.deprecated.laws.absorption gen f g = + x = gen() + y = gen() + expect (f x (g x y) === x && g x (f x y) === x) + +test.deprecated.laws.absorption.doc : Doc +test.deprecated.laws.absorption.doc = + use Nat max min + {{ + Given a generator and two binary functions, constructs a test that they're + connected by the absorption law. + + For example, `` deprecated.laws.absorption test.gen.nat min max `` checks + that `` min a (max a b) === a `` and ``max a (min a b) === a``. + }} + +test.deprecated.laws.adjoint : + '{Gen} o + -> (o ->{g1} o ->{g2} t) + -> (o ->{g3} o ->{g4} o) + -> (o ->{g5} o ->{g6} o) + ->{g1, g2, g3, g4, g5, g6, Gen} Test +test.deprecated.laws.adjoint gen p f g = + a = gen() + b = gen() + c = gen() + expect (p (f a b) c === p a (g b c)) + +test.deprecated.laws.adjoint.doc : Doc +test.deprecated.laws.adjoint.doc = + use Nat + - <= + {{ + `` adjoint gen p f g `` is a {type Test} that verifies that the functions `f` + and `g` are an [adjoint pair](https://en.wikipedia.org/wiki/Adjoint_functor) + with respect to the comparison function `p`. + + That is, it tests that the following property holds, for all `a`, `b`, and + `c` generated by `gen`: + + {{ docExample 6 do p f g a b c -> p (f a b) c === p a (g b c) }} + + # Example + + ``` + runs 100 do adjoint (gen.natIn 0 100) (<=) (-) (+) + ``` + }} + +test.deprecated.laws.associative : + '{Gen} a -> (a ->{e} a ->{e} a) ->{e, Gen} Test +test.deprecated.laws.associative gen f = + x = gen() + y = gen() + z = gen() + expect (f x (f y z) === f (f x y) z) + +test.deprecated.laws.associative.doc : Doc +test.deprecated.laws.associative.doc = + {{ + Given a generator and a binary function, constructs a test that the function + is associative. + }} + +test.deprecated.laws.commutative : + '{Gen} a -> (a ->{e} a ->{e} b) ->{e, Gen} Test +test.deprecated.laws.commutative gen f = + x = gen() + y = gen() + expect (f x y === f y x) + +test.deprecated.laws.commutative.doc : Doc +test.deprecated.laws.commutative.doc = + {{ + Given a generator and a binary function, constructs a test that the function + is commutative. + }} + +test.deprecated.laws.distributive : + '{Gen} a -> (a ->{e} a ->{e} a) -> (a ->{e} a ->{e} a) ->{e, Gen} Test +test.deprecated.laws.distributive gen f g = + x = gen() + y = gen() + z = gen() + expect (f x (g y z) === g (f x y) (f x z)) + +test.deprecated.laws.distributive.doc : Doc +test.deprecated.laws.distributive.doc = + {{ + Given a generator and two binary functions, constructs a test that the first + function distributes over the second. + + For example, `` laws.distributive gen.nat (*) (+) `` checks that + {{ + docExample 3 do a b c -> a Nat.* (b Nat.+ c) === a Nat.* b Nat.+ a Nat.* c + }}. + }} + +test.deprecated.laws.homomorphism : + '{Gen} a + -> (a ->{e} b) + -> (a ->{e} a ->{e} a) + -> (b ->{e} b ->{e} b) + ->{e, Gen} Test +test.deprecated.laws.homomorphism gen f p q = + a = gen() + b = gen() + expect (f (p a b) === q (f a) (f b)) + +test.deprecated.laws.homomorphism.doc : Doc +test.deprecated.laws.homomorphism.doc = + {{ + Given a generator `gen`, a function `f`, and two binary composition operators + `p` and `q`, `` deprecated.laws.homomorphism gen f p q `` constructs a test + that ``f (p a b) === q (f a) (f b)``. + }} + +test.deprecated.laws.idempotence : + '{Gen} a -> (a ->{e} a ->{e} a) ->{e, Gen} Test +test.deprecated.laws.idempotence gen f = + x = gen() + expect (f x x === x) + +test.deprecated.laws.idempotence.doc : Doc +test.deprecated.laws.idempotence.doc = + use Nat min + {{ + Given a generator `gen`, a binary function `f`, constructs a test that the + function is idempotent. + + For example, `` deprecated.laws.idempotence test.gen.nat min `` checks that + ``min x x === x``. + }} + +test.deprecated.laws.lattice : + '{Gen} a -> (a ->{e} a ->{e} a) -> (a ->{e} a ->{e} a) ->{e, Gen} Test +test.deprecated.laws.lattice gen meet join = + use Test both + use laws associative commutative + both + (both + (both + (both (deprecated.laws.absorption gen meet join) (associative gen meet)) + (associative gen join)) + (commutative gen meet)) + (commutative gen join) + +test.deprecated.laws.lattice.doc : Doc +test.deprecated.laws.lattice.doc = + {{ + Given a generator and two binary operations, `meet` and `join`, construct a + test that these operations form a lattice. + + That is, that they obey an absorption law: + + `meet x (join x y) = join x (meet x y) = x` + + That they are commutative: + + `meet x y = meet y x` and `join x y = join y x` + + And associative: + + `meet x (meet y z) = meet (meet x y) z` and + `join x (join y z) = join (join x y) z` + }} + +test.deprecated.laws.reflexive : + '{Gen} a -> (a ->{e} a ->{e} Boolean) ->{e, Gen} Test +test.deprecated.laws.reflexive gen f = + m = gen() + expect (f m m) + +test.deprecated.laws.reflexive.doc : Doc +test.deprecated.laws.reflexive.doc = + {{ + Takes a {type Gen} generator and a binary function that returns + {type Boolean}, and returns a {type Test} that checks that the function is + reflexive. That is, it checks that for all inputs `x`, `f x x` is ``true``. + + # Example + + ``` + prove do laws.reflexive gen.boolean iff + ``` + }} + +test.deprecated.laws.transitive : + '{Gen} a -> (a ->{e} a ->{e} Boolean) ->{e, Gen} Test +test.deprecated.laws.transitive gen f = + a = gen() + b = gen() + c = gen() + expect (implies (f a b && f b c) (f a c)) + +test.deprecated.laws.transitive.doc : Doc +test.deprecated.laws.transitive.doc = + {{ + Tests that the given relation is transitive for all inputs generated by the + given generator. + + # Example + + ``` + prove do laws.transitive gen.boolean iff + ``` + }} + +test.deprecated.prove : '{e, Gen} Test ->{e} [Result] +test.deprecated.prove t = + use deprecated run + testCases = List.flatMap run (deprecated.sample maxNat t) + if List.all isOk testCases then run proved + else List.filter (Boolean.not << isOk) testCases + +test.deprecated.prove.doc : Doc +test.deprecated.prove.doc = + use Text ++ + use gen boolean + {{ + Takes a generator of {type Test}s in the {type Gen} ability and exhaustively + runs them all, returning a {type List} of {type Result}s. If all tests + passed, returns a single {type Result} in the {Proved} state. Otherwise, + returns a {type List} of {type Result}s in the {Failed} state. + + # Example + + ``` + prove do laws.reflexive boolean iff + ``` + + ``` + prove do + b = boolean() + if b then proved else failWith (Boolean.toText b ++ " is not true") + ``` + }} + +test.deprecated.run : Test -> [Result] +test.deprecated.run = toCLIResult << report + +test.deprecated.run.doc : Doc +test.deprecated.run.doc = + use Nat + == + {{ + Run a {type Test} and return the result. + + # Example + + ``` + deprecated.run (expect (1 + 1 == 2)) + ``` + }} + +test.deprecated.runAll : [Test] -> [Result] +test.deprecated.runAll = List.flatMapRight deprecated.run + +test.deprecated.runAll.doc : Doc +test.deprecated.runAll.doc = + use Nat + == + {{ + Run a list of {type Test}s and return the results. + + # Example + + ``` + runAll [expect (1 + 1 == 2), expect (2 + 2 == 4)] + ``` + }} + +test.deprecated.runs : Nat -> '{e, Gen} Test ->{e} [Result] +test.deprecated.runs n ts = + deprecated.run <| Test.tests (deprecated.sample n ts) + +test.deprecated.runs.doc : Doc +test.deprecated.runs.doc = + use Int + == + use gen int + {{ + Run a {type Test} a given number of times. The {type Test} is allowed to use + arbitrary abilities, and {runs} will sample any {type Gen} generators it uses + to generate fresh test data for each run. + + # Example + + This test will run 100 times: + + ``` + runs 100 do + x = int() + y = int() + expect (x + y == y + x) + ``` + }} + +test.deprecated.sample : Nat -> '{e, Gen} a ->{e} [a] +test.deprecated.sample n g = Weighted.sample n (toWeighted g) + +test.deprecated.sample.doc : Doc +test.deprecated.sample.doc = + use deprecated sample + use gen boolean int + {{ + Returns at most a number of sample values from a generator. + + # Examples + + ``` + sample 8 int + ``` + + ``` + sample 8 (gen.optional int) + ``` + + If the generator is exhausted before the number of samples is reached, + {sample} returns a {type List} of fewer than the requested number of + values: + + ``` + sample 20 (pairOf boolean boolean) + ``` + }} + +test.deprecated.Test.both : Test -> Test -> Test +test.deprecated.Test.both = cases + Test k1, Test k2 -> + Test + (scope -> let + r1 = k1 scope + r2 = k2 scope + Report.combine r1 r2) + +test.deprecated.Test.both.doc : Doc +test.deprecated.Test.both.doc = + use Nat + == + use deprecated.Test.label deprecated + {{ + Combines two tests into a single test that passes if both tests pass and + fails if either test fails. Also concatenates the results the two tests into + a single result. + + # Examples + + ``` + deprecated.run + (Test.both + (deprecated "Two" (expect (1 + 1 == 2))) + (deprecated "Four" (expect (2 + 2 == 4)))) + ``` + }} + +test.deprecated.Test.expect : Boolean -> Test +test.deprecated.Test.expect b = if b then Test.ok else Test.fail + +test.deprecated.Test.expect.doc : Doc +test.deprecated.Test.expect.doc = + use Int + == + use gen int + {{ + A {type Test} that expects a {type Boolean} to be `` true `` and fails + otherwise. + + # Example + + ``` + runs 100 do + x = int() + y = int() + expect (x + y == y + x) + ``` + }} + +test.deprecated.Test.fail : Test +test.deprecated.Test.fail = finished Failed + +test.deprecated.Test.fail.doc : Doc +test.deprecated.Test.fail.doc = + {{ + A test that always fails. + + # Example + + ``` + deprecated.run (deprecated.Test.label.deprecated "Bad" Test.fail) + ``` + }} + +test.deprecated.Test.failWith : Text -> Test +test.deprecated.Test.failWith m = deprecated.Test.label.deprecated m Test.fail + +test.deprecated.Test.failWith.doc : Doc +test.deprecated.Test.failWith.doc = + {{ + Fails a test with the provided message. + + # Examples + + ``` + deprecated.run (failWith "This test always fails") + ``` + + ``` + runs 2 do + if gen.boolean() then failWith "This test fails half the time" + else okWith "This test passes half the time" + ``` + + # See also + + * {Test.fail} + * {expect} + }} + +test.deprecated.Test.label.deprecated : Text -> Test -> Test +test.deprecated.Test.label.deprecated n = cases + Test k -> Test (scope -> k <| Scope.cons n scope) + +test.deprecated.Test.label.deprecated.doc : Doc +test.deprecated.Test.label.deprecated.doc = + use Nat == + {{ + Labels a {type Test} with a {type Text} description. + + # Example + + ``` + deprecated.run + (deprecated.Test.label.deprecated "My test" (check' (1 == 1))) + ``` + }} + +test.deprecated.Test.ok : Test +test.deprecated.Test.ok = finished << Expected <| Passed 1 + +test.deprecated.Test.ok.doc : Doc +test.deprecated.Test.ok.doc = + use Int + == + use gen int + {{ + A {type Test} that always succeeds. + + # Example + + ``` + runs 100 do + x = int() + y = int() + if x + y == y + x then Test.ok else Test.fail + ``` + }} + +test.deprecated.Test.okWith : Text -> Test +test.deprecated.Test.okWith m = deprecated.Test.label.deprecated m Test.ok + +test.deprecated.Test.okWith.doc : Doc +test.deprecated.Test.okWith.doc = + use Int + == + use gen int + {{ + Takes a {type Text} message and returns a {type Test} that succeeds with that + message. + + # Example + + ``` + runs 100 do + x = int() + y = int() + if x + y == y + x then okWith "x + y == y + x" + else failWith "x + y != y + x" + ``` + }} + +test.deprecated.Test.pass : Nat -> Test +test.deprecated.Test.pass n = finished (Expected (Passed n)) + +test.deprecated.Test.pass.doc : Doc +test.deprecated.Test.pass.doc = + {{ A {type Test} that passed the given number of test cases. }} + +test.deprecated.Test.proved : Test +test.deprecated.Test.proved = finished <| Expected Proved + +test.deprecated.Test.proved.doc : Doc +test.deprecated.Test.proved.doc = + use Nat == + {{ + A {type Test} that succeeded as "proved", meaning that it passed for all + possible inputs. This is the appropriate result for a property-based test + that has exhaustively checked its property for all possible inputs, or a unit + test that has no inputs. + + # Examples + + A unit test that's considered proved if it passes: + + ``` + deprecated.run (if 1 == 1 then proved else failWith "1 != 1") + ``` + + A property-based test that takes a property and considers it proved if it + exhaustively checks a small {type Domain}: + + @typecheck ``` + forAll : Nat -> Domain a -> (a ->{e} Boolean) ->{e} Test + forAll maxSize domain property = + use List all size + use Nat <= + check testCases = + if all property testCases then pass (size testCases) + else failWith "property failed on some inputs" + match domain with + Small xs + | size xs <= maxSize && all property xs -> proved + | otherwise -> + check (List.take maxSize xs) + Large xs -> check (Weighted.sample maxSize xs) + ``` + }} + +test.deprecated.Test.provedWith : Text -> Test +test.deprecated.Test.provedWith m = deprecated.Test.label.deprecated m proved + +test.deprecated.Test.provedWith.doc : Doc +test.deprecated.Test.provedWith.doc = + {{ + Produces a {type Test} that is finished in the {Proved} state with the given + {type Text} message. + + # Example + + ``` + deprecated.run + let + optionals = [Some true, Some false, None] + if List.map (Optional.filter id) optionals === [Some true, None, None] then + deprecated.Test.provedWith "Optional.filter works on all inputs" + else failWith "Optional.filter failed on some inputs" + ``` + }} + +test.deprecated.Test.tests : [Test] -> Test +test.deprecated.Test.tests = cases + [] -> Test.ok + h +: ts -> List.foldLeft Test.both h ts + +test.deprecated.Test.unexpected.ok : Test +test.deprecated.Test.unexpected.ok = finished << Unexpected <| Passed 1 + +test.deprecated.Test.unexpected.ok.doc : Doc +test.deprecated.Test.unexpected.ok.doc = + use deprecated run + {{ + Creates a {type Test} that unexpectedly {Passed}. Use this when writing a + test that you expect to fail. For example, if you are testing a function that + has a known bug, you can write a test that calls the function and expects it + to fail, and then mark the test as {Passed} with this function. If the test + unexpectedly {Passed}, you will be notified that the bug has been fixed and + reminded to update the test. + + # Example + + ``` + run (if Nat.isEven 3 then unexpected.ok else Test.ok) + ``` + + ``` + run (if Nat.isOdd 4 then Test.ok else unexpected.ok) + ``` + }} + +test.deprecated.Test.unexpected.proven : Test +test.deprecated.Test.unexpected.proven = finished <| Unexpected Proved + +test.deprecated.Test.unexpected.proven.doc : Doc +test.deprecated.Test.unexpected.proven.doc = + use Test ok + use deprecated run + {{ + Creates a {type Test} that unexpectedly finished in the {Proved} state. Use + this when writing a test that you expect to fail. For example, if you are + testing a function that has a known bug, you can write a test that calls the + function and expects it to fail, and then mark the test as {Proved} with this + function. If the test unexpectedly {Proved}, you will be notified that the + bug has been fixed and reminded to update the test. + + # Example + + ``` + run (if Nat.isEven 3 then proven else ok) + ``` + + ``` + run (if Nat.isOdd 4 then ok else proven) + ``` + }} + +test.deprecated.verifyAndIgnore : + '{g, Exception, Each, Random, Label} a ->{g} [Result] +test.deprecated.verifyAndIgnore a = verifyWithSeedAndIgnore 1 a + +test.deprecated.verifyAndIgnore.doc : Doc +test.deprecated.verifyAndIgnore.doc = + use Debug trace + use Each range repeat + use Nat + >= + use Random natIn + use test raiseFailure verify + {{ + {{ + docCallout + (Some {{ 🗑 }}) + {{ + {verifyAndIgnore} has been deprecated. See {verify} for a variant that is + less error-prone. The following code shows an example of how + {verifyAndIgnore} can silently do something unintended: + + ``` + (verifyAndIgnore do 1 + 1 Nat.== 3) + ``` + + When writing this test, you might think that the test will fail since + `1 + 1` does not equal `3`. However, `verifyAndIgnore` expects a + {type Exception} to be [raised]({Exception.raise}) if there is an error, so + it still treats `false` as success. With {verify} you will get a compile + error that hints at the problem. + }} }} + + A simple test runner, with access to {type Exception} (for triggering early + termination of a test), {type Each} (for exhaustive enumeration of a domain), + {type Random} (for random generation), and {type Label} (for adding scoped + labels to tests). + + Use {verifyWithSeedAndIgnore} if you'd like to customize the random seed. + + @signatures{verifyAndIgnore, ensure, ensureEqual, ensureWith, raiseFailure} + + # Tutorial + + Use {ensure} or {ensureWith} or {ensureEqual} to construct test cases: + + ``` + verifyAndIgnore do ensure (1 + 1 Nat.== 2) + ``` + + On failure, the argument to {ensureWith} will be printed to console using + the {trace} function: + + ``` + verifyAndIgnore do ensureWith "label to print on failure" (1 + 1 Nat.== 4) + ``` + + You can use the {type Each} ability for convenient exhaustive enumeration + of test cases. Here we're checking that addition is commutative for all the + numbers in ``range 0 10``: + + ``` + verifyAndIgnore do + n = range 0 10 + m = range 0 10 + ensureEqual (n + m) (m + n) + ``` + + You can also use randomness in your tests: + + ``` + verifyAndIgnore do + use Set == + n = range 0 100 + s = Set.fromList (List.replicate n do natIn 0 20) + ensure (Set.union s s == s) + ``` + + This works nicely with the {repeat} function. Just put that at the start of + your test to repeat the rest of the test multiple times, with different + random values: + + ``` + verifyAndIgnore do + repeat 100 + n = natIn 0 1000 + m = natIn 0 1000 + max = Nat.max n m + ensure (max >= n && max >= m) + ``` + + If you want to add some temporary diagnostics while debugging a test, use + the {ensureWith} function or call {trace} directly. + + You can also use the {type Label} ability to add labels to tests to help + identify the values or conditions that caused the test to fail. For + example: + + @typecheck ``` + verifyAndIgnore do + labeled "This will randomly fail" do + x = natIn 0 10 + y = natIn 0 100 + label "x" x + label "y" y + ensureEqual (x + y) 100 + ``` + + In this example, the labels "x" and "y" are added to the scope "This will + randomly fail". If the test fails, the error message will include the + labels and their values along with the error message that caused the test + to fail: + + ``` raw + 🚫 FAILED + This will randomly fail: + x: 3 + y: 67 + elements not equal + (70, 100) + ``` + + # See also + + * {type Each} for exhaustive enumeration of a domain + * {type Random} for random generation + * {type Label} for adding labels to tests to help identify the values or + conditions that caused the test to fail + * {raiseFailure} for raising a test failure + * {verifyWithSeedAndIgnore} for customizing the seed + }} + +test.deprecated.verifyWithSeedAndIgnore : + Nat -> '{g, Exception, Each, Random, Label} a ->{g} [Result] +test.deprecated.verifyWithSeedAndIgnore seed a = + use Nat * + use Result Fail + go : [Result] ->{g} a ->{g} [Result] + go _ a = [Ok "Passed"] + match Label.run do + catch do splitmix seed do Stream.fold go [] (Each.toStream a) with + (labels, Left (Failure typ msg payload)) -> + ind = Text.repeat (List.size labels * 2) " " + [ Fail + (unlines + ([""] List.++ lines (formatLabels labels) + List.++ [ ind Text.++ msg + , ind Text.++ toDebugText (unsafeExtract payload) + ])) + ] + (labels, Right []) -> [Fail "No test cases provided"] + (labels, Right rs) -> rs + +test.deprecated.verifyWithSeedAndIgnore.doc : Doc +test.deprecated.verifyWithSeedAndIgnore.doc = + {{ + {{ + docCallout + (Some {{ 🗑 }}) + {{ + {verifyWithSeedAndIgnore} is deprecated. See {verifyWithSeed} for an + alternative that is less error-prone and {verifyAndIgnore.doc} for an + explanation of why this version is error-prone. + }} }} Same as {verifyAndIgnore}, but the first argument is a random seed + used for any use of the {type Random} ability. + }} + +test.ensure : Boolean ->{Exception} () +test.ensure cond = ensureWith () cond + +test.ensure.doc : Doc +test.ensure.doc = + {{ + `` ensure true `` returns ``()``, while `` ensure false `` fails the current + test with an {type Exception}. + + Use {ensureWith} if you'd like to provide a more informative error on + failure, or use {test.raiseFailure} directly. + }} + +test.ensureEqual : a -> a ->{Exception} () +test.ensureEqual a1 a2 = + if a1 === a2 then () else test.raiseFailure "elements not equal" (a1, a2) + +test.ensureEqual.doc : Doc +test.ensureEqual.doc = + {{ + `` ensureEqual a1 a2 `` succeeds if `` a1 === a2 `` and otherwise fails with + an exception that includes both `a1` and `a2`. + }} + +test.ensureGreater : x -> x ->{Exception} () +test.ensureGreater x y = + use Text ++ + if Universal.gt x y then () + else + test.raiseFailure + (toDebugText x ++ " is not greater than " ++ toDebugText y) (x, y) + +test.ensureGreater.doc : Doc +test.ensureGreater.doc = + {{ + Ensures that the first argument is greater than the second. If the condition + is not met, the function raises a test failure with a descriptive message. + }} + +test.ensureGreaterOrEqual : x -> x ->{Exception} () +test.ensureGreaterOrEqual x y = + use Text ++ + if Universal.gteq x y then () + else + test.raiseFailure + (toDebugText x ++ " is not greater than or equal to " ++ toDebugText y) + (x, y) + +test.ensureGreaterOrEqual.doc : Doc +test.ensureGreaterOrEqual.doc = + {{ + Ensures that the first argument is greater than or equal to the second. If + the condition is not met, the function raises a test failure with a + descriptive message. + }} + +test.ensureLess : x -> x ->{Exception} () +test.ensureLess x y = + use Text ++ + if Universal.lt x y then () + else + test.raiseFailure + (toDebugText x ++ " is not less than " ++ toDebugText y) (x, y) + +test.ensureLess.doc : Doc +test.ensureLess.doc = + {{ + Ensures that the first argument is less than the second. If the condition is + not met, the function raises a test failure with a descriptive message. + }} + +test.ensureLessOrEqual : x -> x ->{Exception} () +test.ensureLessOrEqual x y = + use Text ++ + if Universal.lteq x y then () + else + test.raiseFailure + (toDebugText x ++ " is not less than or equal to " ++ toDebugText y) + (x, y) + +test.ensureLessOrEqual.doc : Doc +test.ensureLessOrEqual.doc = + {{ + Ensures that the first argument is less than or equal to the second. If the + condition is not met, the function raises a test failure with a descriptive + message. + }} + +test.ensureNotEqual : a -> a ->{Exception} () +test.ensureNotEqual a1 a2 = + if a1 !== a2 then () + else test.raiseFailure "elements are unexpectedly equal" (a1, a2) + +test.ensureNotEqual.doc : Doc +test.ensureNotEqual.doc = + {{ + {{ docExample 2 do a1 a2 -> ensureNotEqual a1 a2 }} succeeds if `` a1 !== a2 + `` and otherwise raises an exception that includes the values of `a1` and + `a2`. + }} + +test.ensureWith : a -> Boolean ->{Exception} () +test.ensureWith a ok = + if ok then () else test.raiseFailure "condition failed" a + +test.ensureWith.doc : Doc +test.ensureWith.doc = + {{ + `` ensureWith msg true `` returns ``()``, while `` ensure false `` calls + {test.raiseFailure} to abort the current test. + }} + +test.ensuring : '{g} Boolean ->{g, Exception} () +test.ensuring thunk = + if thunk() then () else test.raiseFailure "validation failed" thunk + +test.ensuring.doc : Doc +test.ensuring.doc = + use Nat isEven + {{ + Ensure that a thunk returns ``true``. If the thunk returns ``false``, raise a + failure. The payload of the failure is the thunk itself, allowing the user to + inspect the value of the thunk. + + # Example + + ``` + catch do ensuring do isEven 0 + ``` + + ``` + catch do ensuring do isEven 3 + ``` + }} + +test.ensuringWith : Text -> a -> '{g} Boolean ->{g, Exception} () +test.ensuringWith msg payload b = + if b() then () else test.raiseFailure msg payload + +test.ensuringWith.doc : Doc +test.ensuringWith.doc = + {{ + {{ docExample 3 do msg payload b -> ensuringWith msg payload b }} succeeds if + forcing `b` results in ``true``, and otherwise raises an exception with the + given message and payload. + }} + +test.laws.abelianGroup : + '{Each, Random} t + -> (t ->{e} t ->{e1} t) + -> t + -> (t ->{e2} t) + ->{e2, e1, e, Exception, Each, Random, Label} () +test.laws.abelianGroup gen op z inv = + laws.group gen op z inv + commutativity gen op + +test.laws.abelianGroup.doc : Doc +test.laws.abelianGroup.doc = + use Int + + {{ + Checks the abelian group properties of a binary operation, an identity + element, and an inverse operation. + + `` abelianGroup gen op z inv `` checks that the operation `op`, identity + element `z`, and inverse operation `inv` satisfy the {laws.group} and + {commutativity} properties. + + For example, if `op` is {+}, `z` is ``+0``, and `inv` is {Int.negate}, then + the abelian group properties are satisfied. + }} + +test.laws.absorption : + '{Each, Random} a + -> (a ->{e} a ->{e} a) + -> (a ->{e} a ->{e} a) + ->{e, Exception, Each, Random, Label} () +test.laws.absorption gen op1 op2 = labeled "absorption" do + x = gen() + y = gen() + label "x" x + label "y" y + ensureEqual (op1 x (op2 x y)) x + ensureEqual (op2 x (op1 x y)) x + +test.laws.absorption.doc : Doc +test.laws.absorption.doc = + {{ + Checks the absorption property of a pair of binary operations. + + `` test.laws.absorption gen f g `` checks that for all elements `x` and `y` + returned by `gen`, `f x (g x y)` is equal to `x` and also equal to + `g x (f x y)`. + + For example, if `f` is {Nat.max} and `g` is {Nat.min}, then `` + Nat.max x (Nat.min x y) `` is equal to `x` and also equal to + ``Nat.min x (max x y)``. + }} + +test.laws.associativity : + '{Each, Random} a + -> (a ->{e} a ->{e} a) + ->{e, Exception, Each, Random, Label} () +test.laws.associativity gen op = labeled "associativity" do + x = gen() + y = gen() + z = gen() + label "x" x + label "y" y + label "z" z + ensureEqual (op x (op y z)) (op (op x y) z) + +test.laws.associativity.doc : Doc +test.laws.associativity.doc = + {{ + Checks the associativity property of a binary operation. + + `` associativity gen f `` checks that for all elements `x`, `y`, and `z` + returned by `gen`, `f x (f y z)` is equal to `f (f x y) z`. + + For example, if `f` is {Nat.+}, then {{ docExample 4 do x + y z -> x + y + z + }} is equal to {{ docExample 4 do + x y z -> x + y + z }}. + }} + +test.laws.commutativity : + '{Each, Random} a + -> (a ->{e} a ->{e} a) + ->{e, Exception, Each, Random, Label} () +test.laws.commutativity gen op = labeled "commutativity" do + x = gen() + y = gen() + label "x" x + label "y" y + ensureEqual (op x y) (op y x) + +test.laws.commutativity.doc : Doc +test.laws.commutativity.doc = + use Nat + + {{ + Checks the commutativity property of a binary operation. + + `` commutativity gen f `` checks that for all elements `x` and `y` returned + by `gen`, `f x y` is equal to `f y x`. + + For example, if `f` is {+}, then `` x + y `` is equal to ``y + x``. + }} + +test.laws.distributivity : + '{Each, Random} a + -> (a ->{e} a ->{e} a) + -> (a ->{e} a ->{e} a) + ->{e, Exception, Each, Random, Label} () +test.laws.distributivity gen op1 op2 = + x = gen() + y = gen() + z = gen() + label "x" x + label "y" y + label "z" z + ensureEqual (op1 x (op2 y z)) (op2 (op1 x y) (op1 x z)) + +test.laws.distributivity.doc : Doc +test.laws.distributivity.doc = + use Nat * + + {{ + Checks the distributivity property of a pair of binary operations. + + `` distributivity gen f g `` checks that for all elements `x`, `y`, and `z` + returned by `gen`, `f x (g y z)` is equal to `g (f x y) (f x z)`. + + For example, if `f` is {*} and `g` is {+}, then `` x * ( y + z ) `` is equal + to ``x * y + x * z``. + }} + +test.laws.group : + '{Each, Random} t + -> (t ->{e} t ->{e1} t) + -> t + -> (t ->{e2} t) + ->{e2, e1, e, Exception, Each, Random, Label} () +test.laws.group gen op z inv = + monoid gen op z + involutive gen inv + +test.laws.group.doc : Doc +test.laws.group.doc = + use Int + + {{ + Checks the group properties of a binary operation, an identity element, and + an inverse operation. + + `` laws.group gen op z inv `` checks that the operation `op`, identity + element `z`, and inverse operation `inv` satisfy the {monoid} and + {involutive} properties. + + For example, if `op` is {+}, `z` is ``+0``, and `inv` is {Int.negate}, then + the group properties are satisfied. + }} + +test.laws.homomorphism : + '{Each, Random} a + -> (a ->{e} a ->{e} a) + -> (b ->{e} b ->{e} b) + -> (a ->{e} b) + ->{e, Exception, Each, Random, Label} () +test.laws.homomorphism genA opA opB h = labeled "homomorphism" do + x = genA() + y = genA() + label "x" x + label "y" y + ensureEqual (h (opA x y)) (opB (h x) (h y)) + +test.laws.homomorphism.doc : Doc +test.laws.homomorphism.doc = + use Nat + + use Text ++ size + {{ + Checks the homomorphism property of a pair of binary operations and a + function. + + `` test.laws.homomorphism gen f g h `` checks that for all elements `x` and + `y` returned by `gen`, `h (f x y)` is equal to `g (h x) (h y)`. + + For example, if `f` is {++}, `g` is {+} and `h` is {size}, then `` + size (x ++ y) `` is equal to ``size x + size y``. + }} + +test.laws.idempotence : + '{Each, Random} a -> (a ->{e} a) ->{e, Exception, Each, Random, Label} () +test.laws.idempotence gen op = labeled "idempotence" do + x = gen() + label "x" x + ensureEqual (op (op x)) (op x) + +test.laws.idempotence.doc : Doc +test.laws.idempotence.doc = + use Int abs + {{ + Checks the idempotence property of a unary operation. + + `` test.laws.idempotence gen f `` checks that for all elements `x` returned + by `gen`, `f (f x)` is equal to `f x`. + + For example, if `f` is {abs}, then {{ + docExample 2 do Int.abs x -> abs (abs x) }} is equal to ``abs x``. + }} + +test.laws.identity : + '{Each, Random} a + -> (a ->{e} a ->{e} a) + -> a + ->{e, Exception, Each, Random, Label} () +test.laws.identity gen op e = labeled "identity" do + x = gen() + label "x" x + labeled "left identity" do ensureEqual (op e x) x + labeled "right identity" do ensureEqual (op x e) x + +test.laws.identity.doc : Doc +test.laws.identity.doc = + use Nat + + {{ + Checks the identity property of a binary operation. + + `` laws.identity gen f e `` checks that for all elements `x` returned by + `gen`, `f e x` is equal to `x` and `f x e` is equal to `x`. + + For example, if `f` is {+} and `e` is ``0``, then `` 0 + x `` is equal to `x` + and `` x + 0 `` is equal to `x`. + }} + +test.laws.involutive : + '{Each, Random} a -> (a ->{e} a) ->{e, Exception, Each, Random, Label} () +test.laws.involutive gen op = labeled "involutive" do + x = gen() + label "x" x + ensureEqual (op (op x)) x + +test.laws.involutive.doc : Doc +test.laws.involutive.doc = + use Boolean not + {{ + Checks the involutive property of a unary operation. + + `` involutive gen f `` checks that for all elements `x` returned by `gen`, + `f (f x)` is equal to `x`. + + For example, if `f` is {not}, then {{ + docExample 2 do Boolean.not x -> not (not x) }} is equal to `x`. + }} + +test.laws.lattice : + '{Each, Random} a + -> (a ->{e} a ->{e} a) + -> (a ->{e} a ->{e} a) + ->{e, Exception, Each, Random, Label} () +test.laws.lattice gen meet join = labeled "lattice" do + test.laws.absorption gen meet join + associativity gen meet + associativity gen join + commutativity gen meet + commutativity gen join + +test.laws.lattice.doc : Doc +test.laws.lattice.doc = + {{ + Checks the lattice properties of a pair of binary operations. + + `` test.laws.lattice gen meet join `` checks that the operations `meet` and + `join` satisfy the {test.laws.absorption}, {associativity}, and + {commutativity} properties. + + For example, if `meet` is {Nat.min} and `join` is {Nat.max}, then the lattice + properties are satisfied. + }} + +test.laws.monoid : + '{Each, Random} t + -> (t ->{e} t ->{e1} t) + -> t + ->{e1, e, Exception, Each, Random, Label} () +test.laws.monoid gen op z = + associativity gen op + laws.identity gen op z + +test.laws.monoid.doc : Doc +test.laws.monoid.doc = + use Nat + + {{ + Checks the monoid properties of a binary operation and an identity element. + + `` monoid gen op z `` checks that the operation `op` and identity element `z` + satisfy the {associativity} and {laws.identity} properties. + + For example, if `op` is {+} and `z` is ``0``, then the monoid properties are + satisfied. + }} + +test.laws.reflexivity : + '{Each, Random} a + -> (a ->{e} a ->{e} Boolean) + ->{e, Exception, Each, Random, Label} () +test.laws.reflexivity gen op = labeled "reflexivity" do + x = gen() + label "x" x + ensure (op x x) + +test.laws.reflexivity.doc : Doc +test.laws.reflexivity.doc = + use Nat == + {{ + Checks the reflexivity property of a binary operation. + + `` reflexivity gen f `` checks that for all elements `x` returned by `gen`, + `f x x` is true. + + For example, if `f` is {==}, then `` x == x `` is true. + }} + +test.laws.ring : + '{Each, Random} t + -> (t ->{e4} t ->{e3} t) + -> t + -> (t ->{e2} t) + -> (t ->{e1} t ->{e} t) + -> t + ->{e4, e3, e2, e1, e, Exception, Each, Random, Label} () +test.laws.ring gen add zero neg mul one = + abelianGroup gen add zero neg + monoid gen mul one + distributivity gen add mul + +test.laws.ring.doc : Doc +test.laws.ring.doc = + use Int * + + {{ + Checks the ring properties of a pair of binary operations and two identity + elements. + + `` ring gen add zero neg mul one `` checks that the operations `add` and + `mul`, identity elements `zero` and `one`, and inverse operation `neg` + satisfy the {abelianGroup}, {monoid}, and {distributivity} properties. + + For example, if `add` is {+}, `zero` is ``+0``, `neg` is {Int.negate}, `mul` + is {*}, and `one` is ``+1``, then the ring properties are satisfied. + }} + +test.laws.transitivity : + '{Each, Random} a + -> (a ->{e} a ->{e} Boolean) + ->{e, Exception, Each, Random, Label} () +test.laws.transitivity gen op = labeled "transitivity" do + x = gen() + y = gen() + z = gen() + label "x" x + label "y" y + label "z" z + ensure (implies (op x y && op y z) (op x z)) + +test.laws.transitivity.doc : Doc +test.laws.transitivity.doc = + use Nat < + {{ + Checks the transitivity property of a binary operation. + + `` transitivity gen f `` checks that for all elements `x`, `y`, and `z` + returned by `gen`, if `f x y` and `f y z` are true, then `f x z` is true. + + For example, if `f` is {<}, then if `` x < y `` and ``y < z``, then + ``x < z``. + }} + +test.raiseFailure : Text -> a ->{Exception} x +test.raiseFailure msg a = + Exception.raise (Failure (typeLink TestFailure) msg (Any a)) + +test.raiseFailure.doc : Doc +test.raiseFailure.doc = + {{ + `` test.raiseFailure msg a `` raises a test failure exception, containing + `msg` and `a` in the payload. + }} + +test.Result.doc : Doc +test.Result.doc = + use deprecated forAll + use test verify + {{ + The {type Result} type represents the result of a test. It can be either of + the following: + + * {Result.Fail}: The test failed with a message. + * {Ok}: The test succeeded with a message. + + The test runner in the Unison Codebase Manager expects all tests to return a + list of {type Result} values. When you issue the `test` command in UCM it + will evaluate all the expressions of that type in the current namespace and + report the results. + + 📚 Guide: [Testing your Unison Code](https://unisonweb.org/docs/testing) + + # Functions that generate {type Result} values + + These functions create {type List}s of {type Result} values, which is the + expected return type of all tests. + + Check that a boolean expression is ``true``, returning a success if it is + and `` false `` otherwise: + + @signature{check} + + Run a {type Test} and return the results: + + @signature{deprecated.run} + + Run a {type List} of tests and return the results: + + @signature{runAll} + + Run a {type Gen} computation that produces a {type Test}, a {type Nat} + number of times, and return the results: + + @signature{runs} + + Run a computation that may use the {type Random} and {type Each} abilities + to perform random testing. Generally the {type Random} ability is used to + generate test data, and the {type Each} ability is used to loop over the + data and test each value. The computation is expected to raise a + {type Failure} in the {type Exception} ability if the test fails, and + return any value if the test succeeds: + + @signature{verify} + + The same as {verify}, but takes a random seed as an argument: + + @signature{verifyWithSeed} + + Check that a predicate is `` true `` for at least a given {type Nat} number + of values in a {type Domain}. If the {type Domain} is finite and small + enough, {forAll} will check all values in the domain. If the {type Domain} + is large, {forAll} will check a sample of values in the {type Domain}: + + @signature{forAll} + }} + +test.Result.Fail.doc : Doc +test.Result.Fail.doc = + {{ + A {type Result} that represents a test failure. + + # Example + + @typecheck ``` + failingTest = [Result.Fail "This test always fails"] + ``` + }} + +test.Result.firstFailure : [Result] -> Text +test.Result.firstFailure = cases + [] -> "" + r +: rs -> + match r with + Ok _ -> test.Result.firstFailure rs + Result.Fail t -> t + +test.Result.firstFailure.doc : Doc +test.Result.firstFailure.doc = + {{ + Returns the first failure message in a list of test results. If there are no + failures, the result is an empty string. + }} + +test.Result.isOk : Result -> Boolean +test.Result.isOk = cases + Ok _ -> true + _ -> false + +test.Result.isOk.doc : Doc +test.Result.isOk.doc = {{ Returns true if the given {type Result} is {Ok}. }} + +test.Result.Ok.doc : Doc +test.Result.Ok.doc = {{ A {type Result} that indicates a test passed. }} + +test.Result.text : Result -> Text +test.Result.text = cases + Ok t -> t + Result.Fail t -> t + +test.Result.text.doc : Doc +test.Result.text.doc = + {{ + Converts a {type Result} to a {type Text} value. If the result is a success, + the text is the message indicating a passing test. If the result is a + failure, the text is the failure message. + }} + +test.TestFailure.doc : Doc +test.TestFailure.doc = + use test raiseFailure + {{ + A marker type for raising {type Failure}s in tests. This type is used by the + {raiseFailure} function, for example. + + # Example + + ``` + catch (raiseFailure "This test always fails") + ``` + }} + +test.verify : '{g, Exception, Each, Random, Label} () ->{g} [Result] +test.verify = verifyWithSeedAndIgnore 1 + +test.verify.doc : Doc +test.verify.doc = + use Debug trace + use Each range repeat + use Nat + >= + use Random natIn + use test raiseFailure verify + {{ + A simple test runner, with access to {type Exception} (for triggering early + termination of a test), {type Each} (for exhaustive enumeration of a domain), + {type Random} (for random generation), and {type Label} (for adding scoped + labels to tests). + + Use {verifyWithSeed} if you'd like to customize the random seed. + + @signatures{verify, ensure, ensureEqual, ensureWith, raiseFailure} + + # Tutorial + + Use {ensure} or {ensureWith} or {ensureEqual} to construct test cases: + + ``` + verify do ensure (1 + 1 Nat.== 2) + ``` + + On failure, the argument to {ensureWith} will be printed to console using + the {trace} function: + + ``` + verify do ensureWith "label to print on failure" (1 + 1 Nat.== 4) + ``` + + You can use the {type Each} ability for convenient exhaustive enumeration + of test cases. Here we're checking that addition is commutative for all the + numbers in ``range 0 10``: + + ``` + verify do + n = range 0 10 + m = range 0 10 + ensureEqual (n + m) (m + n) + ``` + + You can also use randomness in your tests: + + ``` + verify do + use Set == + n = range 0 100 + s = Set.fromList (List.replicate n do natIn 0 20) + ensure (Set.union s s == s) + ``` + + This works nicely with the {repeat} function. Just put that at the start of + your test to repeat the rest of the test multiple times, with different + random values: + + ``` + verify do + repeat 100 + n = natIn 0 1000 + m = natIn 0 1000 + max = Nat.max n m + ensure (max >= n && max >= m) + ``` + + If you want to add some temporary diagnostics while debugging a test, use + the {ensureWith} function or call {trace} directly. + + You can also use the {type Label} ability to add labels to tests to help + identify the values or conditions that caused the test to fail. For + example: + + @typecheck ``` + verify do + labeled "This will randomly fail" do + x = natIn 0 10 + y = natIn 0 100 + label "x" x + label "y" y + ensureEqual (x + y) 100 + ``` + + In this example, the labels "x" and "y" are added to the scope "This will + randomly fail". If the test fails, the error message will include the + labels and their values along with the error message that caused the test + to fail: + + ``` raw + 🚫 FAILED + This will randomly fail: + x: 3 + y: 67 + elements not equal + (70, 100) + ``` + + # See also + + * {type Each} for exhaustive enumeration of a domain + * {type Random} for random generation + * {type Label} for adding labels to tests to help identify the values or + conditions that caused the test to fail + * {raiseFailure} for raising a test failure + * {verifyWithSeed} for customizing the seed + }} + +test.verifyWithSeed : + Nat -> '{g, Exception, Each, Random, Label} () ->{g} [Result] +test.verifyWithSeed = verifyWithSeedAndIgnore + +test.verifyWithSeed.doc : Doc +test.verifyWithSeed.doc = + {{ + Same as {test.verify}, but the first argument is a random seed used for any + use of the {type Random} ability. + }} + +tests.catchArithmeticFailure : '{IO, Exception} [Result] +tests.catchArithmeticFailure = + do + test.verify do + use Nat / + result = catchAll do 3 / 0 + expected = + Left (Failure (typeLink ArithmeticFailure) "divide by zero" (Any ())) + ensureEqual expected result + +tests.catchBug : '{IO, Exception} [Result] +tests.catchBug = + do + test.verify do + result = catchAll do bug "this bug should be caught" + expected = + Left + (Failure + (typeLink RuntimeFailure) + "builtin.bug" + (Any "this bug should be caught")) + ensureEqual expected result + +tests.catchKill : '{IO, Exception} [Result] +tests.catchKill = do + test.verify do + v = MVar.newEmpty() + t1 = fork do + go = do + sleepMicroseconds 10000 + go() + match catchAll go with + Left e -> unsafeRun! do MVar.put v e + Right _ -> bug "can't happen" + sleepMicroseconds 35000 + concurrent.kill t1 + expected = Failure (typeLink ThreadKilledFailure) "thread killed" (Any ()) + ensureEqual expected (MVar.take v) + +-- builtin Text.!= : Text -> Text -> Boolean + +Text.!=.doc : Doc +Text.!=.doc = + use Text != + {{ + Returns `` true `` if the two {type Text} values are not equal, and `` false + `` otherwise. + + # Examples + + ``` + "foo" != "foo" + ``` + + ``` + "foo" != "bar" + ``` + }} + +-- builtin Text.++ : Text -> Text -> Text + +-- builtin Text.< : Text -> Text -> Boolean + +Text.<.doc : Doc +Text.<.doc = + use Text < + {{ + Returns `` true `` if the first {type Text} value is lexicographically less + than the second, and `` false `` otherwise. + + A {type Text} value `t1` is lexicographically less than another value `t2` if + `t1` is a proper prefix of `t2`, or if the Unicode code point of the first + character that differs between the two is less in `t1` than in `t2`. + + # Examples + + ``` + "foo" < "bar" + ``` + + ``` + "bar" < "foo" + ``` + + ``` + "foo" < "foo" + ``` + }} + +-- builtin Text.<= : Text -> Text -> Boolean + +Text.<=.doc : Doc +Text.<=.doc = + use Text <= + {{ + Returns `` true `` if the first {type Text} value is lexicographically less + than or equal to the second, and `` false `` otherwise. + + A {type Text} value `t1` is lexicographically less than or equal to another + value `t2` if `t1` is a prefix of `t2`, or if the Unicode code point of the + first character that differs between the two is less in `t1` than in `t2`. + + # Examples + + ``` + "foo" <= "bar" + ``` + + ``` + "bar" <= "foo" + ``` + + ``` + "foo" <= "foo" + ``` + }} + +-- builtin Text.== : Text -> Text -> Boolean + +Text.==.doc : Doc +Text.==.doc = + use Text == + {{ + Returns `` true `` if the two {type Text} values are equal, and `` false `` + otherwise. + + # Examples + + ``` + "foo" == "foo" + ``` + + ``` + "foo" == "bar" + ``` + }} + +-- builtin Text.> : Text -> Text -> Boolean + +Text.>.doc : Doc +Text.>.doc = + use Text > + {{ + Returns `` true `` if the first {type Text} value is lexicographically + greater than the second, and `` false `` otherwise. + + A {type Text} value `t1` is lexicographically greater than another value `t2` + if `t2` is a proper prefix of `t1`, or if the Unicode code point of the first + character that differs between the two is greater in `t1` than in `t2`. + + # Examples + + ``` + "foo" > "bar" + ``` + + ``` + "bar" > "foo" + ``` + + ``` + "foo" > "foo" + ``` + }} + +-- builtin Text.>= : Text -> Text -> Boolean + +Text.>=.doc : Doc +Text.>=.doc = + use Text >= + {{ + Returns `` true `` if the first {type Text} value is lexicographically + greater than or equal to the second, and `` false `` otherwise. + + A {type Text} value `t1` is lexicographically greater than or equal to + another value `t2` if `t2` is a prefix of `t1`, or if the Unicode code point + of the first character that differs between the two is greater in `t1` than + in `t2`. + + # Examples + + ``` + "foo" >= "bar" + ``` + + ``` + "bar" >= "foo" + ``` + + ``` + "foo" >= "foo" + ``` + }} + +Text.alignCenterWith : Nat -> Char -> Text -> Text +Text.alignCenterWith w padChar txt = + use Nat - / >= + use Text ++ + len = Text.size txt + d = w - len + r = d / 2 + l = d - r + pad n = Text.repeat n (Char.toText padChar) + if len >= w then txt else pad l ++ txt ++ pad r + +Text.alignCenterWith.doc : Doc +Text.alignCenterWith.doc = + {{ + `` alignCenterWith width pad s `` aligns the given {type Text} `s` to the + center within a field of width `width`, padding with the given {type Text} + `pad`. + + # Example + + ``` + alignCenterWith 10 ?. "hello" + ``` + + # See also + + * {alignLeftWith} + * {alignRightWith} + }} + +test> Text.alignCenterWith.test = + check (alignCenterWith 10 ?. "hello" === "...hello..") + +Text.alignLeftWith : Nat -> Char -> Text -> Text +Text.alignLeftWith w padChar txt = + use Nat - + use Text ++ + rem = w - Text.size txt + if rem === 0 then txt else txt ++ Text.repeat rem (Char.toText padChar) + +Text.alignLeftWith.doc : Doc +Text.alignLeftWith.doc = + {{ + `` alignLeftWith width pad s `` aligns the given {type Text} `s` to the left + within a field of width `width`, padding with the given {type Text} `pad`. + + # Example + + ``` + alignLeftWith 10 ?. "hello" + ``` + + # See also + + {alignRightWith} + }} + +Text.alignRightWith : Nat -> Char -> Text -> Text +Text.alignRightWith w padChar txt = + use Nat - + use Text ++ + rem = w - Text.size txt + if rem === 0 then txt else Text.repeat rem (Char.toText padChar) ++ txt + +Text.alignRightWith.doc : Doc +Text.alignRightWith.doc = + {{ + `` alignRightWith width pad s `` aligns the given {type Text} `s` to the + right within a field of width `width`, padding with the given {type Text} + `pad`. + + # Example + + ``` + alignRightWith 10 ?. "hello" + ``` + + # See also + + {alignLeftWith} + }} + +Text.allSplits : Text -> [(Text, Text)] +Text.allSplits text = + len = Text.size text + splits = Nat.rangeClosed 0 len + List.map (i -> Text.splitAt i text) splits + +Text.allSplits.doc : Doc +Text.allSplits.doc = + {{ + `` allSplits text `` returns a list of all possible splits of the text + `text`. Each split is a pair of the text before the split and the text after + the split. + + # Examples + + ``` + allSplits "abc" + ``` + }} + +Text.break : (Char ->{g} Boolean) -> Text ->{g} (Text, Text) +Text.break p s = + prefix = Text.takeWhile (Boolean.not << p) s + suffix = Text.drop (Text.size prefix) s + (prefix, suffix) + +Text.break.doc : Doc +Text.break.doc = + {{ + Breaks a {type Text} into two pieces at the first occurrence of the given + character. + + # Examples + + ``` + break isSpace "The purple cow jumped over the moon." + ``` + + ``` + break isSpace "abracadabra" + ``` + + ``` + break isSpace "" + ``` + }} + +Text.charAt : Nat -> Text -> Optional Char +Text.charAt index text = Text.head (Text.drop index text) + +Text.charAt.doc : Doc +Text.charAt.doc = + {{ + `` charAt index `` gets the character at the index `index` in the text (using + [zero-based indexing](https://en.wikipedia.org/wiki/Zero-based_numbering)), + or returns {None} if the text has fewer than `index+1` characters. + + # Examples + + ``` + charAt 0 "abc" + ``` + + ``` + charAt 2 "abc" + ``` + + ``` + charAt 3 "abc" + ``` + }} + +test> Text.charAt.tests = test.verify do + Each.run do + index = each [0, 1, maxNat, Random.nat!] + ensureEqual None (charAt index "") + ensureEqual (Some ?a) (charAt 0 "a") + ensureEqual (Some ?a) (charAt 0 "ab") + ensureEqual None (charAt 1 "a") + ensureEqual (Some ?b) (charAt 1 "ab") + ensureEqual (Some ?b) (charAt 1 "abc") + ensureEqual None (charAt 2 "ab") + ensureEqual (Some ?c) (charAt 2 "abc") + ensureEqual (Some ?😈) (charAt 0 "😈") + ensureEqual (Some ?🐠) (charAt 1 "😈🐠🏎") + +Text.chunk : Nat -> Text -> [Text] +Text.chunk n t = + use List ++ + use Nat == + go acc = cases + "" -> acc + t -> go (acc ++ [Text.take n t]) (Text.drop n t) + if n == 0 then [] else go [] t + +Text.chunk.doc : Doc +Text.chunk.doc = + use Text chunk + {{ + Splits a text into chunks of a given size. + + If the size is not a multiple of the length of the text, the last chunk will + be shorter than the others. + + # Examples + + ``` + chunk 3 "abcdef" + ``` + + ``` + chunk 3 "abcdefg" + ``` + }} + +Text.codePoints : Text -> [Nat] +Text.codePoints t = List.map Char.toNat (toCharList t) + +Text.codePoints.doc : Doc +Text.codePoints.doc = + {{ + Returns a list of the Unicode code points in the given text. + + # Example + + ``` + catch do codePoints "👋 hello" + ``` + }} + +Text.cons : Char -> Text -> Text +Text.cons a b = + use Text ++ + Char.toText a ++ b + +Text.cons.doc : Doc +Text.cons.doc = + use Text cons + {{ + Join a {type Char} and a {type Text} into a {type Text}. + + # Examples + + ``` + cons ?a "bc" + ``` + + ``` + cons ?a "" + ``` + }} + +test> Text.cons.tests = test.verify do + use Text cons + ensureEqual "a" (cons ?a "") + ensureEqual "abc" (cons ?a "bc") + ensureEqual "bcd" (cons ?b "cd") + ensureEqual "🌈🌧️" (cons ?🌈 "🌧️") + +Text.contains : Text -> Text -> Boolean +Text.contains searchText text = Text.indexOf searchText text |> isSome + +Text.contains.doc : Doc +Text.contains.doc = + use Text contains + {{ + `` contains searchText text `` returns `` true `` if the `text` contains the + `searchText` as a substring, and `` false `` otherwise. + + # Example + + ``` + List.filter (contains "ll") ["Hello", "world"] + ``` + }} + +Text.doc : Doc +Text.doc = + use Text != ++ < <= > >= drop take + use patterns letter + {{ + A value of type {type Text} is a sequence of Unicode characters. + + # Text literals + + You write {type Text} literals using double quotes: + + ``` + "Hello, world!" + ``` + + You can use + [escape sequences](https://www.unison-lang.org/learn/language-reference/escape-sequences/) + in {type Text} literals: + + ``` + "Hello, \"world\"!" + ``` + + # Conversion from other types + + From a {type List} of {type Char}s: + + ``` + fromCharList [?H, ?e, ?l, ?l, ?o, ?,, ?\s, ?w, ?o, ?r, ?l, ?d, ?!] + ``` + + From a single {type Char}: + + ``` + Char.toText ?H + ``` + + From {type Bytes}: + + ``` + catch do fromUtf8 0xs48656c6c6f2c20776f726c6421 + ``` + + Many types provide a `toText` function for converting to {type Text}: + + ``` + Int.toText +42 + ``` + + ``` + Float.toText 3.14159 + ``` + + ``` + fromSet (Set.fromList [?🍌, ?🍎, ?🍌, ?🍎, ?🍌]) + ``` + + # Comparing {type Text} values + + You can compare {type Text} values using the usual comparison operators: + {Text.==}, {!=}, {<=}, {>=}, {<}, {>}. For example: + + ``` + "abc" Text.== "abc" + ``` + + ``` + "abc" <= "def" + ``` + + One {type Text} value is less than another if it is a prefix of the other, + or if the first character that differs has a lower Unicode code point. + + # Concatenation + + Concatenate {type Text} values using {++}: + + ``` + "Hello, " ++ "world!" + ``` + + Concatenate a {type List} of {type Text} values using {Text.join}: + + ``` + Text.join ", " ["Hello", "world"] + ``` + + # Substrings + + Extract a prefix or suffix from a {type Text} using {take} and {drop}: + + ``` + take 5 "Hello, world!" + ``` + + ``` + drop 7 "Hello, world!" + ``` + + Extract a prefix that matches a predicate: + + ``` + Text.takeWhile isLetter "Hello, world!" + ``` + + Extract a suffix that matches a predicate: + + ``` + takeRightWhile isLetter "Hello, world" + ``` + + Drop a prefix that matches a predicate: + + ``` + Text.dropWhile isLetter "Hello, world!" + ``` + + Drop a suffix that matches a predicate: + + ``` + Text.dropRightWhile isLetter "Hello, world" + ``` + + Check if a {type Text} starts with a prefix: + + ``` + startsWith "Hello" "Hello, world!" + ``` + + Check if a {type Text} ends with a suffix: + + ``` + endsWith "world!" "Hello, world!" + ``` + + # Splitting + + Split a {type Text} into a {type List} of {type Text} values: + + ``` + Text.split ?, "one,two,three" + ``` + + Split a {type Text} into segments that match a predicate: + + ``` + segmentBy isLetter "Hello, world!" + ``` + + # Properties + + Get the size of a {type Text}: + + ``` + Text.size "Hello, world!" + ``` + + Check if a {type Text} is empty: + + ``` + Text.isEmpty "" + ``` + + # Converting to other types + + To a {type List} of {type Char}s: + + ``` + toCharList "Hello, world!" + ``` + + To {type Bytes}: + + ``` + Text.toUtf8 "Hello, world!" + ``` + + Many types provide a `fromText` function that converts {type Text} to that + type: + + ``` + Int.fromText "123" + ``` + + ``` + Bag.occurrenceList (Text.toBag "abracadabra") + ``` + + # Transforming + + Turn a {type Text} into uppercase: + + ``` + Text.toUppercase "Hello, world!" + ``` + + Turn a {type Text} into lowercase: + + ``` + Text.toLowercase "Hello, world!" + ``` + + Reverse a {type Text}: + + ``` + Text.reverse "stressed live" + ``` + + Repeat a {type Text} a number of times: + + ``` + Text.repeat 3 "オラ" + ``` + + Transform a {type Text} using a function: + + ``` + Text.map + (c -> (if c Char.== ?o then Char.toUppercase c else Char.toLowercase c)) + "Hello, world!" + ``` + + Transform a {type Text} using a function that returns {type Text}: + + ``` + Text.flatMap (x -> fromCharList [fromNat.impl 9889, x]) "THUNDER" + ``` + + Filter a {type Text} using a function that returns {type Boolean}: + + ``` + Text.filter isDigit "abc123def" + ``` + + # Padding and alignment + + Justify {type Text} on the left, padding with a {type Char}: + + ``` + alignLeftWith 20 ?- "abc" + ``` + + Justify {type Text} on the right, padding with a {type Char}: + + ``` + alignRightWith 20 ?- "abc" + ``` + + Add some repeated {type Text} on the left until the result is a given size: + + ``` + leftPad 20 ".oOo" "wow" + ``` + + # Searching and pattern matching + + Pattern matching on {type Text} values is supported by the {type Pattern} + type. For example: + + ``` + isMatch + (Pattern.join [many letter, many patterns.digit, many letter]) + "abc123def" + ``` + }} + +-- builtin Text.drop : Nat -> Text -> Text + +Text.drop.doc : Doc +Text.drop.doc = + use Text drop + {{ + `` drop n t `` returns the {type Text} `t` with the first `n` characters + removed. + + If `n` is greater than or equal to the length of `t`, the result is the empty + {type Text}. + + # Examples + + ``` + drop 0 "abc" + ``` + + ``` + drop 1 "abc" + ``` + + ``` + drop 10 "abc" + ``` + }} + +Text.dropRightWhile : (Char ->{g} Boolean) -> Text ->{g} Text +Text.dropRightWhile f t = match Text.unsnoc t with + Some (prefix, c) | f c -> Text.dropRightWhile f prefix + _ -> t + +Text.dropRightWhile.doc : Doc +Text.dropRightWhile.doc = + use Text dropRightWhile + {{ + `` dropRightWhile f text `` will remove characters from the __end__ of a text + value until the function `f` returns `` false `` + + # Examples + + ``` + dropRightWhile isWhitespace "lovely snipe and tender turn \n\r" + ``` + + ``` + dropRightWhile isWhitespace "no whitespace" + ``` + + # See also + + * {dropRightWhile} + }} + +Text.dropUntil : (Char ->{e} Boolean) -> Text ->{e} Text +Text.dropUntil f t = Text.drop 1 (Text.dropWhile (Boolean.not << f) t) + +Text.dropUntil.doc : Doc +Text.dropUntil.doc = + use Text dropUntil + {{ + Drops characters from a {type Text} until a given predicate is satisfied. + + Also drops the first character that satisfies the predicate. + + # Example + + ``` + dropUntil isSpace "hello world" + ``` + + # See also + + * {Text.takeUntil} returns the characters that {dropUntil} would drop. + * {Text.dropWhile} drops the longest prefix where all characters match a + predicate. + * {Text.drop} drops the first n characters. + * {Text.dropRightWhile} drops the longest suffix where all characters match + a predicate. + }} + +Text.dropWhile : (Char ->{g} Boolean) -> Text ->{g} Text +Text.dropWhile f t = match Text.uncons t with + Some (c, suffix) | f c -> Text.dropWhile f suffix + _ -> t + +Text.dropWhile.doc : Doc +Text.dropWhile.doc = + use Text dropWhile + {{ + `` dropWhile f text `` will remove characters from the __beginning__ of a + text value until the function `f` returns `` false `` + + # Examples + + ``` + dropWhile isWhitespace " \n\rA carafe, that is a blind glass" + ``` + + ``` + dropWhile isWhitespace "Celebrate!" + ``` + + # See also + + * {Text.dropRightWhile} + }} + +-- builtin Text.empty : Text + +Text.empty.doc : Doc +Text.empty.doc = {{ The empty {type Text} value, ``""``. }} + +Text.endsWith : Text -> Text -> Boolean +Text.endsWith suffix t = match Text.unsnoc suffix with + Some (suffix', c) -> + match Text.unsnoc t with + Some (t', d) | c === d -> Text.endsWith suffix' t' + _ -> false + _ -> true + +Text.endsWith.doc : Doc +Text.endsWith.doc = + {{ + `` endsWith suffix t `` returns `` true `` if the {type Text} 't' ends with + the {type Text} 'suffix'. + }} + +Text.filter : (Char ->{g} Boolean) -> Text ->{g} Text +Text.filter f input = + go acc text = match Text.unsnoc text with + None -> acc + Some (tail, c) | f c -> go (Text.cons c acc) tail + Some (tail, _) -> go acc tail + go Text.empty input + +Text.filter.doc : Doc +Text.filter.doc = + use Text filter + {{ + {filter} inspects each {type Char} in string with a function that returns a + {type Boolean} and returns a new {type Text} value with only the characters + that return ``true``. + + ``` + filter isUpper "abcABCdef" + ``` + + ``` + filter (c -> Boolean.not (isWhitespace c)) "Hello world!" + ``` + + If no characters match the predicate function, an empty string is returned. + + ``` + filter isDigit "abcdef" + ``` + }} + +Text.findFirst : Class -> Text -> (Text, Text) +Text.findFirst needle haystack = + match Pattern.run + (Pattern.join + [ Pattern.capture (many (patterns.char (Class.not needle))) + , Pattern.capture (patterns.char needle) + ]) + haystack with + Some ([a, b], c) -> (a, b Text.++ c) + _ -> (haystack, "") + +Text.findFirst.doc : Doc +Text.findFirst.doc = + {{ + Breaks a text into two parts, the first of which contains the longest prefix + of the text that does not contain any characters in the given class, and the + second of which contains the remainder of the text. + + # Examples + + If a character matching the class appears at the beginning of the text, the + first part of the result will be the empty text: + + ``` + findFirst Class.letter "abc123" + ``` + + If a character matching the class appears in the middle of the text, the + first part of the result will contain the prefix of the text up to the + character: + + ``` + findFirst Class.digit "abc123" + ``` + + If a character matching the class doesn't appear in the text, the first + part of the result will be the entire text and the second part will be the + empty text: + + ``` + findFirst whitespace "abc123" + ``` + }} + +Text.findFirstIndex : Class -> Text -> Nat +Text.findFirstIndex c t = + (t1, _) = findFirst c t + Text.size t1 + +Text.findFirstIndex.doc : Doc +Text.findFirstIndex.doc = + {{ + Returns the offset of the first character that matches the given {type Class} + in the {type Text}. + + # Example + + ``` + Text.findFirstIndex whitespace "The quick brown fox" + ``` + + # See also + + * {Text.findLastIndex} finds the **last** occurrence instead of the first. + * {findFirst} finds the first occurrence and splits the {type Text} at the + found character. + }} + +Text.findLast : Class -> Text -> (Text, Text) +Text.findLast c t = + use Text reverse + (a, b) = findFirst c (reverse t) + (reverse b, reverse a) + +Text.findLast.doc : Doc +Text.findLast.doc = + {{ + Finds the last occurrence of a character of the given {type Class} in a + {type Text}. + + Returns a pair of {type Text}, the first containing the characters up to and + including the last occurrence of the {type Class}, and the second containing + the characters after the last occurrence of the {type Class}. + + # Example + + ``` + Text.findLast whitespace "The quick brown fox" + ``` + + # See also + + * {Text.findLastIndex} returns the last offset in the {type Text} that + matches instead of splitting it at the found character. + * {findFirst} finds the **first** occurrence instead of the last. + }} + +Text.findLastIndex : Class -> Text -> Nat +Text.findLastIndex c t = + (t1, _) = Text.findLast c t + Text.size t1 + +Text.findLastIndex.doc : Doc +Text.findLastIndex.doc = + {{ + Returns the offset of the last character that matches the given {type Class} + in the {type Text}. + + # Example + + ``` + Text.findLastIndex whitespace "The quick brown fox" + ``` + + # See also + + * {Text.findFirstIndex} finds the **first** occurrence instead of the last. + * {Text.findLast} finds the last occurrence and splits the {type Text} at + the found character. + }} + +Text.flatMap : (Char ->{g} Text) -> Text ->{g} Text +Text.flatMap f t = fromCharList (List.flatMap (toCharList << f) (toCharList t)) + +Text.flatMap.doc : Doc +Text.flatMap.doc = + use Text flatMap + {{ + `` flatMap f t `` applies the {type Text}-valued function `f` to every + {type Char} Unicode code point in the {type Text} `t`, working left to right, + concatenating the resulting {type Text} values. + + # Example + + ``` + flatMap + (cases + ?e -> "a" + ?o -> "oween" + c -> Char.toText c) "Hello" + ``` + }} + +Text.flatMapRight : (Char ->{g} Text) -> Text ->{g} Text +Text.flatMapRight f t = + fromCharList (List.flatMapRight (toCharList << f) (toCharList t)) + +Text.flatMapRight.doc : Doc +Text.flatMapRight.doc = + use Char toText + use Stream toList + use Text flatMap flatMapRight + {{ + `` flatMapRight f t `` applies the {type Text}-valued function `f` to every + {type Char} Unicode code point in the {type Text} `t`, working right to + right, concatenating the resulting {type Text} values. + + The order of traversal only affects arguments that have effects, so + {flatMapRight} is equivalent to {flatMap} for pure functions. + + # Example + + ``` + flatMapRight + (cases + ?e -> "a" + ?o -> "oween" + c -> toText c) "Hello" + ``` + + If the function is pure, then the order of traversal does not matter. + However, if the function has effects, then the order of traversal does + matter. For example, the following code will emit the characters onto a + {type Stream} in reverse order: + + ``` + fromCharList + (toList do + flatMapRight + (c -> let + emit c + toText c) "Hello") + ``` + + This is because the {type Stream} is being built right to left, and the + {type Stream} is being traversed left to right. + + If we use {flatMap} instead, then the order of traversal will be preserved: + + ``` + fromCharList + (toList do + flatMap + (c -> let + emit c + toText c) "Hello") + ``` + + This is because the {type Stream} is being built left to right, and the + {type Stream} is being traversed left to right as well. + }} + +Text.fromAscii : Bytes -> Text +Text.fromAscii bytes = + bytes |> Bytes.toList |> List.map fromNat.impl |> fromCharList + +Text.fromAscii.doc : Doc +Text.fromAscii.doc = + {{ + {fromAscii} converts {type Bytes} to {type Text} by assuming each byte is an + ASCII character. + }} + +-- builtin Text.fromCharList : [Char] -> Text + +Text.fromCharList.doc : Doc +Text.fromCharList.doc = + {{ + Converts a {type List} of {type Char}s to a {type Text}. + + # Examples + + ``` + fromCharList [?h, ?e, ?l, ?l, ?o] + ``` + + ``` + fromCharList [] + ``` + + ``` + fromCharList [?👋, ?\s, ?🌎] + ``` + }} + +Text.fromSet : Set Char -> Text +Text.fromSet chars = fromCharList (Set.toList chars) + +Text.fromSet.doc : Doc +Text.fromSet.doc = + {{ + Converts a {type Set} of {type Char} Unicode code points to a {type Text} + value containing those characters in an unspecified order. + }} + +Text.fromUtf8 : Bytes ->{Exception} Text +Text.fromUtf8 = Either.toException << fromUtf8.impl + +Text.fromUtf8.doc : Doc +Text.fromUtf8.doc = + {{ + Convert a {type Bytes} value to a {type Text} value, assuming that the bytes + are encoded in UTF-8. + }} + +-- builtin Text.fromUtf8.impl : Bytes -> Either Failure Text + +Text.fromUtf8.partial : Bytes ->{Exception} (Text, Bytes) +Text.fromUtf8.partial bytes = + use Nat decrement + totalSize = Bytes.size bytes + charsToInclude i = + use Nat + - == >= and + b = Bytes.at! i bytes + if and b 128 == 0 then Nat.increment i + else + if and b 192 == 128 then + if i == 0 then 0 else charsToInclude (decrement i) + else + charLength = leadingOnes (Nat.shiftLeft b 56) + availableChars = totalSize - i + if availableChars >= charLength then i + charLength else i + let + (txtBytes, remainder) = + handle Bytes.splitAt (charsToInclude (decrement totalSize)) bytes + with cases + { r } -> r + { abort -> _ } -> (bytes, Bytes.empty) + (fromUtf8 txtBytes, remainder) + +Text.fromUtf8.partial.doc : Doc +Text.fromUtf8.partial.doc = + use Text toUtf8 + {{ + Converts a {type Bytes} to a {type Text}, assuming the bytes are encoded as + UTF-8. If there is a partial character at the end of the {type Bytes} (UTF-8 + characters are 1 to 4 bytes long), then the previous characters are returned + in the {type Text} result and the bytes of the last partial character are + returned in the {type Bytes} result. + + This can be useful when reading from a stream of bytes, where the bytes are + not guaranteed to be aligned to UTF-8 character boundaries. + + See {stream} for UTF-8 decoding with a {type Stream}. + + # Examples + + ``` + catch do partial ("hello 😎" |> toUtf8) + ``` + + ``` + catch do partial ("hello 😎" |> toUtf8 |> Bytes.take 9) + ``` + + ``` + catch do partial Bytes.empty + ``` + + ``` + catch do partial 0xsc0decafe + ``` + }} + +test> Text.fromUtf8.partial.tests = + test.verify do + use Bytes ++ + use Text toUtf8 + txt = each ["hello", "😎", "😎😎", "hello 😎", "以呂波耳本部止"] + utf8Bytes = toUtf8 txt + let + (utf8Bytes1, utf8Bytes2) = + Bytes.splitAt (Random.natIn 0 (Bytes.size utf8Bytes)) utf8Bytes + (txt1, remainder) = partial utf8Bytes1 + ensuring do (toUtf8 txt1 ++ remainder ++ utf8Bytes2) === utf8Bytes + +Text.fromUtf8.stream : '{g, Stream Bytes} r -> '{g, Exception, Stream Text} r +Text.fromUtf8.stream = delay stream! + +Text.fromUtf8.stream.doc : Doc +Text.fromUtf8.stream.doc = + {{ + Converts a {type Stream} of {type Bytes} to a {type Stream} of {type Text}, + assuming the bytes are encoded as UTF-8. When a chunk of {type Bytes} in the + input stream ends in the middle of a character (UTF-8 characters are 1 to 4 + bytes), the {type Text} leading up to the partial character is emitted, and + the partial character is retained until the next {emit} on the input stream. + + See {stream!} for a variant in which the output is not delayed. + }} + +test> Text.fromUtf8.stream.tests.invalidUtf8 = + test.verify do + use Bytes ++ drop take + use Random natIn oneOf + use Text toUtf8 + Each.repeat 10 + invalidChunk = + oneOf + [ 0xsc0decafe + , "🤩" |> toUtf8 |> drop (natIn 1 4) + , "🤩" |> toUtf8 |> take (natIn 1 4) + , "இ" |> toUtf8 |> drop (natIn 1 3) + , "இ" |> toUtf8 |> take (natIn 1 3) + , "λ" |> toUtf8 |> drop 1 + , "λ" |> toUtf8 |> take 1 + ] + bytes = + oneOf + [ invalidChunk ++ toUtf8 "hello" + , toUtf8 "hello" ++ invalidChunk + , toUtf8 "hello" ++ invalidChunk ++ toUtf8 "goodbye" + ] + byteStream = bytes |> splits.bytes (natIn 1 10) + handle drain (stream byteStream) + with cases + { r } -> test.raiseFailure "Expected UTF-8 decoding failure" bytes + { Exception.raise e -> _ } -> + ensuring do Text.contains "Invalid UTF-8 stream" (message e) + +test> Text.fromUtf8.stream.tests.success = + test.verify do + check inputText = + use Text ++ + chunkCount = Random.natIn 1 101 + bytes = inputText |> Text.toUtf8 |> splits.bytes chunkCount + outputText = stream bytes |> Stream.fold (++) "" + ensuring do outputText === inputText + text = + each + [ "" + , "Hello, world!" + , "🤩" + , "以呂波耳本部止\n千利奴流乎和加\n餘多連曽津祢那\n良牟有為能於久\n耶万計不己衣天\n阿佐伎喩女美之\n恵比毛勢須" + , "😍💜👻🧠⚠️🤯🤬🤮🤢🤧🥵🥶🥴😵🤪🤠🥳🥺🤓🧐🤔🤫🤭🤥" + ] + check text + +Text.fromUtf8.stream! : '{g, Stream Bytes} r ->{g, Exception, Stream Text} r +Text.fromUtf8.stream! = + use Bytes ++ + go buffer = cases + { r } -> + if Bytes.isEmpty buffer then r + else + emit (fromUtf8 buffer) + r + { emit newBytes -> k } -> + (txt, remainder) = partial (buffer ++ newBytes) + if Text.isEmpty txt then () else emit txt + handle k() with go remainder + thunk -> (handle thunk() with go Bytes.empty) + +Text.fromUtf8.stream!.doc : Doc +Text.fromUtf8.stream!.doc = {{ A non-delayed variant of {stream}. }} + +Text.head : Text -> Optional Char +Text.head = Text.uncons >> Optional.map at1 + +Text.head.doc : Doc +Text.head.doc = + use Text head + {{ + Returns the first character of the input {type Text}, or {None} if it's + empty. + + # Examples + + ``` + head "" + ``` + + ``` + head "a" + ``` + + ``` + head "abc" + ``` + }} + +test> Text.head.tests = test.verify do + use Text head + ensureEqual None (head "") + ensureEqual (Some ?a) (head "a") + ensureEqual (Some ?a) (head "a🅱️c") + ensureEqual (Some ?😈) (head "😈⚠️🙈") + +-- builtin Text.indexOf : Text -> Text -> Optional Nat + +Text.indexOf.doc : Doc +Text.indexOf.doc = + use Text indexOf + {{ + `` indexOf needle haystack `` returns the index of the first occurrence of + `needle` in `haystack`, or `` None `` if `needle` is not found. + + # Examples + + ``` + indexOf "foo" "foobar" + ``` + + ``` + indexOf "edict" "prediction" + ``` + + ``` + indexOf "foo" "bar" + ``` + }} + +test> Text.indexOf.tests.infix = + test.verify do + use Nat == + use Random natIn + use Text ++ indexOf + Each.repeat 200 + szHaystackLeft = natIn 0 100 + szNeedle = natIn 0 100 + szHaystackRight = natIn 0 100 + haystackLeft = ofChars unicode szHaystackLeft + needle = ofChars unicode szNeedle + haystackRight = ofChars unicode szHaystackRight + haystack = haystackLeft ++ needle ++ haystackRight + ix = indexOf needle haystack + if szNeedle == 0 then ensure (ix === Some 0) + else ensuring do indexOf needle haystack === Some szHaystackLeft + +Text.isEmpty : Text -> Boolean +Text.isEmpty t = Text.size t === 0 + +Text.isEmpty.doc : Doc +Text.isEmpty.doc = + {{ {Text.isEmpty} returns `` true `` if the {type Text} is empty. }} + +Text.join : Text -> [Text] -> Text +Text.join sep list = + use Text ++ + List.foldLeft (acc e -> acc ++ e) "" (List.intersperse sep list) + +Text.join.doc : Doc +Text.join.doc = + use Text join + {{ + Given a separator and a {type List} of {type Text}, {join} will create a + {type Text} delimited by the separator. + + ``` + join ", " ["hello", "world"] + ``` + + If the list is empty, the function will return the empty {type Text}: + + ``` + join "!" [] + ``` + }} + +test> Text.join.tests.ex1 = + check (Text.join ", " ["Hello", "Unizens"] === "Hello, Unizens") + +Text.leftPad : Nat -> Text -> Text -> Text +Text.leftPad n pad t = + use Nat - / + use Text ++ size + deficit = n - size t + padSize = size pad + Text.repeat (deficit / padSize) pad + ++ Text.take (Nat.mod deficit padSize) pad + ++ t + +Text.leftPad.doc : Doc +Text.leftPad.doc = + {{ + `` leftPad n pad t `` returns {type Text} with `t` as a suffix and padded + with copies of `pad` as a prefix to make the length of the result exactly + `n`. + + # Examples + + ``` + leftPad 10 "0" "524426" + ``` + + ``` + leftPad 30 "_,.-'~'-.,_" "Plop!" + ``` + + ``` + leftPad 2 "." "Hi" + ``` + }} + +Text.lines : Text -> [Text] +Text.lines text = match Text.split ?\n text with + lines :+ "" -> lines + lines -> lines + +Text.lines.doc : Doc +Text.lines.doc = + {{ + Breaks a string into a list of lines, which were delimited by newlines. The + newline character is not included in the results. + + # Examples + + In the normal case, the string is broken into lines at each newline + character: + + ``` + lines "The purple cow\njumped over the moon." + ``` + + The empty string has no lines and will result in an empty list: + + ``` + lines "" + ``` + + Any trailing newline is ignored: + + ``` + lines "The purple cow jumped over the moon\n" + ``` + + If the string starts with a newline, the first element of the result will + be an empty string: + + ``` + lines "\nThe purple cow jumped over the moon" + ``` + + If the string contains multiple consecutive newlines, the list will contain + empty strings: + + ``` + lines "The purple cow jumped over the moon\n\nand landed on the sun." + ``` + + If the string contains no newlines, the result will be a list with a single + element: + + ``` + lines "The purple cow jumped over the moon." + ``` + + ## See also: + + * {{ docLink (docEmbedTermLink do unlines) }} - the converse of this + function, joining a list of lines into a single string + * {words} - breaks a string into words + * {{ docLink (docEmbedTermLink do unwords) }} - joins a list of words + into a single string + * {Text.split} - splits a string on a delimiter + }} + +test> Text.lines.tests.empty = check (List.isEmpty (lines "")) + +test> Text.lines.tests.leading = + check (lines "\nhello\nworld" === ["", "hello", "world"]) + +test> Text.lines.tests.multi = + check (lines "hello\nworld" === ["hello", "world"]) + +test> Text.lines.tests.single = check (lines "hello" === ["hello"]) + +test> Text.lines.tests.trailing = + check (lines "hello\nworld\n" === ["hello", "world"]) + +Text.map : (Char ->{g} Char) -> Text ->{g} Text +Text.map f t = fromCharList (List.map f (toCharList t)) + +Text.map.doc : Doc +Text.map.doc = + use Text map + {{ + `` map f t `` applies the function `f` to every {type Char} Unicode code + point in the {type Text} `t`. + + # Example + + ``` + map ascii.toUpper "hello" + ``` + }} + +Text.nonempty : Text -> Optional Text +Text.nonempty = cases + "" -> None + t -> Some t + +Text.nonempty.doc : Doc +Text.nonempty.doc = + use Text nonempty + {{ + `` nonempty t `` returns {None} if the {type Text} `t` is `""` (the empty + {type Text}, or `` Some t `` if it's not empty. + + # Examples + + ``` + nonempty "foo" + ``` + + ``` + nonempty "" + ``` + }} + +-- builtin Text.patterns.anyChar : Pattern Text + +Text.patterns.anyChar.doc : Doc +Text.patterns.anyChar.doc = + use Pattern run + {{ + Matches any single character. Fails if given the empty {type Text}. + + ``` + run anyChar "hi" + ``` + + ``` + run anyChar "" + ``` + }} + +test> Text.patterns.anyChar.test = test.verify do + use fromNat impl + c = do interval (impl 0) (impl 55295) + n = Random.natIn 1 256 + t = ofChars c n + ensure (isMatch anyChar t) + ensure (Boolean.not (isMatch anyChar "")) + +Text.patterns.asciiLetter : Pattern Text +Text.patterns.asciiLetter = Pattern.or (charRange ?a ?z) (charRange ?A ?Z) + +Text.patterns.asciiLetter.doc : Doc +Text.patterns.asciiLetter.doc = + {{ + A {type Pattern} that matches any ASCII letter. + + # Example + + ``` + isMatch asciiLetter "abracadabra" + ``` + + ``` + isMatch asciiLetter "Þorsteinn" + ``` + + ``` + isMatch asciiLetter "123" + ``` + }} + +test> Text.patterns.asciiLetter.test = test.verify do + use Boolean not + use Random either + c = do either (do interval ?a ?z) do interval ?A ?Z + notC = do either (do interval ?0 ?9) do interval ?! ?/ + n = Random.natIn 1 256 + t = ofChars c n + ensure (isMatch asciiLetter t) + ensure (not (isMatch asciiLetter "")) + ensure (not (isMatch asciiLetter (ofChars notC n))) + +Text.patterns.captureWithin : Text -> Text -> Pattern Text +Text.patterns.captureWithin t1 t2 = + Pattern.join + [literal t1, Pattern.capture (charUntil Class.any t2), literal t2] + +Text.patterns.captureWithin.doc : Doc +Text.patterns.captureWithin.doc = + {{ + `` captureWithin t1 t2 `` matches text that starts with `t1`, then captures + all text until `t2`, and finally matches `t2`. The captured text is the text + between `t1` and `t2`. The delimiters `t1` and `t2` are not included in the + captured text. + + # Examples + + ``` + Pattern.run (captureWithin "{{" "}}") "{{hello}} world" + ``` + }} + +-- builtin Text.patterns.char : Char.Class -> Pattern Text + +Text.patterns.char.doc : Doc +Text.patterns.char.doc = + use Class + + use Pattern capture run + use patterns char + {{ + Matches a single character in the given {type Class}. + + # Examples + + ``` + isMatch (char Class.alphanumeric) "a" + ``` + + ``` + run (capture (many (char Class.letter))) "abc123" + ``` + + Classes can be combined using the {Class.and}, {+} and {Class.not} + combinators: + + ``` + run (capture (many (char (Class.lower + Class.number)))) "abc123ABC" + ``` + + # See also + + * {type Class} for more information on character classes. + }} + +test> Text.patterns.char.tests.alphanumeric = + test.verify do + use Boolean not + use Class alphanumeric + use Random either + use patterns char + c = + do + either (do either (do interval ?a ?z) do interval ?A ?Z) do + interval ?0 ?9 + notC = do interval ?! ?/ + n = Random.natIn 1 256 + t = ofChars c n + ensure (isMatch (char alphanumeric) t) + ensure (not (isMatch (char alphanumeric) "")) + ensure (not (isMatch (char alphanumeric) (ofChars notC n))) + +test> Text.patterns.char.tests.and = test.verify do + use Class and letter + use patterns char + c = do interval ?a ?z + n = Random.natIn 1 256 + t = ofChars c n + ensure (isMatch (char (and Class.lower letter)) t) + ensure (Boolean.not (isMatch (char (and Class.upper letter)) t)) + +test> Text.patterns.char.tests.not = test.verify do + use patterns char + c = do interval ?a ?z + n = Random.natIn 1 256 + t = ofChars c n + ensure (isMatch (char (Class.not Class.upper)) t) + ensure (Boolean.not (isMatch (char (Class.not Class.letter)) t)) + +test> Text.patterns.char.tests.or = test.verify do + use Class + + use patterns char + c = do interval ?a ?z + n = Random.natIn 1 256 + t = ofChars c n + ensure (isMatch (char (Class.letter + Class.number)) t) + ensure (Boolean.not (isMatch (char (Class.upper + Class.punctuation)) t)) + +-- builtin Text.patterns.charIn : [Char] -> Pattern Text + +Text.patterns.charIn.doc : Doc +Text.patterns.charIn.doc = + {{ + Constructs a {type Pattern} that matches any single character in the given + {type List} of {type Char}s. + + # Example + + ``` + Pattern.run + (Pattern.capture (many (charIn (toCharList "abc")))) "abracadabra" + ``` + }} + +-- builtin Text.patterns.charRange : Char -> Char -> Pattern Text + +Text.patterns.charRange.doc : Doc +Text.patterns.charRange.doc = + {{ + `` charRange ?a ?z `` matches and consume a single character in the given + range (inclusive on both sides). + + ``` + isMatch (charRange ?a ?z) "a" + ``` + + ``` + Pattern.run (Pattern.capture (many (charRange ?A ?Z))) "NOOOOOOO!" + ``` + }} + +Text.patterns.chars : Text -> Pattern Text +Text.patterns.chars txt = charIn (toCharList txt) + +Text.patterns.chars.doc : Doc +Text.patterns.chars.doc = + {{ + `` chars "abc" `` matches and consumes 1 {type Char} in the given + {type Text}. + + ``` + Pattern.run (Pattern.capture (many (chars "abc"))) "bbabbaac🍍" + ``` + }} + +Text.patterns.charUntil : Class -> Text -> Pattern Text +Text.patterns.charUntil class stop = + use Class - + use patterns char + splits text = + len = Text.size text + splits = Nat.rangeClosed 0 len + List.map (i -> Text.splitAt i text) splits + many + (List.foldRight + (cases + (head, tail), p -> + Pattern.or + (Pattern.join [literal head, char (class - in (Text.take 1 tail))]) + p) + (char (Class.not Class.any)) + (dropRight 1 (splits stop))) + +Text.patterns.charUntil.doc : Doc +Text.patterns.charUntil.doc = + use Pattern capture run + {{ + A {type Pattern} that matches any character of the given {type Class} until + the given {type Text} is encountered. + + # Examples + + ``` + run (capture (charUntil word "")) "foo bar" + ``` + + ``` + run (capture (charUntil Class.any "--")) "foo--bar" + ``` + }} + +-- builtin Text.patterns.digit : Pattern Text + +Text.patterns.digit.doc : Doc +Text.patterns.digit.doc = + {{ + Matches and consumes a single digit. Equivalent to ``chars "0123456789"``. + }} + +-- builtin Text.patterns.eof : Pattern Text + +Text.patterns.eof.doc : Doc +Text.patterns.eof.doc = + {{ + A {type Pattern} that matches the end of a {type Text}. + + # Examples + + ``` + isMatch eof "" + ``` + + ``` + isMatch (Pattern.join [literal "rob", eof]) "robot" + ``` + }} + +Text.patterns.hexDigit : Pattern Text +Text.patterns.hexDigit = + use Pattern or + or (charRange ?0 ?9) (or (charRange ?a ?f) (charRange ?A ?F)) + +Text.patterns.hexDigit.doc : Doc +Text.patterns.hexDigit.doc = + {{ + Matches and consumes a single hexidecimal digit. Equivalent to + ``chars "0123456789abcdefABCDEF"``. + }} + +Text.patterns.isFullMatch : Pattern Text -> Text -> Boolean +Text.patterns.isFullMatch p txt = isMatch (Pattern.join [p, eof]) txt + +Text.patterns.isFullMatch.doc : Doc +Text.patterns.isFullMatch.doc = + {{ + `` isFullMatch p txt `` returns `` true `` if `p` matches all of `txt`, with + no remainder {type Text}. + + ``` + isFullMatch (many patterns.letter) "abracadabra" + ``` + + ``` + isFullMatch (many (chars "abc")) "abcabc 123" + ``` + }} + +-- builtin Text.patterns.letter : Pattern Text + +Text.patterns.letter.doc : Doc +Text.patterns.letter.doc = + use patterns letter + {{ + Matches and consumes a single letter. + + More specifically, it matches the `Letter` class from + [the Unicode Character Database](http://www.unicode.org/reports/tr44/tr44-14.html#GC_Values_Table). + + ``` + isMatch letter "A" + ``` + + ``` + isMatch letter "b" + ``` + + ``` + isMatch letter "あ" + ``` + }} + +-- builtin Text.patterns.literal : Text -> Pattern Text + +Text.patterns.literal.doc : Doc +Text.patterns.literal.doc = + use Pattern run + {{ + `` literal txt `` matches any {type Text} that starts with `txt`. Fails + otherwise. + + ``` + run (literal "hi") "hi there!" + ``` + + ``` + run (literal "goodbye") "good" + ``` + }} + +-- builtin Text.patterns.notCharIn : [Char] -> Pattern Text + +Text.patterns.notCharIn.doc : Doc +Text.patterns.notCharIn.doc = + {{ + Matches any character that is not in the given set of characters. + + # Example + + ``` + Pattern.run + (Pattern.capture (many (notCharIn (toCharList "abc")))) "slartibartfast" + ``` + }} + +-- builtin Text.patterns.notCharRange : Char -> Char -> Pattern Text + +Text.patterns.notCharRange.doc : Doc +Text.patterns.notCharRange.doc = + {{ + `` notCharRange ?a ?z `` matches and consume a single character __not__ in + the given range (inclusive on both sides). + + ``` + isMatch (notCharRange ?a ?z) "a" + ``` + + ``` + Pattern.run + (Pattern.capture (many (notCharRange ?A ?Z))) "hi there... GAAAAHHH!" + ``` + }} + +Text.patterns.notChars : Text -> Pattern Text +Text.patterns.notChars txt = notCharIn (toCharList txt) + +Text.patterns.notChars.doc : Doc +Text.patterns.notChars.doc = + {{ + `` notChars "abc" `` matches and consumes 1 {type Char} __not__ in the given + {type Text}. + + ``` + Pattern.run (many (notChars ",!. ")) "well, hello!" + ``` + }} + +-- builtin Text.patterns.punctuation : Pattern Text + +Text.patterns.punctuation.doc : Doc +Text.patterns.punctuation.doc = + {{ + Matches and consumes a single punctuation character. + + More specifically, it matches the `Punctuation` class from + [the Unicode Character Database](http://www.unicode.org/reports/tr44/tr44-14.html#GC_Values_Table). + + ``` + List.map (isMatch patterns.punctuation) ["a", ".", ",", "!", "?"] + ``` + }} + +-- builtin Text.patterns.space : Pattern Text + +Text.patterns.space.doc : Doc +Text.patterns.space.doc = + {{ + Matches and consumes a single whitespace character. + + More specifically, it matches any Unicode space character, and the control + characters ``"\t\n\r\f\v"``. + + ``` + isMatch space " " + ``` + + ``` + isMatch space "\r" + ``` + + ``` + isMatch space "\n" + ``` + }} + +Text.patterns.wordChar : Pattern Text +Text.patterns.wordChar = patterns.char word + +Text.patterns.wordChar.doc : Doc +Text.patterns.wordChar.doc = + {{ + A {type Pattern} that matches a word character. + + # Example + + ``` + Pattern.run (Pattern.capture (many wordChar)) "abc_123" + ``` + + # See also + + * {word} for the character class that this pattern matches. + }} + +-- builtin Text.repeat : Nat -> Text -> Text + +Text.repeat.doc : Doc +Text.repeat.doc = + use Text repeat + {{ + Repeat a {type Text} value a given number of times. + + # Examples + + ``` + repeat 3 "abc" + ``` + + ``` + repeat 0 "abc" + ``` + + ``` + repeat 1 "abc" + ``` + }} + +Text.replaceAll : Text -> Text -> Text -> Text +Text.replaceAll toFind replacement = + use Text ++ + if Text.isEmpty toFind then id + else + go acc content = match Text.indexOf toFind content with + None -> acc ++ content + Some i -> + (before, toFindAndRest) = Text.splitAt i content + rest = Text.drop (Text.size toFind) toFindAndRest + go (acc ++ before ++ replacement) rest + go "" + +Text.replaceAll.doc : Doc +Text.replaceAll.doc = + {{ + `` replaceAll toFind replacement content `` replaces every occurrence of + `toFind` in `content` with `replacement`. If `toFind` is empty, then + `content` is returned unchanged. + + # Examples + + ``` + replaceAll "" "z" "foobar" + ``` + + ``` + replaceAll "o" "o" "foobar" + ``` + + ``` + replaceAll "o" "z" "foobar" + ``` + + ``` + replaceAll "oo" "z" "foobar" + ``` + + ``` + replaceAll "foo" "z" "foobarfoobar" + ``` + + ``` + replaceAll "foo" "bar" "" + ``` + + ``` + replaceAll "" "baz" "" + ``` + }} + +test> Text.replaceAll.tests = test.verify do + ensureEqual "foobar" (replaceAll "" "z" "foobar") + ensureEqual "foobar" (replaceAll "o" "o" "foobar") + ensureEqual "fzzbar" (replaceAll "o" "z" "foobar") + ensureEqual "fzbar" (replaceAll "oo" "z" "foobar") + ensureEqual "zbarzbar" (replaceAll "foo" "z" "foobarfoobar") + ensureEqual "" (replaceAll "foo" "bar" "") + ensureEqual "" (replaceAll "" "baz" "") + +-- builtin Text.reverse : Text -> Text + +Text.reverse.doc : Doc +Text.reverse.doc = + use Text reverse + {{ + The expression `` reverse str `` constructs a new string with all the + characters from `str`, but in reverse order. + + # Examples + + ``` + reverse "drawer" + ``` + + ``` + reverse "" + ``` + }} + +test> Text.reverse.tests.homomorphism = runs 100 do + use Text ++ ascii reverse + xs = ascii() + ys = ascii() + expect ((reverse xs ++ reverse ys) === reverse (ys ++ xs)) + +test> Text.reverse.tests.isomorphism = runs 100 do + use Text reverse + xs = Text.ascii() + expect (reverse (reverse xs) === xs) + +Text.segmentBy : (Char ->{e} Boolean) -> Text ->{e} [Text] +Text.segmentBy p t = + use List :+ + go acc rest = match Text.dropWhile (Boolean.not << p) rest with + "" -> acc + t -> + s = Text.takeWhile p t + go (acc :+ s) (Text.drop (Text.size s) t) + go [] t + +Text.segmentBy.doc : Doc +Text.segmentBy.doc = + {{ + `` segmentBy p t `` breaks the {type Text} 't' into a {type List} of segments + where each {type Char} in the text satisfies the predicate 'p'. Segments that + don't satisfy 'p' are dropped. + + # Examples + + ``` + segmentBy (Boolean.not << isWhitespace) "Lorem ipsum dolor sit amet" + ``` + + ``` + segmentBy isAlphaNum "Lorem, ipsum, dolor, sit, amet" + ``` + }} + +-- builtin Text.size : Text -> Nat + +Text.size.doc : Doc +Text.size.doc = + use Text size + {{ + Returns the number of characters (more precisely, UTF-8 code points) in the + given {type Text}. + + # Examples + + ``` + size "" + ``` + + ``` + size "abc" + ``` + + If the {type Text} contains Unicode characters comprising multiple code + points, the result is the number of code points, not the number of + characters: + + ``` + size "👋🏽" + ``` + }} + +Text.slice : Nat -> Nat -> Text -> Text +Text.slice start end text = + use Nat - + Text.take (end - start) (Text.drop start text) + +Text.slice.doc : Doc +Text.slice.doc = + use Nat - + use Text size slice + {{ + `` slice start end text `` returns the the part of `text` that starts at the + inclusive index `start` and ends immediately before the exclusive index + `end`. + + The length of the result is always ``Nat.min (size text end) - start``. If + `start` is greater than `end`, the result is the empty string. If `end` is + greater than the text size, the result is the same as + ``slice start (size text) text``. + + # Examples + + ``` + slice 1 3 "abcd" + ``` + + ``` + slice 0 2 "abcd" + ``` + }} + +test> Text.slice.tests.example1 = + check + let + actual = Text.slice 1 3 "abcd" + "bc" === actual + +test> Text.slice.tests.example2 = + check + let + actual = Text.slice 0 4 "abcd" + "abcd" === actual + +test> Text.slice.tests.largeEnd = + check + let + actual = Text.slice 1 30 "abcd" + "bcd" === actual + +test> Text.slice.tests.largeStart = + check + let + actual = Text.slice 30 2 "abcd" + "" === actual + +Text.snoc : Text -> Char -> Text +Text.snoc a b = + use Text ++ + a ++ Char.toText b + +Text.snoc.doc : Doc +Text.snoc.doc = + use Text snoc + {{ + Appends the {type Char} to the end of the {type Text}. + + ``` + snoc "abc" ?d + ``` + + ``` + snoc "" ?z + ``` + + See also: {Text.cons} + }} + +Text.split : Char -> Text -> [Text] +Text.split separator text = + use patterns char + class = fromChar separator + Each.toList do + eachCapture + (sepMany (char class) (Pattern.capture (many (char (Class.not class))))) + text + +Text.split.doc : Doc +Text.split.doc = + use Text split + {{ + `` split sep t `` splits the {type Text} `t` on the separator {type Char} + `sep` into a list of {type Text} segments that were delimited by that + {type Char} in `t`. + + # Examples + + ``` + split ?. "foo.bar.baz" + ``` + + ``` + split ?. "" + ``` + + ``` + split ?/ "foo//bar/baz" + ``` + }} + +Text.split.examples.ex1 : [Text] +Text.split.examples.ex1 = Text.split ?. "foo.bar.baz" + +Text.split.examples.ex2 : [Text] +Text.split.examples.ex2 = Text.split ?. "" + +test> Text.split.test = runs 100 do + use Text ++ + x = fromCharList (List.Nonempty.toList (atLeastOne (gen.oneOf [?., ?-]) ())) + expect (List.foldLeft (++) "" (List.intersperse "." (Text.split ?. x)) === x) + +Text.splitAt : Nat -> Text -> (Text, Text) +Text.splitAt n t = (Text.take n t, Text.drop n t) + +Text.splitAt.doc : Doc +Text.splitAt.doc = + use Text splitAt + {{ + Split a text into two pieces at the given index. The length of the first + piece will be the given index, and the length of the second piece will be the + length of the original text minus the given index. + + If the index is out of bounds, the first piece will be the entire text and + the second piece will be empty. + + # Examples + + ``` + splitAt 3 "Hello" + ``` + + ``` + splitAt 10 "Hello" + ``` + + ``` + splitAt 0 "Hello" + ``` + }} + +Text.splitOn : Class -> Text -> [Text] +Text.splitOn sep text = + use patterns char + sepp = char sep + pat = sepMany (some sepp) (Pattern.capture (some (char (Class.not sep)))) + Each.toList do eachCapture pat (Pattern.drop sepp text) + +Text.splitOn.doc : Doc +Text.splitOn.doc = + {{ + Breaks a {type Text} into a {type List} of chunks, which were delimited by + characters in the given {type Class}. The delimiters are not included in the + result. + + # Examples + + ``` + splitOn whitespace "“Isn't 'Ni!' an odd word?” said Arthur." + ``` + + ``` + splitOn (Class.not word) "“Isn't 'Ni!' an odd word?” said Arthur." + ``` + }} + +Text.splitOnNewline : Text -> [Text] +Text.splitOnNewline = Text.split ?\n + +Text.splitOnNewline.doc : Doc +Text.splitOnNewline.doc = + {{ + Breaks a string into a list of lines, which were delimited by newlines. + + # Examples + + ``` + splitOnNewline + "The purple cow jumped over the moon\nand landed on the sun." + ``` + + ``` + splitOnNewline "" + ``` + }} + +Text.startsWith : Text -> Text -> Boolean +Text.startsWith prefix t = match Text.uncons prefix with + Some (c, prefix') -> + match Text.uncons t with + Some (t', t'') | c === t' -> Text.startsWith prefix' t'' + _ -> false + _ -> true + +Text.startsWith.doc : Doc +Text.startsWith.doc = + {{ + `` startsWith prefix t `` returns `` true `` if the {type Text} 't' starts + with the {type Text} 'prefix'. + + # Examples + + ``` + startsWith "abc" "abc123def" + ``` + + ``` + startsWith "abc" "123abc456" + ``` + }} + +test> Text.startsWith.tests.isPrefix = + runs 1000 do + use Nat > + use Text ++ ascii size + t1 = ascii() + t2 = ascii() + p1 = startsWith t1 (t1 ++ t2) + p2 = + implies + (startsWith t1 (t2 ++ t1)) (startsWith t1 t2 || size t1 > size t2) + if Boolean.not (p1 && p2) then bug (t1, t2, p1, p2) else expect (p1 && p2) + +Text.substitute : ([Text] ->{g} Text) -> Pattern Text -> Text ->{g} Text +Text.substitute f p t = Pattern.run p t |> (cases + Some (captures, rest) -> f captures Text.++ rest + None -> t) + +Text.substitute.doc : Doc +Text.substitute.doc = + use Text ++ + {{ + `` substitute f pat t `` passes the captures from the pattern `pat` in the + text `t` to the function `f`, and replaces the region of the text with + matches the pattern, with the result of `f`. If the text `t` does not match + the pattern `pat`, the function returns the original text `t`. + + # Examples + + ``` + substitute (const "x") (literal "a") "abracadabra" + ``` + + ``` + f = cases + [r] -> "<" ++ r ++ ">" + x -> bug x + substitute f (captureWithin "{{" "}}") "{{hello}} world {{foo}} bar" + ``` + }} + +Text.substituteAll : Pattern Text -> ([Text] ->{g} Text) -> Text ->{g} Text +Text.substituteAll pat f t = + use Text ++ + go acc t = Pattern.run pat t |> (cases + Some (captures, rest) -> go (acc ++ f captures) rest + None -> + match t with + "" -> acc + t -> go (acc ++ Text.take 1 t) (Text.drop 1 t)) + go "" t + +Text.substituteAll.doc : Doc +Text.substituteAll.doc = + use Text ++ + {{ + `` substituteAll pat r t `` replaces all occurrences of the pattern `pat` in + the text `t` with the text `r`. + + # Examples + + ``` + substituteAll (literal "a") (const "x") "abracadabra" + ``` + + ``` + f = cases + [r] -> "<" ++ r ++ ">" + x -> bug x + substituteAll (captureWithin "{{" "}}") f "{{hello}} world {{foo}} bar" + ``` + }} + +Text.substituteMany : ([Text] ->{g} Text) -> Pattern Text -> Text ->{g} Text +Text.substituteMany f p t = + use Text ++ + go acc t = Pattern.run p t |> (cases + Some (captures, rest) -> go (acc ++ f captures) rest + None -> acc ++ t) + go "" t + +Text.substituteMany.doc : Doc +Text.substituteMany.doc = + use Text ++ + {{ + `` substituteMany f pat t `` passes the captures from the pattern `pat` in + the text `t` to the function `f`, and replaces the matched region of the text + with the result of `f`. It repeats this process on the rest of the text until + the pattern stops matching. If the text `t` does not match the pattern `pat`, + the function returns the original text `t`. This function is similar to + {substitute} except it matches the pattern multiple times. + + # Examples + + ``` + substituteMany (const "x") (literal "a") "aardvark" + ``` + + ``` + f = cases + [r] -> "<" ++ r ++ ">" + x -> bug x + substituteMany f (captureWithin "{{" "}}") "{{hello}}{{world}}{{foo}} bar" + ``` + }} + +-- builtin Text.take : Nat -> Text -> Text + +Text.take.doc : Doc +Text.take.doc = + use Text take + {{ + `` take n t `` returns the first `n` characters of `t`. + + If `n` is greater than or equal to the length of `t`, the result is `t`. + + # Examples + + ``` + take 0 "abc" + ``` + + ``` + take 2 "abc" + ``` + + ``` + take 10 "abc" + ``` + }} + +Text.takeRightWhile : (Char ->{g} Boolean) -> Text ->{g} Text +Text.takeRightWhile p t' = + use Nat + - + go n t = match Text.unsnoc t with + Some (i, c) | p c -> go (n + 1) i + _ -> Text.drop (Text.size t' - n) t' + go 0 t' + +Text.takeRightWhile.doc : Doc +Text.takeRightWhile.doc = + {{ + Take a suffix of a {type Text} value that matches a predicate. + + # Example + + ``` + takeRightWhile + (Boolean.not << isMatch space << Char.toText) "Hello, world!" + ``` + }} + +Text.takeUntil : (Char ->{e} Boolean) -> Text ->{e} Text +Text.takeUntil f t = match break f t with (h, t) -> h Text.++ Text.take 1 t + +Text.takeUntil.doc : Doc +Text.takeUntil.doc = + use Text takeUntil + {{ + Takes characters from a {type Text} until a given predicate is satisfied. + + Also takes the first character that satisfies the predicate. + + # Example + + ``` + takeUntil isSpace "hello world" + ``` + + # See also + + * {Text.dropUntil} returns the characters that {takeUntil} would leave + behind. + * {Text.takeWhile} takes the longest prefix where all characters match a + predicate. + * {Text.take} takes the first n characters. + * {takeRightWhile} takes the longest suffix where all characters match a + predicate. + }} + +Text.takeWhile : (Char ->{e} Boolean) -> Text ->{e} Text +Text.takeWhile p t = + use Text ++ + go acc rest = match Text.uncons rest with + Some (c, suffix) | p c -> go (acc ++ Char.toText c) suffix + _ -> acc + go "" t + +Text.takeWhile.doc : Doc +Text.takeWhile.doc = + use Text takeWhile + {{ + `` takeWhile p t `` returns the longest prefix of `xs` with characters that + satisfy the predicate 'p'. + + # Examples + + ``` + takeWhile isLetter "abc123def" + ``` + + ``` + takeWhile isLetter "123abc456" + ``` + }} + +test> Text.takeWhile.tests.allMatch = runs 1000 do + t = Text.ascii() + prefix = Text.takeWhile isAlphaNum t + expect (List.all isAlphaNum (toCharList prefix)) + +test> Text.takeWhile.tests.isLongestPrefix = + runs 1000 do + t = Text.ascii() + prefix = Text.takeWhile isAlphaNum t + expect + (Optional.map + isAlphaNum (List.head (List.drop (Text.size prefix) (toCharList t))) + !== Some true) + +test> Text.takeWhile.tests.isPrefix = runs 1000 do + t = Text.ascii() + prefix = Text.takeWhile isAlphaNum t + expect (startsWith prefix t) + +Text.toBag : Text -> Bag Char +Text.toBag = Bag.fromList << toCharList + +Text.toBag.doc : Doc +Text.toBag.doc = + use Bag toText + use Text toBag + {{ + Convert a {type Text} to a {type Bag} of {type Char}s. + + # Examples + + ``` + toText (toBag "") + ``` + + ``` + toText (toBag "hello world") + ``` + + ``` + toText (toBag "🥕🍐🍎🍐🍎🥕🍎🍐🍐🍎🥕🍐") + ``` + }} + +-- builtin Text.toCharList : Text -> [Char] + +Text.toCharList.doc : Doc +Text.toCharList.doc = + {{ + Converts the given {type Text} to a {type List} of the {type Char} values + that make up the text. + + # Example + + ``` + toCharList "hello" + ``` + + ``` + toCharList "👋🏽" + ``` + }} + +Text.toDoc : Text -> Doc +Text.toDoc t = Paragraph (List.map Word (words t)) + +Text.toDoc.doc : Doc +Text.toDoc.doc = + {{ + Returns the given {type Text} as a {type Doc} paragraph. `` + Text.toDoc "Some text" `` is equivalent to `{{ Some text }}`. + }} + +-- builtin Text.toLowercase : Text -> Text + +Text.toLowercase.doc : Doc +Text.toLowercase.doc = + use Text toLowercase + {{ + `` toLowercase txt `` converts all characters in `txt` to lowercase. + + ``` + toLowercase "HELLO THERE!" + ``` + + ``` + toLowercase "ABC 123" + ``` + + Also see: {Text.toUppercase} + }} + +Text.toSet : Text -> Set Char +Text.toSet = Set.fromList << toCharList + +Text.toSet.doc : Doc +Text.toSet.doc = + {{ + Converts {type Text} to the {type Set} of {type Char}s (Unicode code points) + in that {type Text}. + }} + +Text.toStream : Text ->{Stream Char} () +Text.toStream t = + use Nat + + go n = match charAt n t with + None -> () + Some c -> + emit c + go (n + 1) + go 0 + +Text.toStream.doc : Doc +Text.toStream.doc = + {{ + Creates a {type Stream} that produces each character of the given + {type Text}. + + # Example + + ``` + Stream.toList do Text.toStream "abracadabra" + ``` + + # See also + + * {toCharList} + * {eachChar} + }} + +-- builtin Text.toUppercase : Text -> Text + +Text.toUppercase.doc : Doc +Text.toUppercase.doc = + use Text toUppercase + {{ + `` toUppercase txt `` converts all characters in `txt` to uppercase. + + ``` + toUppercase "hello there!" + ``` + + ``` + toUppercase "abc 123" + ``` + + Also see: {Text.toLowercase} + }} + +-- builtin Text.toUtf8 : Text -> Bytes + +Text.toUtf8.doc : Doc +Text.toUtf8.doc = + use Text toUtf8 + {{ + Convert a {type Text} value to a {type Bytes} value, using UTF-8 encoding. + + # Examples + + ``` + toUtf8 "abc" + ``` + + ``` + toUtf8 "🌎" + ``` + + ``` + toUtf8 "👍🏽" + ``` + }} + +Text.trim : Text -> Text +Text.trim t = + t |> Text.dropWhile isWhitespace |> Text.dropRightWhile isWhitespace + +Text.trim.doc : Doc +Text.trim.doc = + {{ + Returns the given {type Text} with leading and trailing whitespace removed. + + # Examples + + ``` + trim " abc " + ``` + + ``` + trim "abc" + ``` + + ``` + trim " " + ``` + }} + +test> Text.trim.tests.all = + use Text == + check (trim " \n \t " == "") + +test> Text.trim.tests.edges = + use Text == + check + (trim " \t \n something with whitespace on edges \t \n" + == "something with whitespace on edges") + +test> Text.trim.tests.none = + use Text == + check (trim "Nothing To Trim" == "Nothing To Trim") + +-- builtin Text.uncons : Text -> Optional (Char, Text) + +Text.uncons.doc : Doc +Text.uncons.doc = + use Text uncons + {{ + Split a {type Text} value into its first character and the rest of the + string. If the string is empty, return {None}. + + # Examples + + ``` + uncons "abc" + ``` + + ``` + uncons "" + ``` + }} + +test> Text.uncons.tests = test.verify do + use Text uncons + ensureEqual None (uncons "") + ensureEqual (Some (?a, "")) (uncons "a") + ensureEqual (Some (?a, "bc")) (uncons "abc") + ensureEqual (Some (?b, "cd")) (uncons "bcd") + ensureEqual (Some (?🌈, "🌧️")) (uncons "🌈🌧️") + +Text.unlines : [Text] -> Text +Text.unlines = + use Text ++ + List.foldLeft (acc e -> acc ++ e ++ "\n") "" + +Text.unlines.doc : Doc +Text.unlines.doc = + {{ + Concatenates a list of lines into a single string after adding a newline + character to the end of each line. + + # Examples + + Note that the newline character is added to the end of each line, including + the last one: + + ``` + unlines ["The purple cow", "jumped over the moon."] + ``` + + {{ docLink (docEmbedTermLink do unlines) }} is not exactly the inverse of + {{ docLink (docEmbedTermLink do lines) }}, due to the newline at the end: + + ``` + unlines (lines "The purple cow\njumped over the moon.") + ``` + + Similarly, {{ docLink (docEmbedTermLink do lines) }} is not exactly the + inverse of {{ docLink (docEmbedTermLink do unlines) }}, if the input lines + contain newlines: + + ``` + lines (unlines ["The purple cow", "jumped over the moon.\n"]) + ``` + + ## See also: + + * {{ docLink (docEmbedTermLink do lines) }} - goes the other way, + breaking a string into lines + * {{ docLink (docEmbedTermLink do unwords) }} - joins a list of words + into a single string separated by spaces + * {words} - splits a string on whitespace + }} + +test> Text.unlines.roundtrip = + test.verify do + use Random natIn + lines = + Random.listOf (do ofChars (do interval ?a ?z) (natIn 0 100)) do + natIn 0 100 + ensureEqual (Text.lines (unlines lines)) lines + +test> Text.unlines.tests.empty = check (unlines [] === "") + +test> Text.unlines.tests.leading = + check (unlines ["", "hello", "world"] === "\nhello\nworld\n") + +test> Text.unlines.tests.multi = + check (unlines ["hello", "world"] === "hello\nworld\n") + +test> Text.unlines.tests.single = check (unlines ["hello"] === "hello\n") + +test> Text.unlines.tests.trailing = + check (unlines ["hello", "world", ""] === "hello\nworld\n\n") + +-- builtin Text.unsnoc : Text -> Optional (Text, Char) + +Text.unsnoc.doc : Doc +Text.unsnoc.doc = + use Text unsnoc + {{ + Split a {type Text} value into its last character and the rest of the string. + If the string is empty, return {None}. + + # Examples + + ``` + unsnoc "abc" + ``` + + ``` + unsnoc "" + ``` + }} + +Text.unwords : [Text] -> Text +Text.unwords = Text.join " " + +Text.unwords.doc : Doc +Text.unwords.doc = + {{ + Joins list of {type Text} with whitespace character as a separator. Inverse + of {{ docLink (docEmbedTermLink do words) }} function. + + # Examples + + ``` + "The purple cow jumped over the moon." |> words |> unwords + ``` + + ``` + " The purple cow jumped over the moon. " |> words |> unwords + ``` + + ``` + "" |> words |> unwords + ``` + + ## See also: + + * {words} - splits a string on whitespace + * {{ docLink (docEmbedTermLink do unlines) }} - joins a list of lines + into a single string + * {{ docLink (docEmbedTermLink do lines) }} - breaks a string into lines + }} + +test> Text.unwords.test = test.verify do + use Text == + x = " one two three " + f = unwords << words + ensure (f x === trim x) + ensure ((x |> f |> f) == trim x) + +Text.words : Text -> [Text] +Text.words t = + use Pattern join + c = Class.not whitespace + word = join [Pattern.capture (some (patterns.char c)), many space] + captures (join [many space, many word]) t + +Text.words.doc : Doc +Text.words.doc = + {{ + Breaks a {type Text} into a list of words, which were delimited by + whitespace. Trims any whitespace at the beginning and at the end. + + A whitespace character is any character that matches the {space} + {type Pattern}. + + {{ + docCallout + None + {{ + **Note:** this function treats consecutive whitespace characters as one. + }} }} + + # Examples + + ``` + words "The purple cow jumped over the moon." + ``` + + ``` + words " The purple cow jumped over the moon. " + ``` + + ``` + words "" + ``` + + ## See also: + + * {{ docLink (docEmbedTermLink do unwords) }} - joins a list of words + into a single string separated by spaces + * {{ docLink (docEmbedTermLink do lines) }} - breaks a string into lines + * {{ docLink (docEmbedTermLink do unlines) }} - joins a list of lines + into a single string + }} + +test> Text.words.test = + test.verify do ensure ((" 1 2 3 4 " |> words |> List.size) Nat.== 4) + +-- builtin time.Clock.internals.monotonic.impl : +-- '{IO} Either Failure time.Clock.internals.TimeSpec + +-- builtin time.Clock.internals.processCPUTime.impl : +-- '{IO} Either Failure time.Clock.internals.TimeSpec + +-- builtin time.Clock.internals.realtime.impl : +-- '{IO} Either Failure time.Clock.internals.TimeSpec + +-- builtin time.Clock.internals.systemTimeZone.impl : +-- Int ->{IO} (Int, Nat, Text) + +-- builtin time.Clock.internals.threadCPUTime.impl : +-- '{IO} Either Failure time.Clock.internals.TimeSpec + +-- builtin time.Clock.internals.TimeSpec.nsec : +-- time.Clock.internals.TimeSpec -> Nat + +-- builtin time.Clock.internals.TimeSpec.sec : +-- time.Clock.internals.TimeSpec -> Int + +time.Clock.monotonic : '{IO, Exception} Duration +time.Clock.monotonic = do + t = Either.toException monotonic.impl() + Duration (sec t) (nsec t) + +time.Clock.monotonic.doc : Doc +time.Clock.monotonic.doc = + {{ + Gets the time elapsed since some fixed point in the past, according to a + clock that can never jump backwards and cannot be set by any process. + + Programs can use {Clock.monotonic} to measure real time without interference + from Daylight Saving Time changes or manipulation of the system clock. + }} + +time.Clock.processCPUTime : '{IO, Exception} Duration +time.Clock.processCPUTime = do + t = Either.toException processCPUTime.impl() + Duration (sec t) (nsec t) + +time.Clock.processCPUTime.doc : Doc +time.Clock.processCPUTime.doc = + {{ + Gets the amount of CPU time spent by the operating system process running + Unison. + }} + +time.Clock.threadCPUTime : '{IO, Exception} Duration +time.Clock.threadCPUTime = do + t = Either.toException threadCPUTime.impl() + Duration (sec t) (nsec t) + +time.Clock.threadCPUTime.doc : Doc +time.Clock.threadCPUTime.doc = + {{ + Gets the amount of CPU time spent by the operating system thread executing + your program. Note that Unison green threads spawned by {fork} do not + necessarily correspond to OS threads. + }} + +time.Clock.timeSinceEpoch : '{IO, Exception} Duration +time.Clock.timeSinceEpoch = do Instant.timeSinceEpoch realtime() + +time.Clock.timeSinceEpoch.doc : Doc +time.Clock.timeSinceEpoch.doc = + {{ + Gets the time elapsed on the real-world clock since midnight UTC on January + 1st 1970. + }} + +time.DayOfWeek.daysSinceSat : DayOfWeek -> Nat +time.DayOfWeek.daysSinceSat = cases + Sat -> 0 + Sun -> 1 + Mon -> 2 + Tue -> 3 + Wed -> 4 + Thu -> 5 + Fri -> 6 + +time.DayOfWeek.daysSinceSat.doc : Doc +time.DayOfWeek.daysSinceSat.doc = + {{ + Returns the number of days since Saturday for the given day of the week. This + is a way of converting a {type DayOfWeek} to a {type Nat}. + + # Examples + + ``` + daysSinceSat Mon + ``` + + ``` + daysSinceSat Sat + ``` + }} + +time.DayOfWeek.doc : Doc +time.DayOfWeek.doc = + {{ + Represents a day of the week in the civil calendar. + + # Construction + + Use the {type DayOfWeek} constructors directly: + + ``` + [Sat, Sun, Mon, Tue, Wed, Thu, Fri] + ``` + + You can also use a {type Nat} representing the number of days since + Saturday: + + ``` + List.map DayOfWeek.number (Nat.range 0 7) + ``` + + Parse an English name for a day of the week, or any unambiguous + abbreviation: + + ``` + List.filterMap fromName ["Mon", "Tuesday", "wed", "th"] + ``` + + # Conversion from other types + + Get the day of the week for a {type LocalDate}: + + ``` + LocalDate.dayOfWeek (LocalDate +2023 6 14) + ``` + + Get the day of the week for a {type LocalDateTime}: + + ``` + Optional.map + LocalDateTime.dayOfWeek (LocalDateTime.fromIso8601 "2023-06-14T12:00:00") + ``` + + Get the day of the week for an {type OffsetDateTime}: + + ``` + Optional.map + OffsetDateTime.dayOfWeek + (OffsetDateTime.fromIso8601 "2023-06-14T12:00:00-07:00") + ``` + + Get the day of the week for an {type Instant}: + + ``` + OffsetDateTime.dayOfWeek (atUTC epoch) + ``` + + # Conversion to other types + + Convert a {type DayOfWeek} to a {type Nat} representing the number of days + since Saturday: + + ``` + List.map daysSinceSat [Sat, Sun, Mon, Tue, Wed, Thu, Fri] + ``` + + Convert a {type DayOfWeek} to a {type Text}: + + ``` + List.map DayOfWeek.name [Sat, Sun, Mon, Tue, Wed, Thu, Fri] + ``` + + ``` + List.map shortName [Sat, Sun, Mon, Tue, Wed, Thu, Fri] + ``` + + # Properties + + Determine whether a {type DayOfWeek} is a weekend day: + + ``` + List.map isWeekend [Sat, Sun, Mon, Tue, Wed, Thu, Fri] + ``` + + Determine whether a {type DayOfWeek} is a weekday: + + ``` + List.map isWeekday [Sat, Sun, Mon, Tue, Wed, Thu, Fri] + ``` + }} + +time.DayOfWeek.fromName : Text -> Optional DayOfWeek +time.DayOfWeek.fromName name = + abbrev = Text.toLowercase (Text.take 2 name) + match abbrev with + "sa" -> Some Sat + "su" -> Some Sun + "mo" -> Some Mon + "tu" -> Some Tue + "we" -> Some Wed + "th" -> Some Thu + "fr" -> Some Fri + _ -> None + +time.DayOfWeek.fromName.doc : Doc +time.DayOfWeek.fromName.doc = + use List filterMap + {{ + Returns the day of the week corresponding to the given English name or + 3-letter abbreviation, or None if the given {type Text} is not the name of an + English day of the week. + + # Examples + + ``` + filterMap + fromName + [ "Saturday" + , "Sunday" + , "Monday" + , "Tuesday" + , "Wednesday" + , "Thursday" + , "Friday" + ] + ``` + + You can supply a three-letter abbreviation instead of the full English + name: + + ``` + filterMap fromName ["Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri"] + ``` + + A two-letter abbreviation also works: + + ``` + filterMap fromName ["Sa", "Su", "Mo", "Tu", "We", "Th", "Fr"] + ``` + + The name is not case-sensitive: + + ``` + filterMap fromName ["sat", "sun", "mon", "tue", "wed", "thu", "fri"] + ``` + + The conversion works as long as the first two letters match: + + ``` + filterMap fromName ["Fridurday", "Friendsday", "Fössari"] + ``` + }} + +time.DayOfWeek.isWeekday : DayOfWeek -> Boolean +time.DayOfWeek.isWeekday = cases + Sat -> false + Sun -> false + _ -> true + +time.DayOfWeek.isWeekday.doc : Doc +time.DayOfWeek.isWeekday.doc = + {{ + Returns whether the given day of the week is a weekday as opposed to a + weekend. + + # Examples + + ``` + isWeekday Sat + ``` + + ``` + isWeekday Mon + ``` + }} + +time.DayOfWeek.isWeekend : DayOfWeek -> Boolean +time.DayOfWeek.isWeekend = cases + Sat -> true + Sun -> true + _ -> false + +time.DayOfWeek.isWeekend.doc : Doc +time.DayOfWeek.isWeekend.doc = + {{ + Returns whether the given day of the week is a weekend day. + + # Examples + + ``` + isWeekend Mon + ``` + + ``` + isWeekend Sat + ``` + }} + +time.DayOfWeek.name : DayOfWeek -> Text +time.DayOfWeek.name = cases + Sat -> "Saturday" + Sun -> "Sunday" + Mon -> "Monday" + Tue -> "Tuesday" + Wed -> "Wednesday" + Thu -> "Thursday" + Fri -> "Friday" + +time.DayOfWeek.name.doc : Doc +time.DayOfWeek.name.doc = + {{ + Returns the full English form of the given day of the week. + + # Example + + ``` + List.map DayOfWeek.name [Sat, Sun, Mon, Tue, Wed, Thu, Fri] + ``` + }} + +time.DayOfWeek.next : DayOfWeek -> DayOfWeek +time.DayOfWeek.next = cases + Sat -> Sun + Sun -> Mon + Mon -> Tue + Tue -> Wed + Wed -> Thu + Thu -> Fri + Fri -> Sat + +time.DayOfWeek.next.doc : Doc +time.DayOfWeek.next.doc = + {{ + Returns the day of the week that follows the given day of the week. + + # Examples + + ``` + next Mon + ``` + + ``` + next Sat + ``` + }} + +time.DayOfWeek.number : Nat -> DayOfWeek +time.DayOfWeek.number n = match Nat.mod n 7 with + 0 -> Sat + 1 -> Sun + 2 -> Mon + 3 -> Tue + 4 -> Wed + 5 -> Thu + 6 -> Fri + _ -> bug "mod 7 should be between 0 and 6" + +time.DayOfWeek.number.doc : Doc +time.DayOfWeek.number.doc = + {{ + Returns the day of the week corresponding to the given number of days since + Saturday. + + # Example + + ``` + List.map DayOfWeek.number (Nat.range 0 7) + ``` + }} + +time.DayOfWeek.previous : DayOfWeek -> DayOfWeek +time.DayOfWeek.previous = cases + Sat -> Fri + Sun -> Sat + Mon -> Sun + Tue -> Mon + Wed -> Tue + Thu -> Wed + Fri -> Thu + +time.DayOfWeek.previous.doc : Doc +time.DayOfWeek.previous.doc = + {{ + Returns the day of the week that precedes the given day of the week. + + # Examples + + ``` + previous Mon + ``` + + ``` + previous Sat + ``` + }} + +time.DayOfWeek.shortName : DayOfWeek -> Text +time.DayOfWeek.shortName = cases + Sat -> "Sat" + Sun -> "Sun" + Mon -> "Mon" + Tue -> "Tue" + Wed -> "Wed" + Thu -> "Thu" + Fri -> "Fri" + +time.DayOfWeek.shortName.doc : Doc +time.DayOfWeek.shortName.doc = + {{ + Returns the short English form of the given day of the week. + + # Example + + ``` + List.map shortName [Sat, Sun, Mon, Tue, Wed, Thu, Fri] + ``` + }} + +(time.Duration.!=) : Duration -> Duration -> Boolean +x time.Duration.!= y = + use Duration == + Boolean.not (x == y) + +(time.Duration.*) : Int -> Duration -> Duration +(time.Duration.*) = cases + n, Duration secs nanos -> + use Int * + / + ns = n * Nat.toInt nanos + Duration (n * secs + ns / +1000000000) (Int.emod ns +1000000000) + +time.Duration.*.doc : Doc +time.Duration.*.doc = + use Duration * + {{ + `` n * d `` scales a {type Duration} `d` by a number `n`. + + # Examples + + ``` + countMinutes (+2 * Duration.hour) + ``` + + ``` + countSeconds (-1 * Duration.minute) + ``` + }} + +(time.Duration.+) : Duration -> Duration -> Duration +(time.Duration.+) = cases + Duration s n, Duration s' n' -> + use Nat / + ns = n Nat.+ n' + Duration + (s Int.+ s' Int.+ Nat.toInt ((n Nat.+ n') / 1000000000)) + (Nat.mod ns 1000000000) + +time.Duration.+.doc : Doc +time.Duration.+.doc = + use Duration * + hour minute + {{ + `` d1 + d2 `` adds two {type Duration} values `d1` and `d2`. + + # Examples + + ``` + countMinutes (hour + hour) + ``` + + ``` + countMinutes (+60 * minute + minute) + ``` + }} + +(time.Duration.-) : Duration -> Duration -> Duration +d1 time.Duration.- d2 = + use Duration + + d1 + Duration.negate d2 + +time.Duration.-.doc : Doc +time.Duration.-.doc = + use Duration - hour minute + {{ + `` d1 - d2 `` subtracts one {type Duration} value `d2` from another `d1`. + + # Examples + + ``` + countMinutes (hour - minute) + ``` + + ``` + countMinutes (hour - hour) + ``` + + ``` + countMinutes (hour - hour - minute) + ``` + }} + +(time.Duration./) : Duration -> Int -> Duration +(time.Duration./) = cases + Duration secs nanos, scalar -> + use Duration + + use Int / + use Nat - + ss = secs / scalar + remainingNano = Int.mod secs scalar Int.* +1000000000 / scalar + positiveNano = Int.isNegative remainingNano |> Boolean.not + secondsDuration = + remainder = Int.abs remainingNano + if positiveNano then Duration ss remainder + else Duration ss (1000000000 - remainder) + ns = Nat.toInt nanos / scalar + secondsDuration + ns Duration.* Duration.nanosecond + +time.Duration./.doc : Doc +time.Duration./.doc = + use Duration / hour + {{ + `` d / n `` divides a {type Duration} value `d` by a number `n`. + + # Examples + + ``` + countMinutes (hour / +2) + ``` + + ``` + countMinutes (hour / -1) + ``` + + ``` + countMinutes (hour / +2 / -1) + ``` + }} + +(time.Duration.<) : Duration -> Duration -> Boolean +x time.Duration.< y = Duration.compare x y === Less + +time.Duration.<.doc : Doc +time.Duration.<.doc = + use Duration == + {{ `` d1 == d2 `` returns true if `d1` is strictly shorter than `d2`. }} + +(time.Duration.<=) : Duration -> Duration -> Boolean +x time.Duration.<= y = Universal.lteq (Duration.compare x y) Equal + +time.Duration.<=.doc : Doc +time.Duration.<=.doc = + use Duration <= + {{ `` d1 <= d2 `` returns true if `d1` is shorter than or equal to `d2`. }} + +(time.Duration.==) : Duration -> Duration -> Boolean +x time.Duration.== y = Duration.compare x y === Equal + +(time.Duration.>) : Duration -> Duration -> Boolean +x time.Duration.> y = Duration.compare x y === Greater + +time.Duration.>.doc : Doc +time.Duration.>.doc = + use Duration > + {{ `` d1 > d2 `` returns true if `d1` is strictly longer than `d2`. }} + +(time.Duration.>=) : Duration -> Duration -> Boolean +x time.Duration.>= y = Universal.gteq (Duration.compare x y) Equal + +time.Duration.>=.doc : Doc +time.Duration.>=.doc = + use Duration >= + {{ `` d1 >= d2 `` returns true if `d1` is longer than or equal to `d2`. }} + +time.Duration.abs : Duration -> Duration +time.Duration.abs d = match d with + Duration s n + | s Int.< +0 -> Duration.negate d + | otherwise -> d + +time.Duration.abs.doc : Doc +time.Duration.abs.doc = + use Duration - abs minute second + {{ + `` abs d `` is the absolute value of `d` as a {type Duration}. If `d` is a + negative {type Duration}, this returns the positive {type Duration} of the + same magnitude. If `d` is already positive, this does nothing. + + # Examples + + ``` + countSeconds (abs (minute - second)) + ``` + + ``` + countSeconds (abs (second - minute)) + ``` + }} + +time.Duration.asDays : Duration -> Float +time.Duration.asDays = cases + Duration s n -> + Float.fromInt s Float./ 86400.0 Float.+ Float.fromNat n Float./ 8.64e13 + +time.Duration.asDays.doc : Doc +time.Duration.asDays.doc = + use Duration * hour + {{ + `` asDays d `` is the {type Duration} `d` expressed as a number of + {Duration.day}s. + + # Examples + + ``` + asDays (+12 * hour) + ``` + + ``` + asDays (+50 * hour) + ``` + }} + +time.Duration.asHours : Duration -> Float +time.Duration.asHours = cases + Duration s n -> + Float.fromInt s Float./ 3600.0 Float.+ Float.fromNat n Float./ 3.6e12 + +time.Duration.asHours.doc : Doc +time.Duration.asHours.doc = + use Duration * + {{ + `` asHours d `` is the {type Duration} `d` expressed as a number of + {Duration.hour}s. + + # Examples + + ``` + asHours (+15 * Duration.minute) + ``` + + ``` + asHours averageYear + ``` + }} + +time.Duration.asJulianYears : Duration -> Float +time.Duration.asJulianYears = cases + Duration s n -> + Float.fromInt s Float./ 3.15576e7 + Float.+ Float.fromNat n Float./ 3.15576e16 + +time.Duration.asJulianYears.doc : Doc +time.Duration.asJulianYears.doc = + use Duration * + {{ + `` asJulianYears d `` is the {type Duration} `d` expressed as a number of + Julian years (see {julianYear}). A Julian year is defined as exactly `` + 365.25 `` days that are each `` 24 `` hours long. + + # Example + + ``` + asJulianYears (+200 * week) + ``` + }} + +time.Duration.asMicroseconds : Duration -> Float +time.Duration.asMicroseconds = cases + Duration s n -> + Float.fromInt s Float.* 1000000.0 Float.+ Float.fromNat n Float./ 1000.0 + +time.Duration.asMicroseconds.doc : Doc +time.Duration.asMicroseconds.doc = + use Duration * + {{ + `` asMicroseconds d `` is the {type Duration} `d` expressed as a number of + microseconds. + + # Example + + ``` + asMicroseconds (+16777216 * Duration.nanosecond) + ``` + }} + +time.Duration.asMilliseconds : Duration -> Float +time.Duration.asMilliseconds = cases + Duration s n -> + Float.fromInt s Float.* 1000.0 Float.+ Float.fromNat n Float./ 1000000.0 + +time.Duration.asMilliseconds.doc : Doc +time.Duration.asMilliseconds.doc = + use Duration * + {{ + Duration.asMilliseconds d`` is the {type Duration} `d` expressed as a number + of milliseconds. + + # Example + + ``` + asMilliseconds (+16777216 * microsecond) + ``` + }} + +time.Duration.asMinutes : Duration -> Float +time.Duration.asMinutes = cases + Duration s n -> + Float.fromInt s Float./ 60.0 Float.+ Float.fromNat n Float./ 6.0e10 + +time.Duration.asMinutes.doc : Doc +time.Duration.asMinutes.doc = + use Duration * + {{ + `` asMinutes d `` is the {type Duration} `d` expressed as a number of + minutes. + + # Example + + ``` + asMinutes (+15 * Duration.second) + ``` + }} + +time.Duration.asNanoseconds : Duration -> Int +time.Duration.asNanoseconds = cases + Duration s n -> s Int.* +1000000000 Int.+ Nat.toInt n + +time.Duration.asNanoseconds.doc : Doc +time.Duration.asNanoseconds.doc = + use Duration * + {{ + `` asNanoseconds d `` is the {type Duration} `d` expressed as a number of + nanoseconds. + + # Example + + ``` + asNanoseconds (+200 * millisecond) + ``` + }} + +time.Duration.asSeconds : Duration -> Float +time.Duration.asSeconds = cases + Duration toSeconds n -> + Float.fromInt toSeconds Float.+ Float.fromNat n Float./ 1.0e9 + +time.Duration.asSeconds.doc : Doc +time.Duration.asSeconds.doc = + use Duration * + {{ + `` asSeconds d `` is the {type Duration} `d` expressed as a number of + seconds. + + # Example + + ``` + asSeconds (+200 * millisecond) + ``` + }} + +test> time.Duration.asSeconds.tests.test1 : [Result] +time.Duration.asSeconds.tests.test1 = test.verify do + ensureEqual (asSeconds (Duration +1 0)) 1.0 + ensureEqual (asSeconds (Duration +1 1)) 1.000000001 + ensureEqual (asSeconds (Duration +1 100000000)) 1.1 + ensureEqual (asSeconds (Duration +1 999999999)) 1.999999999 + ensureEqual (asSeconds (Duration +2 0)) 2.0 + ensureEqual (asSeconds (Duration +2 1)) 2.000000001 + ensureEqual (asSeconds (Duration +2 100000000)) 2.1 + ensureEqual (asSeconds (Duration +2 999999999)) 2.999999999 + +time.Duration.asWeeks : Duration -> Float +time.Duration.asWeeks = cases + Duration s n -> + Float.fromInt s Float./ 604800.0 Float.+ Float.fromNat n Float./ 6.048e14 + +time.Duration.asWeeks.doc : Doc +time.Duration.asWeeks.doc = + use Duration * + {{ + `` asWeeks d `` is the {type Duration} `d` expressed as a number of weeks. + + # Example + + ``` + asWeeks (+365 * Duration.day) + ``` + }} + +time.Duration.asYears : Duration -> Float +time.Duration.asYears = cases + Duration s n -> + Float.fromInt s Float./ 3.1556952e7 + Float.+ Float.fromNat n Float./ 3.1536e16 + +time.Duration.asYears.doc : Doc +time.Duration.asYears.doc = + use Duration * + {{ + `` asYears d `` is the number of whole {averageYear}s in the {type Duration} + `d`. + + # Example + + ``` + asYears (+200 * week) + ``` + }} + +time.Duration.averageMonth : Duration +time.Duration.averageMonth = Duration +2629746 0 + +time.Duration.averageMonth.doc : Doc +time.Duration.averageMonth.doc = + {{ + The average {type Duration} of a month in the Gregorian calendar, which is + 365.2425 / 12 days. + }} + +time.Duration.averageMonths : Int -> Duration +time.Duration.averageMonths n = + use Duration * + n * averageMonth + +time.Duration.averageMonths.doc : Doc +time.Duration.averageMonths.doc = + {{ + `` averageMonths n `` is a {type Duration} of `n` average months in the + Gregorian calendar, each `` 30.436875 `` days long. + }} + +time.Duration.averageYear : Duration +time.Duration.averageYear = Duration +31556952 0 + +time.Duration.averageYear.doc : Doc +time.Duration.averageYear.doc = + {{ + The average {type Duration} of a year in the Gregorian calendar, which is `` + 365.2425 `` days. + }} + +time.Duration.averageYears : Int -> Duration +time.Duration.averageYears y = + use Duration * + y * averageYear + +time.Duration.averageYears.doc : Doc +time.Duration.averageYears.doc = + {{ + `` averageYears n `` is a duration of `n` average years in the Gregorian + calendar, each `` 365.2425 `` days long. + }} + +time.Duration.between : Instant -> Instant -> Duration +time.Duration.between i1 i2 = + use Duration - + use Instant timeSinceEpoch + timeSinceEpoch i2 - timeSinceEpoch i1 + +time.Duration.between.doc : Doc +time.Duration.between.doc = + use Duration day + use Instant + + {{ + `` between x y `` returns the {type Duration} between {type Instant}s `x` and + `y`. If `x` is after `y`, the resulting {type Duration} is negative. + + # Examples + + ``` + countDays (between epoch (epoch + day)) + ``` + + ``` + countDays (between epoch (subtractDuration epoch day)) + ``` + }} + +time.Duration.compare : Duration -> Duration -> Ordering +time.Duration.compare x y = + use Universal ordering + Ordering.andThen + (on ordering countSeconds x y) (on ordering nanosComponent x y) + +time.Duration.compare.doc : Doc +time.Duration.compare.doc = + {{ + `` Duration.compare x y `` returns the relative order the sizes of + {type Duration}s `x` and `y`. + + Note that negative {type Duration}s are always {Less} than positive ones. Use + {Duration.abs} if you want to compare the absolute length of durations. + }} + +time.Duration.countDays : Duration -> Int +time.Duration.countDays = cases Duration s _ -> s Int./ +86400 + +time.Duration.countDays.doc : Doc +time.Duration.countDays.doc = + use Duration * + {{ + `` countDays d `` is the number of whole {Duration.day}s in the + {type Duration} `d`. + + # Example + + ``` + countDays (+50 * Duration.hour) + ``` + }} + +time.Duration.countHours : Duration -> Int +time.Duration.countHours = cases Duration s _ -> s Int./ +3600 + +time.Duration.countHours.doc : Doc +time.Duration.countHours.doc = + use Duration * + {{ + `` countHours d `` is the number of whole {Duration.hour}s in the + {type Duration} `d`. + + # Example + + ``` + countHours (+200 * Duration.minute) + ``` + }} + +time.Duration.countJulianYears : Duration -> Int +time.Duration.countJulianYears = cases Duration s _ -> s Int./ +31557600 + +time.Duration.countJulianYears.doc : Doc +time.Duration.countJulianYears.doc = + use Duration * + {{ + `` countJulianYears d `` is the number of whole Julian years (see + {julianYear}) in the {type Duration} `d`. A Julian year is defined as exactly + `` 365.25 `` days that are each `` 24 `` hours long. + + # Example + + ``` + countJulianYears (+200 * week) + ``` + }} + +time.Duration.countMicroseconds : Duration -> Int +time.Duration.countMicroseconds = cases + Duration s n -> s Int.* +1000000 Int.+ Nat.toInt (n Nat./ 1000) + +time.Duration.countMicroseconds.doc : Doc +time.Duration.countMicroseconds.doc = + use Duration * + {{ + `` countMicroseconds d `` is the number of whole microseconds in the + {type Duration} `d`. + + # Example + + ``` + countMicroseconds (+200 * millisecond) + ``` + }} + +time.Duration.countMilliseconds : Duration -> Int +time.Duration.countMilliseconds = cases + Duration s n -> s Int.* +1000 Int.+ Nat.toInt (n Nat./ 1000000) + +time.Duration.countMilliseconds.doc : Doc +time.Duration.countMilliseconds.doc = + use Duration * + {{ + Duration.asMilliseconds d`` is the number of whole milliseconds in the + {type Duration} `d`. + + # Example + + ``` + countMilliseconds (+200 * Duration.second) + ``` + }} + +time.Duration.countMinutes : Duration -> Int +time.Duration.countMinutes = cases Duration s _ -> s Int./ +60 + +time.Duration.countMinutes.doc : Doc +time.Duration.countMinutes.doc = + use Duration * + {{ + `` countMinutes d `` is the number of whole {Duration.minute}s in the + {type Duration} `d`. + + # Example + + ``` + countMinutes (+200 * Duration.second) + ``` + }} + +time.Duration.countSeconds : Duration -> Int +time.Duration.countSeconds = cases Duration toSeconds _ -> toSeconds + +time.Duration.countSeconds.doc : Doc +time.Duration.countSeconds.doc = + use Duration * + {{ + `` countSeconds d `` is the number of whole {Duration.second}s in the + {type Duration} `d`. + + # Example + + ``` + countSeconds (+200 * millisecond) + ``` + }} + +time.Duration.countWeeks : Duration -> Int +time.Duration.countWeeks = cases Duration s _ -> s Int./ +604800 + +time.Duration.countWeeks.doc : Doc +time.Duration.countWeeks.doc = + use Duration * + {{ + `` countWeeks d `` is the number of whole {week}s in the {type Duration} `d`. + + # Example + + ``` + countWeeks (+365 * Duration.day) + ``` + }} + +time.Duration.countYears : Duration -> Int +time.Duration.countYears = cases Duration s _ -> s Int./ +31536000 + +time.Duration.countYears.doc : Doc +time.Duration.countYears.doc = + use Duration * + {{ + `` countYears d `` is the number of whole {averageYear}s in the + {type Duration} `d`. + + # Example + + ``` + countYears (+200 * week) + ``` + }} + +time.Duration.day : Duration +time.Duration.day = Duration +86400 0 + +time.Duration.day.doc : Doc +time.Duration.day.doc = {{ A {type Duration} of exactly 24 hours. }} + +time.Duration.days : Int -> Duration +time.Duration.days d = + use Duration * + d * Duration.day + +time.Duration.days.doc : Doc +time.Duration.days.doc = {{ `` days n `` is a duration of `n` days. }} + +time.Duration.doc : Doc +time.Duration.doc = + use Duration != * + - / <= == > >= day mod nanosecond toText zero + {{ + A {type Duration} is a finite directed time interval with nanosecond + resolution. It's __directed__ in the sense that it represents the amount of + time __from__ some start time __to__ an end time. A {type Duration} can be + negative in which case the start time is after the end time. + + # Construction + + {zero} is a duration of zero length: + + @signature{zero} + + A few constructors are provided for {type Duration}s of common units. For + example, {nanosecond}, {time.Duration.second}, and {day} construct a + {type Duration} of one nanosecond, one second, and one day, respectively. + + @signature{nanosecond} @signature{microsecond} + @signature{millisecond} @signature{time.Duration.second} + @signature{Duration.minute} @signature{Duration.hour} + @signature{day} @signature{week} @signature{fortnight} + @signature{averageYear} @signature{julianYear} + + {nanoseconds}, {seconds}, and {days}, etc. are also provided for + convenience. Each takes an {type Int} and constructs a duration of that + many nanoseconds, seconds, days, etc. + + @signature{nanoseconds} @signature{microseconds} + @signature{milliseconds} @signature{seconds} + @signature{minutes} @signature{hours} @signature{days} + @signature{weeks} @signature{averageYears} + @signature{julianYears} + + You can get the {type Duration} from one {type Instant} to another: + + @signature{between} + + # Arithmetic + + @signature{*} + + Multiplies {type Duration} `d` by an {type Int} `n`. For example, make a + {type Duration} of 3 seconds with `` +3 * Duration.second `` + + @signature{+} + + Adds {type Duration} `d` to another {type Duration} `e`. For example, use + `` Duration.minutes 4 + Duration.seconds 30 `` to make a duration of 4.5 + minutes. + + @signature{-} + + Takes the difference between two durations. For example, use `` + Duration.minutes 4 - Duration.seconds 30 `` to make a duration of 3.5 + minutes. + + @signature{/} + + Divides a duration by an {type Int}. For example, `` Duration.hours +1 / +2 + `` is 30 minutes. + + @signature{mod} + + `` mod d1 d2 `` is the {type Duration} which is left over after dividing + the {type Duration} `d1` into segments of {type Duration} `d2`. + + @signature{Duration.abs} + + Removes the sign of a negative {type Duration}. + + @signature{Duration.negate} + + Flips the sign of a {type Duration}. + + # Comparison + + Various comparison operators are provided for {type Duration}s: + + @signature{==} @signature{>} @signature{<=} @signature{>=} + @signature{==} @signature{!=} @signature{Duration.compare} + @signature{Duration.isZero} @signature{Duration.isNegative} + + # Conversion + + You can convert a {type Duration} to an {type Int} in various ways: + + @signature{asNanoseconds} @signature{countMicroseconds} + @signature{countMilliseconds} @signature{countSeconds} + @signature{countMinutes} @signature{countHours} + @signature{countDays} @signature{countWeeks} + @signature{countYears} @signature{countJulianYears} + + These convert a {type Duration} to a {type Float}: + + @signature{asMicroseconds} @signature{asMilliseconds} + @signature{asSeconds} @signature{asMinutes} @signature{asHours} + @signature{asDays} @signature{asWeeks} @signature{asYears} + @signature{asJulianYears} + + You can render a {type Duration} as {type Text}: + + @signature{toText} + + For example: + + ``` + toText (fortnight + day) + ``` + }} + +time.Duration.fortnight : Duration +time.Duration.fortnight = Duration +1209600 0 + +time.Duration.fortnight.doc : Doc +time.Duration.fortnight.doc = + {{ The {type Duration} of a fortnight, which is exactly 14 days. }} + +time.Duration.hour : Duration +time.Duration.hour = Duration +3600 0 + +time.Duration.hour.doc : Doc +time.Duration.hour.doc = + {{ The {type Duration} of an hour, which is exactly 60 minutes. }} + +time.Duration.hours : Int -> Duration +time.Duration.hours h = + use Duration * + h * Duration.hour + +time.Duration.hours.doc : Doc +time.Duration.hours.doc = {{ `` hours n `` is a duration of `n` hours. }} + +time.Duration.isNegative : Duration -> Boolean +time.Duration.isNegative = cases Duration s _ -> Int.isNegative s + +time.Duration.isNegative.doc : Doc +time.Duration.isNegative.doc = + {{ + Checks if a {type Duration} is negative. A negative {type Duration} is for + example returned by `` between x y `` if the {type Instant} `x` is after the + {type Instant} `y`. + }} + +time.Duration.isZero : Duration -> Boolean +time.Duration.isZero = cases + Duration secs nanos -> secs Int.== +0 && nanos Nat.== 0 + +time.Duration.isZero.doc : Doc +time.Duration.isZero.doc = + {{ `` Duration.isZero `` checks if a {type Duration} has zero length. }} + +time.Duration.julianYear : Duration +time.Duration.julianYear = Duration +31557600 0 + +time.Duration.julianYear.doc : Doc +time.Duration.julianYear.doc = + {{ + {julianYear} returns the {type Duration} of a year in the Julian calendar, + which is exactly 365.25 days + }} + +time.Duration.julianYears : Int -> Duration +time.Duration.julianYears y = + use Duration * + y * julianYear + +time.Duration.julianYears.doc : Doc +time.Duration.julianYears.doc = + {{ + `` julianYears n `` is a duration of `n` years in the Julian calendar, each + `` 365.25 `` days long. + }} + +time.Duration.maxDuration : Duration +time.Duration.maxDuration = Duration maxInt 999999999 + +time.Duration.maxDuration.doc : Doc +time.Duration.maxDuration.doc = + {{ + The maximum representable {type Duration}, which is + {{ docWord (Duration.toText maxDuration) }}. + }} + +time.Duration.microsecond : Duration +time.Duration.microsecond = Duration +0 1000 + +time.Duration.microsecond.doc : Doc +time.Duration.microsecond.doc = + {{ + {microsecond} returns the {type Duration} of a microsecond, which is one + millionth of a second. + }} + +time.Duration.microseconds : Int -> Duration +time.Duration.microseconds u = + use Duration * + u * microsecond + +time.Duration.microseconds.doc : Doc +time.Duration.microseconds.doc = + {{ `` microseconds n `` is a duration of `n` microseconds. }} + +time.Duration.millisecond : Duration +time.Duration.millisecond = Duration +0 1000000 + +time.Duration.millisecond.doc : Doc +time.Duration.millisecond.doc = + {{ + {millisecond} returns the {type Duration} of a millisecond, which is one + thousandth of a second. + }} + +time.Duration.milliseconds : Int -> Duration +time.Duration.milliseconds m = + use Duration * + m * millisecond + +time.Duration.milliseconds.doc : Doc +time.Duration.milliseconds.doc = + {{ `` milliseconds n `` is a duration of `n` milliseconds. }} + +time.Duration.minDuration : Duration +time.Duration.minDuration = + use Int + + Duration (minInt + +1) 999999999 + +time.Duration.minDuration.doc : Doc +time.Duration.minDuration.doc = + {{ + The minimum representable {type Duration}, which is + {{ docWord (Duration.toText minDuration) }}. + }} + +time.Duration.minute : Duration +time.Duration.minute = Duration +60 0 + +time.Duration.minute.doc : Doc +time.Duration.minute.doc = + {{ + {Duration.minute} returns the {type Duration} of a minute, which is exactly + 60 seconds. + }} + +time.Duration.minutes : Int -> Duration +time.Duration.minutes m = + use Duration * + m * Duration.minute + +time.Duration.minutes.doc : Doc +time.Duration.minutes.doc = {{ `` minutes n `` is a duration of `n` minutes. }} + +time.Duration.mod : Duration -> Duration ->{Exception} Duration +time.Duration.mod = cases + _, Duration s n | s Int.== +0 && n Nat.== 0 -> Duration +0 0 + Duration s n, Duration s' n' -> + use Int < abs + use Natural * + - / fromNat mod toNat + sign = signum s + nanos1 = fromNat (abs s) * fromNat 1000000000 + fromNat n + nanos2 = fromNat (abs s') * fromNat 1000000000 + fromNat n' + nanosResultP = mod nanos1 nanos2 + nanosResult = if sign < +0 then nanos2 - nanosResultP else nanosResultP + secondsResult = toNat (nanosResult / fromNat 1000000000) + nanosResult' = toNat (mod nanosResult (fromNat 1000000000)) + Duration (Nat.toInt secondsResult) nanosResult' + +time.Duration.mod.doc : Doc +time.Duration.mod.doc = + use Duration * + hour minute mod second + {{ + `` mod x y `` returns the modulus of the division of {type Duration} `x` by + {type Duration} `y`. The result is a {type Duration} which is left over after + dividing `x` into segments of duration `y`. + + The result is always positive. Throws {DividedByZero} if `y` is zero. + + # Examples + + ``` + Duration.toText + (unsafeRun! do + mod (+4 * hour + +15 * minute + +12 * second + +123 * millisecond) hour) + ``` + + We can use the result to select for example the seconds component of a + {type Duration}: + + ``` + countSeconds + (unsafeRun! do + mod + (+4 * hour + +15 * minute + +12 * second + +123 * millisecond) minute) + ``` + + We can get the "second of the hour" by dividing by the hour instead of the + minute: + + ``` + countSeconds + (unsafeRun! do + mod (+4 * hour + +15 * minute + +12 * second + +123 * millisecond) hour) + ``` + }} + +time.Duration.nanosComponent : Duration -> Nat +time.Duration.nanosComponent = cases + Duration _ nanosComponent -> nanosComponent + +time.Duration.nanosComponent.doc : Doc +time.Duration.nanosComponent.doc = + {{ The nanosecond component of a {type Duration}. }} + +time.Duration.nanosecond : Duration +time.Duration.nanosecond = Duration +0 1 + +time.Duration.nanosecond.doc : Doc +time.Duration.nanosecond.doc = {{ A {type Duration} of one nanosecond. }} + +time.Duration.nanoseconds : Int -> Duration +time.Duration.nanoseconds n = + use Duration * + n * Duration.nanosecond + +time.Duration.nanoseconds.doc : Doc +time.Duration.nanoseconds.doc = + {{ `` nanoseconds n `` is a duration of `n` nanoseconds. }} + +time.Duration.negate : Duration -> Duration +time.Duration.negate = cases + Duration s n -> + use Int + + use Nat - == + nn = 1000000000 - n + Duration + (Int.negate (s + (if n == 0 then +0 else +1))) (Nat.mod nn 1000000000) + +time.Duration.negate.doc : Doc +time.Duration.negate.doc = + {{ + Negates a {type Duration}. Since a {type Duration} represents a __directed__ + distance between two {type Instant}s, durations can be negative. A duration + of 0 is returned unchanged. + }} + +time.Duration.second : Duration +time.Duration.second = Duration +1 0 + +time.Duration.second.doc : Doc +time.Duration.second.doc = + {{ {Duration.second} is the {type Duration} of one second. }} + +time.Duration.seconds : Int -> Duration +time.Duration.seconds s = + use Duration * + s * Duration.second + +time.Duration.seconds.doc : Doc +time.Duration.seconds.doc = {{ `` seconds n `` is a duration of `n` seconds. }} + +time.Duration.setNanos : Duration -> Nat -> Duration +time.Duration.setNanos d n = Duration (countSeconds d) n + +time.Duration.setNanos.doc : Doc +time.Duration.setNanos.doc = + {{ + `` setNanos d n `` sets the nanosecond component of `d` to `n`, returning a + new {type Duration}. + }} + +time.Duration.setSeconds : Duration -> Int -> Duration +time.Duration.setSeconds d s = Duration s (nanosComponent d) + +time.Duration.setSeconds.doc : Doc +time.Duration.setSeconds.doc = + {{ + `` setSeconds d s `` sets the second component of `d` to `s`, returning a new + {type Duration}. + }} + +test> time.Duration.tests.divNotTruncate.nn = + use Duration / + check (Duration -1 0 / -2 === Duration +0 500000000) + +test> time.Duration.tests.divNotTruncate.np = + use Duration / + check (Duration -1 0 / +2 === Duration -1 500000000) + +test> time.Duration.tests.divNotTruncate.pn = + use Duration / + check (Duration.second / -2 === Duration -1 500000000) + +test> time.Duration.tests.divNotTruncate.pp = + use Duration / + check (Duration.second / +2 === Duration +0 500000000) + +time.Duration.toText : Duration -> Text +time.Duration.toText d = + (if Duration.isNegative d then "T-" else "") + Text.++ (match Duration.abs d with + d + | Universal.gteq d averageYear -> + use Text ++ + remainder = unsafeRun! do Duration.mod d averageYear + Int.toText (countYears d) ++ "y" + ++ (if Duration.isZero remainder then "" + else " " ++ time.Duration.toText remainder) + | Universal.gteq d week -> + use Text ++ + remainder = unsafeRun! do Duration.mod d week + Int.toText (countWeeks d) ++ "w" + ++ (if Duration.isZero remainder then "" + else " " ++ time.Duration.toText remainder) + | Universal.gteq d Duration.day -> + use Text ++ + remainder = unsafeRun! do Duration.mod d Duration.day + Int.toText (countDays d) ++ "d" + ++ (if Duration.isZero remainder then "" + else " " ++ time.Duration.toText remainder) + | Universal.gteq d Duration.hour -> + use Text ++ + remainder = unsafeRun! do Duration.mod d Duration.hour + Int.toText (countHours d) ++ "h" + ++ (if Duration.isZero remainder then "" + else " " ++ time.Duration.toText remainder) + | Universal.gteq d Duration.minute -> + use Text ++ + remainder = unsafeRun! do Duration.mod d Duration.minute + Int.toText (countMinutes d) ++ "m" + ++ (if Duration.isZero remainder then "" + else " " ++ time.Duration.toText remainder) + | Universal.gteq d Duration.second -> + use Float / + use Text ++ + remainder = unsafeRun! do Duration.mod d Duration.second + if Duration.isZero remainder then + Int.toText (countSeconds d) ++ "s" + else + Float.toText (Float.fromInt (asNanoseconds d) / 1.0e9) + ++ "s" + | Universal.gteq d millisecond -> + use Float / + use Text ++ + remainder = unsafeRun! do Duration.mod d millisecond + if Duration.isZero remainder then + Int.toText (countMilliseconds d) ++ "ms" + else + Float.toText (Float.fromInt (asNanoseconds d) / 1000000.0) + ++ "ms" + | Universal.gteq d microsecond -> + use Float / + use Text ++ + remainder = unsafeRun! do Duration.mod d microsecond + if Duration.isZero remainder then + Int.toText (countMicroseconds d) ++ "µs" + else + Float.toText (Float.fromInt (asNanoseconds d) / 1000.0) + ++ "µs" + | otherwise -> + Int.toText (asNanoseconds d) Text.++ "ns") + +time.Duration.toText.doc : Doc +time.Duration.toText.doc = + use Duration * + toText + {{ + Displays a {Duration} in a human-readable format. + + # Examples + + ``` + toText + (+4 * Duration.hour + +15 * Duration.minute + +12 * Duration.second + + +123 * millisecond) + ``` + + ``` + toText (fortnight + Duration.day) + ``` + + ``` + toText (maxInt * Duration.nanosecond) + ``` + }} + +time.Duration.week : Duration +time.Duration.week = Duration +604800 0 + +time.Duration.week.doc : Doc +time.Duration.week.doc = + {{ A {type Duration} of exactly seven 24-hour periods. }} + +time.Duration.weeks : Int -> Duration +time.Duration.weeks w = + use Duration * + w * week + +time.Duration.weeks.doc : Doc +time.Duration.weeks.doc = {{ `` weeks n `` is a duration of `n` weeks. }} + +time.Duration.zero : Duration +time.Duration.zero = Duration +0 0 + +time.Duration.zero.doc : Doc +time.Duration.zero.doc = + {{ {Duration.zero} is the {type Duration} of zero seconds. }} + +(time.Instant.!=) : Instant -> Instant -> Boolean +x time.Instant.!= y = + use Instant == + Boolean.not (x == y) + +(time.Instant.+) : Instant -> Duration -> Instant +(time.Instant.+) = cases + Instant is in, Duration ds dn -> + use Nat / + nanos = in Nat.+ dn + Instant + (is Int.+ ds Int.+ Nat.toInt (nanos / 1000000000)) + (Nat.mod nanos 1000000000) + +time.Instant.+.doc : Doc +time.Instant.+.doc = + use Instant + + {{ + `` i + d `` returns a new {type Instant} that is an amount of time after `i` + according to the {type Duration} `d`. + + # Example + + ``` + OffsetDateTime.toText (atUTC (epoch + Duration.day)) + ``` + }} + +(time.Instant.-) : Instant -> Duration -> Instant +i time.Instant.- d = + use Instant + + i + Duration.negate d + +time.Instant.-.doc : Doc +time.Instant.-.doc = + use Duration * + use Instant - + use OffsetDateTime toText + {{ + `` i - d `` returns a new {type Instant} that's a {type Duration} `d` before + the {type Instant} `i` (or after it if the {type Duration} is negative). + + # Examples + + ``` + toText (atUTC (epoch - Duration.second)) + ``` + + ``` + toText (atUTC (epoch - +235984 * Duration.minute)) + ``` + }} + +(time.Instant.<) : Instant -> Instant -> Boolean +(time.Instant.<) = ltBy Instant.compare + +time.Instant.<.doc : Doc +time.Instant.<.doc = + use Instant < + {{ + `` x < y `` returns `` true `` if the {type Instant} `x` is before `y`, or `` + false `` otherwise. + }} + +(time.Instant.<=) : Instant -> Instant -> Boolean +(time.Instant.<=) = lteqBy Instant.compare + +time.Instant.<=.doc : Doc +time.Instant.<=.doc = + use Instant <= + {{ + `` x <= y `` returns `` false `` if the {type Instant} `x` is after the + {type Instant} `y`, or `` true `` otherwise. + }} + +(time.Instant.==) : Instant -> Instant -> Boolean +x time.Instant.== y = Instant.compare x y === Equal + +time.Instant.==.doc : Doc +time.Instant.==.doc = + {{ Checks if two {type Instant}s are equal to the nanosecond. }} + +(time.Instant.>) : Instant -> Instant -> Boolean +(time.Instant.>) = gtBy Instant.compare + +time.Instant.>.doc : Doc +time.Instant.>.doc = + use Instant > + {{ + `` x > y `` returns `` true `` if the {type Instant} `x` is after the + {type Instant} `y`, or `` false `` otherwise. + }} + +(time.Instant.>=) : Instant -> Instant -> Boolean +(time.Instant.>=) = gteqBy Instant.compare + +time.Instant.>=.doc : Doc +time.Instant.>=.doc = + use Instant >= + {{ + `` x >= y `` returns `` false `` if the {type Instant} `x` is before `y`, or + `` true `` otherwise. + }} + +time.Instant.addYears : Instant -> Int -> Instant +time.Instant.addYears t years = + match atUTC t with + OffsetDateTime (UTCOffset o) (LocalDateTime (LocalDate y m d) t) -> + toInstant + (OffsetDateTime + (UTCOffset o) (LocalDateTime (LocalDate (y Int.+ years) m d) t)) + +time.Instant.addYears.doc : Doc +time.Instant.addYears.doc = + {{ + Add an even number of years to an {type Instant}. + + The result is an {type Instant} shifted forward or backward in time by the + given number of years. The time-of-day, month-of-year, and day-of-month are + preserved. + + # Example + + ``` + Instant.toText (addYears epoch +50) + ``` + }} + +time.Instant.atOffset : Instant -> UTCOffset -> OffsetDateTime +time.Instant.atOffset = cases + Instant epochSeconds secondNanos, UTCOffset offset -> + use Int != * + - / emod min mod + seconds = emod epochSeconds +60 + epochMinutes = ediv epochSeconds +60 + offset + minutes = emod epochMinutes +60 + epochHours = ediv epochMinutes +60 + hours = emod epochHours +24 + epochDays = ediv epochHours +24 + a = epochDays + +678575 + +40587 + quadcent = a / +146097 + b = mod a +146097 + cent = min (b / +36524) +3 + c = b - +36524 * cent + quad = c / +1461 + d = mod c +1461 + y = min (d / +365) +3 + yd = d - +365 * y + year' = quadcent * +400 + cent * +100 + quad * +4 + y + +1 + isLeap = + mod year' +4 Int.== +0 + && (mod year' +100 != +0 || mod year' +400 Int.== +0) + let + (month', day') = dayOfYearToMonthAndDay isLeap (Int.abs yd) + (year, month, day) = + if month' Nat.== 13 then (year' + +1, 1, 1) else (year', month', day') + OffsetDateTime + (UTCOffset offset) + (LocalDateTime + (LocalDate year month day) + (LocalTime hours minutes seconds secondNanos)) + +time.Instant.atOffset.doc : Doc +time.Instant.atOffset.doc = + use Duration * + use Instant atOffset + use OffsetDateTime toText + {{ + `` atOffset i o `` returns an {type OffsetDateTime} representing the time + {type Instant} `i` at {type UTCOffset} `o`. + + {{ + docAside + {{ + This function does not take leap seconds into account. The {type Instant} + type defines a second as exactly 1/86400 of a day, which is not the same as + the SI second on days with leap seconds added or removed. + + If the given {type Instant} falls on a day with a leap second in the UTC + time zone, the resulting {type OffsetDateTime} will represent a time that + is off by as much as 11.57 microseconds from the actual UTC time. + }} }} + + # Examples + + ``` + toText (atOffset epoch CEST) + ``` + + ``` + toText + (atOffset (subtractDuration epoch (maxInt * Duration.nanosecond)) UTC) + ``` + }} + +time.Instant.atUTC : Instant -> OffsetDateTime +time.Instant.atUTC i = Instant.atOffset i (UTCOffset +0) + +time.Instant.atUTC.doc : Doc +time.Instant.atUTC.doc = + {{ + {atUTC} returns an {type OffsetDateTime} representing the time at the given + instant in + [Coordinated Universal Time](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). + + {{ + docAside + {{ + This function does not take leap seconds into account. The {type Instant} + type defines a second as exactly 1/86400 of a day, which is not the same as + the SI second on days with leap seconds added or removed. + + If the given {type Instant} falls on a day with a leap second in the UTC + time zone, the resulting {type OffsetDateTime} will represent a time that + is off by as much as 11.57 microseconds from the true UTC time. + }} }} + }} + +time.Instant.ceiling : Duration -> Instant ->{Exception} Instant +time.Instant.ceiling d i = + use Duration - + use Instant + + di = Instant.timeSinceEpoch i + i + d - Duration.mod di d + +time.Instant.ceiling.doc : Doc +time.Instant.ceiling.doc = + use Instant ceiling + {{ + `` ceiling d i `` rounds the given {type Instant} `i` up to the nearest + multiple of the given {type Duration} `d` (relative to the epoch). + + # Example + + This example rounds the given {type Instant} up to the nearest multiple of + 5 seconds on the clock: + + ``` + catch do Instant.toText (ceiling (seconds +5) (fromEpochSeconds +12)) + ``` + }} + +time.Instant.compare : Instant -> Instant -> Ordering +time.Instant.compare x y = + use Universal ordering + Ordering.andThen + (on ordering secondsSinceEpoch x y) (on ordering nanosecondOfSecond x y) + +time.Instant.compare.doc : Doc +time.Instant.compare.doc = + {{ + `` Instant.compare x y `` returns {Less} if the {type Instant} `x` is before + the {type Instant} `y` {Greater} if `x` is after `y`, or {Equal} if they're + identical. + }} + +time.Instant.doc : Doc +time.Instant.doc = + use Instant != + < <= == > >= now + {{ + An instant of civil time with nanosecond precision. + + {{ + docAside + {{ + This type does not take leap seconds into account. The {type Instant} type + defines a second as exactly 1/86400 of a day, which is not the same as the + SI second on days with leap seconds added or removed. + + If an {type Instant} falls on a day with a leap second in the UTC time + zone, the time represented by the {type Instant} will be off by as much as + 11.57 microseconds from the actual UTC time. + }} }} + + # Construction + + {now} is the current {type Instant} according to the system's real-time + clock: + + @signature{now} + + {epoch} is the {type Instant} representing January 1st 1970 at 00:00 UTC. + + `` fromEpochSeconds s `` is the {type Instant} `s` seconds after the + {epoch}. + + `` fromEpochNanoseconds n `` is the {type Instant} `n` nanoseconds after + the {epoch}. + + `` fromEpochMicroseconds u `` is the {type Instant} `u` microseconds after + the {epoch}. + + `` fromEpochMilliseconds m `` is the {type Instant} `m` milliseconds after + the {epoch}. + + # Adding and removing time + + {+} adds a {type Duration} to an {type Instant}. + + {subtractDuration} subtracts a {type Duration} from an {type Instant}. + + # Comparison + + Various comparison operators are provided for {type Instant}: + + @signature{<} @signature{>} @signature{<=} @signature{>=} + @signature{==} @signature{!=} @signature{Instant.compare} + + # Conversion + + You can convert an {type Instant} to an {type Int} in various ways: + + @signature{nanosecondsSinceEpoch} + @signature{microsecondsSinceEpoch} + @signature{millisecondsSinceEpoch} @signature{secondsSinceEpoch} + + You can convert an {type Instant} to a {type Duration}: + + @signature{Instant.timeSinceEpoch} @signature{between} + + By adding a {type UTCOffset} to an {type Instant}, you can convert an + {type Instant} to {type OffsetDateTime}: + + @signature{Instant.atOffset} + + A convenience function creates an {type OffsetDateTime} at UTC (offset 0): + + @signature{atUTC} + + For example, you can render an {type Instant} as {type Text} via the + {type OffsetDateTime} type: + + ``` + OffsetDateTime.toText (atUTC epoch) + ``` + }} + +time.Instant.epoch : Instant +time.Instant.epoch = Instant +0 0 + +time.Instant.epoch.doc : Doc +time.Instant.epoch.doc = + {{ + {epoch} is the {type Instant} that represents the Unix {epoch} {{ + docWord (OffsetDateTime.toText (atUTC epoch)) }} + }} + +time.Instant.floor : Duration -> Instant ->{Exception} Instant +time.Instant.floor d i = + di = Instant.timeSinceEpoch i + subtractDuration i (Duration.mod di d) + +time.Instant.floor.doc : Doc +time.Instant.floor.doc = + use Instant floor + {{ + `` floor d i `` rounds the given {type Instant} `i` down to the nearest + multiple of the given {type Duration} `d` (relative to the epoch). + + # Example + + This example rounds the given {type Instant} down to the nearest multiple + of 5 seconds on the clock: + + ``` + catch do Instant.toText (floor (seconds +5) (fromEpochSeconds +12)) + ``` + }} + +time.Instant.fromEpochMicroseconds : Int -> Instant +time.Instant.fromEpochMicroseconds i = + use Nat * + Instant (ediv i +1000000) (Int.emod i +1000000 * 1000) + +time.Instant.fromEpochMicroseconds.doc : Doc +time.Instant.fromEpochMicroseconds.doc = + {{ + {Instant.fromEpochMicroseconds i} returns a new {type Instant} that's the + {type Instant} that represents the Unix {epoch}, plus the number of + microseconds `i` (or less if `i` is negative). + + # Example + + ``` + OffsetDateTime.toText (atUTC (fromEpochMicroseconds -14159040000000)) + ``` + }} + +time.Instant.fromEpochMilliseconds : Int -> Instant +time.Instant.fromEpochMilliseconds ms = + use Nat * + Instant (ediv ms +1000) (Int.emod ms +1000 * 1000000) + +time.Instant.fromEpochMilliseconds.doc : Doc +time.Instant.fromEpochMilliseconds.doc = + {{ + {Instant.fromEpochMilliseconds i} returns a new {type Instant} that's the + {type Instant} that represents the Unix {epoch}, plus the number of + milliseconds `i` (or less if `i` is negative). + + # Example + + ``` + OffsetDateTime.toText (atUTC (fromEpochMilliseconds -14159040000)) + ``` + }} + +time.Instant.fromEpochNanoseconds : Int -> Instant +time.Instant.fromEpochNanoseconds n = + Instant (ediv n +1000000000) (Int.emod n +1000000000) + +time.Instant.fromEpochNanoseconds.doc : Doc +time.Instant.fromEpochNanoseconds.doc = + {{ + {Instant.fromEpochNanoseconds i} returns a new {type Instant} that's the + {type Instant} that represents the Unix {epoch}, plus the number of + nanoseconds `i` (or less if `i` is negative). + + # Example + + ``` + OffsetDateTime.toText (atUTC (fromEpochNanoseconds -14159040000000000)) + ``` + }} + +time.Instant.fromEpochSeconds : Int -> Instant +time.Instant.fromEpochSeconds s = Instant s 0 + +time.Instant.fromEpochSeconds.doc : Doc +time.Instant.fromEpochSeconds.doc = + use OffsetDateTime toText + {{ + `` fromEpochSeconds i `` returns a new {type Instant} that represents the + Unix {epoch} plus the number of seconds `i` (note that `i` is an {type Int} + and so can be negative to represent instants before the epoch. + + # Examples + + ``` + toText (atUTC (fromEpochSeconds -14159040)) + ``` + + ``` + toText (atUTC (fromEpochSeconds +1600159040)) + ``` + }} + +time.Instant.fromIso8601 : Text -> Optional Instant +time.Instant.fromIso8601 t = + OffsetDateTime.fromIso8601 t |> Optional.map toInstant + +time.Instant.fromIso8601.doc : Doc +time.Instant.fromIso8601.doc = + {{ + Converts an ISO 8601 date-time string to an {type Instant}. + + The {type Text} must be in the format + `YY-MM-DDThh:mm:ss[.nnnnnnnnn][Z|[+|-]hh[:mm]]` + + # Example + + ``` + Optional.map atUTC (Instant.fromIso8601 "2020-02-14T03:44:55Z") + ``` + }} + +time.Instant.internal.dayOfYearToMonthAndDay : Boolean -> Nat -> (Nat, Nat) +time.Instant.internal.dayOfYearToMonthAndDay isLeap dayOfYear = + use List.Nonempty toList + use Nat + - <= + monthLengths' = monthLengths isLeap + monthLengths'' = Nonempty.scanLeft (+) monthLengths' + month = + List.size (List.takeWhile (x -> x <= dayOfYear) (toList monthLengths'')) + + 1 + day = + dayOfYear + - List.foldLeft (+) 0 (List.take (month - 1) (toList monthLengths')) + + 1 + (month, day) + +time.Instant.internal.dayOfYearToMonthAndDay.doc : Doc +time.Instant.internal.dayOfYearToMonthAndDay.doc = + {{ + `` dayOfYearToMonthAndDay isLeap dayOfYear `` returns the month and day + corresponding to the given day of the year. The {type Boolean} isLeap + indicates whether the given year is a leap year. + + The convention is that January 1 is day 1, and December 31 is day 365 (or 366 + in a leap year). If + + # Examples + + ``` + dayOfYearToMonthAndDay false 1 + ``` + + ``` + dayOfYearToMonthAndDay false 59 + ``` + + ``` + dayOfYearToMonthAndDay false 60 + ``` + + ``` + dayOfYearToMonthAndDay true 60 + ``` + + If the given day of the year is out of range, the result is either the + zeroth day of January, if the given day is ``0``: + + ``` + dayOfYearToMonthAndDay true 0 + ``` + + Or a day of the 13th month if the given day is after December 31: + + ``` + dayOfYearToMonthAndDay false 367 + ``` + }} + +time.Instant.microsecondsSinceEpoch : Instant -> Int +time.Instant.microsecondsSinceEpoch = + countMicroseconds << Instant.timeSinceEpoch + +time.Instant.microsecondsSinceEpoch.doc : Doc +time.Instant.microsecondsSinceEpoch.doc = + {{ + {Instant.microsecondsSinceEpoch i} returns the number of whole microseconds + since the Unix {epoch} that the {type Instant} `i` represents. + + # Example + + ``` + microsecondsSinceEpoch (fromEpochSeconds -14159040) + ``` + }} + +time.Instant.millisecondsSinceEpoch : Instant -> Int +time.Instant.millisecondsSinceEpoch = + countMilliseconds << Instant.timeSinceEpoch + +time.Instant.millisecondsSinceEpoch.doc : Doc +time.Instant.millisecondsSinceEpoch.doc = + {{ + {Instant.millisecondsSinceEpoch i} returns the number of whole milliseconds + since the Unix {epoch} that the {type Instant} `i` represents. + + # Example + + ``` + millisecondsSinceEpoch (fromEpochSeconds -14159040) + ``` + }} + +time.Instant.nanosecondOfSecond : Instant -> Nat +time.Instant.nanosecondOfSecond = cases + Instant _ nanosecondOfSecond -> nanosecondOfSecond + +time.Instant.nanosecondOfSecond.doc : Doc +time.Instant.nanosecondOfSecond.doc = + {{ + The nanosecond component of the {type Instant}. + + # Example + + ``` + Instant.toText &&& nanosecondOfSecond <| fromEpochNanoseconds maxInt + ``` + }} + +time.Instant.nanosecondOfSecond.modify : + (Nat ->{g} Nat) -> Instant ->{g} Instant +time.Instant.nanosecondOfSecond.modify f = cases + Instant secondsSinceEpoch nanosecondOfSecond -> + Instant secondsSinceEpoch (f nanosecondOfSecond) + +time.Instant.nanosecondOfSecond.modify.doc : Doc +time.Instant.nanosecondOfSecond.modify.doc = + {{ + Modifies the nanosecond component of a {type Instant}, keeping the seconds + component unchanged. + }} + +time.Instant.nanosecondOfSecond.set : Nat -> Instant -> Instant +time.Instant.nanosecondOfSecond.set nanosecondOfSecond1 = cases + Instant secondsSinceEpoch _ -> Instant secondsSinceEpoch nanosecondOfSecond1 + +time.Instant.nanosecondOfSecond.set.doc : Doc +time.Instant.nanosecondOfSecond.set.doc = + {{ + Sets the nanosecond component of a {type Instant}, keeping the seconds + component unchanged. + }} + +time.Instant.nanosecondsSinceEpoch : Instant -> Int +time.Instant.nanosecondsSinceEpoch i = + use Int * + + secondsSinceEpoch i * +1000000000 + Nat.toInt (nanosecondOfSecond i) + +time.Instant.nanosecondsSinceEpoch.doc : Doc +time.Instant.nanosecondsSinceEpoch.doc = + {{ + {Instant.nanosecondsSinceEpoch i} returns the number of whole nanoseconds + since the Unix {epoch} that the {type Instant} `i` represents. + + # Example + + ``` + nanosecondsSinceEpoch (fromEpochSeconds -14159040) + ``` + }} + +time.Instant.now : '{IO, Exception} Instant +time.Instant.now = do + t = Either.toException realtime.impl() + Instant (sec t) (nsec t) + +time.Instant.now.doc : Doc +time.Instant.now.doc = + {{ Gets the current real-world time according to the system clock. }} + +time.Instant.round : Duration -> Instant ->{Exception} Instant +time.Instant.round d i = + use Duration - / < + use Instant + + di = Instant.timeSinceEpoch i + r = Duration.mod di d + i + (if r < d / +2 then Duration.negate r else d - r) + +time.Instant.round.doc : Doc +time.Instant.round.doc = + use Instant round toText + {{ + `` round d i `` rounds the given {type Instant} `i` up or down to the nearest + multiple of the given {type Duration} `d` (relative to the epoch). + + # Examples + + ``` + catch do toText (round (seconds +5) (fromEpochSeconds +12)) + ``` + + ``` + catch do toText (round (seconds +5) (fromEpochSeconds +13)) + ``` + + ``` + catch do toText (round (seconds +5) (fromEpochSeconds +15)) + ``` + }} + +time.Instant.secondsSinceEpoch : Instant -> Int +time.Instant.secondsSinceEpoch = cases + Instant secondsSinceEpoch _ -> secondsSinceEpoch + +time.Instant.secondsSinceEpoch.doc : Doc +time.Instant.secondsSinceEpoch.doc = + {{ + The number of seconds since the + [Unix epoch](https://en.wikipedia.org/wiki/Unix_time). represented by the + {type Instant}. + }} + +time.Instant.secondsSinceEpoch.modify : + (Int ->{g} Int) -> Instant ->{g} Instant +time.Instant.secondsSinceEpoch.modify f = cases + Instant secondsSinceEpoch nanosecondOfSecond -> + Instant (f secondsSinceEpoch) nanosecondOfSecond + +time.Instant.secondsSinceEpoch.modify.doc : Doc +time.Instant.secondsSinceEpoch.modify.doc = + {{ + Modifies the seconds since the epoch of a {type Instant}, keeping the + nanoseconds component unchanged. + }} + +time.Instant.secondsSinceEpoch.set : Int -> Instant -> Instant +time.Instant.secondsSinceEpoch.set secondsSinceEpoch1 = cases + Instant _ nanosecondOfSecond -> Instant secondsSinceEpoch1 nanosecondOfSecond + +time.Instant.secondsSinceEpoch.set.doc : Doc +time.Instant.secondsSinceEpoch.set.doc = + {{ + Sets the seconds since the epoch of a {type Instant}, keeping the nanoseconds + component unchanged. + }} + +time.Instant.timeSinceEpoch : Instant -> Duration +time.Instant.timeSinceEpoch = cases Instant s n -> Duration s n + +time.Instant.timeSinceEpoch.doc : Doc +time.Instant.timeSinceEpoch.doc = + use Duration toText + use Instant timeSinceEpoch + {{ + `` timeSinceEpoch i `` returns a {type Duration} representing the time + between the Unix {epoch} and the {type Instant} `i`. This {type Duration} is + negative if `i` is before the {epoch}. + + # Examples + + ``` + toText (timeSinceEpoch (fromEpochSeconds +14159040)) + ``` + + ``` + toText (timeSinceEpoch (fromEpochSeconds -14159040)) + ``` + }} + +time.Instant.toBasicISO8601 : Instant -> Text +time.Instant.toBasicISO8601 = atUTC >> OffsetDateTime.toBasicISO8601 + +time.Instant.toBasicISO8601.doc : Doc +time.Instant.toBasicISO8601.doc = + {{ + Converts an {type Instant} to a {type Text} in the format + `YYYYMMDDThhmmss[.nnnnnnnnn][[+|-]hh[mm]]`. + + # Example + + ``` + Instant.toBasicISO8601 epoch + ``` + }} + +time.Instant.toRFC1123 : Instant -> Text +time.Instant.toRFC1123 = atUTC >> OffsetDateTime.toRFC1123 + +time.Instant.toRFC1123.doc : Doc +time.Instant.toRFC1123.doc = + {{ + Formats an {type Instant} as an RFC 1123 date and time string. + + # Example + + ``` + Instant.toRFC1123 epoch + ``` + }} + +time.Instant.toRFC2822 : Instant -> Text +time.Instant.toRFC2822 = atUTC >> OffsetDateTime.toRFC2822 + +time.Instant.toRFC2822.doc : Doc +time.Instant.toRFC2822.doc = + {{ + Formats an {type Instant} as an RFC 2822 date and time string in the UTC time + zone. + + # Example + + ``` + Instant.toRFC2822 epoch + ``` + }} + +time.Instant.toText : Instant -> Text +time.Instant.toText i = Instant.atOffset i UTC |> OffsetDateTime.toText + +time.Instant.toText.doc : Doc +time.Instant.toText.doc = + {{ + Converts the {type Instant} to its {type Text} representation in the + [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format, assuming UTC time + zone. + + # Example + + ``` + Instant.toText (fromEpochSeconds +0) + ``` + }} + +time.Instant.truncateToCenturies : Instant -> Instant +time.Instant.truncateToCenturies = cases + Instant seconds nanos -> + subtractDuration + (Instant (ediv seconds +3155760000 Int.* +3155760000) 0) + (+25567 Duration.* Duration.day) + +time.Instant.truncateToCenturies.doc : Doc +time.Instant.truncateToCenturies.doc = + {{ + `` truncateToCenturies i `` returns a new {type Instant} that's the + {type Instant} `i` truncated to the nearest century. + + # Example + + ``` + OffsetDateTime.toText (atUTC (truncateToCenturies epoch)) + ``` + }} + +time.Instant.truncateToDays : Instant -> Instant +time.Instant.truncateToDays = cases + Instant seconds nanos -> Instant (ediv seconds +86400 Int.* +86400) 0 + +time.Instant.truncateToDays.doc : Doc +time.Instant.truncateToDays.doc = + {{ + `` truncateToDays i `` returns a new {type Instant} that's the {type Instant} + `i` truncated to the nearest day. + + # Example + + ``` + OffsetDateTime.toText (atUTC (truncateToDays (fromEpochSeconds -14159040))) + ``` + }} + +time.Instant.truncateToHours : Instant -> Instant +time.Instant.truncateToHours = cases + Instant seconds nanos -> Instant (ediv seconds +3600 Int.* +3600) 0 + +time.Instant.truncateToHours.doc : Doc +time.Instant.truncateToHours.doc = + {{ + `` truncateToHours i `` returns a new {type Instant} that's the + {type Instant} `i` truncated to the nearest hour. + + # Example + + ``` + OffsetDateTime.toText + (atUTC (truncateToHours (fromEpochSeconds -14159040))) + ``` + }} + +time.Instant.truncateToMicroseconds : Instant -> Instant +time.Instant.truncateToMicroseconds = cases + Instant seconds nanos -> Instant seconds (nanos Nat./ 1000 Nat.* 1000) + +time.Instant.truncateToMicroseconds.doc : Doc +time.Instant.truncateToMicroseconds.doc = + {{ + `` truncateToMicroseconds i `` returns a new {type Instant} that's the + {type Instant} `i` truncated to the nearest microsecond. + + # Example + + ``` + OffsetDateTime.toText + (atUTC + (truncateToMicroseconds (subtractDuration epoch Duration.nanosecond))) + ``` + }} + +time.Instant.truncateToMillennia : Instant -> Instant +time.Instant.truncateToMillennia = cases + Instant seconds nanos -> + subtractDuration + (Instant (ediv seconds +3153600000000 Int.* +3153600000000) 0) + (+354285 Duration.* Duration.day) + +time.Instant.truncateToMillennia.doc : Doc +time.Instant.truncateToMillennia.doc = + {{ + `` truncateToMillennia i `` returns a new {type Instant} that's the + {type Instant} `i` truncated to the nearest millennium. + + # Example + + ``` + OffsetDateTime.toText (atUTC (truncateToMillennia epoch)) + ``` + }} + +time.Instant.truncateToMilliseconds : Instant -> Instant +time.Instant.truncateToMilliseconds = cases + Instant seconds nanos -> Instant seconds (nanos Nat./ 1000000 Nat.* 1000000) + +time.Instant.truncateToMilliseconds.doc : Doc +time.Instant.truncateToMilliseconds.doc = + {{ + `` truncateToMilliseconds i `` returns a new {type Instant} that's the + {type Instant} `i` truncated to the nearest millisecond. + + # Example + + ``` + OffsetDateTime.toText + (atUTC + (truncateToMilliseconds (subtractDuration epoch Duration.nanosecond))) + ``` + }} + +time.Instant.truncateToMinutes : Instant -> Instant +time.Instant.truncateToMinutes = cases + Instant seconds nanos -> Instant (ediv seconds +60 Int.* +60) 0 + +time.Instant.truncateToMinutes.doc : Doc +time.Instant.truncateToMinutes.doc = + {{ + `` truncateToMinutes i `` returns a new {type Instant} that's the + {type Instant} `i` truncated to the nearest minute. + + # Example + + ``` + OffsetDateTime.toText + (atUTC (truncateToMinutes (subtractDuration epoch Duration.nanosecond))) + ``` + }} + +time.Instant.truncateToSeconds : Instant -> Instant +time.Instant.truncateToSeconds = cases Instant seconds _ -> Instant seconds 0 + +time.Instant.truncateToSeconds.doc : Doc +time.Instant.truncateToSeconds.doc = + {{ + `` truncateToSeconds i `` returns a new {type Instant} that's the + {type Instant} `i` truncated to the nearest second. + + # Example + + ``` + OffsetDateTime.toText + (atUTC (truncateToSeconds (subtractDuration epoch Duration.nanosecond))) + ``` + }} + +time.Instant.truncateToYears : Instant -> Instant +time.Instant.truncateToYears = cases + Instant seconds nanos -> Instant (ediv seconds +31536000 Int.* +31536000) 0 + +time.Instant.truncateToYears.doc : Doc +time.Instant.truncateToYears.doc = + {{ + `` truncateToYears i `` returns a new {type Instant} that's the + {type Instant} `i` truncated to the nearest year. + + # Example + + ``` + OffsetDateTime.toText + (atUTC (truncateToYears (subtractDuration epoch Duration.nanosecond))) + ``` + }} + +time.isLeapYear : Int -> Boolean +time.isLeapYear y = + use Int != == mod + mod y +4 == +0 && (mod y +100 != +0 || mod y +400 == +0) + +time.isLeapYear.doc : Doc +time.isLeapYear.doc = + {{ + Returns `` true `` if the given year is a leap year and `` false `` + otherwise. + + # Example + + ``` + List.map (y -> (y, isLeapYear y)) (Int.range +1978 +1986) + ``` + }} + +time.LocalDate.addDuration : LocalDate -> Duration -> LocalDate +time.LocalDate.addDuration date d = + LocalDateTime.date (LocalDateTime.addDuration (atStartOfDay date) d) + +time.LocalDate.addDuration.doc : Doc +time.LocalDate.addDuration.doc = + {{ + Add a {type Duration} to a {type LocalDate}. + + The result is a {type LocalDate} shifted forward or backward in time by the + {type Duration} (which may be negative). + + # Example + + ``` + LocalDate.toText (LocalDate.addDuration (LocalDate +2020 2 21) week) + ``` + }} + +time.LocalDate.atTime : LocalDate -> LocalTime -> LocalDateTime +time.LocalDate.atTime d t = LocalDateTime d t + +time.LocalDate.atTime.doc : Doc +time.LocalDate.atTime.doc = + {{ + Creates a {type LocalDateTime} from a {type LocalDate} and a + {type LocalTime}. + + # Example + + ``` + LocalDateTime.toText (atTime (LocalDate +2022 10 11) (LocalTime 21 2 12 0)) + ``` + }} + +time.LocalDate.current : '{IO, Exception} LocalDate +time.LocalDate.current = do LocalDateTime.date LocalDateTime.current() + +time.LocalDate.current.doc : Doc +time.LocalDate.current.doc = + {{ + Returns the current {type LocalDate} in the configured system time zone. + }} + +time.LocalDate.day.doc : Doc +time.LocalDate.day.doc = {{ The day of the month, from 1 to 31. }} + +time.LocalDate.day.set.doc : Doc +time.LocalDate.day.set.doc = + {{ + Sets the day component of a {type LocalDate}. + + # Example + + ``` + day.set 12 (LocalDate +2022 10 11) + ``` + }} + +time.LocalDate.dayOfWeek : LocalDate -> DayOfWeek +time.LocalDate.dayOfWeek = cases + LocalDate year month day -> + use Int * - / emod + use Nat < toInt + m = toInt (if month < 3 then month Nat.+ 12 else month) + y = if month < 3 then year - +1 else year + k = toInt (emod y +100) + j = y / +100 + f = + toInt day Int.+ +13 * (m Int.+ +1) / +5 Int.+ k Int.+ k / +4 Int.+ j / +4 + - +2 * j + DayOfWeek.number (emod f +7) + +time.LocalDate.dayOfWeek.doc : Doc +time.LocalDate.dayOfWeek.doc = + use LocalDate dayOfWeek + {{ + Returns the day of the week for the given date. + + # Examples + + ``` + dayOfWeek (LocalDate +1985 10 26) + ``` + + ``` + dayOfWeek (LocalDate +1955 11 5) + ``` + + ``` + dayOfWeek (LocalDate +2015 10 21) + ``` + + ``` + dayOfWeek (LocalDate +1885 9 2) + ``` + }} + +time.LocalDate.doc : Doc +time.LocalDate.doc = + use LocalDate addDuration dayOfWeek fromIso8601 toBasicISO8601 toText + {{ + {type LocalDate} represents a calendar date, without any time zone + information. + + `` LocalDate year month day `` constructs a {type LocalDate} with the + specified year, month and day-of-month: + + ``` + LocalDate +2023 6 14 + ``` + + `` LocalDate.year d `` returns the year of the {type LocalDate} `d`: + + ``` + LocalDate.year (LocalDate +2023 6 14) + ``` + + `` LocalDate.month d `` returns the month of the {type LocalDate} `d`: + + ``` + LocalDate.month (LocalDate +2023 6 14) + ``` + + `` LocalDate.day d `` returns the day of the {type LocalDate} `d`. + + ``` + LocalDate.day (LocalDate +2023 6 14) + ``` + + `` toText d `` returns a {type Text} representation of the date in ISO-8601 + format. + + ``` + toText (LocalDate +2023 6 14) + ``` + + {toBasicISO8601} returns a {type Text} representation in the "basic" ISO-8601 + format: + + ``` + toBasicISO8601 (LocalDate +2023 6 14) + ``` + + {fromIso8601} parses an ISO-8601 {type Text} to a a {type LocalDate}: + + ``` + fromIso8601 "2023-06-14" + ``` + + `` addDuration date dur `` adds a {type Duration} `dur` to the + {type LocalDate} `date`. The duration can be positive or negative: + + ``` + addDuration (LocalDate +2023 6 14) Duration.day + ``` + + `` atTime d t `` combines a {type LocalDate} `d` with a {type LocalTime} `t` + to create a {type LocalDateTime}: + + ``` + atTime (LocalDate +2023 6 14) (LocalTime 12 18 0 0) + ``` + + {atStartOfDay} constructs a {type LocalDateTime} with the specified date and + a time of 00:00: + + ``` + atStartOfDay (LocalDate +2023 6 14) + ``` + + `` dayOfWeek d `` returns the {type DayOfWeek} of the date `d`: + + ``` + dayOfWeek (LocalDate +2023 6 14) + ``` + }} + +time.LocalDate.fromBasicISO8601 : Text -> Optional LocalDate +time.LocalDate.fromBasicISO8601 t = toOptional! do + if Text.size t Nat.== 8 then + use Optional toAbort + use Text drop take + year = toAbort (Int.fromText (take 4 t)) + month = toAbort (Nat.fromText (take 2 (drop 4 t))) + day = toAbort (Nat.fromText (drop 6 t)) + LocalDate year month day + else abort + +time.LocalDate.fromBasicISO8601.doc : Doc +time.LocalDate.fromBasicISO8601.doc = + {{ + Converts a {type Text} in the format `YYYYMMDD` to a {type LocalDate}. + + # Example + + ``` + LocalDate.fromBasicISO8601 "20210207" + ``` + }} + +time.LocalDate.fromIso8601 : Text -> Optional LocalDate +time.LocalDate.fromIso8601 text = + toOptional! do + match Optional.toAbort (Pattern.run iso8601Date text) with + ([year, month, day], _) -> + LocalDate + (Optional.toAbort (Int.fromText year)) + (Optional.toAbort (Nat.fromText month)) + (Optional.toAbort (Nat.fromText day)) + x -> abort + +time.LocalDate.fromIso8601.doc : Doc +time.LocalDate.fromIso8601.doc = + {{ + Converts an ISO 8601 date string to a {type LocalDate}. + + The {type Text} must be in the format `YYYY-MM-DD`. + + # Example + + ``` + LocalDate.fromIso8601 "2021-02-07" + ``` + }} + +time.LocalDate.fromRFC7231 : Text ->{Exception} LocalDate +time.LocalDate.fromRFC7231 text = + match Pattern.run rfc7231Date text with + Some ([weekday, day, month, year], _) -> + use Int + < + month' = match month with + "Jan" -> 1 + "Feb" -> 2 + "Mar" -> 3 + "Apr" -> 4 + "May" -> 5 + "Jun" -> 6 + "Jul" -> 7 + "Aug" -> 8 + "Sep" -> 9 + "Oct" -> 10 + "Nov" -> 11 + "Dec" -> 12 + _ -> raiseGeneric "Invalid month" (typeLink Generic) month + day' = toGenericExceptionWith "Invalid day" day (Nat.fromText day) + year' = toGenericExceptionWith "Invalid year" year (Int.fromText year) + year'' = + if year' < +100 then year' + (if year' < +50 then +2000 else +1900) + else year' + LocalDate year' month' day' + _ -> raiseGeneric "Invalid RFC7231 date" (typeLink Generic) text + +time.LocalDate.fromRFC7231.doc : Doc +time.LocalDate.fromRFC7231.doc = + {{ + Parses a date in the RFC 7231 format. + + # Example + + ``` + catch do LocalDate.fromRFC7231 "Sun, 06-Nov-94" + ``` + }} + +time.LocalDate.month.doc : Doc +time.LocalDate.month.doc = {{ The month of the year, from 1 to 12. }} + +time.LocalDate.month.set.doc : Doc +time.LocalDate.month.set.doc = + {{ + Sets the month component of a {type LocalDate}. + + # Example + + ``` + month.set 11 (LocalDate +2022 10 11) + ``` + }} + +time.LocalDate.toBasicISO8601 : LocalDate -> Text +time.LocalDate.toBasicISO8601 = cases + LocalDate year month day -> + Text.join + "" + [ leftPad 4 "0" (Int.toText year) + , leftPad 2 "0" (Nat.toText month) + , leftPad 2 "0" (Nat.toText day) + ] + +time.LocalDate.toBasicISO8601.doc : Doc +time.LocalDate.toBasicISO8601.doc = + {{ + Converts a {type LocalDate} to a {type Text} in the format `YYYYMMDD`. + + # Example + + ``` + LocalDate.toBasicISO8601 (LocalDate +2021 2 7) + ``` + }} + +time.LocalDate.toText : LocalDate -> Text +time.LocalDate.toText = cases + LocalDate year month day -> + Text.join + "-" + [ leftPad 4 "0" (Int.toText year) + , leftPad 2 "0" (Nat.toText month) + , leftPad 2 "0" (Nat.toText day) + ] + +time.LocalDate.toText.doc : Doc +time.LocalDate.toText.doc = + {{ A rendering of {type LocalDate} in a human-readable ISO-8601 format. }} + +time.LocalDate.year.doc : Doc +time.LocalDate.year.doc = + {{ Returns the year of the given {type LocalDate}, as an {type Int}. }} + +time.LocalDate.year.set.doc : Doc +time.LocalDate.year.set.doc = + {{ + Sets the year component of a {type LocalDate}. + + # Example + + ``` + year.set +2023 (LocalDate +2022 10 11) + ``` + }} + +time.LocalDateTime.addDuration : LocalDateTime -> Duration -> LocalDateTime +time.LocalDateTime.addDuration t d = + localDateTime (OffsetDateTime.addDuration (LocalDateTime.atOffset t UTC) d) + +time.LocalDateTime.addDuration.doc : Doc +time.LocalDateTime.addDuration.doc = + {{ + Add a {type Duration} to a {type LocalDateTime}. + + The result is a {type LocalDateTime} shifted forward or backward in time by + the {type Duration} (which may be negative). + + # Example + + ``` + LocalDateTime.toText + (LocalDateTime.addDuration + (LocalDateTime (LocalDate +2020 2 21) (LocalTime 0 0 0 0)) week) + ``` + }} + +time.LocalDateTime.atOffset : LocalDateTime -> UTCOffset -> OffsetDateTime +time.LocalDateTime.atOffset t offset = OffsetDateTime offset t + +time.LocalDateTime.atOffset.doc : Doc +time.LocalDateTime.atOffset.doc = + {{ + Create an {type OffsetDateTime} from a {type LocalDateTime} and + {type UTCOffset}. + + # Example + + ``` + OffsetDateTime.toText + (LocalDateTime.atOffset + (LocalDateTime (LocalDate +2019 1 1) (LocalTime 0 0 0 0)) UTC) + ``` + }} + +time.LocalDateTime.atStartOfDay : LocalDate -> LocalDateTime +time.LocalDateTime.atStartOfDay d = LocalDateTime d (LocalTime 0 0 0 0) + +time.LocalDateTime.atStartOfDay.doc : Doc +time.LocalDateTime.atStartOfDay.doc = + {{ + Creates a {type LocalDateTime} at midnight of the given {type LocalDate}. + }} + +time.LocalDateTime.current : '{IO, Exception} LocalDateTime +time.LocalDateTime.current = do localDateTime OffsetDateTime.current() + +time.LocalDateTime.current.doc : Doc +time.LocalDateTime.current.doc = + {{ + Returns the current {type LocalDateTime} in the configured system time zone. + }} + +time.LocalDateTime.date.doc : Doc +time.LocalDateTime.date.doc = + {{ + The date component of a {type LocalDateTime}. + + # Example + + ``` + LocalDateTime.date + (LocalDateTime (LocalDate +2022 10 11) (LocalTime 21 2 12 0)) + ``` + }} + +time.LocalDateTime.date.modify.doc : Doc +time.LocalDateTime.date.modify.doc = + {{ Modifies the {type LocalDate} of a {type LocalDateTime}. }} + +time.LocalDateTime.date.set.doc : Doc +time.LocalDateTime.date.set.doc = + {{ + Sets the {type LocalDate} of a {type LocalDateTime}. + + # Example + + ``` + date.set + (LocalDate +2020 12 31) + (LocalDateTime (LocalDate +2020 1 1) (LocalTime 0 0 0 0)) + ``` + }} + +time.LocalDateTime.dayOfWeek : LocalDateTime -> DayOfWeek +time.LocalDateTime.dayOfWeek = cases LocalDateTime d t -> LocalDate.dayOfWeek d + +time.LocalDateTime.dayOfWeek.doc : Doc +time.LocalDateTime.dayOfWeek.doc = + use LocalDateTime dayOfWeek fromIso8601 + use Optional map + {{ + Returns the day of the week for the given date and time. + + # Examples + + ``` + map dayOfWeek (fromIso8601 "1985-10-26T01:21:00") + ``` + + ``` + map dayOfWeek (fromIso8601 "1955-11-05T06:00:00") + ``` + + ``` + map dayOfWeek (fromIso8601 "2015-10-21T13:00:00") + ``` + + ``` + map dayOfWeek (fromIso8601 "1885-09-02T08:00:00") + ``` + }} + +time.LocalDateTime.doc : Doc +time.LocalDateTime.doc = + use LocalDateTime addDuration atOffset dayOfWeek fromIso8601 time toBasicISO8601 toText + {{ + {type LocalDateTime} represents a date and time, without any time zone + information. + + `` LocalDateTime localDate localTime `` constructs a {type LocalDateTime} + with the specified date and time: + + ``` + LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 25 23 0) + ``` + + {fromIso8601} constructs a {type LocalDateTime} from its ISO-8601 {type Text} + representation: + + ``` + fromIso8601 "2023-06-14T12:25:23" + ``` + + `` addDuration dt t `` adds a {type Duration} `t` to the {type LocalDateTime} + `dt`. The duration can be positive or negative: + + ``` + addDuration + (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 25 23 0)) week + ``` + + `` atOffset dt o `` combines a {type LocalDateTime} `dt` with a + {type UTCOffset} `o` to create an {type OffsetDateTime}: + + ``` + atOffset (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 25 23 0)) UTC + ``` + + {atStartOfDay} constructs a {type LocalDateTime} from a {type LocalDate}: + + ``` + atStartOfDay (LocalDate +2023 6 14) + ``` + + `` OffsetDateTime.date d `` returns the {type LocalDate} of the + {type LocalDateTime} `d`: + + ``` + LocalDateTime.date + (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 25 23 0)) + ``` + + `` time d `` returns the {type LocalTime} of the {type LocalDateTime} `d`: + + ``` + time (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 25 23 0)) + ``` + + `` toText `` returns a {type Text} representation of the date and time in + ISO-8601 format: + + ``` + toText (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 25 23 0)) + ``` + + {toBasicISO8601} returns a {type Text} representation in the "basic" ISO-8601 + format: + + ``` + toBasicISO8601 (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 25 23 0)) + ``` + + {dayOfWeek} returns the {type DayOfWeek} of the given {type LocalDateTime}: + + ``` + dayOfWeek (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 25 23 0)) + ``` + }} + +time.LocalDateTime.fromIso8601 : Text -> Optional LocalDateTime +time.LocalDateTime.fromIso8601 t = + toOptional! do + match Optional.toAbort (Pattern.run (Pattern.capture iso8601Date) t) with + ([d], r) -> + use Optional toAbort + date = toAbort (LocalDate.fromIso8601 d) + match toAbort + (Pattern.run + (Pattern.join [literal "T", Pattern.capture iso8601LocalTime]) r) with + ([t], r') -> + time = toAbort (LocalTime.fromIso8601 t) + LocalDateTime date time + _ -> abort + _ -> abort + +time.LocalDateTime.fromIso8601.doc : Doc +time.LocalDateTime.fromIso8601.doc = + {{ + Converts an ISO 8601 date-time string to a {type LocalDateTime}. + + The {type Text} must be in the format `YYYY-MM-DDThh:mm:ss[.nnnnnnnnn]` + + # Example + + ``` + LocalDateTime.fromIso8601 "2021-02-07T03:44:55" + ``` + }} + +time.LocalDateTime.fromRFC7231 : Text ->{Exception} LocalDateTime +time.LocalDateTime.fromRFC7231 text = + match Pattern.run + (Pattern.join + [Pattern.capture rfc7231Date, some space, Pattern.capture rfc7231Time]) + text with + Some ([dt, tm], _) -> + LocalDateTime (LocalDate.fromRFC7231 dt) (LocalTime.fromRFC7231 tm) + _ -> raiseGeneric "Invalid RFC7231 date/time" (typeLink Generic) text + +time.LocalDateTime.fromRFC7231.doc : Doc +time.LocalDateTime.fromRFC7231.doc = + {{ + Parses a date and time in the RFC 7231 format. + + # Example + + ``` + catch do LocalDateTime.fromRFC7231 "Sun, 06-Nov-94 08:49:37" + ``` + }} + +time.LocalDateTime.time.doc : Doc +time.LocalDateTime.time.doc = + {{ + The time component of a {type LocalDateTime}. + + # Example + + ``` + LocalDateTime.time + (LocalDateTime (LocalDate +2022 10 11) (LocalTime 21 2 12 0)) + ``` + }} + +time.LocalDateTime.time.modify.doc : Doc +time.LocalDateTime.time.modify.doc = + {{ Modifies the {type LocalTime} of a {type LocalDateTime}. }} + +time.LocalDateTime.time.set.doc : Doc +time.LocalDateTime.time.set.doc = + {{ + Sets the {type LocalTime} of a {type LocalDateTime}. + + # Example + + ``` + LocalDateTime.time.set + (LocalTime 23 59 59 999999999) + (LocalDateTime (LocalDate +2020 1 1) (LocalTime 0 0 0 0)) + ``` + }} + +time.LocalDateTime.toBasicISO8601 : LocalDateTime -> Text +time.LocalDateTime.toBasicISO8601 = cases + LocalDateTime date time -> + Text.join + "T" [LocalDate.toBasicISO8601 date, LocalTime.toBasicISO8601 time] + +time.LocalDateTime.toBasicISO8601.doc : Doc +time.LocalDateTime.toBasicISO8601.doc = + use LocalDateTime toBasicISO8601 + {{ + Converts a {type LocalDateTime} to a {type Text} in the format + `PYYYYMMDDThhmmss[.nnnnnnnnn]`. + + # Examples + + ``` + toBasicISO8601 (LocalDateTime (LocalDate +2021 2 7) (LocalTime 18 26 47 0)) + ``` + + ``` + toBasicISO8601 + (LocalDateTime (LocalDate +2021 2 7) (LocalTime 18 26 47 123456789)) + ``` + }} + +time.LocalDateTime.toRFC1123AtGMT : LocalDateTime -> Text +time.LocalDateTime.toRFC1123AtGMT = cases + LocalDateTime d@(LocalDate year month day) (LocalTime hour minute second _) -> + use Nat - + use Text ++ + wkDay = LocalDate.dayOfWeek d + dd = leftPad 2 "0" (Nat.toText day) + mon = List.at (month - 1) monthNamesShort |> Optional.getOrElse "???" + yyyy = leftPad 4 "0" (Int.toText year) + hh = leftPad 2 "0" (Nat.toText hour) + mm = leftPad 2 "0" (Nat.toText minute) + ss = leftPad 2 "0" (Nat.toText second) + shortName wkDay ++ ", " ++ dd ++ " " ++ mon ++ " " ++ yyyy ++ " " ++ hh + ++ ":" + ++ mm + ++ ":" + ++ ss + ++ " GMT" + +time.LocalDateTime.toRFC1123AtGMT.doc : Doc +time.LocalDateTime.toRFC1123AtGMT.doc = + {{ + Formats a {type LocalDateTime} as an RFC 1123 date and time string assuming + the GMT time zone. + + # Example + + ``` + toRFC1123AtGMT (LocalDateTime (LocalDate +2022 1 1) (LocalTime 0 0 0 0)) + ``` + }} + +time.LocalDateTime.toText : LocalDateTime -> Text +time.LocalDateTime.toText = cases + LocalDateTime d t -> LocalDate.toText d Text.++ LocalTime.toText t + +time.LocalDateTime.toText.doc : Doc +time.LocalDateTime.toText.doc = + {{ + A rendering of {type LocalDateTime} in a human-readable ISO-8601 format. + }} + +time.LocalTime.addDuration : LocalTime -> Duration -> LocalTime +time.LocalTime.addDuration t d = + OffsetTime.time (OffsetTime.addDuration (LocalTime.atOffset t UTC) d) + +time.LocalTime.addDuration.doc : Doc +time.LocalTime.addDuration.doc = + use LocalTime addDuration toText + {{ + Add a {type Duration} to a {type LocalTime}. + + The result is a {type LocalTime} shifted forward or backward in time by the + {type Duration} (which may be negative). + + # Examples + + ``` + toText (addDuration (LocalTime 21 0 0 0) (minutes +90)) + ``` + + If the result is a time that is after midnight, it will be wrapped around + to the time-of-day the next day. + + ``` + toText (addDuration (LocalTime 21 0 0 0) (hours +9)) + ``` + }} + +time.LocalTime.atOffset : LocalTime -> UTCOffset -> OffsetTime +time.LocalTime.atOffset t offset = OffsetTime offset t + +time.LocalTime.atOffset.doc : Doc +time.LocalTime.atOffset.doc = + {{ + Create an {type OffsetTime} from a {type LocalTime} and {type UTCOffset}. + + # Example + + ``` + LocalTime.atOffset (LocalTime 21 42 0 0) UTC + ``` + }} + +time.LocalTime.current : '{IO, Exception} LocalTime +time.LocalTime.current = do LocalDateTime.time LocalDateTime.current() + +time.LocalTime.current.doc : Doc +time.LocalTime.current.doc = + {{ + Returns the current {type LocalTime} in the configured system time zone. + }} + +time.LocalTime.doc : Doc +time.LocalTime.doc = + {{ + A {type LocalTime} represents a time of day without a date or timezone, to + nanosecond precision. + + # Example + + The time of day in Boston at which this documentation was written: + + ``` + LocalDateTime.time + (localDateTime (fromInstant (fromEpochSeconds +1664670382) AST)) + ``` + + # Construction + + `` LocalTime hour minute second nanosecond `` constructs a {type LocalTime} + with the specified hour, minute, second and nanosecond. + + # Field accessors + + `` LocalTime.hour t `` returns the hour of the {type LocalTime} `t`. + + `` LocalTime.minute t `` returns the minute of the {type LocalTime} `t`. + + `` LocalTime.second t `` returns the second of the {type LocalTime} `t`. + + `` LocalTime.nanosecond t `` returns the nanosecond of the {type LocalTime} + `t`. + + # Conversion + + `` LocalTime.toText t `` returns a {type Text} representation of the time + in ISO-8601 format. + + `` LocalTime.atOffset t o `` combines a {type LocalTime} `t` with a + {type UTCOffset} `o` to create an {type OffsetTime}. + + # Adding and subtracting time + + `` LocalTime.addDuration t d `` adds a {type Duration} `d` to the + {type LocalTime} `t`. The duration can be positive or negative. The time + will wrap around if the duration is large enough. + }} + +time.LocalTime.fromBasicISO8601 : Text -> Optional LocalTime +time.LocalTime.fromBasicISO8601 t = + toOptional! do + match Text.split ?. t with + whole +: frac -> + if Text.size whole Nat.> 6 then abort + else + use Nat > >= fromText + use Optional toAbort + use Text drop size take + hh = toAbort (fromText (take 2 whole)) + mm = + if size whole >= 4 then toAbort (fromText (take 2 (drop 2 whole))) + else 0 + ss = + if size whole >= 6 then toAbort (fromText (take 2 (drop 4 whole))) + else 0 + nano = match frac with + [frac] | size frac > 0 -> toAbort (fromText (take 9 frac)) + _ -> 0 + LocalTime hh mm ss nano + _ -> abort + +time.LocalTime.fromBasicISO8601.doc : Doc +time.LocalTime.fromBasicISO8601.doc = + use LocalTime fromBasicISO8601 + {{ + Converts a {type Text} in the format `hhmmss[.nnnnnnnnn]` to a + {type LocalTime}. + + # Examples + + ``` + fromBasicISO8601 "182647" + ``` + + ``` + fromBasicISO8601 "182647.123456789" + ``` + }} + +time.LocalTime.fromIso8601 : Text -> Optional LocalTime +time.LocalTime.fromIso8601 t = + toOptional! do + match Optional.toAbort (Pattern.run iso8601LocalTime t) with + ([hour, minute, second, fraction], _) -> + LocalTime + (Optional.toAbort (Nat.fromText hour)) + (Optional.toAbort (Nat.fromText minute)) + (Optional.toAbort (Nat.fromText second)) + (Optional.toAbort + (Nat.fromText (Text.take 9 (leftPad 9 "0" fraction)))) + ([hour, minute, second], _) -> + LocalTime + (Optional.toAbort (Nat.fromText hour)) + (Optional.toAbort (Nat.fromText minute)) + (Optional.toAbort (Nat.fromText second)) + 0 + _ -> abort + +time.LocalTime.fromIso8601.doc : Doc +time.LocalTime.fromIso8601.doc = + {{ + Converts an ISO 8601 time string to a {type LocalTime}. + + The {type Text} must be in the format `hh:mm:ss[.nnnnnnnnn]`. + + # Example + + ``` + LocalTime.fromIso8601 "18:26:47" + ``` + }} + +time.LocalTime.fromRFC7231 : Text ->{Exception} LocalTime +time.LocalTime.fromRFC7231 text = + match Pattern.run rfc7231Time text with + Some ([hour, minute, second], _) -> + LocalTime + (toGenericExceptionWith "Invalid hour" hour (Nat.fromText hour)) + (toGenericExceptionWith "Invalid minute" minute (Nat.fromText minute)) + (toGenericExceptionWith "Invalid second" second (Nat.fromText second)) + 0 + e -> raiseGeneric "Invalid RFC7231 time spec" (typeLink Generic) text + +time.LocalTime.fromRFC7231.doc : Doc +time.LocalTime.fromRFC7231.doc = + {{ + Parses a time in the RFC 7231 format. + + # Example + + ``` + catch do LocalTime.fromRFC7231 "18:32:45" + ``` + }} + +time.LocalTime.hour.doc : Doc +time.LocalTime.hour.doc = + {{ Returns the hour of the given {type LocalTime}, in the range 0 to 23. }} + +time.LocalTime.hour.modify.doc : Doc +time.LocalTime.hour.modify.doc = + {{ + Modifies the hour component of a {type LocalTime}. + + # Example + + ``` + hour.modify Nat.increment (LocalTime 12 0 0 0) + ``` + }} + +time.LocalTime.hour.set.doc : Doc +time.LocalTime.hour.set.doc = + {{ + Sets the hour component of a {type LocalTime}. + + # Example + + ``` + hour.set 22 (LocalTime 21 2 12 0) + ``` + }} + +time.LocalTime.minute.doc : Doc +time.LocalTime.minute.doc = + {{ + The minute component of a {type LocalTime}. + + # Example + + ``` + LocalTime.minute (LocalTime 21 2 12 0) + ``` + }} + +time.LocalTime.minute.modify.doc : Doc +time.LocalTime.minute.modify.doc = + {{ + Modifies the minute component of a {type LocalTime}. + + # Example + + ``` + minute.modify Nat.increment (LocalTime 12 0 0 0) + ``` + }} + +time.LocalTime.minute.set.doc : Doc +time.LocalTime.minute.set.doc = + {{ + Sets the minute component of a {type LocalTime}. + + # Example + + ``` + minute.set 3 (LocalTime 21 2 12 0) + ``` + }} + +time.LocalTime.nanosecond.modify.doc : Doc +time.LocalTime.nanosecond.modify.doc = + {{ Modifies the nanosecond component of a {type LocalTime}. }} + +time.LocalTime.nanosecond.set.doc : Doc +time.LocalTime.nanosecond.set.doc = + {{ + Sets the nanosecond component of a {type LocalTime}. + + # Example + + ``` + nanosecond.set 1 (LocalTime 21 2 12 0) + ``` + }} + +time.LocalTime.second.doc : Doc +time.LocalTime.second.doc = + {{ + The second component of a {type LocalTime}. + + # Example + + ``` + LocalTime.second (LocalTime 21 2 12 0) + ``` + }} + +time.LocalTime.second.modify.doc : Doc +time.LocalTime.second.modify.doc = + {{ + Modifies the second component of a {type LocalTime}. + + # Example + + ``` + second.modify Nat.increment (LocalTime 12 0 0 0) + ``` + }} + +time.LocalTime.second.set.doc : Doc +time.LocalTime.second.set.doc = + {{ + Sets the second component of a {type LocalTime}. + + # Example + + ``` + second.set 13 (LocalTime 21 2 12 0) + ``` + }} + +time.LocalTime.toBasicISO8601 : LocalTime -> Text +time.LocalTime.toBasicISO8601 = cases + LocalTime hour minute second nano -> + Text.join + "" + [ leftPad 2 "0" (Nat.toText hour) + , leftPad 2 "0" (Nat.toText minute) + , leftPad 2 "0" (Nat.toText second) + , if nano Nat.== 0 then "" + else + "." + Text.++ Text.dropRightWhile + ((Char.==) ?0) (leftPad 9 "0" (Nat.toText nano)) + ] + +time.LocalTime.toBasicISO8601.doc : Doc +time.LocalTime.toBasicISO8601.doc = + use LocalTime toBasicISO8601 + {{ + Converts a {type LocalTime} to a {type Text} in the format + `hhmmss[.nnnnnnnnn]`. + + # Examples + + ``` + toBasicISO8601 (LocalTime 18 26 47 0) + ``` + + ``` + toBasicISO8601 (LocalTime 18 26 47 123456789) + ``` + }} + +time.LocalTime.toText : LocalTime -> Text +time.LocalTime.toText = cases + LocalTime hour minute second nanosecond -> + Text.join + "" + [ "T" + , leftPad 2 "0" (Nat.toText hour) + , ":" + , leftPad 2 "0" (Nat.toText minute) + , ":" + , leftPad 2 "0" (Nat.toText second) + ] + Text.++ (if nanosecond Nat.== 0 then "" + else + "." + Text.++ Text.dropRightWhile + ((Char.==) ?0) + (leftPad 9 "0" (Nat.toText nanosecond))) + +time.LocalTime.toText.doc : Doc +time.LocalTime.toText.doc = + {{ A rendering of {type LocalTime} in a human-readable ISO-8601 format. }} + +time.monthLengths : Boolean -> List.Nonempty Nat +time.monthLengths isLeap = + 31 +| [if isLeap then 29 else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + +time.monthLengths.doc : Doc +time.monthLengths.doc = + {{ + `` monthLengths isLeap `` returns a {type List.Nonempty} list of length 12, + with the number of days in each corresponding month. The {type Boolean} + isLeap indicates whether the given year is a leap year. + + # Examples + + ``` + monthLengths true + ``` + + ``` + monthLengths false + ``` + }} + +time.monthNames : [Text] +time.monthNames = + [ "January" + , "February" + , "March" + , "April" + , "May" + , "June" + , "July" + , "August" + , "September" + , "October" + , "November" + , "December" + ] + +time.monthNamesShort : [Text] +time.monthNamesShort = + [ "Jan" + , "Feb" + , "Mar" + , "Apr" + , "May" + , "Jun" + , "Jul" + , "Aug" + , "Sep" + , "Oct" + , "Nov" + , "Dec" + ] + +time.OffsetDateTime.addDuration : OffsetDateTime -> Duration -> OffsetDateTime +time.OffsetDateTime.addDuration t d = + use Instant + + instant = toInstant t + d' = instant + d + fromInstant d' (OffsetDateTime.offset t) + +time.OffsetDateTime.addDuration.doc : Doc +time.OffsetDateTime.addDuration.doc = + {{ + Add a {type Duration} to an {type OffsetDateTime}. + + The result is an {type OffsetDateTime} with the same offset as the input, + shifted forward or backward in time by the {type Duration} (which may be + negative). + + # Example + + ``` + OffsetDateTime.toText + (OffsetDateTime.addDuration + (OffsetDateTime + (UTCOffset +0) + (LocalDateTime (LocalDate +2020 2 21) (LocalTime 0 0 0 0))) + week) + ``` + }} + +time.OffsetDateTime.convertOffset : + UTCOffset -> OffsetDateTime -> OffsetDateTime +time.OffsetDateTime.convertOffset = cases + o@(UTCOffset new), OffsetDateTime (UTCOffset old) dt -> + OffsetDateTime o (LocalDateTime.addDuration dt (minutes (new Int.- old))) + +time.OffsetDateTime.convertOffset.doc : Doc +time.OffsetDateTime.convertOffset.doc = + {{ + Converts an {type OffsetDateTime} to the given {type UTCOffset}, accounting + for the time difference. + + # Examples + + 9 PM in New York is 6 PM in Los Angeles: + + ``` + toOptional! do + newYork = + Optional.toAbort (OffsetDateTime.fromIso8601 "1988-12-24T21:00:00-05") + losAngeles = convertOffset (UTC |> subtractHours 8) newYork + OffsetDateTime.toText losAngeles + ``` + }} + +time.OffsetDateTime.current : '{IO, Exception} OffsetDateTime +time.OffsetDateTime.current = do + t = realtime() + o = getOffset t + fromInstant t o + +time.OffsetDateTime.current.doc : Doc +time.OffsetDateTime.current.doc = + {{ + Returns the current {type OffsetDateTime} in the configured system time zone. + }} + +time.OffsetDateTime.date : OffsetDateTime -> LocalDate +time.OffsetDateTime.date = cases + OffsetDateTime _ (LocalDateTime date _) -> date + +time.OffsetDateTime.date.doc : Doc +time.OffsetDateTime.date.doc = + use OffsetDateTime date + {{ + `` date d `` returns the {type LocalDate} of the {type OffsetDateTime} `d`. + + # Example + + ``` + date (fromInstant (fromEpochSeconds +1664670382) AST) + ``` + }} + +time.OffsetDateTime.dayOfWeek : OffsetDateTime -> DayOfWeek +time.OffsetDateTime.dayOfWeek = cases + OffsetDateTime o (LocalDateTime d _) -> LocalDate.dayOfWeek d + +time.OffsetDateTime.dayOfWeek.doc : Doc +time.OffsetDateTime.dayOfWeek.doc = + use OffsetDateTime dayOfWeek fromIso8601 + use Optional map + {{ + Returns the day of the week for the given date and time. + + # Examples + + ``` + map dayOfWeek (fromIso8601 "1985-10-26T01:21:00-07:00") + ``` + + ``` + map dayOfWeek (fromIso8601 "1955-11-05T06:00:00+01:00") + ``` + + ``` + map dayOfWeek (fromIso8601 "2015-10-21T13:00:00Z") + ``` + + ``` + map dayOfWeek (fromIso8601 "1885-09-02T08:00:00+02:00") + ``` + }} + +time.OffsetDateTime.doc : Doc +time.OffsetDateTime.doc = + use OffsetDateTime addDuration dayOfWeek fromIso8601 toBasicISO8601 toText + odt = + OffsetDateTime + AST (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 40 27 0)) + {{ + {type OffsetDateTime} represents a date and time with a time zone offset. + + # Construction + + `` OffsetDateTime offset dateTime `` constructs an {type OffsetDateTime} + from the specified {type UTCOffset} and {type LocalDateTime}: + + ``` + OffsetDateTime + AST (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 40 27 0)) + ``` + + `` fromTimeAndDate time date `` constructs an {type OffsetDateTime} from + the specified {type OffsetTime} and {type LocalDate}: + + ``` + fromTimeAndDate + (OffsetTime AST (LocalTime 12 40 27 0)) (LocalDate +2023 6 14) + ``` + + `` fromInstant instant offset `` constructs an {type OffsetDateTime} from + the specified {type Instant} and {type UTCOffset}: + + ``` + fromInstant epoch UTC + ``` + + {fromIso8601} constructs an {type OffsetDateTime} from its ISO-8601 + {type Text} representation: + + ``` + fromIso8601 "2023-06-14T12:40:27-04" + ``` + + # Field accessors + + `` OffsetDateTime.offset d `` returns the {type UTCOffset} of the + {type OffsetDateTime} `d`: + + ``` + OffsetDateTime.offset + (OffsetDateTime + AST (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 40 27 0))) + ``` + + `` localDateTime d `` strips the {type UTCOffset} from the + {type OffsetDateTime} `d`, returning a {type LocalDateTime}: + + ``` + localDateTime + (OffsetDateTime + AST (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 40 27 0))) + ``` + + `` timeOfDay d `` returns the {type OffsetTime} of the + {type OffsetDateTime} `d`: + + ``` + timeOfDay + (OffsetDateTime + AST (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 40 27 0))) + ``` + + `` OffsetDateTime.date d `` returns the {type LocalDate} of the + {type OffsetDateTime} `d`. + + ``` + OffsetDateTime.date + (OffsetDateTime + AST (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 40 27 0))) + ``` + + {dayOfWeek} returns the {type DayOfWeek} of the {type OffsetDateTime}: + + ``` + dayOfWeek + (OffsetDateTime + AST (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 40 27 0))) + ``` + + # Conversion + + `` toText `` returns a {type Text} representation of the date and time in + ISO-8601 format: + + ``` + toText + (OffsetDateTime + AST (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 40 27 0))) + ``` + + `` toBasicISO8601 `` returns a {type Text} representation in the "basic" + ISO-8601 format: + + ``` + toBasicISO8601 + (OffsetDateTime + AST (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 40 27 0))) + ``` + + `` toInstant d `` returns the {type Instant} corresponding to the + {type OffsetDateTime} `d`: + + ``` + toInstant + (OffsetDateTime + AST (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 40 27 0))) + ``` + + # Adding and subtracting time + + `` addDuration d t `` adds a {type Duration} `t` to the + {type OffsetDateTime} `d`. The duration can be positive or negative: + + ``` + addDuration + (OffsetDateTime + AST (LocalDateTime (LocalDate +2023 6 14) (LocalTime 12 40 27 0))) + week + ``` + }} + +time.OffsetDateTime.fromIso8601 : Text -> Optional OffsetDateTime +time.OffsetDateTime.fromIso8601 t = + toOptional! do + match Optional.toAbort + (Pattern.run (Pattern.capture iso8601LocalDateTime) t) with + ([dt], r) -> + match Optional.toAbort + (Pattern.run (Pattern.capture iso8601Timezone) r) with + ([tz], _) -> + use Optional toAbort + offset = toAbort (UTCOffset.fromIso8601 tz) + dateTime = toAbort (LocalDateTime.fromIso8601 dt) + OffsetDateTime offset dateTime + _ -> abort + _ -> abort + +time.OffsetDateTime.fromIso8601.doc : Doc +time.OffsetDateTime.fromIso8601.doc = + {{ + Converts an ISO 8601 date-time string to an {type OffsetDateTime}. + + The {type Text} must be in the format + `YYYY-MM-DDThh:mm:ss[.nnnnnnnnn][Z|[+|-]hh[:mm]]`. + + # Example + + ``` + OffsetDateTime.fromIso8601 "2021-02-07T03:44:55-04" + ``` + }} + +time.OffsetDateTime.fromRFC1123 : Text ->{Exception} OffsetDateTime +time.OffsetDateTime.fromRFC1123 text = + OffsetDateTime UTC (LocalDateTime.fromRFC7231 text) + +time.OffsetDateTime.fromRFC1123.doc : Doc +time.OffsetDateTime.fromRFC1123.doc = + {{ + Parses a date and time in the HTTP date format specified by + [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231). + + The date and time are assumed to be in UTC. + + If the date and time is not in the correct format, an exception is thrown. + }} + +test> time.OffsetDateTime.fromRFC1123.test = + unsafeRun! do + parsed = fromRFC1123 "Wed, 12 Oct 2022 18:32:45 GMT" + check + (parsed + === OffsetDateTime + GMT + (LocalDateTime (LocalDate +2022 10 12) (LocalTime 18 32 45 0))) + +time.OffsetDateTime.fromRFC2822 : Text ->{Exception} OffsetDateTime +time.OffsetDateTime.fromRFC2822 text = + match Pattern.run rfc2822DateTime text with + Some ([weekday, day, month, year, hour, minute, second, offset], _) -> + use Int + < + month' = match month with + "Jan" -> 1 + "Feb" -> 2 + "Mar" -> 3 + "Apr" -> 4 + "May" -> 5 + "Jun" -> 6 + "Jul" -> 7 + "Aug" -> 8 + "Sep" -> 9 + "Oct" -> 10 + "Nov" -> 11 + "Dec" -> 12 + _ -> raiseGeneric "Invalid month" (typeLink Generic) month + day' = toGenericExceptionWith "Invalid day" day (Nat.fromText day) + year' = toGenericExceptionWith "Invalid year" year (Int.fromText year) + year'' = + if year' < +100 then year' + (if year' < +50 then +2000 else +1900) + else year' + date = LocalDate year' month' day' + time = + LocalTime + (toGenericExceptionWith + "Invalid RFC2822 hour" hour (Nat.fromText hour)) + (toGenericExceptionWith + "Invalid RFC2822 minute" minute (Nat.fromText minute)) + (toGenericExceptionWith + "Invalid RFC2822 second" second (Nat.fromText second)) + 0 + utcOffset = UTCOffset.fromRFC2822 offset + OffsetDateTime utcOffset (LocalDateTime date time) + e -> raiseGeneric "Invalid RFC2822 date" (text, e) + +time.OffsetDateTime.fromRFC2822.doc : Doc +time.OffsetDateTime.fromRFC2822.doc = + {{ + Parses a date and time in the RFC 2822 format. + + # Example + + ``` + catch do OffsetDateTime.fromRFC2822 "Wed, 12 Oct 2022 18:32:45 -0500" + ``` + }} + +test> time.OffsetDateTime.fromRFC2822.test = + unsafeRun! do + parsed = OffsetDateTime.fromRFC2822 "Wed, 12 Oct 2022 18:32:45 -0500" + check + (parsed + === OffsetDateTime + (UTCOffset -300) + (LocalDateTime (LocalDate +2022 10 12) (LocalTime 18 32 45 0))) + +time.OffsetDateTime.fromTimeAndDate : OffsetTime -> LocalDate -> OffsetDateTime +time.OffsetDateTime.fromTimeAndDate t date = + OffsetDateTime (OffsetTime.offset t) (LocalDateTime date (OffsetTime.time t)) + +time.OffsetDateTime.fromTimeAndDate.doc : Doc +time.OffsetDateTime.fromTimeAndDate.doc = + {{ + Create an {type OffsetDateTime} from an {type OffsetTime} and + {type LocalDate}. + + # Example + + ``` + OffsetDateTime.toText + (fromTimeAndDate + (OffsetTime (UTCOffset +0) (LocalTime 21 42 0 0)) (LocalDate +2019 1 1)) + ``` + }} + +time.OffsetDateTime.localDateTime : OffsetDateTime -> LocalDateTime +time.OffsetDateTime.localDateTime = cases OffsetDateTime _ d -> d + +time.OffsetDateTime.localDateTime.doc : Doc +time.OffsetDateTime.localDateTime.doc = + {{ + The {type LocalDateTime} of an {type OffsetDateTime}, without the offset. + }} + +time.OffsetDateTime.offset : OffsetDateTime -> UTCOffset +time.OffsetDateTime.offset = cases OffsetDateTime o _ -> o + +time.OffsetDateTime.offset.doc : Doc +time.OffsetDateTime.offset.doc = + {{ + The {type UTCOffset} of a {type OffsetDateTime}. + + # Example + + ``` + OffsetDateTime.offset + (OffsetDateTime + AST (LocalDateTime (LocalDate +2021 1 1) (LocalTime 12 0 0 0))) + ``` + }} + +time.OffsetDateTime.timeOfDay : OffsetDateTime -> OffsetTime +time.OffsetDateTime.timeOfDay = cases + OffsetDateTime offset (LocalDateTime d t) -> OffsetTime offset t + +time.OffsetDateTime.timeOfDay.doc : Doc +time.OffsetDateTime.timeOfDay.doc = + {{ + Get the {type OffsetTime} part of an {type OffsetDateTime}. + + # Example + + ``` + timeOfDay + (OffsetDateTime + (UTCOffset +0) + (LocalDateTime (LocalDate +2019 1 1) (LocalTime 21 42 0 0))) + ``` + }} + +time.OffsetDateTime.toBasicISO8601 : OffsetDateTime -> Text +time.OffsetDateTime.toBasicISO8601 = cases + OffsetDateTime offset dateTime -> + LocalDateTime.toBasicISO8601 dateTime + Text.++ UTCOffset.toBasicISO8601 offset + +time.OffsetDateTime.toBasicISO8601.doc : Doc +time.OffsetDateTime.toBasicISO8601.doc = + use OffsetDateTime toBasicISO8601 + {{ + Converts a {type OffsetDateTime} to a {type Text} in the format + `YYYYMMDDThhmmss[.nnnnnnnnn][[+|-]hh[mm]]`. + + # Examples + + ``` + toBasicISO8601 + (OffsetDateTime + UTC (LocalDateTime (LocalDate +2021 2 7) (LocalTime 18 26 47 0))) + ``` + + ``` + toBasicISO8601 + (OffsetDateTime + AST + (LocalDateTime (LocalDate +2021 2 7) (LocalTime 18 26 47 123456789))) + ``` + }} + +time.OffsetDateTime.toInstant : OffsetDateTime -> Instant +time.OffsetDateTime.toInstant = cases + OffsetDateTime + (UTCOffset offset) + (LocalDateTime (LocalDate yyyy mon day) (LocalTime hh min sec nan)) -> + use Int * + use Nat toInt + days = + years = yyyy Int.- +1970 + leapYears = + use Int - + totalLeapYears = + use Int + / + prev = yyyy - +1 + prev / +4 - prev / +100 + prev / +400 + totalLeapYears - +477 + years * +365 Int.+ leapYears + Int.+ toInt + (List.foldLeft + (Nat.+) + 0 + (List.take + (mon Nat.- 1) + (List.Nonempty.toList (monthLengths (isLeapYear yyyy))))) + Int.+ toInt day Int.- +1 + seconds = + days * +86400 Int.+ toInt hh * +3600 Int.+ toInt min * +60 + Int.+ toInt sec + Int.- offset * +60 + Instant seconds nan + +time.OffsetDateTime.toInstant.doc : Doc +time.OffsetDateTime.toInstant.doc = + use Instant + + {{ + Converts an {type OffsetDateTime} to an {type Instant}. + + # Example + + Convert to {type Instant}, add 40 days, and convert back to + {type OffsetDateTime}: + + ``` + atUTC + (toInstant + (OffsetDateTime + UTC (LocalDateTime (LocalDate +2019 1 1) (LocalTime 12 0 0 0))) + + days +40) + ``` + }} + +test> time.OffsetDateTime.toInstant.tests.rountrip = test.verify do + Each.repeat 1000 + secs = Random.int() + nanos = Random.natIn 0 1000000000 + offset = UTCOffset (intIn -1440 +1440) + instant = Instant secs nanos + instant' = toInstant (fromInstant instant offset) + ensureEqual instant instant' + ignore (fromInstant instant' offset) + +time.OffsetDateTime.toRFC1123 : OffsetDateTime -> Text +time.OffsetDateTime.toRFC1123 = cases + OffsetDateTime (UTCOffset o) dt -> + toRFC1123AtGMT (LocalDateTime.addDuration dt (Duration.negate (minutes o))) + +time.OffsetDateTime.toRFC1123.doc : Doc +time.OffsetDateTime.toRFC1123.doc = + {{ + Formats an {type OffsetDateTime} as an RFC 1123 date and time string. + + # Example + + ``` + OffsetDateTime.toRFC1123 + (OffsetDateTime + (UTCOffset +0) + (LocalDateTime (LocalDate +2022 1 1) (LocalTime 0 0 0 0))) + ``` + }} + +time.OffsetDateTime.toRFC2822 : OffsetDateTime -> Text +time.OffsetDateTime.toRFC2822 = cases + OffsetDateTime + offset + (LocalDateTime + d@(LocalDate year month day) t@(LocalTime hour minute second _)) -> + use Nat - + use Text ++ + wkDay = LocalDate.dayOfWeek d + dd = leftPad 2 "0" (Nat.toText day) + mon = List.at (month - 1) monthNamesShort |> Optional.getOrElse "???" + yyyy = leftPad 4 "0" (Int.toText year) + hh = leftPad 2 "0" (Nat.toText hour) + mm = leftPad 2 "0" (Nat.toText minute) + ss = leftPad 2 "0" (Nat.toText second) + shortName wkDay ++ ", " ++ dd ++ " " ++ mon ++ " " ++ yyyy ++ " " ++ hh + ++ ":" + ++ mm + ++ ":" + ++ ss + ++ " " + ++ UTCOffset.toRFC2822 offset + +time.OffsetDateTime.toRFC2822.doc : Doc +time.OffsetDateTime.toRFC2822.doc = + {{ + Formats an {type OffsetDateTime} as an RFC 2822 date and time string. + + # Example + + ``` + OffsetDateTime.toRFC2822 + (OffsetDateTime + (UTCOffset +0) + (LocalDateTime (LocalDate +2022 1 1) (LocalTime 0 0 0 0))) + ``` + }} + +time.OffsetDateTime.toText : OffsetDateTime -> Text +time.OffsetDateTime.toText = cases + OffsetDateTime offset dateTime -> + LocalDateTime.toText dateTime Text.++ UTCOffset.toText offset + +time.OffsetDateTime.toText.doc : Doc +time.OffsetDateTime.toText.doc = + use Duration * + use Instant + + use OffsetDateTime toText + {{ + A rendering of {type OffsetDateTime} in a human-readable ISO-8601 format. + + # Examples + + ``` + toText (atUTC epoch) + ``` + + ``` + toText (fromInstant (epoch + maxInt * Duration.nanosecond) CEST) + ``` + }} + +time.OffsetDateTime.toUTC : OffsetDateTime -> OffsetDateTime +time.OffsetDateTime.toUTC = convertOffset UTC + +time.OffsetDateTime.toUTC.doc : Doc +time.OffsetDateTime.toUTC.doc = + {{ + Converts an {type OffsetDateTime} to UTC, accounting for the time difference. + + # Examples + + 6 PM Eastern Daylight Time is 10 PM in Reykjavík: + + ``` + toOptional! do + newYork = + Optional.toAbort (OffsetDateTime.fromIso8601 "2023-06-18T18:00:00-04") + rvk = toUTC newYork + OffsetDateTime.toText rvk + ``` + }} + +time.OffsetDateTime.toUTCLocal : OffsetDateTime -> LocalDateTime +time.OffsetDateTime.toUTCLocal = localDateTime << convertOffset UTC + +time.OffsetDateTime.toUTCLocal.doc : Doc +time.OffsetDateTime.toUTCLocal.doc = + {{ + Converts an {type OffsetDateTime} to the {type LocalDateTime} in UTC, + accounting for the time difference. + + # Examples + + 6 PM Eastern Daylight Time is 10 PM in Reykjavík: + + ``` + toOptional! do + newYork = + Optional.toAbort (OffsetDateTime.fromIso8601 "2023-06-18T18:00:00-04") + rvk = toUTCLocal newYork + LocalDateTime.toText rvk + ``` + }} + +time.OffsetTime.addDuration : OffsetTime -> Duration -> OffsetTime +time.OffsetTime.addDuration t d = + timeOfDay + (OffsetDateTime.addDuration (fromTimeAndDate t (LocalDate +1970 1 1)) d) + +time.OffsetTime.addDuration.doc : Doc +time.OffsetTime.addDuration.doc = + {{ + Add a {type Duration} to an {type OffsetTime}. + + The result is an {type OffsetTime} with the same offset as the input, shifted + forward or backward in time by the {type Duration} (which may be negative). + + # Example + + ``` + OffsetTime.addDuration + (OffsetTime (UTCOffset +0) (LocalTime 0 0 0 0)) (minutes +90) + ``` + }} + +time.OffsetTime.doc : Doc +time.OffsetTime.doc = + {{ + An {type OffsetTime} represents a time of day, with a time zone offset. + + # Example + + The time of day at which this document was written: + + ``` + OffsetTime AST (LocalTime 12 32 50 0) + ``` + + # Construction + + `` OffsetTime offset time `` constructs an {type OffsetTime} from the + specified {type UTCOffset} and {type LocalTime}. + + # Field accessors + + `` OffsetTime.offset t `` returns the {type UTCOffset} of the + + `` OffsetTime.time t `` returns the {type LocalTime} of the + {type OffsetTime} `t`. + + # Conversion + + `` OffsetTime.toText t `` returns a {type Text} representation of the time + in ISO-8601 format. + + # Adding and subtracting time + + `` OffsetTime.addDuration t d `` adds a {type Duration} `d` to the + {type OffsetTime} `t`. The duration can be positive or negative. The time + will wrap around if the duration is large enough. + }} + +time.OffsetTime.offset.doc : Doc +time.OffsetTime.offset.doc = + {{ + The UTC offset component of a {type OffsetTime}. + + # Example + + ``` + OffsetTime.offset (OffsetTime AST (LocalTime 21 2 12 0)) + ``` + }} + +time.OffsetTime.offset.set.doc : Doc +time.OffsetTime.offset.set.doc = + {{ + Sets the {type UTCOffset} of a {type OffsetTime}. + + # Example + + ``` + OffsetTime.offset.set UTC (OffsetTime AST (LocalTime 12 0 0 0)) + ``` + }} + +time.OffsetTime.time.doc : Doc +time.OffsetTime.time.doc = + {{ The {type LocalTime} portion of the {type OffsetTime}. }} + +time.OffsetTime.time.set.doc : Doc +time.OffsetTime.time.set.doc = + {{ + Sets the time component of a {type OffsetTime}. + + # Example + + ``` + OffsetTime.time.set + (LocalTime 21 2 12 0) (OffsetTime AST (LocalTime 20 2 12 0)) + ``` + }} + +time.OffsetTime.toText : OffsetTime -> Text +time.OffsetTime.toText = cases + OffsetTime offset time -> + LocalTime.toText time Text.++ UTCOffset.toText offset + +time.patterns.asctimeFormat : Pattern Text +time.patterns.asctimeFormat = + use Pattern capture + use patterns digit + Pattern.join + [ capture (some wordChar) + , literal " " + , capture (some wordChar) + , literal " " + , Pattern.optional (literal " ") + , capture (some digit) + , literal " " + , capture (some digit) + , literal ":" + , capture (some digit) + , literal ":" + , capture (some digit) + , literal " " + , capture (some digit) + ] + +time.patterns.asctimeFormat.doc : Doc +time.patterns.asctimeFormat.doc = + {{ + A {type Pattern} for the RFC 7231 obsolete asctime() format. + + # Example + + ``` + Pattern.run asctimeFormat "Sun Nov 6 08:49:37 1994" + ``` + + See + [https://tools.ietf.org/html/rfc7231](https://tools.ietf.org/html/rfc7231) + }} + +test> time.patterns.asctimeFormat.test = + parsed = Pattern.run asctimeFormat "Sun Nov 6 08:49:37 1994" + check (parsed === Some (["Sun", "Nov", "6", "08", "49", "37", "1994"], "")) + +time.patterns.iso8601Date : Pattern Text +time.patterns.iso8601Date = + use Pattern capture replicate + use patterns digit + Pattern.join + [ capture (replicate 4 4 digit) + , literal "-" + , capture (replicate 2 2 digit) + , literal "-" + , capture (replicate 2 2 digit) + ] + +time.patterns.iso8601DateTime : Pattern Text +time.patterns.iso8601DateTime = + Pattern.join [iso8601LocalDateTime, iso8601Timezone] + +time.patterns.iso8601LocalDateTime : Pattern Text +time.patterns.iso8601LocalDateTime = + Pattern.join [iso8601Date, literal "T", iso8601LocalTime] + +time.patterns.iso8601LocalTime : Pattern Text +time.patterns.iso8601LocalTime = + use Pattern capture join replicate + use patterns digit + join + [ capture (replicate 2 2 digit) + , literal ":" + , capture (replicate 2 2 digit) + , literal ":" + , capture (replicate 2 2 digit) + , Pattern.optional + (join [Pattern.or (literal ".") (literal ","), capture (some digit)]) + ] + +time.patterns.iso8601Time : Pattern Text +time.patterns.iso8601Time = Pattern.join [iso8601LocalTime, iso8601Timezone] + +time.patterns.iso8601Timezone : Pattern Text +time.patterns.iso8601Timezone = + use Pattern capture join oneOf replicate + use patterns digit + oneOf + (capture (literal "Z") + +| [ join + [ capture (oneOf (literal "+" +| [literal "-"])) + , capture (replicate 2 2 digit) + , Pattern.optional (literal ":") + , capture (replicate 2 2 digit) + ] + , join + [ capture (oneOf (literal "+" +| [literal "-"])) + , capture (replicate 2 2 digit) + ] + ]) + +time.patterns.rfc2822DateTime : Pattern Text +time.patterns.rfc2822DateTime = + Pattern.join + [ rfc7231Date + , some space + , rfc7231Time + , some space + , Pattern.capture rfc2822Offset + ] + +time.patterns.rfc2822DateTime.doc : Doc +time.patterns.rfc2822DateTime.doc = + use Pattern run + {{ + A {type Pattern} for the RFC 2822 date/time format. + + Matches both the preferred HttpDate (IMF-fixdate) format as well as the + obsolete RFC-850 format. + + This is used to parse date/time fields in HTTP headers. + + # Examples + + ``` + run rfc2822DateTime "Wed, 12 Oct 2022 18:32:45 +0000" + ``` + + ``` + run rfc2822DateTime "Sunday, 06-Nov-94 08:49:37 -0500" + ``` + + See + [https://tools.ietf.org/html/rfc2822](https://tools.ietf.org/html/rfc2822) + }} + +time.patterns.rfc2822Offset : Pattern Text +time.patterns.rfc2822Offset = + use Pattern capture oneOf replicate + use patterns digit + oneOf + (Pattern.join + [ capture (chars "+-") + , capture (replicate 2 2 digit) + , capture (replicate 2 2 digit) + ] + +| [capture (oneOf (literal "GMT" +| [literal "UT"]))]) + +time.patterns.rfc2822Offset.doc : Doc +time.patterns.rfc2822Offset.doc = + {{ + A {type Pattern} for the RFC 2822 time zone offset format. + + # Example + + ``` + Pattern.run rfc2822Offset "-0500" + ``` + }} + +time.patterns.rfc7231Date : Pattern Text +time.patterns.rfc7231Date = + use Pattern capture or + use patterns digit + Pattern.join + [ capture (some wordChar) + , literal ", " + , capture (some digit) + , or (literal " ") (literal "-") + , capture (some wordChar) + , or (literal " ") (literal "-") + , capture (some digit) + ] + +time.patterns.rfc7231Date.doc : Doc +time.patterns.rfc7231Date.doc = + {{ + A {type Pattern} for the RFC 7231 date format. + + This is used to parse date/time fields in HTTP headers. + + # Example + + ``` + Pattern.run rfc7231Date "Sun, 06-Nov-94" + ``` + }} + +time.patterns.rfc7231DateTime : Pattern Text +time.patterns.rfc7231DateTime = + Pattern.join [rfc7231Date, some (patterns.char whitespace), rfc7231Time] + +time.patterns.rfc7231DateTime.doc : Doc +time.patterns.rfc7231DateTime.doc = + use Pattern run + {{ + A {type Pattern} for the RFC 7231 date/time format. + + Matches both the preferred HttpDate (IMF-fixdate) format as well as the + obsolete RFC-850 format. + + This is used to parse date/time fields in HTTP headers. + + # Examples + + ``` + run rfc7231DateTime "Wed, 12 Oct 2022 18:32:45 GMT" + ``` + + ``` + run rfc7231DateTime "Sunday, 06-Nov-94 08:49:37 GMT" + ``` + + See + [https://tools.ietf.org/html/rfc7231](https://tools.ietf.org/html/rfc7231) + }} + +test> time.patterns.rfc7231DateTime.test850 = + parsed = Pattern.run rfc7231DateTime "Sunday, 06-Nov-94 08:49:37 GMT" + check + (parsed === Some (["Sunday", "06", "Nov", "94", "08", "49", "37"], " GMT")) + +test> time.patterns.rfc7231DateTime.testIMF = + parsed = Pattern.run rfc7231DateTime "Wed, 12 Oct 2022 18:32:45 GMT" + check + (parsed === Some (["Wed", "12", "Oct", "2022", "18", "32", "45"], " GMT")) + +time.patterns.rfc7231Time : Pattern Text +time.patterns.rfc7231Time = + use Pattern capture replicate + use patterns digit + Pattern.join + [ capture (replicate 2 2 digit) + , literal ":" + , capture (replicate 2 2 digit) + , literal ":" + , capture (replicate 2 2 digit) + ] + +time.patterns.rfc7231Time.doc : Doc +time.patterns.rfc7231Time.doc = + {{ + A {type Pattern} for the RFC 7231 time format. + + This is used to parse date/time fields in HTTP headers. + + # Example + + ``` + Pattern.run rfc7231Time "18:32:45" + ``` + }} + +time.README : Doc +time.README = + use Clock monotonic + {{ + # Time and date + + This namespace contains data types and functions for reading system clocks, + as well as working with time and dates. + + ## Clocks + + A few different clocks are provided. Reading a clock requires the + {type IO} ability. + + `` realtime() `` gets the current real-world time according to the + system clock, as an {type Instant}. + + `` Clock.timeSinceEpoch() `` gets the {type Duration} elapsed since the + {epoch}. Note that the {type Duration} will be negative if the current + time is before the {epoch}. + + `` monotonic() `` is a clock you can use to measure real elapsed time in + your programs. This returns a {type Duration} elapsed since some + arbitrary fixed point in time. The actual length of the {type Duration} + is meaningless on its own, but subsequent calls to `` monotonic() `` are + guaranteed to return monotonically increasing {type Duration}s. This + lets you measure real time without interference from leap-seconds, other + programs setting the system clock, or time-zone changes. + + `` processCPUTime() `` gets the amount of CPU time spent by the + operating system process running Unison, as a {type Duration}. + + `` threadCPUTime() `` Gets the amount of CPU time spent by the operating + system thread executing your program, as a {type Duration}. Note that + Unison green threads spawned by {fork} do not necessarily correspond to + OS threads. + + ## Durations, Instants, Dates, and Times + + {type Duration} represents an amount of time with nanosecond resolution. + + {type Instant} represents a point in time with nanosecond resolution. + + {type LocalDateTime} represents a date and time with nanosecond + resolution, without any time zone information. + + {type LocalTime} represents a time of day with nanosecond resolution, + without any time zone information. + + {type LocalDate} represents a calendar date, without any time zone + information. + + {type OffsetDateTime} represents a date and time with nanosecond + resolution, with an offset from + [UTC](https://www.timeanddate.com/time/aboututc.html). + + {type OffsetTime} represents a time of day with nanosecond resolution, + with an offset from + [UTC](https://www.timeanddate.com/time/aboututc.html). + + {type UTCOffset} represents the offset from + [UTC](https://www.timeanddate.com/time/aboututc.html) of an + {type OffsetDateTime} or {type OffsetTime}. + }} + +time.TimeZone.currentTimeZone : '{IO, Exception} TimeZone +time.TimeZone.currentTimeZone = do getTimeZone realtime() + +time.TimeZone.currentTimeZone.doc : Doc +time.TimeZone.currentTimeZone.doc = + {{ + Gets the configured system {type TimeZone} for the current time. + + Retrieving the {type TimeZone} is an {type IO} action, as it requires a + system call to consult the time zone database on your system. + }} + +time.TimeZone.doc : Doc +time.TimeZone.doc = + use TimeZone name + {{ + {type TimeZone} represents a time zone in the world. It has three fields: + + * {TimeZone.offset} : An {type Int} representing the offset from UTC in + minutes. + * {summerOnly} : A {type Boolean} indicating whether the time zone is only + used in the summer. + * {name} : A short name for the time zone, as {type Text}. For example for + Eastern Daylight Time, the {name} will be "EDT". + + # Getting the system time zone + + Get the configured system time zone for the current time: + + @signature{currentTimeZone} + + Get the configured system time zone for a given {type Instant} in time: + + @signature{getTimeZone} + + # Manual construction + + You can construct a {type TimeZone} by giving all three fields: + + @signature{TimeZone} + }} + +time.TimeZone.getTimeZone : Instant ->{IO} TimeZone +time.TimeZone.getTimeZone t = + use Nat > + (o, s, n) = systemTimeZone.impl (secondsSinceEpoch t) + TimeZone (UTCOffset o) (s > 0) n + +time.TimeZone.getTimeZone.doc : Doc +time.TimeZone.getTimeZone.doc = + {{ + Gets the configured system {type TimeZone} for the given {type Instant} in + time. + + Depending on the locale, the {type TimeZone} may be different at different + times, for example if the configured system locale observes daylight saving + time. + + Retrieving the {type TimeZone} is an {type IO} action, as it requires a + system call to consult the time zone database on your system. + }} + +time.TimeZone.toDuration : TimeZone -> Duration +time.TimeZone.toDuration = cases TimeZone o _ _ -> UTCOffset.toDuration o + +time.TimeZone.toDuration.doc : Doc +time.TimeZone.toDuration.doc = + {{ + Returns the {type Duration} corresponding to the amount of time difference + between UTC and the given {type TimeZone}. + + # Example + + ``` + Duration.toText + (TimeZone.toDuration (TimeZone (UTCOffset -240) true "EDT")) + ``` + }} + +time.UTCOffset.addHours : Nat -> UTCOffset -> UTCOffset +time.UTCOffset.addHours = cases + n, UTCOffset o -> UTCOffset (Nat.toInt n Int.* +60 Int.+ o) + +time.UTCOffset.addHours.doc : Doc +time.UTCOffset.addHours.doc = + {{ + Adds a number of hours to a {type UTCOffset}. + + # Example + + Central European Summer Time is UTC+2 + + ``` + (UTC |> addHours 2) === CEST + ``` + + # See also + + * {subtractHours} + * {addMinutes} + * {subtractMinutes} + }} + +time.UTCOffset.addHours.flipped : UTCOffset -> Nat -> UTCOffset +time.UTCOffset.addHours.flipped = cases + UTCOffset o, n -> UTCOffset (Nat.toInt n Int.* +60 Int.+ o) + +time.UTCOffset.addHours.flipped.doc : Doc +time.UTCOffset.addHours.flipped.doc = + {{ + Adds a number of hours to a {type UTCOffset}. + + # Example + + Central European Summer Time is UTC+2 + + ``` + addHours.flipped UTC 2 === CEST + ``` + + # See also + + * {subtractHours.flipped} + * {addMinutes} + * {subtractMinutes} + }} + +time.UTCOffset.addMinutes : Nat -> UTCOffset -> UTCOffset +time.UTCOffset.addMinutes = cases + n, UTCOffset o -> UTCOffset (Nat.toInt n Int.+ o) + +time.UTCOffset.addMinutes.doc : Doc +time.UTCOffset.addMinutes.doc = + {{ + Adds a number of minutes to a {type UTCOffset}. + + # Example + + India Standard Time is 5 and a half hours ahead of UTC: + + ``` + addMinutes 330 UTC === IST + ``` + + # See also + + * {addHours} + * {subtractHours} + * {subtractMinutes} + }} + +time.UTCOffset.currentOffset : '{IO, Exception} UTCOffset +time.UTCOffset.currentOffset = do getOffset realtime() + +time.UTCOffset.currentOffset.doc : Doc +time.UTCOffset.currentOffset.doc = + {{ + Gets the {type UTCOffset} of the configured system time zone for the current + time. + + Retrieving the {type UTCOffset} is an {type IO} action, as it requires a + system call to consult the time zone database on your system. + }} + +time.UTCOffset.doc : Doc +time.UTCOffset.doc = + use UTCOffset isNegative toText + {{ + {type UTCOffset} represents a timezone offset from UTC with a precision of + minutes. + + # Construction + + A number of named offsets are provided, such as {UTC}, {BST}, {CEST}, + {CDT}, {CST}, {AKDT}, etc. + + ``` + [UTC, MST, CEST] + ``` + + {fromMinutes} constructs a {type UTCOffset} of the specified number of + minutes: + + ``` + fromMinutes -150 + ``` + + {fromHours} constructs a {type UTCOffset} of the specified whole number of + hours. + + ``` + fromHours +3 + ``` + + # Properties + + {isNegative} returns `` true `` if the {type UTCOffset} is negative: + + ``` + isNegative AST + ``` + + {toMinutes} returns the number of minutes in the {type UTCOffset}, as an + {type Int}. For example: + + ``` + toMinutes (fromHours -4) + ``` + + {toHours} returns the number of hours in the {type UTCOffset}, as a + {type Float}. For example, if the offset is +1:30 then {toHours} returns + ``1.5``: + + ``` + toHours (fromMinutes +90) + ``` + + {offsetHours} returns the number of whole hours in the {type UTCOffset}. + For example, if the offset is +1:30, then the {offsetHours} is ``1``: + + ``` + offsetHours (fromMinutes +90) + ``` + + {offsetMinutes} returns the number of minutes in the {type UTCOffset} that + are not divisible by hours. For example, if the offset is +1:30, then the + {offsetMinutes} is ``30``. This is always a positive number, even if the + offset is negative: + + ``` + offsetMinutes (fromMinutes +90) + ``` + + {toText} returns a {type Text} representation of the {type UTCOffset} in + ISO-8601 format: + + ``` + toText CST + ``` + + ``` + toText UTC + ``` + + # Offset arithmetic + + {addHours} adds a number of hours to the offset: + + ``` + addHours 4 UTC + ``` + + {subtractHours} subtracts hours from the offset: + + ``` + subtractHours 4 UTC + ``` + + {addMinutes} adds a number of minutes: + + ``` + addMinutes 30 UTC + ``` + + {subtractMinutes} subtracts a number of minutes: + + ``` + subtractMinutes 30 UTC + ``` + }} + +time.UTCOffset.fromBasicISO8601 : Text -> Optional UTCOffset +time.UTCOffset.fromBasicISO8601 t = + toOptional! do + if t Text.== "Z" then UTCOffset +0 + else + use Int * + fromText + use Nat >= + use Optional toAbort + use Text == drop take + sign = if take 1 t == "-" then -1 else +1 + hh = toAbort (fromText (take 2 (drop 1 t))) + mm = + if Text.size t >= 5 then toAbort (fromText (take 2 (drop 3 t))) else +0 + UTCOffset (sign * (hh * +60 + mm)) + +time.UTCOffset.fromBasicISO8601.doc : Doc +time.UTCOffset.fromBasicISO8601.doc = + use UTCOffset fromBasicISO8601 + {{ + Converts a {type Text} in the format `Z` or `[+|-]hh[mm]` to a + {type UTCOffset}. + + # Examples + + ``` + fromBasicISO8601 "Z" + ``` + + ``` + fromBasicISO8601 "+01" + ``` + + ``` + fromBasicISO8601 "-01" + ``` + + ``` + fromBasicISO8601 "+0130" + ``` + + ``` + fromBasicISO8601 "-0130" + ``` + }} + +time.UTCOffset.fromHours : Int -> UTCOffset +time.UTCOffset.fromHours h = + use Int * + UTCOffset (h * +60) + +time.UTCOffset.fromHours.doc : Doc +time.UTCOffset.fromHours.doc = + {{ + Creates a {type UTCOffset} from the given number of hours. + + # Example + + ``` + fromHours +5 + ``` + + ``` + fromHours -5 + ``` + }} + +time.UTCOffset.fromIso8601 : Text -> Optional UTCOffset +time.UTCOffset.fromIso8601 t = + toOptional! do match Optional.toAbort (Pattern.run iso8601Timezone t) with + (["Z"], _) -> UTC + ([plusMinus, hh] ++ mm, _) -> + use Int * + + use Nat fromText toInt + use Optional toAbort + isPlus = match plusMinus with + "-" -> false + "+" -> true + _ -> abort + h = toAbort (fromText hh) + m = match mm with + [mins] -> toAbort (fromText mins) + [] -> 0 + _ -> abort + mins = toInt h * +60 + toInt m + fromMinutes (if isPlus then mins else Int.negate mins) + _ -> abort + +time.UTCOffset.fromIso8601.doc : Doc +time.UTCOffset.fromIso8601.doc = + use UTCOffset fromIso8601 + {{ + Converts a {type Text} in the format `Z` or `[+|-]hh[:mm]` to a + {type UTCOffset}. + + # Examples + + ``` + fromIso8601 "Z" + ``` + + ``` + fromIso8601 "+01" + ``` + + ``` + fromIso8601 "-01" + ``` + + ``` + fromIso8601 "+01:30" + ``` + + ``` + fromIso8601 "-01:30" + ``` + }} + +time.UTCOffset.fromMinutes : Int -> UTCOffset +time.UTCOffset.fromMinutes = UTCOffset + +time.UTCOffset.fromMinutes.doc : Doc +time.UTCOffset.fromMinutes.doc = + {{ + Constructs a {type UTCOffset} from a number of minutes. + + # Example + + ``` + fromMinutes +60 + ``` + }} + +time.UTCOffset.fromRFC2822 : Text ->{Exception} UTCOffset +time.UTCOffset.fromRFC2822 text = + UTCOffset match Pattern.run rfc2822Offset text with + Some ([sign, hour, minute], _) -> + use Nat + fromText + sign' = match sign with + "+" -> +1 + "-" -> -1 + _ -> raiseGeneric "Invalid sign" (typeLink Generic) sign + hour' = toGenericExceptionWith "Invalid hour" hour (fromText hour) + minute' = + toGenericExceptionWith "Invalid minute" minute (fromText minute) + sign' Int.* Nat.toInt (hour' Nat.* 60 + minute') + Some (["GMT"], _) -> +0 + Some (["UT"], _) -> +0 + _ -> raiseGeneric "Invalid RFC2822 UTC offset" (typeLink Generic) text + +time.UTCOffset.fromRFC2822.doc : Doc +time.UTCOffset.fromRFC2822.doc = + {{ + Parses a UTC offset in the RFC 2822 format. + + # Example + + ``` + catch do UTCOffset.fromRFC2822 "-0500" + ``` + }} + +time.UTCOffset.getOffset : Instant ->{IO} UTCOffset +time.UTCOffset.getOffset t = + (o, _, _) = systemTimeZone.impl (secondsSinceEpoch t) + UTCOffset o + +time.UTCOffset.getOffset.doc : Doc +time.UTCOffset.getOffset.doc = + {{ + Gets the {type UTCOffset} of the configured system time zone for the given + {type Instant} in time. + + Depending on the locale, the {type UTCOffset} may be different at different + times, for example if the configured system locale observes daylight saving + time. + + Retrieving the {type UTCOffset} is an {type IO} action, as it requires a + system call to consult the time zone database on your system. + }} + +time.UTCOffset.isNegative : UTCOffset -> Boolean +time.UTCOffset.isNegative = cases UTCOffset minutes -> Int.isNegative minutes + +time.UTCOffset.isNegative.doc : Doc +time.UTCOffset.isNegative.doc = + {{ + `` UTCOffset.isNegative `` returns `` true `` if the offset represents a time + zone west of UTC. + }} + +time.UTCOffset.offsetHours : UTCOffset -> Int +time.UTCOffset.offsetHours = cases UTCOffset mins -> mins Int./ +60 + +time.UTCOffset.offsetHours.doc : Doc +time.UTCOffset.offsetHours.doc = + {{ + The number of hours in a {type UTCOffset}. + + # Example + + ``` + offsetHours AST + ``` + }} + +time.UTCOffset.offsetMinutes : UTCOffset -> Nat +time.UTCOffset.offsetMinutes = cases UTCOffset mins -> Int.emod mins +60 + +time.UTCOffset.offsetMinutes.doc : Doc +time.UTCOffset.offsetMinutes.doc = + {{ + Returns the remainder of dividing the {type UTCOffset} in minutes by ``+60``. + + # Example + + ``` + offsetMinutes UTC + ``` + + ``` + offsetMinutes (UTCOffset -90) + ``` + }} + +time.UTCOffset.offsetNamed.ADT : UTCOffset +time.UTCOffset.offsetNamed.ADT = fromHours -3 + +time.UTCOffset.offsetNamed.AKDT : UTCOffset +time.UTCOffset.offsetNamed.AKDT = fromHours -8 + +time.UTCOffset.offsetNamed.AKST : UTCOffset +time.UTCOffset.offsetNamed.AKST = fromHours -9 + +time.UTCOffset.offsetNamed.AST : UTCOffset +time.UTCOffset.offsetNamed.AST = fromHours -4 + +time.UTCOffset.offsetNamed.BRST : UTCOffset +time.UTCOffset.offsetNamed.BRST = fromHours -2 + +time.UTCOffset.offsetNamed.BST : UTCOffset +time.UTCOffset.offsetNamed.BST = fromHours +1 + +time.UTCOffset.offsetNamed.CDT : UTCOffset +time.UTCOffset.offsetNamed.CDT = fromHours -5 + +time.UTCOffset.offsetNamed.CEST : UTCOffset +time.UTCOffset.offsetNamed.CEST = fromHours +2 + +time.UTCOffset.offsetNamed.CST : UTCOffset +time.UTCOffset.offsetNamed.CST = fromHours -6 + +time.UTCOffset.offsetNamed.DST : UTCOffset +time.UTCOffset.offsetNamed.DST = fromHours -12 + +time.UTCOffset.offsetNamed.EAT : UTCOffset +time.UTCOffset.offsetNamed.EAT = fromHours +3 + +time.UTCOffset.offsetNamed.GMT : UTCOffset +time.UTCOffset.offsetNamed.GMT = fromMinutes +0 + +time.UTCOffset.offsetNamed.GST : UTCOffset +time.UTCOffset.offsetNamed.GST = fromHours +10 + +time.UTCOffset.offsetNamed.HST : UTCOffset +time.UTCOffset.offsetNamed.HST = fromHours -10 + +time.UTCOffset.offsetNamed.IDLE : UTCOffset +time.UTCOffset.offsetNamed.IDLE = fromHours +12 + +time.UTCOffset.offsetNamed.IDLW : UTCOffset +time.UTCOffset.offsetNamed.IDLW = fromHours +13 + +time.UTCOffset.offsetNamed.IST : UTCOffset +time.UTCOffset.offsetNamed.IST = fromMinutes +330 + +time.UTCOffset.offsetNamed.JST : UTCOffset +time.UTCOffset.offsetNamed.JST = fromHours +9 + +time.UTCOffset.offsetNamed.MSD : UTCOffset +time.UTCOffset.offsetNamed.MSD = fromHours +4 + +time.UTCOffset.offsetNamed.MST : UTCOffset +time.UTCOffset.offsetNamed.MST = fromHours -7 + +time.UTCOffset.offsetNamed.NDT : UTCOffset +time.UTCOffset.offsetNamed.NDT = fromHours -1 + +time.UTCOffset.offsetNamed.PKT : UTCOffset +time.UTCOffset.offsetNamed.PKT = fromHours +5 + +time.UTCOffset.offsetNamed.SLT : UTCOffset +time.UTCOffset.offsetNamed.SLT = fromHours +6 + +time.UTCOffset.offsetNamed.SST : UTCOffset +time.UTCOffset.offsetNamed.SST = fromHours -11 + +time.UTCOffset.offsetNamed.UTC : UTCOffset +time.UTCOffset.offsetNamed.UTC = UTCOffset +0 + +time.UTCOffset.offsetNamed.WET : UTCOffset +time.UTCOffset.offsetNamed.WET = fromHours +0 + +time.UTCOffset.offsetNamed.WIB : UTCOffset +time.UTCOffset.offsetNamed.WIB = fromHours +7 + +time.UTCOffset.offsetNamed.WST : UTCOffset +time.UTCOffset.offsetNamed.WST = fromHours +8 + +time.UTCOffset.subtractHours : Nat -> UTCOffset -> UTCOffset +time.UTCOffset.subtractHours = cases + n, UTCOffset o -> UTCOffset (o Int.- Nat.toInt n Int.* +60) + +time.UTCOffset.subtractHours.doc : Doc +time.UTCOffset.subtractHours.doc = + {{ + Subtracts a number of hours from a {type UTCOffset}. + + # Example + + Eastern Daylight Time is UTC-4 + + ``` + (UTC |> subtractHours 4) === AST + ``` + + # See also + + * {addHours} + * {addMinutes} + * {subtractMinutes} + }} + +time.UTCOffset.subtractHours.flipped : UTCOffset -> Nat -> UTCOffset +time.UTCOffset.subtractHours.flipped = cases + UTCOffset o, n -> UTCOffset (o Int.- Nat.toInt n Int.* +60) + +time.UTCOffset.subtractHours.flipped.doc : Doc +time.UTCOffset.subtractHours.flipped.doc = + {{ + Subtracts a number of hours from a {type UTCOffset}. + + # Example + + Eastern Daylight Time is UTC-4 + + ``` + subtractHours.flipped UTC 4 + ``` + + # See also + + * {addHours.flipped} + * {addMinutes} + * {subtractMinutes} + }} + +time.UTCOffset.subtractMinutes : Nat -> UTCOffset -> UTCOffset +time.UTCOffset.subtractMinutes = cases + n, UTCOffset o -> UTCOffset (o Int.- Nat.toInt n) + +time.UTCOffset.subtractMinutes.doc : Doc +time.UTCOffset.subtractMinutes.doc = + {{ + Subtracts a number of minutes from a {type UTCOffset}. + + # Example + + Newfoundland Standard time is 3 and a half hours behind UTC: + + ``` + subtractMinutes 210 UTC + ``` + + # See also + + * {addHours} + * {subtractHours} + * {addMinutes} + }} + +time.UTCOffset.toBasicISO8601 : UTCOffset -> Text +time.UTCOffset.toBasicISO8601 = cases + UTCOffset minutes -> + if minutes Int.== +0 then "Z" + else + use Int < abs + use Nat / == toText + hh = abs minutes / 60 + mm = Nat.mod (abs minutes) 60 + Text.join + "" + [ if minutes < +0 then "-" else "+" + , leftPad 2 "0" (toText (abs minutes / 60)) + , if mm == 0 then "" else leftPad 2 "0" (toText mm) + ] + +time.UTCOffset.toBasicISO8601.doc : Doc +time.UTCOffset.toBasicISO8601.doc = + use UTCOffset toBasicISO8601 + {{ + Converts a {type UTCOffset} to a {type Text} in the format `Z` or + `[+|-]hh[mm]`. + + # Examples + + ``` + toBasicISO8601 (UTCOffset +0) + ``` + + ``` + toBasicISO8601 (UTCOffset +60) + ``` + + ``` + toBasicISO8601 (UTCOffset -60) + ``` + + ``` + toBasicISO8601 (UTCOffset +90) + ``` + + ``` + toBasicISO8601 (UTCOffset -90) + ``` + }} + +time.UTCOffset.toDuration : UTCOffset -> Duration +time.UTCOffset.toDuration = cases UTCOffset o -> minutes o + +time.UTCOffset.toDuration.doc : Doc +time.UTCOffset.toDuration.doc = + use Duration toText + use UTCOffset toDuration + {{ + Returns the {type Duration} corresponding to the amount of time difference + between UTC and the given {type UTCOffset}. + + # Examples + + ``` + toText (toDuration UTC) + ``` + + ``` + toText (toDuration CST) + ``` + + ``` + toText (toDuration IST) + ``` + + ``` + toText (toDuration CEST) + ``` + }} + +time.UTCOffset.toHours : UTCOffset -> Float +time.UTCOffset.toHours = cases + UTCOffset mins -> Float.fromInt mins Float./ 60.0 + +time.UTCOffset.toHours.doc : Doc +time.UTCOffset.toHours.doc = + {{ + Converts a {type UTCOffset} to a {type Float} representing the number of + hours of offset from UTC. + + # Example + + ``` + toHours (UTCOffset -240) + ``` + }} + +time.UTCOffset.toMinutes : UTCOffset -> Int +time.UTCOffset.toMinutes = cases UTCOffset offsetMinutes -> offsetMinutes + +time.UTCOffset.toMinutes.doc : Doc +time.UTCOffset.toMinutes.doc = + {{ + Converts a {type UTCOffset} to the number of minutes it represents. + + # Example + + ``` + toMinutes AST + ``` + + ``` + toMinutes UTC + ``` + }} + +time.UTCOffset.toMinutes.modify : (Int ->{g} Int) -> UTCOffset ->{g} UTCOffset +time.UTCOffset.toMinutes.modify f = cases + UTCOffset offsetMinutes -> UTCOffset (f offsetMinutes) + +time.UTCOffset.toMinutes.modify.doc : Doc +time.UTCOffset.toMinutes.modify.doc = + {{ + Modifies the number of minutes of a {type UTCOffset} with the given function. + }} + +time.UTCOffset.toMinutes.set : Int -> UTCOffset -> UTCOffset +time.UTCOffset.toMinutes.set offsetMinutes1 = cases + UTCOffset _ -> UTCOffset offsetMinutes1 + +time.UTCOffset.toRFC2822 : UTCOffset -> Text +time.UTCOffset.toRFC2822 = cases + UTCOffset o -> + use Int >= abs + use Nat / toText + use Text ++ + sign = if o >= +0 then "+" else "-" + hh = leftPad 2 "0" (toText (abs o / 60)) + mm = leftPad 2 "0" (toText (Nat.mod (abs o) 60)) + sign ++ hh ++ mm + +time.UTCOffset.toRFC2822.doc : Doc +time.UTCOffset.toRFC2822.doc = + {{ + Formats a {type UTCOffset} as an RFC 2822 time zone string. + + # Example + + ``` + UTCOffset.toRFC2822 (UTCOffset +0) + ``` + }} + +time.UTCOffset.toText : UTCOffset -> Text +time.UTCOffset.toText offset = + use Nat toText + use Text ++ + hours = + (if UTCOffset.isNegative offset then "-" else "+") + ++ leftPad 2 "0" (toText (Int.abs (offsetHours offset))) + minutes = + if offsetMinutes offset Nat.== 0 then "" + else ":" ++ leftPad 2 "0" (toText (offsetMinutes offset)) + if toMinutes offset Int.== +0 then "Z" else hours ++ minutes + +time.UTCOffset.toText.doc : Doc +time.UTCOffset.toText.doc = + use UTCOffset toText + {{ + A rendering of {type UTCOffset} in a human-readable ISO-8601 format. + + # Examples + + ``` + toText GMT + ``` + + ``` + toText AST + ``` + + ``` + toText CEST + ``` + }} + +-- builtin todo : a -> b + +todo.doc : Doc +todo.doc = + use Text ++ + {{ + The {todo} function works the same way as the {bug} function in that it halts + the program with a specific value, printing that value to the console. + + The main difference is that {todo} is intended to be used to communicate + unfinished programs that still need work, where as {bug} communicates runtime + invariants. + + **Example:** + + ``` ucm + >todo (Some ?👋) + ``` + + The value will be printed out as the program halts: + + ``` ucm + 💔💥 + + I've encountered a call to builtin.todo with the following value: + + Some ?👋 + + I'm sorry this message doesn't have more detail about the location of the failure. My makers + plan to fix this in a future release. 😢 + ``` + + {todo} is useful for getting a program to typecheck without being fully ready + to handle all cases of a sum type of instance: + + @typecheck ``` + hello : Optional Text -> Text + hello = cases + Some n -> "👋 Hello " ++ n + None -> todo "handle this case later" + ``` + + To get a list of todos, run the 'dependents todo' command in ucm. + }} + +Unit.doc : Doc +Unit.doc = + use List replicate + use abilities repeat + {{ + The {type Unit} type has only one value: `` () `` (pronouced "unit"), and the + type itself can also be written `()`. + + ``` + () : () + ``` + + This value is most often used in conjunction with + {{ abilitiesTutorialLink }}, either as an argument to a function to delay its + effects, or as the return value of a function that's called only for its + effects and doesn't return any data. + + # Delayed computations + + 📚 Tutorial: + [Delayed computations](https://www.unison-lang.org/docs/fundamentals/values-and-functions/delayed-computations/). + + 📚 Language Reference: + [Delayed computations](https://www.unison-lang.org/docs/language-reference/delayed-computations/). + + A __delayed computation__ is any function that takes {type Unit} as its + argument. The type signature of such a function can be written + `f : () -> x` where `x` is some return type, or it can be written using a + syntactic shorthand: `f : 'x`. + + For example, the function {replicate} takes a delayed computation as an + argument, executes it a specified number of times, and collects the results + in a list: + + @signature{replicate} + + A delayed computation can be __forced__ by passing `` () `` to it, as in + `f ()`. This can be written using a syntactic shorthand as well: `!f` + + ``` + f = do "delayed" + f() + ``` + + # Functions that don't return any data + + The `` () `` value is also used for effectful computations that we execute + only for their effects, and don't need to return any data. For example, + {repeat} takes a delayed computation and executes it a number of times, + ignoring any results: + + @signature{repeat} + + Since this function ignores all the results but has to return something, it + simply returns ``()``. + }} + +(Universal.!==) : a -> a -> Boolean +a Universal.!== b = Boolean.not (a === b) + +Universal.!==.doc : Doc +Universal.!==.doc = + {{ + The inverse of {===}. Returns `` true `` if two objects are + **not structurally identical**. + }} + +-- builtin Universal.=== : a -> a -> Boolean + +Universal.===.doc : Doc +Universal.===.doc = + use Set == fromList + {{ + Structural equality of two Unison objects of the same type. Returns `` true + `` if the objects are **structurally identical**. That is, if they are + represented by the exact same arrangement of bits in memory and would + therefore have the same Unison hash. + + # Examples + + For primitive types like {type Nat}, structural equality coincides with + ordinary equivalence: + + ``` + 1 === 1 + ``` + + For more complex types like {type Set}, two values can be equivalent + without being structurally identical: + + ``` + fromList [0, 1, 2, 3] === fromList [3, 1, 2, 0] + ``` + + This is because although two {type Set} values may represent the same + mathematical set, that mathematical set may have several valid + representations in the {type Set} type: + + ``` + fromList [0, 1, 2, 3] + ``` + + ``` + fromList [3, 1, 2, 0] + ``` + + To check whether two values of a {type Set} type are the same, we use set + equality instead (with {==}): + + ``` + fromList [0, 1, 2, 3] == fromList [3, 1, 2, 0] + ``` + }} + +-- builtin Universal.compare : a -> a -> Int + +Universal.compare.doc : Doc +Universal.compare.doc = + use Universal compare + {{ + Low-level comparison of two Unison objects of the same type. Returns `` 0 `` + if the two objects are identical (see {===}), `` -1 `` if the first object + precedes the second (see {Universal.lt}) or `` +1 `` if the first object + succeeds the second (see {Universal.gt}). + + # Examples + + ``` + compare 1 1 + ``` + + ``` + compare 42 1 + ``` + + ``` + compare 1 42 + ``` + }} + +Universal.compareOn : (a ->{e} x) -> a -> a ->{e} Ordering +Universal.compareOn p x y = on Universal.ordering p x y + +Universal.compareOn.doc : Doc +Universal.compareOn.doc = + use List size + {{ + Structural comparison on some function of two Unison objects of the same + type. Returns `` Equal `` if the value under the function is structurally + identical on both objects (see {===}), `` Less `` if the value from the first + object precedes the second (see {Universal.lt}) or `` Greater `` if the value + from the first object succeeds the second (see {Universal.gt}). + + # Examples + + ``` + compareOn size [] [] + ``` + + ``` + compareOn size [1, 2, 3] [] + ``` + + ``` + compareOn size [1] [2, 3, 4] + ``` + }} + +-- builtin Universal.gt : a -> a -> Boolean + +Universal.gt.doc : Doc +Universal.gt.doc = + {{ + Structural comparison of two Unison objects of the same type according to an + arbitrary lexicographical order on their structure. + + This relation is the opposite order of {Universal.lt}. + }} + +-- builtin Universal.gteq : a -> a -> Boolean + +Universal.gteq.doc : Doc +Universal.gteq.doc = + {{ + Structural comparison of two Unison objects of the same type according to an + arbitrary lexicographical order on their structure. + + This relation is the opposite order of {Universal.lteq}. + }} + +-- builtin Universal.lt : a -> a -> Boolean + +Universal.lt.doc : Doc +Universal.lt.doc = + use Universal lt + {{ + Structural comparison of two Unison objects of the same type according to an + arbitrary lexicographical order on their structure. + + For primitive types like {type Int}, this order coincides with numeric order: + + ``` + lt 1 2 + ``` + + ``` + lt 2 2 + ``` + + For more complex structures, the ordering is arbitrary and + implementation-specific. The purpose of this function is to provide + **some but any** default order for all Unison values so they can participate + in structures that require an ordering on elements (like binary trees). + }} + +-- builtin Universal.lteq : a -> a -> Boolean + +Universal.lteq.doc : Doc +Universal.lteq.doc = + {{ + Structural comparison of two Unison objects of the same type according to an + arbitrary lexicographical order on their structure. + + Returns `` true `` if and only if the first argument precedes the second + (according to {Universal.lt}), or if the two are structurally identical + (according to {===}). + }} + +Universal.max : a -> a -> a +Universal.max a b = if Universal.gt a b then a else b + +Universal.max.doc : Doc +Universal.max.doc = + {{ Returns the greater of two Unison objects according to {Universal.lt}. }} + +test> Universal.max.tests.absorption = + runs 100 do deprecated.laws.absorption gen.int Universal.min Universal.max + +test> Universal.max.tests.associative = + runs 100 do laws.associative gen.int Universal.max + +test> Universal.max.tests.commutative = + runs 100 do laws.commutative gen.int Universal.max + +test> Universal.max.tests.distributesOverMin = + runs 100 do laws.distributive natInOrder Universal.min Universal.max + +test> Universal.max.tests.idempotent = + runs 100 do deprecated.laws.idempotence gen.int Universal.max + +test> Universal.max.tests.partialOrder = runs 100 do + use gen int + x = int() + y = int() + expect (Universal.gteq x y === Universal.max x y === x) + +Universal.min : a -> a -> a +Universal.min a b = if Universal.lt a b then a else b + +Universal.min.doc : Doc +Universal.min.doc = + {{ Returns the lesser of two Unison objects according to {Universal.lt}. }} + +test> Universal.min.tests.associative = + runs 100 do laws.associative gen.int Universal.min + +test> Universal.min.tests.commutative = + runs 100 do laws.commutative gen.int Universal.min + +test> Universal.min.tests.distributesOverMax = + runs 100 do laws.distributive natInOrder Universal.max Universal.min + +test> Universal.min.tests.idempotent = + runs 100 do deprecated.laws.idempotence gen.int Universal.min + +test> Universal.min.tests.partialOrder = runs 100 do + use gen int + x = int() + y = int() + expect (Universal.lteq x y === Universal.min x y === x) + +-- builtin Universal.murmurHash : a -> Nat + +Universal.murmurHash.doc : Doc +Universal.murmurHash.doc = + {{ + An implementation of + [MurmurHash 2](https://en.wikipedia.org/wiki/MurmurHash), a fast hashing + algorithm designed to work with hash tables. MurmurHash generates generates + non-cryptographic hashes, look at {hash} if you need cryptographic hashes + instead. It's also not guaranteed to be stable across machines and + architectures, so you shouldn't use a Murmur hash as a persistent identifier. + + # Example + + `` murmurHash x `` will return a 64-bit hash of the value `x`. + + ``` + murmurHash "hello" + ``` + }} + +Universal.ordering : a -> a -> Ordering +Universal.ordering x y = + c = Universal.compare x y + if Universal.lt c +0 then Less else if c === +0 then Equal else Greater + +Universal.ordering.doc : Doc +Universal.ordering.doc = + use Universal ordering + {{ + Relative structural ordering of two Unison objects of the same type. Returns + {Equal} if the two objects are identical (see {===}), {Less} if the first + object precedes the second (see {Universal.lt}) or {Greater} if the first + object succeeds the second (see {Universal.gt}). + + # Examples + + ``` + ordering 1 1 + ``` + + ``` + ordering 42 1 + ``` + + ``` + ordering 1 42 + ``` + }} + +-- builtin unsafe.coerceAbilities : (a ->{e1} b) -> a -> b + +unsafe.coerceAbilities.doc : Doc +unsafe.coerceAbilities.doc = + {{ + Coerces the abilities of a function to a different set of abilities. This is + unsafe because it can cause the computation to fail at runtime if it tries to + use an ability that was not included in the new set. + + This is sometimes useful when you want to use a function that has a more + general type than you need, but you know that the function will not use + abilities that you don't want to include. + }} + +Void.absurd : Void -> a +Void.absurd x = bug ("ex falso quodlibet", x) + +Void.absurd.doc : Doc +Void.absurd.doc = + {{ + The vacuous function from the empty type to any other type. This function can + never be called (without unsafe typecasting such as with {unsafeExtract}), + since there are no values of the argument type {type Void}. + + This function constitutes proof of the statement "from falsehood, anything + follows". + }} + +Void.absurdly : '{e} Void ->{e} a +Void.absurdly v = absurd v() + +Void.absurdly.doc : Doc +Void.absurdly.doc = + {{ + `` absurdly x `` takes a computation `x` that never returns (its return type + is {type Void}) and executes its effects, potentially forever. + }} + +Void.doc : Doc +Void.doc = + {{ + {type Void} is an uninhabited type. That is, there are no values of type + {type Void}. + + A simple use case for this type is to shut off impossible branches in code. + For example: + + @typecheck ``` + neverLeft : Either Void y -> y + neverLeft = cases Right y -> y + ``` + + The pattern match is exhaustive even though it only has a {Right} branch, + because there are no values of type {type Void} to match against the {Left} + branch. + + Equivalently: + + @typecheck ``` + neverLeft : Either Void y -> y + neverLeft e = Either.fold absurd id e + ``` + + {absurd} is a vacuous function which can never be given an argument, as its + input type is {type Void}. It has no implementation. + + @signature{absurd} + }} +```` + +``` ucm +scratch/main> clone @unison/http/releases/3.3.2 + + Downloaded 10829 entities. + + Cloned @unison/http/releases/3.3.2. + +@unison/http/releases/3.3.2> edit.namespace + + ☝️ + + I added 410 definitions to the top of scratch.u + + You can edit them there, then run `update` to replace the + definitions currently in this namespace. + +``` +```` unison:added-by-ucm scratch.u +type Body + = Body Bytes + +type client.Config + = { proxy : Optional proxy.Config } + +type client.failure.ConnectFailure + = + +type client.failure.InternalFailure + = + +type client.failure.Unsupported + = + +type client.failure.UpstreamFailure + = + +type client.failure.WebSocketHandsakeFailure + = + +ability client.Http where + tryRequest : HttpRequest ->{client.Http} Either Failure HttpResponse + +ability client.HttpWebSocket where + tryWebSocket : HttpRequest ->{client.HttpWebSocket} Either Failure WebSocket + +type client.proxy.Config + = { proxy : Authority } + +type Headers + = Headers (data.Map Text [Text]) + +structural type Holder g + = Holder ('{g} () ->{g} ()) + +type HttpRequest + = HttpRequest Method Version URI Headers Body + +type HttpRequest.RequestLine + = { requestMethod : Method, requestURI : URI, requestVersion : Version } + +type HttpResponse + = HttpResponse HttpResponse.Status Version Headers Body + +type HttpResponse.Status + = { code : Nat, reason : Text } + +type HttpResponse.Status.UnexpectedResponseStatus + = + +type proxy.ProxyPresence + = Proxy + | NoProxy + +type server.Config + = { hostName : Optional HostName, + port : Port, + numThreads : Nat, + tlsConfig : Optional ServerConfig } + +type server.Handler g + = HandlerWebSocket (HttpRequest ->{g, Exception, Abort} WebSocketHandler) + | Handler (HttpRequest ->{g, Exception, Abort} HttpResponse) + +type server.Routes g + = Routes + [Handler g] + (HttpRequest ->{g} HttpResponse) + (Failure -> HttpRequest ->{g} HttpResponse) + +type server.State g + = State (Routes g) Sem + +type server.WebSocketHandler + = WebSocketHandler + (WebSocket + -> ((Either Failure () ->{IO, Exception} ()) ->{IO, Exception} ()) + ->{IO, Exception} ()) + +type Version + = { major : Nat, minor : Nat } + +type websockets.Endpoint + = Client + | Server + +type websockets.errors.WebSocketClosed + = + +type websockets.Frame + = Text Boolean Text + | Close (Optional (Nat, Text)) + | Binary Boolean Bytes + | Continuation Boolean Bytes + | Ping Bytes + | Pong Bytes + +type websockets.Message + = TextMessage Text + | BinaryMessage Bytes + +type websockets.WebSocket + = WebSocket + (Message ->{IO, Exception} ()) + ('{IO, Exception} Message) + ('{IO, Exception} ()) + +Body.decodeBody : + msg -> (msg -> Headers) -> (Body -> msg -> msg) ->{Decode} (msg, Headers) +Body.decodeBody req getHeaders attach = + use Text ++ + headers = getHeaders req + expectTrailers = Headers.contains "Trailer" headers + let + (maybeCompressed, trailers) = + if isChunked headers then decodeChunkedBody expectTrailers + else (decodeNonChunkedBody headers, Headers.empty) + body = match decompressBody headers maybeCompressed with + Right b -> b + Left e -> Decode.failWith ("Error decoding HTTP body: " ++ e) + (attach body req, trailers) + +Body.decodeChunkedBody : Boolean ->{Decode} (Body, Headers) +Body.decodeChunkedBody expectTrailers = + use Decode failWith label until utf8 + use fromList impl + decodeChunkSize : '{Decode} Nat + decodeChunkSize = + do + use Text ++ + until 0xs0d0a do + label "hex-encoded chunk length" do + chunkLengthHex = utf8() + match Nat.fromHex chunkLengthHex with + None -> + failWith + ("Expected a hex-encoded chunk length but got: " + ++ chunkLengthHex) + Some chunkLength -> chunkLength + decodeBodyChunks bodyAcc = + use Nat == + chunkSize = decodeChunkSize() + if chunkSize == 0 then Body bodyAcc + else + nextChunk = + label ("Chunk of " Text.++ Nat.toText chunkSize Text.++ " bytes") do + nextBytes chunkSize + Decode.skip 2 + decodeBodyChunks (bodyAcc Bytes.++ nextChunk) + body = decodeBodyChunks Bytes.empty + if expectTrailers then + emptyTrailers = do + ignore (literalBytes 0xs0d0a) + Headers.empty + populatedTrailers = do + until 0xs0d0a0d0a do match parseHeaders utf8() with + Right headers -> headers + Left e -> failWith e + trailers = Decode.or emptyTrailers populatedTrailers + (body, trailers) + else (body, Headers.empty) + +Body.decodeNonChunkedBody : Headers ->{Decode} Body +Body.decodeNonChunkedBody headers = + match standard.contentLength.get headers with + Some byteCount -> + use Nat == + bs = if byteCount == 0 then Bytes.empty else nextBytes byteCount + Body bs + None -> Body.empty + +Body.decompressBody : Headers -> Body -> Either Text Body +Body.decompressBody headers = cases + body@(Body bs) -> + if Bytes.size bs === 0 then Right body + else + use Text ++ + encodings = getCommaDelimitedValues "Content-Encoding" headers + decode : Text -> Bytes ->{Throw Text} Bytes + decode encoding bytes = + match Map.get encoding Body.standardContentDecoders with + None -> throw ("Unknown content encoding: " ++ encoding) + Some f -> Either.toThrow (f bytes) + toEither do List.foldRight decode bs encodings |> Body + +Body.empty : Body +Body.empty = Body Bytes.empty + +Body.formEncoded : Query -> Body +Body.formEncoded = Body << Text.toUtf8 << encode.impl false + +test> Body.formEncoded.tests = + verifyAndIgnore do + use test ensureEqual + ensureEqual (Body Bytes.empty) (formEncoded Query.empty) + ensureEqual "? " (RawQuery.encode (RawQuery " ")) + ensureEqual + "baz=%25&foo=bar" + (formEncoded (Query.empty & ("foo", "bar") & ("baz", "%")) + |> Body.toBytes + |> fromUtf8) + +Body.standardContentDecoders : data.Map Text (Bytes ->{g} Either Text Bytes) +Body.standardContentDecoders = + Map.fromList + [("gzip", gzip.decompress.impl), ("deflate", zlib.decompress.impl)] + +Body.toBytes : Body -> Bytes +Body.toBytes = cases Body bs -> bs + +client.Config.default : client.Config +client.Config.default = client.Config.Config None + +client.Config.proxyPresence : client.Config -> client.proxy.ProxyPresence +client.Config.proxyPresence cfg = match client.Config.proxy cfg with + None -> client.proxy.ProxyPresence.NoProxy + Some _ -> client.proxy.ProxyPresence.Proxy + +client.examples.query : '{IO, Exception} HttpResponse +client.examples.query _ = + use Path / + google = Authority None (HostName "www.google.com") None + path = root / "search" + query = Query.empty & ("q", "Unison Programming Language") + uri = URI Scheme.https (Some google) path (fromQuery query) Fragment.empty + Http.run do Http.get uri + +client.examples.simple : '{IO, Exception} HttpResponse +client.examples.simple = + do Http.run do Http.get (parseOrBug "https://www.unison-lang.org") + +client.examples.trailingSlash : Path +client.examples.trailingSlash = + use Path / + root / "docs" / "language-reference" / "" + +client.failure.unsupportedScheme : Scheme -> Failure +client.failure.unsupportedScheme = cases + scheme@(Scheme schemeText) -> + Failure + (typeLink Unsupported) + ("Unsupported scheme: " Text.++ schemeText) + (Any (scheme : Scheme)) + +client.Http.configuredHandler : client.Config -> Request {Http} a ->{IO} a +client.Http.configuredHandler cfg = + use Either fold + go : Request {Http} a ->{IO} a + go = cases + { a } -> a + { tryRequest req -> k } -> + res = + catchAll do + bracket + (do configuredHandler.connect cfg req) + (fold (do ()) Connection.close) + (fold + Right + (conn -> + (handle withConnection conn do configuredHandler.http cfg req + with cases + { r } -> Right r + { throw decodeFailure -> _ } -> + Left (toFailure decodeFailure)))) + handle k (Either.flatMapRight id res) with go + go + +client.Http.configuredHandler.connect : + client.Config -> HttpRequest ->{IO, Exception} Either HttpResponse Connection +client.Http.configuredHandler.connect cfg req = + match client.Config.proxy cfg with + None -> Right (connectNoProxy (HttpRequest.uri req)) + Some cfg -> connectViaProxy cfg req + +client.Http.configuredHandler.connectNoProxy : URI ->{IO, Exception} Connection +client.Http.configuredHandler.connectNoProxy uri = match URI.scheme uri with + Scheme "https" -> + use URI host + sock = Socket.client (host uri) (URI.port uri) + onException (_ -> Socket.close sock) do + tryEval do + tlsConfig = default.impl (HostName.toText (host uri)) Bytes.empty + tls = newClient tlsConfig sock |> Tls.handshake + tls.deprecated tls + Scheme "http" -> + sock = Socket.client (URI.host uri) (URI.port uri) + socket sock + otherScheme -> Exception.raise (unsupportedScheme otherScheme) + +client.Http.configuredHandler.connectViaProxy : + proxy.Config -> HttpRequest ->{IO, Exception} Either HttpResponse Connection +client.Http.configuredHandler.connectViaProxy cfg origReq = + uri = HttpRequest.uri origReq + match URI.scheme uri with + Scheme "https" -> + use URI host + tlsConfig = default.impl (HostName.toText (host uri)) Bytes.empty + connectViaProxy.impl + cfg + (Some tlsConfig) + (host uri) + (URI.port uri) + (defaultProxyHeaders (HttpRequest.headers origReq)) + Scheme "http" -> + sock = Socket.client (proxy.host cfg) (proxy.port cfg) + Right (socket sock) + otherScheme -> Exception.raise (unsupportedScheme otherScheme) + +client.Http.configuredHandler.connectViaProxy.impl : + proxy.Config + -> Optional ClientConfig + -> HostName + -> Port + -> Headers + ->{IO, Exception} Either HttpResponse Connection +client.Http.configuredHandler.connectViaProxy.impl + proxyConf tlsConf host port additionalProxyHeaders = + connectReq = connectRequest.impl proxyConf host port additionalProxyHeaders + sock = Socket.client (proxy.host proxyConf) (proxy.port proxyConf) + onException (_ -> Socket.close sock) do + tryEval do + tcpConnection = socket sock + Connection.send + tcpConnection + (HttpRequest.encode client.proxy.ProxyPresence.NoProxy connectReq) + connectResponse = + HttpResponse.fromStream true (receiveByteStream tcpConnection) + if HttpResponse.isSuccess connectResponse then + match tlsConf with + None -> Right (socket sock) + Some tlsConfig -> + tls = newClient tlsConfig sock |> Tls.handshake + Right (tls.deprecated tls) + else Left connectResponse + +client.Http.configuredHandler.http : + client.Config -> HttpRequest ->{Decode, Stream Bytes} HttpResponse +client.Http.configuredHandler.http cfg = cases + origReq@(HttpRequest method version uri headers body) -> + use Headers orElse + req = + headers.modify + (origHeaders -> + orElse (orElse (forURI uri) origHeaders) (userAgent "unison-http")) + origReq + proxyPresence = + match client.Config.proxy cfg with + Some _| URI.scheme uri === Scheme.http -> + client.proxy.ProxyPresence.Proxy + _ -> client.proxy.ProxyPresence.NoProxy + reqPayload = HttpRequest.encode proxyPresence req + emit reqPayload + HttpResponse.decode (method === HEAD) + +client.Http.configuredHandler.webSocket : + client.Config + -> Connection + -> HttpRequest + ->{IO, Random} Either Failure WebSocket +client.Http.configuredHandler.webSocket cfg conn req = + go = + do + use Exception raise + keyStr = Random.bytes 2 |> toBase64 |> fromUtf8 + validate txt = + use Text ++ == + expected = + keyStr ++ magicKeyString |> Text.toUtf8 |> hashBytes Sha1 |> toBase64 + |> fromUtf8 + txt == expected + req' = + req |> setHeader "Connection" ["Upgrade"] + |> setHeader "Upgrade" ["websocket"] + |> setHeader "Sec-WebSocket-Version" ["13"] + |> setHeader "Sec-WebSocket-Key" [keyStr] + let + (resp, leftoverBytes) = + withConnection conn do + decodePartial do configuredHandler.http cfg req' + match HttpResponse.headers resp |> getValues "Sec-WebSocket-Accept" with + [x] -> + if validate x then + threadSafeWebSocket conn Client 4096 leftoverBytes + else + raise + (Failure + (typeLink WebSocketHandsakeFailure) + "Failed websocket handshake" + (Any ())) + _ -> + raise + (Failure + (typeLink WebSocketHandsakeFailure) + "Failed websocket handshake" + (Any ())) + catch go + +client.Http.delete : URI ->{Exception, Http} HttpResponse +client.Http.delete = Either.toException << tryDelete + +client.Http.get : URI ->{Exception, Http} HttpResponse +client.Http.get = Either.toException << tryGet + +client.Http.handler : Request {Http} a ->{IO} a +client.Http.handler = Http.configuredHandler Config.default + +client.Http.handler.doc : Doc +client.Http.handler.doc = + {{ + Handles each {type Http} request by opening a single-use socket to the host. + + Example usage: + + {{ docSource [docSourceElement (docEmbedTermLink do examples.simple) []] }} + + This handler supports the following: tryWebSocket : HttpRequest ->{Http} + Either Failure WebSocket + + * schemes: {Scheme.http} and {Scheme.https} + * transfer encoding: unchunked and chunked + * content encoding: `gzip` and `deflate` (zlib) + + In the future support may be added for connection reuse and conection pools. + }} + +client.Http.patch : URI -> Body ->{Exception, Http} HttpResponse +client.Http.patch uri body = Either.toException (tryPatch uri body) + +client.Http.post : URI -> Body ->{Exception, Http} HttpResponse +client.Http.post uri body = Either.toException (tryPost uri body) + +client.Http.put : URI -> Body ->{Exception, Http} HttpResponse +client.Http.put uri body = Either.toException (Http.tryPut uri body) + +client.Http.request : HttpRequest ->{Exception, Http} HttpResponse +client.Http.request request = Either.toException (tryRequest request) + +client.Http.run : '{g, Http} a ->{g, IO} a +client.Http.run = runConfigured Config.default + +client.Http.run.tests.testHttp : '{IO} [Result] +client.Http.run.tests.testHttp = + do + verifyAndIgnore do + use Nat == + uri = parseOrBug "http://www.unison-lang.org/docs/" + resp = Http.run do Http.get uri + test.ensureEqual + (HttpResponse.status resp) (Status 301 "Moved Permanently") + ensuring do + (resp |> HttpResponse.headers |> getValues "Location" |> List.size) + == 1 + ensuring do + isRight (resp |> HttpResponse.body |> Body.toBytes |> fromUtf8.impl) + +client.Http.run.tests.testHttps : '{IO} [Result] +client.Http.run.tests.testHttps = + do + verifyAndIgnore do + uri = parseOrBug "https://www.unison-lang.org/docs/" + resp = Http.run do Http.get uri + test.ensureEqual (HttpResponse.status resp) (Status 200 "OK") + ensuring do + isRight (resp |> HttpResponse.body |> Body.toBytes |> fromUtf8.impl) + +client.Http.runConfigured : client.Config -> '{g, Http} a ->{g, IO} a +client.Http.runConfigured cfg = + handler = Http.configuredHandler cfg + thunk -> (handle thunk() with handler) + +client.Http.runConfigured.tests.testProxyHttp : '{IO} [Result] +client.Http.runConfigured.tests.testProxyHttp = + do + verifyAndIgnore do + use Nat == + uri = parseOrBug "http://www.unison-lang.org/docs/" + resp = + runConfigured (client.Config.Config (Some localProxyConfig)) do + Http.get uri + test.ensureEqual + (HttpResponse.status resp) (Status 301 "Moved Permanently") + ensuring do + (resp |> HttpResponse.headers |> getValues "Location" |> List.size) + == 1 + ensuring do + isRight (resp |> HttpResponse.body |> Body.toBytes |> fromUtf8.impl) + +client.Http.runConfigured.tests.testProxyHttp.doc : Doc +client.Http.runConfigured.tests.testProxyHttp.doc = + {{ + Test using {Scheme.http} through a proxy. + + **Note:** This test requires a proxy server to be running at the address + specified in {{ docLink (docEmbedTermLink do localProxyConfig) }}. + }} + +client.Http.runConfigured.tests.testProxyHttps : '{IO} [Result] +client.Http.runConfigured.tests.testProxyHttps = + do + verifyAndIgnore do + use Nat > + uri = parseOrBug "https://www.unison-lang.org/docs/" + resp = + runConfigured (client.Config.Config (Some localProxyConfig)) do + Http.get uri + test.ensureEqual (HttpResponse.status resp) (Status 200 "OK") + bodyBytes = resp |> HttpResponse.body |> Body.toBytes + ensuring do Bytes.size bodyBytes > 0 + ensuring do isRight (fromUtf8.impl bodyBytes) + +client.Http.runConfigured.tests.testProxyHttps.doc : Doc +client.Http.runConfigured.tests.testProxyHttps.doc = + {{ + Test using {Scheme.https} through a proxy. + + **Note:** This test requires a proxy server to be running at the address + specified in {{ docLink (docEmbedTermLink do localProxyConfig) }}. + }} + +client.Http.tryDelete : URI ->{Http} Either Failure HttpResponse +client.Http.tryDelete uri = tryRequest (HttpRequest.delete uri) + +client.Http.tryGet : URI ->{Http} Either Failure HttpResponse +client.Http.tryGet uri = tryRequest (HttpRequest.get uri) + +client.Http.tryPatch : URI -> Body ->{Http} Either Failure HttpResponse +client.Http.tryPatch uri body = tryRequest (HttpRequest.patch uri body) + +client.Http.tryPost : URI ->{Http} Body ->{Http} Either Failure HttpResponse +client.Http.tryPost uri body = tryRequest (HttpRequest.post uri body) + +client.Http.tryPut : URI -> Body ->{Http} Either Failure HttpResponse +client.Http.tryPut uri body = tryRequest (HttpRequest.put uri body) + +client.HttpWebSocket.configuredHandler : + client.Config -> Request {HttpWebSocket} a ->{IO} a +client.HttpWebSocket.configuredHandler cfg = + connectFailure httpResponse = + Failure + (typeLink ConnectFailure) + "Failed to connect" + (Any (httpResponse : HttpResponse)) + go : Request {HttpWebSocket} a ->{IO} a + go = cases + { a } -> a + { tryWebSocket req -> k } -> + res = + catchAll do + go conn = + seed = nanosecondOfSecond now() + splitmix seed do configuredHandler.webSocket cfg conn req + Either.fold + (connectFailure >> Left) go (configuredHandler.connect cfg req) + handle k (Either.flatMapRight id res) with go + go + +client.HttpWebSocket.handler : Request {HttpWebSocket} a ->{IO} a +client.HttpWebSocket.handler = HttpWebSocket.configuredHandler Config.default + +client.proxy.connectRequest : proxy.Config -> HttpRequest -> HttpRequest +client.proxy.connectRequest = cases + proxyCfg, HttpRequest _ _ origUri origHeaders _ -> + headers = defaultProxyHeaders origHeaders + connectRequest.impl proxyCfg (URI.host origUri) (URI.port origUri) headers + +client.proxy.connectRequest.impl : + proxy.Config -> HostName -> Port -> Headers -> HttpRequest +client.proxy.connectRequest.impl cfg host port additionalProxyHeaders = + use Text ++ + hostHeader = + Headers.singleton "Host" (HostName.toText host ++ ":" ++ Port.toText port) + headers = Headers.orElse hostHeader additionalProxyHeaders + uri = + URI + Scheme.http + (Some (proxy.Config.proxy cfg)) + root + RawQuery.empty + Fragment.empty + HttpRequest CONNECT Version.http11 uri headers Body.empty + +test> client.proxy.connectRequest.tests.example1 = + verifyAndIgnore do + use Text ++ + origReq = + addUserAgent = + headers.modify + (Headers.union (Headers.singleton "User-Agent" "curl/7.82.0")) + test.unisonDocs |> HttpRequest.get |> addUserAgent + proxy = + host = HostName "proxy.megacorp.com" + port = Port "1080" + auth = Authority None host (Some port) + proxy.Config.Config auth + connectReq = connectRequest proxy origReq + requestText = + HttpRequest.encode client.proxy.ProxyPresence.NoProxy connectReq + |> fromUtf8 + expectedRequestText = + "CONNECT www.unison-lang.org:443 HTTP/1.1\r\n" + ++ "Host: www.unison-lang.org:443\r\n" + ++ "User-Agent: curl/7.82.0\r\n\r\n" + test.ensureEqual requestText expectedRequestText + +client.proxy.defaultProxyHeaders : Headers -> Headers +client.proxy.defaultProxyHeaders origRequestHeaders = + retainOnly (Set.fromList ["User-Agent"]) origRequestHeaders + +client.proxy.host : proxy.Config -> HostName +client.proxy.host cfg = cfg |> proxy.Config.proxy |> Authority.host + +client.proxy.port : proxy.Config -> Port +client.proxy.port cfg = + cfg |> proxy.Config.proxy |> Authority.port + |> Optional.getOrElse (Port "1080") + +client.README : Doc +client.README = + {{ + This library can be used to make HTTP requests and inspect their responses. + + # Usage + + Here is a basic example of fetching the unison-lang.org home page: + + {{ docSource [docSourceElement (docEmbedTermLink do examples.simple) []] }} + + Below is an example of making a simple HTTP request and getting back a + response. It uses the {&} helper for creating a {type RawQuery} (which will + be converted to a URI query string). + + {{ docSource [docSourceElement (docEmbedTermLink do examples.query) []] }} + + # Response Status + + By default, {{ docLink (docEmbedTermLink do Http.run) }} does not return a + {type Failure} for a non-success HTTP status code (such as + `500 Internal Server Error`). It is left up to the user to determine + whether they want to treat a `404` as an error or as an expected case which + they should handle accordingly (for example by returning {None}). You can + use {HttpResponse.isSuccess} to check whether a response has a success + code. In the future we may want to provide some helper methods for common + use-cases of status code handling. + + # Response Body + + The response body is treated as raw bytes. + + @source{type Body} + + @signature{HttpResponse.body} + + This library handles decoding chunked and compressed responses but it is up + to the user to further interpret those bytes. For example you may want to + use {fromUtf8} if you are expecting a text response, and/or you may want to + use a JSON library to parse the response as JSON. In the future we may add + more helper methods for common use-cases. + + # URI Encoding + + You should __not__ attempt to URI-encode the segments in the {type Path} or + the keys/values in the {type RawQuery}. This library will automatically + encode these values when serializing the HTTP request. + + # Trailing Slash + + According to the HTTP specification, + `http://www.unison-lang.org/docs/quickstart` and + `http://www.unison-lang.org/docs/quickstart/` (with a trailing slash) are + two different URIs. The URI __without__ the trailing slash has two path + segments: `docs` and `quickstart`. The URI __with__ the trailing slash + technically has a third path segment that is an empty string. Therefore if + you need to create a path with a trailing slash you can add an empty + segment to the end: + + {{ docSource [docSourceElement (docEmbedTermLink do trailingSlash) []] }} + + ``` + unsafeRun! do fromUtf8 (Path.encode trailingSlash) + ``` + + # Inspiration + + This library was heavily inspired by the excellent + [http4s](https://http4s.org/) Scala library. + }} + +client.test.biboResponseBytes : Bytes +client.test.biboResponseBytes = + 0xs485454502f312e3120323030204f4b0d0a582d726f626f74732d7461673a206e6f696e6465782c6e6f666f6c6c6f770d0a436f6e74656e742d747970653a20746578742f68746d6c3b20636861727365743d5554462d380d0a582d4672616d652d4f7074696f6e733a2064656e790d0a582d5853532d50726f74656374696f6e3a20313b206d6f64653d626c6f636b0d0a582d436f6e74656e742d547970652d4f7074696f6e733a206e6f736e6966660d0a436f6e74656e742d53656375726974792d506f6c6963793a2064656661756c742d737263202773656c66272027756e736166652d696e6c696e65272027756e736166652d6576616c273b207374796c652d737263202773656c66272027756e736166652d696e6c696e65272027756e736166652d6576616c273b206672616d652d737263202773656c66272027756e736166652d696e6c696e65272027756e736166652d6576616c273b20666f6e742d737263202773656c66272027756e736166652d696e6c696e65272027756e736166652d6576616c273b20666f726d2d616374696f6e202773656c66272027756e736166652d696e6c696e65272027756e736166652d6576616c273b207363726970742d737263202773656c66272027756e736166652d696e6c696e65272027756e736166652d6576616c273b20696d672d737263202773656c66273b20636f6e6e6563742d737263202773656c66273b206f626a6563742d73726320276e6f6e65273b206d656469612d73726320276e6f6e65273b207363726970742d6e6f6e636520276e6f6e65273b20706c7567696e2d747970657320276e6f6e65273b207265666c65637465642d78737320276e6f6e65273b207265706f72742d75726920276e6f6e65273b0d0a436f6e74656e742d4c656e6774683a20383437310d0a446174653a204672692c203031204f637420323032312032323a31373a303620474d540d0a5365727665723a205866696e6974792042726f616462616e6420526f75746572205365727665720d0a0d0a + +client.test.biboResponseText : Text +client.test.biboResponseText = + """ + HTTP/1.1 200 OK + X-robots-tag: noindex,nofollow + Content-type: text/html; charset=UTF-8 + X-Frame-Options: deny + X-XSS-Protection: 1; mode=block + X-Content-Type-Options: nosniff + Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' 'unsafe-eval'; frame-src 'self' 'unsafe-inline' 'unsafe-eval'; font-src 'self' 'unsafe-inline' 'unsafe-eval'; form-action 'self' 'unsafe-inline' 'unsafe-eval'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self'; connect-src 'self'; object-src 'none'; media-src 'none'; script-nonce 'none'; plugin-types 'none'; reflected-xss 'none'; report-uri 'none'; + Content-Length: 8471 + Date: Fri, 01 Oct 2021 22:17:06 GMT + Server: Xfinity Broadband Router Server + + + """ + +client.test.formatIOError : IOError -> Text +client.test.formatIOError = cases + AlreadyExists -> "Already Exists" + NoSuchThing -> "NoSuchThing" + ResourceBusy -> "ResourceBusy" + ResourceExhausted -> "ResourceExhausted" + EOF -> "EOF" + IllegalOperation -> "IllegalOperation" + PermissionDenied -> "PermissionDenied" + UserError -> "UserError" + +client.test.header_lines : [Text] +client.test.header_lines = + [ "Location: http://www.google.com/" + , "Content-Type: text/html; charset=UTF-8" + , "Date: Sat, 10 Oct 2020 16:48:40 GMT" + , "Expires: Mon, 09 Nov 2020 16:48:40 GMT" + , "Cache-Control: public, max-age=2592000" + , "Server: gws" + , "Content-Length: 219" + , "X-XSS-Protection: 0" + , "X-Frame-Options: SAMEORIGIN" + ] + +client.test.localProxyConfig : proxy.Config +client.test.localProxyConfig = + proxy.Config.Config + (Authority None (HostName "localhost") (Some (Port "3128"))) + +client.test.redir_response_bytes : Bytes +client.test.redir_response_bytes = + lines : [[Char]] + lines = List.map toCharList redir_response_lines + chars = intercalate [?\r, ?\n] lines + chars |> fromCharList |> Text.toUtf8 + +client.test.redir_response_lines : [Text] +client.test.redir_response_lines = + [ "HTTP/1.1 301 Moved Permanently" + , "Location: http://www.google.com/" + , "Content-Type: text/html; charset=UTF-8" + , "Date: Sat, 10 Oct 2020 16:48:40 GMT" + , "Expires: Mon, 09 Nov 2020 16:48:40 GMT" + , "Cache-Control: public, max-age=2592000" + , "Server: gws" + , "Content-Length: 219" + , "X-XSS-Protection: 0" + , "X-Frame-Options: SAMEORIGIN" + , "" + , "" + , "301 Moved" + , "

301 Moved

" + , "The document has moved" + , "here." + , "" + ] + +client.test.redir_response_text : Text +client.test.redir_response_text = + lines : [[Char]] + lines = List.map toCharList redir_response_lines + chars = intercalate [?\r, ?\n] lines + chars |> fromCharList + +client.test.socketSlurp : Socket ->{IO, Exception} Bytes +client.test.socketSlurp sock = + get = 5 + go acc = + use Bytes ++ + use Nat == + printLine "a" + new = receiveAtMost sock get + printLine "b" + got = Bytes.size new + printLine (Nat.toText got) + if got == get then go (acc ++ new) else acc ++ new + go Bytes.empty + +client.test.testGet : '{IO, Exception} Text +client.test.testGet _ = + use Text ++ + (HttpResponse status version headers (Body body)) = + handle Http.get test.unisonDocs with Http.handler + match status with + Status 200 _ -> fromUtf8 body + Status code text -> + Exception.raise + (Failure + (typeLink Unit) + ("Failed (" ++ Nat.toText code ++ "), Reason: " ++ text) + (Any code)) + +client.test.unisonDocs : URI +client.test.unisonDocs = + use Path / + URI + Scheme.https + (Some (Authority None (HostName "www.unison-lang.org") None)) + (root / "learn" / "") + RawQuery.empty + Fragment.empty + +client.up.base.IO.net.Connection.receiveByteStream : + Connection -> '{IO, Exception, Stream Bytes} () +client.up.base.IO.net.Connection.receiveByteStream connection = + receiveBytes = do + use Nat == + chunk = base.IO.net.Connection.receive connection + if Bytes.size chunk == 0 then () + else + emit chunk + receiveBytes() + receiveBytes + +(Headers.==) : Headers -> Headers -> Boolean +(Headers.==) = cases Headers h1, Headers h2 -> h1 Map.== h2 + +Headers.add : Text -> Text -> Headers -> Headers +Headers.add name value = cases + Headers headers -> Headers (Map.putWith (List.++) name [value] headers) + +Headers.addHeader.doc : Doc +Headers.addHeader.doc = + {{ + Adds a header to an {type HttpRequest}. + + If the header already exists, the value is appended to the existing header. + }} + +Headers.asBytes : Headers -> Bytes +Headers.asBytes = + crlf = 0xs0d0a + cases + Headers headers -> + keyValueBytes key value = + Text.toUtf8 (key Text.++ ": " Text.++ value) Bytes.++ crlf + headerLines = + List.flatMapRight + (cases (key, values) -> List.map (keyValueBytes key) values) + (Map.toList headers) + List.foldLeft (Bytes.++) Bytes.empty headerLines + +test> Headers.asBytes.tests.multivalue = + headers = + Map.fromList + [("Host", ["localhost:1234"]), ("User-Agent", ["cody/1.0", "stew/1.1"])] + expected = + Text.toUtf8 + "Host: localhost:1234\r\nUser-Agent: cody/1.0\r\nUser-Agent: stew/1.1\r\n" + actual = asBytes (Headers headers) + check (actual === expected) + +Headers.contains : Text -> Headers -> Boolean +Headers.contains name = cases Headers m -> Map.contains name m + +Headers.delete : Text -> Headers -> Headers +Headers.delete name = cases + Headers headers -> Headers (data.Map.delete name headers) + +Headers.empty : Headers +Headers.empty = Headers data.Map.empty + +Headers.fromList : [(Text, Text)] -> Headers +Headers.fromList headers = + use List :+ + use Map insert + use data Map + update : Text -> Text -> Map Text [Text] -> Map Text [Text] + update k v m = match Map.get k m with + Some vs -> insert k (vs :+ v) m + None -> insert k [v] m + go acc = cases + [] -> acc + (k, v) +: tail -> go (update k v acc) tail + Headers (go data.Map.empty headers) + +Headers.getCommaDelimitedValues : Text -> Headers -> [Text] +Headers.getCommaDelimitedValues key headers = + parse : Text -> [Text] + parse t = List.map trim (Text.split ?, t) + List.flatMapRight parse (getValues key headers) + +Headers.getDateTime : Text -> Headers ->{Exception} [OffsetDateTime] +Headers.getDateTime headerName headers = + vs = getValues headerName headers + Abort.toGenericException "Invalid date" vs do List.map offsetDateTime vs + +Headers.getDateTime.doc : Doc +Headers.getDateTime.doc = + {{ + Gets the date and time value of a header, as an {type OffsetDateTime}. + + The header value is parsed as a date and time in the format specified by + [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231). + + If the header is not present, or if the value is not a valid date and time, + an exception is thrown. + }} + +Headers.getInstant : Text -> Headers ->{Exception} [Instant] +Headers.getInstant headerName headers = + vs = getValues headerName headers + Abort.toGenericException "Invalid date" vs do + List.map (offsetDateTime >> toInstant) vs + +Headers.getInstant.doc : Doc +Headers.getInstant.doc = + {{ + Gets the date and time value of a header with the given name, as an + {type Instant}. + + The header value is parsed as a date and time in the format specified by + [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231). + + If the header is not present, or if the value is not a valid date and time, + an exception is thrown. + }} + +Headers.getValues : Text -> Headers -> [Text] +Headers.getValues = + asciiToLower : Text -> Text + asciiToLower = fromCharList << List.map ascii.toLower << toCharList + keys key = + use Text == + lower = asciiToLower key + if lower == key then [key] else [key, lower] + lookup m key = Map.getOrElse [] key m + key ve6ccs68a11 -> let + (Headers m) = ve6ccs68a11 + List.flatMapRight (lookup m) (keys key) + +Headers.HttpDate.fromGMTDateTime : LocalDateTime -> Text +Headers.HttpDate.fromGMTDateTime = cases + LocalDateTime d@(LocalDate year month day) (LocalTime hour minute second _) -> + use Nat - + use Text ++ + wkDay = LocalDate.dayOfWeek d + dd = leftPad 2 "0" (Nat.toText day) + mon = List.at (month - 1) monthNamesShort |> Optional.getOrElse "???" + yyyy = leftPad 4 "0" (Int.toText year) + hh = leftPad 2 "0" (Nat.toText hour) + mm = leftPad 2 "0" (Nat.toText minute) + ss = leftPad 2 "0" (Nat.toText second) + shortName wkDay ++ ", " ++ dd ++ " " ++ mon ++ " " ++ yyyy ++ " " ++ hh + ++ ":" + ++ mm + ++ ":" + ++ ss + ++ " GMT" + +Headers.HttpDate.fromGMTDateTime.doc : Doc +Headers.HttpDate.fromGMTDateTime.doc = + {{ + Converts a {type LocalDateTime} to a {type Text} in the format specified by + RFC 1123. This is the format used in HTTP headers. + + # Example + + ``` + fromGMTDateTime + (LocalDateTime (LocalDate +2023 6 18) (LocalTime 23 45 0 0)) + ``` + }} + +Headers.HttpDate.fromInstant : Instant -> Text +Headers.HttpDate.fromInstant = + atUTC >> OffsetDateTime.localDateTime >> fromGMTDateTime + +Headers.HttpDate.fromInstant.doc : Doc +Headers.HttpDate.fromInstant.doc = + use HttpDate fromInstant + {{ + Converts an {type Instant} to a {type Text} in the format specified by RFC + 1123. This is the format used in HTTP headers. + + # Examples + + ``` + fromInstant (fromEpochMilliseconds +1624068300000) + ``` + + ``` + fromInstant epoch + ``` + }} + +Headers.HttpDate.fromOffsetDateTime : OffsetDateTime -> Text +Headers.HttpDate.fromOffsetDateTime = cases + OffsetDateTime (UTCOffset o) dt -> + fromGMTDateTime + (LocalDateTime.addDuration dt (Duration.negate (minutes o))) + +Headers.HttpDate.fromOffsetDateTime.doc : Doc +Headers.HttpDate.fromOffsetDateTime.doc = + {{ + Converts a {type OffsetDateTime} to a {type Text} in the format specified by + RFC 1123. This is the format used in HTTP headers. + + # Example + + ``` + fromOffsetDateTime + (OffsetDateTime + AST (LocalDateTime (LocalDate +2023 6 18) (LocalTime 23 45 0 0))) + ``` + }} + +Headers.HttpDate.parse.localDate : Text ->{Abort} LocalDate +Headers.HttpDate.parse.localDate text = + match Optional.toAbort (Pattern.run HttpDate.patterns.rfc7231Date text) with + ([weekday, day, month, year], _) -> + use Int + < + use Optional toAbort + month' = match month with + "Jan" -> 1 + "Feb" -> 2 + "Mar" -> 3 + "Apr" -> 4 + "May" -> 5 + "Jun" -> 6 + "Jul" -> 7 + "Aug" -> 8 + "Sep" -> 9 + "Oct" -> 10 + "Nov" -> 11 + "Dec" -> 12 + _ -> abort + day' = toAbort (Nat.fromText day) + year' = toAbort (Int.fromText year) + year'' = + if year' < +100 then year' + (if year' < +50 then +2000 else +1900) + else year' + LocalDate year' month' day' + _ -> abort + +Headers.HttpDate.parse.localDateTime : Text ->{Abort} LocalDateTime +Headers.HttpDate.parse.localDateTime text = + match Optional.toAbort + (Pattern.run + (Pattern.join + [ Pattern.capture HttpDate.patterns.rfc7231Date + , Pattern.some space + , Pattern.capture HttpDate.patterns.rfc7231Time + ]) + text) with + ([dt, tm], _) -> LocalDateTime (localDate dt) (localTime tm) + _ -> abort + +Headers.HttpDate.parse.localTime : Text ->{Abort} LocalTime +Headers.HttpDate.parse.localTime text = + match Optional.toAbort (Pattern.run HttpDate.patterns.rfc7231Time text) with + ([hour, minute, second], _) -> + LocalTime + (Optional.toAbort (Nat.fromText hour)) + (Optional.toAbort (Nat.fromText minute)) + (Optional.toAbort (Nat.fromText second)) + 0 + e -> abort + +Headers.HttpDate.parse.offsetDateTime : Text ->{Abort} OffsetDateTime +Headers.HttpDate.parse.offsetDateTime text = + OffsetDateTime UTC (parse.localDateTime text) + +Headers.HttpDate.parse.offsetDateTime.doc : Doc +Headers.HttpDate.parse.offsetDateTime.doc = + {{ + Parses a date and time in the HTTP date format specified by + [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231). + + The date and time are assumed to be in UTC. + + If the date and time is not in the correct format, an exception is thrown. + }} + +Headers.HttpDate.patterns.asctimeFormat : Pattern Text +Headers.HttpDate.patterns.asctimeFormat = + use Pattern capture some + use patterns digit + Pattern.join + [ capture (some wordChar) + , literal " " + , capture (some wordChar) + , literal " " + , Pattern.optional (literal " ") + , capture (some digit) + , literal " " + , capture (some digit) + , literal ":" + , capture (some digit) + , literal ":" + , capture (some digit) + , literal " " + , capture (some digit) + ] + +Headers.HttpDate.patterns.asctimeFormat.doc : Doc +Headers.HttpDate.patterns.asctimeFormat.doc = + {{ + A {type Pattern} for the RFC 7231 obsolete asctime() format. + + # Example + + ``` + Pattern.run HttpDate.patterns.asctimeFormat "Sun Nov 6 08:49:37 1994" + ``` + + See + [https://tools.ietf.org/html/rfc7231](https://tools.ietf.org/html/rfc7231) + }} + +test> Headers.HttpDate.patterns.asctimeFormat.test = + parsed = + Pattern.run HttpDate.patterns.asctimeFormat "Sun Nov 6 08:49:37 1994" + check (parsed === Some (["Sun", "Nov", "6", "08", "49", "37", "1994"], "")) + +Headers.HttpDate.patterns.rfc7231Date : Pattern Text +Headers.HttpDate.patterns.rfc7231Date = + use Pattern capture or some + use patterns digit + Pattern.join + [ capture (some wordChar) + , literal ", " + , capture (some digit) + , or (literal " ") (literal "-") + , capture (some wordChar) + , or (literal " ") (literal "-") + , capture (some digit) + ] + +Headers.HttpDate.patterns.rfc7231DateTime : Pattern Text +Headers.HttpDate.patterns.rfc7231DateTime = + Pattern.join + [ HttpDate.patterns.rfc7231Date + , Pattern.some (patterns.char whitespace) + , HttpDate.patterns.rfc7231Time + ] + +Headers.HttpDate.patterns.rfc7231DateTime.doc : Doc +Headers.HttpDate.patterns.rfc7231DateTime.doc = + use HttpDate.patterns rfc7231DateTime + use Pattern run + {{ + A {type Pattern} for the RFC 7231 date/time format. + + Matches both the preferred HttpDate (IMF-fixdate) format as well as the + obsolete RFC-850 format. + + This is used to parse date/time fields in HTTP headers. + + # Examples + + ``` + run rfc7231DateTime "Wed, 12 Oct 2022 18:32:45 GMT" + ``` + + ``` + run rfc7231DateTime "Sunday, 06-Nov-94 08:49:37 GMT" + ``` + + See + [https://tools.ietf.org/html/rfc7231](https://tools.ietf.org/html/rfc7231) + }} + +test> Headers.HttpDate.patterns.rfc7231DateTime.test850 = + parsed = + Pattern.run + HttpDate.patterns.rfc7231DateTime "Sunday, 06-Nov-94 08:49:37 GMT" + check + (parsed === Some (["Sunday", "06", "Nov", "94", "08", "49", "37"], " GMT")) + +test> Headers.HttpDate.patterns.rfc7231DateTime.testIMF = + parsed = + Pattern.run + HttpDate.patterns.rfc7231DateTime "Wed, 12 Oct 2022 18:32:45 GMT" + check + (parsed === Some (["Wed", "12", "Oct", "2022", "18", "32", "45"], " GMT")) + +Headers.HttpDate.patterns.rfc7231Time : Pattern Text +Headers.HttpDate.patterns.rfc7231Time = + use Pattern capture replicate + use patterns digit + Pattern.join + [ capture (replicate 2 2 digit) + , literal ":" + , capture (replicate 2 2 digit) + , literal ":" + , capture (replicate 2 2 digit) + ] + +Headers.isChunked : Headers -> Boolean +Headers.isChunked headers = + List.any ((===) "chunked") (getValues "Transfer-Encoding" headers) + +Headers.isEmpty : Headers -> Boolean +Headers.isEmpty = cases Headers h -> Map.isEmpty h + +Headers.orElse : Headers -> Headers -> Headers +Headers.orElse = cases Headers h1, Headers h2 -> Headers (Map.union h1 h2) + +Headers.orElse.doc : Doc +Headers.orElse.doc = + {{ + {Headers.orElse} `primary` `backup` performs a left-biased union of headers. + The result will contain all headers from `primary` and will only contain + headers from `backup` if their key was not present in `primary`. + + This is different than {Headers.union}, which will concatenate the value + lists for matching keys. + }} + +test> Headers.orElse.test = verifyAndIgnore do + use Headers == fromList + primary = fromList [("a", "1"), ("b", "2"), ("c", "3")] + backup = fromList [("b", "4"), ("c", "5"), ("d", "6")] + expected = fromList [("a", "1"), ("b", "2"), ("c", "3"), ("d", "6")] + result = Headers.orElse primary backup + ensureEqualBy (==) result expected + +Headers.parseHeaders : Text -> Either Text Headers +Headers.parseHeaders = + use Text ++ + pairs : [Text] -> [(Text, Text)] + pairs as = + pair = cases + [a, b] ++ rest -> Some ((a, b), rest) + _ -> None + List.unfold as pair + headerPattern = + use Pattern capture join many + space = patterns.char (anyOf [?\s, ?\t]) + colon = join [literal ":", many space] + payload = many (notCharIn [?\n, ?\r]) + join [capture token, colon, capture payload] + headersPattern = sepMany (literal "\r\n") headerPattern + headersText -> + (match Pattern.run headersPattern headersText with + Some (headerPairs, "") -> Right (Headers.fromList (pairs headerPairs)) + Some (_, extra) -> + Left ("Unexpected content after HTTP headers\n" ++ extra) + None -> Left ("Invalid HTTP headers\n" ++ headersText)) + +Headers.requireHeader : Text -> Headers ->{Exception} Text +Headers.requireHeader h hs = + hs |> getValues h |> List.head + |> (cases + None -> + Exception.raise + <| Failure + (typeLink Headers) + ("Missing " Text.++ h Text.++ " header in response.") + (Any hs) + Some v -> v) + +Headers.requireHeader.doc : Doc +Headers.requireHeader.doc = + {{ + Given a {type Text} header name and a {type Headers} object, returns the + value of the header. If the header is not present, this raises an + {type Exception}. + }} + +Headers.retainOnly : Set Text -> Headers -> Headers +Headers.retainOnly toRetain origHeaders = + use Text toLowercase + toRetainLowercase = Set.map toLowercase toRetain + retain k = Set.contains (toLowercase k) toRetainLowercase + origHeaders |> Headers.toMap |> up.base.data.Map.filterKeys retain |> Headers + +Headers.setHeader.doc : Doc +Headers.setHeader.doc = + {{ + Sets a header on an {type HttpRequest}. + + If the header already exists, the value is replaced. + }} + +Headers.singleton : Text -> Text -> Headers +Headers.singleton k v = Headers (Map.singleton k [v]) + +Headers.standard.accept : Text -> Headers +Headers.standard.accept = Headers.singleton "Accept" + +Headers.standard.acceptEncoding.default : Headers +Headers.standard.acceptEncoding.default = + Headers (Map.fromList [("Accept-Encoding", ["gzip", "deflate"])]) + +Headers.standard.contentLength : Body -> Headers +Headers.standard.contentLength = cases + Body bytes + | Bytes.size bytes === 0 -> Headers.empty + | otherwise -> + Headers.singleton "Content-Length" (Nat.toText (Bytes.size bytes)) + +Headers.standard.contentLength.get : Headers -> Optional Nat +Headers.standard.contentLength.get headers = + findMap Nat.fromText (getValues "Content-Length" headers) + +Headers.standard.host.forAuthority : Authority -> Headers +Headers.standard.host.forAuthority = cases + Authority _ (HostName h) port -> + use Text ++ + host = Optional.fold (do h) (cases Port p -> h ++ ":" ++ p) port + Headers.singleton "Host" host + +test> Headers.standard.host.forAuthority.tests.noPort = + authority = Authority None (HostName "geocities.com") None + expected = Headers.singleton "Host" "geocities.com" + actual = forAuthority authority + check (actual === expected) + +test> Headers.standard.host.forAuthority.tests.withPort = + authority = Authority None (HostName "geocities.com") (Some (Port "22")) + expected = Headers.singleton "Host" "geocities.com:22" + actual = forAuthority authority + check (actual === expected) + +Headers.standard.host.forURI : URI -> Headers +Headers.standard.host.forURI uri = + Optional.fold (do Headers.empty) forAuthority (URI.authority uri) + +Headers.standard.location.get : Headers -> Optional Path +Headers.standard.location.get = + use Nat > + parsePath : Text -> Optional Path + parsePath pathText = + match Pattern.run (Pattern.capture pattern.path) pathText with + Some (_, remain) | Text.size remain > 0 -> None + Some ([path], _) -> toOptional! do _internal.parsePath path + _ -> None + getValues "Location" >> findMap parsePath + +test> Headers.standard.location.get.tests = + verifyAndIgnore do + use Headers singleton + use location get + use test ensureEqual + ensureEqual + (Path ["foo", "bar"] |> Some) (get (singleton "Location" "/foo/bar")) + ensureEqual None (get (singleton "Stuff" "/foo/bar")) + ensureEqual None (get Headers.empty) + +Headers.standard.request.contentLength.doc : Doc +Headers.standard.request.contentLength.doc = + {{ + Creates the `Content-Length` header with the correct length for the number of + {type Bytes} in the given request body. + + If the given body is empty, the header will be omitted. + }} + +Headers.standard.response.contentLength.doc : Doc +Headers.standard.response.contentLength.doc = + {{ + Creates the `Content-Length` header with the correct length for the number of + {type Bytes} in the given response body. + + If the given body is empty, the header value will be set to `"0"`. + }} + +Headers.standard.userAgent : Text -> Headers +Headers.standard.userAgent = Headers.singleton "User-Agent" + +Headers.toMap : Headers -> data.Map Text [Text] +Headers.toMap = cases Headers h -> h + +Headers.union : Headers -> Headers -> Headers +Headers.union = cases + Headers h1, Headers h2 -> Headers (Map.unionWith (List.++) h1 h2) + +Headers.union.doc : Doc +Headers.union.doc = + {{ + Create a new {type Headers} instance with all of the headers of the first + argument __and__ all of the headers from the second argument. + + NOTE: if both headers include the same key, the value lists for that key are + concatenated. This means that you can potentially end up with duplicate + values for a key. + }} + +test> Headers.union.tests.nonempty = + use Map fromList + h1 = Headers (fromList [("key1", ["v1", "v2"]), ("key2", ["v3", "v4"])]) + h2 = + Headers + (fromList + [ ("key1", ["v1", "v5"]) + , ("key2", ["v6", "v4", "v7"]) + , ("key3", ["v8"]) + ]) + expected = + Headers + (fromList + [ ("key1", ["v1", "v2", "v1", "v5"]) + , ("key2", ["v3", "v4", "v6", "v4", "v7"]) + , ("key3", ["v8"]) + ]) + actual = Headers.union h1 h2 + check (actual === expected) + +test> Headers.union.tests.withEmpty = + use Headers union + h1 = Headers.empty + h2 = Headers (Map.fromList [("key1", ["v1", "v2"]), ("key2", ["v3", "v4"])]) + check (union h1 h2 === h2 && union h1 h2 === union h2 h1) + +Holder.wrap : ('{g} () ->{g} ()) -> Holder g +Holder.wrap effect = Holder effect + +(HttpRequest.==) : HttpRequest -> HttpRequest -> Boolean +(HttpRequest.==) = cases + HttpRequest method1 version1 uri1 headers1 body1, + HttpRequest method2 version2 uri2 headers2 body2 -> + method1 === method2 && version1 === version2 && uri1 === uri2 + && headers1 Headers.== headers2 + && body1 === body2 + +HttpRequest.addHeader : Text -> Text -> HttpRequest -> HttpRequest +HttpRequest.addHeader name value = cases + HttpRequest method version uri headers body -> + HttpRequest method version uri (Headers.add name value headers) body + +HttpRequest.body : HttpRequest -> Body +HttpRequest.body = cases HttpRequest _ _ _ _ b -> b + +HttpRequest.body.set : Body -> HttpRequest -> HttpRequest +HttpRequest.body.set body = cases + HttpRequest m v u h _ -> HttpRequest m v u h body + +HttpRequest.decode : '{Decode} HttpRequest +HttpRequest.decode = + pairs : [Text] -> [(Text, Text)] + pairs as = + pair = cases + [a, b] ++ rest -> Some ((a, b), rest) + _ -> None + List.unfold as pair + do + use Decode failWith + use Text ++ + head = + Decode.label "HTTP request head" do Decode.until 0xs0d0a0d0a Decode.utf8 + req = + match IPattern.run requestLine head with + Some + ( [method, scheme, userInfo, host, port, path, query, fragment, version], + headersWithTrailingCrLf) -> + method' = + Method.fromText method + |> (getOrElse' do failWith ("Invalid method: " ++ method)) + version' = match Version.fromText version with + Right v -> v + Left t -> failWith ("Unsupported HTTP version: " ++ t) + path' = getOrBug "heir part" (toOptional! do parsePath path) + authority = + handle parseAuthority userInfo host port + with cases + { a } -> a + { abort -> _ } -> + failWith + ("Invalid Authority (" + ++ Text.join ", " [userInfo, host, port] + ++ ")") + uri = + URI + (Scheme scheme) + authority + path' + (RawQuery query) + (Fragment fragment) + headers = + match parseHeaders (Text.drop 2 headersWithTrailingCrLf) with + Right headers -> headers + Left e -> failWith e + HttpRequest method' version' uri headers Body.empty + _ -> failWith ("Invalid HTTP request line\n" ++ head) + match decodeBody req HttpRequest.headers HttpRequest.body.set with + (req, trailers) -> + (HttpRequest m v u h b) = req + HttpRequest m v u (Headers.union h trailers) b + +HttpRequest.decodeHeadersOnly.doc : Doc +HttpRequest.decodeHeadersOnly.doc = + {{ + an alternative to HttpRequest.decode that doesn't expoect a body. This is to + be used when parsing the response to a HEAD request. + }} + +HttpRequest.delete : URI -> HttpRequest +HttpRequest.delete uri = + headers = Headers.union (forURI uri) acceptEncoding.default + HttpRequest DELETE Version.http11 uri headers Body.empty + +HttpRequest.doc : Doc +HttpRequest.doc = + use HttpRequest post + {{ + # HttpRequest + + The {type HttpRequest} type represents an HTTP request as defined by + [RFC 2616](https://www.rfc-editor.org/rfc/rfc2616). + + It could be used to represent either a request received by a server, or a + request to be sent by a client. + + ## Constructing an {type HttpRequest} + + There are a number of helper methods for constructing an + {type HttpRequest}: + + * {HttpRequest.get} + * {post} + * {HttpRequest.put} + * {HttpRequest.delete} + + ``` + uri = parseOrBug "https://post.it/here" + body = "{\"Hello\": \"World\"}" |> Text.toUtf8 |> Body + post uri body + ``` + + Or you could construct a HttpRequest directly using its constructor: + + ``` + uri = parseOrBug "http://some.where" + HttpRequest POST Version.http11 uri Headers.empty (Body 0xs) + ``` + }} + +HttpRequest.encode : proxy.ProxyPresence -> HttpRequest -> Bytes +HttpRequest.encode proxyPresence req = + use Bytes ++ + headers = HttpRequest.encodeNoBody proxyPresence req + let + (Body body) = HttpRequest.body req + headers ++ body + +HttpRequest.encode.nonProxyRequestLine : HttpRequest -> Bytes +HttpRequest.encode.nonProxyRequestLine = cases + HttpRequest method version uri headers _ -> + use Bytes ++ + use Text toUtf8 + space = 0xs20 + resource = + match method with + CONNECT -> + authorityText = match getValues "Host" headers with + host +: _ -> host + [] -> forceHostAndPort uri |> Authority.toText + toUtf8 authorityText + _ -> + Path.encode (URI.path uri) + ++ toUtf8 (RawQuery.encode (URI.query uri)) + toUtf8 (Method.toText method) ++ space ++ resource ++ space + ++ toUtf8 (Version.toText version) + +HttpRequest.encode.proxyRequestLineText : HttpRequest -> Text +HttpRequest.encode.proxyRequestLineText = cases + HttpRequest method version uri _ _ -> + use Text ++ + space = " " + uri' = match method with + OPTIONS -> + u = URI.toText uri + if u === "/*" then "*" else u + _ -> URI.toText uri + Method.toText method ++ space ++ URI.toText uri ++ space + ++ Version.toText version + +HttpRequest.encodeChunked : + proxy.ProxyPresence + -> HttpRequest + -> '{Stream Bytes} Headers + ->{Stream Bytes} () +HttpRequest.encodeChunked proxyPresence request body = + request' = HttpRequest.addHeader "Transfer-Encoding" "chunked" request + emit (HttpRequest.encodeNoBody proxyPresence request') + encodeChunkedBody body + +HttpRequest.encodeChunked.doc : Doc +HttpRequest.encodeChunked.doc = + {{ + Given an {type HttpRequest}, and a {type Stream} of {type Bytes}, encode this + request into a stream of bytes represeinting the request. If the given + request has a body, the body will be encoded as the first chunk, and each + {type Bytes}. Any {type Headers} returned at the end of the Stream will be + encoded as Trailers + }} + +test> HttpRequest.encodeChunked.tests.withoutTrailers = + verifyAndIgnore do + use Bytes ++ + bodyStream = do + use Text toUtf8 + emit (toUtf8 "hello") + emit (toUtf8 ", ") + emit Bytes.empty + emit (toUtf8 "world!") + emit (toUtf8 "Sincerely,\n me") + Headers.empty + head = HttpRequest.post (parseOrBug "http://google.com") Body.empty + requestStream = + do HttpRequest.encodeChunked proxy.ProxyPresence.NoProxy head bodyStream + encodedRequest = Stream.fold (++) Bytes.empty requestStream |> fromUtf8 + expected = + Text.join + "\n" + [ "POST / HTTP/1.1\r" + , "Accept-Encoding: gzip\r" + , "Accept-Encoding: deflate\r" + , "Host: google.com\r" + , "Transfer-Encoding: chunked\r" + , "\r" + , "5\r" + , "hello\r" + , "2\r" + , ", \r" + , "6\r" + , "world!\r" + , "F\r" + , "Sincerely,\n me\r" + , "0\r" + , "\r\n" + ] + test.ensureEqual encodedRequest expected + +test> HttpRequest.encodeChunked.tests.withTrailers = + verifyAndIgnore do + use Bytes ++ + use HttpRequest addHeader + use Text toUtf8 + trailers = + Headers.fromList + [ ("Expires", "Wed, 21 Oct 2015 07:28:00 GMT") + , ("Client-Timing", "cache;desc=\"Cache Read\";dur=23.2") + ] + bodyStream = do + emit (toUtf8 "hello") + emit (toUtf8 ", ") + emit Bytes.empty + emit (toUtf8 "world!") + emit (toUtf8 "Sincerely,\n me") + trailers + head = + HttpRequest.post (parseOrBug "http://google.com") Body.empty + |> addHeader "Trailer" "Expires" + |> addHeader "Trailer" "Client-Timing" + requestStream = + do HttpRequest.encodeChunked proxy.ProxyPresence.NoProxy head bodyStream + encodedRequest = Stream.fold (++) Bytes.empty requestStream + expected = + toUtf8 + <| Text.join + "\n" + [ "POST / HTTP/1.1\r" + , "Accept-Encoding: gzip\r" + , "Accept-Encoding: deflate\r" + , "Host: google.com\r" + , "Trailer: Client-Timing\r" + , "Trailer: Expires\r" + , "Transfer-Encoding: chunked\r" + , "\r" + , "5\r" + , "hello\r" + , "2\r" + , ", \r" + , "6\r" + , "world!\r" + , "F\r" + , "Sincerely,\n me\r" + , "0\r" + , "Client-Timing: cache;desc=\"Cache Read\";dur=23.2\r" + , "Expires: Wed, 21 Oct 2015 07:28:00 GMT\r" + , "\r\n" + ] + test.ensureEqual encodedRequest expected + +HttpRequest.encodeNoBody : proxy.ProxyPresence -> HttpRequest -> Bytes +HttpRequest.encodeNoBody proxyPresence req = + use Bytes ++ + use Headers orElse + use fromList impl + (HttpRequest method version uri headers body) = req + headers' = + orElse (forURI uri) (standard.contentLength body) |> orElse headers + requestLine = match proxyPresence with + proxy.ProxyPresence.Proxy -> proxyRequestLineText req |> Text.toUtf8 + proxy.ProxyPresence.NoProxy -> nonProxyRequestLine req + requestLine ++ 0xs0d0a ++ asBytes headers' ++ 0xs0d0a + +HttpRequest.encodeNoBody.doc : Doc +HttpRequest.encodeNoBody.doc = + {{ + Encodes the head (request line and headers) of an HTTP request, then `\r\n`, + but not the body. + }} + +HttpRequest.ensureEqual : HttpRequest -> HttpRequest ->{Exception} () +HttpRequest.ensureEqual a b = + use HttpRequest == + if a == b then () else test.raiseFailure "HttpRequests not equal" (a, b) + +HttpRequest.fromBytes : Bytes ->{Exception} HttpRequest +HttpRequest.fromBytes bs = HttpRequest.fromStream do emit bs + +HttpRequest.fromBytes.doc : Doc +HttpRequest.fromBytes.doc = {{ Parse an {type HttpRequest} }} + +HttpRequest.fromStream : '{g, Stream Bytes} a ->{g, Exception} HttpRequest +HttpRequest.fromStream stream = + Throw.toException toFailure do Decode.fromStream HttpRequest.decode stream + +HttpRequest.get : URI -> HttpRequest +HttpRequest.get uri = + headers = Headers.union (forURI uri) acceptEncoding.default + HttpRequest GET Version.http11 uri headers Body.empty + +HttpRequest.get.doc : Doc +HttpRequest.get.doc = + use Path / + {{ + Construct a simple HTTP GET {type HttpRequest} for a {type URI}. + + This function can be convenient when you don't need to customize request + components such as HTTP headers. + + Example: + + ``` + HttpRequest.get + (URI + Scheme.https + (Some (Authority None (HostName "unison-lang.org") None)) + (root / "docs") + RawQuery.empty + Fragment.empty) + ``` + }} + +HttpRequest.headers : HttpRequest -> Headers +HttpRequest.headers = cases HttpRequest _ _ _ h _ -> h + +HttpRequest.headers.modify : + (Headers ->{g} Headers) -> HttpRequest ->{g} HttpRequest +HttpRequest.headers.modify f = cases + HttpRequest version method uri origHeaders body -> + HttpRequest version method uri (f origHeaders) body + +HttpRequest.headers.pattern.tchar : Pattern Text +HttpRequest.headers.pattern.tchar = + Pattern.or + (patterns.char + (anyOf [?!, ?#, ?$, ?%, ?&, ?\', ?*, ?+, ?-, ?., ?^, ?_, ?`, ?|, ?~])) + alphaNum + +HttpRequest.headers.pattern.tchar.doc : Doc +HttpRequest.headers.pattern.tchar.doc = + {{ + Each header field consists of a case-insensitive field name followed by a + colon (":"), optional leading whitespace, the field value, and optional + trailing whitespace. ''' + + token = 1*tchar + + tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + / DIGIT / ALPHA + ; any VCHAR, except delimiters + + + header-field = field-name ":" OWS field-value OWS + + field-name = token + field-value = *( field-content / obs-fold ) + field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + field-vchar = VCHAR / obs-text + + obs-fold = CRLF 1*( SP / HTAB ) + ; obsolete line folding + ; see Section 3.2.4 + '''q + }} + +HttpRequest.headers.pattern.token : Pattern Text +HttpRequest.headers.pattern.token = Pattern.some tchar + +HttpRequest.method : HttpRequest -> Method +HttpRequest.method = cases HttpRequest m _ _ _ _ -> m + +HttpRequest.method.doc : Doc +HttpRequest.method.doc = {{ Gets the {type Method} of an {type HttpRequest}. }} + +HttpRequest.patch : URI -> Body -> HttpRequest +HttpRequest.patch uri body = + headers = Headers.union (forURI uri) acceptEncoding.default + HttpRequest PATCH Version.http11 uri headers body + +HttpRequest.pattern.method : IPattern Capture Text +HttpRequest.pattern.method = + use IPattern <|> + method.connect <|> method.delete <|> method.get <|> method.head + <|> method.options + <|> method.patch + <|> method.post + <|> method.put + <|> method.trace + +HttpRequest.pattern.method.connect : IPattern Capture Text +HttpRequest.pattern.method.connect = + use patterns char + IPattern.capture + (Pattern.join + [ char (anyOf [?c, ?C]) + , char (anyOf [?o, ?O]) + , char (anyOf [?n, ?N]) + , char (anyOf [?n, ?N]) + , char (anyOf [?e, ?E]) + , char (anyOf [?c, ?C]) + , char (anyOf [?t, ?T]) + ]) + +HttpRequest.pattern.method.delete : IPattern Capture Text +HttpRequest.pattern.method.delete = + use patterns char + IPattern.capture + (Pattern.join + [ char (anyOf [?d, ?D]) + , char (anyOf [?e, ?E]) + , char (anyOf [?l, ?L]) + , char (anyOf [?e, ?E]) + , char (anyOf [?t, ?T]) + , char (anyOf [?e, ?E]) + ]) + +HttpRequest.pattern.method.get : IPattern Capture Text +HttpRequest.pattern.method.get = + use patterns char + IPattern.capture + (Pattern.join + [char (anyOf [?g, ?G]), char (anyOf [?e, ?E]), char (anyOf [?t, ?T])]) + +HttpRequest.pattern.method.head : IPattern Capture Text +HttpRequest.pattern.method.head = + use patterns char + IPattern.capture + (Pattern.join + [ char (anyOf [?h, ?H]) + , char (anyOf [?e, ?E]) + , char (anyOf [?a, ?A]) + , char (anyOf [?d, ?D]) + ]) + +HttpRequest.pattern.method.options : IPattern Capture Text +HttpRequest.pattern.method.options = + use patterns char + IPattern.capture + (Pattern.join + [ char (anyOf [?o, ?O]) + , char (anyOf [?p, ?P]) + , char (anyOf [?t, ?T]) + , char (anyOf [?i, ?I]) + , char (anyOf [?o, ?O]) + , char (anyOf [?n, ?N]) + , char (anyOf [?s, ?S]) + ]) + +HttpRequest.pattern.method.patch : IPattern Capture Text +HttpRequest.pattern.method.patch = + use patterns char + IPattern.capture + (Pattern.join + [ char (anyOf [?p, ?P]) + , char (anyOf [?a, ?A]) + , char (anyOf [?t, ?T]) + , char (anyOf [?c, ?C]) + , char (anyOf [?h, ?H]) + ]) + +HttpRequest.pattern.method.post : IPattern Capture Text +HttpRequest.pattern.method.post = + use patterns char + IPattern.capture + (Pattern.join + [ char (anyOf [?p, ?P]) + , char (anyOf [?o, ?O]) + , char (anyOf [?s, ?S]) + , char (anyOf [?t, ?T]) + ]) + +HttpRequest.pattern.method.put : IPattern Capture Text +HttpRequest.pattern.method.put = + use patterns char + IPattern.capture + (Pattern.join + [char (anyOf [?p, ?P]), char (anyOf [?u, ?U]), char (anyOf [?t, ?T])]) + +HttpRequest.pattern.method.trace : IPattern Capture Text +HttpRequest.pattern.method.trace = + use patterns char + IPattern.capture + (Pattern.join + [ char (anyOf [?t, ?T]) + , char (anyOf [?r, ?R]) + , char (anyOf [?a, ?A]) + , char (anyOf [?c, ?C]) + , char (anyOf [?e, ?E]) + ]) + +HttpRequest.pattern.requestLine : + IPattern + (And + (And + Capture + (And + (And + (And Capture (And (And (And Capture Capture) Capture) Capture)) + Capture) + Capture)) + Capture) + Text +HttpRequest.pattern.requestLine = + use IPattern ++ :+ + space = Pattern.some (patterns.char (anyOf [?\s, ?\t])) + pattern.method :+ space ++ pattern.uri :+ space ++ pattern.version + +HttpRequest.pattern.version : IPattern Capture Text +HttpRequest.pattern.version = + use IPattern <|> + version.http10 <|> version.http11 + +HttpRequest.pattern.version.http10 : IPattern Capture Text +HttpRequest.pattern.version.http10 = + use patterns char + IPattern.capture + (Pattern.join + [ char (anyOf [?h, ?H]) + , char (anyOf [?t, ?T]) + , char (anyOf [?t, ?T]) + , char (anyOf [?p, ?P]) + , char (anyOf [?/, ?/]) + , char (anyOf [?1, ?1]) + , char (anyOf [?., ?.]) + , char (anyOf [?0, ?0]) + ]) + +HttpRequest.pattern.version.http11 : IPattern Capture Text +HttpRequest.pattern.version.http11 = + use patterns char + IPattern.capture + (Pattern.join + [ char (anyOf [?h, ?H]) + , char (anyOf [?t, ?T]) + , char (anyOf [?t, ?T]) + , char (anyOf [?p, ?P]) + , char (anyOf [?/, ?/]) + , char (anyOf [?1, ?1]) + , char (anyOf [?., ?.]) + , char (anyOf [?1, ?1]) + ]) + +HttpRequest.post : URI -> Body -> HttpRequest +HttpRequest.post uri body = + headers = Headers.union (forURI uri) acceptEncoding.default + HttpRequest POST Version.http11 uri headers body + +HttpRequest.put : URI -> Body -> HttpRequest +HttpRequest.put uri body = + headers = Headers.union (forURI uri) acceptEncoding.default + HttpRequest PUT Version.http11 uri headers body + +test> HttpRequest.requestLine.tests = + verifyAndIgnore do + use Path / + use Version http10 http11 + unisonweb = Authority None (HostName "www.unison-lang.org") None + req version method path query = + uri = URI Scheme.http (Some unisonweb) path query Fragment.empty + HttpRequest method version uri Headers.empty Body.empty + expectations = + [ (req http11 GET root RawQuery.empty, "GET / HTTP/1.1") + , (req http10 POST (root / "docs") RawQuery.empty, "POST /docs HTTP/1.0") + , (req http11 PUT (root / "") RawQuery.empty, "PUT / HTTP/1.1") + , ( req http11 PUT (root / "docs" / "") RawQuery.empty + , "PUT /docs/ HTTP/1.1" + ) + , ( req + http10 + GET + (root / "docs") + (fromQuery + (Query + (Map.fromList + [("%wei?rd", ["&he llo/", " th#er=e%"]), ("simple", ["foo"])]))) + , "GET /docs?%25wei?rd=%26he+llo/&%25wei?rd=+th%23er%3de%25&simple=foo HTTP/1.0" + ) + ] + check = cases + (request, expected) -> + test.ensureEqual + (nonProxyRequestLine request |> fromUtf8.impl |> Either.toOptional) + (Some expected) + flipped.deprecated expectations check + +HttpRequest.setHeader : Text -> [Text] -> HttpRequest -> HttpRequest +HttpRequest.setHeader name value req = + setIt = cases Headers map -> Headers (Map.insert name value map) + headers.modify setIt req + +test> HttpRequest.tests.roundTrip = + verifyAndIgnore do + use HttpRequest == + use Path / + scheme = Scheme "http" + authority = + Each.append (do None) do + userInfo = each [None, Some (UserInfo "Grace")] + host = + HostName + <| each + [ "localhost" + , "127.0.0.1" + , "unison-lang.org" + , "www.unison-lang.org" + ] + port = each [None, Some (Port "80"), Some (Port "8080")] + Some (Authority userInfo host port) + path = each [root, root / "foo", root / "foo" / "&%3" / "bar" / ""] + query = + fromQuery + <| each + [ Query.empty + , Query.empty & ("foo", "bar") + , Query.empty & ("foo", "bar") & ("baz", "/&?%") + ] + fragment = Fragment.empty + uri = URI scheme authority path query fragment + method = each [GET, HEAD, POST, PUT, DELETE, TRACE, PATCH, OPTIONS] + headers = + each + [ Headers.empty + , Headers.singleton "My-Header" "42" + , Headers.fromList [("foo", "bar"), ("baz", "")] + ] + body = Body.empty + origReq = each [HttpRequest method Version.http11 uri headers body] + normalizeEncoded = + uri.modify (authority.set None) + >> headers.modify (Headers.orElse (forURI uri)) + normalizedOrig = normalizeEncoded origReq + normalizeDecoded = + uri.modify (scheme.set Scheme.http >> authority.set None) + decoded = + HttpRequest.fromBytes + (HttpRequest.encode proxy.ProxyPresence.NoProxy origReq) + |> normalizeDecoded + ensuring do normalizedOrig == decoded + +HttpRequest.uri : HttpRequest -> URI +HttpRequest.uri = cases HttpRequest _ _ u _ _ -> u + +HttpRequest.uri.modify : (URI ->{g} URI) ->{g} HttpRequest ->{g} HttpRequest +HttpRequest.uri.modify f = cases + HttpRequest method version oldURI headers body -> + HttpRequest method version (f oldURI) headers body + +HttpRequest.version : HttpRequest -> Version +HttpRequest.version = cases HttpRequest _ v _ _ _ -> v + +HttpRequest.version.doc : Doc +HttpRequest.version.doc = {{ Gets the {type Version} of an {HttpRequest}. }} + +(HttpResponse.==) : HttpResponse -> HttpResponse -> Boolean +(HttpResponse.==) = cases + HttpResponse s1 v1 h1 b1, HttpResponse s2 v2 h2 b2 -> + s1 === s2 && v1 === v2 && h1 Headers.== h2 && b1 === b2 + +HttpResponse.addHeader : Text -> Text -> HttpResponse -> HttpResponse +HttpResponse.addHeader name value = cases + HttpResponse (Status code reason) version headers body -> + HttpResponse + (Status code reason) version (Headers.add name value headers) body + +HttpResponse.badRequest : HttpResponse +HttpResponse.badRequest = + HttpResponse + (Status 400 "Bad Request") Version.http11 Headers.empty Body.empty + +HttpResponse.body : HttpResponse -> Body +HttpResponse.body = cases HttpResponse _ _ _ b -> b + +HttpResponse.body.modify : + (Body ->{g} Body) ->{g} HttpResponse ->{g} HttpResponse +HttpResponse.body.modify f = cases + HttpResponse status version headers oldBody -> + HttpResponse status version headers (f oldBody) + +HttpResponse.body.set : Body -> HttpResponse -> HttpResponse +HttpResponse.body.set body = cases + HttpResponse s v h _ -> HttpResponse s v h body + +HttpResponse.decode : Boolean ->{Decode} HttpResponse +HttpResponse.decode headersOnly = + use Decode failWith + use Text ++ + pairs : [Text] -> [(Text, Text)] + pairs as = + pair = cases + [a, b] ++ rest -> Some ((a, b), rest) + _ -> None + List.unfold as pair + head = + Decode.label "HTTP response head" do Decode.until 0xs0d0a0d0a Decode.utf8 + res = + match IPattern.run statusLine head with + Some ([version, status, reason], headersWithTrailingCrLf) -> + version' = match Version.fromText version with + Right v -> v + Left t -> failWith ("Unsupported HTTP version: " ++ t) + statusCode = + Nat.fromText status + |> (getOrElse' do failWith ("Invalid HTTP status code: " ++ status)) + status' = Status statusCode reason + headers = match parseHeaders (Text.drop 2 headersWithTrailingCrLf) with + Right headers -> headers + Left e -> failWith e + HttpResponse status' version' headers Body.empty + _ -> failWith ("Invalid HTTP response status line\n" ++ head) + if headersOnly then res + else + (resp, trailers) = + decodeBody res HttpResponse.headers HttpResponse.body.set + (HttpResponse s v h b) = resp + HttpResponse s v (Headers.union h trailers) b + +HttpResponse.doc : Doc +HttpResponse.doc = + {{ + # HttpResponse + + The {type HttpResponse} type represents an HTTP response as defined by + [RFC 7231](https://www.rfc-editor.org/rfc/rfc7231). + + It could be used to represent either a response received by a client, or a + response to be sent by a server. + + ## Constructing an {type HttpResponse} + + There are a number of helper methods for constructing common + {type HttpResponse}s: + + * {HttpResponse.ok} + * {HttpResponse.notFound} + * {noContent} + * {badRequest} + + Or you could construct a HttpResponse directly using its constructor: + + ``` + HttpResponse + (Status 200 "OK") Version.http11 Headers.empty (Body Bytes.empty) + ``` + }} + +HttpResponse.encode : HttpResponse -> Bytes +HttpResponse.encode response = + use Bytes ++ + headers = HttpResponse.encodeNoBody response + let + (Body body) = HttpResponse.body response + headers ++ body + +HttpResponse.encodeChunked : + HttpResponse -> '{Stream Bytes} Headers ->{Exception, Stream Bytes} () +HttpResponse.encodeChunked response body = + response' = HttpResponse.addHeader "Transfer-Encoding" "chunked" response + emit (HttpResponse.encodeNoBody response') + encodeChunkedBody body + +HttpResponse.encodeChunked.doc : Doc +HttpResponse.encodeChunked.doc = + {{ + Given an {type HttpResponse}, and a {type Stream} of {type Bytes}, encode + this response into a stream of bytes represeinting the response. If the given + response has a body, the body will be encoded as the first chunk, and each + {type Bytes}. Any {type Headers} returned at the end of the Stream will be + encoded as Trailers + }} + +test> HttpResponse.encodeChunked.tests.withoutTrailers = + verifyAndIgnore do + use Bytes ++ + bodyStream = do + use Text toUtf8 + emit (toUtf8 "hello") + emit (toUtf8 ", ") + emit Bytes.empty + emit (toUtf8 "world!") + emit (toUtf8 "Sincerely,\n me") + Headers.empty + head = + HttpResponse.ok Body.empty + |> HttpResponse.addHeader "Content-Type" "text/plain" + responseStream = do HttpResponse.encodeChunked head bodyStream + encodedResponse = Stream.fold (++) Bytes.empty responseStream |> fromUtf8 + expected = + Text.join + "\n" + [ "HTTP/1.1 200 OK\r" + , "Content-Type: text/plain\r" + , "Transfer-Encoding: chunked\r" + , "\r" + , "5\r" + , "hello\r" + , "2\r" + , ", \r" + , "6\r" + , "world!\r" + , "F\r" + , "Sincerely,\n me\r" + , "0\r" + , "\r\n" + ] + test.ensureEqual encodedResponse expected + +test> HttpResponse.encodeChunked.tests.withTrailers = + verifyAndIgnore do + use Bytes ++ + use HttpResponse addHeader + trailers = + Headers.fromList + [ ("Expires", "Wed, 21 Oct 2015 07:28:00 GMT") + , ("Server-Timing", "cache;desc=\"Cache Read\";dur=23.2") + ] + bodyStream = do + use Text toUtf8 + emit (toUtf8 "hello") + emit (toUtf8 ", ") + emit Bytes.empty + emit (toUtf8 "world!") + emit (toUtf8 "Sincerely,\n me") + trailers + head = + HttpResponse.ok Body.empty |> addHeader "Content-Type" "text/plain" + |> addHeader "Trailer" "Expires" + |> addHeader "Trailer" "Server-Timing" + responseStream = do HttpResponse.encodeChunked head bodyStream + encodedResponse = Stream.fold (++) Bytes.empty responseStream |> fromUtf8 + expected = + Text.join + "\n" + [ "HTTP/1.1 200 OK\r" + , "Content-Type: text/plain\r" + , "Trailer: Server-Timing\r" + , "Trailer: Expires\r" + , "Transfer-Encoding: chunked\r" + , "\r" + , "5\r" + , "hello\r" + , "2\r" + , ", \r" + , "6\r" + , "world!\r" + , "F\r" + , "Sincerely,\n me\r" + , "0\r" + , "Expires: Wed, 21 Oct 2015 07:28:00 GMT\r" + , "Server-Timing: cache;desc=\"Cache Read\";dur=23.2\r" + , "\r\n" + ] + test.ensureEqual encodedResponse expected + +HttpResponse.encodeNoBody : HttpResponse -> Bytes +HttpResponse.encodeNoBody = cases + HttpResponse (Status code reason) version headers body -> + use fromList impl + headers' = + if Headers.contains "Content-Length" headers then headers + else Headers.union headers (standard.contentLength body) + statusLine = + Version.toText version Text.++ " " Text.++ Nat.toText code Text.++ " " + Text.++ reason + headers'' = asBytes headers' + Text.toUtf8 statusLine Bytes.++ 0xs0d0a Bytes.++ headers'' Bytes.++ 0xs0d0a + +HttpResponse.ensureEqual : HttpResponse -> HttpResponse ->{Exception} () +HttpResponse.ensureEqual a b = + use HttpResponse == + if a == b then () else test.raiseFailure "HttpResponses not equal" (a, b) + +HttpResponse.ensureStatus : + (HttpResponse.Status -> Boolean) -> HttpResponse ->{Exception} HttpResponse +HttpResponse.ensureStatus predicate response = + expectStatus predicate response + response + +HttpResponse.ensureStatus.doc : Doc +HttpResponse.ensureStatus.doc = + {{ + Ensures that the given {type HttpResponse} has a status code that satisfies + the given predicate. If the predicate is not satisfied, raises an exception + with the response's status code and body. + + # See also + + * {expectStatus} for a version of this function that just returns + {type Unit} if the status code satisfies the predicate. + * {ensureSuccess} for a version of this function that checks for a + successful status code. + }} + +HttpResponse.ensureSuccess : HttpResponse ->{Exception} HttpResponse +HttpResponse.ensureSuccess response = + expectSuccess response + response + +HttpResponse.ensureSuccess.doc : Doc +HttpResponse.ensureSuccess.doc = + {{ + Ensures that the given {type HttpResponse} is a successful response. If it is + not, raise an exception with the response's status code and body. + + # See also + + * {expectSuccess} for a version of this function that just returns + {type Unit} if the response is successful. + * {ensureStatus} for a version of this function that checks for a specific + status code. + }} + +HttpResponse.error : HttpResponse +HttpResponse.error = + HttpResponse + (Status 500 "Internal Server Error") + Version.http11 + Headers.empty + Body.empty + +HttpResponse.expectStatus : + (HttpResponse.Status -> Boolean) -> HttpResponse ->{Exception} () +HttpResponse.expectStatus predicate response = + use HttpResponse status + use Text ++ + if predicate (status response) then () + else + Failure + (typeLink UnexpectedResponseStatus) + ("unexpected response status: " ++ (status response |> Status.toText)) + (Any response) + |> Exception.raise + +HttpResponse.expectStatus.doc : Doc +HttpResponse.expectStatus.doc = + {{ + Checks that the given {type HttpResponse} has a status code that satisfies + the given predicate. If the predicate is not satisfied, raises an exception + with the response's status code and body. + + # See also + + * {ensureStatus} for a version of this function that returns the response + if the status code satisfies the predicate. + * {expectSuccess} for a version of this function that checks for a + successful status code. + }} + +HttpResponse.expectSuccess : HttpResponse ->{Exception} () +HttpResponse.expectSuccess response = expectStatus Status.isSuccess response + +HttpResponse.fromBytes : Boolean -> Bytes ->{Exception} HttpResponse +HttpResponse.fromBytes headersOnly bs = + HttpResponse.fromStream headersOnly do emit bs + +HttpResponse.fromBytes.doc : Doc +HttpResponse.fromBytes.doc = + {{ + Parse an {type HttpResponse}. Takes a boolen which indicates whother or not + we expect a body. This would normally be `false` unless the response is to a + {HEAD} request. + }} + +HttpResponse.fromStream : + Boolean -> '{g, Stream Bytes} a ->{g, Exception} HttpResponse +HttpResponse.fromStream headersOnly stream = + Throw.toException toFailure do + Decode.fromStream (do HttpResponse.decode headersOnly) stream + +HttpResponse.headers : HttpResponse -> Headers +HttpResponse.headers = cases HttpResponse _ _ h _ -> h + +HttpResponse.isSuccess : HttpResponse -> Boolean +HttpResponse.isSuccess = cases + HttpResponse (Status code _) _ _ _| code Nat.>= 200 && code Nat.< 300 -> + true + x -> false + +HttpResponse.noContent : HttpResponse +HttpResponse.noContent = + HttpResponse + (Status 204 "No Content") Version.http11 Headers.empty Body.empty + +HttpResponse.notFound : HttpResponse +HttpResponse.notFound = + HttpResponse (Status 404 "Not Found") Version.http11 Headers.empty Body.empty + +HttpResponse.ok : Body -> HttpResponse +HttpResponse.ok body = + HttpResponse + (Status 200 "OK") Version.http11 (standard.contentLength body) body + +HttpResponse.pattern.reason : IPattern Capture Text +HttpResponse.pattern.reason = + IPattern.capture (Pattern.many (notCharIn [?\r, ?\n])) + +HttpResponse.pattern.status : IPattern Capture Text +HttpResponse.pattern.status = + IPattern.capture (Pattern.replicate 3 3 patterns.digit) + +HttpResponse.pattern.statusLine : + IPattern (And (And Capture Capture) Capture) Text +HttpResponse.pattern.statusLine = + use IPattern ++ :+ + pattern.version :+ literal " " ++ pattern.status :+ literal " " + ++ pattern.reason + +test> HttpResponse.pattern.statusLine.testReasonIsOptional = + parsed = catch do + statusNoReason = + """ + HTTP/1.1 200 + rest + """ + IPattern.run statusLine statusNoReason + verifyAndIgnore do + assertEquals parsed (Right (Some (["HTTP/1.1", "200", ""], "\r\nrest"))) + +HttpResponse.setBody : Body -> HttpResponse -> HttpResponse +HttpResponse.setBody body = cases + HttpResponse s v h _ -> + cl = standard.contentLength body + HttpResponse s v (Headers.union cl h) body + +HttpResponse.status : HttpResponse -> HttpResponse.Status +HttpResponse.status = cases HttpResponse s _ _ _ -> s + +HttpResponse.Status.isFailure : HttpResponse.Status -> Boolean +HttpResponse.Status.isFailure s = Boolean.not (Status.isSuccess s) + +HttpResponse.Status.isSuccess : HttpResponse.Status -> Boolean +HttpResponse.Status.isSuccess s = + use Nat / + digit = code s / 100 + digit === 1 || digit === 2 || digit === 3 + +HttpResponse.Status.toText : HttpResponse.Status -> Text +HttpResponse.Status.toText = cases + Status code msg -> Nat.toText code Text.++ ": " Text.++ msg + +LICENSE : License +LICENSE = License [unisoncomputing, copyrightHolders.alvaroc1] [Year 2023] mit + +LICENSE.doc : Doc +LICENSE.doc = License.toDoc LICENSE + +message.encodeChunkedBody : '{g, Stream Bytes} Headers ->{g, Stream Bytes} () +message.encodeChunkedBody = + use fromList impl + emitChunk : Bytes ->{Stream Bytes} () + emitChunk chunk = + use Bytes ++ + size = + Bytes.size chunk |> Nat.toTextBase 16 |> getOrBug "chunkSize" + |> Text.toUtf8 + emit (size ++ 0xs0d0a ++ chunk ++ 0xs0d0a) + emitChunks = cases + { emit bytes -> rest } -> + if Bytes.isEmpty bytes then () else emitChunk bytes + handle rest() with emitChunks + { trailers } -> + emit 0xs300d0a + emit (asBytes trailers) + emit 0xs0d0a + bodyAndTrailers -> (handle bodyAndTrailers() with emitChunks) + +message.encodeChunkedBody.doc : Doc +message.encodeChunkedBody.doc = + {{ + `` encodeChunkedBody bodyAndTrailers `` encodes the body byte stream and + trailers of a chunked request or response. + }} + +Method.fromText : Text -> Optional Method +Method.fromText = cases + "GET" -> Some GET + "POST" -> Some POST + "PUT" -> Some PUT + "DELETE" -> Some DELETE + "HEAD" -> Some HEAD + "PATCH" -> Some PATCH + "OPTIONS" -> Some OPTIONS + "CONNECT" -> Some CONNECT + "TRACE" -> Some TRACE + _ -> None + +Method.toText : Method -> Text +Method.toText = cases + GET -> "GET" + HEAD -> "HEAD" + POST -> "POST" + PUT -> "PUT" + DELETE -> "DELETE" + CONNECT -> "CONNECT" + TRACE -> "TRACE" + PATCH -> "PATCH" + OPTIONS -> "OPTIONS" + +README : Doc +README = + {{ + # The Unison HTTP library + + This project contains the following: + + * Basic types for working with the HTTP protocol. Notably this includes + definitions of an {type HttpRequest} and {type HttpResponse}. + * An HTTP client. See {{ docLink (docEmbedTermLink do client.README) }}. + * An HTTP server. See {{ docLink (docEmbedTermLink do server.README) }}. + * WebSocket support. See + {{ docLink (docEmbedTermLink do websockets.README) }}. WebSocket + functionality is also baked into the client and server. + + # Attribution and licensing + + See {LICENSE} for license information. + + The WebSocket support in this library was originally authored by {{ + shareSlug "@alvaroc1" }} in {{ shareSlug "@alvaroc1/websocket" }} and has + been merged into this library with Alvaro's permission. + + # Basic types + + {{ HttpRequest.doc }} + + {{ HttpResponse.doc }} + + # HTTP client + + {{ client.README }} + + # HTTP server + + {{ server.README }} + + # WebSockets + + {{ websockets.README }} + }} + +ReleaseNotes : Doc +ReleaseNotes = + {{ + * Upgrade to {{ shareSlug "@unison/base" }} version 3.17 and {{ + shareSlug "@unison/codec" }} version 1.8.4 + }} + +server.CHANGELOG : Doc +server.CHANGELOG = + {{ + {{ v10 }} + + {{ v9 }} + + {{ v8 }} + + {{ v7 }} + + {{ v6 }} + }} + +server.changelogs.v10 : Doc +server.changelogs.v10 = + {{ + # v10 + + Thu Feb 9 03:10:05 PM PST 2023 + + * upgrade to the latest httpclient (v11) + + {authors.stew} + }} + +server.changelogs.v6 : Doc +server.changelogs.v6 = + {{ + # v6 + + * updated to latest http release (v6) + + {authors.stew} + }} + +server.changelogs.v7 : Doc +server.changelogs.v7 = + {{ + # v7 + + * added httpclient as a dependency so that I could start adding integration + tests + * added a README + * added a {type Handler} type to clean up the type signatures + + {authors.stew} + }} + +server.changelogs.v8 : Doc +server.changelogs.v8 = + {{ + # v8 + + Mon Nov 14 08:55:52 PM PST 2022 + + * upgrade to latest http + }} + +server.changelogs.v9 : Doc +server.changelogs.v9 = + {{ + # v9 + + Wed Nov 16 02:24:39 PM PST 2022 + + * upgrade to the latest httpclient (v10) + + {authors.stew} + }} + +server.Config.createSemaphore : server.Config ->{IO} Sem +server.Config.createSemaphore = cases + server.Config.Config _ _ numThreads _ -> Sem.new numThreads + +server.Config.serve : + Routes {IO} -> server.Config ->{IO, Exception} '{IO, Exception} () +server.Config.serve routes config = forkServer config (toFunction routes) + +server.Config.serve.doc : Doc +server.Config.serve.doc = + {{ + {{ docLink (docEmbedTermLink do Config.serve) }} Starts a webserver, returns + a thunk that when evaluated, will stop the server + }} + +server.example.alohaHandler : Handler IO +server.example.alohaHandler = + Handler cases + req + | Boolean.and + (Routes.get (root Path./ "aloha") req) + (withHeader "Accept" "application/json" req) -> + HttpResponse.ok (Body (Text.toUtf8 "{\"aloha\": \"World\"}")) + | Routes.get (root Path./ "aloha") req -> + HttpResponse.ok (Body (Text.toUtf8 "Aloha, world")) + _ -> abort + +server.example.helloHandler : Handler IO +server.example.helloHandler = + Handler cases + req| Routes.get (root Path./ "hello") req -> + HttpResponse.ok (Body (Text.toUtf8 "Hello World")) + _ -> abort + +server.example.helloHandler2 : Handler IO +server.example.helloHandler2 = + Handler cases + HttpRequest GET _ (URI _ _ (Path ["hello"]) _ _) _ _ -> + HttpResponse.ok (Body (Text.toUtf8 "Hello World")) + _ -> abort + +server.example.main : '{IO, Exception} () +server.example.main = do + use Nat * + config = server.Config.Config None (Port "8081") 1000 None + routes = Routes.default <<< alohaHandler <<< helloHandler + stopServer = Config.serve routes config + printLine "started server on port 8081" + sleepMicroseconds (24 * 60 * 60 * 1000000) + stopServer() + +server.example.tracing404 : HttpRequest -> HttpResponse +server.example.tracing404 req = + bug "out" + Debug.trace "404" req + default404 req + +server.forkServer : + server.Config + -> (HttpRequest ->{IO, Exception} Either HttpResponse WebSocketHandler) + ->{IO, Exception} '{IO, Exception} () +server.forkServer config handler = + interrupt = Interrupt.new() + ignore + <| (fork do + IO.logAndIgnore do + toDefault! (do ()) do + interruptibly interrupt do runServer config handler + signalFinalization interrupt) + stopServer = do interruptAndAwaitFinalization interrupt + stopServer + +server.Handler.doc : Doc +server.Handler.doc = + {{ + a {type Handler} represents a function which handles one "Route" of the + server. Handlers come in two types, {Handler} which haandles HTTP 1.1 + requests and {WebSocketHandler} which handles WebSocket requests. + + # Http Handlers + + Http Handlers are functions from a {type HttpRequest} to a {HttpResponse}. + + A `` Handler g `` is a {type Handler} which uses `g` Effects in order to + produce a {type HttpResponse} If a Handler uses the {type Abort} ability, + it will indicate that this handler is not interested in the given + {type HttpRequest} and this request should be sent to the next handler. If + a Handler uses the {type Exception} ability, it indicates a Server error + which will be sent to the user using the 500 handler. + + # Creating a Handler + + A handler is just a function from a {type HttpRequest} to a + {type HttpResponse}, so one could pattern match on the {type HttpRequest} + and return a {type HttpResponse}: + + {{ docSource [docSourceElement (docEmbedTermLink do helloHandler2) []] }} + + However, this is not very convenient, so we provide a {type Handler} type + which is a wrapper around a function from {type HttpRequest} to + {type HttpResponse}. This type provides a number of helper functions to + make it easier to create handlers, for example, the above pattern match can + be rewritten using the {{ docLink (docEmbedTermLink do Routes.get) }} + function as a + [pattern guard](https://www.unison-lang.org/learn/language-reference/guard-patterns/): + + {{ docSource [docSourceElement (docEmbedTermLink do helloHandler) []] }} + + This example shows how can provide different responses based on + {type Headers}: + + {{ docSource [docSourceElement (docEmbedTermLink do alohaHandler) []] }} + + ## WebSocket Handlers + + WebSocket Handlers are functions that take a Http Request and return a + {type WebSocketHandler}. + + {{ WebSocketHandler.doc }} + }} + +server.Http.webSocket : HttpRequest ->{Exception, HttpWebSocket} WebSocket +server.Http.webSocket request = Either.toException (tryWebSocket request) + +server.internal.handleConnection : + (HttpRequest ->{IO, Exception} Either HttpResponse WebSocketHandler) + -> Connection + ->{IO, Exception, Abort} () +server.internal.handleConnection handler conn = + match catchAll do HttpRequest.fromStream (receiveByteStream conn) with + Left (Failure _ t a) -> + Connection.send + conn + (HttpResponse.encode + (HttpResponse + (Status 400 "Bad Request") + Version.http11 + Headers.empty + (Body (Text.toUtf8 t)))) + Right request -> handleRequest conn request handler + +server.internal.handleRequest : + Connection + -> HttpRequest + -> (HttpRequest ->{IO, Exception} Either HttpResponse WebSocketHandler) + ->{IO, Exception} () +server.internal.handleRequest conn request handler = + use List +: + sendResponse = HttpResponse.encode >> Connection.send conn + match handler request with + Left response -> sendResponse response + Right (WebSocketHandler webSocketHandler) -> + response = upgradeResponse request + sendResponse response + webSocket = threadSafeWebSocket conn Server 4096 Bytes.empty + finalizers : Ref IO [Either Failure () ->{IO, Exception} ()] + finalizers = IO.ref [] + addFinalizer : + (Either Failure () ->{IO, Exception} ()) ->{IO, Exception} () + addFinalizer f = Ref.modify finalizers (fs -> f +: fs) + res : Either Failure () + res = catch do webSocketHandler webSocket addFinalizer + foreach.deprecated (do f -> catchAll do f res) (Ref.read finalizers) + +server.internal.socketListener : + server.Config + -> (HttpRequest ->{IO, Exception} Either HttpResponse WebSocketHandler) + -> ListeningServerSocket + ->{IO} () +server.internal.socketListener = cases + config@(server.Config.Config hostName port numThreads tlsConfig), + handler, + sock -> + semaphore = createSemaphore config + acceptWith : + (Connection ->{IO, Exception, Abort} ()) ->{IO, Exception} ThreadId + acceptWith handleWithoutRelease = + Sem.acquire semaphore + useAndRelease : Connection ->{IO, Exception} () + useAndRelease connection = + finally (do Sem.release semaphore) do + tryEval do + finally (do ignore (catchAll do Connection.close connection)) do + tryEval do + handle handleWithoutRelease connection + with cases + { abort -> _ } -> + Connection.send + connection + (HttpResponse.encode + (HttpResponse + (Status 400 "Bad Request") + Version.http11 + Headers.empty + (Body (Text.toUtf8 "Expected a WebSocket request")))) + { _ } -> () + match tlsConfig with + None -> Tcp.acceptFork sock (useAndRelease << socket) + Some tlsConfig -> + Tls.acceptFork tlsConfig sock (useAndRelease << tls.deprecated) + go : '{IO, Exception} () + go _ = forever do acceptWith (handleConnection handler) + IO.logAndIgnore go + +server.README : Doc +server.README = + {{ + This library allows you to run an HTTP server. + + # Quickstart + + {{ docSource [docSourceElement (docEmbedTermLink do example.main) []] }} + + {{ Handler.doc }} + + {{ Routes.doc }} + }} + +(server.Routes.<<<) : Routes {g} -> Handler g -> Routes {g} +routes server.Routes.<<< handler = + use List +: + (Routes handlers notFound error) = routes + Routes (handler +: handlers) notFound error + +(server.Routes.>>>) : Handler g -> Routes {g} -> Routes {g} +(server.Routes.>>>) handler = cases + Routes handlers notFound error -> + Routes (handler List.+: handlers) notFound error + +server.Routes.default : Routes g +server.Routes.default = Routes [] default404 default500 + +server.Routes.default404 : HttpRequest -> HttpResponse +server.Routes.default404 = cases + HttpRequest _ _ uri _ _ -> + use Text ++ + body = "Not found: " ++ Path.toText (URI.path uri) |> Text.toUtf8 |> Body + HttpResponse.notFound |> setBody body + +server.Routes.default500 : Failure -> HttpRequest -> HttpResponse +server.Routes.default500 = cases + Failure _ t _ -> + use Text ++ + body = "Internal server error: " ++ t |> Text.toUtf8 |> Body + response = HttpResponse.error |> setBody body + do response + +server.Routes.doc : Doc +server.Routes.doc = + {{ + {type Routes} is a mechanism for finding a {type Handler} for a given + {type HttpRequest}. + + Routes are made up of a list of {type Handler}s, a "notFound" function and a + "error" function + + When a {type HttpRequest} is received list of handlers will be tried one by + one to handle a given {type HttpRequest}. The first {type HttpResponse} + produced by a {type Handler} will be returned to the client. If one of the + handlers rasises an exception, it will be passed to the "error" function to + produce an {type HttpResponse}. If none of the handlers are able to handle + the {type HttpRequest}, the "notFound" function will be used to produce an + {type HttpResponse}. + + # Creating Routes + + {{ docLink (docEmbedTermLink do Routes.default) }} returns a {type Routes} + with a default "notFound" and "error" functions, and no other Handlers, so + every request would get a 404 response. {{ + docLink (docEmbedTermLink do (<<<)) }} is a function which takes a + {type Handler} and a {type Routes} and returns a new {type Routes} with the + {type Handler} added to the list of handlers. + + so {{ docExample 2 do default helloHandler -> default <<< helloHandler }} + creates a {type Routes} that will only responsd to {GET} reqeusts to + "/hello" + }} + +server.Routes.get : Path -> HttpRequest -> Boolean +server.Routes.get path = cases + HttpRequest GET _ (URI _ _ p _ _) _ _ | p === path -> true + _ -> false + +server.Routes.path : Path -> HttpRequest -> Boolean +server.Routes.path p = cases HttpRequest _ _ (URI _ _ p' _ _) _ _ -> p === p' + +server.Routes.post : Path -> HttpRequest -> Boolean +server.Routes.post path = cases + HttpRequest POST _ (URI _ _ p _ _) _ _ | p === path -> true + _ -> false + +server.Routes.toFunction : + Routes IO -> HttpRequest ->{IO} Either HttpResponse WebSocketHandler +server.Routes.toFunction = cases + Routes routes notFound error, req -> + use Exception raise + go : [Handler IO] ->{IO} Either HttpResponse WebSocketHandler + go = cases + [] -> Left (notFound req) + Handler h +: t -> + handle h req + with cases + { abort -> _ } -> go t + { raise failure -> _ } -> Left (error failure req) + { response } -> + v : Either HttpResponse WebSocketHandler + v = Left response + v + HandlerWebSocket h +: t -> + handle h req + with cases + { abort -> _ } -> go t + { raise failure -> _ } -> Left (error failure req) + { f } -> Right f + go routes + +server.Routes.with404 : + (HttpRequest ->{g} HttpResponse) -> Routes {g} -> Routes {g} +server.Routes.with404 f = cases + Routes routes notFound error -> Routes routes f error + +server.Routes.with500 : + (Failure -> HttpRequest ->{g} HttpResponse) -> Routes {g} -> Routes {g} +server.Routes.with500 f = cases + Routes routes notFound error -> Routes routes notFound f + +server.Routes.withHeader : Text -> Text -> HttpRequest -> Boolean +server.Routes.withHeader header value = cases + HttpRequest _ _ _ headers _ -> + List.any (a -> value === a) (getValues header headers) + +server.runServer : + server.Config + -> (HttpRequest ->{IO, Exception} Either HttpResponse WebSocketHandler) + ->{IO, Exception} () +server.runServer = cases + config@(server.Config.Config hostname port _ _), handler -> + Tcp.listen hostname port (socketListener config handler) + +server.State.error : State g -> Failure -> HttpRequest ->{g} HttpResponse +server.State.error = cases State (Routes _ _ f) _ -> f + +server.State.handlerReq.internal.webSocketKey : HttpRequest -> Optional Text +server.State.handlerReq.internal.webSocketKey = cases + HttpRequest _ _ _ headers _ -> + if List.any + (v -> Text.toLowercase v Text.== "websocket") + (getCommaDelimitedValues "Upgrade" headers) + && List.any + (v -> Text.toLowercase v Text.== "upgrade") + (getCommaDelimitedValues "Connection" headers) then + match getValues "Sec-WebSocket-Key" headers with + [webSocketKey] -> Some webSocketKey + _ -> None + else None + +test> server.State.handlerReq.internal.webSocketKey.tests = + test.verify do + use Headers fromList + testKey = each ["dGhlIHNhbXBsZSBub25jZQ==", "aGhlIHNhbXBsZSBub25jZQ=="] + let + (expectedKey, headers) = + each + [ (None, Headers.empty) + , ( None + , fromList [("Upgrade", "websocket"), ("Connection", "Upgrade")] + ) + , ( Some testKey + , fromList + [ ("Sec-WebSocket-Key", testKey) + , ("Upgrade", "websocket") + , ("Connection", "Upgrade") + ] + ) + , ( Some testKey + , fromList + [ ("Sec-WebSocket-Key", testKey) + , ("Upgrade", "websocket, turbo") + , ("Connection", "keep-alive, Upgrade") + ] + ) + ] + uri = URI.parse "https://www.unison-lang.org/" + method = each [GET, POST] + req = HttpRequest method Version.http11 uri headers Body.empty + test.ensureEqual expectedKey (webSocketKey req) + +server.State.handlers : State g -> [Handler g] +server.State.handlers = cases State (Routes handlers _ _) _ -> handlers + +server.State.notFound : State g -> HttpRequest ->{g} HttpResponse +server.State.notFound = cases State (Routes _ f _) _ -> f + +server.State.semaphore : State g -> Sem +server.State.semaphore = cases State _ sem -> sem + +server.State.semaphore.set : Sem -> State {g} -> State {g} +server.State.semaphore.set semaphore1 = cases + State routes _ -> State routes semaphore1 + +server.test.integration.all : ['{g, IO, Http, HttpWebSocket, Stream Result} ()] +server.test.integration.all = + [client.get, client.post, client.webSocket, manyRequests] + +server.test.integration.baseUri : URI +server.test.integration.baseUri = + URI + (Scheme "http") + (Some + (Authority + None (HostName integration.host) (Some (Port integration.port)))) + (Path []) + RawQuery.empty + Fragment.empty + +server.test.integration.client.get : + '{IO, Http, HttpWebSocket, Stream Result} () +server.test.integration.client.get = + do + go = + do + use Body toBytes + use Bytes toHex + use Text ++ + uri = path.set (Path ["get"]) baseUri + let + (HttpResponse status _ _ body) = Http.get uri + statusResults "get" status + if body === expectedGetResponse then emit (Ok "get response matches") + else + exp = expectedGetResponse |> toBytes |> toHex + got = body |> toBytes |> toHex + emit + (Result.Fail + ("get response does not match: expected: " ++ exp ++ " got: " + ++ got)) + handleClientException go + +server.test.integration.client.handleClientException : + '{IO, Exception, Http, HttpWebSocket, Stream Result} () + ->{IO, Http, HttpWebSocket, Stream Result} () +server.test.integration.client.handleClientException go = + handle go() + with cases + { Exception.raise f@(Failure _ e a) -> _ } -> + Debug.trace "client exception" f + emit (Result.Fail e) + { a } -> () + +server.test.integration.client.handleWSClientException : + '{IO, Exception, HttpWebSocket, Stream Result} () + ->{IO, Http, HttpWebSocket, Stream Result} () +server.test.integration.client.handleWSClientException go = + handle go() + with cases + { Exception.raise (Failure _ e a) -> _ } -> + Debug.trace "client exception" a + emit (Result.Fail e) + { a } -> () + +server.test.integration.client.manyRequests : + '{IO, Http, HttpWebSocket, Stream Result} () +server.test.integration.client.manyRequests = + do + go n = + use Text ++ + uri = path.set (Path ["get"]) baseUri + let + (HttpResponse status _ _ body) = Http.get uri + statusResults "get" status + if body === expectedGetResponse then emit (Ok "get response matches") + else + emit + (Result.Fail + ("get response does not match for request " ++ Nat.toText n)) + Stream.range 1 21 |> Stream.flatMap go |> handleClientException + +server.test.integration.client.post : + '{IO, Http, HttpWebSocket, Stream Result} () +server.test.integration.client.post = + do + go = + do + uri = path.set (Path ["post"]) baseUri + let + (HttpResponse status _ _ body) = + Http.post uri (Body (Text.toUtf8 "post body")) + statusResults "post" status + if body === expectedPostResponse then + emit (Ok "post response matches") + else emit (Result.Fail "post response does not match") + handleClientException go + +server.test.integration.client.statusResults : + Text -> HttpResponse.Status ->{Stream Result} () +server.test.integration.client.statusResults which = cases + Status 200 text -> emit (Ok (which Text.++ " request succeeded")) + Status c t -> + emit + (Result.Fail + (which Text.++ "Request failed (" Text.++ Nat.toText c Text.++ "): " + Text.++ t)) + +server.test.integration.client.webSocket : + '{IO, Http, HttpWebSocket, Stream Result} () +server.test.integration.client.webSocket = + do + use Http webSocket + use HttpRequest get + use Result Fail + use WebSocket close receive send + use path set + basicExchange : '{IO, Exception, Abort, HttpWebSocket, Stream Result} () + basicExchange = do + uri = set (Path ["websocket"]) baseUri + ws = webSocket (get uri) + send ws (TextMessage "Hello") + match receive ws with + TextMessage "world" -> emit (Ok "successful websocket exchange") + _ -> emit (Fail "unexpected websocket response") + close ws + serverSendsFirst : '{IO, Exception, Abort, HttpWebSocket, Stream Result} () + serverSendsFirst = + do + uri = set (Path ["websocket-server-sends-first"]) baseUri + ws = webSocket (get uri) + match receive ws with + TextMessage "What is your name?" -> + emit (Ok "Received WebSocket message initiated by server") + _ -> emit (Fail "unexpected websocket response") + send ws (TextMessage "Grace") + match receive ws with + TextMessage "Hello, Grace" -> + emit (Ok "successful websocket exchange initiated by server") + _ -> emit (Fail "unexpected websocket response") + close ws + quickSends : '{IO, Exception, Abort, HttpWebSocket, Stream Result} () + quickSends = + do + use Text ++ + use flipped deprecated + uri = set (Path ["websocket-echo"]) baseUri + bracket + (do webSocket (get uri)) + close + (ws -> + let + msgs = + initialize + 40 (i -> TextMessage ("This is message " ++ Nat.toText i)) + deprecated msgs (send ws) + deprecated msgs (msg -> test.ensureEqual msg (receive ws)) + emit (Ok "Sent many messages and then received them all")) + handleAbort : '{g, Abort} a ->{g, Stream Result} () + handleAbort thunk = + handle thunk() + with cases + { abort -> _ } -> emit (Fail "websocket abort") + { _ } -> () + handleClientException do + handleAbort do + basicExchange() + serverSendsFirst() + quickSends() + +server.test.integration.expectedGetResponse : Body +server.test.integration.expectedGetResponse = Body (Text.toUtf8 "get response") + +server.test.integration.expectedPostResponse : Body +server.test.integration.expectedPostResponse = + Body (Text.toUtf8 "post response") + +server.test.integration.host : Text +server.test.integration.host = "localhost" + +server.test.integration.main : '{IO, Exception} [Result] +server.test.integration.main = + do + use List ++ + use Stream toList + use flipped deprecated + use integration all + ignore do + {{ + If we don't wait a little while after we stop the server, then starting a + new server fails sometimes. It seems that the old server needs a bit of + time to release all of its resources. The weird thing is that when the + hang happens we don't see an exception about the port already being in + use. + }} + stopTestServer stop = + stop() + sleep (milliseconds +2) + config = + server.Config.Config + (Some (HostName integration.host)) (Port integration.port) 10 None + res = bracket (do Config.serve integration.server config) stopTestServer do + sleep (milliseconds +2) + test = do deprecated all (t -> t()) + handle handle toList test with Http.handler with HttpWebSocket.handler + res2 = + bracket (do forkServer config integration.newServer) stopTestServer do + sleep (milliseconds +2) + test = do deprecated all (t -> t()) + handle handle toList test with Http.handler with HttpWebSocket.handler + res ++ res2 + +server.test.integration.newServer : + HttpRequest -> Either HttpResponse WebSocketHandler +server.test.integration.newServer = cases + HttpRequest GET _ (URI _ _ (Path ["get"]) _ _) _ _ -> + Left (HttpResponse.ok expectedGetResponse) + HttpRequest POST _ (URI _ _ (Path ["post"]) _ _) _ _ -> + Left (HttpResponse.ok expectedPostResponse) + HttpRequest GET _ (URI _ _ (Path ["websocket"]) _ _) _ _ -> + Right + (WebSocketHandler + (ws addFinalizer -> let + addFinalizer do WebSocket.close ws + match WebSocket.receive ws with + TextMessage "Hello" -> WebSocket.send ws (TextMessage "world") + _ -> ())) + HttpRequest GET _ (URI _ _ (Path ["websocket-server-sends-first"]) _ _) _ _ -> + Right + (WebSocketHandler + (ws addFinalizer -> let + use Text ++ + use WebSocket send + addFinalizer do WebSocket.close ws + send ws (TextMessage "What is your name?") + match WebSocket.receive ws with + TextMessage name -> send ws (TextMessage ("Hello, " ++ name)) + _ -> ())) + HttpRequest GET _ (URI _ _ (Path ["websocket-echo"]) _ _) _ _ -> + Right + (WebSocketHandler + (ws addFinalizer -> let + addFinalizer do WebSocket.close ws + forever do WebSocket.send ws (WebSocket.receive ws))) + _ -> Left HttpResponse.notFound + +server.test.integration.port : Text +server.test.integration.port = "18734" + +server.test.integration.server : Routes g +server.test.integration.server = + d : Routes g + d = Routes.default + d <<< webSocketHandler <<< getHandler <<< postHandler + +server.test.integration.server.getHandler : Handler g +server.test.integration.server.getHandler = Handler cases + req | Routes.get (Path ["get"]) req -> HttpResponse.ok expectedGetResponse + _ -> abort + +server.test.integration.server.postHandler : Handler g +server.test.integration.server.postHandler = Handler cases + req | Routes.post (Path ["post"]) req -> HttpResponse.ok expectedPostResponse + _ -> abort + +server.test.integration.server.routes : Routes g +server.test.integration.server.routes = + d : Routes g + d = Routes.default + d <<< getHandler <<< postHandler + +server.test.integration.server.webSocketHandler : Handler g +server.test.integration.server.webSocketHandler = + use Routes get + use WebSocket receive send + basicExchange ws rf = + match receive ws with + TextMessage "Hello" -> send ws (TextMessage "world") + _ -> () + sleepMicroseconds 2000000 + serverSendsFirst ws rf = + use Text ++ + send ws (TextMessage "What is your name?") + match receive ws with + TextMessage name -> send ws (TextMessage ("Hello, " ++ name)) + _ -> () + echo ws addFinalizer = + addFinalizer (_ -> WebSocket.close ws) + forever do send ws (receive ws) + HandlerWebSocket cases + req + | get (Path ["websocket"]) req -> + WebSocketHandler basicExchange + | get (Path ["websocket-server-sends-first"]) req -> + WebSocketHandler serverSendsFirst + | get (Path ["websocket-echo"]) req -> + WebSocketHandler echo + _ -> abort + +server.up.base.IO.logAndIgnore : '{IO, Exception} a ->{IO} () +server.up.base.IO.logAndIgnore x = + threadKilled = typeLink ThreadKilledFailure + match catchAll x with + Left (Failure typ msg _)| typ !== threadKilled -> + unsafeRun! do putText stdErr msg + _ -> () + +server.WebSocketHandler.async : + (Message ->{IO, Stream Message} ()) + -> '{IO, Exception, Stream Message} () + ->{IO, Exception} WebSocketHandler +server.WebSocketHandler.async onMessage effect = + fn : + WebSocket + -> ((Either Failure () ->{IO, Exception} ()) ->{IO, Exception} ()) + ->{IO, Exception} () + fn ws addFinalizer = + addFinalizer handleClose + outgoingHandler : Request {Stream Message} () ->{IO, Exception} () + outgoingHandler = cases + { emit msg -> k } -> + WebSocket.send ws msg + handle k() with outgoingHandler + { u } -> u + incomingMessageThread = + fork do + unsafeRun! do + toDefault! (do ()) do + forever do + handle onMessage (WebSocket.receive ws) with outgoingHandler + handle effect() with outgoingHandler + concurrent.kill incomingMessageThread + handleClose : Either Failure () ->{IO} () + handleClose = cases + Left failure -> Debug.trace "WebSocket.failure: " failure + Right _ -> () + WebSocketHandler fn + +server.WebSocketHandler.async.doc : Doc +server.WebSocketHandler.async.doc = + {{ + Creates a {type WebSocketHandler} that forks a thread that continously reads + incoming web socket messages calls the provided `onMessage` on them. This is + useful when you want to receive messages asynchronously and not block sending + when you are waiting for a message. + + Example: + + * Receives messages and responds to them, all on a forked thread + * Send a message every 4 seconds + + {{ docSource [docSourceElement (docEmbedTermLink do async.doc.example1) []] + }} + }} + +server.WebSocketHandler.async.doc.example1 : '{IO, Exception} () +server.WebSocketHandler.async.doc.example1 = do + use Path / + use Text ++ + config = server.Config.Config None (Port "8081") 1000 None + socketHandler = async (cases + TextMessage msg -> emit (TextMessage ("You said: " ++ msg)) + _ -> ()) do + abilities.repeat 100 do + sleepMicroseconds 4000000 + emit (TextMessage "Regularly scheduled message for you") + socketRouteHandler : Handler IO + socketRouteHandler = HandlerWebSocket cases + req | Routes.get (root / "socket") req -> socketHandler + _ -> abort + routes : Routes IO + routes = Routes.default <<< socketRouteHandler + stopServer = Config.serve routes config + printLine "started server on port 8081. Press to stop." + _ = readLine() + stopServer() + +server.WebSocketHandler.doc : Doc +server.WebSocketHandler.doc = + {{ + A {type WebSocketHandler} is a pair of functions to handle a {type WebSocket} + connection. + + The first function is called when the WebSocket connection is closed. + + The second function is called after a {type WebSocket} connection has been + successfully negotiated. It takes, as input, a {type WebSocket}. It can use + {{ docLink (docEmbedTermLink do WebSocket.send) }}, + {{ docLink (docEmbedTermLink do WebSocket.receive) }}, {{ + docLink (docEmbedTermLink do WebSocket.close) }} to interact with the + websocket. + + Note: When the handler function returns, the underlying {type Connection} + will be closed. + + There are are a couple of helpers for creating a {type WebSocketHandler}; + {{ docLink (docEmbedTermLink do sync) }}, {{ + docLink (docEmbedTermLink do async) }} + + {{ docLink (docEmbedTermLink do sync) }} : {{ sync.doc }} + + {{ docLink (docEmbedTermLink do async) }} : {{ async.doc }} + }} + +server.WebSocketHandler.sync : + '{IO, Exception, Ask Message, Stream Message} () ->{IO} WebSocketHandler +server.WebSocketHandler.sync effect = + handleClose : Either Failure () ->{IO} () + handleClose = cases + Left failure -> Debug.trace "WebSocket.failure: " failure + Right _ -> () + newEffect : + WebSocket + -> ((Either Failure () ->{IO, Exception} ()) ->{IO, Exception} ()) + ->{IO, Exception} () + newEffect ws addFinalizer = + addFinalizer handleClose + handler : Request {Ask Message, Stream Message} () ->{IO, Exception} () + handler = cases + { ask -> k } -> + msg = WebSocket.receive ws + handle k msg with handler + { emit msg -> k } -> + WebSocket.send ws msg + handle k() with handler + { u } -> u + handle effect() with handler + WebSocketHandler newEffect + +server.WebSocketHandler.sync.doc : Doc +server.WebSocketHandler.sync.doc = + {{ + Creates a {type WebSocketHandler} that reads and writes messages + synchronously. This is useful when you want to receive messages synchronously + and block sending when you are waiting for a message. + + Example: + + * Receives 10 messages + * Sends a single response acknowledging the 10 messages + * Sends a response to each of the 10 messages + + {{ docSource [docSourceElement (docEmbedTermLink do sync.doc.example1) []] }} + }} + +server.WebSocketHandler.sync.doc.example1 : '{IO, Exception} () +server.WebSocketHandler.sync.doc.example1 = do + use Path / + use Text ++ + config = server.Config.Config None (Port "8081") 1000 None + socketHandler = sync do + messages = fill' 10 do ask + emit (TextMessage ("You sent me 10 messages: " ++ toDebugText messages)) + flipped.deprecated messages cases + TextMessage msg -> emit (TextMessage ("You said: " ++ msg)) + _ -> () + socketRouteHandler : Handler IO + socketRouteHandler = HandlerWebSocket cases + req | Routes.get (root / "socket") req -> socketHandler + _ -> abort + routes : Routes IO + routes = Routes.default <<< socketRouteHandler + stopServer = Config.serve routes config + printLine "started server on port 8081. Press to stop." + _ = readLine() + stopServer() + +tests.checkHttpRequestRoundTrip : HttpRequest ->{Exception, Each} () +tests.checkHttpRequestRoundTrip req = + bs = HttpRequest.encode proxy.ProxyPresence.NoProxy req + decodedReq = HttpRequest.fromBytes bs + normalizedReq = + (HttpRequest method version (URI scheme _ path query fragment) headers body) + = + req + normalizedUri = URI Scheme.empty None path query fragment + HttpRequest method version normalizedUri headers body + normalizedDecodedReq = + headers.modify (Headers.delete "Content-Length") decodedReq + HttpRequest.ensureEqual normalizedDecodedReq normalizedReq + +tests.checkHttpRequestRoundTrip.doc : Doc +tests.checkHttpRequestRoundTrip.doc = + {{ + Checks that when the provided {type HttpRequest} is encoded and then decoded, + the result is roughly the same as the original request. + + There is some leniency in the equivalence of the decoded request. For + example, the decoded request will not have host/port information as that is + not part of the encoded HTTP request. Likewise, the encoder may add a + `Content-Length` header that is not present on the original request. + }} + +test> tests.chunkedWithoutTrailers = + verifyAndIgnore do + use HttpResponse == + use Text toUtf8 + head = ["HTTP/1.1 200 OK", "Transfer-Encoding:chunked"] + headText = + List.foldLeft (Text.++) Text.empty (List.intersperse "\r\n" head) + chunkToText = cases + (length, value) -> length Text.++ "\r\n" Text.++ value Text.++ "\r\n" + body = + List.map + chunkToText + [ ("3", "123") + , ("A", "1234\n67890") + , ("1A", "12345678901\n\n4567\n90123456") + , ("0", "") + ] + responseStream : '{Stream Bytes} () + responseStream = + Stream.map + toUtf8 + ((do emit (headText Text.++ "\r\n\r\n")) Stream.++ Stream.fromList body) + parsed = HttpResponse.fromStream false responseStream + expected = + headers = Headers.singleton "Transfer-Encoding" "chunked" + body = Body (toUtf8 "1231234\n6789012345678901\n\n4567\n90123456") + HttpResponse (Status 200 "OK") Version.http11 headers body + ensuring do expected == parsed + +test> tests.chunkedWithTrailers = + verifyAndIgnore do + use HttpResponse == + use Text toUtf8 + head = + [ "HTTP/1.1 200 OK" + , "Transfer-Encoding:chunked" + , "Trailer:Server-Timing,Expires" + ] + headText = + List.foldLeft (Text.++) Text.empty (List.intersperse "\r\n" head) + chunkToText = cases + (length, value) -> length Text.++ "\r\n" Text.++ value Text.++ "\r\n" + body = + List.map + chunkToText + [ ("3", "123") + , ("A", "1234\n67890") + , ("1A", "12345678901\n\n4567\n90123456") + , ( "0" + , "Server-Timing:cache;desc=\"Cache Read\";dur=23.2\r\nExpires: Wed, 21 Oct 2015 07:28:00 GMT" + ) + ] + responseStream : '{Stream Bytes} () + responseStream = + Stream.map + toUtf8 + ((do emit (headText Text.++ "\r\n\r\n")) Stream.++ Stream.fromList body + Stream.++ (do emit "\r\n")) + parsed = HttpResponse.fromStream false responseStream + expected = + headers = + Headers.fromList + [ ("Transfer-Encoding", "chunked") + , ("Trailer", "Server-Timing,Expires") + , ("Server-Timing", "cache;desc=\"Cache Read\";dur=23.2") + , ("Expires", "Wed, 21 Oct 2015 07:28:00 GMT") + ] + body = Body (toUtf8 "1231234\n6789012345678901\n\n4567\n90123456") + HttpResponse (Status 200 "OK") Version.http11 headers body + ensuring do expected == parsed + +test> tests.chunkedWithTrailersAreActuallyOptional = + verifyAndIgnore do + use Text toUtf8 + head = + ["HTTP/1.1 200 OK", "Transfer-Encoding:chunked", "Trailer:Server-Timing"] + headText = + List.foldLeft (Text.++) Text.empty (List.intersperse "\r\n" head) + chunkToText = cases + (length, value) -> length Text.++ "\r\n" Text.++ value Text.++ "\r\n" + body = + List.map + chunkToText + [ ("3", "123") + , ("A", "1234\n67890") + , ("1A", "12345678901\n\n4567\n90123456") + , ("0", "") + ] + responseStream : '{Stream Bytes} () + responseStream = + Stream.map + toUtf8 + ((do emit (headText Text.++ "\r\n\r\n")) Stream.++ Stream.fromList body) + parsed = catch do HttpResponse.fromStream false responseStream + expected = + headers = + Headers.fromList + [("Transfer-Encoding", "chunked"), ("Trailer", "Server-Timing")] + body = Body (toUtf8 "1231234\n6789012345678901\n\n4567\n90123456") + HttpResponse (Status 200 "OK") Version.http11 headers body + test.ensureEqual (Right expected) parsed + +tests.exampleRequest : Bytes +tests.exampleRequest = + use Bytes ++ + Text.toUtf8 "POST /foo HTTP/1.1\r\n" ++ Bytes.drop 17 exampleResponse + +tests.exampleResponse : Bytes +tests.exampleResponse = + 0xs485454502f312e3120323030204f4b0d0a436f6e6e656374696f6e3a204b6565702d416c6976650d0a436f6e74656e742d4c616e67756167653a20656e5f55530d0a436f6e74656e742d4c656e6774683a20323236320d0a436f6e74656e742d547970653a20746578742f68746d6c3b20636861727365743d7574662d380d0a446174653a205468752c203135204a756e20323032332030323a33373a313920474d540d0a4b6565702d416c6976653a2074696d656f75743d31300d0a4c6173742d4d6f6469666965643a2053756e2c203236204d617220323032332030383a35343a303520474d540d0a4163636570742d456e636f64696e673a20677a69702c206465666c6174652c206964656e746974790d0a5365727665723a20435550532f322e34204950502f322e310d0a582d4672616d652d4f7074696f6e733a2044454e590d0a436f6e74656e742d53656375726974792d506f6c6963793a206672616d652d616e636573746f727320276e6f6e65270d0a0d0a3c21444f43545950452048544d4c3e0a3c68746d6c3e0a20203c686561643e0a202020203c6c696e6b2072656c3d227374796c6573686565742220687265663d222f637570732e6373732220747970653d22746578742f637373223e0a202020203c6c696e6b2072656c3d2273686f72746375742069636f6e2220687265663d222f6170706c652d746f7563682d69636f6e2e706e672220747970653d22696d6167652f706e67223e0a202020203c6d65746120636861727365743d227574662d38223e0a202020203c6d65746120687474702d65717569763d22436f6e74656e742d547970652220636f6e74656e743d22746578742f68746d6c3b20636861727365743d7574662d38223e0a202020203c6d65746120687474702d65717569763d22582d55412d436f6d70617469626c652220636f6e74656e743d2249453d39223e0a202020203c6d657461206e616d653d2276696577706f72742220636f6e74656e743d2277696474683d6465766963652d7769647468223e0a202020203c7469746c653e486f6d65202d204355505320322e342e323c2f7469746c653e0a20203c2f686561643e0a20203c626f64793e0a202020203c64697620636c6173733d22637570732d686561646572223e0a2020202020203c756c3e0a093c6c693e3c6120687265663d2268747470733a2f2f6f70656e7072696e74696e672e6769746875622e696f2f637570732f22207461726765743d225f626c616e6b223e4f70656e5072696e74696e6720435550533c2f613e3c2f6c693e0a093c6c693e3c6120636c6173733d226163746976652220687265663d222f223e486f6d653c2f613e3c2f6c693e0a093c6c693e3c6120687265663d222f61646d696e223e41646d696e697374726174696f6e3c2f613e3c2f6c693e0a093c6c693e3c6120687265663d222f636c61737365732f223e436c61737365733c2f613e3c2f6c693e0a093c6c693e3c6120687265663d222f68656c702f223e48656c703c2f613e3c2f6c693e0a093c6c693e3c6120687265663d222f6a6f62732f223e4a6f62733c2f613e3c2f6c693e0a093c6c693e3c6120687265663d222f7072696e746572732f223e5072696e746572733c2f613e3c2f6c693e0a2020202020203c2f756c3e0a202020203c2f6469763e0a202020203c64697620636c6173733d22637570732d626f6479223e0a2020202020203c64697620636c6173733d22726f77223e0a093c68313e4f70656e5072696e74696e67204355505320322e342e323c2f68313e0a093c703e546865207374616e64617264732d62617365642c206f70656e20736f75726365207072696e74696e672073797374656d20646576656c6f706564206279203c6120636c6173733d226a756d626f6c696e6b2220687265663d2268747470733a2f2f6f70656e7072696e74696e672e6769746875622e696f2f22207461726765743d225f626c616e6b223e4f70656e5072696e74696e673c2f613e20666f72204c696e7578c2ae20616e64206f7468657220556e6978c2ae2d6c696b65206f7065726174696e672073797374656d732e20435550532075736573203c6120687265663d2268747470733a2f2f7777772e7077672e6f72672f6970702f657665727977686572652e68746d6c22207461726765743d225f626c616e6b223e4950502045766572797768657265e284a23c2f613e20746f20737570706f7274207072696e74696e6720746f206c6f63616c20616e64206e6574776f726b207072696e746572732e3c2f703e0a2020202020203c2f6469763e0a2020202020203c64697620636c6173733d22726f77223e0a093c64697620636c6173733d22746869726473223e0a0920203c68323e4355505320666f722055736572733c2f68323e0a0920203c703e3c6120687265663d2268656c702f6f766572766965772e68746d6c223e4f76657276696577206f6620435550533c2f613e3c2f703e0a0920203c703e3c6120687265663d2268656c702f6f7074696f6e732e68746d6c223e436f6d6d616e642d4c696e65205072696e74696e6720616e64204f7074696f6e733c2f613e3c2f703e0a093c2f6469763e0a093c64697620636c6173733d22746869726473223e0a0920203c68323e4355505320666f722041646d696e6973747261746f72733c2f68323e0a0920203c703e3c6120687265663d2268656c702f61646d696e2e68746d6c223e416464696e67205072696e7465727320616e6420436c61737365733c2f613e3c2f703e0a0920203c703e3c6120687265663d2268656c702f706f6c69636965732e68746d6c223e4d616e6167696e67204f7065726174696f6e20506f6c69636965733c2f613e3c2f703e0a0920203c703e3c6120687265663d2268656c702f6e6574776f726b2e68746d6c223e5573696e67204e6574776f726b205072696e746572733c2f613e3c2f703e0a0920203c703e3c6120687265663d2268656c702f6669726577616c6c732e68746d6c223e4669726577616c6c733c2f613e3c2f703e0a0920203c703e3c6120687265663d2268656c702f6d616e2d63757073642e636f6e662e68746d6c223e63757073642e636f6e66205265666572656e63653c2f613e3c2f703e0a093c2f6469763e0a093c64697620636c6173733d22746869726473223e0a0920203c68323e4355505320666f7220446576656c6f706572733c2f68323e0a0920203c703e3c6120687265663d2268656c702f63757073706d2e68746d6c223e435550532050726f6772616d6d696e67204d616e75616c3c2f613e3c2f703e0a0920203c703e3c6120687265663d2268656c702f6170692d66696c7465722e68746d6c223e46696c74657220616e64204261636b656e642050726f6772616d6d696e673c2f613e3c2f703e0a093c2f6469763e0a2020202020203c2f6469763e0a202020203c2f6469763e0a202020203c64697620636c6173733d22637570732d666f6f746572223e436f707972696768742026636f70793b20323032312d32303232204f70656e5072696e74696e672e20416c6c207269676874732072657365727665642e3c2f6469763e0a20203c2f626f64793e0a3c2f68746d6c3e0a + +test> tests.testChunkedRequestRoundTrip = + verifyAndIgnore do + use Text toUtf8 + req = HttpRequest.get (parseOrBug "http://google.com") + req' = HttpRequest.body.set (Body (toUtf8 "hello, world!")) req + trailer = do + emit (toUtf8 "blah blah") + Headers.empty + stream = + do HttpRequest.encodeChunked proxy.ProxyPresence.NoProxy req' trailer + ignore (HttpRequest.fromStream stream) + +test> tests.testChunkedRequestRoundTripNoTrailers = verifyAndIgnore do + req = HttpRequest.get (parseOrBug "http://google.com") + trailer = do + emit (Text.toUtf8 "blah blah") + Headers.empty + stream = do HttpRequest.encodeChunked proxy.ProxyPresence.NoProxy req trailer + ignore (HttpRequest.fromStream stream) + +test> tests.testChunkedRequestRoundTripWithTrailers = + verifyAndIgnore do + use HttpRequest addHeader + req = + HttpRequest.get (parseOrBug "http://google.com") + |> addHeader "Trailer" "Expires" + |> addHeader "Trailer" "Client-Timing" + trailer = + do + emit (Text.toUtf8 "blah blah") + Headers.fromList + [ ("Expires", "Wed, 21 Oct 2015 07:28:00 GMT") + , ("Client-Timing", "cache;desc=\"Cache Read\";dur=23.2") + ] + stream = + do HttpRequest.encodeChunked proxy.ProxyPresence.NoProxy req trailer + ignore (HttpRequest.fromStream stream) + +test> tests.testChunkedResponseRoundTrip = verifyAndIgnore do + use Text toUtf8 + res = HttpResponse.ok (Body (toUtf8 "hello, world")) + trailer = do + emit (toUtf8 "blah blah") + Headers.empty + stream = do HttpResponse.encodeChunked res trailer + ignore (HttpResponse.fromStream false stream) + +test> tests.testFromStream = + verifyAndIgnore do + use Path / + use Text toUtf8 + request = + """ + GET /docs?%25wei?rd=%26he+llo/&%25wei?rd=+th%23er%3de%25&simple=foo HTTP/1.0 + Content-Type: text/plain + Content-Length: 12 + + Hello World! + """ + stream = do emit (toUtf8 request) + actual = HttpRequest.fromStream stream + headers = + Headers.fromList + [("Content-Type", "text/plain"), ("Content-Length", "12")] + expected = + HttpRequest + GET + Version.http10 + (URI + (Scheme "") + None + (root / "docs") + (RawQuery "%25wei?rd=%26he+llo/&%25wei?rd=+th%23er%3de%25&simple=foo") + Fragment.empty) + headers + (Body (toUtf8 "Hello World!")) + test.ensureEqual expected actual + +test> tests.testHttpRequestRoundTrip = verifyAndIgnore do + use HttpRequest addHeader + body = Body (Text.toUtf8 "hello, world!") + req = HttpRequest.post (parseOrBug "http://google.com") body + req1 = req |> addHeader "Accept" "application/json" + req2 = req1 |> addHeader "User-agent" "ucm" + checkHttpRequestRoundTrip req + checkHttpRequestRoundTrip req1 + checkHttpRequestRoundTrip req2 + +test> tests.testHttpResponseRoundTrip = verifyAndIgnore do + use HttpResponse addHeader encode ensureEqual fromBytes + res = HttpResponse.ok (Body (Text.toUtf8 "hello, world")) + res1 = res |> addHeader "Accept" "application/json" + res2 = res1 |> addHeader "User-agent" "ucm" + nc = noContent + bs = encode res + bs1 = encode res1 + bs2 = encode res2 + bs3 = encode nc + ensureEqual res (fromBytes false bs) + ensureEqual res1 (fromBytes false bs1) + ensureEqual res2 (fromBytes false bs2) + ensureEqual nc (fromBytes false bs3) + +test> tests.testParseRequest = verifyAndIgnore do + (HttpRequest m v u h b) = HttpRequest.fromStream do emit exampleRequest + use test ensureEqual + ensureEqual 2262 (Bytes.size (Body.toBytes b)) + ensureEqual 11 (data.Map.size (Headers.toMap h)) + +test> tests.testParseResponse = + verifyAndIgnore do + (HttpResponse s v h b) = + HttpResponse.fromStream false do emit exampleResponse + use test ensureEqual + ensureEqual 2262 (Bytes.size (Body.toBytes b)) + ensureEqual 11 (data.Map.size (Headers.toMap h)) + +test> tests.testRequestLine = + verifyAndIgnore do + use Text ++ + validate rl = + match IPattern.run pattern.requestLine rl with + Some (requestLine, "") -> () + _ -> test.raiseFailure ("failed to parse requestLine: " ++ rl) (Any ()) + validate "GET / HTTP/1.1" + validate "POST /docs HTTP/1.0" + validate "PUT / HTTP/1.1" + validate "PUT /docs/ HTTP/1.1" + validate + "GET /docs?%25wei?rd=%26he+llo/&%25wei?rd=+th%23er%3de%25&simple=foo HTTP/1.0" + +test> tests.testStatusLine = + verifyAndIgnore do IPattern.run statusLine "HTTP/1.1 200 OK" + +up.base.Bytes.encodeNat8 : Nat -> Bytes +up.base.Bytes.encodeNat8 n = fromList.impl [Nat.and 255 n] + +up.base.data.Map.filterKeys : + (k ->{g2} Boolean) -> data.Map k v ->{g2} data.Map k v +up.base.data.Map.filterKeys f = + Map.foldLeftWithKey + (acc k v -> (if f k then Map.insert k v acc else acc)) data.Map.empty + +up.base.IO.net.Connection.receive : Connection ->{IO, Exception} Bytes +up.base.IO.net.Connection.receive c = Connection.receiver c () + +up.base.test.ensureEqualBy : (a -> a -> Boolean) -> a -> a ->{Exception} () +up.base.test.ensureEqualBy eq a1 a2 = + if eq a1 a2 then () else test.raiseFailure "elements not equal" (a1, a2) + +util.slurpByteStream : '{Stream Bytes} r -> Bytes +util.slurpByteStream stream = + use Bytes ++ + handler : Bytes -> Request {Stream Bytes} r -> Bytes + handler acc = cases + { emit bs -> tail } -> handle tail() with handler (acc ++ bs) + { done } -> acc + handle stream() with handler 0xs + +Version.fromText : Text -> Either Text Version +Version.fromText = cases + "HTTP/1.1" -> Right Version.http11 + "HTTP/1.0" -> Right Version.http10 + x -> Left ("invalid http version: " Text.++ x) + +Version.http10 : Version +Version.http10 = Version 1 0 + +Version.http11 : Version +Version.http11 = Version 1 1 + +Version.http20 : Version +Version.http20 = Version 2 0 + +Version.toText : Version -> Text +Version.toText = cases + Version major minor -> + "HTTP/" Text.++ Nat.toText major Text.++ "." Text.++ Nat.toText minor + +websockets.Endpoint.isClient : Endpoint -> Boolean +websockets.Endpoint.isClient = cases + Client -> true + Server -> false + +websockets.example : '{IO, Exception} () +websockets.example = + do + use Message text + use Nat * + use WebSocket send + handleConnection connection = withConnection connection do + request = HttpRequest.decode() + emit (HttpResponse.encode (upgradeResponse request)) + ws = threadSafeWebSocket connection Server (1024 * 1024) Bytes.empty + message = WebSocket.receive ws + Debug.trace "Received" message + send ws (text "From SERVER") + send ws (text "From SERVER 2") + bracket + (do Socket.server None (Port "9011")) + (cases BoundServerSocket socket -> Socket.close socket) + (boundSocket -> + let + listeningSocket = boundSocket |> Socket.listen + bracket + (do Socket.accept listeningSocket |> Connection.socket) + Connection.close + handleConnection) + +websockets.Frame.decoder : '{Decode, DecodeBits, Throw DecodeError} Frame +websockets.Frame.decoder = + do + Decode.label "WebSocket frame" do + use Decode failWith label + use DecodeBits bit + use Text ++ + label "WebSocket frame header" do bitsFromBytes 1 + isFin = bit() + match (bit(), bit(), bit()) with + (false, false, false) -> () + _ -> failWith "Non zero RSV bits!" + opcode = label "WebSocket opcode" do wordN 4 + bitsFromBytes 1 + isMasked = label "WebSocket isMasked" do bit() + payloadLength = label "WebSocket payload length" do match wordN 7 with + 126 -> Decode.nat16be() + 127 -> Decode.nat64be() + n -> n + maskingKey = + if isMasked then Some (label "WebSocket masking key" do nextBytes 4) + else None + payloadBytes = label "WebSocket payload" do nextBytes payloadLength + payload = match maskingKey with + None -> payloadBytes + Some key -> + match catch do maskOrUnmask key payloadBytes with + Left e -> failWith "Error unmasking" + Right v -> v + match opcode with + 0 -> Continuation isFin payload + 1 -> Text isFin (runDecode Decode.utf8 payload) + 2 -> Binary isFin payload + 8 -> Close (decodeCloseReason payload) + 9 -> Ping payload + 10 -> Pong payload + _ -> failWith ("Unsupported opcode: " ++ Nat.toText opcode) + +websockets.Frame.decoder.decodeCloseReason : + Bytes ->{Throw DecodeError} Optional (Nat, Text) +websockets.Frame.decoder.decodeCloseReason payload = + use Nat == + if Bytes.size payload == 0 then None + else payload |> (runDecode do Some (Decode.nat16be(), Decode.utf8())) + +websockets.Frame.decoder.maskOrUnmask : Bytes -> Bytes -> Bytes +websockets.Frame.decoder.maskOrUnmask key payload = + use Bytes toList + keyAsList = key |> toList + payloadAsList = payload |> toList + payloadAsList + |> mapIndexed + (idx v -> Nat.xor v (keyAsList |> List.unsafeAt (Nat.mod idx 4))) + |> (bs -> unsafeRun! do Bytes.fromList bs) + +test> websockets.Frame.decoder.tests.fragmentedContinuation = + use fromList impl + check + ((toEither do runDecode decoder 0xs80026c6f) + === Right (Continuation true 0xs6c6f)) + +test> websockets.Frame.decoder.tests.fragmentedTextUnmasked = + check + ((toEither do runDecode decoder 0xs010348656c) === Right (Text false "Hel")) + +test> websockets.Frame.decoder.tests.ping = + use fromList impl + check + ((toEither do runDecode decoder 0xs890548656c6c6f) + === Right (Ping 0xs48656c6c6f)) + +test> websockets.Frame.decoder.tests.pong = + use fromList impl + check + ((toEither do runDecode decoder 0xs8a0548656c6c6f) + === Right (Pong 0xs48656c6c6f)) + +test> websockets.Frame.decoder.tests.singleTextMasked = + check + ((toEither do runDecode decoder 0xs818537fa213d7f9f4d5158) + === Right (Text true "Hello")) + +test> websockets.Frame.decoder.tests.singleTextUnmasked = + check + ((toEither do runDecode decoder 0xs810548656c6c6f) + === Right (Text true "Hello")) + +websockets.Frame.encoder : Optional Bytes -> Frame -> Bytes +websockets.Frame.encoder maskingKey frame = + use Bytes ++ + use Nat < + use Text toUtf8 + (isFin, opcode, payload) = + match frame with + Continuation fin payload -> (fin, 0, payload) + Text fin payload -> (fin, 1, toUtf8 payload) + Binary fin payload -> (fin, 2, payload) + Close (Some (reasonCode, reason)) -> + (true, 8, encodeNat16be reasonCode ++ toUtf8 reason) + Close None -> (true, 8, 0xs00) + Ping payload -> (true, 9, payload) + Pong payload -> (true, 10, payload) + bs1 = EncodeBits.toBytes do + putBit isFin + putBit false + putBit false + putBit false + putWordN 4 opcode + putBit (maskingKey |> isSome) + match Bytes.size payload with + n + | n < 126 -> putWordN 7 n + | n < 65536 -> + putWordN 7 126 + putWordN 16 n + | otherwise -> + putWordN 7 127 + putWordN 64 n + bs2 = match maskingKey with + None -> payload + Some key -> key ++ maskOrUnmask key payload + bs1 ++ bs2 + +test> websockets.Frame.encoder.tests.fragmentedContinuation = + check (encoder None (Continuation true (Text.toUtf8 "lo")) === 0xs80026c6f) + +test> websockets.Frame.encoder.tests.fragmentedTextUnasked = + check (encoder None (Text false "Hel") === 0xs010348656c) + +test> websockets.Frame.encoder.tests.ping = + check (encoder None (Ping (Text.toUtf8 "Hello")) === 0xs890548656c6c6f) + +test> websockets.Frame.encoder.tests.pong = + check (encoder None (Pong (Text.toUtf8 "Hello")) === 0xs8a0548656c6c6f) + +test> websockets.Frame.encoder.tests.singleTextMasked = + use fromList impl + check + (encoder (Some 0xs37fa213d) (Text true "Hello") + === 0xs818537fa213d7f9f4d5158) + +test> websockets.Frame.encoder.tests.singleTextUnmasked = + check (encoder None (Text true "Hello") === 0xs810548656c6c6f) + +websockets.handshake : HttpRequest -> Connection ->{IO, Exception} () +websockets.handshake request connection = + response = upgradeResponse request + Connection.send connection (HttpResponse.encodeNoBody response) + +websockets.handshake.doc : Doc +websockets.handshake.doc = {{ Performs a WebSocket handshake on a connection }} + +websockets.internal.readFrame : Connection ->{IO, Exception} Frame +websockets.internal.readFrame conn = + Throw.toException (e -> Generic.failure "Error decoding websocket frame" e) do + fromConnection conn decoder + +websockets.Message.binary : Bytes -> Message +websockets.Message.binary = BinaryMessage + +websockets.Message.binary.doc : Doc +websockets.Message.binary.doc = + {{ Create a binary websocket message from a {type Bytes} value. }} + +websockets.Message.doc : Doc +websockets.Message.doc = + {{ + Represents a WebSocket message. It can be either a text message or a binary + message. + + ``` + Message.text "hello" + ``` + + ``` + binary 0xsdeadbeef + ``` + }} + +websockets.Message.text : Text -> Message +websockets.Message.text = TextMessage + +websockets.Message.text.doc : Doc +websockets.Message.text.doc = + {{ Create a websocket message using text. It will be encoded as UTF-8. }} + +websockets.Message.toFrames : Nat -> Message ->{Stream Frame} () +websockets.Message.toFrames maxFrameSize message = + use Bytes isEmpty + continue : Bytes -> () + continue bytes = match Bytes.splitAt maxFrameSize bytes with + (part, rest) + | isEmpty rest -> emit (Continuation true part) + | otherwise -> + emit (Continuation false part) + continue rest + match message with + TextMessage value -> + go : Text -> () + go v = match Text.splitAt maxFrameSize v with + (part, "") -> emit (Text true part) + (part, rest) -> + emit (Text false part) + continue (Text.toUtf8 rest) + go value + BinaryMessage value -> + go : Bytes -> () + go v = match Bytes.splitAt maxFrameSize v with + (part, rest) + | isEmpty rest -> emit (Binary true part) + | otherwise -> + emit (Binary false part) + continue rest + go value + +websockets.metadata.authors.alvaroc1 : Author +websockets.metadata.authors.alvaroc1 = Author alvaroc1.guid "Alvaro Carrasco" + +websockets.metadata.authors.alvaroc1.guid : GUID +websockets.metadata.authors.alvaroc1.guid = + GUID 0xs443b5bb24a76958a6bd79437bd3ad1cb023c39dd2e44d2311e42b2c0ce53f394 + +websockets.metadata.copyrightHolders.alvaroc1 : CopyrightHolder +websockets.metadata.copyrightHolders.alvaroc1 = + CopyrightHolder alvaroc1.guid "Alvaro Carrasco" + +websockets.metadata.licenses.alvaroc12023 : License +websockets.metadata.licenses.alvaroc12023 = + License [copyrightHolders.alvaroc1] [Year 2023] mit + +websockets.protocol.receive : + '{Abort, Decode, DecodeBits, Throw DecodeError, Stream Bytes} Message +websockets.protocol.receive = + do + use Decode failWith + continueMaybe : + Boolean + -> Message + ->{Abort, Decode, DecodeBits, Throw DecodeError, Stream Bytes} Message + continueMaybe isFin msg = if isFin then msg else continue msg + sendPong : Bytes -> () + sendPong payload = emit (encoder None (Pong payload)) + continue : + Message + ->{Abort, Decode, DecodeBits, Throw DecodeError, Stream Bytes} Message + continue accum = + match decoder() with + Close _ -> abort + Ping payload -> + sendPong payload + continue accum + Pong payload -> continue accum + Text _ _ -> + failWith "Received WebSocket text frame while expecting continuation" + Binary _ _ -> + failWith + "Received WebSocket binary frame while expecting continuation" + Continuation isFin payload -> + msg = match accum with + TextMessage init -> + use Text ++ + payloadText = runDecode Decode.utf8 payload + TextMessage (init ++ payloadText) + BinaryMessage init -> BinaryMessage (init Bytes.++ payload) + continueMaybe isFin msg + go : '{Abort, Decode, DecodeBits, Throw DecodeError, Stream Bytes} Message + go = + do + match decoder() with + Close _ -> abort + Ping payload -> + sendPong payload + go() + Pong _ -> go() + Text isFin payload -> continueMaybe isFin (TextMessage payload) + Binary isFin payload -> continueMaybe isFin (BinaryMessage payload) + Continuation _ _ -> + failWith + "Received WebSocket continuation frame with no previous value" + go() + +websockets.README : Doc +websockets.README = + {{ + Client and server-side WebSockets are supported. + + On the client side, you will typically use {{ + docLink (docEmbedTermLink do Http.webSocket) }} and {{ + docLink (docEmbedTermLink do HttpWebSocket.handler) }} to create a + {type WebSocket} connection. + + On the server side you will typically create a {type WebSocketHandler} to + handle a {type WebSocket} connection. + + However, some lower-level WebSocket functionality is provided for advanced + use cases. Here is an example that uses some of the lower-level + functionality: + + {{ docSource [docSourceElement (docEmbedTermLink do websockets.example) []] + }} + }} + +websockets.upgradeResponse : HttpRequest ->{Exception} HttpResponse +websockets.upgradeResponse request = + use Headers add + key = + match webSocketKey request with + Some key -> key + None -> + Exception.raise + (Generic.failure + "Not a valid websocket request" (HttpRequest.headers request)) + acceptString = createAcceptString key + responseHeaders = + Headers.empty |> add "Upgrade" "websocket" |> add "Connection" "Upgrade" + |> add "Sec-WebSocket-Accept" acceptString + HttpResponse + (Status 101 "Switching Protocols") + Version.http11 + responseHeaders + Body.empty + +websockets.util.createAcceptString : Text ->{Exception} Text +websockets.util.createAcceptString key = + use Text ++ + key ++ magicKeyString |> Text.toUtf8 |> hashBytes Sha1 |> toBase64 + |> fromUtf8 + +test> websockets.util.createAcceptString.tests.ex1 = + check + ((catch do createAcceptString "dGhlIHNhbXBsZSBub25jZQ==") + === Right "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") + +websockets.util.handshakeRequestDecoder : '{Exception, Decode} HttpRequest +websockets.util.handshakeRequestDecoder = do + use Bytes ++ + delimiter = 0xs0d0a0d0a + bytes = consumeUntil delimiter + HttpRequest.fromStream do emit (bytes ++ delimiter) + +websockets.util.magicKeyString : Text +websockets.util.magicKeyString = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + +websockets.WebSocket.close : WebSocket ->{IO, Exception} () +websockets.WebSocket.close = WebSocket.closer >> force + +websockets.WebSocket.closer : WebSocket -> '{IO, Exception} () +websockets.WebSocket.closer = cases WebSocket _ _ closer -> closer + +websockets.WebSocket.closer.modify : + ('{IO, Exception} () ->{g} '{g1, IO, Exception} ()) + -> WebSocket + ->{g} WebSocket +websockets.WebSocket.closer.modify f = cases + WebSocket send receiver closer -> WebSocket send receiver (f closer) + +websockets.WebSocket.closer.set : + '{g, IO, Exception} () -> WebSocket -> WebSocket +websockets.WebSocket.closer.set closer1 = cases + WebSocket send receiver _ -> WebSocket send receiver closer1 + +websockets.WebSocket.doc : Doc +websockets.WebSocket.doc = + {{ + A WebSocket connection. The primary operations on a WebSocket are: + + * {{ docLink (docEmbedTermLink do WebSocket.send) }} to send a message. + * {{ docLink (docEmbedTermLink do WebSocket.receive) }} to receive a message + (blocking until one arrives). + * {{ docLink (docEmbedTermLink do WebSocket.close) }} to signal to the other + end of the connection that the connection should be closed. + }} + +websockets.WebSocket.receive : WebSocket ->{IO, Exception} Message +websockets.WebSocket.receive = WebSocket.receiver >> force + +websockets.WebSocket.receiver : WebSocket -> '{IO, Exception} Message +websockets.WebSocket.receiver = cases WebSocket _ receiver _ -> receiver + +websockets.WebSocket.receiver.modify : + ('{IO, Exception} Message ->{g} '{g1, IO, Exception} Message) + -> WebSocket + ->{g} WebSocket +websockets.WebSocket.receiver.modify f = cases + WebSocket send receiver closer -> WebSocket send (f receiver) closer + +websockets.WebSocket.receiver.set : + '{g, IO, Exception} Message -> WebSocket -> WebSocket +websockets.WebSocket.receiver.set receiver1 = cases + WebSocket send _ closer -> WebSocket send receiver1 closer + +websockets.WebSocket.send : WebSocket -> Message ->{IO, Exception} () +websockets.WebSocket.send = cases WebSocket send _ _ -> send + +websockets.WebSocket.send.modify : + ((Message ->{IO, Exception} ()) ->{g} Message ->{g1, IO, Exception} ()) + -> WebSocket + ->{g} WebSocket +websockets.WebSocket.send.modify f = cases + WebSocket send receiver closer -> WebSocket (f send) receiver closer + +websockets.WebSocket.send.set : + (Message ->{g, IO, Exception} ()) -> WebSocket -> WebSocket +websockets.WebSocket.send.set send1 = cases + WebSocket _ receiver closer -> WebSocket send1 receiver closer + +websockets.WebSocket.threadSafeWebSocket : + Connection -> Endpoint -> Nat -> Bytes ->{IO} WebSocket +websockets.WebSocket.threadSafeWebSocket + connection endpoint maxSendFrameSize initialReceiveBuffer = + receive : '{IO, Exception} Message + receive = + receiveMutex = Signal.new (Some initialReceiveBuffer) + do + use Changes write + use Exception raise + previousLeftovers = change receiveMutex do match Changes.read with + Some leftovers -> + write None + leftovers + None -> Changes.retry + go = + do + withConnection connection do + decodePartial + (Abort.toOptional do + if Bytes.isEmpty previousLeftovers then () + else remit previousLeftovers + protocol.receive()) + handle tryEval do Throw.toException toFailure go + with cases + { (message, leftovers) } -> + change receiveMutex do write (Some leftovers) + getOrElse' + (do + raise + (Failure (typeLink WebSocketClosed) "WebSocket closed" (Any ()))) + message + { raise f -> _ } -> + change receiveMutex do write (Some Bytes.empty) + raise f + send = + frameBytes : Frame ->{IO} Bytes + frameBytes frame = + maskingKey = if isClient endpoint then Some (randomBytes 4) else None + encoder maskingKey frame + sendMutex = Mutex.new() + message -> + withMutex sendMutex do + toConnection + connection + (Stream.map frameBytes do toFrames maxSendFrameSize message) + close = do Connection.send connection (encoder None (Close None)) + WebSocket send receive close + +websockets.WebSocket.threadSafeWebSocket.doc : Doc +websockets.WebSocket.threadSafeWebSocket.doc = + use WebSocket send + {{ + {{ + docExample 4 do + connection endpoint maxSendFrameSize initialReceiveBuffer -> + threadSafeWebSocket + connection endpoint maxSendFrameSize initialReceiveBuffer }} creates a + {type WebSocket} wrapper for the underlying connection. + + `connection` is the network connection for the WebSocket. The WebSocket + handshake should occur __before__ calling + {{ docLink (docEmbedTermLink do threadSafeWebSocket) }}, which is handled by + the relevant HTTP client and HTTP server logic. + + `endpoint` denotes whether this is the client or server end of the + connection. If it is {Client} then all {{ docLink (docEmbedTermLink do send) + }} operations will be masked. + + `maxSendFrameSize` is the maximum number of bytes that should be sent in a + single WebSocket frame. Messages beyond this size will be split into multiple + frames. + + `initialReceiveBuffer` is a (possibly empty) sequence of bytes that should be + read as part of the initial {{ + docLink (docEmbedTermLink do WebSocket.receive) }} call. This is used to work + around a potential race condition in which the server sends an upgrade HTTP + response immediately followed by a WebSocket message and the client receives + both with a single {Socket.receive} call. + + It is safe to use the {{ docLink (docEmbedTermLink do send) }} and {{ + docLink (docEmbedTermLink do WebSocket.receive) }} functions on the returned + {type WebSocket} from multiple threads. The implementation ensures that + bytes/frames from separate messages are not interwoven. + }} +```` +