val createReservation : reservation:'a -> 'b
Full name: index.createReservation
val reservation : 'a
val createReservation : (obj -> obj)
Full name: index.createReservation
val validate : ('a -> 'b)
Full name: index.validate
val f : ('a -> 'b)
Full name: index.f
val g : ('b -> 'c)
Full name: index.g
val h : (obj -> obj)
Full name: index.h
type 'T option = Option<'T>
Full name: Microsoft.FSharp.Core.option<_>
val persist : ('a -> 'b)
Full name: index.persist
val persist' : ('a -> 'b)
Full name: index.persist'
val persist' : (obj option -> obj option)
Full name: index.persist'
module Option
from Microsoft.FSharp.Core
val bind : binder:('T -> 'U option) -> option:'T option -> 'U option
Full name: Microsoft.FSharp.Core.Option.bind
val bind : binder:('a -> 'b option) -> option:'a option -> 'b option
Full name: index.bind
val binder : ('a -> 'b option)
Multiple items
val option : 'a option
--------------------
type 'T option = Option<'T>
Full name: Microsoft.FSharp.Core.option<_>
union case Option.Some: Value: 'T -> Option<'T>
val value : 'a
union case Option.None: Option<'T>
val sendNotification' : (obj option -> obj option)
Full name: index.sendNotification'
val map : mapping:('T -> 'U) -> option:'T option -> 'U option
Full name: Microsoft.FSharp.Core.Option.map
val map : mapping:('a -> 'b) -> option:'a option -> 'b option
Full name: index.map
val mapping : ('a -> 'b)
Railway Oriented Programming
Functional approach to error handling
Happy path
Surely this is all that can happen...
Imperative example
1:
2:
3:
4:
5:
6:
|
public IHttpActionResult CreateReservation(ReservationDTO reservation) {
Validate(reservation);
PersistAndUpdate(reservation);
SendNotification(reservation);
return Json(reservation);
}
|
Functional example
1:
2:
3:
4:
5:
|
let createReservation reservation =
validate reservation
|> persistAndUpdate
|> sendNotification
|> Json
|
Let's aim for point-free
1:
2:
3:
4:
5:
|
let createReservation =
validate
>> persistAndUpdate
>> sendNotification
>> Json
|
But we can't have nice things
- Validations fail
- DB connections drop
- SMTP servers get overloaded
Imperative
1:
2:
3:
4:
5:
6:
|
public IHttpActionResult CreateReservation(ReservationDTO reservation) {
Validate(reservation);
PersistAndUpdate(reservation);
SendNotification(reservation);
return Json(reservation);
}
|
Imperative
1:
2:
3:
4:
5:
6:
7:
8:
9:
|
public IHttpActionResult CreateReservation(ReservationDTO reservation) {
var validated = Validate(reservation);
if (!validated) {
return BadRequest("Reservation invalid!");
}
PersistAndUpdate(reservation);
SendNotification(reservation);
return Json(reservation);
}
|
Imperative
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
|
public IHttpActionResult CreateReservation(ReservationDTO reservation) {
var validated = Validate(reservation);
if (!validated) {
return BadRequest("Reservation invalid!");
}
var updatedReservation = PersistAndUpdate(reservation);
if (updatedReservation == null) {
return BadRequest("Unable to persist reservation!");
}
SendNotification(updatedReservation);
return Json(reservation);
}
|
Imperative
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
|
public IHttpActionResult CreateReservation(ReservationDTO reservation) {
var validated = Validate(reservation);
if (!validated) {
return BadRequest("Reservation invalid!");
}
try {
var updatedReservation = PersistAndUpdate(reservation);
if (updatedReservation == null) {
return BadRequest("Unable to update reservation!");
}
} catch {
return InternalServerError("DB error: unable to persist reservation!");
}
SendNotification(updatedReservation);
return Json(updatedReservation);
}
|
Imperative
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
|
public IHttpActionResult CreateReservation(ReservationDTO reservation) {
var validated = Validate(reservation);
if (!validated) {
return BadRequest("Reservation invalid!");
}
try {
var updatedReservation = PersistAndUpdate(reservation);
if (updatedReservation == null) {
return BadRequest("Unable to update reservation!");
}
} catch {
return InternalServerError("DB error: unable to persist reservation!");
}
try {
SendNotification(updatedReservation);
} catch {
log.Error("Confirmation not sent!");
}
return Json(updatedReservation);
}
|
Functional?
1:
2:
3:
4:
5:
|
let createReservation =
validate
>> persistAndUpdate
>> sendNotification
>> respond
|
What does any of this have to do with railways?
1:
2:
|
let f: 'a -> 'b = ...
let g: 'b -> 'c = ...
|
1:
2:
3:
|
let f: 'a -> 'b = ...
let g: 'b -> 'c = ...
let h = f >> g
|
1:
|
let h: 'a -> 'c = f >> g
|
Switches
1:
2:
|
let validate: Reservation -> Reservation option = ...
let persist: Reservation -> Reservation option = ...
|
1:
2:
|
let validate: Reservation -> Reservation option = ...
let persist': Reservation option -> Reservation option = ...
|
1:
2:
|
let persist': Reservation option -> Reservation option =
Option.bind persist
|
1:
2:
3:
4:
|
let bind binder option =
match option with
| Some value -> binder value
| None -> None
|
1:
2:
|
let sendNotification': Reservation option -> Reservation option =
Option.map sendNotification
|
1:
2:
3:
4:
|
let map mapping option =
match option with
| Some value -> Some (mapping value)
| None -> None
|
And many more...
- unit-returning functions
- exception-throwing functions
- inspections
- ...
Functional "real path"
1:
2:
3:
4:
5:
|
let createReservation =
validate
>> persistAndUpdate
>> sendNotification
>> respond
|
Result<'TSuccess, 'TError> in FSharp.Core 4.1 (coming soon)