
Published: 2021-01 (January 2021)
Relevant Java Version: Java 15.
Algebraic – a term that is often associated with school mathematics, and is commonly understood as a branch of mathematics which deals with symbols and rules for operating such symbols. The symbols usually represent quantities without fixed values (referred to as variables).
In programming languages, it carries a similar meaning. An algebraic data type is a composite containing variables. a composite can further contain other types as variables as well. A recursive type can contain another instance of itself as a variable. Algebraic refers to the property that an Algebraic Data Type is created by algebraic operations. The algebra discussed, is sums, products and patterns.
Algebraic data types were introduced in Hope, a small functional programming language developed in the 1970s at the University of Edinburgh.
Delving into Algebra
Sum Types
A sum type:
- represents alternation (for three values A, B, C → A or B or C but not any combination or other subset).
- defines variants
- is a logical OR operator, only one of the variants is possible.
Product Types
A product type:
- represents combination (for three values A, B, C → A and B and C, possible to hold empty for one or more).
- holds values
- is a logical AND operator
Pattern matching
Pattern matching is the check of a given sequence of tokens for presence of the constituents of some pattern. The match has to be exact without ambiguity, so has to evaluate to either is a match or is not a match.
What has algebra got to do with Java ?
Java has primitive as well as non-primitive data types.
Primitive: boolean, byte, char, short, int, long, float and double.
Non-Primitive: String, array, Object, composite objects etc.
This blog will cover some samples of algebraic or composite non-primitive data types in Java.
Algebra in Java
Sum types
Enum
Enumerations (enum
) are a special sum type. Enums cannot have additional data associated with them once instantiated.
Enums can have final attributes that can be set via constructors and can have methods defined that can access such final attributes.
An enum
can declare abstract methods which must then be implemented by each variant. Similarly, an enum
can implement an interface, but each variant must implement such an interface.
An enum
can be instantiated or is assignable via a static method Enum.valueOf(String)
. The valueOf()
accepts a String instance and matches it to the declared enum variant.
Pattern matching for
The following pattern matching can be used in identifying enum instances.enum
•Class.isEnum()
– Works forenum
without any body (no methods and extra attributes).
•object instanceof Enum
A regularinstanceof
check againstjava.lang.Enum
.
•Enum.class.isAssignableFrom(object.getClass())
– Using theClass.isAssignableFrom()
matching.
Optional
A java.util.Optional
allows two variants. The optional contains either a value of the specified generic or an empty.
Using Optional
correctly, prevents the dreaded NullPointerException
.
Additionally, an Optional
guarantees that the consumer of the object will always receive an object and can act upon either the contained non-empty value, if present or handle the lack of value.
Read more about Optional
at my Java Optional blog.
Pattern matching for
The following pattern matching can be used in identifying Optional instances.Optional
•object instanceof Optional
A regularinstanceof
check againstjava.util.Optional
.
•Optional.class.isAssignableFrom(object.getClass())
– Using theClass.isAssignableFrom()
matching.
Sealed types
Java has had final classes and non-final (open, abstract) classes forever. These were two extremes in terms of inheritance. Limiting inheritance and extension to a finite set of was not very easy prior to the recent introduction of sealed
types. Sealed types were introduced in Java 15.
A sealed
type (class or interface) permits
finite extensions or implementations while preventing any others not listed in the permits
clause.
The permitted types can be either non-sealed
or final
(or can also be a record
, more on this later in the blog). A non-sealed
type implies it is open to extension.
Pattern matching for
The following pattern matching can be used in identifying Optional instances.sealed
types
•object instanceof
<Class> A regularinstanceof
check against the class ancestry for the instance.
• <Class>.class.isAssignableFrom(object.getClass())
– Using theClass.isAssignableFrom()
matching.
Product Types
Class
A regular Java POJO (Plain Old Java Object) class is considered to be a product type. It is a composite which allows for attributes that are grouped together.
Pattern matching for POJO types
The following pattern matching can be used in identifying Optional instances.
•object instanceof
<Class> A regularinstanceof
check against the class ancestry for the instance.
• <Class>.class.isAssignableFrom(object.getClass())
– Using theClass.isAssignableFrom()
matching.
Tuple
A special POJO class extending what a Class offers. Tuples currently are not included in the Java, except maybe, the ephemeral Map.Entry
(API) that is available while iterating over Map
instances. Tuples can include Unit, Pair, Twin, Triple etc. Tuples are useful when using collections.
Pattern matching for Tuple types
The following pattern matching can be used in identifying Optional instances.
•object instanceof
<Class> A regularinstanceof
check against the class ancestry for the instance.
• <Class>.class.isAssignableFrom(object.getClass())
– Using theClass.isAssignableFrom()
matching.
Record
A record
is an immutable data object introduced in Java 14.
A record can be declared with just its attributes. An all-attribute constructor and accessors (getters) for each attribute are synthetically generated. The accessors do not have a get
prefix normally used in POJOs. The name of the attribute is the same as the name of the accessor for the attribute. Mutator (setter) methods are not allowed on a record.
A record
also allows for a Compact Constructor. This constructor is exactly the same as an all-attribute constructor without have to list them in the constructor signature. A record
can use the compact constructor to validate / enforce rules during instantiation.
A record can implement an interface. A record
works well with sealed
types. A sealed
type can permit records.
Starting Java 15, a local record
can be created within a method. This limits the scope of the record to within the said method.
Pattern matching for
The following pattern matching can be used in identifying Optional instances.record
types
•object.getClass().isRecord()
Using theClass.isRecord()
.
•object instanceof Record
A regularinstanceof
check againstjava.lang.Record
.
•Record.class.isAssignableFrom(object.getClass())
– Using theClass.isAssignableFrom()
matching.
Summary
We touched upon the few Algebraic Data Types in Java. There is a lot more to discuss. This includes Catamorphism, Homomorphism and Anamorphism. The next blog will include these. Additionally we will look into the Visitor Pattern, Variances (Invariance, Covariance and Contravariance).
Further, there is excellent reading material available online. Here are a few links to read more:
- Brian Goetz’s thoughts on Datum
- Wikipedia Article on Algebraic Data Types
- Brian Goetz’s Thoughts on Pattern Matching
- Wikipedia Article on Pattern Matching
- Wikipedia Article on Catamorphism
Thanks for reading !!!
#CommunityFIRST #SharingIsCaring #OpenSourceFUN