Pull to refresh

Comments 38

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


let {a} = obj.optionalMethod?.(...param) ? x : y ?? #{a: 1}

обучение языку превратится в знаменитую картинку "как нарисовать сову"

Да ничего сложного тут. Если и злоупортреблять просто тернарным оператором, то нечитаемая фигня получается.
UFO just landed and posted this here
Со «старым» синтаксисом подобная конструкция выглядит еще более уродливой:
const { a } = obj.optionalMethod && obj.optionalMethod(...param) ? x : y !== undefined ? y : { a: 1 }

старый синтаксис заставляет её быть настолько длинной, что вынуждает разработчика таки разбить её на несколько инструкций. Я бы и с новым за такое по рукам бил, но если не экономить на строчках, то на "совсем старом" синтаксисе получится (не считая особенностей const)


if (obj.optionalMethod && obj.optionalMethod.apply(this, param)) {
  var a = x.a
} else if (y !== undefined) {
  var a = y.a
} else {
  var a = 1
}

Я понимаю, что никто не запрещает писать с новыми фичами не экономя строк
И я твёрдо уверен, что каждая из использованных в примере фичей — крайне полезна, большинство из них я регулярно использую.
Но монстры, которых МОЖНО породить с этими возможностями, пугают :)
Не могу удержаться процитировать прекрасного aemkei и сравнить свою конструкцию с полностью валидным JS: image


P.S: как раз на картинке используются только самые базовые функции JS и развлечения с приведением типов

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

1.2. Ограничения на содержимое записей и кортежей

Записи:
  • значения должны быть примитивами (включая записи и кортежи).


Не получится.

Почему? Можно ведь засунуть все из VNode, кроме child'ов и event'ов в запись, и с помощью этого просто и красиво сравнивать все, кроме child'ов и event'ов в границах одного VNode.

Только если сделать объект-обёртку, в котором будут запись с иммутабельными данными и объект с потомками и ивентами. Как я понял из статьи, вложить в запись объект или массив нельзя. А толку от задумки с такой обёрткой я лично не вижу, вопрос со сравнением никуда не денется.


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

Как я понял из статьи, вложить в запись объект или массив нельзя.

Зато можно вложить в запись кортеж или другую запись.


Плюс, представим ситуацию, что у нас список из 10 одинаковых VNode, и нам нужно что-то сделать с одной конкретной.

Ключи никуда не денутся.


Записи и кортежи дают удобные объекты, у которых сравнение работает быстрее, они гарантированно иммутабльны.

Записи и кортежи дают удобные объекты, у которых сравнение работает быстрее, они гарантированно иммутабльны.

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

Ключи никуда не денутся.

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

Вовсе не обязательно. Почему бы не использовать записи только для всех полей VNode, кроме событий, родителей и детей?

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

А вот для пропсов кстати это может оказаться очень полезным, как ниже писали. Не знаю, как в реакте, а во Vue для того, чтобы изменение было реактивным, нужно создавать новый объект. Вот там бы точно такое пригодилось и в пропсах, и в computed, и в стейтах Vuex.

Кстати, во Vue иммутабельные типы врятли зайдут. Второй Vue работает через множество defineGetter'ов, третий через Proxy. У Vue все мутабельно (ну кроме пропсов, но и там придется конверировать все это дело). В React'е это может вполне таки зайти: в реакте большинство вещей иммутабльны: пропсы, данные компонентов (если использовать функциональные компоненты и хуки). В прибавку, Redux, самый популярный стейт менеджмент для React использует иммутабельный стор, который на каждое изменение создается новый (shallow copy).

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

А в чем проблема то? Посмотрите мой комментарий выше.

Может кто-нибудь подсказать живые примеры, где иммутабельные объекты и массивы могут пригодиться? На ум приходят только какие-нибудь константы и конфигурации, которые изначально прописаны в коде. В большинстве задач всё равно используются составные типы, в которых что-то меняется, и тут кортежи и записи не дают никакого выигрыша.

"FP"-подобный код. Скажем redux store. Всякие #[...#items, newItem] должны быть гораздо быстрее, чем [...items, newItem] (O(n) vs O(log n)).

Это полезно, например, в React, когда в качестве prop передается не примитив. Тогда понять, изменился ли prop можно быстрым prevProp !== prop, вместо какого-нибудь deepEqual.

Эту штуку нельзя применить в реакте. Потому что в пропсах почти никогда не передаётся только примитив. Во всяком случае во всём коде который я писал, всегда передавались объекты, какие-нибудь map'ы или функции, но чтобы были только строки или только числа — вот такого не встречал почти никогда.

