Операторы ?., ?? и |>: будущие возможности JavaScript, которые вам понравятся

https://medium.freecodecamp.org/here-are-three-upcoming-changes-to-javascript-that-youll-love-387bce1bfb0b
  • Перевод
Джастин Фуллер, автор материала, перевод которого мы сегодня публикуем, предлагает рассмотреть три новых возможности, появление которых ожидается в JavaScript в обозримом будущем. Сначала он расскажет о процессе развития JS, а после этого представит обзор этих возможностей и покажет примеры их использования. Речь пойдёт об операторах ?., ?? и |>.

О стандарте ECMAScript и развитии JavaScript


Если вы уже знакомы с особенностями деятельности рабочей группы ECMA TC39, с тем, как она осуществляет отбор и обработку предложений о совершенствовании JavaScript, вы вполне можете пропустить этот раздел. Если же вы из тех, кому интересно об этом узнать — вот краткий обзор того, чем занимается TC39.

JavaScript — это реализация стандарта, называемого ECMAScript, который был создан для стандартизации реализаций языка, которые появились в ранние годы существования веб-браузеров.

Существует восемь редакций стандарта ECMAScript и семь релизов (четвёртая редакция стандарта не выходила, после третьей сразу идёт пятая). Разработчики JavaScript-движков приступают к реализации новшеств языка после выхода стандарта. Здесь можно увидеть, что не каждый движок реализует все возможности, при этом некоторым движкам для введения новшеств требуется больше времени, чем другим. Хотя такое положение дел и не идеально, это, всё же, лучше, чем полное отсутствие стандартов.

Предложения


Новые возможности языка изначально представляют собой так называемые предложения, которые, до включения в стандарт, проходят процедуру согласования. Если предложение признано полезным и обратно совместимым со всем тем, что уже существует, оно будет включено в следующую редакцию стандарта.

Процесс рассмотрения предложений состоит из пяти шагов, описанных в этом документе. В самом начале предложение находится в состоянии черновика (strawman), это то же самое, что и Stage 0. На этом шаге предложение либо ещё не представлено техническому комитету, либо оно ещё не отвергнуто, но пока ещё не соответствует критериям, позволяющим перейти к следующему этапу согласования. Те возможности, о которых мы будем говорить ниже, уже прошли Stage 0.

Мне хотелось бы порекомендовать читателям избегать использования в продакшне новшеств JS, предложения, описывающие которые, находятся на этапе Stage 0. Лучше дождаться перехода их к более стабильным этапам согласования. Цель этой рекомендации заключается в том, чтобы помочь вам избежать проблем в том случае, если предложение будет отвергнуто или окажется очень сильно изменённым.

Система тестирования


Материалы, в которых рассказывают о новых возможностях языков программирования, часто содержат фрагменты кода, вырванные из контекста. Иногда эти возможности используются для создания неких учебных приложений. Однако, ни того, ни другого мы делать здесь не будем. Так как я — большой поклонник TDD, я полагаю, что лучший способ изучения некоей новой технологии заключается в её тестировании.

Мы будем использовать здесь, для освоения описываемых возможностей JS, то, что Джим Ньюкирк называет обучающими тестами. Такие тесты построены не на утверждениях о коде, написанном на некоем языке. Они построены на анализе утверждений, касающихся самого языка. Тот же подход может оказаться полезным и при изучении API сторонних разработчиков, и при освоении любой возможности языка.

Транспиляторы


Если вы не знакомы с транспиляторами, то у вас, возможно, возникнет вопрос о том, как мы собираемся использовать возможности языка, которые пока не реализованы. Тут надо отметить, что JavaScript постоянно развивается, а для реализации в распространённых движках его новых возможностей, интересных программистам, нужно время. В результате в экосистеме JS существует такое понятие, как транспиляторы.

Они позволяют преобразовывать код, написанный на JS с использованием новейших возможностей, которые, например, ещё не включены в стандарты и не реализованы популярными движками, в JS-код, который понимают существующие среды выполнения JavaScript-программ. Это позволят, например, использовать в коде даже предложения уровня Stage 0, а то, что получится после обработки кода транспилятором, можно будет выполнить, например, в современных браузерах или в среде Node.js. Делается это путём преобразования нового кода таким образом, что он, для среды исполнения, выглядит как код, написанный на одной из поддерживаемых ей версий JS.

Одним из самых популярных JavaScript-транспиляторов является Babel, совсем скоро мы поговорим о том, как им пользоваться.

Подготовка рабочей среды


