Skip to content

Instantly share code, notes, and snippets.

@lazyvalue
Last active February 20, 2023 14:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lazyvalue/c3e14f9d6c950fefcaab84107ff17cd0 to your computer and use it in GitHub Desktop.
Save lazyvalue/c3e14f9d6c950fefcaab84107ff17cd0 to your computer and use it in GitHub Desktop.
Typeclasses in Scala3
// A quick tutorial on typeclasses in Scala 3.
// We declare a typeclass for universal IDs, called tokens, for domain objects
// in our system, like users, transaction records, shopping cart items,
// text messages, whatever.
// The typeclass gives us some type safety, some handy methods (well, one so far),
// and the set of tokens are open-ended (you can add more domain objects time).
// You know the drill ...
import java.util.UUID
// Declare a case class like normal. Note that in Scala 3 you can use
// Python-like syntax. No need for curly braces.
// This case class is basically just a wrapper for a UUID. It also takes
// a type parameter `T`, which requires that a `given` TokenEntity[T] is in
// scope. Will talk about `given`, and what `TokenEntity` buys us, in a moment.
// The `T` type parameter will be used to strongly type what kind of token
// we are creating.
case class Token[T: TokenEntity](uuid: UUID):
// Convenience method to string-ify the UUID without the dashes.
def showUUID = uuid.toString.replace("-", "")
// This is a companion object for a Token. Its use is to create a special
// constructor for tokens that generates the UUID for us. This is just a
// convenience.
object Token:
def apply[T: TokenEntity]: Token[T] = Token[T](UUID.randomUUID())
// A partially abstract trait. Implementors of the trait will need
// to provide a value for `tokenPrefix`. The purpose of the trait is
// to semantically declare that a type `T` is something in our domain
// that should have its own token.
trait TokenEntity[T]:
// Abstract, give me a value.
val tokenPrefix: String
// Extension is a keyword in a Scala 3. It means, add the following
// methods like they were declared as part of the class. So we end
// up with `Token[T].showToken`.
// The `showToken` extension method is concrete below. Implementors
// of `TokenEntity` do not have to worry about it.
extension(token: Token[T])
def showToken: String = s"${tokenPrefix}_${token.showUUID}"
// Just a plain old boring case class. Represents a typical user with a token
// in our domain.
case class User(token: Token[User], name: String)
// Earlier, when we declared the Token case class, we declared it with
// `[T: TokenEntity]`. Meaning, a `TokenEntity[T]` must be *given* in order
// to construct the Token. When we defined the `User` case class, we specified
// that the T means User for this case: `Token[User]`. And here, we are
// satisfying the requirement that a `TokenEntity[User]` is available.
// The `given` keyword is what makes it available to anyone who asks
// (eg, indicates it via `[T: TokenEntity]`).
// Note that we only specified the `tokenPrefix` here. This is because
// it is the only abstract prt of `TokenEntity`.
given TokenEntity[User] with
val tokenPrefix = "usr"
// Put it all together. `@main` declares the main function of a program.
// You have the luxury of naming the function whatever you want.
@main def hello: Unit = {
// Construct the token. This uses the companion object so we don't
// need to manually generate the UUID.
val someNewToken = Token.apply[User]
// Construct a user with the token. We don't really use that here,
// suffice for demonstration.
val someJerk = User(someNewToken, "Scott")
// Print out the token to the console:
// > Such a charming token: usr_3d2347fab2e741e3ae3c6acaf478f09e
// Scala finds the instance of `TokenEntity` for `User`. The
// method `.showToken` is made possible by the `extension` keyword
// in the `TokenEntity` definition. And the `TokenEntity[User]`
// instance provides the 'usr' prefix.
// Note that the `String` type declaration is not necessary, it is
// just added for clarity.
val output: String = someNewToken.showToken
println(s"Such a charming token: $output")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment