Как стать автором
Обновить

Комментарии 58

НЛО прилетело и опубликовало эту надпись здесь

Скурпулезных замеров не проводил, но вот есть небольшой бенчмарк https://github.com/xonixx/jsqry/blob/master/bench.js.
У меня 100000 прогонов запроса вида


jsqry.one(o1, '[_.id>=2].name[_.toLowerCase()[0]==?].length', 's')

отрабатывает за 425ms.

За что я люблю LiveScript, так это за то, что подобные вещи можно достаточно компактно писать без библиотек.
До:


query([1,2,3,4,5],'[_>2]{_+10}')

После:


[1,2,3,4,5].filter(-> it>2).map(-> it+10)

it это стандартная для LiveScript переменная, обозначающая первый аргумент функции. И никакого парсинга в рантайме не нужно.


Или же ES2016, если странспайлер используется:


[1,2,3,4,5].filter(it => it>2).map(it => it+10)

Спасибо, интересно.
А такой юз-кейс:


var hotel = {
    name: 'Name',
    facilities: [
        {name:'Fac 1',
        services: [
            {name:'Service 1', visible:false},
            {name:'Service 2'}
        ]},
        {name:'Fac 1',
        services: [
            {name:'Service 3'},
            {name:'Service 4', visible:false},
            {name:'Service 5'}
        ]}
    ]
};

console.info(query(hotel,'facilities.services[_.visible !== false].name')); // [ 'Service 2', 'Service 3', 'Service 5' ]
Два пути: красивый, но с добавлением одной функции в Array.prototype
Array.prototype.concatItems = function(){ return Array.prototype.concat.apply([], this) };

hotel.facilities.map(_=>_.services).concatItems().filter(_=>_.visible !== false).map(_=>_.name); // [ 'Service 2', 'Service 3', 'Service 5' ]

и не такой красивый
[].concat.apply([], hotel.facilities.map(_=>_.services)).filter(_=>_.visible !== false).map(_=>_.name); // [ 'Service 2', 'Service 3', 'Service 5' ]
hotel.facilities.map(_=>_.services).reduce((a,b)=>a.concat(b)).map(_=>_.name);
Вот да, только хотел спросить, чем стандартные map/reduce/filter не угодили.
Я подумал об этом, но только после отправки комментария.
Замечу, что у такого решения есть недостаток — вычислительная сложность у него O(n*m) где n — суммарное количество элементов в подмассивах, m — количество подмассивов. У concat это O(n).
На самом деле тут вариантов несколько.
Если нужна лаконичнось, все укладывается в 1 reduce:
hotel.facilities.reduce(
 (acc, f) => acc.concat(f.services.filter(s => s.visible !== false).map(s => s.name)), 
 []
);
// ["Service 2", "Service 3", "Service 5"]

Если нужна скорость — пожалуйста.
hotel.facilities.reduce((acc, f) => {
 f.services.forEach(s => {
  if (s.visible !== false) {
   acc.push(s.name);
  }
 });
 return acc;
}, []);
// ["Service 2", "Service 3", "Service 5"]

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


Не только. Её вариант проще Вашего лаконичного варианта, правильно обрабатывает null/undefined, работает на любом древнем JS.
По-моему плюсов не так и мало.
query(hotel,'facilities.services[_.visible !== false].name')
Что значит правильно обрабатывает? Нул не должен обрабатываться никак и undefined, на самом деле, тоже. Сейчас я почитаю код библиотеки и точно скажу насколько оно правильно.
Посмотрел. Функция defined плохая. Обычно требуется не проверка «a.b === undefined», а «b in a», потому что undefined может быть действительно значением поля, которое мы хотим извлечь. Если заявлена поддержка старых браузеров, то там значение undefined вообще можно переопределить, по этой причине я долго оборачивал весь код в функцию (function(undefined){ код })(), пока не перешел на void 0.
там значение undefined вообще можно переопределить


