Functional Data Validation
ABOUT ME
- Functional Programming advocate
- Scala and Haskell
- Occasional writer at:
- GitHub repository
Functional Data Validation
- Simple validation: Maybe Monad
- A bit more descriptive: Either Monad
- Validation with full report of errors
Everybody needs to validate something. From the content of a file to a web form.
SIMPLE VALIDATION
MAYBE MONAD
data Address =
Address {
getHouseNumber :: Int,
getStreetName :: String
} deriving (Eq, Show)
- House Number > 0
- Street Name not empty
makeAddress :: Int -> String -> Maybe Address
data Maybe a = Nothing | Just a
makeAddress x y
| x <= 0 || y == "" = Nothing
| otherwise = Just $ Address x y
SIMPLE VALIDATION
MAYBE MONAD
data Person =
Person {
getName :: String,
getAddress :: Address
} deriving (Eq, Show)
- Name not empty
makePerson :: String -> Maybe Address -> Maybe Person
makePerson n address
| n == "" = Nothing
| otherwise = address >>= \a -> Just $ Person n a
A BIT MORE DESCRIPTIVE
Either MONAD
data Address =
Address {
getHouseNumber :: Int,
getStreetName :: String
} deriving (Eq, Show)
- House Number > 0
- Street Name not empty
makeAddress :: Int -> String -> Either Error Address
data Either a b = Left a | Right b
A BIT MORE DESCRIPTIVE
Either MONAD
makeAddress :: Int -> String -> Either Error Address
streetNumberGreaterThanZero :: Int -> Either Error Int
streetNumberGreaterThanZero x
| x <= 0 = Left "Number must be greater than zero!"
| otherwise = Right x
notEmpty :: String -> Error -> Either Error String
notEmpty x err
| x == "" = Left err
| otherwise = Right x
streetNameNotEmpty :: String -> Either Error String
streetNameNotEmpty x = notEmpty x "Street name must not be empty!"
makeAddress x y = do
number <- streetNumberGreaterThanZero x
name <- streetNameNotEmpty y
Right $ Address number name
A BIT MORE DESCRIPTIVE
Either MONAD
data Person =
Person {
getName :: String,
getAddress :: Address
} deriving (Eq, Show)
- Name not empty
makePerson :: String -> Either Error Address -> Either Error Person
nameNotEmpty :: String -> Either Error String
nameNotEmpty x = notEmpty x "Name must not be empty!"
makePerson n a = do
name <- nameNotEmpty n
address <- a
Right $ Person name address
makePerson' :: String -> Either Error Address -> Either Error Person
makePerson' n a =
nameNotEmpty n >>= \name ->
(Person name) <$> a
FULL REPORT OF ERRORS
THE IMPERATIVE WAY
def makeAddress(number: Int, name: String): Address = {
if (number <= 0) {
throw new Exception("Street number must be greater than zero!")
} else if (name.isEmpty) {
throw new Exception("Street name must not be empty!")
} else if (name.length > 10) {
throw new Exception("Street name must not have more than 10 characters!")
} else {
Address(number, name)
}
}
def makePerson(name: String, address: Address): Person = {
if (name.isEmpty) {
throw new Exception("Name must not be empty!")
} else if (address == null) {
throw new IllegalArgumentException()
} else {
Person(name, address)
}
}
try {
val address = makeAddress(0, "")
val person = makePerson("Gabi", address)
println(person)
} catch {
case e: Exception => println(e.getMessage)
}
FULL REPORT OF ERRORS
THE OOP WAY: NOtification Pattern
private def validateAddress(houseNumber: Int, streetName: String, notification: Notification) = {
if (houseNumber <= 0)
notification.addError("Street number must be greater than zero!")
if (streetName.isEmpty)
notification.addError("Street name must not be empty!")
if (streetName.length > 10)
notification.addError("Street name must not have more than 10 characters!")
}
private def validatePerson(personName: String, notification: Notification) = {
if (personName.isEmpty)
notification.addError("Name must not be empty!")
}
val notification = new Notification()
validateAddress(houseNumber, streetName, notification)
validatePerson(personName, notification)
val address = Address(houseNumber, streetName)
val person = Person(personName, address)
if (notification.hasErrors) println(notification.reportErrors)
else println(person)
FULL REPORT OF ERRORS
Validation Applicative
data Address =
Address {
getHouseNumber :: Int,
getStreetName :: String
} deriving (Eq, Show)
- House Number > 0
- Street Name not empty
type Result a = Validation [Error] a
makeAddress :: Int -> String -> Result Address
data Validation err a = Failure err | Success a
FULL REPORT OF ERRORS
Validation Applicative
numberGreaterThanZero :: Int -> Result Int
numberGreaterThanZero x
| x > 0 = Success x
| otherwise = Failure ["Number must be greater than zero!"]
notEmpty :: String -> Error -> Result String
notEmpty x m
| x == "" = Failure [m]
| otherwise = Success x
streetNameNotEmpty :: String -> Result String
streetNameNotEmpty x = notEmpty x "Street name must not be empty!"
data Validation err a = Failure err | Success a
type Result a = Validation [Error] a
FULL REPORT OF ERRORS
Validation Applicative
instance Functor (Validation a) where
fmap _ (Failure e) = Failure e
fmap f (Success x) = Success $ f x
makeAddress :: Int -> String -> Result Address
makeAddress x y = Address <$> numberGreaterThanZero x <*> streetNameNotEmpty y
import Data.Semigroup
instance Semigroup String where
x <> y = x ++ y
instance Semigroup err => Applicative (Validation err) where
pure = Success
(Success f) <*> (Failure e) = Failure e
(Failure e) <*> (Success x) = Failure e
(Failure e) <*> (Failure t) = Failure $ e <> t
(Success f) <*> (Success x) = Success $ f x
FULL REPORT OF ERRORS
Validation Applicative
data Address =
Address {
getHouseNumber :: Int,
getStreetName :: String
} deriving (Eq, Show)
- Street Name length <= 10
streetNameLength :: String -> Result String
streetNameLength x
| length x > 10 = Failure ["Name must not exceed 10 characters!"]
| otherwise = Success x
FULL REPORT OF ERRORS
Validation Applicative
instance Semigroup err => Semigroup (Validation err a) where
(Success x) <> (Success y) = Success y
(Failure x) <> (Failure y) = Failure (x <> y)
_ <> (Failure y) = Failure y
(Failure x) <> _ = Failure x
streetNameValidation :: String -> Result String
streetNameValidation x = streetNameNotEmpty x <> streetNameLength x
makeAddress' :: Int -> String -> Result Address
makeAddress' x y = Address <$> numberGreaterThanZero x <*> streetNameValidation y
FULL REPORT OF ERRORS
Validation Applicative
data Person =
Person {
getName :: String,
getAddress :: Address
} deriving (Eq, Show)
- Name not empty
makePerson :: String -> Result Address -> Result Person
nameNotEmpty :: String -> Result String
nameNotEmpty x = notEmpty x "Name must not be empty!"
makePerson n a = Person <$> (nameNotEmpty n) <*> a
FULL REPORT OF ERRORS
Validation Applicative
Summarising
- Validation type
- Semigroup instance
- Functor instance
- Applicative instance
- Some rules
Summarising
Validation typeSemigroup instanceFunctor instanceApplicative instance- Some rules
QUESTIONS?
Functional Data Validation
DANKJEWEL!!!
Functional Data Validation
By Gabriel Volpe
Functional Data Validation
Functional Data Validation in Haskell
- 3,080