Как я проект с JavaScript на Scala переписывал


    Я никогда не смогу ходить! Потому что я ползаю.
    —Цитаты великих

        Меня всегда учили прежде всего здороваться, так что — здравствуйте. Сегодня я расскажу про творческие (и не очень) муки, страдания и боль, которые я испытывал на протяжении определенного периода своей жизни, который я обозначу как ПРОЕКТ. Сначала он был на JavaScript (node.js), а теперь он на Scala (Play). Сразу скажу, что я — один из самых субъективных негодяев в обозримой Вселенной, поэтому некоторые обороты, высказывания и иже с ними могут быть восприняты уважаемыми читателями весьма неоднозначно. Короче, я предупредил. И у меня еще одна небольшая просьба — если уж взялись прочитать статью, то не кидайтесь сразу строчить разоблачающие комментарии. Дочитайте. Я не Пастернак, правду говорю. И вообще, почти все спорные моменты так или иначе освещаю, объясняю.

    Пролог


        Но для начала я позволю себе небольшое отступление и расскажу, что же я делал, и как давно это началось.
    Примерно года полтора назад я как раз стоял перед выбором темы для дипломной работы в моем техническом ВУЗе. Конечно, я мог бы отмазаться каким-то банальным сайтом для автомойки, очередной концепцией нового дизайна для сайта Кофехауза или еще чем-то похожим (кстати, это реальные дипломные проекты, и меня от этого коробит, но опустим). Но мы ведь упертые, да еще и профессионалы в своем деле! Легких путей не ищем и бла бла бла.
        Стоит сказать, что к тому времени у меня за плечами было уже порядка 3 лет именно рабочего опыта и ~6-7 лет просто угорания по программированию, а конкретно — по вебу. Поэтому вопросов о реализации передо мной не стояло. Осталась тема, то есть тот самый ПРОЕКТ. Местные ребята должны знать забавную статью про разработку через страдания.
        Так вот на тот момент я действительно испытывал страдания при совместной работе или изучении очередного ЯП с товарищами. Мне нужен был инструмент на подобии pastebin, т.е. банальный как квадрат (отправил — копипастнул ссылку — поделился), но все же с фишкой Google Docs, а именно — одновременным редактированием кода. Согласитесь — это круто, когда ты просто видишь, как кто-то поставил курсор на косяк и исправил его в два нажатия по клавишам. Ну всяко быстрее, чем один и тот же код дублировать каждый раз, меняться ссылкам. Боль.
         И вот пошарился я по этим вашим интернетам в поисках такого сервиса, и… не нашел. Конечно, я в курсе про плагины к тому же Eclipse, Sublime и т.п., и даже знаю про целые standalone-решения. Но вот что-то простое как pastebin я не нашел. Отсюда и начал свой отсчет мой ПРОЕКТ.

    Глава 1. JavaScript


        Из пролога у вас могло уже сформироваться краткое ТЗ, что же из себя представляет нужный сервис, и как его следует исполнить.
    Имеем, грубо говоря, некие пользовательские чатики, где вместо чата — код, над которым все и корпеют. Как? Если кратко — не люблю флэш, хочу вебсокеты.
        На тот момент я плотно сидел на PHP, но на нем писать WebSocket сервис, где полнодуплексные соединения могут висеть примерно бесконечность — прямая дорога в Ад. Поэтому я обратил свое внимание на node.js как WS-сервис, а статику генерить и отдавать пыхой. И знаете, что? Это было круто. Прототип я накидал буквально за пару дней. Все работало, и это было непередаваемое ощущение. Будто ты только что до конца осознал теорию струн. Или догадался, куда пропадает информация в черной дыре. Ну вы меня поняли, да? А тогда как раз еще вышел релиз nginx'a, который умел в проксирование WebSocket.
    Ляпота
    //Кусок кода, который отвечал за прием сообщений и контроль соединения. Довольно кратко и доступно.
    var sockjs_im = sockjs.createServer(sockjs_opts);
    sockjs_im.on('connection', function(c) {
        //устанавливаем таймаут на начало обмена
        sender.setValidTimeout(c);
        //если пришли данные
        c.on('data', function(message) {
            sender.process(message,c);
        });
        c.on('close',function() {
            connection.removeConnection(c);
        })
    });
    
    var server = http.createServer();
    
    server.addListener('upgrade', function(req,res){
        res.end();
    });
    
    sockjs_im.installHandlers(server, {
        prefix: config.get("path")
    });
    
    exports.startWSServer = function() {
        server.listen(2410, '0.0.0.0',function() {
            console.log(' [*] Listening WS on 0.0.0.0:2410' );
        });
    }
    


        Но вернемся к делу. Я схватил этот молоток и начал колотить по всему вокруг, а там уже пусть сам разбираются — кто гвоздь, а кто верблюд. Взял самые хипстерские технологии: SockJS как клиент-сервер под WS-соединения, MongoDB как, вы не поверите, базу данных, Ace Editor как редактор на клиенте, слепил их и начал писать обвязку, логику.
    Тут я сделаю маленьку пометочку — проект был дипломный, поэтому мне нужно было делать все быстрокачественнодешево и сразу, а ведь еще и работу надо было работать, сдавать что-то.

        Месяц спустя после очередной банки энергетика я, оторвав красные глаза от экрана, понял, что на свет родился монстр, тварь, нечто, что лучше бы исдохло сразу. Не, оно работало, без сбоев, функционал был почти весь готов. Но то, как оно работало — внушало священный ужас. Я совершил фатальную ошибку и не переписал прототип. Я его нарастил. Код стал слиииишком сложен и избыточен.
        С этого момента работа над проектом превратилась в пытку. Добавление новой функции или фишки требовало немалого усилия. Я чувствовал себя Сизифом, только камень был еще и квадратным. Страдания.
        Логичный вопрос — что ж ты такого там наговнокодил? Вот небольшой список:
    • Callback hell
    • Попытка писать ООП на JS с его prototype
    • … отсюда попытка сделать уберабстракцию над абстракцией
    • … а пока ты абстрагируешься — уже приходит седьмой, мать его, круг callback-ада и ты здороваешься за руку с богохульниками и содомитами (как же был прав Данте!)

        И вот подходит ко мне Минотавр, который охраняет пояса седьмого круга и говорит:
        -Эй, парень. Ну ты же сам дурак. Написал чертовщину, ногу сломишь, сам же сознался. В чем твоя проблема?
    И знаете, что я отвечу? Это все JavaScript. То есть не поймите неправильно, я не имею ввиду, что я такой милый и пушистый с iq >>> бесконечности. Я имею ввиду то, что язык сам подталкивает тебя писать именно так, а не иначе. Эдакий змий, который шепчет:
        -Да ладно, браток, воткни-ссс здесь быстренько третий колбэк в аргументы, ничего не убудет-ccc....
    И такой длинный красный язык перед глазами.
    Выглядело все это примерно так
    
    /**
    * Загрузка сессии. а точнее - передача колбэка в загрузчик сессии...
    */
    var loadSession = function(sessid, callback) {
        if(typeof sessid != 'string') return;
    
        var sess = sessions.getSession(sessid);
    
        sess.setLoadCallback(callback);
    }
    
    ...
    
    /**
    * Загрузим сессиию, получим пользователя, попробуем получить комнату, потом попробуем туда пихнуть юзера.
    * Все в колбэки-колбэки, а там внутри еще колбэки.
    */
    loadSession(msgObj["sessid"], function(sess) {
            var r = rooms.getRoom(msgObj["room"]);
    
            r.setLoadCallback(function() {
                r.addUser(connection, sess, function(newUser) {
                    if(newUser!==false) {
                        var outMsg = {
                            action: "newUser",
                            data: newUser
                        }
                        r.broadcast(connection, outMsg);
    
                        r.write(connection,{
                            action: "join"
                        });
                    }else{
                        error(connection,{
                            error:true,
                            errsmg: "something went wrong"
                        })
                    }
                });
            });
        });
    ...
    


        Возможно, это все энергетики, и никакой минотавр со змием ко мне и не приходили (приползали), но получилось то, что получилось. Диплом был сдан, но тварь жить осталась, если это можно было назвать жизнью.
         После нескольких попыток отрефакторить это чудовище или вообще переписать все на coffeescript, я забросил всю эту чертовщину до лучших времен и уехал кататься по Европе. Дааа, так глубоко я пал и так далеко бежал от кошмаров прошлого!

    Глава 2. Scala


        Прошло полгода. Чудище все существовало, а у меня не было никакого желания его добить и выпустить уже в продакшн. Открывая код на JavaScript мне приходилось срочно бежать за металлическим тазиком, который со звоном использовался под рвотные массы.
        И тут, заблудившись в интернете, я оказался на сайте Play Framework. Уже не помню, что же меня привлекло и задержало на сайте, да и важно ли? В итоге, через день я уже копался с фреймворком, писал первое приложение и записался на курс Scala на coursera.org.
        Не могу сказать, что было просто, особенно по началу. Конечно, питон или пыха попроще, но имея бэкграунд на Qt/C++ и Java я разобрался в скалке довольно быстро, по крайней мере в основных моментах. Чтобы вкурить в неявные преобразования и параметры, ко/контр/инвариантность и иже с ними понадобилось поднапрячь свой гугл-скилл в поисках различных примеров и документации, дабы составить общую картину происходящего где-то там, под капотом. И все же какое-то время я чувствовал себя тупым валенком, хотя есть мнение, что это нормально.
        И вот, немного набив руку, я решил посмотреть, как Play умеет в вебсокеты. И вот тут меня словно ушатом с ледяной водой окатили. Первая реакция была простой — WTF??? Куда делись милые и доступные решения, что это за функционалохардкор с околонизкоуровневыми Iteratee/Enumeratee? Да, ничего общего с той ляпотой на JavaScript. Верните мне мои push и onMessage!
    Хрен тебе, а не каналы
    def index = WebSocket.using[String] { request => 
      
      // Just consume and ignore the input
      val in = Iteratee.consume[String]()
      
      // Send a single 'Hello!' message and close
      val out = Enumerator("Hello!").andThen(Enumerator.eof)
      
      (in, out)
    }
    


        Однако, будучи наученным горьким опытом слишком простых решений, я решил не сдаваться и снова взял в руки… Нет, не молоток. Гугл. В итоге нашелся приятный подход через
    Concurrent.unicast
    val promiseIn = Promise[Iteratee[String, Unit]]()
            val out = Concurrent.unicast[String](
              onStart = onStart(promiseIn, r, userSession),
              onError = onError
            )
    (Iteratee flatten (promiseIn.future), out)
    
    ...
    
    private def onStart(promiseIn: Promise[Iteratee[String, Unit]], ...): (Channel[String] => Unit) = {
    ...
        (ch: Channel[String]) => {
          val channel = new ChannelContainer(ch)
          for (optUserConnection <- isConnectedF(r, channel)) yield {
            optUserConnection match {
              case Some(userConnection) => {
                val in = Iteratee.foreach[String] {
                  MessageController onMessage (r, userConnection, channel) //bind handler for room and this connection
                } map {
                  MessageController onDisconnect (r, userConnection, channel) //handler for disconnect
                }
                promiseIn success in //success promise and fill it with iteratee
              }
              case None => channel eofAndEnd //in case of some troubles with new user creation - close connection
            }
          }
        }
    }
    


        Да, согласен. Смотрится все же это не так мило и весьма громоздко, но позволяет использовать каноничные каналы, создающиеся для обмена сообщениями, запихивать эти каналы в обертки, передавать их сообщениями в акторы… Акторы!
        Акторы в Scala. Рахат-лукум моего сердца. BEST PARADIGM EVAH! Ну или уж точно то, что надо для моих целей. Комната — актор, менеджер комнат — актор. И даже клиенты — акторы. Логично вливается в идеологию об обмене сообщениями между пользователями. Кстати, разработчики Play тоже прочухали эту тему, и начиная с недавно вышедей версии 2.3
    WebSocket-соединения теперь тоже акторы
    import play.api.mvc._
    import play.api.Play.current
    
    def socket = WebSocket.acceptWithActor[String, String] { request => out =>
      MyWebSocketActor.props(out)
    }
    

    import akka.actor._
    
    object MyWebSocketActor {
      def props(out: ActorRef) = Props(new MyWebSocketActor(out))
    }
    
    class MyWebSocketActor(out: ActorRef) extends Actor {
      def receive = {
        case msg: String =>
          out ! ("I received your message: " + msg)
      }
    


        И это прекрасно, когда Вселенная тебя слышит. Мои сигналы таки отразились от какой-то поверхности в световых годах отсюда, на планете бабочек, какающих радугами, и прилетели обратно. Кванты таки запутались, Алиса и Боб нашли друг друга. А я обрел спокойствие.
        А почему? Потому что Scala в большинстве случаев просто смотрит на тебя как на говно, если ты делаешь что-то не по правилам. Она как бы говорит тебе:
        -Парень, я даю тебе нативную поддержку Future в виде монад, JSON сообщения кастую в нужные тебе инстансы case class'ов, предоставляю возможность наследования множества трейтов, я проверяю тебя, слежу за каждым чихом при компиляции, не играй со мной в игры. Делай нормально или возвращайся в свой Ад к Минотаврам.
        Строгий, но справедливый компаньон. Бородатый Хайзенберг программирования. Yea, Scala, biaaatch! А в спину тебя буравит глазами сам Мартин Одерски.

    Эпилог


        Итак, что можно сказать после столь сумбурного потока водысознания, каков вывод?
        Первое. Я не сравниваю языки. Упаси макарон. Сравнивать Scala и JavaScript — это как сравнивать льва и бабушкины пирожки. Да, вы можете съесть бабушкины пирожки и будете сыты. А вот льва — вряд ли. Зато лев может съесть вас. Это разные языки для разных задач. Кто будет писать на Scala фронтенд?
        Второе. Я сравниваю возможности, даже вернее — подходы, которые предоставляют языки и платформы, используемые в решении конкретных задач. Господа, но колбэки не компонуются! Это гребаный приговор.
        Я могу взять и сделать в одну строку из десятка Future один единственный и ждать его исполнения. Просто потому, что Future — это монада, все выглядит просто и естественно, в духе языка. Для колбэков мне придется писать обертку, которая либо будет считать количество окончившихся функций, либо станет полноценным Deferred/Promise. Да, да, я знаю про существование подобных библиотек для JavaScript. Но это же просто раздутые обертки к тем самым колбэкам. Замкнутый адкруг.
        Третье. Написал бы я сейчас на JavaScript лучше, чем тогда? Несомненно. Это, возможно, даже выглядело и работало бы попроще. Опыт в решении задач одного типа — это опыт, его не пропьешь. Вышло бы лучше, чем на Scala? Не факт. Да, на Scala местами приходится писать гораздо больше кода, но я, черт возьми, уверен в нем! Мне не нужно писать сотни тестов на один метод, который принимает объект из JSON'a, чтоб убедиться, что все идет так, как я задумал. Что какой-то скрипт-кидди не подставил строку вместо массива, массив вместо числа и т.п. За меня это сделает язык, платформа, компилятор.
        Четвертое. Scala сложна, Scala сложна, Scala сложна. Тысячи их. Бла бла бла. Знаете, что сложно? Держать в голове контекст this и еще сотни аспектов, которые постоянно мытарствуют по нервным узлам труъ JavaScript-ниндзя. Серьезно, что бы писать большой проект с кучей логики на JSe, нужно быть гораздо большим спецом, чем на Scala. Я это ощутил на своей заднице. Уважения тем, кто каждый день идет в этот неравный бой. Вы круты, без шуток. JavaScript не прост.
        Но для себя я решил — не страдать. Зачем?
    Поделиться публикацией

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

      +47
      У меня такое ощущение, что callback hell — это специальный, восьмой круг. Специально для тех, кто «статику пыхой отдает».
        +2
        Возможно. Однако у меня не было на тот момент времени писать еще и обвязку типа реги/логина, логики доступа к комнатам и их рендера на js'e. Так что я просто взял ранее написанные пыхорешения, встали как влитые.

        Сейчас-то все, само собой, на одной платформе работает.
          0
          Я второй год на Scala Days продвигаю тему, что разработчики Play — бакланы :) В прошлый раз задавал им вопросы и было много лулзов. Spray куда лучше, хотя скорее не для сайтиков, а для http. Хотя шаблонизатор туда очень просто влезает и с WS не думаю, что какие-то проблемы. Правда чуваки используют scalaz и IDEA иногда не очень понимает, что происходит :)
            0
            Ну хз, сейчас мне фуллстек фв как-то приятнее.

            Конечно, можно взять http(s) сервис, натянуть шаблонизатор и любимую либу к бд, собрать свой лего. Но опять же — деплой с нуля руками пиши, sbt ковыряй. Выполнимо, не суперсложно, но зачем?

            Кстати, плей переезжает/переехал на spray, если мне не изменяет память.
              0
              Ну, как бы, api first.

              Вот, чтобы клиентский api был удобен, без магии. Да и вроде в Typesafe Reactive Platform для работы с БД Slick положен, а тут по дефолту не он, что странно.

              Переехали на spray-io, клиентский api опять же свой.

              Может правда я покастомнее люблю, но мы с пацанами когда выбирали, как-то сразу всем spray больше понравился.
        +3
        Я могу взять и сделать в одну строку из десятка Future один единственный и ждать его исполнения.

        Также как и в JS'ике.

        Вообще можно было с самого начала написать с оглядкой на грядущий «callback hell».
        Или же сразу посмотреть в сторону bacon.js, Rx.

        Если ещё есть время — можно поиграться с websockets в эрланге, думаю будет интересно ;)
          +2
          Да я с Iteratee итак навеселился вдоволь. Ребята вдруг решили, что кто-то по wsу будет гонять тонны мегабайт, поэтому СРОЧНО нужно делать асинхронные итераторы/энумераторы с возможностью вытягивать ограниченное количество данных. Благо, от стандартных on/push не отказались до конца.

          Вот теперь хочу перенести WS соединения на акторы в новой версии фреймворка.

          Если честно, приелось писать одно и то же ради писанины :) Хочу уже запустить корабль в плавание.
          0
          ОФФТОП: пейстбин для совместного кодинга полно, я лично юзаю collabedit.com/
            0
            Я когда в 2013 тыкался по поисковикам с запросами типа collaborative editor, в топе были редакторы из статьи википедии. И ничего, что было нужно мне
            +14
            Может это все ваши энергетики, но подача мне понравилась, легко читать, когда постоянно что-то эмоциональное в тексте происходит
              0
              Я с ними завязал и стараюсь более не употреблять, а то вот с животными разговариваешь.

              Но на добром слове спасибо.
                0
                Присоединяюсь к предыдущему оратору. Стиль изложения понравился.
              +4
              github.com/caolan/async#waterfall спасает от ада кобеков
                +6
                От callback hell так же отлично спасают промисы.
                  0
                  Для колбэков мне придется писать обертку, которая либо будет считать количество окончившихся функций, либо станет полноценным Deferred/Promise. Да, да, я знаю про существование подобных библиотек для JavaScript. Но это же просто раздутые обертки к тем самым колбэкам.

                  Я к тому, что нормальных нативных решений на тот момент не было. Не знаю, как там сейчас промисы в ECMA поживают.
                  А все подобные либы и решения — обвязки, костыли, пусть иногда и красивые, но все же в парадигму языка вливаются не на 100%.
                    0
                    А что вы называете «парадигмой языка»?
                  +1
                  А теперь, если вы с полученными знаниями перепишете все заново на JS, используя аналогичные инструменты и библиотеки — получится ничуть не хуже.

                  1) Хорошая архитектура на языке X всегда лучше плохой на языке Y.
                  2) Scala + Play vs. raw JS? Что мешало использовать JS-фреймворки?
                  3) Любая success-story вида «переписал с языка X на язык Y» неинтересна: второй подход к тому же снаряду всегда получится лучше, ибо набиты шишки, и четко известно, что надо писать.
                    0
                    Написал бы я сейчас на JavaScript лучше, чем тогда? Несомненно. Это, возможно, даже выглядело и работало бы попроще. Опыт в решении задач одного типа — это опыт, его не пропьешь.

                    Однако есть еще одно но, я его в статье не упомянул, так как ну ооочень субъективно. Я получаю удовольствие от Scala, с ее статической типизацией внутри спокойнее как-то что ли. От JS я страдаю, поэтому даже нынешний фронтэнд писал на coffee + angular
                      +2
                      Я вовсе не защищаю JS как язык. Сравнивать тщательно спроектированную Scala с написанным в спешке за 10 дней JS — глупо, да и потроллить nodejs-хипстеров я сам в первых рядах. Я всего лишь о некорректности сравнения. Да, то, что есть в Scala из коробки, в JS достигается дополнительными инструментами, но это же не имеет никакого значения для достижения результата.

                      Angular — это прекрасно; когда приходится дописывать старые проекты, где он не используется, испытываю адские мучения. Ну вот в ангуляре же вы наверняка используете deferred/promise, и никакого callback-hell нет и в помине? А q.all — тот же future, вид сбоку. :) Да, не так эстетично, но работает.

                      Типизацию можно получить в compile-time как минимум двумя способами — closure compiler и typescript.

                      Так можно сказать по каждому пункту. Мой посыл же в том, что:
                      1) выбор архитектуры и инструментов куда важнее выбора языка,
                      2) переписанное всегда будет лучше изначально накостыленного, вне зависимости от выбора языка.

                      Ведь кто-то может подумать, что надо срочно выбрасывать написанное на node.js и переписывать все на Scala. Нет, может быть, что-то и стоит выбросить, но причиной тому всегда будет не язык, а неудачная архитектура.
                        0
                        Так да, я говорил о том, что не переписал прототип.

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

                        В сухом остатке меньше кода — меньше багов (не всегда, но чаще всего).
                          +1
                          Сотни библиотек имеют тенденцию превращаться в стройный полноценный фреймворк. А при наличии такового уже становится не важно, какой язык в основе.
                    –3
                    Эмоциональная статья. Но так и должно быть — Вы сами должны были найти свои грабли о пройтись по ним. JavaScript всё же возник для клиентской части с её событийной моделью поведения (оттуда и колл-беки). Писать на нём серверную часть можно, но утомительно.

                    А ведь могли сразу взять Питон и наслаждаться ;-)
                      +11
                      Дык промисы жеж github.com/kriskowal/q — вот кстати дефолтная реализация.

                      *** Понаписывают Js под энергетиком а потом всякая нечисть является )) ***
                        0
                        Мне промисы помогли, наверно, потому что я начал сразу с них, избежал колбэков ;)
                        И да, я тоже написал свой GoogleDocs+pastebin+возможность запускать.
                          0
                          промисы, asyncawait для ноды (как простое решение) или fp/frp (как чуть более хитрое) запросто избавляют от многих проблем.
                          особенно fp/frp, если его правильно использовать.
                            +3
                            понравилось изложение, спасибо)
                              0
                              Господа, но колбэки не компонуются! Это гребаный приговор.


                              Браво!
                                0
                                Node исповедует (в лице npm) концепцию маленького ядра и кучи мелких модулей.

                                Так-то рекомендовал бы q, например.

                                Да и вообще, есть еще счастье в виде node --harmony (http://kangax.github.io/compat-table/es6/).

                                Или еще большее счастье в виде habrahabr.ru/post/116124/

                                  0
                                  А почему node-fibers большее счастье чем generators & yield?
                                    0
                                    возможно, и ничем. мне привычнее :)
                                  +1
                                  Автор не справился со сложностью и сделал ошибки, затем переписал. Причем здесь Node.js или Scala, мог бы ведь быть ровно обратный порядок. Или любые другие языки. Проблема то в том, что видимо не была изначально продумана архитектура приложения.
                                    +2
                                    Если нравятся акторы, но не нравятся iteratee — пишите на эрланге и будет вам счастье.
                                      0
                                      Счастья на Эрланге не будет! Проверял. Эрланг счастье толко для тех, кто любит тратить свою жизнь на изобретение велосипедов.
                                      Ибо библиотек нету.
                                      Увы, видимо scala -реальный выбор.
                                        +1
                                        Вы неправильно понимаете, как его использовать.

                                        Эрланг — это роутинг, парсинг и поддержание коннектов. Все остальное — если его мало, тоже на э-ге; если много — на другом языке.
                                          +1
                                          Звучит как-то похоже на «статику генерить и отдавать пыхой» :)

                                          Ну т.е. в каком-то жестком хайлоаде это может и имеет смысл, иначе — еще один промежуточный уровень чего-то, что может поломаться. Мерфи почти всегда, сука, прав.
                                          0
                                          А каких вам лично библиотек не хватило для счастья?
                                            0
                                            Ерланг сильно специфичный. Причем в своих специфичных нишах далеко не лучший.
                                            Вот говорят что ерланг хорош для веба.
                                            Лично мне не хватило нормального http клиента, например ;))
                                            Такого, чтобы не умирал на некоторых dns. Такого чтобы >200 коннектов поддерживал ;))
                                            А то как-то нелепо, когда язык позиционирует себя как «многопоточный для интернета»
                                            а over 200 http-коннекшенов- не добится.
                                            И надо самому http клиента пилить.

                                            Я писал на эрланге паука dirs.info/spider
                                            и стриминг сервер к-й стримит поток jpeg'ов через webcoкeты.

                                            В итоге парсил html на недо-парсере от mochiweb'a. Он слабый. XPath имплементированы на половину только. Пилить его и пилить.
                                            Работы со строками нормальной сильно не хватило ;)) Списки-строки идут на юг. Никогда больше!
                                            Когда html страничка была больше 2 мегабайт строки на списках плакали и работали часами. Приходилось со страничками как с бинарниками работать.

                                            Огорчает когда язык позиционируется как кластеризуемый из коробки, и при этом нет zero-deployment'a из коробки.
                                            Меня gridgain на java куда больше радовал в плане легкости работы именно из-за zero-деплоймента.

                                            В общем, считаю что зря потерял время. Лучше бы scala какую выучил или акку.
                                            Говорят сейчас какая-то либа появилась для zero -deploymenta на ерланге. Ну дай-то бог.

                                            Ну про надежность эрланга не буду говорить, это отдельная тема вызывающая холивары.
                                              0
                                              Ваша история очень похода на мою — я тоже на Erlang на работе пишу пауков уже пару лет (Одного сложного дорабатываю постоянно и штуки 4 помельче). И отчасти я с вами согласен.

                                              * HTTP клиент lhttpc работает отлично, ни разу проблем не возникало. Использовал 500-800 параллельных потоков в штатном режиме. Особо приятный момент — возможность жёстко ограничить время на скачивание странички — не уложился в 20 секунд — отбой. Но поддержку кукисов пришлось дописать. Помимо lhttpc есть вполне годные hackney и ibrowse.

                                              * Для парсинга HTML тоже использую mochiweb_html. Даже бенчмарк делал — работает достаточно шустро, быстрее чем парсеры на большинстве динамических языков, по потреблению памяти так вообще почти всех делает. Зачем вы использовали list — строки вместо binary не знаю, видимо по неопытности…

                                              * Для XPath брал тот же mochiweb_xpath — тут действительно была сильно урезанная версия и пришлось некоторые вещи дописывать — их сейчас влили в апстрим, так что поддержка XPath сейчас практически полная.

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

                                              Что я получил в замен на свои «мучения»:
                                              * Приемлемую производительность (первоначальная версия паука на Python + gevent + mongoDB для общих данных выдавала в ~10 раз меньше запросов и хрен поймёшь в каком месте был затык)
                                              * Возможность подпатчить код без остановки паука (зашел на сервер, сделал nano src/cookie_storage.erl; rebar compile; erl -remsh spider@localhost; l(cookie_storage). и вот уже паук работает на подправленной версии).
                                              * Удалённый шелл сам по себе отличная штука — можно подключиться к работающему процессу и что-то там посмотреть (включить профилирование / покопаться в памяти / изменить настройки / включить-отключить подсистему). Пользуюсь постоянно.
                                              * Относительно легко подключать другие языки — у меня к пауку подключены интерпретаторы JS и Python.
                                              * Выпилил лишние сущности (в Python версии шаренные данные /списки proxy, статистика, cookies etc/ складывали в MongoDB, т.к. gevent по ядрам CPU не умеет расползаться, в Erlang они хранятся прямо в памяти)
                                              * Сам код стало проще структурировать и поддерживать, т.к. OTP к этому располагает.

                                              С вебсокетами тоже баловался — вот этот сервис написал полностью на Erlang: http://dropmail.me/ru/ и там самые что ни на есть вебсокеты.
                                                0
                                                зеро-деплоймент когда говоришь выполнить такой-то класс на такой-то ноде
                                                класс (вместе с зависимостями) сам туда деплоится выполняется а и возвращается результат. и все рантайме.
                                                Ну на эрланге видать это бим должен быть ;))

                                                ibrowse — богат на функционал но что-то типа не больше 20 коннекшенов
                                                у остальных функционал беднее.

                                                Хорошо что кто-то платил вам за весь этот допил. вместо того чтобы купить на эти деньги сервак поднять на нем инстансы и гонять паук помедленнее ;))

                                                пауков-то немерянно есть написанных
                                        0
                                        А при чём здесь язык Питон, изображённый на картинке? :)
                                          +1
                                          Попрошу, это удав!

                                          Отозвался в гугле на безысходность, вот еще цитату вспомнил.
                                            0
                                            Ммм. Действительно удав. Значит я все эти годы программировал на удаве!
                                          0
                                          Скажите, вы остались на Mongo? Используете Slick, Reactive Mongo?
                                            0
                                            reactivemongo и его Future отлично подошли
                                              0
                                              а можно еще пару слов, что понравилось, что не понравилось в reactivemongo?
                                                0
                                                Хм.
                                                Понравились неявные преобразования объектов в json и обратно с ходу, ну и неблокирующие запросы офк.
                                                Не особо радует документация на запросах сложнее find(), часто приходится лезть в API и гугл.
                                                Вот так выглядит простенькая агрегация с подсчетом и группировкой:
                                                    val command =
                                                    BSONDocument(
                                                      "aggregate" -> "rooms",
                                                      "$pipeline" -> BSONArray(
                                                        BSONDocument("$match"->BSONDocument("owner._id"->userId)),
                                                        BSONDocument("$group"->BSONDocument("_id"->"$language", "count"-> BSONDocument("$sum"->1)))
                                                      )
                                                    )
                                                
                                                    val comm = Aggregate("rooms", Seq(
                                                      Match(BSONDocument("owner._id"->userId)),
                                                      GroupField("language")("count"->SumValue(1))
                                                    ))
                                                
                                                    Db.db.command(comm)
                                                


                                                Я пока этот запрос родил — прошерстил с десяток страниц апилок и гугл тредов.
                                                  0
                                                  Ну это как писать на sql для тех кто никогда не видел в глаза sql, дело просто в том, что тупо не знаешь как этим пользоваться. Вот какая-нибудь обвязка а-ля query builder помогла бы
                                                    0
                                                    Да нее. С самим монго я работаю давно и успешно, знаю что к чему.
                                                    Просто чтоб это расчехлилось из rm — нужно много стараться.

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

                                                    У них же .command сделан как раз для таких вот сложных запросов. Подход простой — нам впадлу описывать это в std api, поэтому сделаем вот так.
                                            0
                                            Господа, но колбэки не компонуются! Это гребаный приговор.


                                            Второй раз за неделю вижу подобное утверждения. Кто-то может объяснить почему же они не компонуются?
                                            Написать async compose для ноди не так сложно.
                                              +2
                                              колбэки в чистом виде не компонуются. обертки уже упомянуты как и в статье, так и обмусолены в комментах.
                                                0
                                                Следуя вашей логике, функции в js тоже не компонуются в чистом виде (в js нет оператора для композиции функций). Извиняють сударь, но это смешно.

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

                                                То что действительно неудобно, так это CPS.
                                                  +1
                                                  Вот некий JS псевдокод с колбэками, суть которого найдется в любом примере к node.js и mongo

                                                  db.getData(filter, function(error, someData) {
                                                  
                                                  });
                                                  
                                                  db.getAnotherData(anotherFilter, function(error, anotherData) {
                                                  
                                                  });
                                                  


                                                  Скомпонуйте результаты выполнения этих двух асинхронных запросов без лишней писанины.

                                                  В Scala я сделаю так:
                                                  val futureResult = db.getData(filter)
                                                  val anotherFutureResult = db.getAnotherData(anotherFilter)
                                                  
                                                   //Future[MyDataObj]
                                                  val completeResult = for(result <- futureResult; anotherResult <- anotherFutureResult) yield MyDataObj(result, anotherResult)
                                                  

                                                  И это будет вполне scalish way, поскольку for скомпилится во flatMap, а это привет монадам, интерфейс которых имплементируется в scala.concurrent.Future. Все смотрится и работает целостно, без всяких конвенций и договоренностей.
                                                    0
                                                    В принципе должны уже быть библиотеки, которые оборачивают такие вещи. Если нет — то очень просто написать. И тогда будет:

                                                    Q.when([
                                                      Q.nfcall(db.getData, filter),
                                                      Q.nfcall(db.getAnotherData,anotherFilter)
                                                    ]).done(function(){});
                                                    
                                                      0
                                                      В node при помощи со & thunkify вот так:

                                                      co(function* () {
                                                        var result = yield [thunkify(db.getData)(filter), thunkify(db.getAnotherData)(anotherFilter)];
                                                      
                                                        var completeResult = MyDataObj(result[0], result[1]);
                                                        //...
                                                      }());
                                                      


                                                      Ах, да нужен --harmony mode.
                                                        0
                                                        Вот, пожалуйста:

                                                        var futureResult = db.getData.bind(db, filter);
                                                        var anotherFutureResult = db.getAnotherData.bind(db, anotherFilter);
                                                        
                                                        waitFor(futureResult, anotherFutureResult, function(error, someData, anotherData) {
                                                          // ... do the job
                                                        });
                                                        


                                                        Вы правы, стандартная либа js не имеет что то подобного к waitFor. Но то, что такого комбинатора нет в стандартной поставке, еще не значит что коллбеки не могут быть скомпонованы. Нужно всего лиш написать такой комбинатор:

                                                        function waitFor() {
                                                          var args = Array.prototype.slice.call(arguments);
                                                          var done = args[args.length - 1];
                                                          var result = new Array(args.length - 1);
                                                          var hasError = false;
                                                          var left = args.length - 1;
                                                        
                                                          args.slice(-1).forEach(function(asyncFn, i) {
                                                            asyncFn(function(error) {
                                                              if (error) {
                                                                hasError = true;
                                                                return done(error);
                                                              }
                                                        
                                                              result[i] = Array.prototype.slice.call(arguments, 1);
                                                              left--;
                                                        
                                                              if (left === 0) {
                                                                return done.apply(null, [null].concat(result));
                                                              }
                                                            });
                                                          });
                                                        }
                                                        


                                                        Тот же async — это по сути набор специальних комбинаторов для разных сценариев использования.
                                                  +2
                                                  Как говорит Дуглас Крокфорд у каждого языка есть свои good and bad parts. На мой взгляд проблема JS в том что он был спроектирован за 10 дней а еще благодаря MS все ее плохие стороны вместо того что бы быть ликвидированными были досконально документированы и вошли в стандарт. А сколько потратил Одерский на Scala? Тем более что автор scala до ее создания трудился над generic в java и имел прекрасное представление о недостатках java. Соглашусь с Дугласом Крокфордом о том что, js это самый широко используемый язык в мире который большинство людей его использующих не понимают. Я люблю оба языка и нахожу что каждый хорош в своей области, но несмотря на это они одинаково хорошо подходят для решения многих схожих задач.

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

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