Тот кто это сделает — будет гнусным извращенцем. За то, как поведёт себя код в руках гнусного извращенца, разработчик не отвечает.
К сожалению, тут проблема двусторонняя… И «гнусный извращенец» может пролезть к вам в сборку с 50-ой зависимостью какого-нибудь npm-пакета :)

Среди разработчиков сколь-нибудь серьёзных библиотек извращенцев нет, а несерьёзные — проще самому написать.

Что значит правильно обрабатывает


Как Ваши варианты решения отработают на какой-то из записей facilities без поля services?
Ну окей. Что, если мне нужно показать сообщение об ошибке, что с сервера пришли битые данные (без services — просто абстрактный пример)? Ваша библиотека, так сказать, прибила гвоздями проверку на undefined, и, соответственно, молча продолжит выборку, когда, используя стандартные функции, можно добавить и/или исключить все необходимые проверки.
Это гибко и удобно, хоть и не позволяет сэкономить лишние пару строк.
Даже не про этот кейс, тут можно итоговый length проверить. А вот приходит с сервера [{name:1}, {name: undefined}, {name:7}] -> обычно из него хотят отрисовать вьюху где первый и последний элемент будет содержать 1 и 7, а вместо второго надпись «введите значение», с волшебной библиотекой мы не узнаем о том что значения во втором поле нет, а особенно важно что мы не узнаем что его нет именно во втором элементе. Причём я понимаю когда эта библиотека может быть удобна, но такие граничные поведения надо писать огромными буквами в документации, потому что это может вылиться в часы отладки через месяц использования библиотеки.
Ну это философский спор. Но, например, разработчики angularjs сделали так же.
Forgiving: In JavaScript, trying to evaluate undefined properties generates ReferenceError or TypeError. In Angular, expression evaluation is forgiving to undefined and null
Кстати, это очень здорово, что Вы упомянули этот момент в ангуляре. В свое время он доставил немало «удовольствия» в поиске места, куда приткнуться, чтобы понять странность получаемого результата и отладить это их «небольшое усовершенствование». Думаю, что с jsqry рано или поздно случилось бы то же самое…
Может автор учёл undefined элементы этой цепочки.

Весьма верное замечание! Вариант с Jsqry корректно прожует вариант объекта без поля services. Вариант с ES-кунг-фу скорее всего упадет с Null Pointer.

Не упадёт, если обратиться к несуществующему проперти, оно будет undefined. При попытке обращения к более вложенным полям несуществующего поля упало бы, на этот случай можно использовать _.get из lodash или синтаксис obj?.prop из CoffeeScript.
Вся необходимая логика (да и вообщем-то любые проходы по коллекциям) так или иначе сводится к map/reduce. Собственно, вся логика выборки (в том числе и все-возможные проверки на undefined/null/коня_в_вакууме) прекрасно умещается в лямбдах для них.

«ES-кунг-фу» гораздо более предсказуемо, его проще и удобнее отлаживать и, что самое важное, поддерживать — это часть стандарта.
Я не спорю с кунг-фу, поскольку сам люблю строить развесистые комбо, но хотел обратить внимание что (возможно (не читал код этой библиотеки) ) приведённая цепочка мэп\фильтро\редьюсов не является эквивалентом.

Я бы реализовал следующим образом.


LiveScript:


[].concat(...hotel.facilities.map(-> it.services.filter(-> it.visible!==false).map(-> it.name)))

ES2016:


[].concat(...hotel.facilities.map(it => it.services.filter(it => it.visible!==false).map(it => it.name)))

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

А зачем пример на LiveScript, если пример — на ES2016 то же самое?

Изначально на LS написал, вот и здесь повторил.
А вообще я считаю что LS незаслужено обделен вниманием. В нём много интересных решений, которых мне не хватало в CoffeeScript, возможно, мой комментарий вынудит кого-то зайти на сайт и посмотреть что он собой представляет.

