FP - the scary parts¶
So you've been mapping, folding and reducing for a while and you're starting to dig this whole "functional programming" thing. Awesome! There's a whole new world to discover, from algebraic datatypes to the dreaded M-word, the 💀MONAD💀.
This post is an attempt to gently introduce you to this concept by building up from what you know. FP purists might want to close this document now, this is by no means a 100% complete or even accurate resource. This post is aimed at the beginner that wants to get an initial feel for all this functional stuff so that you can decide if and when to dive deeper yourself.
Prerequisites: types and functions¶
Before we build up toward the M-word we need to make sure we're up to snuff with some fundamental concepts: types, functions and function types. Feel free to skip over this section and dive right into Functors if you already have a solid grasp on types, record types, union types and function types.
Types¶
Types represent a collection of values. The string type represents the set of all valid strings. "Bob" is a string. So is "Alice". Even "", the empty string can join the party. Most languages have lots of built in primitive types for you to use, like strings, integers, booleans, etc.
But the fun does not end there, you can combine these basic types to make your own complex types.
Let's introduce some complex types ourselves:
type Age = Age of int
type Email = Email of string
type Person = { Age: Age; Email: Email }
Both Age and Email are simple wrappers for int and string. Person is a more interesting type, it combines an Age and an Email value to represent a Person.
Once the types are defined, you can create values for these types. Let's make a person!
let age : Age = Age 13
let email : Email = Email "jo@gmail.com"
let me : Person = {Age = age; Email = email }
me
{ Age = Age 13\n Email = Email "jo@gmail.com" }
Age |
|
Item | 13 |
Email "jo@gmail.com"
Item | jo@gmail.com |
We call types like Person record types. They combine other types into a new type.
Next to record types, lots of languages also provide support for union types. They look a lot like enums if enums took a gallon of steroids.
In this post we'll be using our version of the Result type.
type MyBasicResult = Ok | Error
MyBasicResult is a type, so it describes and represents a set of values. What are the values? There's only two. Ok and Error. Types like Result are used to represent that something is either a valid Ok result, or an Error case and nothing else.
let a : MyBasicResult = Ok
let b : MyBasicResult = Error
(a,b)
(Ok, Error)
Item1 |
|
Error
That's interesting. But that's basic stuff. That's enums. So where is the "on steroids" part? Well, each of these cases can include their own bag of data:
type MyResult = Ok of int | Error of string
let a = Ok 13
let b = Error "an error"
(a,b)
(Ok 13, Error "an error")
Item1 |
|
Item | 13 |
Error "an error"
Item | an error |
Note that each case can contain different types. In the Ok case we keep track of an integer, but for Errors we would like to keep track of a string containing the error message.
That's very cool. Let's take it one step further. We don't want to define a new Result type for every type it could contain. We don't have to with generic type parameters.
type Result<'a,'b> = Ok of 'a | Error of 'b
This one type declaration represents a whole lot of types. For every type you can dream up and plug in for 'a and for every type you plug in for 'b, there is now a Result<'a,'b> type!
let ok : Result<int,string> = Ok 13
let nok : Result<int,string> = Error "a problem!"
(ok, nok)
(Ok 13, Error "a problem!")
Item1 |
|
Item | 13 |
Error "a problem!"
Item | a problem! |
let ok : Result<string,int> = Ok "an okay string value"
let nok : Result<string,int> = Error 666
(ok,nok)
(Ok "an okay string value", Error 666)
Item1 |
|
Item | an okay string value |
Error 666
Item | 666 |
For reasons that will become clear much, much later on in this post we're going to go one step further and build another type on top of Result. Let's meet our very own Validation:
type Validation<'a> = Result<'a, string list>
let ok : Validation<int> = Ok 13
let nok : Validation<bool> = Error ["We have a problem!"]
(ok,nok)
(Ok 13, Error ["We have a problem!"])
Item1 |
|
Item | 13 |
Error ["We have a problem!"]
Item |
|
HeadOrDefault | We have a problem! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663764 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | T |
ReturnTypeCustomAttributes | T |
ReturnParameter | T |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663765 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnTypeCustomAttributes | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnParameter | Microsoft.FSharp.Collections.FSharpList`1[T] |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
We have a problem!
[ ]
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663764 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | T |
ReturnTypeCustomAttributes | T |
ReturnParameter | T |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663765 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnTypeCustomAttributes | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnParameter | Microsoft.FSharp.Collections.FSharpList`1[T] |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
[ We have a problem! ]
Validation is a generic type that already fills in what kind of data the Error case should contain. Validation also has a generic type parameter for the Ok case, but all Error cases will contain a list of strings. Why we do that will become clear in a bit.
That's most of the prerequisites out of the way. Last but not least, functions also have types!
let foo a = a + 1
foo is a function that takes an integer value a and returns a + 1. a + 1 is also an integer. We represent function types with arrows. foo maps ints to ints, so its type is:
int->int
You can also explicitly annotate your function types in F# code, that looks something like this:
let foo (a : int) : int = a + 1
(* |a is an int | it returns an int *)
Or using the arrow syntax for function types:
let foo : int -> int =
(* | takes an int
| returns an int
*)
fun a -> a + 1
Now, functions can also work on those other kinds of types like records and unions. Let's define a function that creates a Person record given an Age and an Email:
let mkPerson (a: Age) (e: Email) : Person = { Age = a; Email = e }
mkPerson (Age 99) (Email "me@old.com")
{ Age = Age 99\n Email = Email "me@old.com" }
Age |
|
Item | 99 |
Email "me@old.com"
Item | me@old.com |
Phew! that's all our prerequisites down. Let's build up towards the M-word now. We're going to do that in 3 steps, each building up on the previous. We'll start off with Functors, then introduce Applicative and have our grand finale with Monad.
Functors¶
We now know everything about types. In order to get a grasp on Functors, Applicatives and Monads, we need to talk about different kinds of worlds of types, normal worlds and elevated worlds.
Sidebar: elevated worlds¶
The normal world talks about types like int, bool, but also function types a->b, string->int.
The elevated world elevates everything you can dream up in the normal world to one and only one elevated counterpart. For example, we have int in the normal world, which has a counterpart E<int> in the elevated world. E<int> is a type in the elevated world. You can also elevate function types: a->b has its counterpart E<a->b>. There's tons of different E's out there. We've already encountered one today: Validation is one of those types that elevates normal types to the elevated world. Validation<int> elevates the int type to the elevated world of Validations.
So, back to functors. Functors are elevated types that support a map function. Map you probably know, lots of languages provide support for it. Let's take a look at map's implementation for our Validation type:
//map :: (a->b)->E(a)->E(b)
let map (f: 'a -> 'b) (a: Validation<'a>) : Validation<'b> =
match a with
| Ok v -> Ok(f v)
| Error e -> Error e
Whoa, what's happening here? map takes a function f from the normal world and a value a from the elevated Validation<a> world and returns a value from the elevated Validation<b> world.
Right about now would be a good time for a concrete example, what kind of functions and values can we feed into map?
Let's build a function that let's us have a birthday. It lives in the normal world: it takes a value of type Age and returns another value of type Age, so it has the signature Age->Age.
let haveBirthDay (Age a) : Age = Age (a + 1)
haveBirthDay (Age 35)
Age 36
Item | 36 |
Can we feed this function to map? We sure can!
let ok : Validation<Age> = Ok (Age 35)
map haveBirthDay ok
Ok (Age 36)
Item |
|
Item | 36 |
let nok : Validation<Age> = Error ["not a valid age"]
map haveBirthDay nok
Error ["not a valid age"]
Item |
|
HeadOrDefault | not a valid age | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head | ||||||||||||||||||||||||||||||
DeclaringType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
ReflectedType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
MemberType | Method | ||||||||||||||||||||||||||||||
MetadataToken | 100663764 | ||||||||||||||||||||||||||||||
Module |
|
MDStreamVersion | 131072 |
FullyQualifiedName | /home/jovyan/.dotnet/tools/.store/microsoft.dotnet-interactive/1.0.515601/microsoft.dotnet-interactive/1.0.515601/tools/net8.0/any/FSharp.Core.dll |
ModuleVersionId | 8b3b61bb-207b-2ce2-fc65-52fadafedf72 |
MetadataToken | 1 |
ScopeName | FSharp.Core.dll |
Name | FSharp.Core.dll |
Assembly | FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a |
ModuleHandle | System.ModuleHandle |
CustomAttributes | [ ] |
True
False
False
System.RuntimeMethodHandle
Value | 140367459419264 |
T
ParameterType | T |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | T get_Head() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
T
ParameterType | T |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | T get_Head() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
False
False
False
True
False
False
False
True
True
False
False
False
False
False
False
False
True
False
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail | ||||||||||||||||||||||||||||||
DeclaringType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
ReflectedType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
MemberType | Method | ||||||||||||||||||||||||||||||
MetadataToken | 100663765 | ||||||||||||||||||||||||||||||
Module |
|
MDStreamVersion | 131072 |
FullyQualifiedName | /home/jovyan/.dotnet/tools/.store/microsoft.dotnet-interactive/1.0.515601/microsoft.dotnet-interactive/1.0.515601/tools/net8.0/any/FSharp.Core.dll |
ModuleVersionId | 8b3b61bb-207b-2ce2-fc65-52fadafedf72 |
MetadataToken | 1 |
ScopeName | FSharp.Core.dll |
Name | FSharp.Core.dll |
Assembly | FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a |
ModuleHandle | System.ModuleHandle |
CustomAttributes | [ ] |
True
False
False
System.RuntimeMethodHandle
Value | 140367459419280 |
Microsoft.FSharp.Collections.FSharpList`1[T]
ParameterType | Microsoft.FSharp.Collections.FSharpList`1[T] |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
Microsoft.FSharp.Collections.FSharpList`1[T]
ParameterType | Microsoft.FSharp.Collections.FSharpList`1[T] |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
False
False
False
True
False
False
False
True
True
False
False
False
False
False
False
False
True
False
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
not a valid age
[ ]
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head | ||||||||||||||||||||||||||||||
DeclaringType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
ReflectedType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
MemberType | Method | ||||||||||||||||||||||||||||||
MetadataToken | 100663764 | ||||||||||||||||||||||||||||||
Module |
|
MDStreamVersion | 131072 |
FullyQualifiedName | /home/jovyan/.dotnet/tools/.store/microsoft.dotnet-interactive/1.0.515601/microsoft.dotnet-interactive/1.0.515601/tools/net8.0/any/FSharp.Core.dll |
ModuleVersionId | 8b3b61bb-207b-2ce2-fc65-52fadafedf72 |
MetadataToken | 1 |
ScopeName | FSharp.Core.dll |
Name | FSharp.Core.dll |
Assembly | FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a |
ModuleHandle | System.ModuleHandle |
CustomAttributes | [ ] |
True
False
False
System.RuntimeMethodHandle
Value | 140367459419264 |
T
ParameterType | T |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | T get_Head() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
T
ParameterType | T |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | T get_Head() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
False
False
False
True
False
False
False
True
True
False
False
False
False
False
False
False
True
False
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail | ||||||||||||||||||||||||||||||
DeclaringType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
ReflectedType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
MemberType | Method | ||||||||||||||||||||||||||||||
MetadataToken | 100663765 | ||||||||||||||||||||||||||||||
Module |
|
MDStreamVersion | 131072 |
FullyQualifiedName | /home/jovyan/.dotnet/tools/.store/microsoft.dotnet-interactive/1.0.515601/microsoft.dotnet-interactive/1.0.515601/tools/net8.0/any/FSharp.Core.dll |
ModuleVersionId | 8b3b61bb-207b-2ce2-fc65-52fadafedf72 |
MetadataToken | 1 |
ScopeName | FSharp.Core.dll |
Name | FSharp.Core.dll |
Assembly | FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a |
ModuleHandle | System.ModuleHandle |
CustomAttributes | [ ] |
True
False
False
System.RuntimeMethodHandle
Value | 140367459419280 |
Microsoft.FSharp.Collections.FSharpList`1[T]
ParameterType | Microsoft.FSharp.Collections.FSharpList`1[T] |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
Microsoft.FSharp.Collections.FSharpList`1[T]
ParameterType | Microsoft.FSharp.Collections.FSharpList`1[T] |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
False
False
False
True
False
False
False
True
True
False
False
False
False
False
False
False
True
False
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
[ not a valid age ]
What's happening here? map does the heavy lifting. If a is an Error, nothing happens. But if a is an Ok case, it unwraps the elevated value, applies the function f that lives in the normal world to it's innards and returns the output as an elevated value again.
This is abstraction is pretty useful because stuff that behaves like this is very easy to compose:
Ok (Age 23)
|> map haveBirthDay
|> map haveBirthDay
|> map haveBirthDay
Ok (Age 26)
Item |
|
Item | 26 |
nok
|> map haveBirthDay
|> map haveBirthDay
|> map haveBirthDay
Error ["not a valid age"]
Item |
|
HeadOrDefault | not a valid age | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head | ||||||||||||||||||||||||||||||
DeclaringType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
ReflectedType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
MemberType | Method | ||||||||||||||||||||||||||||||
MetadataToken | 100663764 | ||||||||||||||||||||||||||||||
Module |
|
MDStreamVersion | 131072 |
FullyQualifiedName | /home/jovyan/.dotnet/tools/.store/microsoft.dotnet-interactive/1.0.515601/microsoft.dotnet-interactive/1.0.515601/tools/net8.0/any/FSharp.Core.dll |
ModuleVersionId | 8b3b61bb-207b-2ce2-fc65-52fadafedf72 |
MetadataToken | 1 |
ScopeName | FSharp.Core.dll |
Name | FSharp.Core.dll |
Assembly | FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a |
ModuleHandle | System.ModuleHandle |
CustomAttributes | [ ] |
True
False
False
System.RuntimeMethodHandle
Value | 140367459419264 |
T
ParameterType | T |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | T get_Head() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
T
ParameterType | T |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | T get_Head() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
False
False
False
True
False
False
False
True
True
False
False
False
False
False
False
False
True
False
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail | ||||||||||||||||||||||||||||||
DeclaringType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
ReflectedType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
MemberType | Method | ||||||||||||||||||||||||||||||
MetadataToken | 100663765 | ||||||||||||||||||||||||||||||
Module |
|
MDStreamVersion | 131072 |
FullyQualifiedName | /home/jovyan/.dotnet/tools/.store/microsoft.dotnet-interactive/1.0.515601/microsoft.dotnet-interactive/1.0.515601/tools/net8.0/any/FSharp.Core.dll |
ModuleVersionId | 8b3b61bb-207b-2ce2-fc65-52fadafedf72 |
MetadataToken | 1 |
ScopeName | FSharp.Core.dll |
Name | FSharp.Core.dll |
Assembly | FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a |
ModuleHandle | System.ModuleHandle |
CustomAttributes | [ ] |
True
False
False
System.RuntimeMethodHandle
Value | 140367459419280 |
Microsoft.FSharp.Collections.FSharpList`1[T]
ParameterType | Microsoft.FSharp.Collections.FSharpList`1[T] |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
Microsoft.FSharp.Collections.FSharpList`1[T]
ParameterType | Microsoft.FSharp.Collections.FSharpList`1[T] |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
False
False
False
True
False
False
False
True
True
False
False
False
False
False
False
False
True
False
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
not a valid age
[ ]
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head | ||||||||||||||||||||||||||||||
DeclaringType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
ReflectedType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
MemberType | Method | ||||||||||||||||||||||||||||||
MetadataToken | 100663764 | ||||||||||||||||||||||||||||||
Module |
|
MDStreamVersion | 131072 |
FullyQualifiedName | /home/jovyan/.dotnet/tools/.store/microsoft.dotnet-interactive/1.0.515601/microsoft.dotnet-interactive/1.0.515601/tools/net8.0/any/FSharp.Core.dll |
ModuleVersionId | 8b3b61bb-207b-2ce2-fc65-52fadafedf72 |
MetadataToken | 1 |
ScopeName | FSharp.Core.dll |
Name | FSharp.Core.dll |
Assembly | FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a |
ModuleHandle | System.ModuleHandle |
CustomAttributes | [ ] |
True
False
False
System.RuntimeMethodHandle
Value | 140367459419264 |
T
ParameterType | T |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | T get_Head() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
T
ParameterType | T |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | T get_Head() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
False
False
False
True
False
False
False
True
True
False
False
False
False
False
False
False
True
False
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail | ||||||||||||||||||||||||||||||
DeclaringType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
ReflectedType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
MemberType | Method | ||||||||||||||||||||||||||||||
MetadataToken | 100663765 | ||||||||||||||||||||||||||||||
Module |
|
MDStreamVersion | 131072 |
FullyQualifiedName | /home/jovyan/.dotnet/tools/.store/microsoft.dotnet-interactive/1.0.515601/microsoft.dotnet-interactive/1.0.515601/tools/net8.0/any/FSharp.Core.dll |
ModuleVersionId | 8b3b61bb-207b-2ce2-fc65-52fadafedf72 |
MetadataToken | 1 |
ScopeName | FSharp.Core.dll |
Name | FSharp.Core.dll |
Assembly | FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a |
ModuleHandle | System.ModuleHandle |
CustomAttributes | [ ] |
True
False
False
System.RuntimeMethodHandle
Value | 140367459419280 |
Microsoft.FSharp.Collections.FSharpList`1[T]
ParameterType | Microsoft.FSharp.Collections.FSharpList`1[T] |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
Microsoft.FSharp.Collections.FSharpList`1[T]
ParameterType | Microsoft.FSharp.Collections.FSharpList`1[T] |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
False
False
False
True
False
False
False
True
True
False
False
False
False
False
False
False
True
False
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
[ not a valid age ]
If somewhere in this pipeline we end up with an Error, all the following steps get short-circuited and we end up with that Error. If it's Ok's all the way down, you end up with an Ok value in the end.
All elevated types (like our Validation type) where you can come up with a map function like this are called Functors. Technically your elevated type and your map implementation need to follow some rules, but that would take us way too far today, so let's just ignore that tiny detail (sorry fp purists).
Now let's take our elevated types a step further and take a look at Applicative Functors!
Applicative Functor¶
Remember map's type?
//map::(a->b)->E(a)->E(b)
In order to talk about Applicative Functors (we'll just call them Applicatives from now on), we need to introduce another function called apply that looks almost exactly like map:
//apply::E(a->b)->E(a)->E(b)
See? Looks awfully similar. The only difference with map is that the input function is also elevated. Let's take a look at a possible implementation of apply for our Validation:
let apply (f: Validation<'a -> 'b>) (a: Validation<'a>) : Validation<'b> =
match f, a with
| Ok f, Ok v -> Ok(f v)
| Error e1, Error e2 -> Error(e1 @ e2)
| Error e, _ -> Error e
| _, Error e -> Error e
What's happening here? If both the elevated function f and value a contain an Ok, we apply the function to the value and wrap it in another Ok. In all other cases we propagate the Error. If we have hold of two errors, we just smoosh the two together. See? That's why our Validation type takes string lists for the Error case, two lists are very easy to smoosh together by using the concat operator @.
Now, how would you use apply and what benefits does it bring? In order for that, let's define a couple more functions.
let parseAge (text: string) : Validation<Age> =
match System.Int32.TryParse text with
| false, _ -> Error [ sprintf "<%s> is not a valid age" text ]
| true, parsed -> Ok(Age parsed)
let parseEmail (text: string) : Validation<Email> =
if text.Contains("@") then
Ok(Email text)
else
Error [ sprintf "<%s> is not a valid email" text ]
let okAge = parseAge "12"
let nokAge = parseAge "jo@gmail.com"
let okEmail = parseEmail "jo@gmail.com"
let nokEmail = parseEmail "13"
(okAge,nokAge,okEmail, nokEmail)
(Ok (Age 12), Error ["<jo@gmail.com> is not a valid age"], Ok (Email "jo@gmail.com"), Error ["<13> is not a valid email"])
Item1 |
|
Item |
|
Item | 12 |
Error ["<jo@gmail.com> is not a valid age"]
Item |
|
HeadOrDefault | <jo@gmail.com> is not a valid age | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663764 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | T |
ReturnTypeCustomAttributes | T |
ReturnParameter | T |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663765 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnTypeCustomAttributes | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnParameter | Microsoft.FSharp.Collections.FSharpList`1[T] |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
<jo@gmail.com> is not a valid age
[ ]
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663764 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | T |
ReturnTypeCustomAttributes | T |
ReturnParameter | T |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663765 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnTypeCustomAttributes | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnParameter | Microsoft.FSharp.Collections.FSharpList`1[T] |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
[ <jo@gmail.com> is not a valid age ]
Ok (Email "jo@gmail.com")
Item |
|
Item | jo@gmail.com |
Error ["<13> is not a valid email"]
Item |
|
HeadOrDefault | <13> is not a valid email | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663764 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | T |
ReturnTypeCustomAttributes | T |
ReturnParameter | T |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663765 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnTypeCustomAttributes | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnParameter | Microsoft.FSharp.Collections.FSharpList`1[T] |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
<13> is not a valid email
[ ]
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663764 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | T |
ReturnTypeCustomAttributes | T |
ReturnParameter | T |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663765 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnTypeCustomAttributes | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnParameter | Microsoft.FSharp.Collections.FSharpList`1[T] |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
[ <13> is not a valid email ]
Notice anything strange about these parsing functions? Why, they're not quite normal functions. Not are they elevated functions. Rather, they are world-crossing functions: they take a type from the normal world as input but their return type live in the elevated world. Once you know these world-crossing functions exist, you'll start to notice them everywhere! Let's not dive too much into detail right now, we'll revisit these world-crossing functions in the next chapter.
Okay, back to Applicative. What would it look like to use apply in this case?
let f = Ok mkPerson
let age = parseAge "13"
let email = parseEmail "jo@gmail.com"
apply (apply f age) email
Ok { Age = Age 13\n Email = Email "jo@gmail.com" }
Item |
|
Age |
|
Item | 13 |
Email "jo@gmail.com"
Item | jo@gmail.com |
Hold on a minute, stuff just got confusing. We had to elevate mkPerson to a Validation by wrapping it in Ok, but stuff got even weirder! How can we feed the mkPerson function to our apply, as mkPerson expects 2 input arguments: age and email and our map type E(a->b) only talks about a function with one input type and one output?!
That my dear friend, is another sidebar. We can do this thanks to the magic of "Currying" and "partial function application".
Sidebar: currying and partial function application¶
//A function with 2 input arguments
//add::int->int->int
let add one other = one + other
add 2 3
5
You can transform any function that takes n arguments into a function that takes n - 1 arguments by "prefilling" one of the arguments:
//addThree::int->int
let addThree = add 3
addThree 2
5
Pretty neat huh? Thanks to this black magic, we can feed our two-argument mkPerson function to our apply and everything works out nicely.
Back to Applicative¶
But what is the practical use of all this apply wizardry? Let's define some infix operators for a nicer syntax.
let (<*>) = apply
(Ok mkPerson) <*> (parseAge "13") <*> (parseEmail "jo@gmail.com")
Ok { Age = Age 13\n Email = Email "jo@gmail.com" }
Item |
|
Age |
|
Item | 13 |
Email "jo@gmail.com"
Item | jo@gmail.com |
What happens when some of those parse results return an Error instead of an Ok? Aha! Now this is where things get interesting:
(Ok mkPerson) <*> (parseAge "invalid age") <*> (parseEmail "invalid email")
Error\n ["<invalid age> is not a valid age"; "<invalid email> is not a valid email"]
Item |
|
HeadOrDefault | <invalid age> is not a valid age | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | <invalid email> is not a valid email | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663764 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | T |
ReturnTypeCustomAttributes | T |
ReturnParameter | T |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663765 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnTypeCustomAttributes | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnParameter | Microsoft.FSharp.Collections.FSharpList`1[T] |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
<invalid email> is not a valid email
[ ]
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663764 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | T |
ReturnTypeCustomAttributes | T |
ReturnParameter | T |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663765 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnTypeCustomAttributes | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnParameter | Microsoft.FSharp.Collections.FSharpList`1[T] |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
[ <invalid email> is not a valid email ]
<invalid age> is not a valid age
[ <invalid email> is not a valid email ]
HeadOrDefault | <invalid email> is not a valid email | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663764 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | T |
ReturnTypeCustomAttributes | T |
ReturnParameter | T |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663765 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnTypeCustomAttributes | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnParameter | Microsoft.FSharp.Collections.FSharpList`1[T] |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
<invalid email> is not a valid email
[ ]
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663764 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | T |
ReturnTypeCustomAttributes | T |
ReturnParameter | T |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail |
DeclaringType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReflectedType | Microsoft.FSharp.Collections.FSharpList`1[T] |
MemberType | Method |
MetadataToken | 100663765 |
Module | FSharp.Core.dll |
IsSecurityCritical | True |
IsSecuritySafeCritical | False |
IsSecurityTransparent | False |
MethodHandle | System.RuntimeMethodHandle |
Attributes | Public, HideBySig, SpecialName |
CallingConvention | Standard, HasThis |
ReturnType | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnTypeCustomAttributes | Microsoft.FSharp.Collections.FSharpList`1[T] |
ReturnParameter | Microsoft.FSharp.Collections.FSharpList`1[T] |
IsCollectible | False |
IsGenericMethod | False |
IsGenericMethodDefinition | False |
ContainsGenericParameters | True |
MethodImplementationFlags | IL |
IsAbstract | False |
IsConstructor | False |
IsFinal | False |
IsHideBySig | True |
IsSpecialName | True |
IsStatic | False |
IsVirtual | False |
IsAssembly | False |
IsFamily | False |
IsFamilyAndAssembly | False |
IsFamilyOrAssembly | False |
IsPrivate | False |
IsPublic | True |
IsConstructedGenericMethod | False |
CustomAttributes | [ ] |
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
[ <invalid email> is not a valid email ]
[ <invalid age> is not a valid age, <invalid email> is not a valid email ]
What's cool about this is that if you squint this looks like good old plain function application with an extra sprinkle of input validation:
//Plain old function application
mkPerson (Age 36) (Email "jo@gmail.com")
{ Age = Age 36\n Email = Email "jo@gmail.com" }
Age |
|
Item | 36 |
Email "jo@gmail.com"
Item | jo@gmail.com |
//With baked-in validation, thanks to our apply
(Ok mkPerson) <*> (Ok (Age 36)) <*> (Ok (Email "jo@gmail.com"))
Ok { Age = Age 36\n Email = Email "jo@gmail.com" }
Item |
|
Age |
|
Item | 36 |
Email "jo@gmail.com"
Item | jo@gmail.com |
Very cool, we now have a way of parsing all our inputs and building a Person record that looks scarily similar to the same function call without all the parsing logic. In the case of any parse Errors, we even return a list of ALL the parsing errors that occurred instead of stopping at first one. Now that's friendly UX!
Monad¶
Okay, we now have enough bagage to deal with the big M. In order to introduce it, let's get creative. Remember map's signature?
//map::(a->b)->E(a)->E(b)
Remember that a and b are generic type parameters,we can fill these in with any concrete type. Let's do something 🌈totally crazy🌈 and fill in E(b) for b:
//map::(a->E(b))->E(a)->E(E(b))
Two things of note here.
First: notice that a->E(b) ? That's exactly the type of those ubiquituous world-crossing functions we talked about earlier!
Second: the return type. E(E(b)). Eew. Anyone that has worked with elevated types before can relate: it's not fun to work with nested elevated types.
Thankfully, we have a solution for that nesting, we can flatten nested elevated types! An example we all know is flattening a list of lists into a single list:
let nested = [[1];[2;3];[4]]
let flatten = List.concat
flatten nested
[ 1, 2, 3, 4 ]
HeadOrDefault | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 4 | ||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
4
[ ]
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
[ 4 ]
3
[ 4 ]
HeadOrDefault | 4 | ||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
4
[ ]
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
[ 4 ]
[ 3, 4 ]
2
[ 3, 4 ]
HeadOrDefault | 3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 4 | ||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
4
[ ]
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
[ 4 ]
3
[ 4 ]
HeadOrDefault | 4 | ||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
4
[ ]
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
[ 4 ]
[ 3, 4 ]
[ 2, 3, 4 ]
1
[ 2, 3, 4 ]
HeadOrDefault | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 4 | ||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
4
[ ]
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
[ 4 ]
3
[ 4 ]
HeadOrDefault | 4 | ||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
4
[ ]
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
[ 4 ]
[ 3, 4 ]
2
[ 3, 4 ]
HeadOrDefault | 3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 4 | ||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
4
[ ]
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
[ 4 ]
3
[ 4 ]
HeadOrDefault | 4 | ||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
4
[ ]
HeadOrDefault | 0 | ||||||||||||||||
TailOrNull | <null> | ||||||||||||||||
Head |
|
TargetSite | T get_Head() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method563(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method564(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Message | The input list was empty. |
Data | [ ] |
InnerException | <null> |
HelpLink | <null> |
Source | FSharp.Core |
HResult | -2146233079 |
StackTrace | at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method564(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58 |
[ ]
[ 4 ]
[ 3, 4 ]
[ 2, 3, 4 ]
[ 1, 2, 3, 4 ]
Now we're totally ready to introduce monad. A monad is an elevated type with a corresponding bind function. And that bind function? That's nothing more than our trusty Functor map with a built-in flatten!
//map:: (a->b) ->E(a)->E(b)
//map:: (a->E(b))->E(a)->E(E(b))
//bind::(a->E(b))->E(a)->E(b)
Let's take a look at how we could implement bind for our Validation elevated type.
//bind::(a->E(b))->E(a)->E(b)
let bind (f: ('a -> Validation<'b>)) (a: Validation<'a>) : Validation<'b> =
match a with
| Ok v -> f v
| Error e -> Error e
Now, let's see bind in action
bind (fun age -> bind (fun email -> Ok(mkPerson age email)) (parseEmail "jo@gmail.com")) (parseAge "13")
Ok { Age = Age 13\n Email = Email "jo@gmail.com" }
Item |
|
Age |
|
Item | 13 |
Email "jo@gmail.com"
Item | jo@gmail.com |
Now, that technically works. But oh boy is that painful on the eyes 😅 We can make things a bit more readable by using the pipe operator |>
parseAge "13"
|> bind (fun age ->
parseEmail "jo@gmail.com"
|> bind (fun email ->
Ok(mkPerson age email)))
Ok { Age = Age 13\n Email = Email "jo@gmail.com" }
Item |
|
Age |
|
Item | 13 |
Email "jo@gmail.com"
Item | jo@gmail.com |
Better, but still not great. Let's introduce a tiny bit of F# black magic that's way beyond the scope of this post. If you want to know more try searching for "Computation Expressions.
//For F# "do syntax", we need some magic dust
type ValidatedResultBuilder() =
member _.Return(x) = Ok x
member _.Bind(m, f) = bind f m
let valid = new ValidatedResultBuilder()
This black magic allows us to write our bind function calls using the following syntax:
valid {
let! age = parseAge "13"
let! email = parseEmail "jo@gmail.com"
return mkPerson age email
}
Ok { Age = Age 13\n Email = Email "jo@gmail.com" }
Item |
|
Age |
|
Item | 13 |
Email "jo@gmail.com"
Item | jo@gmail.com |
Wow, that almost reads like procedural code, doesn't it? But don't be fooled, all those binds are still happening. For example, take a look at what happens when we parse an invalid age:
valid {
let! age = parseAge "invalid"
let! email = parseEmail "jo@gmail.com"
return mkPerson age email
}
Error ["<invalid> is not a valid age"]
Item |
|
HeadOrDefault | <invalid> is not a valid age | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull |
|
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head | ||||||||||||||||||||||||||||||
DeclaringType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
ReflectedType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
MemberType | Method | ||||||||||||||||||||||||||||||
MetadataToken | 100663764 | ||||||||||||||||||||||||||||||
Module |
|
MDStreamVersion | 131072 |
FullyQualifiedName | /home/jovyan/.dotnet/tools/.store/microsoft.dotnet-interactive/1.0.515601/microsoft.dotnet-interactive/1.0.515601/tools/net8.0/any/FSharp.Core.dll |
ModuleVersionId | 8b3b61bb-207b-2ce2-fc65-52fadafedf72 |
MetadataToken | 1 |
ScopeName | FSharp.Core.dll |
Name | FSharp.Core.dll |
Assembly | FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a |
ModuleHandle | System.ModuleHandle |
CustomAttributes | [ ] |
True
False
False
System.RuntimeMethodHandle
Value | 140367459419264 |
T
ParameterType | T |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | T get_Head() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
T
ParameterType | T |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | T get_Head() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
False
False
False
True
False
False
False
True
True
False
False
False
False
False
False
False
True
False
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail | ||||||||||||||||||||||||||||||
DeclaringType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
ReflectedType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
MemberType | Method | ||||||||||||||||||||||||||||||
MetadataToken | 100663765 | ||||||||||||||||||||||||||||||
Module |
|
MDStreamVersion | 131072 |
FullyQualifiedName | /home/jovyan/.dotnet/tools/.store/microsoft.dotnet-interactive/1.0.515601/microsoft.dotnet-interactive/1.0.515601/tools/net8.0/any/FSharp.Core.dll |
ModuleVersionId | 8b3b61bb-207b-2ce2-fc65-52fadafedf72 |
MetadataToken | 1 |
ScopeName | FSharp.Core.dll |
Name | FSharp.Core.dll |
Assembly | FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a |
ModuleHandle | System.ModuleHandle |
CustomAttributes | [ ] |
True
False
False
System.RuntimeMethodHandle
Value | 140367459419280 |
Microsoft.FSharp.Collections.FSharpList`1[T]
ParameterType | Microsoft.FSharp.Collections.FSharpList`1[T] |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
Microsoft.FSharp.Collections.FSharpList`1[T]
ParameterType | Microsoft.FSharp.Collections.FSharpList`1[T] |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
False
False
False
True
False
False
False
True
True
False
False
False
False
False
False
False
True
False
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
<invalid> is not a valid age
[ ]
HeadOrDefault | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TailOrNull | <null> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Head |
|
TargetSite |
|
Name | get_Head | ||||||||||||||||||||||||||||||
DeclaringType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
ReflectedType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
MemberType | Method | ||||||||||||||||||||||||||||||
MetadataToken | 100663764 | ||||||||||||||||||||||||||||||
Module |
|
MDStreamVersion | 131072 |
FullyQualifiedName | /home/jovyan/.dotnet/tools/.store/microsoft.dotnet-interactive/1.0.515601/microsoft.dotnet-interactive/1.0.515601/tools/net8.0/any/FSharp.Core.dll |
ModuleVersionId | 8b3b61bb-207b-2ce2-fc65-52fadafedf72 |
MetadataToken | 1 |
ScopeName | FSharp.Core.dll |
Name | FSharp.Core.dll |
Assembly | FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a |
ModuleHandle | System.ModuleHandle |
CustomAttributes | [ ] |
True
False
False
System.RuntimeMethodHandle
Value | 140367459419264 |
T
ParameterType | T |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | T get_Head() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
T
ParameterType | T |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | T get_Head() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
False
False
False
True
False
False
False
True
True
False
False
False
False
False
False
False
True
False
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Head() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4141 at lambda_method316(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
System.InvalidOperationException: The input list was empty.\n at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146\n at lambda_method317(Closure, FSharpList`1)\n at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrE...
TargetSite |
|
Name | get_Tail | ||||||||||||||||||||||||||||||
DeclaringType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
ReflectedType | Microsoft.FSharp.Collections.FSharpList<T> | ||||||||||||||||||||||||||||||
MemberType | Method | ||||||||||||||||||||||||||||||
MetadataToken | 100663765 | ||||||||||||||||||||||||||||||
Module |
|
MDStreamVersion | 131072 |
FullyQualifiedName | /home/jovyan/.dotnet/tools/.store/microsoft.dotnet-interactive/1.0.515601/microsoft.dotnet-interactive/1.0.515601/tools/net8.0/any/FSharp.Core.dll |
ModuleVersionId | 8b3b61bb-207b-2ce2-fc65-52fadafedf72 |
MetadataToken | 1 |
ScopeName | FSharp.Core.dll |
Name | FSharp.Core.dll |
Assembly | FSharp.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a |
ModuleHandle | System.ModuleHandle |
CustomAttributes | [ ] |
True
False
False
System.RuntimeMethodHandle
Value | 140367459419280 |
Microsoft.FSharp.Collections.FSharpList`1[T]
ParameterType | Microsoft.FSharp.Collections.FSharpList`1[T] |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
Microsoft.FSharp.Collections.FSharpList`1[T]
ParameterType | Microsoft.FSharp.Collections.FSharpList`1[T] |
Name | <null> |
HasDefaultValue | False |
DefaultValue | |
RawDefaultValue | |
MetadataToken | 134217728 |
Attributes | None |
Member | Microsoft.FSharp.Collections.FSharpList`1[T] get_Tail() |
Position | -1 |
IsIn | False |
IsLcid | False |
IsOptional | False |
IsOut | False |
IsRetval | False |
CustomAttributes | [ ] |
False
False
False
True
False
False
False
True
True
False
False
False
False
False
False
False
True
False
The input list was empty.
<null>
<null>
FSharp.Core
-2146233079
at Microsoft.FSharp.Collections.FSharpList`1.get_Tail() in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4146 at lambda_method317(Closure, FSharpList`1) at Microsoft.DotNet.Interactive.Formatting.MemberAccessor`1.GetValueOrException(T instance) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\MemberAccessor{T}.cs:line 58
[ ]
[ <invalid> is not a valid age ]
So cool! We have something that looks very similar to procedural code, but in between the lines we're handling all those potential Validations for free. That's why some people jokingely call monads programmable semicolons.
A small detour: applicative vs. monadic style¶
So, if Monads are strictly more powerful than Applicative Functors, why would we ever want to grab for Applicative's apply again? Can't we just monadic bind all the things? There's a subtle difference worth mentioning.
bind implies a sequential order, whereas apply doesn't impose any sequential constraints. bind is super useful if what you do in step 2 depends on the results of step 1. If step 1 and 2 are fully independent (and therefore if you want to run those steps in parallel for example), using apply might be the better option. apply doesn't impose any sequential constraints, bind very much does.
Conclusion¶
Well, congratulations. You have achieved fp enlightenment! You just dipped your toes into what can objectively be called the scariest part of functional programming.
We talked about normal and elevated worlds and world-crossing functions.
We took a look at what a functor is: an elevated type with a corresponding map function. map allows us to apply functions in the normal world while keeping our inputs and outputs in the elevated world.
Next, we took a look at applicative functors. These are elevated types with a corresponding apply function. They allow us to lift our functions to the elevated world as well. Which provides us with a very natural way to combine values from the elevated world while keeping the actual code we write very similar to the same code in the normal world. We used parsing with potential parser errors as a concrete example.
Finally, we introduced the scariest concept of them all, the monad. A monad is an elevated type with a corresponding bind function. And that bind is nothing more than a map with built-in flattening so we don't have to work with nested elevated types ourselves. This allows us to compose world-crossing functions very naturally.
This post hopefully did its job in making all these scary terms a bit less so. As I said, we only scratched the surface here and made some wild generalisations the fp police would have my head for. If you're ready, I invite you to dig deeper in the Further reading section.
Good luck out there!