// thinkbeforecoding

Applicatives IRL

2020-10-03T16:33:28 / jeremie chassaing

This is the content of the presentation of the same name I did at Open FSharp (San Francisco) and NewCrafts (Paris)

Monads are all the rage in FP land... but Applicatives are also very useful and quite easy to get. We'll go through 3 real use cases to discover what they're up to.

Examples will be in F#. This is of course applicable in other languages with currying.

Options

We start with a simple structure: Option<'t>. As a reminder, its definition is

 type Option<'t> =
     | Some of 't
     | None

It's a value that either contains a value of type 't (the Some case) or doesn't contain a value (None)

map

We can easily write a map function for this structure.

map takes a 'a -> 'b function and applies it to the inner value of the option if any. If the option is None, it simply returns None.

 let map (f: 'a -> 'b) (x: Option<'a>) : Option<'b> =
     match x with
     | Some value -> Some (f value)
     | None -> None

when looking at map as a function taking a single parameter its signature is

('a -> 'b) -> (Option<'a> -> Option<'b>)

map takes a ('a -> 'b) function and change it in a (Option<'a> -> Option<'b>) function.

map2

map takes a single argument function f and make it Option aware. But what about a function taking two arguments ?

We can define a map2 function with the following signature:

('a -> 'b -> 'c) -> (Option<'a> -> Option<'b> -> Option<'c>)

It takes the function and calls it with the value of the two other Option parameter if they both have a value. It returns None if any is None.

 let map2 (f: 'a -> 'b -> 'c) (x: Option<'a>) (y: Option<'b>) : Option<'c> =
     match x,y with
     | Some xvalue, Some yvalue -> Some (f xvalue yvalue)
     | _ -> None

map3, map4...

For a 3 argument function, a 4 argument function we'd then have to write extra map functions. That would be doable but tedious. And where should we stop ?

Let's take a simple two argument function and se what happens when we use map on it:

 let add x y = x + y

 let mappedAdd = map add

Notice first that it compiles because add can be seen as a one argument function returning another 1 argument function:

let add x =
    fun y -> x + y

So its signature can be read as (int -> (int -> int))

Using map, it will become:

Option<int> -> Option<int -> int>

If you call it with the value Some 3, the result will be Some function that take an int and add 3 to it. Called with None, you get no function.

Lets take the most basic function application:

 let basicApply f x = f x

basicApply is a function that take a function f, a value v, and pass the value v to the function f (this is the F# <| operator).

we can make it work with option using map2:

 let apply f x = map2 basicApply f x

basicApply signature is

('a -> 'b) -> 'a -> 'b

and we can look at it as a 2 argument function:

(('a -> 'b) -> 'a) -> 'b

And map2 will change it to:

(Option<'a -> 'b> -> Option<'a>) -> Option<'b>

This is a function that take an optional function as first argument and an optional value as a second argument. If both have a value, the value is passed to the function. If any is None the result is None.

 let partialAdd = map add (Some 3) // Some(fun y -> 3 + Y)
 apply partialAdd (Some 5) // Some(3 + 5) => Some 8
Some 8

The nice thing is that it works for function with more than 2 arguments since any function can be considered as a function of 1 arguement.

 type User =
     { FirstName: string
       LastName: string
       Age: int
       FavoriteLanguage: string }
 let user firstname lastname age favoriteLang =
     { FirstName = firstname
       LastName = lastname
       Age = age
       FavoriteLanguage = favoriteLang }

 let user' = map user (Some "John") // Option<string -> int -> string -> User>
 let user'' = apply user' (Some "Doe") // Option<int -> string -> User>
 let user''' = apply user'' (Some 42) // Option<string -> User>
 apply user''' (Some "F#") // Option<User>
Some { FirstName = "John"
       LastName = "Doe"
       Age = 42
       FavoriteLanguage = "F#" }

Hopefully we can simplify the writing by defining two infix operators for map and apply.

 let (<!>) = map
 let (<*>) = apply

Now we can write

 user
 <!> Some "John"
 <*> Some "Doe"
 <*> Some 42
 <*> Some "F#"

this is very usefull with validation, and you can easily do the same thing for Result<'a,string> type, a type that has either a value of type 'a, or an error message. map is already defined and we have to write a map2:

 let map2 (f: 'a -> 'b -> 'c) 
          (xresult: Result<'a, string>)
          (yresult: Result<'b, string>) 
          : Result<'c, string> =
    match xresult, yresult with
    | Ok x, Ok y -> Ok (f x y)
    | Error fe, Ok _ -> Error fe
    | Ok _, Error xe -> Error xe
    | Error fe, Error xe -> Error (fe + "\n" + xe)

 let apply f x = map2 basicApply f x

 let (<!>) = Result.map
 let (<*>) = apply

 open System

 let notEmpty prop value =
     if String.IsNullOrEmpty value then
         Error (sprintf "%s should not be empty" prop)
     else
         Ok value
 
 let tryParse prop input =
    match Int32.TryParse(input: string) with
    | true, value -> Ok value
    | false, _ -> Error (sprintf "%s should be an number" prop)

  // define a user as previously
 type User =
     { FirstName: string
       LastName: string
       Age: int
       FavoriteLanguage: string }
 let user firstname lastname age favoriteLang =
     { FirstName = firstname
       LastName = lastname
       Age = age
       FavoriteLanguage = favoriteLang }


 user
 <!> notEmpty "firstname" "John"
 <*> notEmpty "lastname" "Doe"
 <*> tryParse "age" "42"
 <*> notEmpty "favorite language" "F#"

Now try to play with the input values and look at the result. Instead of getting Ok with a typed user, you'll get an error with a message describing why the user could ne be constructed. Neat!

Series

Applicatives can be defined on anything on which we can write a map2 functions. Any construct that is zippable.

A good example of zippable, non trivial stucture is time series. Let's define one:

 type Series<'a> = Series of 'a * (DateTime * 'a) list

It has a initial value of type 'a and a list of changes consisting of the date of change and the new value.

For instance we can easily build constant series like this:

 let always x = Series(x, [])

it defines a Series with initial value x that never change.

We can also define a map:

 let map f (Series(initial, changes)) =
    Series( f initial, List.map (fun (d,v) -> d, f v) changes)

It applies the function f to the initial value and every change values.

map2 is a bit more convoluted. The two series start with an initial value that can be fed to the given function. After that, one of the series change, and we call f with the initial value of the one that didn't change an the new value of the other one.. each time a value change, we use this value and the last known value of the other one. We use a recursive function for this.

 let map2 f (Series(ix, cx)) (Series(iy, cy)) =
    let rec zip lastx lasty changesx changesy =
        [ match changesx, changesy with
          | [],[] -> () // we're done
          | [], _ ->
             // x won't change anymore
             for d,y in changesy do 
                d, f lastx y
          | _, [] -> 
             // y won't change anymore
             for d,x in changesx do 
                d, f x lasty
          | (dx,x) :: tailx , (dy,y) :: taily ->
            if dx < dy then
                // x changes before y
                dx, f x lasty
                yield! zip x lasty tailx changesy
            elif dy < dx then
                // y changes before x
                dy, f lastx y
                yield! zip lastx y changesx taily
            else
                // x and y change at the same time
                // dx is equal to dy
                dx, f x y
                yield! zip x y tailx taily
        ]
    Series(f ix iy, zip ix iy cx cy)

using map2 we can add two series:

 let add x y = x + y
 map2 add
        (Series(0, [DateTime(2020,10,03), 1
                    DateTime(2020,10,05), 5 ]))
        (Series(2, [DateTime(2020,10,03), 2 
                    DateTime(2020,10,04), 3]))
Series
  (2,
   [(03/10/2020 00:00:00, 3); (04/10/2020 00:00:00, 4); (05/10/2020 00:00:00, 8)])

Here is a graphical representation:

         Initial   2020-10-3    4   5
x:          0              1    -   5
y:          2              2    3   -
result:     2              3    4   8

We define apply and the two operators as we did for option:

 let apply f x = map2 basicApply f x
 let (<!>) = map
 let (<*>) = apply

And we can use it with a user

 type User =
     { FirstName: string
       LastName: string
       Age: int
       FavoriteLanguage: string }
 let user firstname lastname age favoriteLang =
     { FirstName = firstname
       LastName = lastname
       Age = age
       FavoriteLanguage = favoriteLang } 

 let birthDate = DateTime(1978,10,01) 
 user
    <!> always "John"
    <*> Series("Dow", [ DateTime(2010,08,17), "Snow"]) // married
    <*> Series(0, [for y in 1 .. 42 -> birthDate.AddYears(y), y ])
    <*> Series("C#", [DateTime(2005,05,01), "F#"])

The result is a Series of User with the new values on each change.

We can now take any function that work on non-Series values, and apply it to Series of values. This is especially useful whith hotels data. Hotels define prices, availability, closures and all other properties for their rooms for each night in a calendar. Each property can be modeled as a Series.

Let's compute the potential sells for a room:

 let potentialSells availability price closed =
     if closed then
         0m
     else
         decimal availability * price

This function has no notion of time and Series.

Now we want to apply it for every nights:

 let availability = 
    Series(0, [ DateTime(2020,10,03), 5
                DateTime(2020,10,07), 3
                DateTime(2020,10,09), 0])
 let price =
    Series(100m, [ DateTime(2020,10,04), 110m
                   DateTime(2020,10,06), 100m ])
 let closed =
    Series(false, [DateTime(2020,10,5), true
                   DateTime(2020,10,6), false])
 potentialSells
 <!> availability
 <*> price
 <*> closed
Series
  (0M,
   [(03/10/2020 00:00:00, 500M); (04/10/2020 00:00:00, 550M);
    (05/10/2020 00:00:00, 0M); (06/10/2020 00:00:00, 500M);
    (07/10/2020 00:00:00, 300M); (09/10/2020 00:00:00, 0M)])

Just imagine the headache if we did not have this applicative..

Queries

The first two examples were structures that "contain" data. For this third case, we'll consider a different problem.

We have a document store - like elastic search - where we can query documents and request specific properties for the returned document. For instance in the query for a user we ask for the "firstname", "lastname" and "age" properties and we get these properties in the result.

For the demo we'll use a simple function:

 let queryService (properties: string Set) : Map<string,string> =
    Map.ofList [
        if Set.contains "firstname" properties then
            "firstname", "John"
        if Set.contains "lastname" properties then
            "lastname", "Doe"
        if Set.contains "age" properties then
            "age", "42"
        if Set.contains "favoritelanguage" properties then
            "favoritelanguage", "F#"
    ]

The true one would take a document id and actually call the document database.

Despite being very useful (we get only the properties we're interested in), this kind of interface often leads to irritating bugs. When accessing the result, we must be sure that all the properties we use have been correctly requested. And when we stop using a property, we should not forget to remove it from the query.

That would be awsome if we could just use the properties and the query would magically know which one to fetch.

Let's introduce the Query type:

 type Query<'t> =
    { Properties: string Set
      Get: Map<string,string> -> 't }

This type is exactly here to do what we want. It contains a list of properties to query and a function that use the result to extract a value from it. We have to create simple ways to build it safely.

The first thing is to be able to query a single property

 let prop name : Query<string> =
      { Properties = set [ name ]
        Get = fun response -> response.[name] }

This function takes a property name and builds a Query that:

  • has the expected property name in Properties
  • can extract this property from the result

We can define stock properties like using the prop function:

 let firstname = prop "firstname"
 let lastname = prop "lastname"
 let favoriteLanguage = prop "favoritelanguage"

We can fetch them with the following function:

 let callService (query: Query<'t>) : 't =
    let response = queryService query.Properties
    query.Get response

For age, we'd like to convert it to an int. This is easily done with a map function that will change the result using a given function

 let map (f: 'a -> 'b) (q: Query<'a>) : Query<'b> =
     { Properties = q.Properties
       Get = fun response ->
               let v = q.Get response
               f v }

 let (<!>) = map

 let age = int <!> prop "age"

Building a full name could be done like that (it can be more complicated):

 let fullname firstname lastname =
     firstname + " " + lastname

We need to retrieve both first name and last name and pass it to the function. But we can also make a map2:

 let map2 (f: 'a -> 'b -> 'c) (x: Query<'a>) (y: Query<'b>) : Query<'c> =
    { Properties = x.Properties + y.Properties
      Get = fun response ->
               let xvalue = x.Get response
               let yvalue = y.Get response
               f xvalue yvalue 
    }

The result is a Query that:

  • has the union of the properties of both queries
  • get values from both and pass them to the given function

With a map2, we can define apply and the opertor:

 let apply f x = map2 basicApply f x
 let (<*>) = apply

With this we can query a user:

 type User =
     { FirstName: string
       LastName: string
       Age: int
       FavoriteLanguage: string }
 let user firstname lastname age favoriteLang =
     { FirstName = firstname
       LastName = lastname
       Age = age
       FavoriteLanguage = favoriteLang } 
 
 let userQuery =
     user
     <!> firstname
     <*> lastname
     <*> age
     <*> favoriteLanguage

 callService userQuery

using userQuery, we just composed basic properties to form a query for a larger structure, so we know that we cannot use a property without requesting it in the query.

Other applicatives

Applicatives can be found it a lot of places. To zip lists, execute async computations in parallel.

It can also be used to create formlets. A Formlet<'t> is a UX form to fill a 't structure. The simples formlet are input fields. A text input is a Formlet<string>, a checkbox a Formlet<bool>. A label function is a (string -> Formlet<'a> -> Formlet<'a>) function that adds a label to an existing formlet. A map function can change Formlet<string> to Formlet<Address> given a (string -> Addess) function. And we use map2 and apply to take several formlets and compose them in a Formlet<User> for instance.

Once you start to see it, you'll spot them everywhere.

Be careful to not abuse it. For a single use, it's often better to compute the result directly.

But when you have many places that are impacted, especially if the code change often, it can reduce code complexity by a fair amount.

Don't hesitate to ping me on twitter if you find nice uses of applicatives!

As suggested by Chet Husk, I'll soon post about the new Applicatives support in Computation Expressions.

val basicApply: f: ('a -> 'b) -> x: 'a -> 'b
val f: ('a -> 'b)
val x: 'a
Multiple items
module Option from Microsoft.FSharp.Core
<summary>Contains operations for working with options.</summary>
<category>Options</category>


--------------------
type Option<'t> = | Some of 't | None
union case Option.Some: 't -> Option<'t>
union case Option.None: Option<'t>
val map: f: ('a -> 'b) -> x: Option<'a> -> Option<'b>
val x: Option<'a>
val value: 'a
module Option from Microsoft.FSharp.Core
<summary>Contains operations for working with options.</summary>
<category>Options</category>
val map2: f: ('a -> 'b -> 'c) -> x: Option<'a> -> y: Option<'b> -> Option<'c>
val f: ('a -> 'b -> 'c)
val y: Option<'b>
val xvalue: 'a
val yvalue: 'b
val add: x: int -> y: int -> int
val x: int
val y: int
val mappedAdd: (Option<int> -> Option<(int -> int)>)
Multiple items
val int: value: 'T -> int (requires member op_Explicit)
<summary>Converts the argument to signed 32-bit integer. This is a direct conversion for all primitive numeric types. For strings, the input is converted using <c>Int32.Parse()</c> with InvariantCulture settings. Otherwise the operation requires an appropriate static conversion method on the input type.</summary>
<param name="value">The input value.</param>
<returns>The converted int</returns>


--------------------
[<Struct>] type int = int32
<summary>An abbreviation for the CLI type <see cref="T:System.Int32" />.</summary>
<category>Basic Types</category>


--------------------
type int<'Measure> = int
<summary>The type of 32-bit signed integer numbers, annotated with a unit of measure. The unit of measure is erased in compiled code and when values of this type are analyzed using reflection. The type is representationally equivalent to <see cref="T:System.Int32" />.</summary>
<category>Basic Types with Units of Measure</category>
val apply: f: Option<('a -> 'b)> -> x: Option<'a> -> Option<'b>
val f: Option<('a -> 'b)>
val partialAdd: Option<(int -> int)>
type User = { FirstName: string LastName: string Age: int FavoriteLanguage: string }
User.FirstName: 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>


--------------------
type string = System.String
<summary>An abbreviation for the CLI type <see cref="T:System.String" />.</summary>
<category>Basic Types</category>
User.LastName: string
User.Age: int
User.FavoriteLanguage: string
val user: firstname: string -> lastname: string -> age: int -> favoriteLang: string -> User
val firstname: string
val lastname: string
val age: int
val favoriteLang: string
val user': Option<(string -> int -> string -> User)>
val user'': Option<(int -> string -> User)>
val user''': Option<(string -> User)>
val map2: f: ('a -> 'b -> 'c) -> xresult: Result<'a,string> -> yresult: Result<'b,string> -> Result<'c,string>
val xresult: Result<'a,string>
Multiple items
module Result from Microsoft.FSharp.Core
<summary>Contains operations for working with values of type <see cref="T:Microsoft.FSharp.Core.Result`2" />.</summary>
<category>Choices and Results</category>


--------------------
[<Struct>] type Result<'T,'TError> = | Ok of ResultValue: 'T | Error of ErrorValue: 'TError
<summary>Helper type for error handling without exceptions.</summary>
<category>Choices and Results</category>
val yresult: Result<'b,string>
union case Result.Ok: ResultValue: 'T -> Result<'T,'TError>
<summary> Represents an OK or a Successful result. The code succeeded with a value of 'T. </summary>
val y: 'b
union case Result.Error: ErrorValue: 'TError -> Result<'T,'TError>
<summary> Represents an Error or a Failure. The code failed with a value of 'TError representing what went wrong. </summary>
val fe: string
val xe: string
val apply: f: Result<('a -> 'b),string> -> x: Result<'a,string> -> Result<'b,string>
val f: Result<('a -> 'b),string>
val x: Result<'a,string>
val map: mapping: ('T -> 'U) -> result: Result<'T,'TError> -> Result<'U,'TError>
<summary><c>map f inp</c> evaluates to <c>match inp with Error e -&gt; Error e | Ok x -&gt; Ok (f x)</c>.</summary>
<param name="mapping">A function to apply to the OK result value.</param>
<param name="result">The input result.</param>
<returns>A result of the input value after applying the mapping function, or Error if the input is Error.</returns>
namespace System
val notEmpty: prop: string -> value: string -> Result<string,string>
val prop: string
val value: string
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.IsNullOrEmpty(value: string) : bool
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>
val tryParse: prop: string -> input: string -> Result<int,string>
val input: string
[<Struct>] type Int32 = member CompareTo: value: int -> int + 1 overload member Equals: obj: int -> bool + 1 overload member GetHashCode: unit -> int member GetTypeCode: unit -> TypeCode member ToString: unit -> string + 3 overloads member TryFormat: destination: Span<char> * charsWritten: byref<int> * ?format: ReadOnlySpan<char> * ?provider: IFormatProvider -> bool static member Parse: s: ReadOnlySpan<char> * ?style: NumberStyles * ?provider: IFormatProvider -> int + 4 overloads static member TryParse: s: ReadOnlySpan<char> * style: NumberStyles * provider: IFormatProvider * result: byref<int> -> bool + 3 overloads static val MaxValue: int static val MinValue: int
<summary>Represents a 32-bit signed integer.</summary>
Int32.TryParse(s: string, result: byref<int>) : bool
Int32.TryParse(s: ReadOnlySpan<char>, result: byref<int>) : bool
Int32.TryParse(s: string, style: Globalization.NumberStyles, provider: IFormatProvider, result: byref<int>) : bool
Int32.TryParse(s: ReadOnlySpan<char>, style: Globalization.NumberStyles, provider: IFormatProvider, result: byref<int>) : bool
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>


--------------------
type string = String
<summary>An abbreviation for the CLI type <see cref="T:System.String" />.</summary>
<category>Basic Types</category>
val value: int
Multiple items
union case Series.Series: 'a * (DateTime * 'a) list -> Series<'a>

--------------------
type Series<'a> = | Series of 'a * (DateTime * 'a) list
Multiple items
[<Struct>] type DateTime = new: year: int * month: int * day: int -> unit + 10 overloads member Add: value: TimeSpan -> DateTime member AddDays: value: float -> DateTime member AddHours: value: float -> DateTime member AddMilliseconds: value: float -> DateTime member AddMinutes: value: float -> DateTime member AddMonths: months: int -> DateTime member AddSeconds: value: float -> DateTime member AddTicks: value: int64 -> DateTime member AddYears: value: int -> DateTime ...
<summary>Represents an instant in time, typically expressed as a date and time of day.</summary>

--------------------
DateTime ()
   (+0 other overloads)
DateTime(ticks: int64) : DateTime
   (+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : DateTime
   (+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : DateTime
   (+0 other overloads)
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 always: x: 'a -> Series<'a>
val map: f: ('a -> 'b) -> Series<'a> -> Series<'b>
val initial: 'a
val changes: (DateTime * 'a) list
Multiple items
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 map: mapping: ('T -> 'U) -> list: 'T list -> 'U list
<summary>Builds a new collection whose elements are the results of applying the given function to each of the elements of the collection.</summary>
<param name="mapping">The function to transform elements from the input list.</param>
<param name="list">The input list.</param>
<returns>The list of transformed elements.</returns>
val d: DateTime
val v: 'a
val map2: f: ('a -> 'b -> 'c) -> Series<'a> -> Series<'b> -> Series<'c>
val ix: 'a
val cx: (DateTime * 'a) list
val iy: 'b
val cy: (DateTime * 'b) list
val zip: ('a -> 'b -> ('d * 'a) list -> ('d * 'b) list -> ('d * 'c) list) (requires comparison)
val lastx: 'a
val lasty: 'b
val changesx: ('d * 'a) list (requires comparison)
val changesy: ('d * 'b) list (requires comparison)
val d: 'd (requires comparison)
val dx: 'd (requires comparison)
val tailx: ('d * 'a) list (requires comparison)
val dy: 'd (requires comparison)
val taily: ('d * 'b) list (requires comparison)
val apply: f: Series<('a -> 'b)> -> x: Series<'a> -> Series<'b>
val f: Series<('a -> 'b)>
val x: Series<'a>
val birthDate: DateTime
DateTime.AddYears(value: int) : DateTime
val potentialSells: availability: int -> price: decimal -> closed: bool -> decimal
val availability: int
val price: decimal
val closed: bool
Multiple items
val decimal: value: 'T -> decimal (requires member op_Explicit)
<summary>Converts the argument to System.Decimal using a direct conversion for all primitive numeric types. For strings, the input is converted using <c>UInt64.Parse()</c> with InvariantCulture settings. Otherwise the operation requires an appropriate static conversion method on the input type.</summary>
<param name="value">The input value.</param>
<returns>The converted decimal.</returns>


--------------------
[<Struct>] type decimal = Decimal
<summary>An abbreviation for the CLI type <see cref="T:System.Decimal" />.</summary>
<category>Basic Types</category>


--------------------
type decimal<'Measure> = decimal
<summary>The type of decimal numbers, annotated with a unit of measure. The unit of measure is erased in compiled code and when values of this type are analyzed using reflection. The type is representationally equivalent to <see cref="T:System.Decimal" />.</summary>
<category>Basic Types with Units of Measure</category>
val availability: Series<int>
val price: Series<decimal>
val closed: Series<bool>
module Queries from 2020-10-03-applicatives-irl
val queryService: properties: Set<string> -> Map<string,string>
val properties: Set<string>
Multiple items
module Set from Microsoft.FSharp.Collections
<summary>Contains operations for working with values of type <see cref="T:Microsoft.FSharp.Collections.Set`1" />.</summary>

--------------------
type Set<'T (requires comparison)> = interface IReadOnlyCollection<'T> interface IComparable interface IEnumerable interface IEnumerable<'T> interface ICollection<'T> new: elements: seq<'T> -> Set<'T> member Add: value: 'T -> Set<'T> member Contains: value: 'T -> bool override Equals: obj -> bool member IsProperSubsetOf: otherSet: Set<'T> -> bool ...
<summary>Immutable sets based on binary trees, where elements are ordered by F# generic comparison. By default comparison is the F# structural comparison function or uses implementations of the IComparable interface on element values.</summary>
<remarks>See the <see cref="T:Microsoft.FSharp.Collections.SetModule" /> module for further operations on sets. All members of this class are thread-safe and may be used concurrently from multiple threads.</remarks>


--------------------
new: elements: seq<'T> -> Set<'T>
Multiple items
module Map from Microsoft.FSharp.Collections
<summary>Contains operations for working with values of type <see cref="T:Microsoft.FSharp.Collections.Map`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>
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>
val contains: element: 'T -> set: Set<'T> -> bool (requires comparison)
<summary>Evaluates to "true" if the given element is in the given set.</summary>
<param name="element">The element to test.</param>
<param name="set">The input set.</param>
<returns>True if <c>element</c> is in <c>set</c>.</returns>
type Query<'t> = { Properties: Set<string> Get: Map<string,string> -> 't }
Query.Properties: Set<string>
Query.Get: Map<string,string> -> 't
val prop: name: string -> Query<string>
val name: string
val set: elements: seq<'T> -> Set<'T> (requires comparison)
<summary>Builds a set from a sequence of objects. The objects are indexed using generic comparison.</summary>
<param name="elements">The input sequence of elements.</param>
<returns>The created set.</returns>
val response: Map<string,string>
val firstname: Query<string>
val lastname: Query<string>
val favoriteLanguage: Query<string>
val callService: query: Query<'t> -> 't
val query: Query<'t>
val map: f: ('a -> 'b) -> q: Query<'a> -> Query<'b>
val q: Query<'a>
Query.Get: Map<string,string> -> 'a
val age: Query<int>
val fullname: firstname: string -> lastname: string -> string
val map2: f: ('a -> 'b -> 'c) -> x: Query<'a> -> y: Query<'b> -> Query<'c>
val x: Query<'a>
val y: Query<'b>
Query.Get: Map<string,string> -> 'b
val apply: f: Query<('a -> 'b)> -> x: Query<'a> -> Query<'b>
val f: Query<('a -> 'b)>
val userQuery: Query<User>