Описание с примерами можно почитать на гитхабе (лицензия MIT): github.com/xpl/useless

Предвижу вопросы «зачем это нужно», и «это уже есть в XXX», заранее отвечаю: эта штука существует и пишется уже не первый год, как pet project используемый в собственных разработках, то есть это некий сублимированный опыт просто. В последний год удалось собрать это все вместе в рамках открытой технологии, и опубликовать.

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

По поводу названия:

image

Комментарии (31)


  1. alex_blank Автор
    07.07.2015 13:29
    -4

    Починил баг в _.cps.memoize: теперь оно понимает функцию без аргументов


  1. kahi4
    07.07.2015 13:30
    +16

    Простите конечно, но мало того, что даже примерно не понятны преимущества, даже не понятно что это и для чего это. Можете чуть подробнее рассказать этот момент?


    1. alex_blank Автор
      07.07.2015 13:36
      -12

      Я думаю правильнее всего будет так: это нужно для тех, кто понимает, зачем это нужно. То есть чтобы это использовать нужно уже обладать серьезным опытом разработки, с использованием реализованных в этой библиотеке вещей, но в других средах. Я ничего нового не изобрел, просто реализовал то что я уже где-то использовал ранее, но не в JavaScript (в котором этого не было или было сделано хреново). В JavaScript я пришел относительно недавно, у меня в основном бэкграунд из desktop/enterprise вселенных.

      То есть у меня нет задачи в данный момент объяснить зачем нужны абстрактные continuation-passing style алгоритмы, или зачем нужны компоненты, или юнит-тесты с асинхронными ассертами, или traits какие-нибудь. Это очень сложная задача и она выходит за рамки моих текущих… может, когда-нибудь. То есть, поймите правильно, это потребует довольно внушительной по объему книги.


      1. northicewind
        07.07.2015 13:56
        +5

        Не надо описывать зачем нужны continuation-passing style алгоритмы, тесты или компоненты. Надо в первую очередь написать что вас не устроило в существующих решениях и как вы это решили. Почему вы сделали вывод, что ваша реализация более правильная, чем другие. Это было бы читать интересно.

        но не в JavaScript (в котором этого не было или было сделано хреново)

        Что вы понимаете под «сделано хреново» и чего, конкретно нет.


      1. kahi4
        07.07.2015 13:56
        +9

        Эм.
        1. Что это и для чего я так и не понял (я не про continuation-passing style и прочее, а про вашу библиотеку. Кросс-платформенную!)
        2. JS и node.js в частности — сложившаяся экосистема со своими наборами стандартных подходов. Не понимаю, почему у всех чешутся руки притянуть в нее что-то из других языков, когда готового уже навалом, а при умелом использовании оно еще даст фору возможностям других языков.
        3. «Asynchronous primitives» — посмотрите на promise и не городите костылей.
        «Advanced type detection / pattern matching» почему возвращает для bar: «baz: [number]», а не «object», как стоит ожидать без передачи отдельного флага.
        А что будет, если baz — не гомотипный массив, а, например, содержит строки и числа? И главное: зачем это?
        «Math utility for front-end works» — mathjs.org чем не угодил?

        domElement.css (BBox.fromPoints (pts).grow (20).offset (position.inverse).css)
        

        Адская строка.
        «Multicast model for method calls with simple functional I/O» — библиотек для events сотни. И да, в js так не принято, что если передается функция — выполняется привязка, а если аргументы — вызов. А если аргумент — это функция? Всегда есть on (или bind) и emit. Что-то типа стандарта. Более того, в последних версиях той же node.js из коробки есть.

        «Test framework» — mocha.js
        «Logging» — winston. Под него написаны коннекторы подовсе подряд, например, ваша библиотека может писать по UDP логи в Grayscale 2?

        Что я вижу при ближайшем рассмотрении? Что это — набор велосипедов, который вдобавок не совместим по API с стандартными решениями, местами необдуманное, ну и конечно же гораздо менее проработанное (над каждой из этих библиотек в отдельности работает больше людей, чем тут над всем вместе взятым). Прости уж.


        1. alex_blank Автор
          07.07.2015 14:15

          > когда готового уже навалом

          Готового всегда уже навалом. Вот появилось еще одно готовое. Так это «навалом» и появляется, между прочим. Кто-то считает что это плохо, но это спорно, я вот считаю что это очень хорошо.

          > посмотрите на promise

          Promise это другой интерфейс, он более сложный, чем просто CPS функции. Это просто разные вещи, кто-т любит функциональный интерфейс например. Вас же никто не заставляет отказываться от promise :) Хорошо когда есть выбор.

          > И главное: зачем это

          Там объяснено в readme, зачем. Наверное это стоит убрать оттуда, чтобы не создавалось впечатление, что это какой-то selling point, тогда как это просто низкоуровневые утилиты для более сложных вещей, которые еще не опубликованы. По этой причине оно возвращает [number], а не object, т.к. это нужно было для построения валидатора схемы данных (это более специфичный constraint, чем просто object)

          > библиотек для events сотни
          > Что-то типа стандарта.

          Ну мне этот стандарт не нравится, я не люблю синтаксический оверхед. Мне показалось прикольным сделать минималистичный двусторонний I/O интерфейс, когда вызов и биндит и вызывает. И это сработало.

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

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


          1. northicewind
            07.07.2015 14:23
            +1

            Я думаю вы неправильно поняли. Никто не отговаривает вас ничего публиковать. Просто людям всегда интересны подробности. Опишите более детально ваши решения, назначение, сделайте сравнение.
            Библиотек много не бывает, но применимость той или иной библиотеки очень часто решает наличие документации и примеров.


            1. alex_blank Автор
              07.07.2015 14:30
              -1

              Я понимаю что интересны, но и вы поймите, что это серьезная работа, описать все подробности, это даже более сложно чем эти подробности реализовать, в какой-то мере.

              Просто вы относитесь к этому анонсу как к презентации чего-то законченного, тогда как я просто ищу достаточно заинтересованных людей, чтобы присоедениться к разработке довольно сырой (как вы правильно заметили) на данный момент вещи — потому что я знаю, что такие люди есть, хоть их и немного :) Возможно я слишком давно не писал ничего на Хабрахабр и что-то упустил, и тут такое публиковать не принято, mea culpa в таком случае. Хотя в той же англоязычной среде народ в массе заценил, например — уж не знаю, то ли это связано со спецификой Хабра, то ли там народ более открытый и менее консервативный.

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


          1. kahi4
            07.07.2015 14:38
            +4

            Я не убеждаю вас в том, что вы зря это опубликовали. Я убеждаю вас в том, что вы зря это сделали, а не разобрались в том, что уже есть. На это есть несколько причин:

            Те же события — я привел пример, когда ваш вариант не сработает. А, между прочим, не такая редкая ситуация. Более того, как разбиндить? Как привязать несколько событий? Как сделать once? Продолжить?

            Promise — мало того, что у вас адски нечитаемый синтаксис получился, так еще опять же: промисы нативные, промисы простые, в промисы можно скармливать стандартные функции из-за особенностей порядка аргументов, дак они еще вылавливают ошибки (у вас этого не увидел. оО а где reject? Как вы ошибки в асинхронном коде собрались ловить?). Ну и можете не убеждать, что ваша библиотека для другого — именно альтернативой промисов она и выступает. Точнее, есть уже async.js.

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

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

            И вот в этом аргументе кроется вся соль — пока набор задач узок — все вроде работает. Но когда начинается шаг влево-вправо, вы тратите время на дописывание своего велосипеда, вместо того, чтобы решать боевые задачи. Более того, ваш велосипед охватывает слишком много областей, при этом не заходит глубоко ни в одну из них. А хуже — он противоречит сложившимся традициям, потому что «вам кажется так удобнее». Раз люди так не делают, значит на то есть причины, а не потому что люди глупы и делают так, как неудобно.

            А что касается
            отового всегда уже навалом. Вот появилось еще одно готовое. Так это «навалом» и появляется, между прочим. Кто-то считает что это плохо, но это спорно, я вот считаю что это очень хорошо.

            Качественное из этого «навалом» появляется редко. Как правило — небольшой велосипед, который умирает через неделю. Чтобы сделать действительно большой шаг — нужно предложить что-то вкусное. В вашем же случае — это сомнительный набор велосипедов, от которых даже вы откажитесь в ближайшем времени (надеюсь, сегодня же) в пользу существующих альтернатив, поняв, что это позволит решать бизнес-задачи, а не сидеть днями над допиливанием своей же библиотеки.


            1. alex_blank Автор
              07.07.2015 14:52
              -2

              > Как вы ошибки в асинхронном коде собрались ловить

              Для сложной семантики всегда есть promise, иногда достаточно просто что-то типа вернуть undefined в коллбек, вот и весь error handling. То есть promise мне видится тяжелой артиллерией, и сравнивать его с обычными континуейшенами нет смысла, это разного уровня абстракции вещи совершенно.

              По поводу async.js — это писалось когда никакого async.js еще и в помине не было, и по итогу мне это больше нравится, чем async.js. Я просто хотел сделать порт базовых underscore-примитивов в CPS термины, и вот оно есть. При чем тут promise, я хз. Это сравнение теплого и мягкого.

              > Современные библиотеки для тестирования на js и логирования друг с другом совместимы

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

              > решать бизнес-задачи

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


              1. kahi4
                07.07.2015 15:03
                +4

                По поводу async.js — это писалось когда никакого async.js еще и в помине не было

                Первый коммит в async.js — май 2010. Не думаю, что вашему велосипеду столько лет. Ну и я в упор не вижу разницы между вашей библиотекой и тем же Q, не считая синтаксиса и пространства имен (и гораздо большего количества возможностей у последнего).
                 _.mapReduce (array, {
                                  maxConcurrency: 10,
                                  next: function (item, index, next, skip, memo) { ... },
                                  complete: function (memo) { ... })
                

                против
                Q.all([func]).then(complete);
                

                Да, массив func подготовить нужно будет предварительно, слету не придумаю, но что-то мне подсказывает, что есть способ подставить туда map из underscore.

                Я решаю творческие задачи в первую очередь, бизнес-задачи мне не так интересны,

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

                Поймите, я ни в коем случае вас не упрекаю, просто говорю, что большинство из этого есть, готовое, проверенное, отлично работающее, и вам использование этого сэкономит много времени.

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

                Опять, рано или поздно вам придется сделать шаг влево или вправо. И тут либо использовать уже готовые модули, либо городить остальные свои интерфейсы. Вот честное слово, так скоро дойдете до «node.js ерунда. Вот моя альтернатива на моем движке, потому что мне не нравится как там сделано обращение с файлами!»


                1. alex_blank Автор
                  07.07.2015 15:14
                  -4

                  Я что-то сомневаюсь, что в 2010 году кто-то знал про async.js кроме его разработчика :) Та функция из useless что вы привели кстати примерно в тех годах и появилась, и мне самому её интерфейс не очень нравится, т.к. он несовместим с прочими функциями из неймспейса _.cps. Это что-то типа obsolete кода, до переделки которого еще руки не дошли.

                  Если вам нужно асинхронно пробежаться по массиву параллельно/последовательно, без ограничения maxConcurrency, то правильно будет сравнивать с _.cps.reduce или _.cps.map или с _.cps.each, в зависимости от требуемой семантики:

                  _.cps.map ([1,2,3], function (x, then) { then (x + 1) }, log) // prints [2,3,4]
                  


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


            1. alex_blank Автор
              08.07.2015 01:33
              +1

              Извиняюсь, только сейчас руки дошли ответить на ваши вопросы по событиям.

              Те же события — я привел пример, когда ваш вариант не сработает. А, между прочим, не такая редкая ситуация.

              Ситуация довольно редкая (в проекте на много kloc всего раз пришлось так сделать). Но вы прежде чем делать выводы предосудительные, учли бы что в readme-файле я не собирался все-все-все use cases расписывать, это интро, а не референс (которого еще попросту нет, вместо него можно почитать исходник просто). Не надо думать что этого там нет, если вы не прочитали про это там :)

              Передать аргумент-функцию можно вызвав write-метод у такого объекта-функции напрямую. Почему он называется write — потому что и trigger, и barrier и observable реализованы как конфигурации абстракции stream (у которой есть read и write), т.е. это типа абстрактного потока ввода-вывода. Поэтому и название абстрактное, а не «fire» например.

              somethingHappened = _.trigger ()
              somethingHappened (function (x) { log.red  (x) })
              somethingHappened (function (x) { log.blue (x) }) // под «привязать несколько cобытий», вы это имели в виду?
              somethingHappened.write (function () {}) // передаст эту функцию в коллбеки
              

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

              somethingHappened (log.red) (log.blue)
              


              Более того, как разбиндить?

              Разбиндить можно с помощью метода off (в котором кстати был неприятный баг, не покрытый тестами, и если бы не вы, я бы его возможно не скоро заметил!):

              somethingHappened.off (log.red)
              


              Как сделать once?

              Пока никак, не было необходимости еще (хотя я понимаю что это нужно, в jquery я юзал once пару раз в практике). Я добавляю фичи по мере востребования. Если кому-то понадобится, реализовать это можно довольно быстро.

              Продолжить?

              Продолжайте, конечно. Вы помогаете.


              1. alex_blank Автор
                08.07.2015 01:43

                Ах да, еще можно отбиндить сам коллбек от всего, на что он был забинжен:

                whenSomething     (log.red)
                whenSomethingElse (log.red)
                ...
                _.off (log.red)
                


              1. alex_blank Автор
                08.07.2015 04:38

                Как сделать once?


                Добавил once:

                whenSomething.once (doSomething)
                


              1. alex_blank Автор
                08.07.2015 04:50

                Удобнее всего это использовать с компонентами, псевдокод примитивного слайдера:

                Sliddah = $component ({
                
                    $requires: {
                        min:   'number',
                        max:   'number',
                        value: 'number' },
                
                    value: $observableProperty (),
                
                    init: function () {
                        this.dom = $('<div class="sliddah">')
                            .append (this.handle = $('<em>'))
                            .drag ({
                                callMoveAtStart: true,
                                move: this.$ (function (memo, where, offset) {
                                    this.value = _.rescale (offset.x,
                                        [0,        this.dom.width ()],
                                        [this.min, this.max], { clamp: true }) }) })
                
                        _.delay (this.$ (function () {
                            this.valueChange (this.$ (function (v) {
                                this.handle.css ({
                                    left: Math.round (_.rescale (v,
                                                        [this.min, this.max],
                                                        [0, this.dom.width ()], { clamp: true })) }) })) })) },
                
                    destroy: function () {
                        this.dom.destroy () } })
                

                var slider = new Sliddah ({
                    min:   10,
                    max:   100,
                    value: 42,                                          // inits $observableProperty from config
                    valueChange: function (x) {                         // binds from config
                        this.handle.text (x.toFixed (0))  } })          // 'this' passed to trigger callback
                
                $(document.body).append (slider.dom)
                


                1. alex_blank Автор
                  08.07.2015 05:05

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


              1. kahi4
                08.07.2015 13:34

                Я впечатлен вашим стремлением!

                somethingHappened (function (x) { log.red  (x) })
                somethingHappened (function (x) { log.blue (x) })
                

                Очень очень неочевидно, на мой взгляд. Ну и лично мое мнение — когда что-то можно использовать как функцию, и одновременно как объект (т.е. тут вы вызываете как функцию, а потом используете off всякие) — плохой тон. Но это лично мое мнение.

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

                Опять же посоветую посмотреть на тот же winston. На ту же mocha.js. События — нравится такая семантика — ну что поделаешь. Но тут опять же эффективнее было бы расширить что-то готовое (обернуть точнее), чем писать свое.


                1. alex_blank Автор
                  08.07.2015 13:56

                  нравится такая семантика — ну что поделаешь


                  Мне она кажется более literate, т.е. гуманизированой, что-ли. «еслиЧтоТо (сделатьТоТо)» это более по-человечески и кратко, чем «еслиЧтоТо.связатьС (сделатьТоТо)», как-то так.

                  Но опять же, зависит от способа мышления. Надо понимать, что технологии которые мы делаем отражают способ которым мы видим мир, и у каждого этот способ свой. Хорошо когда есть выбор и каждый может найти что-то пригодное для себя (а если нет, то сделать это). Иначе это фашизм попросту ;)


                1. alex_blank Автор
                  09.07.2015 14:12
                  -1

                  По поводу плохого тона — дело в том, что кому-то например jQuery-объект $ и chaining кажется плохим тоном. Вам возможно тяжело в это поверить, потому что многие из сегодняшних разработчиков уже не представляют жизнь без jQuery, но когда-то это было очень спорным решением. Я, к счастью, к таким не отношусь и вообще не оперирую такими вещами как «плохой тон» в аргументации — это субъективизм, а если вы хотите что-то кому-то объяснить, к такому прибегать нельзя.

                  Биндинг событий к функциям это настолько частая операция в коде, что само собой напрашивается сделать это еще проще, убрав промежуточный вызов on/bind. Собственно, это ровно та же мотивация, что побудила разработчиков упомянутого jQuery создать оператор $. Стремление к простоте и выразительности, стемления к избавлению от захламляющего код бесконечных повторений одного и того же.


    1. alex_blank Автор
      07.07.2015 13:54

      К тому же это просто анонс. Позже, по мере поступления я буду делать демо-приложения / комплексные примеры, где будут рассматриваться конкретные use cases тех или иных вещей. Просто это очень сложная работа, на которую не всегда есть время, и она сразу из ниоткуда не появится — а в текущем виде это адресовано тем, кто и без этого сможет разобраться и найти для себя что-то полезное. Это лучше, чем ничего.


      1. Ashot
        07.07.2015 14:27
        +5

        Если честно, даже на анонс не тянет.

        это адресовано тем, кто и без этого сможет разобраться и найти для себя что-то полезное

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


        1. alex_blank Автор
          07.07.2015 14:31

          Ну у меня есть и обратные примеры, то есть мне конечно печально что кто-то воспринял это как наезд и личную обиду — то что им анонсировали что-то для них непонятное и не потрудились объяснить, зачем. Но это не так.


          1. Ashot
            07.07.2015 14:39
            +2

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


            1. alex_blank Автор
              07.07.2015 14:53
              -1

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


  1. 505abc
    07.07.2015 14:32
    +1

    Так это типо Utils.js или Helpers.js, которые есть у любого синьора JS разработчика и которые он тащит из проекта в проект? Только в данном случаем вы еще и со своим самоваром пришли?


    1. alex_blank Автор
      07.07.2015 14:37
      +2

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


    1. KReal
      07.07.2015 19:27
      -1

      У синьора свои Helpers.js? Это ещё личинка синьора тогда)


      1. alex_blank Автор
        07.07.2015 21:01

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


  1. dom1n1k
    07.07.2015 19:58

    Я так и не понял, что подразумевается — use less или useless?


    1. alex_blank Автор
      07.07.2015 21:02
      +1

      Тут полная свобода интерпретации. Каждый увидит то, что ему по душе.