Ненормальное программирование

индекс
287,20

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


Метапрограммирование в языке 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. Скомкано получилось, голова болит, понедельник после релиза. Лень, ад и погибель. Простите. Но лучше так, чем вобще ничего (:
+32
2 февраля 2009, 14:49
24

комментарии (22)

+1
scrat #
хабракат не помешает
+1
semka #
сделано, простите
0
GKelpi #
хорошо что он идет по пути smaltalk, но последний всё же шире, да и роднее.
+1
semka #
На счет шире не уверен, io это такой маленький Smalltalk с человеческим синтаксисом. В некотором роде io даже больше Smalltalk, чем сам Smalltalk. Тут нет вообще ни одного зарезервированного слова, все — объект. (Хотя по правде, io выгодно отличается от ST именно синтаксисом) (:
0
tenshi #
в смалтолке параметры функций именованные, а в йо — идут сплошной простынёй. у примитивных абстракций большие проблемы с читабельностью, поддерживаемостью и выразительностью нетривиальных вещей.
+2
semka #
Именованные параметры это простите что?

setSomeObjectsWidthTo: width AndHeightTo: height;

Так? Я кроме ST такое только в ObjC видел и в лиспе. Чем плохо:

setSomeObjectWidthAndHeight := method(width, height, ... )


/нудеж/: в ST нет функций, там МЕТОДЫ, блеять.
+2
tenshi #
тем, что по вызову obj.callMyMethod( true, true, false, 1 ) совершенно не понятно, что происходит и если позарез нужно указать последний параметры, а остальные — нет, всё-равно придётся заполять их нуллами.

именованные параметры есть в питоне, руби, яваскрипте, ада

community.livejournal.com/ru_dmitriid/2388.html

/нунадож/: какая наxyй разница?
+1
semka #
А, дошло про именованные параметры. Да, есть такое.
+1
semka #
Вобще надо как-нибудь под пиво придумать как их реализовать в io, потому что риальни гламурни удобни фича.
0
semka #
Ну и про примитивные абстракции тоже поподробней, а то я нихтферштейн совершенно пока.
НЛО прилетело и опубликовало эту надпись здесь
+3
semka #
А тут ничего особо сложного нет, прототипы призваны обеспечить высокую мутабельность свойств объектов без механизма наследования. Это имеет смысл, во-первых мы отказываемся от понятия «класс», получая в замен готовый к использованию объект. Во-вторых наследование зачастую сильно усложняет логику программы мешая чтению исходников. А тут все просто:
«Авраам родил Исаака; Исаак родил Иакова; Иаков родил Иуду и братьев его»
Abrahaam := Object clone
Isaac := Abraham clone
Iakow := Isaac clone
Judas := Iakow clone
JudasBrothers := Iakow clone


Как-то так.
+1
tenshi #
у наследования через классы и наследования через прототипы нет принципиальной разницы. всё одно:
у авраама был тик, у исаака был тик и колики, а у бедного иуды ещё и понос…
+1
semka #
Ну а что поделать.
+2
semka #
Основной посыл таков: это не лучше, это по-другому.
+1
antage #
Да нет никаких плюсов. Можно точно так же писать на ruby:

irb(main):001:0> class A; end
=> nil
irb(main):002:0> B = A.clone
=> B
irb(main):003:0> class B; def hello; puts "hello"; end; end
=> nil
irb(main):004:0> B.new.hello
hello
=> nil
irb(main):005:0> A.hello
NoMethodError: undefined method `hello' for A:Class
from (irb):5

А теперь найди 10 отличий между class B < A и B = A.clone.
0
antage #
Ну насчет 10 отличий, я конечно погорячился, отличия есть :)
0
Deepwalker #
А вот такой вопрос — я так понимаю, что Python тоже может работать как prototyped? Ну в смысле функцию clone написать это же просто достаточно с его средствами интроспекции.
+5
semka #
Если трамвай поставить на дебаркадер — он сможет плыть по воде!
–2
shai_xylyd #
Извините, но не удержался:

Исходники io я посеял, а эти двое упырей сказали, что особой ценности в нем и небыло
0
acy #
Вам помогло магическое число 42, ничего удивительного.
0
brondr #
Io — это сила. Особенно с учётом того, что можно намутить сверх.

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