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.

461 comments:

«Oldest   ‹Older   401 – 461 of 461
sasi said...

This was an excellent post and very good information provided, Thanks for sharing.
Python Training in Chennai
Python Training in Bangalore
Python Training in Coimbatore
Python course in bangalore
angular training in bangalore
Web Designing Course in bangalore
python training in hyderabad
Best Python Training in Bangalore
python training in marathahalli
Python Classes in Bangalore

Gusion said...

http://62.171.145.61/anugerahtoto/
Togel Online
Poker Online
bandarq
Bandar Kometqq
capsa online
agen online qq
agen poker

Gusion said...

pelangiqq
pelangiqq
pelangiqq
pelangiqq
pelangiqq
pelangiqq
pelangiqq
pelangiqq

Gayatri said...

Thanks for taking the efforts in sharing your knowledge with us.
python certification training in Bangalore
best React JS Training course in Bangalore

Satyam said...

We are writing information for a period of time and keeping people updated about different issues. Regardless what concerns you,you can definitely see it on Blockcrux. This is the great reason why we are so popular all around the globe. Once you see this site you can find that they go right with tech and business information and gradually go on towards some generic information that covers a broad range of issues. Not only that,you can still see the entire section devoted to only health information.Then you see, there is virtually nothing under the light that isn' 't covered by the genius structure!

The India said...


PPC Service in Delhi
PPC Companies in Delhi
SEO Company in Delhi
SEO Company in Delhi NCR
SMO Company in Delhi
SMO Company in India

madhu said...

Nice article. I liked very much. All the informations given by you are really helpful for my research. keep on posting your views.
ccna course in Chennai
ccna Training in Chennai
ccna Training institute in Chennai
ccna institute in Chennai
Best CCNA Training Institute in Chennai

madhu said...

Nice article. I liked very much. All the informations given by you are really helpful for my research. keep on posting your views.
ccna course in bangalore
ccna course in marathahalli
ccna training institutes in btm
ccna course in Coimbatore
ccna course in Madurai
ccna training in madurai
ccna training in coimbatore

subha said...

Thanks for providing such a useful blog, waiting for next updates...
AWS Training in Bangalore
AWS Training in Chennai
AWS Training in BTM
AWS Training in Marathahalli
Best AWS Training in Marathahalli
Data Science Courses in Bangalore
DevOps Training in Bangalore
PHP Training in Bangalore
DOT NET Training in Bangalore
Spoken English Classes in Bangalore

renshiya said...

thanks for your information.
glass art work in pondicherry
glass painting work in Pondicherry
glass artwork in Puducherry
glass painting work in Puducherry
glass wall art in Pondicherry
glass artist in Pondicherry
church glass artwork in pondicherry
wedding hall glass artwork in Pondicherry
temple glass artwork in Pondicherry
mosque glass artwork in Pondicherry
interior glass artwork in Pondicherry
marriage brokers in nagercoil
nadar matrimony in nagercoil
thirumana thagaval maiyam in nagercoil
nagercoil matrimony
csi matrimony nagercoil
hindu matrimony in nagercoil
christian matrimony from nagercoil
matrimony in kanyakumari
matrimony in marthandam
glass art work in chennai
glass painting work in chennai
glass artwork in chennai
glass painting work in chennai
glass wall art in chennai
glass artist in chennai
church glass artwork in chennai
wedding hall glass artwork in chennai
temple glass artwork in chennai
mosque glass artwork in chennai
interior glass artwork in chennai

Kartik Web Technology said...

Kartik Web Technology is one of the most leading IT Service provider company which is listed in Gurgaon. Gurgaon is now big IT sector where lots of famous companies are located. If you want to grow your business at higher level then you need a good website to represent your self in the Marketing. Hire us to design your company's website. We will convert your all mind imagination into reality. Give us chance to serve our services.

best website designing company in Gurgaon

renshiya said...

thanks for your information.
glass art work in pondicherry
glass painting work in Pondicherry
glass artwork in Puducherry
glass painting work in Puducherry
glass wall art in Pondicherry
glass artist in Pondicherry
church glass artwork in pondicherry
wedding hall glass artwork in Pondicherry
temple glass artwork in Pondicherry
mosque glass artwork in Pondicherry
interior glass artwork in Pondicherry
marriage brokers in nagercoil
nadar matrimony in nagercoil
thirumana thagaval maiyam in nagercoil
nagercoil matrimony
csi matrimony nagercoil
hindu matrimony in nagercoil
christian matrimony from nagercoil
matrimony in kanyakumari
matrimony in marthandam
glass art work in chennai
glass painting work in chennai
glass artwork in chennai
glass painting work in chennai
glass wall art in chennai
glass artist in chennai
church glass artwork in chennai
wedding hall glass artwork in chennai
temple glass artwork in chennai
mosque glass artwork in chennai
interior glass artwork in chennai

