Wednesday, July 09, 2008

Type-safe Builder Pattern in Scala

The Builder Pattern is an increasingly popular idiom for object creation. Traditionally, one of it's shortcomings in relation to simple constructors is that clients can try to build incomplete objects, by omitting mandatory parameters, and that error will only show up in runtime. I'll show how to make this verification statically in Scala.


So, let's say you want to order a shot of scotch. You'll need to ask for a few things: the brand of the whiskey, how it should be prepared (neat, on the rocks or with water) and if you want it doubled. Unless, of course, you are a pretentious snob, in that case you'll probably also ask for a specific kind of glass, brand and temperature of the water and who knows what else. Limiting the snobbery to the kind of glass, here is one way to represent the order in scala.
sealed abstract class Preparation  /* This is one way of coding enum-like things in scala */
case object Neat extends Preparation
case object OnTheRocks extends Preparation
case object WithWater extends Preparation

sealed abstract class Glass
case object Short extends Glass
case object Tall extends Glass
case object Tulip extends Glass

case class OrderOfScotch(val brand:String, val mode:Preparation, val isDouble:Boolean, val glass:Option[Glass])

A client can instantiate their orders like this:
val normal = new OrderOfScotch("Bobby Runner", OnTheRocks, false, None)
val snooty = new OrderOfScotch("Glenfoobar", WithWater, false, Option(Tulip));

Note that if the client doesn't want to specify the glass he can pass None as an argument, since the parameter was declared as Option[Glass]. This isn't so bad, but it can get annoying to remember the position of each argument, specially if many are optional. There are two traditional ways to circumvent this problem — define telescoping constructors or set the values post-instantiation with accessors — but both idioms have their shortcomings. Recently, in Java circles, it has become popular to use a variant of the GoF Builder pattern. So popular that it is Item 2 in the second edition of Joshua Bloch's Effective Java. A Java-ish implementation in Scala would be something like this:
class ScotchBuilder {
private var theBrand:Option[String] = None
private var theMode:Option[Preparation] = None
private var theDoubleStatus:Option[Boolean] = None
private var theGlass:Option[Glass] = None

def withBrand(b:Brand) = {theBrand = Some(b); this} /* returning this to enable method chaining. */
def withMode(p:Preparation) = {theMode = Some(p); this}
def isDouble(b:Boolean) = {theDoubleStatus = some(b); this}
def withGlass(g:Glass) = {theGlass = Some(g); this}

def build() = new OrderOfScotch(theBrand.get, theMode.get, theDoubleStatus.get, theGlass);
}

This is almost self-explanatory, the only caveat is that verifying the presence of non-optional parameters (everything but the glass) is done by the Option.get method. If a field is still None, an exception will be thrown. Keep this in mind, we'll come back to it later.

The var keyword prefixing the fields means that they are mutable references. Indeed, we mutate them in each of the building methods. We can make it more functional in the traditional way:
object BuilderPattern {
class ScotchBuilder(theBrand:Option[String], theMode:Option[Preparation], theDoubleStatus:Option[Boolean], theGlass:Option[Glass]) {
def withBrand(b:String) = new ScotchBuilder(Some(b), theMode, theDoubleStatus, theGlass)
def withMode(p:Preparation) = new ScotchBuilder(theBrand, Some(p), theDoubleStatus, theGlass)
def isDouble(b:Boolean) = new ScotchBuilder(theBrand, theMode, Some(b), theGlass)
def withGlass(g:Glass) = new ScotchBuilder(theBrand, theMode, theDoubleStatus, Some(g))

def build() = new OrderOfScotch(theBrand.get, theMode.get, theDoubleStatus.get, theGlass);
}

def builder = new ScotchBuilder(None, None, None, None)
}

The scotch builder is now enclosed in an object, this is standard practice in Scala to isolate modules. In this enclosing object we also find a factory method for the builder, which should be called like so:
import BuilderPattern._

val order = builder withBrand("Takes") isDouble(true) withGlass(Tall) withMode(OnTheRocks) build()

Looking back at the ScotchBuilder class and it's implementation, it might seem that we just moved the huge constructor mess from one place (clients) to another (the builder). And yes, that is exactly what we did. I guess that is the very definition of encapsulation, sweeping the dirt under the rug and keeping the rug well hidden. On the other hand, we haven't gained all the much from this "functionalization" of our builder; the main failure mode is still present. That is, having clients forget to set mandatory information, which is a particular concern since we obviously can't fully trust the sobriety of said clients*. Ideally the type system would prevent this problem, refusing to typecheck a call to build() when any of the non-optional fields aren't set. That's what we are going to do now.

