Skip to main content

· 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$8708/739628048@224d996a

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$8409/1609029207@183380af

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$8411/1082088733@55e053c0 // 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$8412/2120903508@249f69dc

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$8413/1421579510@73b650dc

val people: List[PersonWithRandomAge] = List(new PersonWithRandomAge("Kup", " Quickdew"), new PersonWithRandomAge("Hellweed", "Underhill"))
// people: List[PersonWithRandomAge] = List(
// repl.MdocSession$MdocApp$PersonWithRandomAge@6b55ffa6,
// repl.MdocSession$MdocApp$PersonWithRandomAge@8695850
// )

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

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@20789fc0

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@34e5b981

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@20c7f12b

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.

· 7 min read

In this tutorial I will be walking through the development of my HTML builder Python package.

The completed package is available at bvennes/html_builder on GitHub.

Creating a Clientside Project With Python

The purpose of the HTML builder is to programmatically create an HTML file using Python. In the future, however, I would like to make SCSS and JavaScript builders as well so that the entirety of a web application could be built using Python.

HTML is a good place to start because the structure of an HTML file is fairly simple. HTML tags look like <tag-name class="class-1 class-2"></tag-name> and are nested to build out component hierarchies.

An HTML tag can be broken down into 4 major components:

  • name of the tag
  • list of classes for the tag
  • additional attributes like onclick or style
  • child tags or text

Html Python Class

To create an HTML tag in Python, I created a basic object called Html holding access to the HTML tag's name, class names, and attributes like onclick: doSomething().

It would also be possible for the classes list to be given as an attribute, since class is technically just an attribute, but this is a good opportunity to make use of Python's *args functionality.

Here is what the initial code looked like for the Html object:

class Html:
def __init__(self, tag_name, *class_names, **attributes):
"""Initializes a new html tag."""
self.name = tag_name
self.class_names = list(class_names)
self.attributes = attributes
self.children = []

Render Method

In order to output the Html object as a string, I added a render method.

def render(self):
"""Renders the html tag as a string."""
html = f'<{self.name}'

if self.class_names.__len__() > 0:
classes_list = ' '.join(self.class_names)
html += f' class="{classes_list}"'

for key, value in self.attributes.items():
html += f' {key}="{value}"'

html += f'></{self.name}>'

return html

To render the HTML tag as a string, we maintain an html element that begins with <self.name, add the classes as a list separated by a space, add the sets of attribute key/value pairs, and finally close it off with ></self.name>.

With both the __init__ and render methods completed, the Html class looks like:

class Html:
def __init__(self, tag_name, *class_names, **attributes):
"""Initializes a new html tag."""
self.name = tag_name
self.class_names = list(class_names)
self.attributes = attributes
self.children = []

def render(self):
"""Renders the html tag as a string."""
html = f'<{self.name}'

if self.class_names.__len__() > 0:
classes_list = ' '.join(self.class_names)
html += f' class="{classes_list}"'

for key, value in self.attributes.items():
html += f' {key}="{value}"'

html += f'></{self.name}>'

return html

Html Class Testing

Before testing this class out, let's setup the full html_builder Python package.

First, create a new directory called html_builder.

Add a blank file within this directory called __init__.py

Add a subdirectory to html_builder also called html_builder.

Within the html_builder subdirectory, add a file named html.py and copy the Html class from above.

Then, create a test script in the same folder as the top-level html_builder directory named html_test.py.

# html_test.py
from html_builder.html import Html

button = Html('button', 'class-1', 'class-2', onclick="alert('Hello world!')")

print(button.render())

The output of this Python script should be <button class="class-1 class-2" onclick="alert('Hello world!')"></button>. Let's copy this to a file named button.html and open it in a web browser. We should see a tiny button with no text. If we click on it, the window alerts us with the message Hello world!. That's a promising start!

But users will want to be able to add text to their button. In order to do this, we want our button to be able to contain some child elements, like this:

