Skip to main content

4 posts tagged with "Scala"

View All Tags

· 16 min read

I’ve spent several years working heavily with Scala. I’ve particularly liked how it provides type-safe support of higher-kinded types. These types let you operate on generic shapes containing other shapes. Lists, asynchronous types, and optional types are all examples of types that can be operated on in a higher abstraction. I love working with Scala, but it is losing a ton of popularity and now feels like the right time to redirect my focus to another language ecosystem.

JavaScript is a language which I doubt will lose any popularity in the next 20 years. Everyone needs JavaScript (whether they like it or not) to do just about anything on the web and it’s gained a lot of traction for building all sorts of applications beyond just web pages. A quick tangent, but I’d really like to have more long-term job stability from the languages that I spend a lot of time with. It’s true that Scala is a great language that helped me build some amazing projects. It’s also true that I won’t be getting a Scala job anytime in the near future. Bummer!

In Scala I can write a function which operates on some generic type containing a single type “hole”. A list of items fits this description nicely. The list itself is a collection containing items of some other type. Most strictly-typed languages let you work on types with generic customizations, like lists containing a generic element type. But Scala takes this idea a step further. Instead of abstracting over the inner components of a type, you can abstract over the shape of the type itself. For example, I might want to change the value of every single element of a particular collection. Like dividing every element by two or appending a newline character to every string. This functionality can be written without references to the collection types themselves in Scala using the following code:

import cats.implicits._

def add1ToAll[F[_]](stuff: F[Int])(using Monad[F]): F[Int] =
stuff.map(_ + 1)

add1ToAll(List(1, 2, 3, 4)) // List(2, 3, 4, 5)

Even better, if I want to work on any number type that has the ability to add 1:

import cats.implicits._

def add1ToAll[F[_], N](stuff: F[N])(using N: Number[N]): F[N] =
stuff.map(n => N.add(n, 1))

