A Domain Specific Language is a language specialized for a particular application domain. The structure of such a language reflects the specifics of the tasks solved with its help. A prime example of such a language is the jQuery, library, which most programmers are familiar with (or at least have heard of). You can use it to control the display and behavior of webpages:
// Calling methods via a point in one line
// The item with the class box is set a text color and height
('.box').css('color', '#333').height(200);
Here, the DSL is created using Fluent Interface. With this way of organizing code, the processing looks like a chain of methods in a row. From the technical side, there are two ways to create such an interface.
This
The first method is based on returning this
from methods that are involved in building chains. this
is a reference to the object in whose context the method is called, and therefore it can be returned as a normal value.
class Collection {
constructor(coll) {
this.coll = coll;
}
map(fn) {
// The map() array method is called on the collection
this.coll = this.coll.map((element) => fn(element));
return this;
}
filter(fn) {
this.coll = this.coll.filter((element) => fn(element));
return this;
}
// Returns the collection itself, not this.
// This method is always the last one in the Collection call chain.
all() {
return this.coll;
}
}
const cars = new Collection([
{ model: 'rapid', year: 2016 },
{ model: 'rio', year: 2013 },
{ model: 'mondeo', year: 2011 },
{ model: 'octavia', year: 2014 },
]);
cars.filter((car) => car.year > 2013)
.map((car) => car.model);
cars.all(); // [rapid, octavia]
This method has one serious disadvantage – the object changes. This means that you can't just re-use a collection object for different samples, because they'll begin to overlap.
In practice, a different approach is often used, which we were introduced to in the last course. All you need to do is add some functionality to the OOP, i.e., don't return this
, but rather create a new object of the same type with the updated collection.
class Collection {
constructor(coll) {
this.coll = coll;
}
map(fn) {
const newColl = this.coll.map((element) => fn(element));
return new Collection(newColl);
}
filter(fn) {
const newColl = this.coll.filter((element) => fn(element));
return new Collection(newColl);
}
// Returns the collection itself, not this.
// This method is always the last one in the Collection call chain.
all() {
return this.coll;
}
}
const cars = new Collection([
{ model: 'rapid', year: 2016 },
{ model: 'rio', year: 2013 },
{ model: 'mondeo', year: 2011 },
{ model: 'octavia', year: 2014 },
]);
const filteredCars = cars.filter((car) => car.year > 2013);
const mappedCars = filteredCars.map((car) => car.model);
mappedCars.all(); // [rapid, octavia]
cars.all();
// [
// { model: 'rapid', year: 2016 },
// { model: 'rio', year: 2013 },
// { model: 'mondeo', year: 2011 },
// { model: 'octavia', year: 2014 },
// ]
Each call now returns a new object. This code is much safer to use and allows new collections to be reused without issue. Changing one will not automatically change all the others.
Recommended materials
Are there any more questions? Ask them in the Discussion section.
The Hexlet support team or other students will answer you.
For full access to the course you need a professional subscription.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.