SlideShare a Scribd company logo
1 of 57
Download to read offline
Input Validation in PureScript
Simpler, Safer, Stronger
Joe Kachmar
LambdaConf 2018
4 Jun 2018
Introduction
● Software Engineer at WeWork
○ Mostly writing Scala
○ Snuck a little PureScript into my last project
● Learned PureScript after Haskell
○ Finished Haskell Programming from First Principles
○ Mostly using JavaScript at work at the time
○ Wanted Haskell-style FP with JavaScript support
(actual me)
(GitHub me)
Overview
● Show what we’re trying to avoid (a.k.a. Boolean Validation)
● Define errors that can be encountered while validating any field
● Write functions that perform these primitive validations
● Show how primitive validation functions can be combined
● Define types that represent valid fields
● Define field-specific errors in terms of primitive errors
● Write functions to perform field-specific validations
● Write a function that combines field-specific validations to validate a form
Boolean Validation
function validateForm({ emailAddress, password }) {
const isEmailValid = validateEmail(emailAddress);
const isPasswordValid = validatePassword (password);
return isEmailValid && isPasswordValid
}
● validateEmailand validatePasswordreturn booleans
● Any validation failures will cause the whole function to return false
○ Doesn’t communicate which elements failed validation
○ Doesn’t communicate why failing elements were invalid
Validations on Primitive Types
The Type of Validation
data V err result = Invalid err | Valid result
● V - the validation type, parameterized by types describing errors and results
● Invalid- construct a validation error
● Valid- construct a successful validation result
Validation Helper Functions
● purescript-validation doesn’t expose Validor Invaliddirectly
● invalidfunction used to signal validation errors
● validfunction used to signal validation success
● unV function used to unwrap the validation and handle errors or successes
Representing Simple Form Input
type UnvalidatedForm =
{ emailAddress :: String
, password :: String
}
● PureScript record representation of a simple form with two input fields
● Fields have very simple, “primitive” types
Representing Primitive Errors
● Empty fields
● Fields with invalid email addresses
● Fields that are too short
● Fields that are too long
● Fields that only have uppercase characters
● Fields that only have lowercase characters
data InvalidPrimitive
= EmptyField
| InvalidEmail String
| TooShort Int Int
| TooLong Int Int
| NoLowercase String
| NoUppercase String
Primitive Validation Type Signature
validateMinimumLength
:: String
-> Int
-> V (NonEmptyList InvalidPrimitive ) String
● Validates that the input string is greater than some given length
● Takes a input and minimum length arguments
● Returns either a list of primitive errors or a valid result
Primitive Validation Function Implementation
validateMinimumLength input min
| (length input) < min =
invalid (singleton ( TooShort (length input) min))
| otherwise = pure input
● Return an error if the input is too short
● Otherwise return the valid input
Full Primitive Validation Function
validateMinimumLength
:: String
-> Int
-> V (NonEmptyList InvalidPrimitive ) String
validateMinimumLength input min
| (length input) < min =
invalid (singleton ( TooShort (length input) min))
| otherwise = pure input
● Validates that the input string is greater than some given length
Primitive Validation Function Type Signature
(Round 2)
validateContainsLowercase
:: String
-> V (NonEmptyList InvalidPrimitive ) String
● Validates that the input string is greater than some given length
● Takes a String (the input to be validated)
● Returns either a non-empty list of invalid primitive errors, or the input string
Primitive Validation Function Implementation
(Round 2)
validateContainsLowercase input
| (toUpper input) == input =
invalid (singleton ( NoLowercase input))
| otherwise = pure input
● Return an error if the input is equal to an uppercase version of itself
● Otherwise return the valid input
validateContainsLowercase
:: String
-> V (NonEmptyList InvalidPrimitive ) String
validateContainsLowercase input
| (toUpper input) == input =
invalid (singleton ( NoLowercase input))
| otherwise = pure input
● Validates that the input string contains at least one lowercase character
Full Primitive Validation Function
(Round 2)
Exercise 1.1
Write a Function to Validate that Input is Non-Empty
● Exercise URL: http://bit.ly/2sckTgl
● Hint:
○ Check out Data.String#null for testing string emptiness
● When you’re done, you should see the following output on the right hand side
Solution 1.1
Write a Function to Validate that Input is Non-Empty
validateNonEmpty
:: String
-> V (NonEmptyList InvalidPrimitive ) String
validateNonEmpty input
| null input = invalid (singleton EmptyField)
| otherwise = pure input
Exercise 1.2
Write a Function to Validate that Input Contains Uppercase Characters
● Exercise URL: http://bit.ly/2LyG6t9
● Hints:
○ Think back to the validateContainsLowercase function from before
○ Check out Data.String#toLower for making lowercase strings
● When you’re done, you should see the following output on the right hand side
Solution 1.2
Write a Function to Validate that Input Contains Uppercase Characters
validateContainsUppercase
:: String
-> V (NonEmptyList InvalidPrimitive ) String
validateContainsUppercase input
| (toLower input) == input =
invalid (singleton ( NoUppercase input))
| otherwise = pure input
Combining Primitive Validations
(with Semigroup and Apply)
Combining Validations with Apply
instance applyV :: Semigroup err => Apply (V err) where
apply (Invalid err1) (Invalid err2) = Invalid (err1 <> err2)
apply (Invalid err) _ = Invalid err
apply _ ( Invalid err) = Invalid err
apply (Valid f) (Valid x) = Valid (f x)
● Applylet’s us sequence validations together in a way that accumulates errors
● Note the Semigroupconstraint on err
● Applyalso defines an infix operator for the applyfunction: (<*>)
Embedding Valid Results with Applicative
instance applicativeV :: Semigroup err => Applicative (V err) where
pure = Valid
● Applicativelet’s us wrap a valid result in V using pure
● Note the Semigroupconstraint on err
Combining Primitive Validations
validateLength
:: String
-> Int
-> Int
-> V (NonEmptyList InvalidPrimitive ) String
validateLength input minLength maxLength =
validateMinimumLength input minLength
*> validateMaximumLength input maxLength
● *> is an infix operator we can use to sequence validations
● Applyguarantees that all errors will be collected and returned
Exercise 2
Write a Function to Validate that Input Contains Upper- and Lowercase Characters
● Exercise URL: http://bit.ly/2xeF6XT
● Hints:
○ Use primitive functions defined earlier
○ All primitive validation functions from earlier have been defined
● When you’re done, you should see the following output on the right hand side
Solution 2
Write a Function Validating that a Field Contains Uppercase Characters
validateContainsMixedCase
:: String
-> V (NonEmptyList InvalidPrimitive ) String
validateContainsMixedCase input =
validateContainsLowercase input
*> validateContainsUppercase input
Break
Validations on Specialized Fields
Representing Validated Fields
newtype EmailAddress = EmailAddress String
newtype Password = Password String
● Validated fields are opaque wrappers around String
● One unique wrapper per unique field
● Creates a boundary between unvalidated input and validated output
Representing Field Errors
data InvalidField
= InvalidEmailAddress (NonEmptyList InvalidPrimitive )
| InvalidPassword (NonEmptyList InvalidPrimitive )
● Each field must have a corresponding InvalidFieldconstructor
● Each constructor wraps a list of errors from that field’s validation
Applying a Single Primitive Validation to a Field
validateEmailAddress
:: String
-> V (NonEmptyList InvalidField) EmailAddress
validateEmailAddress input =
let result = validateNonEmpty input
in bimap (singleton <<< InvalidEmailAddress ) EmailAddress result
● Apply a single validation to the input
● Transform the primitive validation into a field validation
Applying Multiple Primitive Validations to a Field
validateEmailAddress
:: String
-> V (NonEmptyList InvalidField) EmailAddress
validateEmailAddress input =
let result = validateNonEmpty input *> validateEmailRegex input
in bimap (singleton <<< InvalidEmailAddress ) EmailAddress result
● Nearly identical to the previous function
● Performs two validations on the input
● Any and all errors from either validation will be collected
Exercise 3
Write a Function to Validate a Password
● Exercise URL: http://bit.ly/2GWa0Uh
● Solution must check for...
○ Non-empty input
○ Mixed case input
○ Valid length
● When you’re done, you should see the following output on the right hand side
Solution 3
Write a Function to Validate a Password
validatePassword
:: String
-> Int
-> Int
-> V (NonEmptyList InvalidField) Password
validatePassword input minLength maxLength =
let result =
validateNonEmpty input
*> validateContainsMixedCase input
*> (validateLength input minLength maxLength)
in bimap (singleton <<< InvalidPassword ) Password result
Validations on Forms
Representing a Form
● An unvalidated form is a record where all fields are primitive types
type UnvalidatedForm =
{ emailAddress :: String
, password :: String
}
● A validated form is a record where all fields are unique, opaque types
type ValidatedForm =
{ emailAddress :: EmailAddress
, password :: Password
}
Form Validation Type Signature
validateForm
:: UnvalidatedForm
-> V (NonEmptyList InvalidField) ValidatedForm
● Validates that a form contains only valid fields
● Returns either a validated form or a list of invalid field errors
Form Validation Function Implementation
validateForm { emailAddress, password } =
{ emailAddress: _, password: _ }
<$> (validateEmailAddress emailAddress)
<*> (validatePassword password 0 60)
● Destructure the unvalidated form input to get its fields
● Create an anonymous function to produce the validated record
● Partially apply the function to the email validation result with (<$>)
● Fully apply the function to the password validation result with (<*>)
Full Form Validation Function
validateForm
:: UnvalidatedForm
-> V (NonEmptyList InvalidField) ValidatedForm
validateForm { emailAddress, password } =
{ emailAddress: _, password: _ }
<$> (validateEmailAddress emailAddress)
<*> (validatePassword password 0 60)
● Validates a simple input form
Extending the Form Validation Function
validateForm { emailAddress, password, thirdField } =
{ emailAddress: _, password: _, thirdField: _ }
<$> (validateEmailAddress emailAddress)
<*> (validatePassword password 0 60)
<*> (validateThirdInput thirdField)
● Extending the validation is relatively straightforward
Exercise 4
Extend the Form Validator to Support an Additional Field
● Exercise URL: http://bit.ly/2Jcu2PK
● More free-form than previous exercises
● Details provided in block comments in the exercise
● Hints:
○ The names of the destructured record must match the actual record field names
○ Your newtype will need some instances, check the bottom of the module and copy
what was done for EmailAddress and Password
○ InvalidField’s Show instance will need to be updated for your new field error,
check the bottom of the module and copy what was done for
InvalidEmailAddress and InvalidPassword
Solution 4
Extend the Form Validator to Support an Additional Field
● Solution URL: http://bit.ly/2JcXogJ
Break
Validations on Branching Fields
(with Semiring and Alt)
Representing Branching Fields
● What if one of the fields could have multiple valid representations?
data UserId
= EmailAddress String
| Username String
● Represent the field with a sum type!
● UserIdis the new field type
● Can be constructed as either an email address or username
Representing Branching Errors
● Semigroupcan’t accumulate branching errors
● Semiringcan!
○ Uses (+) to represent associative operations (like (<>))
○ Uses (*) to represent distributive operations
● Let’s us model branching validations while still collecting all errors
Combining Validations with Apply
(and Semiring)
instance applyV :: Semiring err => Apply (V err) where
apply (Invalid err1) (Invalid err2) = Invalid (err1 * err2)
apply (Invalid err) _ = Invalid err
apply _ ( Invalid err) = Invalid err
apply (Valid f) (Valid x) = Valid (f x)
● Nearly identical to the Semigroupversion
○ err has a Semiringconstraint instead of a Semigroupconstraint
○ Uses (*) instead of (<>) for combining errors
Embedding Valid Results with Applicative
(and Semiring)
instance applicativeV :: Semiring err => Applicative (V err) where
pure = Valid
● Again, nearly identical to the Semigroupversion
Selecting Alternative Validations with Alt
instance altV :: Semiring err => Alt (V err) where
alt (Invalid err1) (Invalid err2) = Invalid (err1 + err2)
alt (Invalid _) a = a
alt (Valid a) _ = Valid a
● Alt let’s us provide alternative validations in a way that accumulates errors
● Note the Semiringconstraint on err
○ Uses (+) to collect errors, rather than (*) or (<>)
● Alt also defines an infix operator: (<|>)
Branching Email Address Validation Function
validateEmailAddress
:: String
-> V (Free InvalidField) UserId
validateEmailAddress input =
let result = validateNonEmpty input *> validateEmailRegex
input
in bimap (free <<< InvalidEmailAddress ) EmailAddress result
● Email validation that supports Alt and Semiring
Branching Username Validation Function
validateUsername
:: String -> Int -> Int
-> V (Free InvalidField) UserId
validateUsername input min max =
let result =
validateNonEmpty input
*> (validateLength input minLength maxLength)
in bimap (free <<< InvalidUsername ) UserName result
● Username validation that supports Alt and Semiring
Exercise 5
Extend the Form Validator to Support Branching Validation with Semiring and Alt
● Exercise URL: http://bit.ly/2kLTaiw
● Guided exercise (there’s a lot going on here, so we’ll go through it together)
○ Switch from Data.Validation.Semigroup to Data.Validation.Semiring
○ Update all validation functions to use Free and free
○ Create a UserId sum type with EmailAddress and Username constructors
○ Add an InvalidUsername constructor to the InvalidField type
○ Update validateEmailAddress to work with UserId
○ Write validateUsername function to validate a username
○ Use Control.Alt#(<|>) select between alternative validations
○ Update UnvalidatedForm and ValidatedForm with new input and output fields
Solution 5
Extend the Form Validator to Support Branching Validation with Semiring and Alt
● Solution URL: http://bit.ly/2xNcBRx
Summary
Further Reading
● My own “validation experiment”
○ https://github.com/jkachmar/purescript-validation-experiment
● purescript-polyform - validation toolkit using some of these concepts
○ https://github.com/paluh/purescript-polyform
● purescript-ocelot - component library using polyform
○ https://github.com/citizennet/purescript-ocelot
These Ideas Aren’t Specific to Validation!
Tuple <$> Just 1 <*> Just 2 == Just (Tuple 1 2)
Tuple <$> Just 1 <*> Nothing == Nothing
● Build an optional Tuplefrom optional components
Ajax.get "http://foo.com" <|> Ajax.get "http://bar.com"
● Select the first successful response from concurrent computations
We’re Hiring!
Questions?