One technique, which is very common in Java fluent interfaces, would be to write an interface for each intermediate state containing only applicable methods. So we would begin with an interface VoidBuilder having all our withFoo() methods but no build() method, and a call to, say, withMode() would return another interface (maybe BuilderWithMode), and so on, until we call the last withBar() for a mandatory Bar, which would return an interface that finally has the build() method. This technique works, but it requires a metric buttload of code — for n mandatory fields 2n interfaces should be created. This could be automated via code generation, but there is no need for such heroic efforts, we can make the typesystem work in our favor by applying some generics magic. First, we define two abstract classes:
abstract class TRUE
abstract class FALSE

Then, for each mandatory field, we add to our builder a generic parameter:
class ScotchBuilder[HB, HM, HD](val theBrand:Option[String], val theMode:Option[Preparation], val theDoubleStatus:Option[Boolean], val theGlass:Option[Glass]) {

/* ... body of the scotch builder .... */

}

Next, have each withFoo method pass ScotchBuilder's type parameters as type arguments to the builders they return. But, and here is where the magic happens, there is a twist on the methods for mandatory parameters: they should, for their respective generic parameters, pass instead TRUE:
class ScotchBuilder[HB, HM, HD](val theBrand:Option[String], val theMode:Option[Preparation], val theDoubleStatus:Option[Boolean], val theGlass:Option[Glass]) {
def withBrand(b:String) =
new ScotchBuilder[TRUE, HM, HD](Some(b), theMode, theDoubleStatus, theGlass)

def withMode(p:Preparation) =
new ScotchBuilder[HB, TRUE, HD](theBrand, Some(p), theDoubleStatus, theGlass)

def isDouble(b:Boolean) =
new ScotchBuilder[HB, HM, TRUE](theBrand, theMode, Some(b), theGlass)

def withGlass(g:Glass) =
new ScotchBuilder[HB, HM, HD](theBrand, theMode, theDoubleStatus, Some(g))
}

The second part of the magic act is to apply the world famous pimp-my-library idiom and move the build() method to an implicitly created class, which will be anonymous for the sake of simplicity:
implicit def enableBuild(builder:ScotchBuilder[TRUE, TRUE, TRUE]) = new {
def build() =
new OrderOfScotch(builder.theBrand.get, builder.theMode.get, builder.theDoubleStatus.get, builder.theGlass);
}

Note the type of the parameter for this implicit method: ScotchBuilder[TRUE, TRUE, TRUE]. This is the point where we "declare" that we can only build an object if all the mandatory parameters are specified. And it really works:
scala> builder withBrand("hi") isDouble(false) withGlass(Tall) withMode(Neat) build()
res5: BuilderPattern.OrderOfScotch = OrderOfScotch(hi,Neat,false,Some(Tall))

scala> builder withBrand("hi") isDouble(false) withGlass(Tall) build()
<console>:9: error: value build is not a member of BuilderPattern.ScotchBuilder[BuilderPattern.TRUE,BuilderPattern.FALSE,BuilderPattern.TRUE]
builder withBrand("hi") isDouble(false) withGlass(Tall) build()

So, we achieved our goal (see the full listing below). If you are worried about the enormous parameter lists inside the builder, I've posted here an alternative implementation with abstract members instead. It is more verbose, but also cleaner.

Now, remember those abstract classes TRUE and FALSE? We never did subclass or instantiate them at any point. If I'm not mistaken, this is an idiom named Phantom Types, commonly used in the ML family of programming languages. Even though this application of phantom types is fairly trivial, we can glimpse at the power of the mechanism. We have, in fact, codified all 2n states (one for each combination of mandatory fields) as types. ScotchBuilder's subtyping relation forms a lattice structure and the enableBuild() implicit method requires the supremum of the poset (namely, ScotchBuilder[TRUE, TRUE, TRUE]). If the domain requires, we could specify any other point in the lattice — say we can doll-out a dose of any cheap whiskey if the brand is not given, this point is represented by ScotchBuilder[_, TRUE, TRUE]. And we can even escape the lattice structure by using Scala inheritance. Of course, I didn't invent any of this; the idea came to me in this article by Matthew Fluet and Riccardo Pucella, where they use phantom types to encode subtyping in a language that lacks it.