renshiya said...

thanks for your information.
web design company in nagercoil
best web design company in nagercoil
website design company in nagercoil
web development company in nagercoil
website development company in nagercoil
web designing company in nagercoil
website designing company in nagercoil
digital marketing company in nagercoil
digital marketing service in nagercoil
ppc service in nagercoil
web design company in nagercoil
best web design company in nagercoil
web design company in nagercoil
website design company in nagercoil
web development company in nagercoil
website development company in nagercoil
web designing company in nagercoil
website designing company in nagercoil
digital marketing company in nagercoil
digital marketing service in nagercoil
ppc service in nagercoil

neethu said...

thanks for your information.
web design company in nagercoil
best web design company in nagercoil
website design company in nagercoil
web development company in nagercoil
website development company in nagercoil
web designing company in nagercoil
website designing company in nagercoil
digital marketing company in nagercoil
digital marketing service in nagercoil
digital marketing service in nagercoil
ppc service in nagercoil
best web design company in nagercoil
web design company in nagercoil
website design company in nagercoil
web development company in nagercoil
website development company in nagercoil
web designing company in nagercoil
website designing company in nagercoil
digital marketing company in nagercoil
digital marketing service in nagercoil
ppc service in nagercoil

raji kannan said...

Amazing information,thank you for your ideas.after along time i have studied an interesting information's.we need more updates in your blog.
Best online shopping sites | Online shopping in india

Packer Movers Mart said...

Packers and Movers in Nashik
Packers and Movers in Indore
Packers and Movers in Surat
Packers and Movers in Alwar
Packers and Movers in Ajmer

Unknown said...

awesome post
global asset management

renshiya said...

thanks for your information.
web design company in nagercoil
best web design company in nagercoil
website design company in nagercoil
web development company in nagercoil
website development company in nagercoil
web designing company in nagercoil
website designing company in nagercoil
digital marketing company in nagercoil
digital marketing service in nagercoil
SEO service in nagercoil
SEO company in nagercoil
Social media marketing in nagercoil
Social media service in nagercoil
ppc service in nagercoil
best web design company in nagercoil
web design company in nagercoil
website design company in nagercoil
web development company in nagercoil
website development company in nagercoil
web designing company in nagercoil
website designing company in nagercoil
digital marketing company in nagercoil
digital marketing service in nagercoil
SEO service in nagercoil
SEO company in nagercoil
Social media marketing in nagercoil
Social media service in nagercoil
ppc service in nagercoil

balu said...

try this
B Best Hair Oil
kunkumadi face Oil

Wheat Grass Powder
Balu Herbals
UB-Slim caps
B-Care Dia Powder
Noni

rightselects said...


Thank You author for providing such insightful information on travel if anyone out there is looking for top travel companies in india then, visit right selects where you’ll get the most accurate information on the top 10 travel companies in india that you can use to choose the list of top travel companies in india.

Vivek Vichu said...

Awesome blog, very informative content... Thanks for sharing waiting for next update...
Artificial Intelligence Course in Chennai
AI Training in chennai
artificial intelligence course fee in chennai
C C++ Training in Chennai
javascript training in chennai
Html5 Training in Chennai
QTP Training in Chennai
Spring Training in Chennai
DOT NET Training in Chennai

anonymus said...

https://togelhoky1.blogspot.com/
https://togelresmi8.blogspot.com/
https://togelsgphk8.blogspot.com/
https://situstogelkita.blogspot.com/
https://togelonlinejudi.blogspot.com/
https://togel2020wap.blogspot.com/

Jaswal Gupta said...

When I originally commented I appear to have clicked on the -Notify me when new comments are added- checkbox and from now on each time a comment is added I get four emails with the exact same comment. Is there an easy method you are able to remove me from that service? Cheers! KBC Head Office Number

Rahul said...

Nice Information..Thanks...