<button class="class-1 class-2" onclick="alert('Hello world!')">
Click me!
</button>

Html Children

Let's add a child element to our button.

button = Html('button', onclick="alert('Hello world!')")
button.children += ['Click me!']

However, our render method isn't rendering any of our children, so let's fix that by adding a method called render_children(). We will make it a private method so that we can encapsulate any additional logic that might occur while rendering the tag's children. To make the method private, add __ before the method name.

This is also a good time to make the HTML format nicely when printed using newline characters and spaces.

def __render_children(self):
"""Renders the tag's children"""
rendered_children = ''

for child in self.children:
rendered_children += '\n '
if type(child) is Html:
rendered_children += child.render().replace('\n','\n ')
else:
rendered_children += child

rendered_children += '\n'

return rendered_children

Let's also modify the render to use the new private method.

def render(self):
"""Renders the html tag as a string."""
html = f'<{self.name}'

if self.class_names.__len__() > 0:
classes_list = ' '.join(self.class_names)
html += f' class="{classes_list}"'

for key, value in self.attributes.items():
html += f' {key}="{value}"'

rendered_children = self.__render_children()

html += f'>{rendered_children}</{self.name}>'

return html

The Html class should now look like this:

# html.py
class Html:
def __init__(self, tag_name, *class_names, **attributes):
"""Initializes a new html tag."""
self.name = tag_name
self.class_names = list(class_names)
self.attributes = attributes
self.children = []

def render(self):
"""Renders the html tag as a string."""
html = f'<{self.name}'

if self.class_names.__len__() > 0:
classes_list = ' '.join(self.class_names)
html += f' class="{classes_list}"'

for key, value in self.attributes.items():
html += f' {key}="{value}"'

rendered_children = self.__render_children()

html += f'>{rendered_children}</{self.name}>'

return html

def __render_children(self):
"""Renders the tag's children"""
rendered_children = ''

for child in self.children:
rendered_children += '\n '
if type(child) is Html:
rendered_children += child.render().replace('\n','\n ')
else:
rendered_children += child

rendered_children += '\n'

return rendered_children

Looks good! Now we'll want to update our test script to make use of the new functionality. I've added div and title elements to test out nested HTML.

Testing Child HTML Tags

# html_test.py
from html_builder.html_builder.html import Html

div = Html('div')

title = Html('h1')

button = Html('button', onclick="alert('Hello world!')")

button.children += ['Click me!']
title.children += ['HTML Builder Test']
div.children += [title, button]

print(div.render())

After running the html_test.py script we should see the output

<div>
<h1>
HTML Builder Test
</h1>
<button onclick="alert('Hello world!')">
Click me!
</button>
</div>

After copying the output to test.html and reloading the browser, it should show our title and a button that says 'Click me!'.

Rendering to an HTML File

At this point, our builder is just about done. But it might be helpful for our users to render HTML directly into a file. So I've added an output_file parameter to the render method so that users can specify a path where the rendered HTML should go. Before returning the HTML as a string, I've also added a section for opening the file if it is given, writing to it, and closing it.

def render(self, output_file_path=None):
"""Renders the html tag as a string."""
html = f'<{self.name}'

if self.class_names.__len__() > 0:
classes_list = ' '.join(self.class_names)
html += f' class="{classes_list}"'

for key, value in self.attributes.items():
html += f' {key}="{value}"'

rendered_children = self.__render_children()

html += f'>{rendered_children}</{self.name}>'

if output_file_path != None:
output_file = open(output_file_path, 'w')
output_file.write(html)
output_file.close()

return html

To verify this behavior works correctly, I've specified the output file in the test script.

from html_builder.html_builder.html import Html

div = Html('div')

title = Html('h1')

button = Html('button', onclick="alert('Hello world!')")

button.children += ['Click me!']
title.children += ['HTML Builder Test']
div.children += [title, button]

print(div.render('./test.html'))

Now when we run the script, the HTML is printed to both the output terminal and the test.html file. Neat!