This talk aims to be a gentle introduction to Refinement types in Scala. The talk will also introduce many related topics in the subculture of Typelevel programming in FP like Type theory, Generics (Shapeless), Optics (Monocle) and Law-based testing.
Why refinement types?
When 'String' just doesn't cut it anymore. By encoding validation into a type at the edge-of-the-system layer of your application, you can build your core domain logic without having to worry much about type-safety.
Who is it for?
Ajay would be revisiting the necessary foundations at the beginning of the talk, so beginners are welcome. People who have worked on Scala before would be able to appreciate the power of refined types more.
26. type HexDigit = Char Refined LetterOrDigit
type ThirtyTwoList[T] = List[T] Refined Size[32]
type GUID = ThirtyTwoList[HexDigit]
case class Request(guid: GUID)
val r1 = Request("2312k3j123...123dasd") // Compiles
val r2 = Request("asdas_asda_21#!@##$@#$...2234") // Does not compile
// At runtime
refineV[GUID]("asdasd2323...12312asd") // Either[String, GUID]
Typeserasedatcompiletimeleavingyouwithonlyprimitives
Atruntime,predicatescomputedonvaluesaswouldbethecasenormally
27. YoucandomuchmorewithRefined
Thelibrarycomeswiththesepredefinedpredicates
Boolean
True: constant predicate that is always true
False: constant predicate that is always false
Not[P]: negation of the predicate P
And[A, B]: conjunction of the predicates A and B
Or[A, B]: disjunction of the predicates A and B
Xor[A, B]: exclusive disjunction of the predicates A and B
Nand[A, B]: negated conjunction of the predicates A and B
Nor[A, B]: negated disjunction of the predicates A and B
AllOf[PS]: conjunction of all predicates in PS
AnyOf[PS]: disjunction of all predicates in PS
OneOf[PS]: exclusive disjunction of all predicates in PS
28. Char
Digit: checks if a Char is a digit
Letter: checks if a Char is a letter
LetterOrDigit: checks if a Char is a letter or digit
LowerCase: checks if a Char is a lower case character
UpperCase: checks if a Char is an upper case character
Whitespace: checks if a Char is white space
29. Collection
Contains[U]: checks if a Traversable contains a value equal to U
Count[PA, PC]: counts the number of elements in a Traversable which satisfy the predicate PA and passes the result to the predicate PC
Empty: checks if a Traversable is empty
NonEmpty: checks if a Traversable is not empty
Forall[P]: checks if the predicate P holds for all elements of a Traversable
Exists[P]: checks if the predicate P holds for some elements of a Traversable
Head[P]: checks if the predicate P holds for the first element of a Traversable
Index[N, P]: checks if the predicate P holds for the element at index N of a sequence
Init[P]: checks if the predicate P holds for all but the last element of a Traversable
Last[P]: checks if the predicate P holds for the last element of a Traversable
Tail[P]: checks if the predicate P holds for all but the first element of a Traversable
Size[P]: checks if the size of a Traversable satisfies the predicate P
MinSize[N]: checks if the size of a Traversable is greater than or equal to N
MaxSize[N]: checks if the size of a Traversable is less than or equal to N
31. Numeric
Less[N]: checks if a numeric value is less than N
LessEqual[N]: checks if a numeric value is less than or equal to N
Greater[N]: checks if a numeric value is greater than N
GreaterEqual[N]: checks if a numeric value is greater than or equal to N
Positive: checks if a numeric value is greater than zero
NonPositive: checks if a numeric value is zero or negative
Negative: checks if a numeric value is less than zero
NonNegative: checks if a numeric value is zero or positive
Interval.Open[L, H]: checks if a numeric value is in the interval (L, H)
Interval.OpenClosed[L, H]: checks if a numeric value is in the interval (L, H]
Interval.ClosedOpen[L, H]: checks if a numeric value is in the interval [L, H)
Interval.Closed[L, H]: checks if a numeric value is in the interval [L, H]
Modulo[N, O]: checks if an integral value modulo N is O
Divisible[N]: checks if an integral value is evenly divisible by N
NonDivisible[N]: checks if an integral value is not evenly divisible by N
Even: checks if an integral value is evenly divisible by 2
Odd: checks if an integral value is not evenly divisible by 2
NonNaN: checks if a floating-point number is not NaN
32. String
EndsWith[S]: checks if a String ends with the suffix S
IPv4: checks if a String is a valid IPv4
IPv6: checks if a String is a valid IPv6
MatchesRegex[S]: checks if a String matches the regular expression S
Regex: checks if a String is a valid regular expression
StartsWith[S]: checks if a String starts with the prefix S
Uri: checks if a String is a valid URI
Url: checks if a String is a valid URL
Uuid: checks if a String is a valid UUID
ValidByte: checks if a String is a parsable Byte
ValidShort: checks if a String is a parsable Short
ValidInt: checks if a String is a parsable Int
ValidLong: checks if a String is a parsable Long
ValidFloat: checks if a String is a parsable Float
ValidDouble: checks if a String is a parsable Double
ValidBigInt: checks if a String is a parsable BigInt
ValidBigDecimal: checks if a String is a parsable BigDecimal
Xml: checks if a String is well-formed XML
XPath: checks if a String is a valid XPath expression
Trimmed: checks if a String has no leading or trailing whitespace
HexStringSpec: checks if a String represents a hexadecimal number
35. trait Kilogram
trait Metres
type Weight = Double @@ Kilogram
type Height = Double @@ Metres
def getWeight(): Weight
def getHeight(): Height
// Units are already encoded in the type now
def bmi(weight: Weight, height: Height): Double
bmi(getWeight(), getHeight()) // OK
bmi(getHeight(), getWeight()) // Compilation error, just like we wanted
Noruntimecostoftypetags
MinoroverheadofTaggedOps
Coderemainssaneandself-documenting
Scalaintroducing NewType and Opaque and Transparent typestoScalasoon
enough,whereTaggingandZero-overheadencapsulatedconstructorswouldbecome
partofthestandardlibrary