Oracle DBA Training in Chennai
Oracle DBA Training in Velachery
Best Oracle Training Institute in Chennai with Placement
Oracle Classes near me
Oracle Training Center in Chennai
Cognos Training in Chennai
Best Cognos Course in Chennai
Best MongoDB Training in Chennai
MongoDB Course in Chennai
MongoDB Training in Chennai Velachery
Ab Initio Course in Chennai
Best Ab Initio Training in Chennai
Best Ab Initio Training in Chennai Velachery
Best Ab Initio Training Insitute in Chennai
Ab Initio Course in Chennai
Best Ab Initio Course in Chennai Velachery
Ab Initio Training in Chennai

Supreme mobiles said...

Awesome post...!!!
suprememobiles

Riya Raj said...

Wonderful blog!!! Thanks for sharing this great information with us...
SEO Training in Chennai
SEO Course in Chennai
SEO Training Institute in Chennai
Best seo training in chennai
SEO training in Velachery
SEO training in Adyar
Python Training in Chennai
Software testing training in chennai
JAVA Training in Chennai

Sitelinx said...

אהבתי מאוד את סגנון הכתיבה.
מומחה קידום אתרים

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

Unknown said...

מעולה. תודה על הכתיבה היצירתית.
עריסה מתחברת

Rahuldevan said...

Awesome blog, informative content...thanks for sharing...
Html5 Training in Chennai
Html5 Courses in Chennai
Html5 Training Institutes in Chennai
Html5 Training in OMR
Html5 Training in Porur
DOT NET Training in Chennai
core java training in chennai
Hibernate Training in Chennai
Mobile Testing Training in Chennai
SAS Training in Chennai

Unknown said...

פוסט מרענן במיוחד. לגמרי משתף.
ברוקר לי

Venkatesh CS said...

Excellent Blog. Thank you so much for sharing.
salesforce training in chennai
salesforce training in omr
salesforce training in velachery
salesforce training and placement in chennai
salesforce course fee in chennai
salesforce course in chennai
salesforce certification in chennai
salesforce training institutes in chennai
salesforce training center in chennai
salesforce course in omr
salesforce course in velachery
best salesforce training institute in chennai
best salesforce training in chennai

Chiến NHX said...

ok anhi

máy xông tinh dầu phun sương bottle cap

may xong phong ngu

may xong huong

may phun suong nao tot

Lopa said...

Thanks you for sharing this unique useful information content with us. Really awesome work. keep on blogging.
Python Training
Digital Marketing Training
AWS Training

sachin sinhmar said...

You can get information about mywifiext setup here. Our experts will guide you very easy steps so that you can setup your new extender by yourself

mywifiext

mywifiext local

mywifiext.local

www.mywifiext.local

sachin sinhmar said...

visit here for new wireless device setup at 123. hp. com. get setp by setp guidance here.

san diego seo expert

seo company in india

sachin sinhmar said...

We are one of the best seo company in India. You can contact us for pocket friendly seo services in India.
123.hp.com

123.hp.com/setup

sachin sinhmar said...

We are on of the best san diego seo expert. You can contact us for best seo experts in san diego.
mywifiext

mwifiext.net

Kamal S said...

Excellent explanation of how to create objects in Scala. You've made the concept easily understandable through your scotch example. As always, great content. Looking forward to your next blog.

Mobile App Development Company in Chennai

Dogma said...

כתיבה מעולה, אהבתי. אשתף עם העוקבים שלי.
מציאות רבודה

Unknown said...

python course in coimbatore
python training in coimbatore
java course in coimbatore
java training in coimbatore
android course in coimbatore
android training in coimbatore
php course in coimbatore
php training in coimbatore
digital marketing course in coimbatore
digital marketing training in coimbatore
software testing course in coimbatore
software testing training in coimbatore

Unknown said...

python course in coimbatore
java course in coimbatore
python training in coimbatore
java training in coimbatore
php course in coimbatore
php training in coimbatore
android course in coimbatore
android training in coimbatore
datascience course in coimbatore
datascience training in coimbatore
ethical hacking course in coimbatore
ethical hacking training in coimbatore
artificial intelligence course in coimbatore
artificial intelligence training in coimbatore
digital marketing course in coimbatore
digital marketing training in coimbatore
embedded system course in coimbatore
embeddedsystem training in coimbatore

Ben Johnson said...

Thanks for Sharing...Nicely Written Blog...

