Skip to content

Commit

Permalink
Expand discussion about FSMs.
Browse files Browse the repository at this point in the history
  • Loading branch information
noelwelsh committed Nov 2, 2023
1 parent 4c0bec0 commit 76850cf
Showing 1 changed file with 59 additions and 2 deletions.
61 changes: 59 additions & 2 deletions src/pages/adt/applications.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Applications of Algebraic Data Types
## Finite State Machines as Algebraic Data Types

We seen some examples of algebraic data types already. Some of the simplest are the basic enumeration, like

Expand Down Expand Up @@ -44,4 +44,61 @@ enum Job[A] {
}
```

If you look around the code you work with I expect you'll quickly find many other examples.
It's often the case that we want to define methods on only a subset of the states in a finite state machine. A simple example is a resource, like a file, that can be either open or closed. An open file can be closed and a closed file can be opened, but not vice versa. There are a number of ways to model this, and we don't yet have all the tools to see all the possible implementations. However one particularly simple implementation is to use the not-quite algebraic data type approach hinted at in an earlier section.

```scala mdoc:silent
sealed abstract class Resource

final case class Open() extends Resource {
def close: Closed = ???
}
final case class Closed() extends Resource {
def open: Open = ???
}
```

In this approach we need to be careful with types of methods that are common across states.
For example, image we have a method `copy` to duplicate a `Resource`.
We could define this as

```scala
sealed abstract class Resource {
def copy: Resource
}
```

but this loses the type information of whether a `Resource` is open or closed, and therefore we cannot call the `open` or `close` method on a copy without an unsafe cast.

Another approach is to simply duplicate the `copy` method with the appropriate return type.

```scala mdoc:reset:silent
sealed abstract class Resource

final case class Open() extends Resource {
def copy: Open = ???
def close: Closed = ???
}
final case class Closed() extends Resource {
def copy: Closed = ???
def open: Open = ???
}
```

This solves the problem but introduces its own limitations: it can require a lot of code duplication, and there is no way to write a method that copies either an `Open` or `Closed` resource. One way to address these is with what is called **F-bound polymorphism**.

```scala mdoc:reset:silent
sealed abstract class Resource[Self <: Resource[Self]] {
def copy: Self = ???
}

final case class Open() extends Resource[Open] {
def close: Closed = ???
}
final case class Closed() extends Resource[Closed] {
type Self = Closed

def open: Open = ???
}
```

The solution we should take depends on the context. F-bound polymorphism is confusing to many new programmers, and the simpler solution we saw earlier may work depending on our requirements. Later on we'll address this problem with using parameters, known as implicit parameters in Scala 2.

0 comments on commit 76850cf

Please sign in to comment.