Javascript →
JavaScript как язык с утиной типизацией
JavaScript это мощный язык с динамической типизацией и это позволяет писать код с утиной типизацией. В двух словах утиная типизация позволяет выполнять функцию для обьектов которые обладают нужными нам свойствами (т.к. js функицональный язык то и метод является полноправным свойством). Я уверен что многие используют утиную типизацию, но я думаю данная заметка будет все равно интересна некоторому подмножеству множества аудитории хабра. В этой заметке мы проследим эволюцию функции range из работающей только с числами до функции которая работает с любыми обьектами.
Итак, давайте предположим что мы пишем функцию range, думаю не стоит расписывать что она делает. Вероятнее всего вы уже встречались с этой функцией, и выглядела она примерно так:
Окей, функция с блеском возвращает массив в нужном нам интервале:
Но давайте попробуем передать функции не число.
В других реализациях ответ мог содержать список из NaN, но это не суть важно.
Текущяя реализация range плоха тем что она работает только с числами. Давайте будем использовать числа не как числа, а как элемент перечисляемой последовательности, для этого добавим метод succ в прототип числа:
Теперь перепишем range:
Окей, функция отрабатывает как и раньше, и также не поддерживает работу со строками, давайте допишем метод succ для строк.
Окей, метод работает (хотя и не достаочно хорошо, но это сейчас не важно), но мы все равно не можем получить последовательности для строк, давайте немного изменим нашу реализуцию range:
Хорошо, теперь range работает и для строк:
Но у нас все еще есть проблемы, давайте попробуем написать простой класс с методом succ:
Теперь если мы попробуем запустить Range для двух экземпляров класса Digit то у нас ничего не выйдет, и причина в том что foo === bar только тогда когда foo и bar есть ссылки на один и тот же обьект, и даже если foo и bar обладают одинаковыми свойствами они все равно не равны, и для этого есть причины. Так что давайте перепишем наш range немного подругому:
А также допишем метод equalTo для класса Digit.
И давайте протестируем:
Теперь мы можем смело сказать что наша реализация функции range, будет работать с любым обьектом который удовлетворяет её потребностям.
Хочу заметить что в данной заметке я расширял прототипы, с одной стороны это плохо, но с другой стороны это просто, но давайте расматривать это как генную инженерию, вы можете этим пользоваться, а можете и не пользоваться ;)
Итак, давайте предположим что мы пишем функцию range, думаю не стоит расписывать что она делает. Вероятнее всего вы уже встречались с этой функцией, и выглядела она примерно так:
function range (a, b) {
var list = [];
do {
list.push(a);
} while (++a < b)
return list;
}
Окей, функция с блеском возвращает массив в нужном нам интервале:
range(0, 4); //=> [0, 1, 2, 3]
Но давайте попробуем передать функции не число.
range('a', 'f'); //=> ["a"]
В других реализациях ответ мог содержать список из NaN, но это не суть важно.
Текущяя реализация range плоха тем что она работает только с числами. Давайте будем использовать числа не как числа, а как элемент перечисляемой последовательности, для этого добавим метод succ в прототип числа:
Number.prototype.succ = function () {
return this + 1;
};
Теперь перепишем range:
function range (a, b) {
var list = [];
do {
list.push(a);
a = a.succ();
} while (a < b)
return list;
}
Окей, функция отрабатывает как и раньше, и также не поддерживает работу со строками, давайте допишем метод succ для строк.
String.prototype.succ = function () {
return this.slice(0, this.length - 1) +
String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
}
Окей, метод работает (хотя и не достаочно хорошо, но это сейчас не важно), но мы все равно не можем получить последовательности для строк, давайте немного изменим нашу реализуцию range:
function range (a, b) {
var list = [];
do {
list.push(a);
a = a.succ();
} while (a !== b)
return list;
}
Хорошо, теперь range работает и для строк:
range('a', 'f'); //=> ['a', 'b', 'c', 'd', 'e']
Но у нас все еще есть проблемы, давайте попробуем написать простой класс с методом succ:
function Digit (d) {
this.digit = d % 10;
}
Digit.prototype = {
succ : function () {
return new Digit(this.digit + 1);
}
};
Теперь если мы попробуем запустить Range для двух экземпляров класса Digit то у нас ничего не выйдет, и причина в том что foo === bar только тогда когда foo и bar есть ссылки на один и тот же обьект, и даже если foo и bar обладают одинаковыми свойствами они все равно не равны, и для этого есть причины. Так что давайте перепишем наш range немного подругому:
function range (a, b) {
var list = [];
do {
list.push(a);
a = a.succ();
} while (a.equalTo !== undefined ? a.equalTo(b) : a !== b)
return list;
}
А также допишем метод equalTo для класса Digit.
equalTo : function (a) {
return this.digit === a.digit;
}
И давайте протестируем:
range(new Digit(3), new Digit(8)); //=> [{digit:3}, {digit:4}, {digit:5}, {digit:6}, {digit:7}]
Теперь мы можем смело сказать что наша реализация функции range, будет работать с любым обьектом который удовлетворяет её потребностям.
Хочу заметить что в данной заметке я расширял прототипы, с одной стороны это плохо, но с другой стороны это просто, но давайте расматривать это как генную инженерию, вы можете этим пользоваться, а можете и не пользоваться ;)
04.09.2011 21:51+0400