The Reader monad is good for computations which read values from a shared environment.
- State: Similar, but it allows you to modify the environment.
One usage of the Reader monad is an alternative to dependency injection or currying
in order to pass around dependencies. The below code comes from F# Online - Josef Starýchfojtů - FSharpPlus - Advanced FP concepts in F#. You can find the presenter on github as @starychfojtu.
Why would you want to do this style?
- When you want to pass around a single environment instead of using dependency injection.
Why wouldn't you want to use this style?
- The downside of this style is that it supposes that your environment is relatively immutable. If you have different lifetimes for different implementation classes dependency injection frameworks can be easier to use.
Note:
open System
open FSharpPlus
open FSharpPlus.Data
type IUserRepository =
abstract GetUser : email : string -> string
type IShoppingListRepository =
abstract AddToCart : shoppingList : string list -> string
let getUser email =
Reader(fun (env : #IUserRepository) -> env.GetUser email)
let addToShoppingList shoppingListItems =
Reader(fun (env : #IShoppingListRepository) -> env.AddToCart shoppingListItems)
let addShoppingListM email = monad {
let! user = getUser email
//
let shoppingListItems = ["Apple"; "Pear";]
return! addToShoppingList shoppingListItems
}
type MockDataEnv() = // This is how an environment could be constructed
interface IUserRepository with
member this.GetUser email =
"Sandeep"
interface IShoppingListRepository with
member this.AddToCart shoppingListItems =
sprintf "Added the following items %A to the cart" shoppingListItems
Reader.run (addShoppingListM "sandeep@test.com") (MockDataEnv())
Sample from The Reader monad on Haskell Wiki
open System
open FSharpPlus
open FSharpPlus.Data
/// This the abstract syntax representation of a template
type Template =
/// Text
| T of string
/// Variable
| V of Template
/// Quote
| Q of Template
/// Include
| I of Template*(Definition list)
/// Compound
| C of Template list
and Definition = | D of Template*Template
/// Our environment consists of an association list of named templates and
/// an association list of named variable values.
type Environment = {templates: Map<string,Template>
variables: Map<string,string>}
/// lookup a variable from the environment
let lookupVar (name:string) (env:Environment) : string option = tryItem name env.variables
/// lookup a template from the environment
let lookupTemplate (name:string) (env:Environment) : Template option = tryItem name env.templates
/// add a list of resolved definitions to the environment
let addDefs (defs:(string*string) list) env = { env with variables = plus (Map.ofList defs) env.variables}
/// resolve a template into a string
let rec resolve : Template -> Reader<Environment,string> = function
| T s -> result s
| V t -> monad {
let! varName = resolve t
let! env = ask
let varValue = lookupVar varName env
return option id "" varValue }
| Q t -> monad {
let! tmplName = resolve t
let! env = ask
let body = lookupTemplate tmplName env
return option string "" body }
| I (t,ds) -> monad {
let! tmplName = resolve t
let! env = ask
let body = lookupTemplate tmplName env
match body with
| Some t' ->
let! defs = List.traverse resolveDef ds
return! local (addDefs defs) (resolve t')
| None -> return ""
}
| C ts -> monad {
let! resolved = List.traverse resolve ts
return String.Concat<string> resolved
}
and
/// resolve a Definition and produce a (name,value) pair
resolveDef: Definition -> Reader<Environment,string*string> =
function
| D (t,d) -> monad {
let! name = resolve t
let! value = resolve d
return (name,value) }
namespace System
namespace FSharpPlus
namespace FSharpPlus.Data
type IUserRepository =
abstract GetUser: email: string -> string
Multiple items
val string: value: 'T -> string
<summary>Converts the argument to a string using <c>ToString</c>.</summary>
<remarks>For standard integer and floating point values the and any type that implements <c>IFormattable</c><c>ToString</c> conversion uses <c>CultureInfo.InvariantCulture</c>. </remarks>
<param name="value">The input value.</param>
<returns>The converted string.</returns>
<example id="string-example"><code lang="fsharp"></code></example>
--------------------
type string = String
<summary>An abbreviation for the CLI type <see cref="T:System.String" />.</summary>
<category>Basic Types</category>
type IShoppingListRepository =
abstract AddToCart: shoppingList: string list -> string
type 'T list = List<'T>
<summary>The type of immutable singly-linked lists. </summary>
<remarks>See the <see cref="T:Microsoft.FSharp.Collections.ListModule" /> module for further operations related to lists.
Use the constructors <c>[]</c> and <c>::</c> (infix) to create values of this type, or
the notation <c>[1; 2; 3]</c>. Use the values in the <c>List</c> module to manipulate
values of this type, or pattern match against the values directly.
See also <a href="https://docs.microsoft.com/dotnet/fsharp/language-reference/lists">F# Language Guide - Lists</a>.
</remarks>
val getUser: email: string -> Reader<#IUserRepository,string>
val email: string
Multiple items
union case Reader.Reader: ('r -> 't) -> Reader<'r,'t>
--------------------
module Reader
from FSharpPlus.Data
<summary>
Basic operations on Reader
</summary>
--------------------
[<Struct>]
type Reader<'r,'t> =
| Reader of ('r -> 't)
static member ( *> ) : x: Reader<'R,'T> * y: Reader<'R,'U> -> Reader<'R,'U>
static member (<!>) : f: ('T -> 'U) * x: Reader<'R,'T> -> Reader<'R,'U>
static member ( <* ) : x: Reader<'R,'U> * y: Reader<'R,'T> -> Reader<'R,'U>
static member (<*>) : f: Reader<'R,('T -> 'U)> * x: Reader<'R,'T> -> Reader<'R,'U>
static member (=>>) : Reader<'Monoid,'T> * f: (Reader<'Monoid,'T> -> 'U) -> Reader<'Monoid,'U> (requires member ``+``)
static member (>>=) : x: Reader<'R,'T> * f: ('T -> Reader<'R,'U>) -> Reader<'R,'U>
static member Delay: body: (unit -> Reader<'R,'T>) -> Reader<'R,'T>
static member Extract: Reader<'Monoid,'T> -> 'T (requires member Zero)
static member Return: x: 'T -> Reader<'R,'T>
static member TryFinally: Reader<'a2,'a3> * f: (unit -> unit) -> Reader<'a2,'a3>
...
<summary> Computation type: Computations which read values from a shared environment.
<para /> Binding strategy: Monad values are functions from the environment to a value. The bound function is applied to the bound value, and both have access to the shared environment.
<para /> Useful for: Maintaining variable bindings, or other shared environment.</summary>
val env: #IUserRepository
abstract IUserRepository.GetUser: email: string -> string
val addToShoppingList: shoppingListItems: string list -> Reader<#IShoppingListRepository,string>
val shoppingListItems: string list
val env: #IShoppingListRepository
abstract IShoppingListRepository.AddToCart: shoppingList: string list -> string
val addShoppingListM: email: string -> Reader<'a,string> (requires 'a :> IShoppingListRepository and 'a :> IUserRepository)
val monad<'monad<'t>> : MonadFxBuilder<'monad<'t>>
<summary>
Creates a (lazy) monadic computation expression with side-effects (see http://fsprojects.github.io/FSharpPlus/computation-expressions.html for more information)
</summary>
val user: string
Multiple items
type MockDataEnv =
interface IShoppingListRepository
interface IUserRepository
new: unit -> MockDataEnv
--------------------
new: unit -> MockDataEnv
val this: MockDataEnv
val sprintf: format: Printf.StringFormat<'T> -> 'T
<summary>Print to a string using the given format.</summary>
<param name="format">The formatter.</param>
<returns>The formatted result.</returns>
<example>See <c>Printf.sprintf</c> (link: <see cref="M:Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThen``1" />) for examples.</example>
val run: Reader<'R,'T> -> ('R -> 'T)
type Template =
| T of string
| V of Template
| Q of Template
| I of Template * Definition list
| C of Template list
This the abstract syntax representation of a template
union case Template.T: string -> Template
Text
union case Template.V: Template -> Template
Variable
union case Template.Q: Template -> Template
Quote
union case Template.I: Template * Definition list -> Template
Include
type Definition = | D of Template * Template
union case Template.C: Template list -> Template
Compound
union case Definition.D: Template * Template -> Definition
type Environment =
{
templates: Map<string,Template>
variables: Map<string,string>
}
Our environment consists of an association list of named templates and
an association list of named variable values.
Environment.templates: Map<string,Template>
Multiple items
module Map
from FSharpPlus
<summary>
Additional operations on Map<'Key, 'Value>
</summary>
--------------------
module Map
from Microsoft.FSharp.Collections
<summary>Contains operations for working with values of type <see cref="T:Microsoft.FSharp.Collections.FSharpMap`2" />.</summary>
--------------------
type Map<'Key,'Value (requires comparison)> =
interface IReadOnlyDictionary<'Key,'Value>
interface IReadOnlyCollection<KeyValuePair<'Key,'Value>>
interface IEnumerable
interface IComparable
interface IEnumerable<KeyValuePair<'Key,'Value>>
interface ICollection<KeyValuePair<'Key,'Value>>
interface IDictionary<'Key,'Value>
new: elements: seq<'Key * 'Value> -> Map<'Key,'Value>
member Add: key: 'Key * value: 'Value -> Map<'Key,'Value>
member Change: key: 'Key * f: ('Value option -> 'Value option) -> Map<'Key,'Value>
...
<summary>Immutable maps based on binary trees, where keys are ordered by F# generic comparison. By default
comparison is the F# structural comparison function or uses implementations of the IComparable interface on key values.</summary>
<remarks>See the <see cref="T:Microsoft.FSharp.Collections.MapModule" /> module for further operations on maps.
All members of this class are thread-safe and may be used concurrently from multiple threads.</remarks>
--------------------
new: elements: seq<'Key * 'Value> -> Map<'Key,'Value>
Environment.variables: Map<string,string>
val lookupVar: name: string -> env: Environment -> string option
lookup a variable from the environment
val name: string
val env: Environment
Multiple items
val option: f: ('g -> 'h) -> n: 'h -> _arg1: 'g option -> 'h
<summary>
Takes a function, a default value and a option value. If the option value is None, the function returns the default value.
Otherwise, it applies the function to the value inside Some and returns the result.
</summary>
<category index="0">Common Combinators</category>
--------------------
type 'T option = Option<'T>
<summary>The type of optional values. When used from other CLI languages the
empty option is the <c>null</c> value. </summary>
<remarks>Use the constructors <c>Some</c> and <c>None</c> to create values of this type.
Use the values in the <c>Option</c> module to manipulate values of this type,
or pattern match against the values directly.
'None' values will appear as the value <c>null</c> to other CLI languages.
Instance methods on this type will appear as static methods to other CLI languages
due to the use of <c>null</c> as a value representation.</remarks>
<category index="3">Options</category>
val tryItem: n: 'K -> source: 'Indexed<'T> -> 'T option (requires member TryItem)
<summary>
Tries to get an item from the given index.
</summary>
<category index="16">Indexable</category>
val lookupTemplate: name: string -> env: Environment -> Template option
lookup a template from the environment
val addDefs: defs: (string * string) list -> env: Environment -> Environment
add a list of resolved definitions to the environment
val defs: (string * string) list
val plus: x: 'Monoid -> y: 'Monoid -> 'Monoid (requires member ``+``)
<summary>
Combines two monoids in one.
</summary>
<category index="4">Monoid</category>
val ofList: elements: ('Key * 'T) list -> Map<'Key,'T> (requires comparison)
<summary>Returns a new map made from the given bindings.</summary>
<param name="elements">The input list of key/value pairs.</param>
<returns>The resulting map.</returns>
<example id="oflist-1"><code lang="fsharp">
let input = [ (1, "a"); (2, "b") ]
input |> Map.ofList // evaluates to map [(1, "a"); (2, "b")]
</code></example>
val resolve: _arg1: Template -> Reader<Environment,string>
resolve a template into a string
val s: string
val result: x: 'T -> 'Functor<'T> (requires member Return)
<summary>
Lifts a value into a Functor. Same as return in Computation Expressions.
</summary>
<category index="2">Applicative</category>
val t: Template
val varName: string
val ask<'MonadReader<'R,'T> (requires member get_Ask)> : 'MonadReader<'R,'T> (requires member get_Ask)
<summary>The environment from the monad.</summary>
<category index="18">Monad Transformers</category>
val varValue: string option
val id: x: 'T -> 'T
<summary>The identity function</summary>
<param name="x">The input value.</param>
<returns>The same value.</returns>
<example id="id-example"><code lang="fsharp">
id 12 // Evaulates to 12
id "abc" // Evaulates to "abc"
</code></example>
val tmplName: string
val body: Template option
val ds: Definition list
union case Option.Some: Value: 'T -> Option<'T>
<summary>The representation of "Value of type 'T"</summary>
<param name="Value">The input value.</param>
<returns>An option representing the value.</returns>
val t': Template
Multiple items
module List
from FSharpPlus.Data
<summary>
Additional operations on List
</summary>
--------------------
module List
from FSharpPlus
<summary>
Additional operations on List
</summary>
--------------------
module List
from Microsoft.FSharp.Collections
<summary>Contains operations for working with values of type <see cref="T:Microsoft.FSharp.Collections.list`1" />.</summary>
<namespacedoc><summary>Operations for collections such as lists, arrays, sets, maps and sequences. See also
<a href="https://docs.microsoft.com/dotnet/fsharp/language-reference/fsharp-collection-types">F# Collection Types</a> in the F# Language Guide.
</summary></namespacedoc>
--------------------
type List<'T> =
| op_Nil
| op_ColonColon of Head: 'T * Tail: 'T list
interface IReadOnlyList<'T>
interface IReadOnlyCollection<'T>
interface IEnumerable
interface IEnumerable<'T>
member GetReverseIndex: rank: int * offset: int -> int
member GetSlice: startIndex: int option * endIndex: int option -> 'T list
static member Cons: head: 'T * tail: 'T list -> 'T list
member Head: 'T
member IsEmpty: bool
member Item: index: int -> 'T with get
...
<summary>The type of immutable singly-linked lists.</summary>
<remarks>Use the constructors <c>[]</c> and <c>::</c> (infix) to create values of this type, or
the notation <c>[1;2;3]</c>. Use the values in the <c>List</c> module to manipulate
values of this type, or pattern match against the values directly.
</remarks>
<exclude />
val traverse: f: ('T -> 'Applicative<'U>) -> xs: 'T list -> 'Applicative<list<'U>> (requires member Map and member ``<*>`` and member Traverse and member Return)
val resolveDef: _arg10: Definition -> Reader<Environment,(string * string)>
resolve a Definition and produce a (name,value) pair
val local: f: ('R1 -> 'R2) -> m: 'MonadReader<'R2,'T> -> 'MonadReader<'R1,'T> (requires member Local)
<summary> Executes a computation in a modified environment. </summary>
<category index="18">Monad Transformers</category>
<param name="f"> The function to modify the environment. </param>
<param name="m"> Reader to run in the modified environment. </param>
union case Option.None: Option<'T>
<summary>The representation of "No value"</summary>
val ts: Template list
val resolved: string list
Multiple items
type String =
interface IEnumerable<char>
interface IEnumerable
interface ICloneable
interface IComparable
interface IComparable<string>
interface IConvertible
interface IEquatable<string>
new: value: nativeptr<char> -> unit + 8 overloads
member Clone: unit -> obj
member CompareTo: value: obj -> int + 1 overload
...
<summary>Represents text as a sequence of UTF-16 code units.</summary>
--------------------
String(value: nativeptr<char>) : String
String(value: char[]) : String
String(value: ReadOnlySpan<char>) : String
String(value: nativeptr<sbyte>) : String
String(c: char, count: int) : String
String(value: nativeptr<char>, startIndex: int, length: int) : String
String(value: char[], startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : String
String.Concat<'T>(values: Collections.Generic.IEnumerable<'T>) : string
(+0 other overloads)
String.Concat([<ParamArray>] values: string[]) : string
(+0 other overloads)
String.Concat([<ParamArray>] args: obj[]) : string
(+0 other overloads)
String.Concat(arg0: obj) : string
(+0 other overloads)
String.Concat(values: Collections.Generic.IEnumerable<string>) : string
(+0 other overloads)
String.Concat(str0: string, str1: string) : string
(+0 other overloads)
String.Concat(str0: ReadOnlySpan<char>, str1: ReadOnlySpan<char>) : string
(+0 other overloads)
String.Concat(arg0: obj, arg1: obj) : string
(+0 other overloads)
String.Concat(str0: string, str1: string, str2: string) : string
(+0 other overloads)
String.Concat(str0: ReadOnlySpan<char>, str1: ReadOnlySpan<char>, str2: ReadOnlySpan<char>) : string
(+0 other overloads)
val d: Template
val value: string