Если вы хотите самостоятельно повторить всё то, о чём мы будем говорить — вы можете это сделать, настроив npm-проект и установив необходимые зависимости. Предполагается, что сейчас у вас уже установлены Node.js и NPM.

Для того чтобы подготовиться к нашим экспериментам, выполните следующую команду, находясь в отведённой для этих экспериментов директории:

npm init -f && npm i ava@1.0.0-beta.3 @babel/preset-env@7.0.0-beta.42 @babel/preset-stage-0@7.0.0-beta.42 @babel/register@7.0.0-beta.42 @babel/polyfill@7.0.0-beta.42 @babel/plugin-transform-runtime@7.0.0-beta.42 @babel/runtime@7.0.0-beta.42 --save-dev

Затем добавьте следующее в файл package.json:

"scripts": {
  "test": "ava"
},
"ava": {    
  "require": [      
    "@babel/register",
    "@babel/polyfill"   
  ]  
}

Далее, создайте файл .babelrc со следующим содержимым:

{  
  "presets": [    
    ["@babel/preset-env", {      
      "targets": {        
        "node": "current"      
      }    
    }],    
    "@babel/preset-stage-0"  
  ],  
  "plugins": [    
    "@babel/plugin-transform-runtime"
  ]
}

Теперь вы готовы к тому, чтобы писать тесты, исследующие новые возможности JS.

1. Оператор ?.


В ходе написания приложений на JavaScript мы постоянно работаем с объектами. Однако иногда эти объекты не имеют той структуры, которой мы от них ожидаем. Вот, например, объект с данными. Подобный объект можно получить, скажем, в результате запроса к базе данных или при обращении к некоему API.

const data = {
  user: {
    address: {
      street: 'Pennsylvania Avenue',
    }, 
  },
};

Этот объект описывает пользователя, прошедшего регистрацию. А вот — похожий объект, но в данном случае пользователь, которого он описывает, регистрацию не завершил.

const data = {
  user: {},
};

Если попытаться получить доступ к свойству street объекта address, вложенного в этот «неполный» объект, рассчитывая на то, что он будет выглядеть так же, как объект, содержащий все необходимые данные, можно столкнуться с такой ошибкой:

console.log(data.user.address.street); // Uncaught TypeError: Cannot read property 'street' of undefined

Для того чтобы этого избежать, в текущих условиях необходимо использовать, для обращения к свойству street, такую конструкцию:

const street = data && data.user && data.user.address && data.user.address.street;
console.log(street); // undefined

Я полагаю, что всё это, во-первых, плохо выглядит, во-вторых, это тяжело писать, а в-третьих — подобные конструкции оказываются слишком длинными.

Именно в таких ситуациях как нельзя кстати оказываются опциональные последовательности или опциональные цепочки (optional chaining), представленные оператором, выглядящим как знак вопроса с точкой (?.).

console.log(data.user?.address?.street); // undefined

Выглядит это лучше, да и строить такие конструкции проще. Уверен, вы с этим утверждением согласитесь. Убедившись в полезности этой новой возможности, исследуем её. Напишем тест, поместив код в файл optional-chaining.test.js. Мы, в этом разделе, будем постепенно дополнять этот файл новыми тестами.

import test from 'ava';

const valid = {
  user: {
    address: {
      street: 'main street',
    },
  },
};

function getAddress(data) {
  return data?.user?.address?.street;
}

test('Optional Chaining returns real values', (t) => {
  const result = getAddress(valid);
  t.is(result, 'main street');
});

Этот тест позволяет нам убедиться в том, что оператор ?., в том случае, если объект выглядит так, как ожидается, работает точно так же, как способ доступа к свойствам объекта через точку. Теперь проверим поведение этого оператора в ситуации, когда объект не является тем, чем мы его считаем.

test('Optional chaining returns undefined for nullish properties.', (t) => {
  t.is(getAddress(), undefined);
  t.is(getAddress(null), undefined);
  t.is(getAddress({}), undefined);
});

А вот как опциональные последовательности работают при применении их для доступа к элементам массивов:

const valid = {
  user: {
    address: {
      street: 'main street',
      neighbors: [
        'john doe',
        'jane doe',
      ],
    },
  },
};

function getNeighbor(data, number) {
  return data?.user?.address?.neighbors?.[number];
}

test('Optional chaining works for array properties', (t) => {
  t.is(getNeighbor(valid, 0), 'john doe');
});

test('Optional chaining returns undefined for invalid array properties', (t) => {
  t.is(getNeighbor({}, 0), undefined);
});

