fuss-free data validation without using exceptions: scala, rust, swift, haskell
TRANSCRIPT
![Page 1: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/1.jpg)
Fuss-free data validation without using exceptionsScala, Rust, Swift, Haskell
Franklin Chenhttp://franklinchen.com/
Pittsburgh Code and Supply
April 9, 2015
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 1 / 45
![Page 2: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/2.jpg)
Outline
1 Introduction
2 A data validation example task
3 Models of computation
4 Option[T]
5 Either[E, T]
6 ValidationList[E, T]
7 Conclusion
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 2 / 45
![Page 3: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/3.jpg)
Goals
Explain concepts using fully worked-out example
Show real code (it’s all up on GitHub)
Hope you learn something you go out and use
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 3 / 45
![Page 4: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/4.jpg)
Why cover four languages?
Pittsburgh Code and Supply’s polyglot advantage
Opportunity for you to explore a new language, and compare differentdesigns
More efficient than giving multiple presentation about the exact sameconcepts
Why Scala, Rust, Swift, Haskell?
First-class functions
Tagged union types
But any language with first-class functions can be used:
JavaScript, etc.
Swift is still changing
Limited Swift code examples: language and compiler not stable (Swift 1.2was officially released yesterday !)
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 4 / 45
![Page 5: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/5.jpg)
A data validation example task
From Martin Fowler’s article, “Replacing Throwing Exceptions withNotification in Validations”:
Goal: create a valid event from a theater booking request
Given: a date string that is possible null, a number of seats that ispossible null
Validate:I Possibly null date string
F Date string must not be nullF Date string must actually parse to a dateF Request date must not be earlier than now
I Possibly null number of seatsF Number of seats must not be nullF Number of seats must be positive
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 5 / 45
![Page 6: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/6.jpg)
Java codepublic void check() {
if (date == null)
throw new IllegalArgumentException("date is missing");
LocalDate parsedDate;
try {
parsedDate = LocalDate.parse(date);
}
catch (DateTimeParseException e) {
throw new IllegalArgumentException("Invalid format for date", e);
}
if (parsedDate.isBefore(LocalDate.now()))
throw new IllegalArgumentException("date cannot be before today");
if (numberOfSeats == null)
throw new IllegalArgumentException("number of seats cannot be null");
if (numberOfSeats < 1)
throw new IllegalArgumentException("number of seats must be positive");
}
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 6 / 45
![Page 7: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/7.jpg)
Normal execution
A thread of execution, toward a destination
Stack of pending operations
When an operation is complete, pops off the stack
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 7 / 45
![Page 8: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/8.jpg)
Exceptions considered problematic
Exceptions mean jumping up the stack
Have to explicitly watch for and catch them
Tedious to collect more than one error if exceptions are used
What happens when there is concurrency?
Some languages don’t have exceptions
C
Go
Rust
Swift
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 8 / 45
![Page 9: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/9.jpg)
Railway-oriented programming
Railway-oriented programming:
Keeping computation on the tracks.
Cleanly handle track-switching and merging.
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 9 / 45
![Page 10: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/10.jpg)
null: the unloved second track
null is Tony Hoare’s billion-dollar mistake, invented in 1965
Adds a second track to every single computation involving a referencetype
Null Pointer Exceptions, seg faults
No more mention of null here!
All four languages mentioned have an improvement we take as astarting point.
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 10 / 45
![Page 11: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/11.jpg)
Option[T]
Scala Rust Swift Haskell
Type Option[T] Option<T> Optional<T>1 Maybe t
Nonexistence None None .None2 Nothing
Existence Some(x) Some(x) .Some(x)3 Just x
1Abbreviated T?2Abbreviated nil3Special syntactic sugar available
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 11 / 45
![Page 12: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/12.jpg)
Chaining computations over Option[T]
Example
Railway chaining values of an Option type
If encountering a None:I Bail out permanently to the failure track
Else if encountering a Some(x):I Stay on the success track
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 12 / 45
![Page 13: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/13.jpg)
Scala: chaining syntactic sugar for Option[T]
Example
“Find the winner: your best friend’s oldest sister’s youngest child”
/** Assume: bestFriend(), oldestSister(), youngestChild()
each returns Option[Person] */
def winner(person: Person): Option[Person] = for {
friend <- person.bestFriend()
sister <- friend.oldestSister()
child <- sister.youngestChild()
} yield child
Scala’s “for comprehensions” inspired by Haskell
Generic for railway-oriented programming
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 13 / 45
![Page 14: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/14.jpg)
Scala: non-sugar chaining for Option[T]
Example
“Find the winner: your best friend’s oldest sister’s youngest child”
/** Assume: bestFriend(), oldestSister(), youngestChild()
each returns Option[Person] */
def unsweetWinner(person: Person): Option[Person] =
person.bestFriend() .flatMap( friend =>
friend.oldestSister() .flatMap( sister =>
sister.youngestChild()
))
Sugar is preprocessed to this code before compilation
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 14 / 45
![Page 15: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/15.jpg)
Swift: chaining syntactic sugar for T?
Example
“Find the winner: your best friend’s oldest sister’s youngest child”
/** Assume: bestFriend(), oldestSister(), youngestChild()
each return Person? */
func winner(person: Person) -> Person? = {
return person.bestFriend()?.
oldestSister()?.
youngestChild()
}
Swift’s special chaining sugar
Specific to Optional only!
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 15 / 45
![Page 16: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/16.jpg)
Rust: no syntactic sugar for Option<T>
Example
“Find the winner: your best friend’s oldest sister’s youngest child”
/// Assume: best_friend(), oldest_sister(), youngest_child()
/// each returns Option<Person>
fn winner(person: Person) -> Option<Person> {
person.best_friend() .and_then( |friend|
friend.oldest_sister() .and_then( |sister|
sister.youngest_child()
))
}
Rust: no syntactic sugar
Deprecate use of Option for error signaling!
Sugar provided for what Rust recommends instead (next topic)
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 16 / 45
![Page 17: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/17.jpg)
Haskell: chaining syntactic sugar for Maybe t
Example
“Find the winner: your best friend’s oldest sister’s youngest child”
-- | Assume: bestFriend, oldestSister, youngestChild
-- each returns ’Maybe Person’
winner :: Person -> Maybe Person
winner person = do
friend <- person & bestFriend
sister <- friend & oldestSister
sister & youngestChild
Haskell’s “do notation” invented in 1993
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 17 / 45
![Page 18: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/18.jpg)
Option considered harmful
Warning
An Option-chained failure gives zero information about why and wheresomething failed!
When winner(person) returns None:
Did the person’s best friend’s oldest sister not have any children?
Or did the person’s best friend not have any sisters?
Or did the person not have any friends?
Knowledge is power
“Enquiring minds want to know!”
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 18 / 45
![Page 19: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/19.jpg)
Either[E, T]
Scala Swift Haskell
Type Either[E, T]4 Either<E, T>5 Either e t
Bad Left(e) .Left(e) Left e
Good Right(x) .Right(x) Just x
Rust
Type Result<T, E>6
Bad Err(e)
Good Ok(x)
4The Scalaz library provides an improved version called E \/ T we will prefer5Either<E, T> not in Swift’s standard library, but provided in Swiftx6Rust chose a more informative name, and placed success type param T first
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 19 / 45
![Page 20: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/20.jpg)
Converting between Option[T] to E \/ T
Conversion is simple
Examples using Scalaz:
E \/ T to Option “Forget” an error by replacing it with None:
optX = eitherX.toOption
Option[T] to E \/ T “Add” an error by replacing None with an error:
eitherX = optX.toRightDisjunction("some error")
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 20 / 45
![Page 21: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/21.jpg)
Chaining computations over Either[E, T]
Exact same concept as with Option[T].
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 21 / 45
![Page 22: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/22.jpg)
Scala: chaining syntactic sugar for E \/ T
Example
“Find the winner: your best friend’s oldest sister’s youngest child”
/** Assume: bestFriend(), oldestSister(), youngestChild()
each returns MyError \/ Person */
def winner(person: Person): MyError \/ Person = for {
friend <- person.bestFriend()
sister <- friend.oldestSister()
child <- sister.youngestChild()
} yield child
Exact same code as with Option[T]!
We are using Scalaz library’s disjunction E \/ T because standardScala’s Either[E, T] has limitations
Genericity of railway-oriented code: large topic in itself
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 22 / 45
![Page 23: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/23.jpg)
Swift: no syntactic sugar for Either<E, T>
Example
“Find the winner: your best friend’s oldest sister’s youngest child”
/** Assume: bestFriend(), oldestSister(), youngestChild()
each return Either<MyError, Person> */
func winner(person: Person) -> Either<MyError, Person> = {
return person.bestFriend() .flatMap { friend in
friend.oldestSister() .flatMap { sister in
sister.youngestChild()
}}
}
Use Swiftx library
Swift does not have general railway-oriented syntactic sugar
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 23 / 45
![Page 24: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/24.jpg)
Rust: chaining syntactic sugar for Result<T, E>
Example
“Find the winner: your best friend’s oldest sister’s youngest child”
/// Assume: best_friend(), oldest_sister(), youngest_child()
/// each returns Result<Person, MyError>
fn winner(person: Person) -> Result<Person, MyError> {
let friend = try!(person.best_friend());
let sister = try!(friend.oldest_sister());
sister.youngest_child()
}
Rust
Can use exactly the same non-sugar code as for Option<T> if wanted
Standard library has macro try! for use with Result<T, E>
Does not have exceptions, so ease of use of Result<T, E> is critical!
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 24 / 45
![Page 25: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/25.jpg)
Haskell: chaining syntactic sugar for Either e t
Example
“Find the winner: your best friend’s oldest sister’s youngest child”
-- | Assume: bestFriend, oldestSister, youngestChild
-- each returns ’Either MyError Person’
winner :: Person -> Either MyError Person
winner person = do
friend <- person & bestFriend
sister <- friend & oldestSister
sister & youngestChild
Exact same code as with Maybe t!
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 25 / 45
![Page 26: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/26.jpg)
Constructors considered harmful
Constructors in many languages do not return a result, so failures areindicated by either:
I Throwing an exceptionI Return null and setting an error object elsewhere
Alternative: “factory method” that returns anEither[SomeError, ThingToCreate]
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 26 / 45
![Page 27: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/27.jpg)
Constructing Seat: Scala
case class Seats private(val num: Int) extends AnyVal
object Seats {
sealed trait Error
case class BadCount(num: Int) extends Error {
override def toString =
s"number of seats was $num, but must be positive"
}
def make(num: Int): BadCount \/ Seats = {
if (num < 0)
BadCount(num).left
else
Seats(num).right
}
}
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 27 / 45
![Page 28: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/28.jpg)
Notes on clean module design
The constructor for Seats:I Is trivial, not bloated: just saves off parameters into fieldsI Is private, to guarantee only approved factory methods can call it
Errors:I Each module defines its own set of errors as a union type, here called
ErrorI (Here only one, BadCount)
Factory methods:I Each Seats factory method returns Error \/ Seats
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 28 / 45
![Page 29: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/29.jpg)
Constructing Seats: Rustpub struct Seats {
num: i32 // private
}
pub enum Error {
BadCount(i32)
}
impl Seats {
pub fn make(num: i32) -> Result<Seats, Error> {
if num <= 0 {
Err(Error::BadCount(num))
} else {
Ok(Seats { num: num })
}
}
}
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 29 / 45
![Page 30: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/30.jpg)
Constructing Seats: Haskell
-- | Wrapper around ’Int’ that ensures always positive.
newtype Seats = Seats { getNum :: Int }
data Error = BadCount Int -- ^ attempted number of seats
instance Show Error where
show (BadCount seats) = "number of seats was " ++
show seats ++ ", but must be positive"
-- | Smart constructor for ’Seats’ that
-- ensures always positive.
make :: Int -> Validation Error Seats
make seats | seats < 0 = Failure $ BadCount seats
| otherwise = Success $ Seats seats
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 30 / 45
![Page 31: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/31.jpg)
Constructing Seats: Swift
No example: because didn’t want to dive into the flaws of
failable initializers
Cocoa factory methods
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 31 / 45
![Page 32: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/32.jpg)
Either considered insufficient
Warning
An Either-chained failure returns information only about the first failure(“fail fast”).What if we want to chain multiple result-returning computations whilecollecting all failures along the way?
Examples:
Booking request example: date and number of seats may both beinvalid; we want to know about both failures
Facebook: concurrently accesses many data sources, collecting allfailures7
The goal
“Enquiring minds want to know everything !”
7Facebook open-sourced their Haskell library Haxl for thisFranklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 32 / 45
![Page 33: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/33.jpg)
Introduction
Validation libraries:
Scala Scalaz library
Rust I wrote my own library, may generalize and publish it
Swift Swiftz (superset of Swiftx) is based on Scalaz
Haskell validation library
Differences in naming and design
Because of differences, we will use Scalaz terminology.
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 33 / 45
![Page 34: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/34.jpg)
Definition
Validation[E, T]
Continue to track success/failure in an Either[E, T]-like object:
Leave sequential railway-oriented computation model
Adopt parallel computation model
ValidationList[E, T]
Just a synonym for Validation[List[E], T]:
Replace individual failure with a collection of failures
Annoying names
In the Scalaz library, the real name for ValidationList[E, T] isValidationNel[E, T]:
“Nel” stands for NonEmptyList
ValidationNel[E, T] is a synonym forValidation[NonEmptyList[E], T]
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 34 / 45
![Page 35: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/35.jpg)
All the different Either types
Either[E, T] E \/ T Validation[E, T]
Bad Left(e) -\/(e) Failure(e)
Good Right(x) \/-(x) Success(x)
Purpose symmetric, neutral railway-oriented accumulation
Conversion among them is simple: just replacing the tag.
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 35 / 45
![Page 36: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/36.jpg)
BookingRequest types: Scala
case class BookingRequest private(
val date: Date,
val seats: Seats
)
sealed trait Error
// These wrap errors from other modules.
case class DateError(e: Date.Error) extends Error
case class SeatsError(e: Seats.Error) extends Error
// Our additional errors.
case class DateBefore(date1: Date, date2: Date) extends Error
case class Missing(label: String) extends Error
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 36 / 45
![Page 37: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/37.jpg)
BookingRequest types: Rust
pub struct BookingRequest {
date: Date,
seats: seats::Seats
}
pub enum Error {
DateError(date::Error),
SeatsError(seats::Error),
DateBefore(Date, Date),
Missing(String)
}
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 37 / 45
![Page 38: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/38.jpg)
BookingRequest types: Haskell
data BookingRequest =
BookingRequest { getDate :: Date.Date
, getSeats :: Seats.Seats
}
data Error =
DateError Date.Error
| SeatsError Seats.Error
| Missing String -- ^ label
| DateBefore Date.Date -- ^ date that was attempted
Date.Date -- ^ the current date at attempt
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 38 / 45
![Page 39: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/39.jpg)
Seats creation
def makeSeats(optSeats: Option[Int]):
Error \/ Seats = for {
num <- optSeats.toRightDisjunction(Missing("seats"))
validSeats <- Seats.make(num).leftMap(SeatsError)
} yield validSeats
Use chaining:
Convert the Option[Int] to Error \/ Int
Use leftMap to lift from Seats.Error \/ Seats toError \/ Seats
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 39 / 45
![Page 40: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/40.jpg)
Date validation against now
def timelyBookingDate(date: Date, now: Date):
DateBefore \/ Date = {
if (!date.isBefore(now))
date.right
else
DateBefore(date, now).left
}
A validator that just passes along what comes in if it’s OK.
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 40 / 45
![Page 41: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/41.jpg)
Date creation
def makeTimelyBookingDate(now: Date,
optDateString: Option[String]): Error \/ Date = for {
dateString <- optDateString.
toRightDisjunction(Missing("date"))
date <- Date.parse(dateString).leftMap(DateError)
timelyDate <- timelyBookingDate(date, now)
} yield timelyDate
Use chaining:
First, get the requested date
Then validate that against now
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 41 / 45
![Page 42: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/42.jpg)
BookingRequest factory method
def make(
now: Date,
optDateString: Option[String],
optSeats: Option[Int]
): ValidationNel[Error, BookingRequest] = {
val combinedBuilder =
makeTimelyBookingDate(now, optDateString).
validation.toValidationNel |@|
makeSeats(optSeats).
validation.toValidationNel
combinedBuilder(BookingRequest(_, _))
}
Combination of techniques:
Sequential: each of Seats, Date validated creation is railway-oriented
Parallel: in principle, the combiner can be parallelizedFranklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 42 / 45
![Page 43: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/43.jpg)
Technical notes
Concepts covered, in order from more specific to more general:
Sequential, railway-oriented programming is called monadic: Option
and Either are the simplest monads; there is a vast number of morecomplex monads
Parallelizable composition is called applicative: Validation is anapplicative functor
Parallelizable combination is called monoidal: List is a monoid
(NonEmptyList is a semigroup, a monoid without identity element)
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 43 / 45
![Page 44: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/44.jpg)
Conclusion
Summary:
We saw how to break down a messy validation problem
Design with types to reflect intent
Use result objects that track failures, force error handling
Factory methods to create only valid objects
Sequential railway-oriented chaining of validator functions
Parallel computations can be expressed by separating independentcomponents
Minimizing if/else-style programming feels good!
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 44 / 45
![Page 45: Fuss-free data validation without using exceptions: Scala, Rust, Swift, Haskell](https://reader030.vdocuments.site/reader030/viewer/2022020307/55a999be1a28abb4758b46ab/html5/thumbnails/45.jpg)
Slides and code
Slides in source and PDF form:
https://github.com/FranklinChen/data-validation-demo
Complete code with tests run on Travis CI:
Scala https://github.com/FranklinChen/
data-validation-demo-scala
Rust https://github.com/FranklinChen/
data-validation-demo-rust
Haskell https://github.com/pittsburgh-haskell/
data-validation-demo-haskell
Swift https://github.com/FranklinChen/
data-validation-demo-swift
Franklin Chen http://franklinchen.com/ (Pittsburgh Code and Supply)Fuss-free data validation April 9, 2015 45 / 45