add1ToAll(List(1, 2, 3, 4), _ + _) // List(2, 3, 4, 5)
add1ToAll(List(1L, 2L, 3L, 4L, _ + _) // List(2L, 3L, 4L, 5L)

In Scala, this is pretty standard stuff. You don’t need to be too familiar with any of the code above to understand the general idea of what’s happening: I have a type named “F” which has a characteristic called “Monad” (allows modifying inner elements) and I’m changing every element the “stuff” has access to by increasing the value by one.

This add1ToAll function could now be applied to any type which contains number elements: asynchronous processes, lists, dictionaries with string keys, optional values, and much more.

Most languages don’t have support for defining the types that are being used in these functions. As far as I know, Scala and Haskell are the only two with really good support for higher-kinded type programming. However, there’s nothing stopping us from building this functionality in other languages, especially in JavaScript where well-defined “types” don’t actually exist at all.

This lead me to writing a small module I named “veggies” (a reference to a blog I read a long time ago Write code. Not too much. Mostly functions.). This module contains some useful tools for writing “generic” code without needing to know specific names or fields for every supported type.

To start, I made some basic helper functions that I knew would come in handy. Feel free to skip over these:

// This is needed for common nullability checks.
function empty(value) {
return value === null || value === undefined;
}

// I'll need this later on, checks for an array being an array and non-empty
function nonemptyArray(value) {
return !empty(value) && !empty(value.length) && value.length > 0;
}

// I'll be passing around a lot of functions. It's always
// nice to know a function is a function before I try to call it.
function isFn(value) {
return typeof value === "function";
}

A tool I always relied on while writing Scala code is the Either type. At its core, Either let me define values that can be in one of two different states: Left or Right. Most often, I used it to represent a result that could be successful or raise an error. I want a similar type in JavaScript. Since JavaScript doesn’t provide a union type in its standard toolbox, I have to rely on the concept of “tagged unions” for defining my Result type.

function ok(ok) {
return {
ok: ok,
};
}

function isOK(value) {
return !empty(value) && value.ok !== undefined;
}

function error(error) {
return {
error: error,
};
}

function isError(value) {
return !empty(value) && value.error !== undefined;
}

Now whenever I want to return an error from a function, I can wrap it in an error function to allow my upstream user to decide how to handle it later on. This might seem convoluted compared to using exceptions and try-catch mechanics, but it’s a useful pattern to start using as I’ll begin to compose multiple operations together. The other important part of this structure is that I’ve now created a type that fills the “single generic hole” characteristic. I’ll refer to this type going forward as a “monad”. This is a mathematical and unnecessarily fancy term, but it’s short and easy to reference. A monad provides two important functionalities: values can be wrapped in the monad’s context (create a list from a single value, for example) and a monad can be operated on using a function which returns another value in the monad context (start with a result type and perform a second step which also returns a result). Just about every post I have on my site contains me thinking through what monads are and how to explain them in different ways. Definitely check out my post from a while back talking about type classes and other fun stuff at Typeclasses and Ad-Hoc Polymorphism.

The result type I’ve defined above is a monad because values can always become results (wrap the value in ok()) and results can be operated on using an additional function (if the first result is ok then run another function returning a result by using the .ok field as the argument). These two functions provide a surprising amount of utility and extensibility in our functional code when composed together with other functions.

To add support for using monads, I’ll first write a function which wraps values of any kind in a monad context. I’m going to call this function apply because it “applies” the monad context to the value.

// listing all the possible errors that could occur
// these can be referenced by function callers to see what error occurred
const applyErrors = {
emptyTypeclassInstance:
"Expected 'monadTypeclass' argument to 'apply' to be non-empty.",
emptyValue: "Expected 'value' argument for 'apply' to be non-empty.",
notMonadInstance: (keys) => {
const keysJson = JSON.stringify(
keys,
);
return `Expected 'monadTypeclass' argument to 'apply' to be a Monad typeclass (needs 'apply' and 'flatMap' functions). Got an object with keys '${keysJson}'.`;
},
};

// defines the generic function that can wrap any value into any monad instance
export function apply(monadTypeclass, value) {
if (empty(monadTypeclass)) {
throw new Error(applyErrors.emptyTypeclassInstance);
}

if (empty(value)) {
throw new Error(applyErrors.emptyValue);
}

const applyFn = monadTypeclass.apply;

if (empty(applyFn) || !isFn(applyFn)) {
throw new Error(applyErrors.notMonadInstance(Object.keys(monadTypeclass)));
}

return applyFn(value);
}

I wrote a lot of validation logic since JavaScript gives me no guarantees on what kind of arguments I might receive when this function is called. It’s a trade-off of JavaScript, but something I can work with. Adding all the checks ensures that the actual functionality will be called using the arguments and shapes I’d expect: an instance of a monad type class and a value.

I chose to have each block in the validation steps throw an exception instead of return the error type. This makes it a lot easier to chain together steps that are likely to be successful. If I returned the Result monad from my function instead, I’d end up with a lot of nested types, which leads to a lot of confusion and type errors.

Another common type in Scala is the Option type. An Option encodes the behavior of a value existing or not existing. Basically a type-safe version of null.

The implementation in JavaScript using “tagged unions” looks a lot like the Result implementation. I’ll even add a function which converts from Result to Option.

function some(some) {
return {
some: some,
};
}

function isSome(value) {
return !empty(value) && value.some !== undefined;
}

const none = { none: null };

function isNone(value) {
return !empty(value) && value.none === null;
}

function option(value) {
if (empty(value)) {
return none;
} else {
return some(value);
}
}

function getOrElse(value, alt) {
if (isSome(value)) {
return value.some;
}

return alt;
}

// converts from a Result type to an option type
// uses Some when the Result is OK
function okOption(value) {
if (isOK(value)) {
return some(value.ok);
}

return none;
}

// converts from a Result type to an option type
// uses Some when the Result is Error
function errorOption(value) {
if (isError(value)) {
return some(value.error);
}

return none;
}

This type is a more “safe” alternative to using null values and provides many of the same usages of null. If a field is optional, it can be referenced using the safe Option functions.

With the useful Option functions available, I’ll now go back to expanding on my monad functions with two new additions: flatMap and map.

const flatMapErrors = {
emptyTypeclassInstance:
"Expected 'monadTypeclass' argument to 'flatMap' to be non-empty.",
emptyValue: "Expected 'value' argument for 'flatMap' to be non-empty.",
emptyFunction: "Expected 'fn' argument for 'flatMap' to be non-empty.",
invalidFunction: "Expected 'fn' argument for 'flatMap' to be a function.",
notMonadInstance: (keys) => {
const keysJson = JSON.stringify(
keys,
);
return `Expected 'monadTypeclass' argument to 'flatMap' to be a Monad typeclass (needs 'apply' and 'flatMap' functions). Got an object with keys '${keysJson}'.`;
},
};

function flatMap(value, monadTypeclass, fn) {
if (empty(monadTypeclass)) {
throw new Error(flatMapErrors.emptyTypeclassInstance);
}

if (empty(value)) {
throw new Error(flatMapErrors.emptyValue);
}

if (empty(fn)) {
throw new Error(flatMapErrors.emptyFunction);
}

if (!isFn(fn)) {
throw new Error(flatMapErrors.invalidFunction);
}

const flatMapFn = monadTypeclass.flatMap;

if (empty(flatMapFn) || !isFn(flatMapFn)) {
throw new Error(
flatMapErrors.notMonadInstance(Object.keys(monadTypeclass)),
);
}

return flatMapFn(value, fn);
}

const mapErrors = {
emptyValue: "Expected 'value' argument for 'map' to be non-empty.",
emptyTypeclassInstance:
"Expected 'monadTypeclass' argument to 'map' to be non-empty.",
emptyFunction: "Expected 'fn' argument for 'map' to be non-empty.",
notMonadInstance: (keys) => {
const keysJson = JSON.stringify(
keys,
);
return `Expected 'monadTypeclass' argument to 'map' to be a Monad typeclass (needs 'apply' and 'flatMap' functions). Got an object with keys '${keysJson}'.`;
},
};

function map(value, monadTypeclass, fn) {
if (empty(value)) {
throw new Error(mapErrors.emptyValue);
}

if (empty(monadTypeclass)) {
throw new Error(mapErrors.emptyTypeclassInstance);
}

if (empty(fn)) {
throw new Error(mapErrors.emptyFunction);
}

const applyFn = monadTypeclass.apply;
const flatMapFn = monadTypeclass.flatMap;

if (empty(applyFn) || empty(flatMapFn) || !isFn(applyFn) || !isFn(flatMapFn)) {
throw new Error(mapErrors.notMonadInstance(Object.keys(monadTypeclass)));
}

const output = flatMapFn(
value,
(x) => applyFn(fn(x)),
);

return output;
}

The flatMap implementation follows the pattern I used when implementing the general apply method. I first do a bunch of validation and eventually call into the flatMap defined on the provided monad object.

The map implementation is a bit more interesting, not much, but there’s something extra going on. I’m first calling the function the user provides for modifying the value in the context, then I’m lifting that value into the context itself. This is all happening inside the monad context by using the monad’s flatMap function. If this function was operating on a list, you could think of it as first separating all the elements, then changing each one, then making a bunch of tiny one-element lists, and finally gluing them all back together.

Finally, I’m going to add two additional composed methods called flatten and foreach. The flatten function lets me combine nested structures together, like a list of list, or a result containing a result. The foreach function acts a lot like the foreach method on JavaScript arrays, but it works on Result and Option types as well!

const flattenErrors = {
emptyValue: "Expected 'value' argument for 'flatten' to be non-empty.",
emptyMonadInstance:
"Expected 'monadTypeclass' argument for 'flatten' to be non-empty.",
notMonadInstance: (keys) => {
const keysJson = JSON.stringify(
keys,
);
return `Expected 'monadTypeclass' argument to 'flatten' to be a Monad typeclass (needs 'apply' and 'flatMap' functions). Got an object with keys '${keysJson}'.`;
},
};

function flatten(value, monadTypeclass) {
if (empty(value)) {
throw new Error(flattenErrors.emptyValue);
}

if (empty(monadTypeclass)) {
throw new Error(flattenErrors.emptyMonadInstance);
}

const flatMapFn = monadTypeclass.flatMap;

if (empty(flatMapFn) || !isFn(flatMapFn)) {
throw new Error(
flattenErrors.notMonadInstance(Object.keys(monadTypeclass)),
);
}

return flatMapFn(value, (x) => x);
}

const foreachErrors = {
emptyValue:
"Expected 'value' argument to 'foreach' function to be non-empty.",
emptyMonadInstance:
"Expected 'monadTypeclass' argument to 'foreach' function to be non-empty.",
emptyFunction:
"Expected 'fn' argument to 'foreach' function to be non-empty.",
invalidFunction: (got) => {
const gotText = JSON.stringify(got);
return `Expected 'fn' argument to 'foreach' function to be a function type. Got '${gotText}'.`;
},
invalidMonadTypeclass: (keys) => {
const keysText = JSON.stringify(keys);

return `Expected Monad typeclass instance argument for 'foreach' function to have function keys 'apply' and 'flatMap'. Got object with '${keysText}'.`;
},
};

function foreach(value, monadTypeclass, fn) {
if (empty(value)) {
throw new Error(foreachErrors.emptyValue);
}

if (empty(monadTypeclass)) {
throw new Error(foreachErrors.emptyMonadInstance);
}

if (empty(fn)) {
throw new Error(foreachErrors.emptyFunction);
}

if (!isFn(fn)) {
throw new Error(foreachErrors.invalidFunction);
}

const flatMapFn = monadTypeclass.flatMap;
const applyFn = monadTypeclass.apply;

if (
empty(flatMapFn) || empty(applyFn) || !isFn(flatMapFn) || !isFn(applyFn)
) {
throw new Error(
foreachErrors.invalidMonadTypeclass(Object.keys(monadTypeclass)),
);
}

return flatMapFn(
value,
(x) => {
fn(x);
return applyFn({});
},
);
}

The flatten function only needs access to the Monad’s flatMap function, and instead of changing the inner elements using flatMap, we are just returning the initial value. Using our list example once more, this would be like separating the list out into individual elements and gluing these individual elements all together. Since the individual elements are all lists themselves, they can be glued together without any additional changes.

The foreach function operates a lot like map, but the result of executing the provided function is discarded instead of returned. This function is particularly nice when the current state should only be logged if a value in an Option type exists.

Now this is all neat and fun, but how can these functions actually improve my code? Well first off, I’ll need to make Monad instances for the types I’m interested in: Result and Option.

// list possible errors that could happen when trying to flatMap a result
const resultFlatMapErrors = {
invalidResultInput: (x) => {
const inputJSON = JSON.stringify(x);
return `Expected 'value' argument for Result 'flatMap' function to be a Result type (ok or error keys). Got '${inputJSON}'.`;
},
};

const resultMonad = {
apply: (x) => ok(x),
flatMap: (x, fn) => {
if (isOK(x)) {
const output = fn(x.ok);
return output;
} else if (isError(x)) {
return x;
} else {
return error(resultFlatMapErrors.invalidResultInput(x));
}
},
};

const optionMonad = {
apply: (x) => some(x),
flatMap: (x, fn) => {
if (isSome(x)) {
return fn(x.some);
} else {
return none;
}
},
};

These two instances define the logic I talked about a little earlier: general values need to be able to be pulled into a context and a mechanism must be available for changing the elements in the context.

With these two instances available, I can put together an example project! I’ll use a contrived example of a spaceship launch system. The launch system has two state validation steps. Both of these steps returns a Result and the launch is successful only when there are no errors with the validations.

function validateFuel(ship) {
if (empty(ship)) {
return error("No ship information provided!");
}

if (empty(ship.fuel)) {
return error("No ship fuel information provided!");
}

if (ship.fuel < 20) {
return error("Ship fuel is critically low!");
}

return ok(ship.fuel);
}

function validateCrew(ship, needs) {
if (empty(ship)) {
return error("No ship information provided!");
}

if (empty(ship.crew)) {
return error("No ship crew information provided!");
}

const needsOption = option(needs);

const neededCrew = getOrElse(needsOption, 0);

if (ship.crew < neededCrew) {
return error(
`Not enough crew! Need ${neededCrew} members. Only have ${ship.crew}.`,
);
}

return ok(ship.crew);
}

const ship = {
fuel: 100,
crew: 50,
};

const neededCrew = 30;

const fuelValid = validateFuel(ship);
const fuelError = errorOption(fuelValid);
const crewValid = validateCrew(ship, neededCrew);
const crewError = errorOption(crewValid);

foreach(
fuelError,
optionMonad,
(error) => {
console.log("Invalid fuel. Can't launch. " + error);
},
);

foreach(
crewError,
optionMonad,
(error) => {
console.log("Invalid crew! Can't launch. " + error);
},
);

if (isSome(fuelError) || isSome(crewError)) {
console.log("Aborting launch!");
} else {
flatMap(
fuelValid,
resultMonad,
(fuel) =>
flatMap(
crewValid,
resultMonad,
(crew) => {
console.log(`Fuel at ${fuel}%.`);
console.log(`${crew} crew members on board.`);
console.log("Go for launch!");
return ok({});
},
),
);
}

Tweaking the ship state and neededCrew number alters the results and logging messages. With the current values, the following will be logged:

Fuel at 100%.
50 crew members on board.
Go for launch!

When the fuel level is changed to 10%:

Invalid fuel. Can't launch. Ship fuel is critically low!
Aborting launch!

And finally, when the needed crew is set to 100:

Invalid fuel. Can't launch. Ship fuel is critically low!
Invalid crew! Can't launch. Not enough crew! Need 100 members. Only have 50.
Aborting launch!

It works! It’s alive!

This is a really great start and there’s even more that I could do with this foundation. I could add a function combine which takes two monad values of the same type and combines them together into a list within the same monad context. This would allow composing two different values together before performing a step. I could also add a function ifElse which performs one of two branches based on a boolean value stored in a monad context. The sky is really the limit from here! I might even explore adding additional type-classes, like Traverse, Semigroup, or Recursion which could be combined with Monad to implement even more behaviors. And of course, nesting flatMap operations gets pretty ugly. It would be nice to be able to chain them together somehow!

As always, thanks for reading and I’ll see you next time!

· 14 min read

A quick-start guide to the Scala 3 programming language.

This guide won’t include steps to set-up a Scala build environment locally, so I encourage you to follow along using Scastie, an online Scala integrated development environment (IDE). Scastie makes it easy to add library dependencies and get code written and tested quickly. Scala can be a hassle to install successfully and I’d much prefer to get you right into coding. If you are interested in setting up a Scala environment then I’ll have some helpful resources at the bottom of the page.

Functions

Functions let us define re-usable blocks of expressions and statements that can be modified using parameters. We can define functions in two ways: using the def or val keywords. A function definition contains four components: the name, the parameters, the return type, and the body.

The following snippet contains an example function defined in either style.

def myFunction(parameter1: String, parameter2: Int): Boolean =
parameter1 == "example" || parameter2 == 100

// or
val myFunctionValue: (String, Int) => Boolean = (parameter1, parameter2) =>
parameter1 == "example" || parameter2 == 100
// myFunctionValue: Function2[String, Int, Boolean] = repl.MdocSession$MdocApp$$Lambda$8832/1270295766@3983def3

All function parameters must have a provided type like String and Int for parameter1 and parameter2 above. Although not required, the return type should also be provided, like Boolean.

In general, the def syntax is preferred because it’s less verbose, but you’ll find reasons to use either one.

The // syntax from the snippet means that any comments on the same line and to the right are not compiled as Scala code.

Higher Order Functions

Functions are also values and can be provided to other functions as parameters and to types as fields. Function values can be called the same way you would a function defined with def or val.

def combineStrings(myList: List[String]): String =
myList.mkString(" ")

def splitAndApply(input: String, splitOn: String, f: List[String] => String): String =
f(input.split(splitOn).toList)

splitAndApply("hello/world", "/", combineStrings) // "hello world"
// res0: String = "hello world"

This example defines a function combineStrings that places spaces between each text in the provided list myList. Another function named splitAndApply will split the given input text any time the given splitOn text appears in input. After splitting, the new list of split phrases is provided to the given function f which determines how to re-join the split phrases. Finally, splitAndApply is called with an example text hello/world and the splitting symbol /. The function combineStrings is provided as a value to the splitAndApply method to combine the split phrases using a space. Altogether, this example yields the text hello world.

Types

Types can be defined in several ways depending on the use-case. I'll go over the basic constructs you'll want to use most-often and leave the rest for you to find as you need them.

Standard Library Types And Collections

The following types are automatically provided by the standard library and can be used without additional imports. There’s many more than this list available to you, but these are a good place to start.

  • Int — a number negative or positive
  • Booleantrue or false
  • String — a list of characters representing text, like “carrots”
  • Double — a double-precision floating point number, something like 101.111
  • Option — an optional value with two cases Some(value) and None
  • Either — a value with two cases Left and Right with types for each case
  • List — a collection of items with a provided type, instantiated with the syntax List(1, 2, 3, 4)
  • Map — a collection of one-to-one key and value pairs, instantiated using a special function/syntax Map("a" -> 1, "b" -> 2")

Case Classes

In general, the case class should be your go-to type for constructing data. A case class can be used to define an immutable type with automatic pattern matching built-in and several other goodies.

case class Videogame(title: String, year: Int, platforms: List[String])

This snippet creates a new type called Videogame with the fields title, year, and platforms. Each field must have a type assigned to it. A new instance of Videogame can be created with the format of Videogame(”Mario Kart 8 Deluxe”, 2014, List(”Switch”)).

Although case classes can contain related functionality in the form of functions and other values, it is best to avoid adding logic beyond it’s basic fields if possible. In functional programming, types should be independent of the logic that can be used to operate on them. However, there is sometimes logic that is vital to the domain of the type regardless of context. When you have a function that will almost always be called on a type when the type is used, place it in the body of the case class.

For example, the following code contains another case class with a method.

case class Car(make: String, model: String) {
def show: String = s"Car { make: $make, model: $model } "
}

The method can then conveniently be called using the code Car("Toyota”, “GR86”).show yielding a nice visual representation “Car { make: Toyota, model: GR86 }”. The s operator used in the snippet is a little bit of magic called string interpolation that is commonly used for formatting text.

Opaque Types

Often it’s convenient to use types from the standard library and dependency libraries directly in your code. For example, String, List, and Map. However, there is a downside. Your library or app code now has a direct dependency on that type and can’t be changed. I encourage you instead to try and use the opaque type. Opaque types let you mask an existing type with your own symbol, thus allowing you to modify the underlying type implementation without affecting your provided interface.

For example, the following snippet contains an opaque type representing a list of names. The only functions I’ve provided for this new type are getLastNames, getFirstNames, and show. If in the future I decide that I don’t want to use a list of tuples to represent my names, then clients of my library won’t be affected (including my own downstream code).

opaque type Names = List[(String, String)]

def firstNames(names: Names): List[String] =
names.map(_._1)

def lastNames(names: Names): List[String] =
names.map(_._2)

def show(names: Names): String =
names
.map(name => s"${name._2}, ${name._1}")
.mkString("\n")

It may be that in the future I’ve decided to change the type of Names from List[(String, String)] to List[Name] where Name is defined as case class Name(first: String, second: String). Downstream code will not see any impact from the underlying type change.

Sealed Traits

Complex union abstract data types can be created using the sealed trait syntax. A sealed trait defines the root of a type tree with a finite number of cases. An example of a union type common to every programming language is the Boolean type. The following code defines a custom Boolean2 tree. I've added a 2 post-fix to the type definitions to avoid colliding with the standard library Boolean type.

sealed trait Boolean2
case class True() extends Boolean2
case class False() extends Boolean2

val trueValue: Boolean2 = True()
// trueValue: Boolean2 = True()
val falseValue: Boolean2 = False()
// falseValue: Boolean2 = False()

Many types you encounter will be defined using the sealed trait with case class structure. Here’s another example, a functional linked list. As in the union example, I've added a 2 post-fix to all the types to avoid type collisions with the List type from the standard library.

sealed trait List2[A]
case class Empty2[A]() extends List2[A]
case class NonEmpty2[A](head: A, tail: List2[A]) extends List2[A]

val emptyList: List2[Int] = Empty2()
// emptyList: List2[Int] = Empty2()
val nonEmptyList: List2[Int] = NonEmpty2(1, NonEmpty2(2, Empty2()))
// nonEmptyList: List2[Int] = NonEmpty2(
// head = 1,
// tail = NonEmpty2(head = 2, tail = Empty2())
// )

This List2 snippet defines a list that can have two cases:

  • Empty2 — the list is empty and contains no values
  • NonEmpty2 — the list has at least one item (the head) and may contain more items (the tail)
    • The tail of the NonEmpty list can then either be Empty or NonEmpty. And the pattern repeats itself.

Enums

Another way to define a union type is as an enum. Enums are especially useful when you have a simple union type with no recursion. The Boolean2 example from earlier can be re-written in a simpler form using enum.

enum BooleanAsEnum {
case True()
case False()
}

The only difference between the enum and sealed trait syntax is that enum types can’t be used to define higher-order types like List and are intended only for simple type enumerations. In general, it’s best practice to use enums unless you need your root type to contain type parameters like List.

Companion Objects

A companion object is a singleton type containing helpful operations to perform on the accompanying type. The best functions for a companion object are constructors that create new instances of the accompanying type, often with some domain validation. These constructors can have any name you’d like, but common names are make or create. There is also a special companion function apply that can be defined and called as if instantiating the type directly.

object Videogame {
def make(title: String, year: Int, platforms: List[String]): Option[Videogame] =
if title.isEmpty then None
else Some(Videogame(title, year, platforms))

def apply(title: String, year: Int): Videogame =
Videogame(title, year, List.empty[String])
}

// `make` function now available on `Videogame` type
val granTurismo: Option[Videogame] = Videogame.make("Gran Turismo 7", 2022, List("PS5"))
// granTurismo: Option[Videogame] = Some(
// value = Videogame(
// title = "Gran Turismo 7",
// year = 2022,
// platforms = List("PS5")
// )
// )

val unreleased: Option[Videogame] = Videogame.make("", 2025, List.empty[String])
// unreleased: Option[Videogame] = None

// equivalent to Videogame.apply
val diablo: Videogame = Videogame("Diablo", 2023)
// diablo: Videogame = Videogame(
// title = "Diablo",
// year = 2023,
// platforms = List()
// )

The above snippet defines two additional constructors to the Videogame type, make and apply. The make constructor performs some additional validation on the provided parameters and returns a None value if the input is invalid. The apply method lets callers provide just the title and year of the videogame and provides a default of List.empty[String] for the platforms field. Providing additional apply functions in a companion object can be a convenient way to provide common defaults for users. In general, the apply method should always return just the companion type and not any validation-related type like Option or Either.

Pattern Matching

Values can be deconstructed and matched using the match and case syntax. Matching can be especially helpful for determining the particular union or enumeration sub-case that a value represents. For example, we can match on our List2 snippet to perform a different step depending on if the list is empty or contains items.

def isEmpty[A](list: List2[A]): Boolean =
list match {
case Empty2() => true
case NonEmpty2(_, _) => false
}

In the above snippet, we use the _ syntax to ignore particular values of the match. We can also provide local names for fields of the matched case.

def sum(list: List2[Int]): Int =
list match {
case Empty2() => 0
case NonEmpty2(first, remaining) => first + sum(remaining)
}

This snippet uses pattern matching to define a recursive function which calculates the sum of the list. If the list contains items, we add the value of the first item to the sum of the remaining items. If the list is empty, then we default to the value 0.

Typeclasses

Typeclasses define a set of standard functionality for a category of types. For example, you may want a function that operates on any collection of items without needing duplicated logic for each collection that users need. This example is common in functional programming, but is more in-depth than what we’ve seen so far.

trait Collection[F[_]] {
extension [A](collection: F[A])
def modifyItems[B](f: A => B): F[B]
}

given Collection[List] with
extension [A](list: List[A])
def modifyItems[B](f: A => B): List[B] = list.map(f)

given Collection[Option] with
extension [A](option: Option[A])
def modifyItems[B](f: A => B): Option[B] =
option match {
case Some(item) => Some(f(item))
case None => None
}

extension [F[_]](collection: F[Int])(using Collection[F])
def addOneToAllItems: F[Int] =
collection.modifyItems((item: Int) => item + 1)

List(1, 2, 3).addOneToAllItems
// res1: List[Int] = List(2, 3, 4)

extension (option: Option[Int])
def addOne: Option[Int] = option.addOneToAllItems

Some(1).addOne
// res2: Option[Int] = Some(value = 2)
None.addOne
// res3: Option[Int] = None

The Collection typeclass above requires that implementations provide the logic for the modifyItems function. The F[_] syntax specifies that overriding types must have a single type parameter, where the _ is a placeholder for the collection’s item type. The placeholder F type can then be used when defining the required functions for Collection instances. The extension syntax is used to define functions on types that can be used as if they were defined on the types themselves. Any type with a Collection instance in scope automatically has the modifyItems function available. In the case of the snippet above, we could write List(1, 2, 3, 4).modifyItems(num ⇒ num + 2) to update the list to contain all elements with 2 added.

In order to use the Collection syntax on various types, typeclass instances need to be defined using given … with syntax. Instances have been defined in the snippet for List and Option types. Although they are very different types with different shapes, they both contain collections of items. The case of a List might be more clear than with Option, but an Option is a collection of zero or one item. If the Option contains no item (None) then the item doesn’t need to be transformed by the modifyItems function. If it contains a single item (Some) then it will be modified by the function value provided to modifyItems.

Finally, the addOneToAllItems function extends any type with a Collection instance to include the function as a new method on any values of the collection type. With Collection instances available for both List and Option, we can call addOneToAllItems on both types without needing to explicitly define the extensions for both List and Option. I've defined a special extension method addOne for the Option[Int] type because typeclasses are only available for the specific types that have instances. The Collection typeclass must be invoked with the Option trait instead of using the Some and None sub-types.

Typeclasses are powerful tools for abstracting common and repetitious operations to many different kinds of types. Even a small typeclass with one function, like Collection, can be used as a base to build more complex functions across many types.

Additional Resources

This post has only begun to scratch the surface of the syntax and structures available in Scala programs, though the ones I’ve gone through provide a strong platform that should allow you to write many types of programs and applications. I recommend tinkering with the structures discussed here first and then expanding your knowledge using the following resources as you wish to do more in your programs.

Scala-Lang Docs

The official Scala-Lang documentation site contains everything from this post and more.

SDK-Man

Manages JDK instances on your computer and allows changing JDKs on the fly. There are many versions and distributions available, but any of the v17 JDKs are good choices for basic programs.

Rock The JVM

Rock The JVM is an amazing set of tutorials and guides to learning pieces of the Scala language. I learned most of my fundamental knowledge on Scala from Daniel Ciocirlan's courses.

This Week In Scala

A weekly newsletter rounding up all the new library improvements, releases, and news from the week. The newsletter is a good way to keep up to date with what’s happening in the community.

· 6 min read

This is my third or fourth iteration of a blog site. I've tried a custom Angular app, Jekyll, Hugo, and now Mdoc + Docusaurus.

I've been writing Scala and JS for a few years now, so I'm a lot more comfortable with the tooling this time around (sorry Ruby) and feel confident enough to explain the project. There are probably very few people who would choose SBT for building a blog, but maybe this can help out a fellow Scala dev with their own site.

For setting up the blog site, I used the following versions of tools:

  • SBT 1.7.2
  • Scala 2.13.10
  • Mdoc 2.3.6
  • Docusaurus 2.2.0
  • Node 18.9.0
  • Yarn 1.22.19

Configuring SBT

If you aren't familiar with SBT, or even Scala, I would recommend trying out a basic Scala project first before diving in here. The Scala site has a good Getting Started page with some helpful initial resources.

SBT is a modular and extensible build tool, designed for compiling JVM projects (Scala specifically), but suitable for just about any project (even compiling a blog).

I've configured my build.sbt to contain a module for compiling my blog posts and a root module for project aggregation.

lazy val `posts-mdoc` = project // name something other than 'posts' or 'docs'
.enablePlugins(MdocPlugin, DocusaurusPlugin) // I'll explain these in a bit
.settings(
mdocIn := file("posts"), // The directory containing the blog posts
mdocOut := file("website/blog"), // The target directory for the compiled blog posts
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.8.0" // 😺
),
scalaVersion := "2.13.10" // The Scala version to use when compiling Scala snippets
)

lazy val root = project
.in(file("."))
.settings(
name := "blog",
organization := "org.vennes"
)
.aggregate(`posts-mdoc`)

The posts-mdoc module is configured to use the MdocPlugin and DocusaurusPlugin plugins. In order to use these plugins, you need to add the sbt-mdoc plugin artifact to the project/plugins.sbt file.

addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.6" )

The MdocPlugin plugin compiles Scala code snippets in markdown files located in the posts directory and output into the website/blog directory.

By default, Mdoc uses the docs folder as the input directory, and the target/mdoc folder as the output directory. I've changed the input directory to posts and the output directory to website/blog, which is where Docusaurus will expect blog posts to be landed at.

I've also included cats-core as a dependency for the posts-mdoc project. Any dependencies you want to use in your Markdown Scala snippets should be added to the libraryDependencies collection. If you haven't checked out Cats, I highly recommend taking a look once you are done reading!

Initializing Docusaurus

Before initializing your Docusaurus website, be sure to install the following tools:

Docusaurus is a static site generator that can scaffold a blog with some nice-looking default styles. It comes out of the box with a static site perfect for project documentation and/or a blog. Fortunately, features can be enabled or disabled. For my blog, I've disabled the documentation features and am only using the blogging options.

To initialize the Docusaurus project, run the following command:

npx create-docusaurus@latest website classic

Npx comes built-in with an installation of Node and NPM. And although Npx was used to initialize the project, I will be using Yarn to run the Docusaurus commands and install dependencies.

Once the project has finished initializing, there will be a new website in the root of your blog project.

Configuring Docusaurus

The website directory will contain multiple subdirectories and configuration files. There are a lot of settings to tweak, but for this example, I'll only change the basics required for blog-only mode.

The website/docusaurus.config.js file contains the complete configuration for the Docusaurus project. It is structured as a JavaScript object, which provides some type-safety of configuration settings. Most editors may even provide some auto-completion for the various configurations. I've found the auto-completion helpful for feature discovery.

For now, we are going to change a couple options in the config. For reference, these steps follow closely with the Blog Setup instructions from the Docusaurus documentation. If you'd like more details, I recommend checking out the documentation.

I will also be leaving all the default styles and layouts in place. Docusaurus provides some documentation on styling, theming, and modifying the site, but I will not be covering them in this post.

  1. In the themeConfig.navbar.items array, remove the docs section and change the to field of the blog section to /.
    • Removes the Docs link from the navbar and changes the Blog link to point to the root of the website.
  2. In presets.docs, set the docs field to false.
    • Disable the docs section of the website.
  3. In presets.blog, set the property routeBasePath to '/'.
  4. Remove the index.js file from the website/src/pages directory.
    • Removes the default landing page from the website.
    • You may actually enjoy having the landing page, so feel free to skip this step.
    • The contents of 'index.js' can also be changed to be more blog-focused.

There will be a couple remaining text snippets and images that need to be updated to match your particular style, but I'll leave that up to you.

Undraw has some great icons and illustrations that are free to use to add some flavor to your site. Also, check out Unsplash for free stock photos that make great banner images.

Creating The Site

Now that we have configured SBT and the Docusaurus project, we can create the site.

The Mdoc plugin provides some helpful utilities for compiling the Scala documentation and generating the Docusaurus site:

  • mdoc - Compiles the markdown files in the mdocIn directory into the mdocOut directory.
  • docusaurusCreateSite - Creates the Docusaurus site in the website/build directory.

But before we start, be sure to add a blog post or two to the posts directory. If you need an example containing some Scala code, feel free to copy the content of my Typeclasses and Ad Hoc Polymorphism Post.

To mark Scala code in your posts for compilation with Mdoc, be sure to use the mdoc tag after scala in the code block.

For example:

```scala mdoc
val x = 1
x
```

For more info on Mdoc options, take a look at the Mdoc Modifiers Documentation.

Now that we have some posts, we can create our site and serve it locally:

sbt docusaurusCreateSite
yarn --cwd website run serve

If you want to run the site in a development version instead, run sbt mdoc and then yarn --cwd website start. This will run a server which reloads the site when changes are made to the markdown files.

And that's all there is to it!

· 12 min read

Typeclasses are a construct for declaring categorical behavior on types in the Haskell programming language, however, the concept is not restricted to just Haskell. The typeclass is an implementation of ad-hoc polymorphism, which unlike with interface or class inheritance, allows us to define polymorphic behavior on the fly.

Typeclasses provide an abstraction by defining interfaces and the values that implement them. In most object-oriented languages, interfaces are defined using direct inheritance from a child type to a parent type. Instead of operating on the class level, typeclasses define an interface, then instantiate the implementation in the form of a value. This allows flexibility with interface implementation, since the typeclass instances can be interchanged through function parameters and package imports. With typeclasses, library consumers have the ability to extend the functionality of types without modifying the source.

A typeclass consists of some generic interface and an implementation for a particular type. The type can be any representation of a value (ex. int, String, Person, Vehicle...) and the value itself can be anything. The most important part of a typeclass is that the implementation exists seperately from the implementation of the value itself and can be used like a value.

Typeclass Implementations Across Languages

For the following implementation examples, we will be implementing the Semigroup typeclass on int, string, and list types.

Semigroup defines a single combine function that takes two instances of type T as inputs and outputs their combined value.

For example, calling combine with the integers 4 and 6 should return 10 if we are using addition as the combine implementation (we could also implement this for multiplication).

As an example of how typeclasses can be used within a library, I'll also be creating a function applyTo that will combine each value in a list with a given value. For the input List(1, 2, 3, 4, 5), applyTo with a value of 8 would return List(9, 10, 11, 12, 13).

It is an implicit requirement that a Semigroup's combine operation also be associative: combining a group of values can occur in any order as in (1 + 2) + 3 = 1 + (2 + 3). However, this is not a possible restriction in most programming languages, so it will not be factored into these examples.

Scala

Although there is not first-class support for typeclasses in Scala, there are language constructs to help create them.

We first create a trait representing our Semigroup typeclass, then create a typeclass instance representing integer addition, and finally create a function which implicitly takes a typeclass instance as a parameter. The implicit keyword in Scala 2 lets us create a value in the implicit scope and summon it when the typeclass is requested.

trait Semigroup[T] {
def combine(a: T, b: T): T
}

implicit val intAdditionSemigroup: Semigroup[Int] = (a: Int, b: Int) => a + b
// intAdditionSemigroup: Semigroup[Int] = repl.MdocSession$MdocApp$$Lambda$8533/1443703711@2695e7e7

def applyTo[A](values: List[A], value: A)(implicit semigroup: Semigroup[A]): List[A] =
values.map(v => semigroup.combine(v, value))

applyTo(List(1, 2, 3, 4, 5), 8)
// res0: List[Int] = List(9, 10, 11, 12, 13)

C#

Typeclasses in C# require some creativity because anonymous objects aren't a thing. To access typeclass instances, we will provide an instance static method which returns a singleton typeclass instance. Although this will make it difficult to use the Semigroup<T> typeclass in a generic context, it makes the code a little bit neater.

public interface Semigroup<T>
{
T Combine(T a, T b);
}

public class IntAdditionSemigroup : Semigroup<int>
{
public static IntSemigroup instance = new IntSemigroup();

public int Combine(int a, int b) = a + b;
}

public List<T> ApplyTo<T>(Semigroup<T> instance, List<T> values, T value)
{
return values.Select(v => instance.combine(v, value));
}

ApplyTo(IntAdditionSemigroup.instance, new List<int> { 1, 2, 3, 4, 5 }, 8);

Note that because C# has no equivalent of the implicit scope found in Scala, the semigroup instance must be provided directly to the ApplyTo function.

Typescript

Typescript's implementation is a little neater, but also requires passing the semigroup instance directly because an implicit scope doesn't exist. Typescript does support instantiating anonymous objects, which makes creating the typeclass instances simple.

interface Semigroup<T> {
combine(a: T, b: T): T
}

const numberAdditionSemigroup = {
combine(a: number, b: number): number {
return a + b
}
} as Semigroup<number>

function combineAll<T>(instance: Semigroup<T>, values: T[], value: T): T[] {
return values.map(v => instance.combine(v, value))
}

combineAll(numberAdditionSemigroup, [1, 2, 3, 4, 5], 8)

Rust

Rust supports Ad-Hoc polymorphism out of the box since the implementation of interfaces for types must be defined separately from the types themselves. The trait implementations in Rust act just like typeclass definitions with an accompying impl block for instance definitions.

I've made the Semigroup typeclass receive the values a and b by reference so that we don't have to take ownership of the values, which makes it easier to work with.

trait Semigroup {
fn combine(a: &Self, b: &Self) -> Self;
}

impl Semigroup for i32 {
fn combine(a: &i32, b: &i32) -> i32 {
a + b
}
}

fn apply_to<T>(values: Vec<T>, value: T) -> Vec<T> where T: Semigroup {
values.iter().map(|v| { T::combine(&v, &value) } ).collect()
}

apply_to(vec![1, 2, 3, 4, 5], 8)

We do not have the option to provide multiple implementations of Semigroup for i32 within the same scope, so any additional implementations of combine (multiplication) would have to be placed in a separate scope and imported.

Helpful Typeclasses

There are a number of common typeclasses that can be combined to implement similar behavior across all implementing types. Semigroup, Eq, and Show are simple typeclasses, but more complex ones like Monoid, Monad, and Functor can provide a lot of additional functionality.

I will be implmenting the following examples with Scala, but symmetric implementations can be made for C#, Typescript, and Rust using the methods outlined in our Semigroup example.

Eq

Eq provides a typesafe equals method eqv. Eq should be used when we want to check that the value of two values with the same type is the same. Calling eqv with two values of different types should fail to compile.

trait Eq[T] {
def eqv(a: T, b: T): Boolean
}

Because we provide a function that determines if two values are equal, we also get a function determining if two values are not equal for free.

object Eq {
def neqv[T](a: T, b: T)(implicit eq: Eq[T]): Boolean = !eq.eqv(a, b)
}

For implementing neqv I've created a companion class which takes an implicit Eq instance. The neqv method can also be defined in the Eq trait itself.

Then in our library, we can use our type-safe Eq implementation instead of the == which can vary in accuracy depending on type.

def combineIfNotEqual[T](a: T, b: T, otherwise: T)(implicit eq: Eq[T], semigroup: Semigroup[T]): T =
if (Eq.neqv(a, b)) semigroup.combine(a, b)
else otherwise

Show

Show provides a method to get an explicit function for turning a value into a String type. This is very helpful when we want to print the state of a complex object to the console without having to override any existing toString method directly on the type implementation. Also, when a function wants to print the value of a generic type to the console, it can use its Show implementation instead of relying on the built-in toString method to be correct. Often, the default toString method will print out garbage, expecially for complex class instances in the JVM.

trait Show[T] {
def show(value: T): String
}

Then, when we want to do some debugging from a function we write, we can require an explicit Show implementation which the function caller provides.

implicit val showInt: Show[Int] = (value: Int) => s"Integer($value)" // ex. Integer(5)
// showInt: Show[Int] = repl.MdocSession$MdocApp$$Lambda$8535/1877531700@4e0648c3 // ex. Integer(5)

def print[T](value: T)(implicit show: Show[T]): Unit = println(show.show(value))

print(500)
// Integer(500)

For a more complex object, this can save us a lot of headache.

class PersonWithRandomAge(val first: String, val last: String) {
private val random = scala.util.Random

val age = random.nextInt(100)
}

implicit val showPerson: Show[PersonWithRandomAge] = (person: PersonWithRandomAge) =>
s"Person(name: ${person.last}, ${person.first}, age: ${person.age})"
// showPerson: Show[PersonWithRandomAge] = repl.MdocSession$MdocApp$$Lambda$8536/458042945@1344a2dc

Even better, with some help from the Scala Cats library, we can use this implementation when we have a collection of people without any extra work. We just have to be sure to implement the cats.Show trait instead of our custom Show trait.

import cats.implicits._

implicit val catsShowPerson: cats.Show[PersonWithRandomAge] = (person: PersonWithRandomAge) =>
s"Person(name: ${person.last}, ${person.first}, age: ${person.age})"
// catsShowPerson: Show[PersonWithRandomAge] = repl.MdocSession$MdocApp$$Lambda$8537/1387467488@6ff8a027

val people: List[PersonWithRandomAge] = List(new PersonWithRandomAge("Kup", " Quickdew"), new PersonWithRandomAge("Hellweed", "Underhill"))
// people: List[PersonWithRandomAge] = List(
// repl.MdocSession$MdocApp$PersonWithRandomAge@74576f3d,
// repl.MdocSession$MdocApp$PersonWithRandomAge@352b20c0
// )

people.show
// res2: String = "List(Person(name: Quickdew, Kup, age: 73), Person(name: Underhill, Hellweed, age: 33))"

Monoid

The Monoid typeclass is an extension of Semigroup with an additional method empty that returns a value representing the default state of non-existence. For integers, this value would be 0 or for strings "".

It is often helpful to extend the functionality of one typeclass with that of another. We can expand on our previous Semigroup trait to implement Monoid.

trait Semigroup[T] {
def combine(a: T, b: T): T
}

trait Monoid[T] extends Semigroup[T] {
def empty: T
}

Any implementation of Monoid can be used where a Semigroup is required. This comes in handy when we want to fold over a collection of values.

implicit val intAdditionMonoid: Monoid[Int] = new Monoid[Int] {
override def combine(a: Int, b: Int): Int = a + b
override def empty: Int = 0
}
// intAdditionMonoid: Monoid[Int] = repl.MdocSession$MdocApp3$$anon$9@311796af

def combineAll[T](values: List[T])(implicit monoid: Monoid[T]) =
values.foldLeft(monoid.empty)(monoid.combine)

combineAll(List(1, 2, 3, 4, 5)) // 15
// res4: Int = 15

Functor

The Functor typeclass defines a map method from type A to type B so that a Functor[A] can be mapped to a Functor[B] type. This is loosely related to the mathematical definition of a Functor F which defines a mapping from a set A to a set B such that F[idA] -> F[idB] and F[g * f] -> F[g] * F[f]. In this definition, the identity element idA depends on the identity of the the category (0 for integer addtion and 1 for integer multiplication) and g and f are functions applied to the element contained in F. For example, if g(x) = x + 1 and f(x) = x + 5, then F[g(f(3))] must be equal to F[g(3)] + F[f(3)].

For the typeclass definition of Functor, we are going to take advantage of Scala's ability to define generic type arguments with a number of "holes". For example, Functor[F[_]] defines a Functor type for a generic argument named F that itself has a single type argument. In most languages, this restriction will not be possible, which can make defining this Functor type tricky.

In our example, I will use Option as the Functor argument. Option in Scala contains a previously defined map method, but I will instead show how the implementation works using just Some and None case classes.

I'm also adding an additional Functor object with a summoner function which pulls the given implicit functor out of the implicit scope. This will let us call the Functor map function directly.

trait Functor[F[_]] {
def map[A, B](functorA: F[A], mapping: A => B): F[B]
}

object Functor {
// summoner function
def apply[F[_]](implicit functor: Functor[F]): Functor[F] = functor
}

implicit val optionFunctor: Functor[Option] = new Functor[Option] {
override def map[A, B](functorA: Option[A], mapping: A => B): Option[B] =
functorA match {
case Some(a) => Some(mapping(a))
case None => None
}
}
// optionFunctor: Functor[[A >: Nothing <: Any] => Option[A]] = repl.MdocSession$MdocApp3$$anon$12@6f85deec

Functor[Option].map[Int, Int](Some(50), x => x * 10)
// res5: Option[Int] = Some(value = 500)
Functor[Option].map[Int, Int](None, x => x * 10)
// res6: Option[Int] = None

Monad

The Monad typeclass is an extension on the Functor typeclass which provides a flatten function. The flatten function squashes a value of type F[F[_]] into F[_]. Once defined, we can combine flatten and map to create flatMap, which works like map except it takes in a function of type A => F[B] instead of A => B and returns the type F[B].

trait Monad[F[_]] extends Functor[F] {
def flatten[A](nestedFunctorA: F[F[A]]): F[A]

def flatMap[A, B](functorA: F[A], mapping: A => F[B]): F[B] = flatten(map(functorA, mapping))
}

implicit val listMonad: Monad[List] = new Monad[List] {
override def map[A, B](functorA: List[A], mapping: A => B): List[B] = functorA match {
case head :: tail => mapping(head) :: map(tail, mapping)
case _ => List()
}

override def flatten[A](nestedFunctorA: List[List[A]]): List[A] = nestedFunctorA match {
case head :: tail => head ++ flatten(tail)
case _ => List()
}
}
// listMonad: Monad[List] = repl.MdocSession$MdocApp3$$anon$16@4fed0b1

object Monad {
// summoner function
def apply[F[_]](implicit monad: Monad[F]): Monad[F] = monad
}

Monad[List].map(List(100, 5, 22), (x: Int) => x * 5)
// res7: List[Int] = List(500, 25, 110)

Monad[List].flatten(List(List(1, 3, 4), List(9, 18, 0)))
// res8: List[Int] = List(1, 3, 4, 9, 18, 0)

Monad[List].flatMap(List(1, 2, 3, 4), (x: Int) => List(x % 2, x % 3))
// res9: List[Int] = List(1, 1, 0, 2, 1, 0, 0, 1)

Conclusion

Typeclasses are a flexible and functional approach to abstraction and generic programming. They are type-safe, modular, and simple to test and relieve many headaches software developers encounter with common behaviors on types.