object BuilderPattern {
sealed abstract class Preparation
case object Neat extends Preparation
case object OnTheRocks extends Preparation
case object WithWater extends Preparation

sealed abstract class Glass
case object Short extends Glass
case object Tall extends Glass
case object Tulip extends Glass

case class OrderOfScotch(val brand:String, val mode:Preparation, val isDouble:Boolean, val glass:Option[Glass])

abstract class TRUE
abstract class FALSE

class ScotchBuilder
[HB, HM, HD]
(val theBrand:Option[String], val theMode:Option[Preparation], val theDoubleStatus:Option[Boolean], val theGlass:Option[Glass]) {
def withBrand(b:String) =
new ScotchBuilder[TRUE, HM, HD](Some(b), theMode, theDoubleStatus, theGlass)

def withMode(p:Preparation) =
new ScotchBuilder[HB, TRUE, HD](theBrand, Some(p), theDoubleStatus, theGlass)

def isDouble(b:Boolean) =
new ScotchBuilder[HB, HM, TRUE](theBrand, theMode, Some(b), theGlass)

def withGlass(g:Glass) = new ScotchBuilder[HB, HM, HD](theBrand, theMode, theDoubleStatus, Some(g))
}

implicit def enableBuild(builder:ScotchBuilder[TRUE, TRUE, TRUE]) = new {
def build() =
new OrderOfScotch(builder.theBrand.get, builder.theMode.get, builder.theDoubleStatus.get, builder.theGlass);
}

def builder = new ScotchBuilder[FALSE, FALSE, FALSE](None, None, None, None)
}



* Did you hear that noise? It's the sound of my metaphor shattering into a million pieces



EDIT 2008-07-09 at 19h00min: Added introductory paragraph.

456 comments:

«Oldest   ‹Older   401 – 456 of 456
James said...

You got a really useful blog I have been here reading for about half an hour. I am a newbie and your post is valuable for me.

dentist near me

Bhuvankumar said...

Great Post, I was looking for this kind of information, Keep posting. Thanks for sharing.
Data Science Courses in Bangalore

Tech Institute said...

The blog is informative and very useful therefore, I would like to thank you for your effort in writing this article.
Data Analytics Course in Lucknow

360DigiTMGAurangabad said...

great article!! sharing these type of articles is the nice one and i hope you will share an article on data Analytics. By giving a institute like 360DigiTMG. it is one the best institute for doing certified courses
data analytics course aurangabad

Professional Course said...

This is just the information I find everywhere. Thank you for your blog, I just subscribed to your blog. It's a good blog.

Best Data Analytics Courses in Bangalore

James said...

A useful post shared.
https://www.rundoyen.com/training-plans/

skrishtech said...

Thanks for sharing a valuable information.

Best Interior Designers in Chennai
Best Interior Decorators in Chennai
chennai renovation
flat renovation contractor services in chennai
modular kitchen in chennai
false ceiling contractor services in chennai
carpenter services in chennai

Sunil said...

I want to post a remark that "The quality information of your post is amazing" Great work.
Data Science Course in Jabalpur

Professional Course said...

What an incredible message this is. Truly one of the best posts I have ever seen in my life. Wow, keep it up.

Data Scientist Training in Bangalore

Natures Pired said...

Jointless Dressing Drums are used for Sterilizing and Storing different types of dressings, instruments, and surgical requisites. Slotted belt with clamp for loosening or tightening of the belt to open or close the perforated on the body.
dental uv chambers

Natures Pired said...

The step of implementing GST in India was a historical move, as it marked a significant indirect tax reform in the country. Almost four and a half years ago, GST replaced 17 local levies like excise duty, service tax, VAT and 13 cesses.
online sole proprietorship registration

Career Program and Skill Development said...

Develop technical skills and become an expert in analyzing large sets of data by enrolling for the Best Data Science course in Bangalore. Gain in-depth knowledge in Data Visualization, Statistics, and Predictive Analytics along with the two famous programming languages and Python. Learn to derive valuable insights from data using skills of Data Mining, Statistics, Machine Learning, Network Analysis, etc, and apply the skills you will learn in your final Capstone project to get recognized by potential employers.

Best Data Science Training institute in Bangalore

Career Programs Excellence said...

With decision-making becoming more and more data-driven, learn the skills necessary to unveil patterns useful to make valuable decisions from the data collected. Also, get a chance to work with various datasets that are collected from various sources and discover the relationships between them. Ace all the skills and tools of Data Science and step into the world of opportunities with the Best Data Science training institutes in Bangalore.

Data Science Course in Bangalore with Placement

Educational Training and Learning said...

The Data Sciences domain opened up many career opportunities for IT professionals. More and more IT professionals are looking for the best Data Science training to boost their careers. 360DigiTMG is the best place to start your technical training. We are equipped with a world-class curriculum to suit all your technical needs.

