Io Language: Система сообщений

    Сегодня продолжим цикл статей, начатый достопочтенным semka. Поговорим о сообщениях.

    В Ио нет вызовов функций, но есть посылка сообщений. У сообщения могут быть аргументы (почти как аргументы функции), но аргументы сообщений не выполняются перед посылкой.


    Как посылают сообщения

    Посылка сообщения выглядит как «объект», «пробел», «сообщение»:
    Database connect

    Сообщение может содержать аргументы:
    Database findByName("Oleg")

    Результату посылки сообщения тоже можно послать сообщение:
    Database findByName("Oleg") lastName # возможно, вернет "Andreev"

    Иными словами, конструкция a b c d эквивалентна a().b().c().d() в каком-нибудь джава-подобном синтаксисе.

    Как выполняются сообщения

    Для начала запомните, что аргументы сообщения не выполняются. Если было написано Database connect(blah-blah), то blah-blah не будет выполнено перед посылкой connect, а будет передано как часть сообщения «connect(blah-blah)» (да-да, словами).

    Когда объект получает сообщение, он ищет слот с именем этого сообщения (в нашем примере connect). Если слот не найден (как обычно и происходит), его поиск осуществляется рекурсивно во всех прототипах объекта и их прототипах. Если нужный слот нигде не найден, то запускается поиск слота forward (аналог method_missing в Руби). Как только какой-нибудь подходящий слот найден, его значение активируется (activation). (Примечание: в каком же именно объекте лежит найденный слот вам расскажет Object contextWithSlot(slotName).)

    Активация

    Для обычных значений активация ничего не делает, в просто возвращает это самое значение. Таким образом, активация обычных слотов, хранящих числа, строки или многие другие объекты, ничем не отличается от получения значения через getSlot(slotName). А вот те объекты, которые помечены как activatable, вызывают метод activate. По-умолчанию, только два объекта активируемые: Block (блоки и методы) и CFunction (линки к сишным функциям). Все остальные объекты можно сделать активируемыми с помощью setIsActivatable(true).

    Активация слота не происходит при вызове метода getSlot(slotName) (разумеется, сам слот «getSlot» активируется, а вот slotName — уже нет). Поэтому, если вы хотите получить метод или блок as-is, не вызывая его, то пользуйтесь getSlot(methodName).

    С активацией связан один подводный камень, о котором пойдет речь в конце статьи.

    Эээ. А когда аргументы выполняются-то?

    Сообщение послано, слот найден и активирован. Но когда и в каком контексте будут вычислены аргументы сообщения?

    Рассмотрим пример:
    withoutArgs := method() # метод возвращает nil
    withoutArgs("Hello!" println)

    Этот код ничего не выведет потому что аргумент «Hello!» println не был выполнен.
    withArgs := method(a, b, c, list(a,b,c)) # метод возвращает список аргументов
    withArgs("Hello!" println) # возвращает list("Hello!", nil, nil)

    Этот код выполнит первый аргумент и выведет на экран Hello!. Недостающие аргументы будут равны nil.

    Еще один пример:
    withTwoArgs := method(a, b, nil)
    withTwoArgs(1 print, 2 print, 3 print, 4 print)

    Этот код напечатает 12, но не 1234.

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

    Интроспекция вызова метода

    Вы еще помните, что в Ио есть только объекты, прототипы, слоты и сообщения? Глобальных и локальных переменных в этом списке нет.

    Когда Ио активирует слот (сиречь вызывает метод), он создает специальный объект Locals. Это довольно забавный объект, имеющий ряд важных особенностей. Во-первых, прототип этого объекта — self, т.е. указатель на тот объект, которому послано сообщение. Таким образом, мы можем создавать локальные слоты (aka локальные переменные), не вмешиваясь в объект-получатель, а также получить доступ ко всем слотам этого объекта. Во-вторых, в этом объекте есть как минимум три слота: self (указывает на объект-получатель), call (содержит массу информации о вызове) и updateSlot. Последний отличается от обычного updateSlot тем, что обновляет слот не в locals, а в объекте-получателе. Это сделано исключительно удобства ради, чтобы писать a = b вместо self a = b.

    Самое замечательное в объекте call — это несколько слотов:
    • call sender — объект (locals), в котором было послано сообщение.
      call target — объект, которому было послано сообщение (== self).
      call message — собственно, сообщение, содержащее имя и аргументы.
      call evalArgAt(argNumber) и evalArgs выполняют некоторый или все аргументы в контексте сендера (call sender). Но никто не мешает сделать что-нибудь такое:
      Object do := method(call message arguments first doInContext(self) )
      "string" do(
      type println
      size println
      encoding println
      )

      На экране мы увидим:
      Sequence
      6
      ascii


      Иными словами, мы можем манипулировать кодом как нам угодно: передавать, хранить, выполнять в произвольных контекстах. Можно посмотреть код уже созданного объекта: Coroutine getSlot(«yield») code вернет код метода yield в виде строки:
      method(
      if(yieldingCoros isEmpty, return )
      yieldingCoros append(self)
      next := yieldingCoros removeFirst
      if(next == self, return )
      if(next, next resume)
      )


      Кстати, про прототип locals я приврал. Если вы еще помните, кроме методов в Ио есть блоки (замыкания). Их единственное отличие от методов состоит в том, что прототип Locals у блоков не указатель на получатель сообщения, а указатель на scope, т.е. тот объект, в котором блок был создан (и на который он замкнут). Чтобы у вас окончательно уехала крыша, добавлю, что слот блока scope в точности равен call sender в момент вызова метода block.

      Обратно к локальным переменным

      Обладая столь мощным оружием, как call sender и call message, мы можем описать вызов метода и выполнение аргументов на языке Ио. Действительно, говоря method(a, b, nil), мы сообщаем, что хотим создавать слоты «a» и «b» в locals и заполнять их значениями, которые получены после выполнения соответствующих аргументов в контексте call sender. Домашнее задание: написать метод method2, который создает методы так же, как и встроенный method и проделывает все необходимые манипуляции с doInContext для объявленных и переданных аргументов.

      Обещанная уловка с активацией

      В джава-подобном синтаксисе доступ к функции и её вызов выглядят так: obj.func и obj.func(). В Ио получение значения без активации возможно через obj getSlot(«slot»), когда obj slot обязательно активирует значение слота.

      Предположим, вы пишите метод, который может принимать другой метод в качестве аргумента:
      method(func, ...)

      Если вы напишите func внутри тела метода, то вы неминуемо вызовете переданный метод. Если вам нужно просто получить его значение, то вам придется каждый раз обращаться к нему через getSlot(«func»).

      Конструирование сообщений

      Метод message возвращает свой первый аргумент как невыполненное сообщение:
      msg := message(something(arg1, arg2, arg3) nextMessage(arg1, arg2) more)
      msg name # => "something"
      msg next # => message(nextMessage(arg1, arg2) more)
      msg next next # => message(more)
      msg next name # => "nextMessage"
      msg arguments # => list(message(arg1), message(arg2), message(arg3))


      Резюме

      Теперь вы знаете как происходит посылка сообщений, вызов методов и выполнение аргументов. Надеюсь, вам стали понятны конструкции типа list(1,2,3) foreach(print) (print посылается каждому элементу списка) или list(1,2,3) map(*2) (каждому элементу посылается *(2) при создании копии списка).

      Bonus track: call sites

      Полтора землекопа, которые дочитали до конца статьи могут быть вознаграждены особо восхитительной информацией. В концепции Ио возможно достучаться то так называемой «точки вызова». Это не колстек, не контекст aka «call sender», а именно точка кода, в которой произошел вызов. Все дело в том, что call message всегда возвращает один и тот же объект, если он не сгенерирован на лету, а описан в коде (ну, или сгенерирован один раз и все время посылается). Что это дает? Поскольку один метод может обрабатывать разнообразные сообщения (в разных местах программы), становится возможным закешировать что-нибудь полезное в релевантных местах.

      Самое простое: Message setCachedResult(res) устанавливает вычисленное значение сообщения, которое будет возвращаться при попытке послать это сообщение куда-либо. Более сложный вариант: кешировать специальную логику, адаптированную под конкретное сообщение в данной точке вызова.

      Например, у метода List map есть три реализации: с одним, двумя и тремя аргументами. В текущей реализации проверка количества аргументов происходит при каждом вызове, хотя возможно закешировать нужный вариант метода в call message.
      На основе этой функциональности возможно построение более сложных схем оптимизации.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      0
      Неужели в Io устроили настоящий call by name?
        0
        Уточните, что имеете в виду. В смолтоке, селфе и руби то же самое.
          +1
          Call by name
          In call-by-name evaluation, the arguments to functions are not evaluated at all — rather, function arguments are substituted directly into the function body using capture-avoiding substitution. If the argument is not used in the evaluation of the function, it is never evaluated; if the argument is used several times, it is re-evaluated each time. (c) Wikipedia

          В смолтоке совершенно традиционный call by value, правда, value может быть блоком. В руби почти так же, насчёт селфа не знаю.

          Здесь же, если верить тексту, call by name бывает по умолчанию.

          Тестовый пример:
          pair := method(a, list(a,a))
          pair(1 print)

          Я так понимаю, напечатается "11" ?
            0
            Напечатает "1". А вот pair(method(1 print)) напечатает "11" потому что ты пишешь list(a,a), а не list(getSlot("a"), getSlot("a")) и метод активируется каждый раз, когда ты к нему стучишься.
        0
        Уххх. Это из серии BrainFuck или реально для чего то применяется?
          +1
          Это экспериментальный язык, но некоторые его фичи соблазняют на серьезную работу. Например, сопрограммы (coroutines) и эффективные сетевые библиотеки позволяют писать эффективные сетевые приложения красивее, чем в Эрланге. Не говоря уже о том, что для объектного моделирования Ио гораздо удобнее, чем Эрланг. Но текущая реализация посылки сообщений в каждой затычке сжирает производительность в некоторых местах (Ио медленнее Руби раза в 2-3). Стив Декорте думает над Io Lite, версией для LuaJIT без ленивого выполнения аргументов, но с блоками, как синтаксической единицей. Я же думаю о том, как бы оптимизировать Ио в рамках текущей концепции.
            0
            тоже возник такой вопрос, когда извилины узлом завязались
            +2
            Мозг вскипел.
            Я люблю это ощущение, большое спасибо автору :)
              0
              Забавно. Подростки переосмысливают ООП в духе - мы Буча не читали, но свое придумаем и скажем, по своему обзовём, войдём в историю...

              Если наберётся ещё достаточное количество чайников не способных промыслить базовые принципы то оно и прокатит. Наивные безграмотные чудики не способные промыслить концепцию. Бла-бла-бла... вставили пробел... бла-бла-бла... Интроспекция вызова... бла-бла-бла... создает специальный объект Locals...

              Ребят, вы если действительно понимаете основные базовые принципы, то напишите вменяемый текст, чётко и внятно описывающий новые достижения. Ото эти мутные примеры в духе ...Если нужный слот нигде не найден, то запускается поиск слота forward... Мне абсолютно всё равно поиск какого слота там запускается. Я не пишу на этом языке, но я хочу уловить мысль. Мне может и интересно, но научитесь писать вменяемым языком. Я читал предыдущие статьи, но таки и не понял ключевую мысль. Сакцентируйтесь на этом, а не перечисляйте названия операторов. Я, по своей простоте, полагаю, что для большинства людей это сейчас бесполезно.

              Эти вот ваши ...мы можем манипулировать кодом как нам угодно: передавать, хранить, выполнять в произвольных контекстах...
              Не поверите, но я тож манипулирую кодом как мне угодно... передаю, храню, выполняю в произвольном контексте...

              Грубо говоря - прочтите фундаментальный учебник ООП и чётко и внятно изложите в чём есть достижения. Если уж хотите пропагандировать... Да. :-)


              PS: Бля, как же серёзно глючит Хабр. Стыдно за него.
                –1
                Действительно придумывание "новых" концепций на базе уже существующих - извращенство. Речь идет именно о концепциях.
                  +1
                  Суть проекта Ио в том, чтобы обобщить многие известные концепции и сделать язык максимально чистым. Например, выкинуть понятие глобальных, статических переменных, член-переменных, локальных переменных взамен слотов. Выкинуть понятие классов, метаклассов и лексических контекстов взамен иерархии прототипов. Любое действие — посылка сообщения, любое выражение — элемент дерева сообщений, доступный для исследования и модификации в рантайме (привет лиспу). Плюс, приятные фишки типа сопрограмм, асинхронного ввода-вывода и встраиваемой виртуальной машины.

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

                  Разжевывающий тон статьи употреблен намеренно, чтобы быть понятым теми, кто на лекции не ходил и Буча не читал. Что касается зауми, то меня лично интересует адаптивная оптимизация виртуальной машины Ио и литература подобная этой: http://www.cs.ucsb.edu/~urs/oocsb/papers…
                    0
                    Понимаете, тусовать набор возможностей, вырванных из разных языков, в надежде получить новый удобный язык, дело несложное, но достаточно неблагодарное. Вариантов уж очень много. Посему принято иметь концепцию, которую нужно уметь чётко пояснить (защитить, по сути). Не просто перечислить что было перетасовано, а внятно пояснить ЗАЧЕМ это было сделано. очертить какие преимущества и в каких случаях дают те или иные возможности (или мешают, если вы их убираете). К примеру, руль в автомобиле можно поставить сбоку, педали сзади, а зеркала на уровне бампера. Всё это обозвать крутым концепт-каром и пытаться ездить, поглядывая какие-такие преимущества это привнесло в процесс вождения. Одно из преимуществ видно сразу – греющий пятки экстрим и драйв в вождении. :-)

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

                    Скажу больше, в глобальном смысле, в большинстве случаев цель одна – помочь справиться со сложностью систем. Посему, когда вы говорите ... выкинуть понятие глобальных переменных... это понятно и альтернативные варианты легко обосновываются. Когда вы говорите ...выкинуть понятие классов... то хочется спросить, а чем же ваш одинавсеслучаи-класс-объект-мутант лучше? Классы, это дополнительный уровень абстракции, причём отражающий специфику мышления человеческого мозга (мы классифицируем окружающие предметы). Имея классы, можно в пять секунд сэмулировать вашу концепцию. Достаточно ввести один класс на всё приложение, снабдить его парой динамических структур и пихать в них всё что вздумается.

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

                    С передачей сообщений примерно такая же картинка (вызов метода, кстати, если абстрагироваться, также не более чем передача сообщения). Легко моделируется там где надо, а где не надо – используется простой и понятный подход. Который к тому же намного более эффективен.

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

                      Смысл Ио не в том, чтобы получить инструмент для эффективного моделирования реальности, а для того, чтобы исследовать языковую унификацию. Как токи пона. Да, это хобби, а не инструмент для решения какой-то жизненной задачи. Иногда имеет смысл пошалить, чтобы посмотреть что получится и сделать выводы.
                        0
                        Супербизон! Но молодежи надо давать баловаться, им нужны достижения. Хотя с другой стороны эти самоутверждающиеся личности часто становятся в нашем гребанном айти руководителями и всякими архитекторами и уже реально портят реальным людям реальную кровь.
                          0
                          Портят кровь глупые люди, а не просто все, кто играется. Умные поиграются и вычленят важное.
                    0
                    Чем-то это система напоминает регистры процессора.
                      0
                      Тем, что и то, и другое — красное? оО
                      0
                      Олег, добрый день!
                      Подскажите, пожалуйста, как с Вами можно связаться кроме как по почте.
                      Есть предложение о работе).
                        0
                        gtalk/email oleganza at gmail.com
                        skype olegandreev1986
                          0
                          постучалась в скайп)

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

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