More Related Content

What's hot

Data Type Conversion in C++
Data Type Conversion in C++Data Type Conversion in C++
Data Type Conversion in C++
Danial Mirza
 
05 control structures 2
05 control structures 205 control structures 2
05 control structures 2
Jomel Penalba
 
Categories for the Working C++ Programmer
Categories for the Working C++ ProgrammerCategories for the Working C++ Programmer
Categories for the Working C++ Programmer
Platonov Sergey
 

What's hot (19)

Working of while loop
Working of while loopWorking of while loop
Working of while loop
 
Chapter 3 : Programming with Java Operators and Strings
Chapter 3 : Programming with Java Operators and  StringsChapter 3 : Programming with Java Operators and  Strings
Chapter 3 : Programming with Java Operators and Strings
 
Functional Swift
Functional SwiftFunctional Swift
Functional Swift
 
Inline Functions and Default arguments
Inline Functions and Default argumentsInline Functions and Default arguments
Inline Functions and Default arguments
 
Inline function in C++
Inline function in C++Inline function in C++
Inline function in C++
 
Data Type Conversion in C++
Data Type Conversion in C++Data Type Conversion in C++
Data Type Conversion in C++
 
Inline function in C++
Inline function in C++Inline function in C++
Inline function in C++
 
escape sequences and substitution markers
escape sequences and substitution markersescape sequences and substitution markers
escape sequences and substitution markers
 
