The essence of programming is to encode, decode, and to manipulate data in a useful way. Therefore, there are ways to represent data based on different programming philosophies. In Functional Programming, data representation is usually in Algebraic Data Type.
Today, I would like to explain about two ways of representing ADT in Scala briefly. The first is sealed trait
and case class
. The other is not often discussed in describing data, but it is an alternative way of generalized data representation if you want to do more generalized programming.
A Visit to ADT (Algebraic Data Type)
Algebraic data types (ADT) is functional programming concepts with a fancy name for data representations using “ands” and “ors”.
In the ADT terms, “ands” are usually named as products, and “ors” are coproducts. We can also say coproducts as Sum Types. If you are interested in knowing more about ADT, check out my previous post about ADT.
Let’s look at an example of how you can represent data of the description below:
- A Payment Method can be a CreditCard or Paypal
- A CreditCard has a first four and last six
- A Paypal has a username and password
Representing with Case Class Sealed Trait
In Scala, we will represent the product as case class
and coproduct it as sealed trait
, which means “ands” with case class
and “ors” with sealed trait
.
sealed trait PaymentMethod
case class CreditCard(name:String, expiryDate:Instant) extends PaymentMethod
case class Paypal(username:String, password:String) extends PaymentMethod
The beauty of ADT is type safe. That means, the compiler has a complete knowledge on what the data type is, enabling us to write complete, correctly typed method, involving our types (basically easier to pattern match):
def derivePaymentMethod(paymentMethod:Payment): String = paymentMethod match {
case CreditCard(name, expiryDate) => s"Creditcard name ${name}"
case Paypal(username, password) => s"Paypal user $username"
}
Alternative Data Encoding
sealed trait
and case class
are undoubtedly the most convenient ways of representing data. One being it is easier to reason and understand, and it makes encoding data type-safe. However, there are other ways to encode data into ADT in Scala. In Scala standard library, we can represent the product as Tuples and coproduct as Either.
Let’s take an example from above:
type PaymentMethod2 = Either[CreditCard, Paypal]
type CreditCard = (String, Instant)
type Paypal = (String, String)
def derivePaymentMethod(paymentMethod: PaymentMethod): String = paymentMethod match {
case Right((username,password)) => s"Paypal user $username"
case Left((name, expiryDate)) => s"Creditcard name ${name}"
}
Using Tuples and Either is less readable than case class and sealed trait from the first example, but both have the same desirable properties.
However, PaymentMethod2 is more general than PaymentMethod. Any code that operates with a pair of String
and Instant
can also work with CreditCard and vice versa.
Conclusion
As a scala developer, we prefer to represent data in a semantics with case class and sealed trait instead of the generic ones Tuples and Either. However, in some cases, generic is desirable. For instance, if we want to serialize data into HTML components, we don’t care about a String or Paypal pair. We write the two numbers into an HTML format and be done with it. Generalized programming also helps to solve problems with a wide variety of types with little code, avoiding repetition between various types.
To learn more about Generalized programming with Shapeless.