iltotore / scalabuilder

A new approach for creating Builder patterns

GitHub

ScalaBuilder

ScalaBuilder is a new way to build objects using a ScalaFX-like DSL.

Actual problem

You have a case class:

case class Item(material: String,
  amount: Int = 1,
  displayName: Option[String] = Option.empty,
  enchantments: Seq[Enchatment] = Seq.empty)

You can define your case class like this:

val item = Item("sword", 1, Option("King's sword"))

It can become pretty annoying to wrap in Option all your optional argument, in particular for bigger case classes.

This could be better using the builder pattern:

val item = Item.builder("sword")
  .named("King's sword")
  .withEnchantment(FireEnchatment(level = 2))
  .build

The problem is can it feels less elegant when you use enclosed builders:

val item = Item.builder("sword")
  .named("King's sword")
  .withEnchantment(FireEnchatment(level = 2))
  .withMeta(
    WeaponMeta.builder
      .withDamage(5)
      .withDurability(20)
      .withAttackSpeed(0.5)
      .build
  )
  .build

Thanks to Scala's flexibility, you can define your Item like this:

val item = new Item.Builder("sword") {
  name = "King's sword"
  enchantments = Seq(FireEnchantment(2))
  meta = new WeaponMeta.Builder {
    damage = 5
    durability = 10
    attackSpeed = 0.5
  }.build
}.build

by defining accessors like this:

private var name: Option[String] = Option.empty

def name: Option[String] = nameOpt
def name_=(value: String): Unit = nameOpt = Option(value)

Provided solution

Implicit build

Firstly, ScalaBuilder provides a simple implicit conversion method to build your object, allowing you to do:

import io.github.iltotore.scalabuilder.Builder.autoBuild


val item: Item = new Item.Builder("sword") {
  name = "King's sword"
  enchantments = Seq(FireEnchantment(2))
  meta = new WeaponMeta.Builder {
    damage = 5
    durability = 10
    attackSpeed = 0.5
  }
}

ScalaBuilder generates the builder semi and fully-automatically using two annotations:

Property annotation on variable

You can use the @property(name) annotation to generate variable accessors automatically:

@property("name")
private var nameOpt: Option[String] = Option.empty

gives:

private var nameOpt: Option[String] = Option.empty

def name: Option[String] = nameOpt
def name_=(value: String): Unit = nameOpt = Option(value)

Full builder generation

You can annotate your case class using `@buildable:

@buildable
case class Item(material: String,
  amount: Int = 1,
  displayName: Option[String] = Option.empty,
  enchantments: Seq[Enchatment] = Seq.empty)

ScalaBuilder will generate your Item.Builder for you like shown before.