Inline functions & macros
Inline functions & macrosInline functions & macros
Inline functions & macros
 
Functional programming
Functional programmingFunctional programming
Functional programming
 
Closures
ClosuresClosures
Closures
 
Inline and lambda function
Inline and lambda functionInline and lambda function
Inline and lambda function
 
Lecture 3
Lecture 3Lecture 3
Lecture 3
 
Function overloading in c++
Function overloading in c++Function overloading in c++
Function overloading in c++
 
Review Python
Review PythonReview Python
Review Python
 
05 control structures 2
05 control structures 205 control structures 2
05 control structures 2
 
Categories for the Working C++ Programmer
Categories for the Working C++ ProgrammerCategories for the Working C++ Programmer
Categories for the Working C++ Programmer
 
Swift Tutorial Part 1. The Complete Guide For Swift Programming Language
Swift Tutorial Part 1. The Complete Guide For Swift Programming LanguageSwift Tutorial Part 1. The Complete Guide For Swift Programming Language
Swift Tutorial Part 1. The Complete Guide For Swift Programming Language
 
C++ overloading
C++ overloadingC++ overloading
C++ overloading
 

Similar to LC2018 - Input Validation in PureScript

Loop structures chpt_6
Loop structures chpt_6Loop structures chpt_6
Loop structures chpt_6
cmontanez
 
Csc1100 lecture05 ch05
Csc1100 lecture05 ch05Csc1100 lecture05 ch05
Csc1100 lecture05 ch05
IIUM
 
Code Kata: String Calculator in Flex
Code Kata: String Calculator in FlexCode Kata: String Calculator in Flex
Code Kata: String Calculator in Flex
Chris Farrell
 
IN C++ PLEASE Topics Loops While statement Description Write.pdf
IN C++ PLEASE     Topics   Loops While statement   Description   Write.pdfIN C++ PLEASE     Topics   Loops While statement   Description   Write.pdf
IN C++ PLEASE Topics Loops While statement Description Write.pdf
asdepartmantal
 
Java căn bản - Chapter6
Java căn bản - Chapter6Java căn bản - Chapter6
Java căn bản - Chapter6
Vince Vo
 

Similar to LC2018 - Input Validation in PureScript (20)

Thinking in Functions
Thinking in FunctionsThinking in Functions
Thinking in Functions
 
Loop structures chpt_6
Loop structures chpt_6Loop structures chpt_6
Loop structures chpt_6
 
Object Oriented Programming with C++
Object Oriented Programming with C++Object Oriented Programming with C++
Object Oriented Programming with C++
 
Csc1100 lecture05 ch05
Csc1100 lecture05 ch05Csc1100 lecture05 ch05
Csc1100 lecture05 ch05
 
Chap 5 c++
Chap 5 c++Chap 5 c++
Chap 5 c++
 
Code Kata: String Calculator in Flex
Code Kata: String Calculator in FlexCode Kata: String Calculator in Flex
Code Kata: String Calculator in Flex
 
IN C++ PLEASE Topics Loops While statement Description Write.pdf
IN C++ PLEASE     Topics   Loops While statement   Description   Write.pdfIN C++ PLEASE     Topics   Loops While statement   Description   Write.pdf
IN C++ PLEASE Topics Loops While statement Description Write.pdf
 
Chap 5 c++
Chap 5 c++Chap 5 c++
Chap 5 c++
 
Iterative control structures, looping, types of loops, loop working
Iterative control structures, looping, types of loops, loop workingIterative control structures, looping, types of loops, loop working
Iterative control structures, looping, types of loops, loop working
 
C++ loop
C++ loop C++ loop
C++ loop
 
Python Basics
Python BasicsPython Basics
Python Basics
 
Function in C++, Methods in C++ coding programming
Function in C++, Methods in C++ coding programmingFunction in C++, Methods in C++ coding programming
Function in C++, Methods in C++ coding programming
 
C#
C#C#
C#
 
Python Unit 3 - Control Flow and Functions
Python Unit 3 - Control Flow and FunctionsPython Unit 3 - Control Flow and Functions
Python Unit 3 - Control Flow and Functions
 
C++ Functions.ppt
C++ Functions.pptC++ Functions.ppt
C++ Functions.ppt
 
Functional programming 101
Functional programming 101Functional programming 101
Functional programming 101
 
P3
P3P3
P3
 
Ch03
Ch03Ch03
Ch03
 
Java căn bản - Chapter6
Java căn bản - Chapter6Java căn bản - Chapter6
Java căn bản - Chapter6
 
05 operators
05   operators05   operators
05 operators
 

Recently uploaded

Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Medical / Health Care (+971588192166) Mifepristone and Misoprostol tablets 200mg
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
masabamasaba
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
masabamasaba
 
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Medical / Health Care (+971588192166) Mifepristone and Misoprostol tablets 200mg
 
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
chiefasafspells
 

Recently uploaded (20)

%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
tonesoftg
tonesoftgtonesoftg
tonesoftg
 
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni%in Benoni+277-882-255-28 abortion pills for sale in Benoni
%in Benoni+277-882-255-28 abortion pills for sale in Benoni
 
What Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the SituationWhat Goes Wrong with Language Definitions and How to Improve the Situation
What Goes Wrong with Language Definitions and How to Improve the Situation
 
%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto
 