Иногда случает так, что нам неизвестно, реализована ли в объекте какая-то функция. Например, такая ситуация распространена при работе с браузерами. Устаревшие браузеры могут не содержать реализаций неких функций. Благодаря оператору ?. мы можем узнать, реализована ли в объекте интересующая нас функция или нет. Вот как это сделать:

const data = {
  user: {
    address: {
      street: 'main street',
      neighbors: [
        'john doe',
        'jane doe',
      ],
    },
    getNeighbors() {
      return data.user.address.neighbors;
    }
  },
};

function getNeighbors(data) {
  return data?.user?.getNeighbors?.();
}
  
test('Optional chaining also works with functions', (t) => {
  const neighbors = getNeighbors(data);
  t.is(neighbors.length, 2);
  t.is(neighbors[0], 'john doe');
});

test('Optional chaining returns undefined if a function does not exist', (t) => {
  const neighbors = getNeighbors({});
  t.is(neighbors, undefined);
});

Выражения не будут выполнены, если в цепочке что-то не так. Если говорить о внутренней реализацией этого механизма, то наше выражение преобразуется примерно в следующее:

value == null ? value[some expression here]: undefined;

В результате после оператора ?. не будет выполнено ничего в том случае, если значение представлено в виде undefined или null. Взглянуть на это правило в действии можно с помощью следующего теста:

let neighborCount = 0;

function getNextNeighbor(neighbors) {
  return neighbors?.[++neighborCount];
}
  
test('It short circuits expressions', (t) => {
  const neighbors = getNeighbors(data);
  t.is(getNextNeighbor(neighbors), 'jane doe');
  t.is(getNextNeighbor(undefined), undefined);
  t.is(neighborCount, 1);
});

Как видите, опциональные последовательности позволяют уменьшить потребность в конструкциях if, в сторонних библиотеках вроде lodash, и в использовании неуклюжих конструкций, в которых применяется &&.

▍Оператор?.. и производительность


Вероятно, вы заметили, что использование опциональных последовательностей означает дополнительную нагрузку на систему. Всё дело в том, что каждый раз, когда используется оператор ?., система вынуждена проводить дополнительные проверки. Злоупотребление оператором ?. может заметно повлиять на производительность программ.

Я посоветовал бы вам использовать эту возможность вместе с некоей системой проверки, которая позволяет анализировать объекты при их получении откуда-либо или при их создании. Это сократит необходимость в использовании конструкции ?. и ограничит её воздействие на производительность.

2. Оператор ??


Вот несколько распространённых операций, с которыми можно встретиться в процессе работы с JavaScript. Обычно они выглядят как единое выражение, смысл которого заключается в следующем:

  1. Проверка значения на undefined и null.
  2. Указание значения, используемого по умолчанию.
  3. Обеспечение того, что появление значений 0, false, и '' не приводит к использованию значения, применяемого по умолчанию.

Вот пример подобного выражения:

value != null ? value : 'default value';

Можно встретить и неграмотно написанный вариант такого выражения:

value || 'default value'

Проблема второго примера заключается в том, что третий пункт вышеприведённого списка здесь не выполняется. Появление тут числа 0, значения false или пустой строки будет распознано как значение false, а это — не то, что нам нужно. Именно поэтому проверку на null и undefined нужно проводить в явном виде:

value != null

Это выражение аналогично такому:

value !== null && value !== undefined

В подобных ситуациях очень кстати окажется новый оператор, называемый «объединение со значением null» (nullish coalescence), который выглядит как два знака вопроса (??). В подобной ситуации теперь можно будет воспользоваться следующей конструкцией:

value ?? 'default value';

Это защищает нас от случайного использования значения по умолчанию при появлении в выражениях значений, распознаваемых как false, но при этом позволяет выявлять значения null и undefined, не прибегая к тернарному оператору и к проверке вида != null.

Теперь, познакомившись с этим оператором, мы можем написать тесты для того, чтобы проверить его в деле. Эти тесты поместим в файл nullish-coalescing.test.js.

import test from 'ava';

test('Nullish coalescing defaults null', (t) => {
  t.is(null ?? 'default', 'default');
});

test('Nullish coalescing defaults undefined', (t) => {
  t.is(undefined ?? 'default', 'default');
});

test('Nullish coalescing defaults void 0', (t) => {
  t.is(void 0 ?? 'default', 'default');
});

test('Nullish coalescing does not default 0', (t) => {
  t.is(0 ?? 'default', 0);
});

