From 4daf14f82286caea93e124e33cdf324f1f19005e Mon Sep 17 00:00:00 2001 From: Matt Thornton Date: Wed, 30 Jun 2021 20:29:58 +0100 Subject: [PATCH] Add XML documentation. (#7) --- .../Api.fs | 54 +++++-- .../BindResult.fs | 33 ++++ .../Binder.fs | 150 +++++++++++++++--- .../Decode.fs | 54 ++++++- .../BinderTests.fs | 16 +- 5 files changed, 264 insertions(+), 43 deletions(-) diff --git a/src/Symbolica.Extensions.Configuration.FSharp/Api.fs b/src/Symbolica.Extensions.Configuration.FSharp/Api.fs index d2d8eda..8e3e5cd 100644 --- a/src/Symbolica.Extensions.Configuration.FSharp/Api.fs +++ b/src/Symbolica.Extensions.Configuration.FSharp/Api.fs @@ -2,28 +2,62 @@ namespace Symbolica.Extensions.Configuration.FSharp [] module Api = + /// A computation expression that is used to create a binder which binds the data of config section to a user defined type. + /// An instance of the builder. let bind = Binder.Builder() + /// A combinator for taking an existing binder and nesting it under a parent section at the given key. + /// The key of the child section to which the should be bound. + /// The binder that binds the child section to some type. + /// A binder for the section at the . let section key sectionBinder = key |> Binder.section |> Binder.nest sectionBinder + /// A combinator for taking an existing binder and nesting it under an optional parent section at the given key. + /// If the does not exist or has an empty value then the binder will evaluate to None. + /// The key of the child section to which the should be bound. + /// The binder that binds the child section to some type. + /// A binder for the section at the . let optSection key sectionBinder = key |> Binder.optSection |> Binder.nestOpt sectionBinder - let value = - Binder.section >> Binder.nest Binder.Section.value + /// Binds the value at the as a string. + /// The key whose value should be bound. + /// A binder for the value at the . + let value key = + key + |> Binder.section + |> Binder.nest Binder.Section.value - let valueOf decoder = value >> Binder.bind decoder + /// Binds the value at the using the . + /// The key whose value should be bound. + /// The decoder to use when converting the value. + /// A binder for the value at the . + let valueOf decoder key = key |> value |> Binder.bind decoder - let optValue = - Binder.optSection - >> Binder.nestOpt Binder.Section.value + /// Binds the optional value at the as a string. + /// If the does not exist or has an empty value then the binder will evaluate to None. + /// The key whose value should be bound. + /// A binder for the optional value at the . + let optValue key = + key + |> Binder.optSection + |> Binder.nestOpt Binder.Section.value - let optValueOf decoder = - optValue - >> Binder.bind + /// Binds the optional value at the using the . + /// + /// If the does not exist or has an empty value then the binder will evaluate to None. + /// If the value exists but the decoding fails then the binder will evaluate to Some(Failure(message))). + /// + /// The key whose value should be bound. + /// The decoder to use when converting the value. + /// A binder for the optional value at the . + let optValueOf decoder key = + key + |> optValue + |> Binder.bind (function | Some s -> s |> decoder |> Binder.map Some - | None -> None |> Success |> Binder.ofBind) + | None -> None |> Success |> Binder.ofBindResult) diff --git a/src/Symbolica.Extensions.Configuration.FSharp/BindResult.fs b/src/Symbolica.Extensions.Configuration.FSharp/BindResult.fs index babf3d3..ad47360 100644 --- a/src/Symbolica.Extensions.Configuration.FSharp/BindResult.fs +++ b/src/Symbolica.Extensions.Configuration.FSharp/BindResult.fs @@ -6,6 +6,17 @@ type BindResult<'a> = module BindResult = + /// + /// Applies the to the function . + /// + /// + /// This applicative instance accumulates errors. + /// If both and are Success then the outcome will be Success; + /// otherwise the outcome will be a Failure containing all errors. + /// + /// The function to which the value will be applied. + /// The value to be applied to the function. + /// A containing the result of applying the value to the function. let apply f a : BindResult<'b> = match f, a with | Failure e1, Failure e2 -> Failure(List.append e1 e2) @@ -13,21 +24,43 @@ module BindResult = | Success _, Failure e2 -> e2 |> Failure | Success f, Success a -> a |> f |> Success + /// Monadic bind for a . + /// + /// If the input is Success then the function will be evaluated + /// with the data contained in that ; otherwise the original Failure will be maintained. + /// + /// The function to which the value will be bound. + /// A . let bind f : BindResult<'a> -> BindResult<'b> = function | Success x -> x |> f | Failure e -> e |> Failure + /// + /// Extracts the value from applying the compensation function + /// in the Failure case. + /// let defaultWith f = function | Success x -> x | Failure e -> e |> f + /// Maps the output of the . + /// The function used to map the output. + /// A whose output is mapped by the function . let map f : BindResult<'a> -> BindResult<'b> = function | Success x -> x |> f |> Success | Failure e -> e |> Failure + /// Combines two instances. + /// + /// If both instances are Success then the result is a Success containing a tuple with the value + /// of both instances. + /// If at least one instance is a Failure then the result is a Failure holding all of the errors. + /// + /// The left hand side of the zip. + /// The right hand side of the zip. let zip x y : BindResult<'a * 'b> = match x, y with | Failure e1, Failure e2 -> Failure(List.append e1 e2) diff --git a/src/Symbolica.Extensions.Configuration.FSharp/Binder.fs b/src/Symbolica.Extensions.Configuration.FSharp/Binder.fs index b6100e5..df1b2e7 100644 --- a/src/Symbolica.Extensions.Configuration.FSharp/Binder.fs +++ b/src/Symbolica.Extensions.Configuration.FSharp/Binder.fs @@ -2,18 +2,56 @@ namespace Symbolica.Extensions.Configuration.FSharp open Microsoft.Extensions.Configuration +/// +/// A specialised type of Reader monad binding values from an . +/// +/// +/// By utilising the Reader monad the caller can avoid having to supply an +/// until the end of the computation. Making the binding code less cluttered and more declarative. +/// type Binder<'a> = Binder of (IConfiguration -> BindResult<'a>) module Binder = - let ofBind b = Binder(fun _ -> b) - - let run (Binder b) = b - - let eval config (b: Binder<'a>) = (run b) config - + /// Create a from a . + /// + /// Effectively this is creating a that doesn't need to read from the . + /// + /// The from which to create this . + /// A . + let ofBindResult bindResult = Binder(fun _ -> bindResult) + + /// Unwraps the . + /// The function which was contained inside in the . + let run (Binder binder) = binder + + /// Evaluates the for the . + /// The to bind to. + /// The to be evaluated. + /// A . + let eval config (binder: Binder<'a>) = (run binder) config + + /// + /// Applies the to the function . + /// + /// + /// This applicative instance accumulates errors. + /// If both and are Success then the outcome will be Success; + /// otherwise the outcome will be a Failure containing all errors. + /// + /// The function to which the value will be applied. + /// The value to be applied to the function. + /// A containing the result of applying the value to the function. let apply (f: Binder<'a -> 'b>) (a: Binder<'a>) : Binder<'b> = Binder(fun config -> BindResult.apply (f |> eval config) (a |> eval config)) + /// Monadic bind for a . + /// + /// If is Success then the function will be evaluated + /// with the data contained in ; otherwise the original Failure will be maintained. + /// + /// The function to which the value will be bound. + /// The that contains the input value to the function . + /// A . let bind (f: 'a -> Binder<'b>) (m: Binder<'a>) : Binder<'b> = Binder (fun config -> @@ -21,33 +59,93 @@ module Binder = |> eval config |> BindResult.bind (f >> eval config)) + /// Maps the inputs to the . + /// The function used to map the input. + /// The to be contramapped. + /// A whose input is mapped by the function . let contramap f (m: Binder<'a>) = Binder(f >> run m) - let nest (b: Binder<'a>) parentBinder = + /// Maps the output of the . + /// The function used to map the output. + /// The to be mapped. + /// A whose output is mapped by the function . + let map f (m: Binder<'a>) : Binder<'b> = + Binder(fun config -> m |> eval config |> BindResult.map f) + + /// + /// Nests a of a child under a of a + /// parent . + /// + /// + /// The that operates on the child . + /// + /// + /// The that extracts the child from the parent + /// . + /// + /// + /// A that is the composition of first evaluating the and + /// then evaluating the . + /// + let nest (childBinder: Binder<'a>) parentBinder = Binder (fun parent -> parentBinder |> eval parent - |> BindResult.bind (fun subSection -> b |> eval subSection)) - - let nestOpt (b: Binder<'a>) (sectionBinder: Binder) = + |> BindResult.bind (fun subSection -> childBinder |> eval subSection)) + + /// + /// Nests a of a child under an optional + /// of a parent . + /// + /// + /// The that operates on the child . + /// + /// + /// The that extracts the optional child from the parent + /// . + /// + /// + /// A that is the composition of first evaluating the and + /// then evaluating the . + /// + let nestOpt (childBinder: Binder<'a>) (parentBinder: Binder) = Binder (fun parent -> - sectionBinder + parentBinder |> eval parent |> BindResult.bind (function - | Some subSection -> b |> eval subSection |> BindResult.map Some + | Some subSection -> childBinder |> eval subSection |> BindResult.map Some | None -> None |> Success)) - let map f (m: Binder<'a>) : Binder<'b> = - Binder(fun config -> m |> eval config |> BindResult.map f) - - let result x = x |> Success |> ofBind - + /// Create a from a plain value. + /// + /// This does not read from the and always returns Success. + /// + /// The value to lift up in a . + let result x = x |> Success |> ofBindResult + + /// Combines two instances. + /// + /// If both instances are Success then the result is a Success containing a tuple with the value + /// of both instances. + /// If at least one instance is a Failure then the result is a Failure holding all of the errors. + /// + /// The left hand side of the zip. + /// The right hand side of the zip. let zip x y : Binder<'a * 'b> = Binder(fun config -> BindResult.zip (x |> eval config) (y |> eval config)) + /// + /// Attempts to bind the child located at the of the + /// input . + /// + /// The key at which to try and find a child . + /// + /// A whose input is a parent and whose output when + /// evaluated is the child . + /// let section key = Binder (fun parent -> @@ -59,6 +157,15 @@ module Binder = [ $"The key '{key}' does not exist at '{parent |> path}'." ] |> Failure) + /// + /// Binds the optional child located at the of the + /// input . + /// + /// The key at which to try and find a child . + /// + /// A whose input is a parent and whose output when + /// evaluated is the optional child . + /// let optSection key = Binder (fun parent -> @@ -72,6 +179,9 @@ module Binder = )) module Section = + /// + /// Attempts to bind the value of the current . + /// let value = Binder (fun section -> @@ -83,11 +193,15 @@ module Binder = [ $"Expected a simple value at '{section |> path}' but found an object." ] |> Failure) + /// + /// Attempts to bind the value of the current using the . + /// + /// The decoder to apply to the value. let valueOf decoder = value |> bind decoder type Builder() = member _.Bind(x: Binder<'a>, f) = x |> bind f member _.BindReturn(x: Binder<'a>, f) = x |> map f member _.MergeSources(x1, x2) = zip x1 x2 - member _.Return(x: 'a) : Binder<'a> = x |> Success |> ofBind + member _.Return(x: 'a) : Binder<'a> = x |> result member _.ReturnFrom(x: Binder<'a>) = x diff --git a/src/Symbolica.Extensions.Configuration.FSharp/Decode.fs b/src/Symbolica.Extensions.Configuration.FSharp/Decode.fs index cac0c88..151cbb1 100644 --- a/src/Symbolica.Extensions.Configuration.FSharp/Decode.fs +++ b/src/Symbolica.Extensions.Configuration.FSharp/Decode.fs @@ -1,25 +1,65 @@ namespace Symbolica.Extensions.Configuration.FSharp +/// +/// A function that takes a value and returns a which evaluates to Success if the +/// value can be decoded otherwise Failure. +/// type Decoder<'a> = string -> Binder<'a> +/// Contains decoders for common types and combinator functions for building new decoders. module Decode = + + /// + /// Lifts the function up to operate on the contents of the . + /// + /// Useful for converting a decoder of one type to another. + /// The mapping function to apply to the contents of the . + /// The . + /// A containing the mapped contents. let map f (m: Decoder<'a>) : Decoder<'b> = m >> Binder.map f - let ofParser (parser: string -> bool * 'parsed) value = - Binder - (fun section -> - match parser value with - | true, x -> Success x - | false, _ -> - Failure [ $"Could not decode '{value}' at path '{section |> path}' as type '{typeof<'parsed>}'." ]) + /// Creates a from a System.Type.TryParse style parsing function. + /// + /// Useful for creating a parser for a primitive type for which a TryParse function already exists. + /// + /// The parsing function with which to create the . + /// The value to be decoded. + /// A . + let ofParser (parser: string -> bool * 'parsed) : Decoder<'parsed> = + fun value -> + Binder + (fun section -> + match parser value with + | true, x -> Success x + | false, _ -> + Failure [ $"Could not decode '{value}' at path '{section |> path}' as type '{typeof<'parsed>}'." ]) + /// A for values. let bool = ofParser System.Boolean.TryParse + + /// A for values. let char = ofParser System.Char.TryParse + + /// A for values. let dateTime = ofParser System.DateTime.TryParse + + /// A for values. let float = ofParser System.Double.TryParse + + /// A for values. let int16 = ofParser System.Int16.TryParse + + /// A for values. let int = ofParser System.Int32.TryParse + + /// A for values. let int64 = ofParser System.Int64.TryParse + + /// A for values. let uint16 = ofParser System.UInt16.TryParse + + /// A for values. let uint = ofParser System.UInt32.TryParse + + /// A for values. let uint64 = ofParser System.UInt64.TryParse diff --git a/tests/Symbolica.Extensions.Configuration.FSharp.Tests/BinderTests.fs b/tests/Symbolica.Extensions.Configuration.FSharp.Tests/BinderTests.fs index be8fe43..b39a774 100644 --- a/tests/Symbolica.Extensions.Configuration.FSharp.Tests/BinderTests.fs +++ b/tests/Symbolica.Extensions.Configuration.FSharp.Tests/BinderTests.fs @@ -36,20 +36,20 @@ module Binder = [] let ``Failure(e1) apply Success(x) should be Failure(e1)`` e1 (x: int) = test - <@ Binder.ofBind (Failure(e1)) <*> Binder.result x + <@ Binder.ofBindResult (Failure(e1)) <*> Binder.result x |> Binder.eval config = Failure(e1) @> [] let ``Success(f) apply Failure(e2) should be Failure(e2)`` (f: int -> int) e2 = test - <@ Binder.result f <*> Binder.ofBind (Failure(e2)) + <@ Binder.result f <*> Binder.ofBindResult (Failure(e2)) |> Binder.eval config = Failure(e2) @> [] let ``Failure(e1) apply Failure(e2) should be Failure(e1 append e2)`` e1 e2 = test - <@ Binder.ofBind (Failure(e1)) - <*> Binder.ofBind (Failure(e2)) + <@ Binder.ofBindResult (Failure(e1)) + <*> Binder.ofBindResult (Failure(e2)) |> Binder.eval config = Failure(e1 |> List.append <| e2) @> module Bind = @@ -74,7 +74,7 @@ module Binder = [] let ``Failure(e) >>= f should be Failure(e)`` e (f: int -> Binder) = test - <@ Binder.ofBind (Failure(e)) + <@ Binder.ofBindResult (Failure(e)) >>= f |> Binder.eval config = Failure(e) @> @@ -105,19 +105,19 @@ module Binder = [] let ``zip Failure(e1) Success(b) should be Failure(e1)`` e1 (b: string) = test - <@ Binder.zip (Binder.ofBind (Failure(e1))) (Binder.result b) + <@ Binder.zip (Binder.ofBindResult (Failure(e1))) (Binder.result b) |> Binder.eval config = Failure(e1) @> [] let ```zip Success(a) Failure(e2) should be Failure(e2)`` (a: int) e2 = test - <@ Binder.zip (Binder.result a) (Binder.ofBind (Failure(e2))) + <@ Binder.zip (Binder.result a) (Binder.ofBindResult (Failure(e2))) |> Binder.eval config = Failure(e2) @> [] let ```zip Failure(e1) Failure(e2) should be Failure(e1 append e2)`` e1 e2 = test - <@ Binder.zip (Binder.ofBind (Failure(e1))) (Binder.ofBind (Failure(e2))) + <@ Binder.zip (Binder.ofBindResult (Failure(e1))) (Binder.ofBindResult (Failure(e2))) |> Binder.eval config = Failure(e1 |> List.append <| e2) @> module Section =