Artyushina_Guest lecture_YorkU CS May 2024.pptx
Artyushina_Guest lecture_YorkU CS May 2024.pptxArtyushina_Guest lecture_YorkU CS May 2024.pptx
Artyushina_Guest lecture_YorkU CS May 2024.pptx
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 
Announcing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareAnnouncing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK Software
 
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
Abortion Pills In Pretoria ](+27832195400*)[ 🏥 Women's Abortion Clinic In Pre...
 
WSO2Con204 - Hard Rock Presentation - Keynote
WSO2Con204 - Hard Rock Presentation - KeynoteWSO2Con204 - Hard Rock Presentation - Keynote
WSO2Con204 - Hard Rock Presentation - Keynote
 
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
Love witchcraft +27768521739 Binding love spell in Sandy Springs, GA |psychic...
 
BUS PASS MANGEMENT SYSTEM USING PHP.pptx
BUS PASS MANGEMENT SYSTEM USING PHP.pptxBUS PASS MANGEMENT SYSTEM USING PHP.pptx
BUS PASS MANGEMENT SYSTEM USING PHP.pptx
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
 
WSO2CON 2024 Slides - Open Source to SaaS
WSO2CON 2024 Slides - Open Source to SaaSWSO2CON 2024 Slides - Open Source to SaaS
WSO2CON 2024 Slides - Open Source to SaaS
 

LC2018 - Input Validation in PureScript

  • 1. Input Validation in PureScript Simpler, Safer, Stronger Joe Kachmar LambdaConf 2018 4 Jun 2018
  • 2. Introduction ● Software Engineer at WeWork ○ Mostly writing Scala ○ Snuck a little PureScript into my last project ● Learned PureScript after Haskell ○ Finished Haskell Programming from First Principles ○ Mostly using JavaScript at work at the time ○ Wanted Haskell-style FP with JavaScript support (actual me) (GitHub me)
  • 3. Overview ● Show what we’re trying to avoid (a.k.a. Boolean Validation) ● Define errors that can be encountered while validating any field ● Write functions that perform these primitive validations ● Show how primitive validation functions can be combined ● Define types that represent valid fields ● Define field-specific errors in terms of primitive errors ● Write functions to perform field-specific validations ● Write a function that combines field-specific validations to validate a form
  • 4. Boolean Validation function validateForm({ emailAddress, password }) { const isEmailValid = validateEmail(emailAddress); const isPasswordValid = validatePassword (password); return isEmailValid && isPasswordValid } ● validateEmailand validatePasswordreturn booleans ● Any validation failures will cause the whole function to return false ○ Doesn’t communicate which elements failed validation ○ Doesn’t communicate why failing elements were invalid
  • 6. The Type of Validation data V err result = Invalid err | Valid result ● V - the validation type, parameterized by types describing errors and results ● Invalid- construct a validation error ● Valid- construct a successful validation result
  • 7. Validation Helper Functions ● purescript-validation doesn’t expose Validor Invaliddirectly ● invalidfunction used to signal validation errors ● validfunction used to signal validation success ● unV function used to unwrap the validation and handle errors or successes
  • 8. Representing Simple Form Input type UnvalidatedForm = { emailAddress :: String , password :: String } ● PureScript record representation of a simple form with two input fields ● Fields have very simple, “primitive” types
  • 9. Representing Primitive Errors ● Empty fields ● Fields with invalid email addresses ● Fields that are too short ● Fields that are too long ● Fields that only have uppercase characters ● Fields that only have lowercase characters data InvalidPrimitive = EmptyField | InvalidEmail String | TooShort Int Int | TooLong Int Int | NoLowercase String | NoUppercase String
  • 10. Primitive Validation Type Signature validateMinimumLength :: String -> Int -> V (NonEmptyList InvalidPrimitive ) String ● Validates that the input string is greater than some given length ● Takes a input and minimum length arguments ● Returns either a list of primitive errors or a valid result
  • 11. Primitive Validation Function Implementation validateMinimumLength input min | (length input) < min = invalid (singleton ( TooShort (length input) min)) | otherwise = pure input ● Return an error if the input is too short ● Otherwise return the valid input
  • 12. Full Primitive Validation Function validateMinimumLength :: String -> Int -> V (NonEmptyList InvalidPrimitive ) String validateMinimumLength input min | (length input) < min = invalid (singleton ( TooShort (length input) min)) | otherwise = pure input ● Validates that the input string is greater than some given length
  • 13. Primitive Validation Function Type Signature (Round 2) validateContainsLowercase :: String -> V (NonEmptyList InvalidPrimitive ) String ● Validates that the input string is greater than some given length ● Takes a String (the input to be validated) ● Returns either a non-empty list of invalid primitive errors, or the input string
  • 14. Primitive Validation Function Implementation (Round 2) validateContainsLowercase input | (toUpper input) == input = invalid (singleton ( NoLowercase input)) | otherwise = pure input ● Return an error if the input is equal to an uppercase version of itself ● Otherwise return the valid input
  • 15. validateContainsLowercase :: String -> V (NonEmptyList InvalidPrimitive ) String validateContainsLowercase input | (toUpper input) == input = invalid (singleton ( NoLowercase input)) | otherwise = pure input ● Validates that the input string contains at least one lowercase character Full Primitive Validation Function (Round 2)
  • 16. Exercise 1.1 Write a Function to Validate that Input is Non-Empty ● Exercise URL: http://bit.ly/2sckTgl ● Hint: ○ Check out Data.String#null for testing string emptiness ● When you’re done, you should see the following output on the right hand side
  • 17. Solution 1.1 Write a Function to Validate that Input is Non-Empty validateNonEmpty :: String -> V (NonEmptyList InvalidPrimitive ) String validateNonEmpty input | null input = invalid (singleton EmptyField) | otherwise = pure input
  • 18. Exercise 1.2 Write a Function to Validate that Input Contains Uppercase Characters ● Exercise URL: http://bit.ly/2LyG6t9 ● Hints: ○ Think back to the validateContainsLowercase function from before ○ Check out Data.String#toLower for making lowercase strings ● When you’re done, you should see the following output on the right hand side
  • 19. Solution 1.2 Write a Function to Validate that Input Contains Uppercase Characters validateContainsUppercase :: String -> V (NonEmptyList InvalidPrimitive ) String validateContainsUppercase input | (toLower input) == input = invalid (singleton ( NoUppercase input)) | otherwise = pure input
  • 21. Combining Validations with Apply instance applyV :: Semigroup err => Apply (V err) where apply (Invalid err1) (Invalid err2) = Invalid (err1 <> err2) apply (Invalid err) _ = Invalid err apply _ ( Invalid err) = Invalid err apply (Valid f) (Valid x) = Valid (f x) ● Applylet’s us sequence validations together in a way that accumulates errors ● Note the Semigroupconstraint on err ● Applyalso defines an infix operator for the applyfunction: (<*>)
  • 22. Embedding Valid Results with Applicative instance applicativeV :: Semigroup err => Applicative (V err) where pure = Valid ● Applicativelet’s us wrap a valid result in V using pure ● Note the Semigroupconstraint on err
  • 23. Combining Primitive Validations validateLength :: String -> Int -> Int -> V (NonEmptyList InvalidPrimitive ) String validateLength input minLength maxLength = validateMinimumLength input minLength *> validateMaximumLength input maxLength ● *> is an infix operator we can use to sequence validations ● Applyguarantees that all errors will be collected and returned
  • 24. Exercise 2 Write a Function to Validate that Input Contains Upper- and Lowercase Characters ● Exercise URL: http://bit.ly/2xeF6XT ● Hints: ○ Use primitive functions defined earlier ○ All primitive validation functions from earlier have been defined ● When you’re done, you should see the following output on the right hand side
  • 25. Solution 2 Write a Function Validating that a Field Contains Uppercase Characters validateContainsMixedCase :: String -> V (NonEmptyList InvalidPrimitive ) String validateContainsMixedCase input = validateContainsLowercase input *> validateContainsUppercase input
  • 26. Break
  • 28. Representing Validated Fields newtype EmailAddress = EmailAddress String newtype Password = Password String ● Validated fields are opaque wrappers around String ● One unique wrapper per unique field ● Creates a boundary between unvalidated input and validated output
  • 29. Representing Field Errors data InvalidField = InvalidEmailAddress (NonEmptyList InvalidPrimitive ) | InvalidPassword (NonEmptyList InvalidPrimitive ) ● Each field must have a corresponding InvalidFieldconstructor ● Each constructor wraps a list of errors from that field’s validation
  • 30. Applying a Single Primitive Validation to a Field validateEmailAddress :: String -> V (NonEmptyList InvalidField) EmailAddress validateEmailAddress input = let result = validateNonEmpty input in bimap (singleton <<< InvalidEmailAddress ) EmailAddress result ● Apply a single validation to the input ● Transform the primitive validation into a field validation
  • 31. Applying Multiple Primitive Validations to a Field validateEmailAddress :: String -> V (NonEmptyList InvalidField) EmailAddress validateEmailAddress input = let result = validateNonEmpty input *> validateEmailRegex input in bimap (singleton <<< InvalidEmailAddress ) EmailAddress result ● Nearly identical to the previous function ● Performs two validations on the input ● Any and all errors from either validation will be collected
  • 32. Exercise 3 Write a Function to Validate a Password ● Exercise URL: http://bit.ly/2GWa0Uh ● Solution must check for... ○ Non-empty input ○ Mixed case input ○ Valid length ● When you’re done, you should see the following output on the right hand side
  • 33. Solution 3 Write a Function to Validate a Password validatePassword :: String -> Int -> Int -> V (NonEmptyList InvalidField) Password validatePassword input minLength maxLength = let result = validateNonEmpty input *> validateContainsMixedCase input *> (validateLength input minLength maxLength) in bimap (singleton <<< InvalidPassword ) Password result
  • 35. Representing a Form ● An unvalidated form is a record where all fields are primitive types type UnvalidatedForm = { emailAddress :: String , password :: String } ● A validated form is a record where all fields are unique, opaque types type ValidatedForm = { emailAddress :: EmailAddress , password :: Password }
  • 36. Form Validation Type Signature validateForm :: UnvalidatedForm -> V (NonEmptyList InvalidField) ValidatedForm ● Validates that a form contains only valid fields ● Returns either a validated form or a list of invalid field errors
  • 37. Form Validation Function Implementation validateForm { emailAddress, password } = { emailAddress: _, password: _ } <$> (validateEmailAddress emailAddress) <*> (validatePassword password 0 60) ● Destructure the unvalidated form input to get its fields ● Create an anonymous function to produce the validated record ● Partially apply the function to the email validation result with (<$>) ● Fully apply the function to the password validation result with (<*>)
  • 38. Full Form Validation Function validateForm :: UnvalidatedForm -> V (NonEmptyList InvalidField) ValidatedForm validateForm { emailAddress, password } = { emailAddress: _, password: _ } <$> (validateEmailAddress emailAddress) <*> (validatePassword password 0 60) ● Validates a simple input form
  • 39. Extending the Form Validation Function validateForm { emailAddress, password, thirdField } = { emailAddress: _, password: _, thirdField: _ } <$> (validateEmailAddress emailAddress) <*> (validatePassword password 0 60) <*> (validateThirdInput thirdField) ● Extending the validation is relatively straightforward
  • 40. Exercise 4 Extend the Form Validator to Support an Additional Field ● Exercise URL: http://bit.ly/2Jcu2PK ● More free-form than previous exercises ● Details provided in block comments in the exercise ● Hints: ○ The names of the destructured record must match the actual record field names ○ Your newtype will need some instances, check the bottom of the module and copy what was done for EmailAddress and Password ○ InvalidField’s Show instance will need to be updated for your new field error, check the bottom of the module and copy what was done for InvalidEmailAddress and InvalidPassword
  • 41. Solution 4 Extend the Form Validator to Support an Additional Field ● Solution URL: http://bit.ly/2JcXogJ
  • 42. Break
  • 43. Validations on Branching Fields (with Semiring and Alt)
  • 44. Representing Branching Fields ● What if one of the fields could have multiple valid representations? data UserId = EmailAddress String | Username String ● Represent the field with a sum type! ● UserIdis the new field type ● Can be constructed as either an email address or username
  • 45. Representing Branching Errors ● Semigroupcan’t accumulate branching errors ● Semiringcan! ○ Uses (+) to represent associative operations (like (<>)) ○ Uses (*) to represent distributive operations ● Let’s us model branching validations while still collecting all errors
  • 46. Combining Validations with Apply (and Semiring) instance applyV :: Semiring err => Apply (V err) where apply (Invalid err1) (Invalid err2) = Invalid (err1 * err2) apply (Invalid err) _ = Invalid err apply _ ( Invalid err) = Invalid err apply (Valid f) (Valid x) = Valid (f x) ● Nearly identical to the Semigroupversion ○ err has a Semiringconstraint instead of a Semigroupconstraint ○ Uses (*) instead of (<>) for combining errors
  • 47. Embedding Valid Results with Applicative (and Semiring) instance applicativeV :: Semiring err => Applicative (V err) where pure = Valid ● Again, nearly identical to the Semigroupversion
  • 48. Selecting Alternative Validations with Alt instance altV :: Semiring err => Alt (V err) where alt (Invalid err1) (Invalid err2) = Invalid (err1 + err2) alt (Invalid _) a = a alt (Valid a) _ = Valid a ● Alt let’s us provide alternative validations in a way that accumulates errors ● Note the Semiringconstraint on err ○ Uses (+) to collect errors, rather than (*) or (<>) ● Alt also defines an infix operator: (<|>)
  • 49. Branching Email Address Validation Function validateEmailAddress :: String -> V (Free InvalidField) UserId validateEmailAddress input = let result = validateNonEmpty input *> validateEmailRegex input in bimap (free <<< InvalidEmailAddress ) EmailAddress result ● Email validation that supports Alt and Semiring
  • 50. Branching Username Validation Function validateUsername :: String -> Int -> Int -> V (Free InvalidField) UserId validateUsername input min max = let result = validateNonEmpty input *> (validateLength input minLength maxLength) in bimap (free <<< InvalidUsername ) UserName result ● Username validation that supports Alt and Semiring
  • 51. Exercise 5 Extend the Form Validator to Support Branching Validation with Semiring and Alt ● Exercise URL: http://bit.ly/2kLTaiw ● Guided exercise (there’s a lot going on here, so we’ll go through it together) ○ Switch from Data.Validation.Semigroup to Data.Validation.Semiring ○ Update all validation functions to use Free and free ○ Create a UserId sum type with EmailAddress and Username constructors ○ Add an InvalidUsername constructor to the InvalidField type ○ Update validateEmailAddress to work with UserId ○ Write validateUsername function to validate a username ○ Use Control.Alt#(<|>) select between alternative validations ○ Update UnvalidatedForm and ValidatedForm with new input and output fields
  • 52. Solution 5 Extend the Form Validator to Support Branching Validation with Semiring and Alt ● Solution URL: http://bit.ly/2xNcBRx
  • 54. Further Reading ● My own “validation experiment” ○ https://github.com/jkachmar/purescript-validation-experiment ● purescript-polyform - validation toolkit using some of these concepts ○ https://github.com/paluh/purescript-polyform ● purescript-ocelot - component library using polyform ○ https://github.com/citizennet/purescript-ocelot
  • 55. These Ideas Aren’t Specific to Validation! Tuple <$> Just 1 <*> Just 2 == Just (Tuple 1 2) Tuple <$> Just 1 <*> Nothing == Nothing ● Build an optional Tuplefrom optional components Ajax.get "http://foo.com" <|> Ajax.get "http://bar.com" ● Select the first successful response from concurrent computations