Last active
February 20, 2023 14:14
-
-
Save lazyvalue/c3e14f9d6c950fefcaab84107ff17cd0 to your computer and use it in GitHub Desktop.
Typeclasses in Scala3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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