test('Nullish coalescing does not default empty strings', (t) => {
  t.is('' ?? 'default', '');
});

test('Nullish coalescing does not default false', (t) => {
  t.is(false ?? 'default', false);
});

Из этих тестов можно понять, что значения по умолчанию используются для null, undefined и void 0 (оно преобразуется в undefined). При этом значения по умолчанию не применяются в тех случаях, когда в выражении появляются значения, воспринимаемые как ложные, вроде 0, пустой строки и false.

3. Оператор |>


В функциональном программировании есть такое понятие, как композиция. Это — действие, представляющее собой объединение в цепочку нескольких вызовов функций. Каждая функция принимает, в качестве входных данных, выходные данные предыдущей функции. Вот пример композиции, подготовленный средствами обычного JavaScript:

function doubleSay (str) {
  return str + ", " + str;
}
function capitalize (str) {
  return str[0].toUpperCase() + str.substring(1);
}
function exclaim (str) {
  return str + '!';
}
let result = exclaim(capitalize(doubleSay("hello")));
result //=> "Hello, hello!"

Подобное распространено настолько широко, что средства композиции функций существуют во множестве библиотек, поддерживающих функциональное программирование, например, в таких, как lodash и ramda.

Благодаря новому конвейерному оператору (pipeline operator), который выглядит как комбинация вертикальной черты и знака «больше» (|>), можно отказаться от использования сторонних библиотек и переписать вышеприведённый пример следующим образом:

let result = "hello"
  |> doubleSay
  |> capitalize
  |> exclaim;

result //=> "Hello, hello!"

Цель появления этого оператора заключается в том, чтобы улучшить читаемость цепочек вызовов функций. Кроме того, в будущем, этот оператор найдёт применение в конструкциях частичного применения функций. Сейчас это можно сделать следующим образом:

let result = 1
  |> (_ => Math.max(0, _));

result //=> 1
let result = -5
  |> (_ => Math.max(0, _));

result //=> 0

Разобравшись с основами, напишем тесты, разместив их в файле pipeline-operator.test.js:

import test from 'ava';

function doubleSay (str) {
  return str + ", " + str;
}

function capitalize (str) {
  return str[0].toUpperCase() + str.substring(1);
}

function exclaim (str) {
  return str + '!';
}

test('Simple pipeline usage', (t) => {
  let result = "hello"
    |> doubleSay
    |> capitalize
    |> exclaim;

  t.is(result, 'Hello, hello!');
});

test('Partial application pipeline', (t) => {
  let result = -5
    |> (_ => Math.max(0, _));

  t.is(result, 0);
});

test('Async pipeline', async (t) => {
  const asyncAdd = (number) => Promise.resolve(number + 5);
  const subtractOne = (num1) => num1 - 1;
  const result = 10
    |> asyncAdd
    |> (async (num) => subtractOne(await num));
  
  t.is(await result, 14);
});

Анализируя эти тесты, можно заметить, что при применении в конвейере асинхронной функции, объявленной с использованием ключевого слова async, нужно дождаться появления значения, воспользовавшись ключевым словом await. Дело тут в том, что значение становится объектом Promise. Имеются несколько предложений изменений, направленных на поддержку конструкций вида |> await asyncFunction, но они ещё не реализованы и решение об их дальнейшей судьбе пока не принято.

Итоги


Надеемся, вам понравились ожидаемые возможности JS, которым посвящён этот материал. Вот репозиторий с тестами, которыми мы занимались в этом материале. А вот, для удобства — ссылки на рассмотренные здесь новшества: оператор ?., оператор ??, оператор |>.

Уважаемые читатели! Как вы относитесь к появлению новых возможностей JS, о которых мы рассказали?