Data Science Training in Delhi

Natures Pired said...

Seo services in Gurgaon

Educational Courses said...

Develop technical skills and become an expert in analyzing large sets of data by enrolling for the Best Data Science course in Bangalore. Gain in-depth knowledge in Data Visualization, Statistics, and Predictive Analytics along with the two famous programming languages and Python. Learn to derive valuable insights from data using skills of Data Mining, Statistics, Machine Learning, Network Analysis, etc, and apply the skills you will learn in your final Capstone project to get recognized by potential employers.


Data Science Course in Delhi

Professional Courses and Training said...

The new wave of innovation that is changing the way people do business is called data science. Gain expertise in organizing, sorting, and transforming data to uncover hidden patterns Learn the essential skills of probability, statistics, and machine learning along with the techniques to break your data into a simpler format to derive meaningful information. Enroll in Data science in Bangalore and give yourself a chance to power your career to greater heights.

Data Scientist Course in Delhi

Unknown said...

thanks for your blog
https://www.trainingincoimbatore.in/sap-training-in-coimbatore.html

avsmedical said...

Medical Equipment Manufacturers in Medical Equipment Manufacturers in Kolkata should also consider shortening the distance between their factories and their customers as much as possible. If this is not possible, then a company should consider a central distribution point to concentrate shipments to allocate in an efficient manner.

Medical Equipment Manufacturers in Kolkata

Eye Care Hospital | Shree Eye Care said...

Thank you for sharing this information. Great work.
best eye hospital in Dehradun

test blog said...

very good writes. feel good after reading this. I really like this. Thank you for this blog post.
Tourist Destination Near Bali
Best Island Around Bali
Best Islands Near Bali
Near Bali Reunion Island Tour Packages
Tourist Destination Near Bali

keshav said...
This comment has been removed by the author.
BabuPrasad said...

I found your blog using msn. This is a very well written article. I'll be sure to bookmark it and come back for more useful information.software development company in chennai.Thank you for sharing this wonderful site.

BEST CS IN FARIDABAD said...

Buzziptv
best-urdu-iptv-channel-provider-in-usa
best-punjabi-iptv-channel-provider-in-usa
best-bengali-iptv-channel-provider-in-usa
best-english-iptv-channel-provider-in-usa
best-gujrati-iptv-channel-provider-in-usa
best-hindi-iptv-channel-provider-in-usa
best-malyalam-iptv-channel-provider-in-usa
best-tamil-iptv-channel-provider-in-usa
best-telugu-iptv-channel-provider-in-usa

Clark said...

Your blog brings me a great deal of fun. Good luck with the site.
lean six sigma illinois

BabuPrasad said...
This comment has been removed by the author.
BabuPrasad said...
This comment has been removed by the author.
BabuPrasad said...
This comment has been removed by the author.
Clark said...

I was exactly searching for. Thanks for such a post and please keep it up.
lean six sigma new hampshire

INDIA FREE COURSE said...

We currently provide you with Best full-body massage in Delhi. We are a reputable service provider with competitive pricing. We constantly work to provide you with the best ambiance, comfort, and hospitality.

Angel17 said...

Thanks for sharing this post. Keep sharing! ford repair parts

Clark said...

Thanks for sharing this information. Good luck with the site.
we buy ugly houses Tampa

cyphershield said...

tron smart contract audit

cyphershield said...

blockchain full node audit

Rupesh Kumar said...

Nice Content, Thanks for sharing with us. If you are looking to improve your English language skills in kuwait. Ziyyara's online English language classes in Kuwait are designed to help learners improve their spoken English skills with the guidance of expert tutors.

For more info contact +91-9654271931 | UAE +971- 505593798 or visit Learn English Online in Kuwait

anirudh said...

You must have a strong background education to become a successful expert in data science. And not only knowledge of a degree is required, but you can also learn data science course.Data science certification course in Nashik

James said...

This is great, should have seen this sooner.

homeoptions.us/we-buy-houses-carollwood/

Anonymous said...

typewriting result February 2023

Ziyyara Edutech said...

Thank you so much for a providing well content, Ziyyara's online spoken English language classes in Kuwait are a fantastic opportunity for individuals looking to improve their English communication skills.
For more info visit Spoken English Language Class in Kuwait

Ziyyara said...

Great Post! I am glad to read the article its very interesting. Ziyyara’s online home tuition in Coimbatore brings quality education. With a team of highly skilled and experienced tutors, we provide personalized one-on-one sessions tailored to your child's specific needs.
Enroll Today Home Tuition in Coimbatore

pauljin said...


