Pull to refresh

Io Language: Метапрограммирование

Reading time4 min
Views2.3K

Метапрограммирование в языке io



В продолжении серии статей про чудесный язык io я таки собрался написать про метапрограммирование.
habrahabr.ru/blogs/crazydev/29375
habrahabr.ru/blogs/crazydev/28254
habrahabr.ru/blogs/crazydev/28167
habrahabr.ru/blogs/crazydev/28041

Метапрограммирование — создание программ, которые создают другие программы как результат своей работы (либо — частный случай — изменяющие или дополняющие себя во время выполнения).
© Православная википедия

Совершенно не интересен канонический смысл термина «метапрограммирование», любой тьюринг-полный язык имеющий библиотеки IO способен генерировать исходные тексты программ на произвольном языке. Намного интересней возможность самопознания программы — интроспекция, а так же генерация и модификация самой себя в процессе выполнения.

Если посмотреть на крестных отцов io, то можно увидеть Lisp, Smalltalk, Self и другие динамические языки. Но все они решают проблему метапрограммирования по своему, например Lisp генерирует код в процессе компиляции из макросов, очень мощных штук. Smalltalk содержит развитую систему «отражений» (reflection) позволяющую гулять по дереву объектов в любую сторону. Io пошел по пути smalltalk'а и строит свои динамические механизмы на базе объектной модели, а не универсальности синтаксиса (как лисп).


Интроспекция


Для исследования объектов io предоставляет ряд методов, которые позволяют делать всякие смешные штуки, на пример метод slotNames. Если отправить сообщение slotNames объекту, можно получить список его слотов. Ну например:
    Io> Lobby slotNames
    ==> list("exit", "Protos", "forward", "args", "Lobby", "launchPath")
  

Как видно метод возвращает обычный список, по которому можно ползать foreach'ем и прочими map'ами. Ну и в принципе делать что душе угодно.
Точно так же можно получить список протоколов объекта (метод protos):

    Io> Lobby protos
    ==> list(Object_0x4191a0)
  


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

    Io> SampleObject := Object clone
    ==>  SampleObject_0x4461c0:
    type             = "SampleObject"
    
    Io> SampleObject m := method("Look ma, i can fly!" println)
    ==> method(
    "Look ma, i can fly!" println
    )
    Io> SampleObject anotherM := SampleObject getSlot("m")
    ==> method(
    "Look ma, i can fly!" println
    )
    Io> SampleObject anotherM
    Look ma, i can fly!
    ==> Look ma, i can fly!
    Io>
  

И совсем уже неземной метод code. Он позволяет получить исходный код любого метода в нормализованной форме:

    Io> List getSlot("map") code
    ==> method(setSlot("aList", List clone) ;
    setSlot("a1", call argAt(0)) ;
    if(a1 ==(nil), Exception raise("missing argument")) ;
    setSlot("a2", call argAt(1)) ;
    setSlot("a3", call argAt(2)) ;
    if(a2 ==(nil), self foreach(v, setSlot("ss", stopStatus(setSlot("c", a1 doInContext(getSlot("v"), call sender)))) ;
    if(ss isReturn, ss return(getSlot("c"))) ;
    if(ss stopLooping, break) ;
    if(ss isContinue, continue) ;
    aList append(getSlot("c"))) ;
    return(aList)) ;
    if(a3 ==(nil), setSlot("a1", a1 name) ;
    self foreach(v, call sender setSlot(a1, getSlot("v")) ;
    setSlot("ss", stopStatus(setSlot("c", a2 doInContext(call sender, call sender)))) ;
    if(ss isReturn, ss return(getSlot("c"))) ;
    if(ss stopLooping, break) ;
    if(ss isContinue, continue) ;
    aList append(getSlot("c"))) ;
    return(aList)) ;
    setSlot("a1", a1 name) ;
    setSlot("a2", a2 name) ;
    self foreach(i, v, call sender setSlot(a1, i) ;
    call sender setSlot(a2, getSlot("v")) ;
    setSlot("ss", stopStatus(setSlot("c", a3 doInContext(call sender, call sender)))) ;
    if(ss isReturn, ss return(getSlot("c"))) ;
    if(ss stopLooping, break) ;
    if(ss isContinue, continue) ;
    aList append(getSlot("c"))) ;
    return(aList))
  

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


Модификация


Любой метод в io можно перегрузить простым способом, вернемся к синглтону на io:

    Singleton := Object clone
    Singleton clone := Singleton
  

Стандартный метод clone любого объекта создает его копию (если грубо), а в данном случае метод clone будучи перегруженным возвращает уже имеющийся экземпляр объекта, чего собственно и стоило ожидать от синглтона.
Любой объект в io можно неограниченно расширять, изменять и интроспектировать.
(Вообще, пол года назад, когда я начинал писать статью про метапрограммирование мы с lrrr'ом и oleganza наваяли sql-генератор, который преобразовывал нормальные с точки зрения синтаксиса io вызовы методов в строку с sql-запросом. Выглядело это примерно вот так:

    SQLGen select (*) from t_some_table t where t.id = 42
  

В результате получалась sql строка. Но исходники я посеял, а эти двое упырей сказали, что особой ценности в нем и небыло (:
Так что никто не расстроился.


Ну вот и все, наверное. Можно написать намного больше, но лучше покопаться в мануалах на официальном сайте: www.iolanguage.com/docs.
p.s. Скомкано получилось, голова болит, понедельник после релиза. Лень, ад и погибель. Простите. Но лучше так, чем вобще ничего (:
Tags:
Hubs:
Total votes 38: ↑35 and ↓3+32
Comments22

Articles