RUVDS.com 697,65
RUVDS – хостинг VDS/VPS серверов
Поделиться публикацией
Похожие публикации
Комментарии 64
  • +2
    Оператор? = слёзы счастья
    • 0
      Логичнее бы & смотрелся. Ведь именно этот символ участвует в нынешней реализации
      • 0

        ?.. — это элвис оператор. Его скорее всего подтянули из других языков.
        Странно описан абзац про производительность. Так как фактически это синтаксический сахар.
        Код


        left?.right

        будет эквивалентен


        left && left.right
        • +2

          Ну, тут фокус в том, что прежде чем написать left && left.right программист подумает а может ли left отсутствовать в принципе — а всякие left?.right можно привыкнуть писать просто по умолчанию.

    • +1
      Оператор |> = какая-то уродливая дичь
      • +20
        Зачем вы так говорите? Сейчас вас придут минусовать все хаскельщики. Все полтора.
        • +2
          Там для композиции функций используется точка, а этот оператор определен для одной структуры данных век же библиотеке. Так что спокойно пейте смузи дальше.
          • –1
            В Haskell для этого есть &, один символ (перевёрнутая версия $). Но в Haskell любой оператор — это обычная инфиксная функция, так что если мне что-то уродливо, я могу сделать так:
            a ⊥ b = b a
            и всё, у меня теперь есть этот оператор, который вы ещё джва года будете ждать.

            В Haskell вообще не стоит вопрос о введении новых операторов в язык, потому что это может сделать любая библиотека.

            А вообще в этот ваш жовоскрипт постепенно переезжает весь LiveScript в плане сахара.
          • +5
            А мне в F# жутко нравится.
            1. Код читается как простая последовательность команд:
             fun pad -> pad.text |>OneTimePad.encrypAndSave |> WritePadView.result |> htmlView
            

            2. Заставляет делать функции еще более узкими и чистыми. Прими — верни, и лучше без сайд эффектов :) Не всегда удается конечно, но все равно приятнее.
            • –3

              юзал в Elixir ( https://hexdocs.pm/elixir/Kernel.html#%7C%3E/2 ), крайне удобный operator для pipes (проброса выхлопа одной функции на вход другой).
              этот паттерн — древний и юзается например в linux для перенаправления output одной прграммы на input другой (https://ru.wikipedia.org/wiki/%D0%A4%D0%B8%D0%BB%D0%BE%D1%81%D0%BE%D1%84%D0%B8%D1%8F_Unix


              Правило композиции: Разрабатывайте программы так, чтобы их можно было соединить с другими программами.

              http://www.linfo.org/pipes.html
              )
              здесь — применяют этот паттерн для конекта функций


              vlreshet


              О себе
              Пишу на JS, PHP, Java и бумажных листиках.

              теперь стало понятно)

              • 0
                Согласен. Самое убогое — невозможность переиспользовать функцию-результат, т.е. в отличие от оператора композиции функций, этот оператор не порождает новую функцию на множестве функций, а просто тупо пайпит вызовы.
                • 0
                  т.е. в отличие от оператора композиции функций

                  Подскажите людям, далёким от настоящих функциональных языков, что это такое и как там переиспользуется результат?

                  • +1
                    Композиция функций в функциональных языках — это бинарный оператор на множестве функций. Т.е. он также порождает функцию.
                    Пусть f и g – функции, например:
                    f :: Int -> Int
                    f x = x + 1
                    
                    g :: Int -> Int
                    g x = x * 2
                    

                    Тогда оператор композиции функций (.) порождает значение на множестве функцию, т.е. новую функцию:
                    g . f -- Точка — бинарный оператор, порождающий новую функцию
                    

                    Это значит, мы можем объявить новую функцию, являющуюся композицией функций:
                    h x = g . f $ x
                    

                    В итоге имеем функцию h:
                    h :: Int -> Int -- Объявление типа здесь для ясности
                    h x = g . f $ x
                    

                    Вызов которой равноценен вызову g(f(x)).
                    Вот простой пример:
                    f :: Int -> Int
                    f x = x + 1
                    g :: Int -> Int
                    g x = x * 2
                    
                    h x = g . f $ x
                    
                    main = do 
                      print (h 10) -- Печатает 22
                    

                    Представьте следующий пример на TypeScript (для ясности, поскольку он типизирован):
                    const f = (x: number): number => x + 1;
                    const g = (x: number): number => x * 2;
                    

                    Как бы мы могли реализовать функцию compose, чтобы она была похожа на оператор композиции функций?
                    // Опрередим тип для функции, принимающей параметр типа P и возвращающая результат типа R
                    type F<P, R> = (value: P) => R;
                    
                    const compose = <P, R, O>(f1: F<R, O>, f2: F<P, R>) => (x: P): O => f1(f2(x));
                    
                    // Мы можем применить эту функцию создания новой функции:
                    const h = compose(g, f); 
                    
                    h(10); // Возвращает 22
                    
                    // Теперь представьте, что в языке есть оператор (.) для записи compose:
                    const h = g . f;
                    
                    // Поскольку этот оператор определён на множестве функций и он правоассоциативен, допустимо использовать несколько функций:
                    const m = h . g . f;
                    // Это эквивалентно следующему коду:
                    const m = h . (g . f);
                    // Или следующему, если переписать с использованием функции compose:
                    const m = compose(h, compose(g, f));
                    

                    Соответственно, отличие пайп-оператора в том, что он не возвращает новую функцию, а просто применяет функцию к параметру.
                    const pipe = <P, R>(x: P, f: F<P, R>): R => f(x);
                    
                    pipe(10, f); // Возвращает 11
                    
                    // Теперь представьте, что в языке есть оператор (|>) для записи pipe:
                    const result = 10 |> f;
                    // Поскольку этот оператор левоассоциативен, допустимо использовать его с несколькими значениями:
                    const result = 10 |> f |> g;
                    // Это эквивалентно следующему коду:
                    const result = (10 |> f) |> g;
                    // Или следующему, если переписать с использованием функции pipe:
                    const result = pipe(pipe(10, f), g); // Возвращает 22
                    

                    Разница, думаю, очевидна :)
                    • +1

                      Спасибо, теперь понятно. Лично мне в моём |> гораздо полезнее, чем .. По сути я вижу в нём удобную возможность писать код слева-направо без ада скобочек. Ну и возможность писать цепочки преобразований даже тогда, когда нужного метода нет в прототипе объекта.


                      В то время как . я вижу как удобный примитив для создания новых функций на основе существующих. Последнее мне нужно очень редко (в таком простом виде). Не уверен, что я нашёл бы . частое применение.


                      А почему он, кстати, справа-налево?


                      P.S. мне кажется f . g . h в Haskell (или что это за язык?) на деле гораздо дешевле/быстрее, чем compose(f, g, h) в JS.

            • 0
              Первые два очень полезны и желанны, третий — ну как-то не особо. Смотрится инородно в контексте JS.
              • +2

                В предложении есть еще несколько примеров использования, может они вас убедят:


                • +7

                  Некоторые люди ждут pipe-оператора гораздо гораздо больше ?., ?? и любых других ??????. Это же возможность писать функциональный код слева на право! А изнутри-налево, как сейчас. И это избавления от ада скобочек. И это возможность продолжить цепочки методов методами, которые в цепочке отсутствуют. Да это моя самая желанная вещь со времён async-await! :)

                  • 0
                    Поддерживаю полностью, первые два — приятный синтаксический сахар, а пайплайн — это прямо новая парадигма, по сути, API библиотек будут создаваться с учетом него, это круто.
                • +1

                  --> красиво смотрелся бы для pipelines

                  • 0
                    К сожалению, комбинация операторов — и > уже существует в языке…
                    • +1

                      ~>

                      • +1
                        a-->b это b(a) или a-- > b
                        • –1
                          ИМХО, любая подобная конструкция всё же будет смотреться несколько инородно для js. Как по мне то идеальным вариантом было бы что-то вроде
                          let result = "hello".do(doubleSay, capitalize, exclaim);
                          
                          Или даже
                          let result = doubleSay.pipe(capitalize).pipe(exclaim).pass("hello")
                          

                          Вместо
                          let result = "hello"
                              |> doubleSay
                              |> capitalize
                              |> exclaim;
                          
                          И транспайлить такое будет довольно просто, и синтаксис однообразный. Почему для нового функционала обязательно надо городить новый синтаксис?
                          • –2
                            Человек кинувший минус — мне то пофиг, но ты хоть аргументировал бы, что-ли, за что :) Я же написал что это чисто моё мнение. А то получается «не знаю как, но не так»
                            • +2
                              А что если у объекта уже есть метод do?
                              • –4
                                Во-первых вариант с ключевым словом do — просто один из вариантов. Во-вторых «коробочные» поля могут быть спокойно перезаписаны. Так что старый код врядли сломался бы, а новый можно было бы писать уже с учётом нового метода. Зато вариант с .do можно реализовать прям сейчас в 5 строчек кода, без дописывания компилятора.
                                • +2
                                  Вот только применять такую конструкцию можно будет только для своих объектов…
                                  • +2

                                    Для null и undefined не получится это реализовать.


                                    Зато вариант с .do можно реализовать прям сейчас в 5 строчек кода, без дописывания компилятора.

                                    То есть нет никаких ограничений чтобы предложенный вами вариант использовать уже сейчас в своем коде.


                                    А синтаксис на то он и синтаксис, чтобы делать некоторые вещи опрятнее и проще для понимания, если на ваш вкус это выглядит инородно, то для себя я вижу множество моментов, где с удовольствием бы использовал данный оператор исходя из эстетических предпочтений.

                                • +3

                                  Как угодно, но не так. Вы взяли уже существующий оператор . и расширили его странным поведением в совершенно базовых условиях, ломая весь существующий js-код. Как вам такое в голову пришло? :) К тому же вы явно невнимательно посмотрели все use-case pipe-оператора.

                                • +2

                                  Есть уже существующие библиотеки, например Рамда:


                                  const R = require('ramda')
                                  
                                  let resultA = R.pipe(
                                    doubleSay,
                                    capitalize,
                                    exclaim
                                  )('hello')
                                  
                                  let resultB = R.compose(exclaim, capitalize, doubleSay)('hello')
                                  • 0

                                    Разница между pipe и compose только в порядке применения функций?

                                    • 0

                                      Видимо, да.

                                      • 0

                                        Да.


                                        В JS операторы идут в порядке приоритетов, методы чейнятся от объекта слева направо, а функции оборачивают аргументы справа налево, образуя кучу вложенных скобок.


                                        В рамде хотели избавиться от этого разнообразия, сделать так чтобы строку кода можно было просто прочитать справа налево, от инпута до присваивания, в порядке применения функций. То есть буквально сделать все операции функциями.


                                        По этому в библиотеке собрано много функций обёрток над операторами яваскрипта. А чтобы не получилось много вложенных скобок был сделан конвейер compose.


                                        Только получается так что, если читать функции справа налево из-за скобок более-менее привычно, то читать аргументы конвейера снизу вверх уже не очень. По этому был дополнительно сделан pipe, который немного не соответствует общей филосософии рамды.

                                  • +13
                                    Предлагаю оператор o_O. Смотрите, как красиво:
                                    let result = "hello" o_O doubleSay o_O capitalize o_O exclaim;
                                    • 0
                                      А вместо?.. и ?? использовать :/ и х_Х соответственно.

                                      var result = capitalize(doubleSay(obj && obj.value? value: «hello»))
                                      V
                                      const result = obj:/value x_X «hello» o_O doubleSay o_O capitalize
                                      • +1
                                        почему у меня ощущение что на каждом шаге он удивляется всё больше и больше?
                                    • +1
                                      В принципе, это ведь синтаксический сахар.
                                      Почему бы и нет? Похожие нововведения местами улучшили синтаксис PHP, и для JS это не особо повлияет на концептуальность языка.
                                      • 0
                                        В PHP ?: и ?? чистый сахар, однозначное и простое приведение к известным «паттернам». Тут с?.. сложнее, по-моему.
                                      • 0
                                        Вместо a.b?.c?.d?.e гораздо читабельнее было бы ?(a.b.c.d.e) или даже nullsafe(a.b.c.d.e)
                                        • 0

                                          Или a.b.c.d.e ?? default

                                        • +2
                                          Так нельзя выразить, на каких местах точно не null, чтобы компилятор не генерировал лишнюю проверку.

                                          Вот эти выражения по смыслу разные, как их отличать: a?.b?.c.d.e, или a.b?.c.d.e, или a?.b.c.d?.e
                                          • –2
                                            Лишняя проверка ничего не стоит практически, и в большинстве случаев неизвестно точно какие поля не null, так что будет повсеместно нечитаемая лапша a.b?.c?.d?.e
                                          • 0
                                            Вариант ?(a.b.c.d.e) мне нравится и я бы хотел его видеть как дополнение к введенному. Можно было бы тогда так делать записи — a?.b.?(c.d.e). Но «цепочная» проверка будет постоянно производиться в таких выражениях a?.b.?(c.d.e) и a?.b.?(c.d.f), что не очень хорошо может сказаться на производительности, если таких «хвостов» много. Для меня легче всегда делать разложение проверки на уровни.
                                          • 0

                                            А что за прикол в лямбдах названия аргументов называть как "_"? Дырка на месте переменной улучшает читабельность?

                                            • 0

                                              Это placeholder, который "выталкивает" аргумент на последнее место при частичном применении аргументов. http://ramdajs.com/docs/#__

                                            • –1
                                              Не знаю, на мой вкус?.. порождает странный код, в котором мы сразу ломимся к непроверенной сущности, вместо того, чтобы где-то раньше проверить, что сущности нет, и уже не пытаться копаться в её потрохах. Может, нужна какая-то простая конструкция, проверяющая, что нужные поля у структуры/объекта есть (а заодно, например, что они проходят какие-то ещё проверки — по регекспам, например)?

                                              Ну, что-то типа

                                                match(user, 
                                                   { login : /[A-Za-z]{1,10}/, 
                                                      street : { 
                                                          id : /[0-9]+/, 
                                                          name: /[A-Za-z 0-9]+/ }, 
                                                          password : function(v) { return true; }  
                                                                  }
                                                   } ); 
                                              
                                              • +1

                                                Опциональные свойства могут быть даже у проверенных сущностей.

                                              • +4
                                                Хех. Когда, по прогнозам, JS догонит Perl? В том смысле, что любая комбинация знаков препинания будет рабочим кодом? :)
                                                • 0
                                                  На самом деле тут вопрос в удобстве. А то, что кому-то не нравится просто прихоти. К любому синтаксису привыкаешь и через некоторое время посмотрев на предыдущие работы с недоумением смотришь какой я некрасивый код писал. Очень полезные нововведения как по мне.
                                                  • +2
                                                    FiraCode как-нибудь покрасивее оформит комбинацию |> и будет счастье
                                                    • 0

                                                      Кстати да. Сейчас там такое есть (в JS не работает):
                                                      image

                                                      • 0
                                                        Рискую получить тапком в лицо…
                                                        Почему FiraCode выглядит нереально убого? Это «что-то» сверх-расфокусированное, от чего глаза почти сразу кровью течь хотят. Или у людей это «четкий» шрифт?
                                                        • 0
                                                          Вполне все «четко» выглядит, даже на Ретине.
                                                          • 0

                                                            Это не от FiraCode зависит, насколько я понимаю, а от вашего экрана. Я привёл скриншот, и если он бьёт вам по глазам, то рискую предположить, что установи вы его себе на компьютер, у вас он будет выглядеть иначе. Т.к. это зависит от ОС и её настроек. А ещё хочу отметить, что шрифты на картинках выглядят часто непривычно или даже не красиво, а будь вы рядом с конкретным монитором, где эта картинка была сделана, вам бы оно могло показаться очень даже ничего. Всё дело в DPI. У меня 3 монитора (мне так удобнее) с разными DPI и на всех 3 всё выглядит немного иначе. Бывает люди подгоняют себе такой DPI и монитор, что им комфортно использовать 1px шрифты (когда все линии равны аккуратно 1px), и в других условиях оно выглядит просто ужасно, а у них всё симпатично.


                                                            В общем шрифты это дико холиварная тема. Скажем у меня здесь на хабре стоит 130% zoom и все картинки выглядят мылом. Но зато всё большое и читать приятнее. Компромисс :)

                                                            • 0
                                                              Я привёл скриншот, и если он бьёт вам по глазам

                                                              o_O… пардон, из-за диагональных текстов думал, что это «просто картинка». Нет, на ней как раз все выглядит отменно. Плохо же выглядит в IDE, где я естественно, первым делом, и побежал смотреть. Увидев и разочаровавшись, пошел тестировать на машине коллеги. Там так же «не очень»/«сплошное мыло».
                                                              Всё дело в DPI.

                                                              Ок, это многое объясняет, не подумал сперва — виноват.
                                                          • 0
                                                        • 0
                                                          Конструкции хороши, мне понравились.
                                                          Всяких коротких языковых конструкций можно еще в perl`e подсмотреть.
                                                          • 0
                                                            Мне не нравится этот оператор?.. а с присваиваниями он будет работать?
                                                            let data = {};
                                                            data?.user?.getNeighbors = true;
                                                            В любом случае, я никогда не понимал, почему нельзя было сделать обычно, что обращение к undefined возвращает undefined. Это же так просто, есть оператор. который, если имеет undefined левый аргумент, то возвращает undefined. и к тому же оператор () который тоже если имеет undefined левый аргумент то возвращает undefined…
                                                            • 0
                                                              Я никогда не понимал, почему нельзя было сделать обычно, что обращение к undefined возвращает undefined
                                                              Потому что, если Вы вдруг обращаетесь к свойствам undefined, то, вероятно, это ошибка. Если тихо пропускать это, это может привести к багам.

                                                              Ну и раз появится оператор «?.», то уже нет разницы.
                                                              • 0
                                                                Если тихо пропускать это, это может привести к багам.

                                                                Просто в конце длинной цепочки вычислений вы получите undefined. Искать ошибку будет труднее.

                                                              • 0
                                                                undefined() Uncaught TypeError: undefined is not a function
                                                                undefined.test Uncaught TypeError: Cannot read property 'a' of undefined

                                                                Или вы не понимаете почему оба так работают?

                                                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                              Самое читаемое