koupit řidičský průkaz
Comprar Carta
acheter permis de conduire en France
belgisch rijbewijs kopen
Comprare patente
comprar carnet de conducir
rijbewijs kopen online

Rupesh kumar said...

I loved the information given above, I appreciate the writing. Enhance your learning experience with our top-notch online home tuition classes for Class 11.
For more info Contact us: +91-9654271931, +971-505593798 or visit online tutoring sites for class 11

Rupesh kumar said...

Very useful tutorials and very easy to understand. Thanks for sharing. Ziyyara Edutech brings you the perfect solution with our comprehensive online English classes in Riyadh, Saudi Arabia.
For more info visit Spoken English classes in Riyadh or Call +971505593798

Innov Touch said...

There is certainly a lot to know about this subject. thank you for sharing.. We do Website Design and Web Development Company in Chennai.

Ngoma said...

Non ancora, ho ancora bisogno del tuo aiuto
e are well known for our hard work and satisfied customers.
Comprare Patente
kupiti vozačke dozvole
cumpara permis de conducere

Rupesh Kumar said...

Very useful and information content has been shared out here, Thanks for sharing it. Are you searching for the best CBSE online tuition classes near you? Ziyyara Edutech brings you the ultimate solution for CBSE board home tuition.
For more info contact +91-9654271931 or visit Best CBSE Online Tuition Classes

Farhan Zaidi said...

This blog is really nice and informative blog, The explanation given is really comprehensive and informative. Welcome to Edtech Reader, your all-in-one resource for discovering free business listing sites tailored to the booming your Business visibility.
visit Business listing sites list

Project Center in Chennai said...

Nice blog... Thanks for sharing...
best project center in Chennai

Best software project center in chennai said...

Nice post
CSE project in chennai| python projects| java projects| android projects| web application| artificial intelligence projects| machine learning projects| block chain projects

Best Website Designing Company In Delhi said...


Web Solution Centre is a leading Web Design Company In East Delhi, offering innovative and customized web solutions to businesses of all sizes. With a team of experienced professionals, we specialize in creating visually appealing and user-friendly websites that drive results and enhance online presence.

Best Website Designing Company In Delhi said...

Web Solution Centre is a leading Website Design Company In East Delhi, offering a wide range of web design and development services. With a team of skilled professionals, they specialize in creating custom websites tailored to meet the unique needs of businesses and organizations.

Best Website Designing Company In Delhi said...

Looking for top-notch Ecommerce Web Designers In Delhi? Look no further than Web Solution Centre. Our expert team combines design and functionality to create stunning e-commerce websites that drive sales. Contact us today!

Buy Android App Reviews Online said...

Looking to boost visibility and credibility for your Android app? Buy Android App Reviews Online and see the positive impact on your app's success. Purchase authentic and high-quality app reviews for Android today to increase downloads and engagement.

Best Embedded Training in Chennai said...

Nice blog..Thanks for sharing..
Best Embedded Training in Chennai|Placement Assurance in written agreement

Balaji said...

The Builder pattern is a creational design pattern that helps in constructing complex objects step by step. In Scala, you can implement a type-safe Builder pattern using case classes and default parameter values. Here's a basic example:

```scala
// Define the product class
case class Car(make: String, model: String, year: Int, color: String)

// Define the Builder class
case class CarBuilder(make: String = "", model: String = "", year: Int = 0, color: String = "") {
def withMake(make: String): CarBuilder = this.copy(make = make)
def withModel(model: String): CarBuilder = this.copy(model = model)
def withYear(year: Int): CarBuilder = this.copy(year = year)
def withColor(color: String): CarBuilder = this.copy(color = color)

def build(): Car = Car(make, model, year, color)
}

// Example usage
val car = CarBuilder()
.withMake("Toyota")
.withModel("Camry")
.withYear(2022)
.withColor("Blue")
.build()

println(car) // Output: Car(Toyota,Camry,2022,Blue)
```

In this implementation:

- We have a case class `Car` which represents the product we want to build.
- We define a case class `CarBuilder` with default parameter values for each property of the `Car`.
- We provide methods like `withMake`, `withModel`, etc., to set the values of the properties.
- The `build` method constructs and returns the `Car` object.

This approach ensures type-safety because each method call returns a new instance of the builder with the updated property values. It also allows you to enforce required properties by omitting default values or using Option types if certain properties are optional.

Anonymous said...

Nice blog...Thanks for sharing...
Best Embedded Training in Chennai| Placement Assurance in Written Agreement| Embedded system Course in Chennai

«Oldest ‹Older   401 – 456 of 456   Newer› Newest»