О, это тот редкий случай когда eval и new Function имеют право на жизнь. Либо генерировать функцию и делать кэш по строке, либо функцию которая компилирует выражение в функцию, и возвращает функцию которая принимает непосредственно объект.
Уже посмотрел что решение использует кэширование ast, значит со скоростью всё не так плохо как я предполагал. И наличие самого AST тоже было очень приятно. Я имел в виду отдавать не результат вычислений, а функцию с замкнутой функцией соответствующей конкретному AST, после чего эта функция принимает на вход данные, а даёт результат.
В первом примере нужен break:
var name;
for (var i = 0; i < users.length; i++) {
    if (users[i].id == 123) {
        name = users[i].name;
        break;
    }
}
А я вообще первый пример заменил бы, на простую и понятную конструкцию.
var name = ( users.find( u => u.id == 123 ) || {} ).name

Да, спасибо.

Нужно больше строковых конструкций! Как это дебажить?

В этом смысле так же как и регулярные выражения, JQuery-селекторы или тот же SQL.

Тогда лучше взять что-то типа lodash/fp, Ramda, воспользоваться линзами (хотя это больше для хаскеля)

А что — разве будет легче дебажить?

Конечно, я же могу раскидать лямбды на именованные функции если надо + `tap` у lodash

Ну когда запрос настолько сложен, что требует дебага, можно и в коде переписать.
А так-то можно и в Jsqry, хотя, признаться, о таком способе использования я раньше не думал.


function f1(elt) { return elt > 2 }
function f2(elt) { return elt + 10 }
console.info(jsqry.query([1,2,3,4,5],'[ ?(_) ]{ ?(_) }', f1, f2)); // [ 13, 14, 15 ]
Натыкался на JSONSelect, 1,5k звезд на гитхабе, но к сожалению, последний апдейт 3 года назад.
Из приятного (что лично нравится мне) — это CSS selector-like way для выборки данных (то есть довольно интуитивно для веб-девелоперов), есть тесты, и есть подтверждение востребованности в виде большого количества девелоперов обративших на это внимание.
XPath — большой стандарт в котором есть много дополнительных опций для выборки. Насколько ваш вариант совместим со стандартом?
Я не стал заморачиваться. Просто //, но чую можно довести до ума :) Потом…
ммм выглядит интересно, но реальная польза будет когда подобный синтаксис запросов будет описан на нативном языке в виде модуля ES6 или ES7.

Библиотеки очень похожи по задумке. Jsqry значительно проще как по устройству так и по использованию за счет переиспользования обычного js для предикатов, в JSPath же изобрели свой язык предикатов со своими операторами и т.д. По функционалу, имхо 80-90% совпадает. Хотя в Jsqry срезы более продвинутые (поддерживется step параметр, как в Python), а также не нашел трансформации у них, только фильтрация. С другой стороны, в JSPath, разумеется есть и возможности, коих нет в Jsqry, например, '..' и '|' в location path. А, да, у них еще более громоздкий синтаксис подстановок — именованные вместо '?'. Впрочем, это можно расценить и как плюс.

Да, нужно больше языков, конечно!
Если серьезно, то по-моему после появления arrow-functions в ES6 или typescript (в котором они были от рождения), это всё на столько бесполезно…
как Arrow-функции помогают делать запросы к объектам? это обычные функции без собственного контекста.
Компактностью аргументов в методах обработки массивов типа map и reduce.
Самый серьезный минус в том, что рефакторинг и подсветка в ИДЕ не будет работать и не будет работать линт и тайпскрипт компилятор так же ничего проверить не сможет.
Нечто похожее я сделал для ActionScript, но сделал это на функциях и как часть библиотеки для работы с коллекциями. В принципе, я согласен с тем, что arrow-functions эту нишу во многом закрывают.
Array.prototype.find и filter, имхо, вполне решают основную часть проблем (и сразу есть в стандарте ES5-ES2015). В крайнем случае, можно и lodash подключить
Спасибо за проделанную работу.
Лично я планирую использовать эту библиотеку.
А то, что эту задачу можно решить в ES6 или typescript, ну так отлично. Ее можно решить используя еще множество других инструментов.
В данном случае мне нравится, что это JS изначально, а не «обертки», да и размер самой библиотеки для меня является тоже плюсом.
НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории