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.

655 comments:

«Oldest   ‹Older   601 – 655 of 655
s said...


Plastic Corrugated Sheet - Corpac is India’s No.1 Plastic Corrugated Sheets & Box Manufacturer, suppliers in Qatar, New Zealand, Germany, Philippines, UK and Spain.

Data Science Training in Bangalore said...

Very good message. I stumbled across your blog and wanted to say that I really enjoyed reading your articles. Anyway, I will subscribe to your feed and hope you post again soon.

Business Analytics Course

Techinstitute said...

I wanted to leave a little comment to support you and wish you the best of luck. We wish you the best of luck in all of your blogging endeavors.

Data Analytics Course in Bangalore

Data Science Course in Bangalore said...

Actually I read it yesterday I looked at most of your posts but I had some ideas about it . This article is probably where I got the most useful information for my research and today I wanted to read it again because it is so well written.
Data Science Course in Bangalore

Petersons said...

Thanks for the update you have nicely covered this topic. keep it up


Gloss Garage Doors

360DigiTMGNoida said...

This Blog is very useful and informative.
data scientist course in noida

360digitmgdelhi said...

I read that Post and got it fine and informative. Please share more like that...
data science course delhi

stock market expert in delhi said...

Lakshay consultant is known for giving best services to there clients with full of honesty and dignity . Lakshay consultancy deals in Equities, Currencies, Commodities, IPOs, Stock Market Expert in Delhi , Bonds/NCDs through NSE, BSE, MCXSX, USE. Depository services through NSDL and CDSL.

arshiya said...

I enjoyed reading your article. Thanks for taking the time to post such a valuable article.
seo content writing tips
language similar to english
salesforce basics
star certification
hacking books
interview questions on tableau

Rick said...

Excellent, and thanks for sharing this.

Rick Anderson

Petersons said...

Cool blog you got here and thank you for the valuable info.


garage door repair

R ADK said...

Organic Chemistry tutor
Organic chemistry
online tutor

360DigiTMG said...

This is a great motivational article. In fact, I am happy with your good work. They publish very supportive data, really. Continue. Continue blogging. Hope you explore your next post
data scientist malaysia

Petersons said...

This is really great news. Thank you for sharing it with us!


garage door blog

Inyo Fine Cannabis Dispensary said...

Your service is really cool.
e-learning

Petersons said...

This is a brilliant writing and very pleased to find this site. I couldn’t discover to much different information on your blog. I will surely be back again to look at some other important posts that you have in future.


garage door repair Guelph

shakthi said...

digital marketing company in chennai, seo company in chennai, ecommerce website development company in chennai

Nictcspbc said...


Thank you for sharing such a great information. Here NICTCSPBC is the best service provider of SBI Kiosk Banking in India which provides the best services of Kiosk Banking at reliable prices to their customers.

Csp registration
Csp apply

Unknown 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.


garage door repair Scarborough 

Tech Institute said...

Fantastic blog with excellent information and valuable content just added your blog to my bookmarking sites thank for sharing.
Data Science Course in Chennai

Tech said...

I really enjoy every part and have bookmarked you to see the new things you post. Well done for this excellent article. Please keep this work of the same quality.
Artificial Intelligence course in Chennai

madhu said...

Hi guys, here we are servicing all home appliances brands .we is servicing to here. Like washing machine, refrigerator, air conditioner, microwave oven, the coolers are accessible in essentially three sorts they resemble. Whirlpool Washing machine Service Center in Neral Single door, double door entryway and one next to the other entryway coolers. The new age ridges are accompanying some incredible choices like. Convertible alternatives this is perhaps the main innovation in the coolers. Whirlpool Washing machine Service Center in Chunabhatti Also, the clothes washer is perhaps the best item at present days. Whirlpool Washing machine Service Center in Tilak Nagar The clothes washers will wash the materials rapidly and clothes washers will eliminate the actual pressure of the clients.

madhu said...

Single drum will only provide washing process and dual drum will provide the washing process along with draining function. Whirlpool Washing machine Service Center in ChemburThe single drum will occupies very less space to store comparing to double drum washing machines the new clothes washers are updated with many progressed washing highlights. Whirlpool Washing Machine Service Center in Mira RoadThe utilization of the climate control systems are mostly increments in summer seasons. Whirlpool Washing machine Service Center in KandivaliSince climate control systems are use for take just cool air. What's more, these forced air systems can run for quite a long time with no issue and this

madhu said...

will gives its service the microwave gives any issues microwave not warming is a typical issue. Whirlpool Washing machine Service Center in MaladThis duct AC has the best capacity of cooling so this will provide the fast cooling to the room. Whirlpool Washing Machine Service Center in MulundThese air conditioners are one of the great appliances in this generation and the air conditioners will requires a good maintenanceWhirlpool Washing machine Service Center Jogeshwari So these air conditioners will need a best servicing to run it properly. Home-appliances repair our professionals accomplished to in this documented. Whirlpool Washing Machine Service Center in Dombivli They are accomplished and they are providing for you best support of your item.

madhu said...

Subterranean insect sort of issue they will tackier of it they have colossal information to take care of the issue. Whirlpool Refrigerator Service Center in MankhurdFurthermore, we are giving best proposals to you on month general assistance guarantee and multi month service guarantee to your items. Whirlpool Refrigerator Service Center in Vashi We are giving every minute of every day hour’s service to the clients. Best help and poor clients will fulfill with our service. Whirlpool Refrigerator Service Center in Churchgate One of the best services to the customers. Our supervisory group has over 10 years of involvement service suppliers; we are consistently offers the best types of assistance to the customers. Whirlpool Refrigerator Service Center in Marine LinesWith the best specialized unit we help to tackle the any major and minor fixes in the item. And we take the all safety measures of covid-19. And our technicians will give to you best services to your products.

Michael Oliver said...

Good post,

Digital Marketing Companies in Chennai, Website Design Companies in Chennai, SEO Companies in Chennai, Digital Marketing Company Chennai, Web Design Company Chennai.

https://www.wisewebtek.com

Petersons said...

חשבתי שאתקל בסתם עוד מאמר שטחי. כמובן שטעיתי.

פוליש קריסטל

Inyo Fine Cannabis Dispensary said...

Good post. Thank you for sharing this with us.
News For Finance

Nisha said...

Website Development Services In Delhi
Web Cloud Technology offers world class web development services in Delhi, India. We promise to take care of all your website development needs by providing high end and modern solutions that are inventive and profitable.

For more information visit on:-
https://webcloudtechnologies.com/

Nisha said...

Website Development Services In Delhi
Web Cloud Technology offers world-class web development services in Delhi, India. We promise to take care of all your website development needs by providing high-end and modern solutions that are inventive and profitable.

For more information visit on:-
https://webcloudtechnologies.com/

Data Science Training in Hyderabad said...

Fantastic Site with useful and unique content looking forward to the next update thank you.
Data Science Training in Hyderabad

David Smith said...

Good writing...keep posting dear friend

אטרקציות לבר מצווה

Data Science Institute Bangalore said...

It's very educational and well-written content for a change. It's good to see that some people still understand how to write a great article!
Data Science Institute in Bangalore

Data Analytics Courses in Bangalore 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.

Data Analytics Courses in Bangalore

Tech said...

I really enjoy every part and have bookmarked you to see the new things you post. Well done for this excellent article. Please keep this work of the same quality.
Artificial Intelligence course in Chennai

Data Science in Pune said...

You can comment on the blog ordering system. You should discuss, it's splendid. Auditing your blog would increase the number of visitors. I was very happy to find this site. Thank you...

Data Science Classes in Pune

Jack Harry said...

I am looking for the informative post thanks for share it.
Finance Reports 24/7

Data Science Training said...

Excellent site with great content and very informative. I would like to thank you for the efforts you have made in writing.
Data Science Training in Bangalore

Best Data Science Courses said...

This is an excellent article. I like this topic. This site has many advantages. I have found a lot of interesting things on this site. It helps me in so many ways. Thanks for posting this again.

Best Data Science Courses in Bangalore

Data Science Training in Bangalore said...

I really enjoyed reading your blog. It was very well written and easy to understand. Unlike other blogs that I have read which are actually not very good. Thank you so much!

Data Science Training in Bangalore

Unknown said...

אני לא מסכים עם כל מה שכתוב, אבל מאמר מעניין מאוד

דוכני מזון לאירועים 

Data Analytics Courses in Bangalore said...

Very good message. I stumbled across your blog and wanted to say that I really enjoyed reading your articles. Anyway, I will subscribe to your feed and hope you post again soon.

Data Analytics Courses in Bangalore

Data Science in Bangalore said...

A good blog always contains new and exciting information and as I read it I felt that this blog really has all of these qualities that make a blog.

Data Science In Bangalore

Data Science in Pune said...

I enjoyed the coursework, the presentations, the classmates and the teachers. And because my company reimbursed 100% of the tuition, the only cost I had to pay on my own was for books and supplies. Otherwise, I received a free master's degree. All I had to invest was my time.
Data Science Training in Pune

Jack Harry said...

Nice content
greenairductcleaningpittsburgh.com

Data Science Institutes in Bangalore said...

Really impressed! Everything is a very open and very clear clarification of the issues. It contains truly facts. Your website is very valuable. Thanks for sharing.

Business Analytics Course in Bangalore

Unknown said...

I was very pleased to find this site.I wanted to thank you for this great read!! I definitely enjoying every little bit of it and I have you bookmarked to check out new stuff you post.


commercial hood cleaning pittsburgh 

David Smith said...

This is a really good read for me, Must admit that you are one of the best bloggers I ever saw.Thanks for posting this informative article.

garage door repair in Evergreen

Data Science Institutes in Bangalore said...

I'm glad I found this blog! Occasionally, students want to know the keys to writing productive literary essays. Your first-class knowledge of this great job can become a suitable foundation for these people. Good

Data Science Institutes in Bangalore

Tech said...

I really enjoy every part and have bookmarked you to see the new things you post. Well done for this excellent article. Please keep this work of the same quality.
Artificial Intelligence course in Chennai

Tech Science said...

Fantastic blog with excellent information and valuable content just added your blog to my bookmarking sites thank for sharing.
Data Science Course in Chennai

Jack Harry said...

This amazing post impressed me
garage door repair in pittsburgh

Business said...

Nice Information. Thanks for sharing. Reach us For the same,
WEB Technologies,
IMAGE Editing Services,
LEATHER Products.

UltraGITS said...

Nice Information. Thanks for sharing. Reach Our Services

Data Science Bangalore said...

I really enjoy reading all of your blogs. It is very helpful and very informative and I really learned a lot from it. Definitely a great article
Data Science Course Bangalore

«Oldest ‹Older   601 – 655 of 655   Newer› Newest»