Выбрать главу

var list = {

  value: 1,

  rest: {

    value: 2,

    rest: {

      value: 3,

      rest: null

    }

  }

};

В результате объекты формируют цепочку:

Списки удобны тем, что они могут делиться частью своей структуры. Например, можно сделать два списка, {value: 0, rest: list} и {value: -1, rest: list}, где list – это ссылка на ранее объявленную переменную. Это два независимых списка, при этом у них есть общая структура list, которая включает три последних элемента каждого из них. Кроме того, оригинальный список также сохраняет свои свойства как отдельный список из трёх элементов.

Напишите функцию arrayToList, которая строит такую структуру, получая в качестве аргумента [1, 2, 3], а также функцию listToArray, которая создаёт массив из списка. Также напишите вспомогательную функцию prepend, которая получает элемент и создаёт новый список, где этот элемент добавлен спереди к первоначальному списку, и функцию nth, которая в качестве аргументов принимает список и число, а возвращает элемент на заданной позиции в списке или же undefined в случае отсутствия такого элемента.

Если ваша версия nth нерекурсивна, тогда напишите её рекурсивную версию.

console.log(arrayToList([10, 20]));

// → {value: 10, rest: {value: 20, rest: null}}

console.log(listToArray(arrayToList([10, 20, 30])));

// → [10, 20, 30]

console.log(prepend(10, prepend(20, null)));

// → {value: 10, rest: {value: 20, rest: null}}

console.log(nth(arrayToList([10, 20, 30]), 1));

// → 20

Глубокое сравнение

Оператор == сравнивает переменные объектов, проверяя, ссылаются ли они на один объект. Но иногда полезно было бы сравнить объекты по содержимому.

Напишите функцию deepEqual, которая принимает два значения и возвращает true, только если это два одинаковых значения или это объекты, свойства которых имеют одинаковые значения, если их сравнивать рекурсивным вызовом deepEqual.

Чтобы узнать, когда сравнивать величины через ===, а когда – объекты по содержимому, используйте оператор typeof. Если он выдаёт "object" для обеих величин, значит нужно делать глубокое сравнение. Примите во внимание одно дурацкое исключение, существующее по историческим причинам: typeof null тоже возвращает "object".

var obj = {here: {is: "an"}, object: 2};

console.log(deepEqual(obj, obj));

// → true

console.log(deepEqual(obj, {here: 1, object: 2}));

// → false

console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));

// → true

5. Функции высшего порядка

Цу-ли и Цу-су похвалялись размерами своих новых программ. «Двести тысяч строк»,- сказал Цу-ли,- «не считая комментариев!» Цу-су ответил: «Пф-ф, моя – почти миллион строк». Мастер Юнь-Ма сказал: «Моя лучшая программа занимает пятьсот строк». Услышав это, Цу-ли и Цу-су испытали просветление.

Мастер Юнь-Ма, «Книга программирования»

Есть два способа построения программ: сделать их настолько простыми, что там очевидно не будет ошибок, или же настолько сложными, что там не будет очевидных ошибок.

Энтони Хоар, 1980, лекция на вручении премии Тьюринга

Большая программа – затратная программа, и не только из-за времени её написания. Большой размер обычно означает сложность, а сложность сбивает с толку программистов. Сбитые с толку программисты делают ошибки в программах. Большая программа означает, что багам есть где спрятаться, и их получается труднее отыскать.

Вернёмся ненадолго к двум примерам из введения. Первый самодостаточен и занимает шесть строк.

var total = 0, count = 1;

while (count <= 10) {

  total += count;

  count += 1;

}

console.log(total);

Второй основан на двух внешних функциях и занимает одну строку.

console.log(sum(range(1, 10)));

В каком из них скорее встретится ошибка?

Если мы добавим размер определений sum и range, вторая программа тоже получится большой – больше первой. Но я всё равно утверждаю, что она скорее всего будет правильной.

Это будет потому, что выражение решения непосредственно относится к решаемой задаче. Суммирование числового промежутка – это не циклы и счётчики. Это суммы и промежутки.