всегда передавались объекты, какие-нибудь map'ы или функции

Вместо object-ов, массивов и map-ов просто #{} и #[] (их вкладывать можно). А вот с функциями облом, это да

И опять пойдём по пути "заражения"? Как уже было с async/await. Конечно возможность крутая, но для этого нужно переписать очень и очень много библиотек, при этом сильно уменьшив область их применения. А переделывать каждый раз обычных объект в такой вот словарик — кажется дорогим удовольствием. Самое главное, что если нам нужно постоянно для каждого компонента вычищать колбеки и у пропсов переделывать их тип, то легче уже будет делать это всё в самом shouldComponentUpdate.

Я думаю для props в React это в целом не применимо. Тут скорее речь про pure FP. Какие-нибудь immutable stores аля redux

Просто коренной комментарий был про использование в React)


На счёт immutable сторов я не очень понял в чём разница с frozen объектами, кроме того, что тут deepFreeze. На уровне интерпретатора замороженные объекты явно имеют какое-то специальное свойство и никто не запрещает v8 или spidermonkey применять оптимизации сравнения для двух frozen объектов.


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

я не очень понял в чём разница с frozen объектами, кроме того, что тут deepFreeze

BigO


На уровне интерпретатора замороженные объекты явно имеют какое-то специальное свойство и никто не запрещает v8 или spidermonkey применять оптимизации сравнения для двух frozen объектов.

Интересная мысль. Мне кажется таких оптимизаций не будет. Т.к. вначале всё равно придётся сформировать весь объект пошагово а потом его трансформировать в персистентный… Да и очень уж это будет похоже на хак.

В итоге придет к тому что просто изменятся функции для сравнения по значению. Чтобы не заводить этих новых объектов на случай необходимости изменить свойства, ну или хотя бы сравнить по указателю. Так что лучше бы просто ввели указатели на значение, можно и рекурсивные. И адекватную обработку сравнений с ними. Хотя конечно сейчас все это синтаксический сахар, который можно добавить на свой проект не добавляя новый символ в весь язык.
Я изучал JS после питона и в процессе много страдал по поводу того, что есть слишком много того, что в питоне — одна строчка, а в js — пачка кода (lodash и ко не считаем). В последнее время JS очень активно набирает функционал в стандартную библиотеку. И писать на нём становится всё приятнее и приятнее.
да хватит уже тащить в JS все что под руку попадется! Горшочек не вари!

Идея прикольная, вот только её не примут. Самая главная конструкция с этими типами === не имеет никакого специального синтаксиса. Значит транспилировать её не получится. Именно по той же самой причине транспилируемости у нас приватные поля делаются через #, а не свойствами самого поля. Именно поэтому у нас для всего придумывают новый синтаксис такой, чтобы он был чётко отличим от старого...


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


const a = #[1, 2, 3]
    , b = #[2, 3, 4]

assert(a === b)  # должен быть false

Вот в этом примере явно видно, что на этапе компиляции нельзя понять что будет храниться в a и b, а соответственно нельзя со 100% уверенностью применить обычный оператор === или именно полифил для этой штуки c поэлементным сравнением.


Если же каждый оператор === заменять полифилом, то получится слишком много кода.

Я полагаю, что просто никто всерьёз это полифилить и не будет.

Хм. А как они планируют их полифилить? Просто оставят обычными объектами и массивами + Object.freeze? Или обернут какими-нибудь persistent data structure обёртками?

Пока не встречал к сожалению объяснений, если встречу, отпишу.

typeof will return an incorrect value when provided a Record or Tuple. This is because the polyfill implements the proposal via interning frozen objects

В общем я выше угадал :) А вот реализация Tuple. Никаких persistent data structure. Во всяком случае на данный момент. Возможно они в планах.

Будущее: классы, экземпляры которых сравниваются по значению?

Тут бы отлично подошли кортежи/записи + символы. Например, ввести символ Symbol.equal, который вызывается при сравнения ===. Тогда можно было бы определять этот символ в экземпляре и там возвращать кортеж/запись


Как бы могло выглядеть


class Test {
  [Symbol.equal]() {
    return Record({
      a: this.a,
      b: this.b,
      c: this.c
    });
  }

  constructor(a, b, c) {
    this.a = a;
    this.b = b;
    this.c = c;
  }
}

const test1 = new Test(1, 2 ,3);
const test2 = new Test(1, 2, 3));
console.log(test1 === test2);

Звучит как отличный новый пропозал!

UFO just landed and posted this here
Sign up to leave a comment.

Articles