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) }
-
Highly recommended Matt Thornton's blog Grokking the Reader Monad.
It contains examples using F#+ and an explanation from scratch.
namespace System
namespace FSharpPlus
namespace FSharpPlus.Data
type IUserRepository =
abstract GetUser: email: string -> string
Multiple items
val string: value: 'T -> string
--------------------
type string = String
type IShoppingListRepository =
abstract AddToCart: shoppingList: string list -> string
type 'T list = List<'T>
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
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
--------------------
type Map<'Key,'Value (requires comparison)> =
interface IReadOnlyDictionary<'Key,'Value>
interface IReadOnlyCollection<KeyValuePair<'Key,'Value>>
interface IEnumerable
interface IStructuralEquatable
interface IComparable
interface IEnumerable<KeyValuePair<'Key,'Value>>
interface ICollection<KeyValuePair<'Key,'Value>>
interface IDictionary<'Key,'Value>
new: elements: ('Key * 'Value) seq -> Map<'Key,'Value>
member Add: key: 'Key * value: 'Value -> Map<'Key,'Value>
...
--------------------
new: elements: ('Key * 'Value) seq -> 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>
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)
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 Ask)> : 'MonadReader<'R,'T> (requires member Ask)
<summary>The environment from the monad.</summary>
<category index="18">Monad Transformers</category>
val varValue: string option
val id: x: 'T -> 'T
val tmplName: string
val body: Template option
val ds: Definition list
union case Option.Some: Value: 'T -> Option<'T>
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
--------------------
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
...
val traverse: f: ('T -> 'Applicative<'U>) -> xs: 'T list -> 'Applicative<list<'U>> (requires member Map and member ``<*>`` and member Traverse and member Return)
<summary>
Maps each element of the list to an action, evaluates these actions from left to right and collect the results.
</summary>
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>
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 array) : 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 array, 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 array) : string
(+0 other overloads)
String.Concat([<ParamArray>] args: obj array) : 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