Artificial Intelligence Training in Chennai
Best Artificial Intelligence Training in Chennai BITA Academy
artificial Intelligence certification training in chennai
artificial Intelligence training institutes in chennai
artificial Intelligence course in chennai
artificial Intelligence training course in chennai
artificial Intelligence course in chennai with placement
artificial Intelligence course fees in chennai
AI Training in Chennai
artificial Intelligence training in omr
artificial Intelligence training in velachery
Best artificial Intelligence course fees in chennai
artificial Intelligence course in omr
artificial Intelligence course in velachery
Best artificial Intelligence course in chennai

How To Asset Manage said...

Thanks for sharing the info
global asset management seoul

Global Asset Management Udemy said...

Thank you for sharing.
global asset management

colaninfotech said...

I have scrutinized your blog its engaging and imperative. I like your blog.
custom application development services
Software development company
software application development company
offshore software development company
custom software development company

Mr Rahman said...

Really Nice Article & Thanks for sharing.

Oflox Is The Best Website Design & Development Company In Dehradun

Venkatesh CS said...

Excellent Blog. Thank you so much for sharing.
salesforce training in chennai
salesforce training in omr
salesforce training in velachery
salesforce training and placement in chennai
salesforce course fee in chennai
salesforce course in chennai
salesforce certification in chennai
salesforce training institutes in chennai
salesforce training center in chennai
salesforce course in omr
salesforce course in velachery
best salesforce training institute in chennai
best salesforce training in chennai

Poker Online 88 said...

Poker Deposit Pulsa
DominoQQ Pulsa
IDN Poker Pulsa
Poker88 Pulsa
Poker Pulsa
QQ Pulsa
Ceme Online
Situs Poker Deposit Pulsa

svrtechnologies said...

Thanks for sharing such an useful and informative stuff...

Selenium Testing Training
Selenium Tutorials

Unknown said...

python course in coimbatore
python training in coimbatore
java course in coimbatore
java training in coimbatore
android course in coimbatore
android training in coimbatore
php course in coimbatore
php training in coimbatore
digital marketing course in coimbatore
digital marketing training in coimbatore
software testing course in coimbatore
software testing training in coimbatore

Unknown said...

python course in coimbatore
java course in coimbatore
python training in coimbatore
java training in coimbatore
php course in coimbatore
php training in coimbatore
android course in coimbatore
android training in coimbatore
datascience course in coimbatore
datascience training in coimbatore
ethical hacking course in coimbatore
ethical hacking training in coimbatore
artificial intelligence course in coimbatore
artificial intelligence training in coimbatore
digital marketing course in coimbatore
digital marketing training in coimbatore
embedded system course in coimbatore
embeddedsystem training in coimbatore

Babas Judi said...

I am glad to be one of several visitants on this outstanding internet site (:, appreciate it for posting . https://royalcbd.com/cbd-legal-status/

Babas Judi said...

I am glad to be one of several visitants on this outstanding internet site (:, appreciate it for posting . https://royalcbd.com/cbd-legal-status/

Supreme mobiles said...

Nice post today gold rate

telkahost said...

شرکت تلکا هاست پیشرو در میزبانی انواع وب سایت و هم چنین دارای انواع هاست فوق ارزان با کنترل پنل سی پنل میباشد.حتما از سایت ما دیدن کنید و از قیمت های مناسب برای انواع سرویس های وب و ثبت انواع دامنه شگفت زده شوید.

Indhu said...

thanks for sharing this informations.
Selenium Training in Coimbatore

Software Testing Course in Coimbatore

python training institute in coimbatore

data science training in coimbatore

android training institutes in coimbatore

ios training in coimbatore

aws training in coimbatore

AK said...

It was great to read your article. Read mine here
Mirakee
Framasphere
Mundoalbiceleste

domnic wade said...

Thank you so much for this excellent Post and all the best for your future. I hope you and we will be sharing more thoughts and keep writing more like this one.

office/setup
mcafee/activate

Unknown said...

python course in coimbatore
python training in coimbatore
java course in coimbatore
java training in coimbatore
android course in coimbatore
android training in coimbatore
php course in coimbatore
php training in coimbatore
digital marketing course in coimbatore
digital marketing training in coimbatore
software testing course in coimbatore
software testing training in coimbatore

all bank csp said...

Thanks for sharing this articles
all bank csp
csp registration
sbi csp registration
digital csp registration

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