LINUX.ORG.RU

Homoiconic C

 homoiconicity,


3

2

Я тут упоролся и подумал, а что если бы у нас был ЯП уровня Си с гомоиконным синтаксисом в стиле Io или Julia. То есть — у нас есть интерпретатор гомоиконного языка, который работает как макропроцессор, и результат трансформаций исходного кода скармливается затем компилятору языка с Си-подобной семантикой. И у нас будет нормальный тьюринг-полный макроязык, который позволит бескостыльно расширять возможности ЯП неограниченно. Компилирующаяя же часть будет по сути обычным компилятором Си (просто читающим входные данные в неСишном синтаксисе).

Это ж кайф. Выражения типа regexp(«^[a-z][a-z0-9]») или format(«%s - %s (%d)», bla1, bla2, i) можно будет автоматически обрабатывать макропроцессором и отправлять компилятору оптимизированный вариант. Это значит, регулярка, например, будет скопилирована в конечный автомат при компиляции программы, а не при выполнении.

Вот эта вот странная задачка, на которой dr_jumba проверял лаконичность языков, записывалась бы как-то вот так:

sample_function := fn(a(iterable(T))) to(T) {
    a select(match(regexp(/^J[a-z]+/))) each_chunk(3) map(format_with("~1 and ~2 follow ~3")) join("\n")
}

Дискас.

Вы не поверите, но выражения типа F(known) где F - реализованная чистая функция, ещё лет д-цать назад расчитывались на макро АССЕМБЛЕРЕ ЕС ЭВМ.

MKuznetsov ★★★★★
()
Ответ на: комментарий от geekless

А потом к результату выражения применяется новое действие, и получаются цепочки

На CL:

(use-package :message-oo)

(@ object (:action1 param1 param2) (:action2 param3) ...)

На Scheme(Racket):

(send* object (action1 param1 param2) (action2 param3) ...)

monk ★★★★★
()
Ответ на: комментарий от quasimoto

То есть достигать выполнения инвариантов не дедуктивно (написали нечто и насилуем это проверками), а индуктивно, то есть так, что сами вещи композицонно (согласно type rules) не смогут образовать expressions которые нам не нужны (содержат kmalloc в handler), по аналогии с тем как

Все хорошо написал, только типы тут не при чем. Этот паттерн называется «интерфейс», работает он одинаково, что при статической, что при динамической типизации (т.к. типам ортогонален), он действительно позволяет получить гарантии безопасности, которые мало какая система типов даст. Например как в том же хаскеле - когда системы типов не хватает, то пишут монадический интерфейс и обеспечивают корректность безо всяких типов. Косяк тут один - если ЯП не поддерживает развитого метапрограммирования, то подход очень слабо применим. Тот же хаскель в пример - для монад пришлось писать специальный ДСЛ. В общем случае каждый интерфейс требует своего ДСЛ.

anonymous
()
Ответ на: комментарий от tailgunner

она реализуется без макросов.

Но зачем без макросов, если с ними удобнее, проще, качественнее, более поддерживаемо?

anonymous
()
Ответ на: комментарий от tailgunner

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

А что нужно?

anonymous
()
Ответ на: комментарий от tailgunner

и вообще у тебя всё будет из макросов и точек втраивания, даже небо, даже аллах.

Именно так. В этом и профит.

anonymous
()
Ответ на: комментарий от tailgunner

введение nomalloc потребует замены макроса определения функции _и_ макроса определения указателя на функцию

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

anonymous
()
Ответ на: комментарий от tailgunner

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

Так описанная система - это _и есть_ макросы. И она уже реализована (ну в тех же лиспах).

anonymous
()
Ответ на: комментарий от geekless

И поэтому оптимальный порядок записи аргументов не «действие аргумент1 аргумент2 аргумент3 ...», а «аргумент1 действие аргумент2 аргумент3».

Для всех нормальных людей оптимальный порядок как раз первый. А второй - вообще какая-то непонятная херь. Почему действие в середине? Почему слева именно один аргумент? Почему не «аргумент1 аргумент2 действие аргумент3 аргумент4»? То есть на самом деле понятно почему - это результат костыльной реализации ООП на конкретной архитектуре. И запись типа «аргумент1 действие аргумент2 аргумент3» все равно приходится в уме преобразовывать к интуитивно понятному человеку «действие аргумент1 аргумент2 аргумент3 ...». Зачем выполнять в уме работу парсера? Совершенно не нужно, ящитаю.

anonymous
()
Ответ на: комментарий от quasimoto

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

А что тут доказывать? ЯП макросов выразительно равен целевому ЯП (т.к. совпадает с ним). На целевом ЯП можно написать (в силу тьюринг-полноты) компилятор любого ЯП. И тайпчекер любой системы типов. Значит, на макросах тоже можно.

Вообще, я формально определяю понятие «макросистема» следующим образом - возьмем все разрешимые системы типов, упорядочим их по выразительности, систему типов, которая является точной верхней гранью полученного ЧУМ, будем называть макросистемой.

anonymous
()
Ответ на: комментарий от anonymous

Почему действие в середине?

Потому что человек размышляет «от объекта». «Берем вот эту хрень и что-то с ней делаем. А потом берем то, что получилось, и снова с этим что-то делаем.» А не так: «вот действие, где ж блджад объекты для него?». Да вспомни, как ты жрать готовишь, блин! Человек в реальном мире оперирует объектами, а не действиями. И ЯП должен отражать мышление человека.

То есть на самом деле понятно почему - это результат костыльной реализации ООП на конкретной архитектуре.

Когда ж вы научитесь отделять представление от семантики, а семантику от реализации? Специалисты... При любом каком угодно закрученном параметрическом полиморфизме безо всякого ООП, запись с выделенным объектом будет просто тупо удобнее. Более того — выделенным объектом может быть в принципе любой из аргументов функции. И в идеале, язык должен позволять синтаксические трансформации, чтобы записывать слева от функции любой из аргументов.

Вот эту вот херню:

(* (reduce + select((> 10) somelist)) somevalue)
Мозгу один хрен приходится парсить изнутри наружу, подсознательно считая скобочки, чтобы не запутаться.

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

somelist select(>(10)) reduce(+) *(somevalue)

Да вспомни, как выглядят пайпы в sh! Всё то же самое по сути.

geekless ★★
() автор топика
Последнее исправление: geekless (всего исправлений: 4)
Ответ на: комментарий от geekless

Потому что человек размышляет «от объекта» [...] А не так: «вот действие, где ж блджад объекты для него?»

Ненене, а как же фраза «пошел ты <удалено цензурой>»? Здесь сначала действие, а потом объекты %)

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от geekless

«эй_вы_оба(все_троя,бегом,ко_мне,шагом_марш)»

ок

qulinxao ★★☆
()
Ответ на: комментарий от geekless

Потому что человек размышляет «от объекта».

Это не человек так размышляет, это кривизна конкретной архитектуры заставляет его так размышлять.

Когда ж вы научитесь отделять представление от семантики, а семантику от реализации?

Я же тебе и объясняю - семантика выбрана такой именно из-за особенностей низкоуровневой реализации.

При любом каком угодно закрученном параметрическом полиморфизме безо всякого ООП, запись с выделенным объектом будет просто тупо удобнее.

Не будет.

Мозгу один хрен приходится парсить изнутри наружу, подсознательно считая скобочки, чтобы не запутаться.

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

somelist select(>(10)) reduce(+) *(somevalue)

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

Да вспомни, как выглядят пайпы в sh!

Пайпы - это конкретный частный случай.

anonymous
()
Ответ на: комментарий от anonymous

Да, конечно. Тем, что подавляющее большинство компиляторов написано традиционным способом, а не как набор макросов - собственно, кроме будущего Nemerle2 я ничего нетрадиционного и вспомнить не могу, разве что Shen.

А ты можешь обосновать свои слова?

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Тем, что подавляющее большинство компиляторов написано традиционным способом, а не как набор макросов

И? Как отсюда следуют твои false?

А ты можешь обосновать свои слова?

Конечно.

anonymous
()
Ответ на: комментарий от anonymous

Тем, что подавляющее большинство компиляторов написано традиционным способом, а не как набор макросов

И?

И вот.

Как отсюда следуют твои false?

Прямо.

А ты можешь обосновать свои слова?

Конечно.

Я знал, что ты скажешь это.

tailgunner ★★★★★
()
Ответ на: комментарий от geekless

Потому что человек размышляет «от объекта».

Не было гвоздя - брось новое ПодковоГвоздьНеНайденИсключение(«гвоздей нет!»)

Не было подковы - ЛошадеДоктор.получитьМестнуюКопию().получитьЛошадеДиспетчер().давайделай()

Лошадь захромала - СкаковойКлуб.получитьСсписокОповещенияПодписчиковНаездников().получитьРассылателя().вперед(новое СообщениеРассылка(Конюшня.получитьНулевойЛошадеОбразец()))

http://steve-yegge.blogspot.ru/2006/03/execution-in-kingdom-of-nouns.html

http://anonym-mouse.livejournal.com/1294.html

anonymous
()
Ответ на: комментарий от geekless

У вас Java головного мозга.

Ну так где ты, а где Java. Java-профессионалы уже два десятка лет мыслят от объекта, они достигли совершенства. Ты просто еще юнец, делающий первые шажки. Будешь хорошо трудиться, и сам постигнешь суть ООП.

anonymous
()

По поводу верификации задачи про kmalloc - у меня есть подозрение что использовать макросы для верификации будет очень неудобно, особенно, когда условия будут накладываться на одни и теже конструкции (например кто-то еще придумает пару правил про kmalloc) и как они все друг с другом будут дружить не ясно. Все-таки для верификации нужно уметь поддерживать проверку инвариантов (и пофиг на каком языке описываются эти инварианты).

Да и что самое мерзкое, учитывая тьюринг-полноту таких макросов IDE к такому языку будет невозможно, а компилиться все будет невероятно долго (а сишечку многие любят в частности за скорость компиляции). По мне так либо уж что-то без макросов, либо уж полная динамика, но в духе Jetbrain MPS, где для каждой задачи свой инструмент (который даже позволит верифицировать code flow в роде недостижимых участков кода) и IDE одновременно строится.

theos ★★★
()
Ответ на: комментарий от geekless

Ну, вообще то куда удобней перегружать операцию один раз как внешнюю. В питоне с этим трэш угар и содомия - приходиться пилить и __op__ и __rop__... Но это про реализацию, а про использование - пока в школах не перейдут на префиксную форму записи в алгебре *2 2=>4 лиспу мир не захватить;-)

AIv ★★★★★
()
Ответ на: комментарий от anonymous

Что и вот?

А что «и»?

Конкретнее цепочку вывода распиши.

Не раньше, чем ты обоснуешь свою точку зрения.

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

(with-list

так будет неудобно.

Либо классически

(iter
  (for x in somelist)
  (when (> x 10))
  (sum (* x somevalue)))

Либо у тебя select должен принимать фильтрующую функцию а не пару абстрактных аргутментов. То есть будет что-то типа

(with-list somelist
  (select (rcurry #'> 10))
  (reduce #'+)
  (curry #'* somevalue))
Если на Scheme, то #" можно убрать, но всё остальное останется.

monk ★★★★★
()
Ответ на: комментарий от anonymous

ООП уже не нужно(те его принципы, от которых можно получить пользу, можно использовать вне ООП, более того, они и использовались до него). ФП + концепты(С++)/тайпклассы(Haskel)/trait'ы(scala). Но вы и дальше можете фапать на «паттерны», которые в нормальных языках или вообще вам не потребуются или будут заменены соответствующими языковыми концепциями.

anonymous
()
Ответ на: комментарий от anonymous

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

Несмешивание IO и STM происходит именно из-за проверок типов - конструкторы типов IO :: * -> * и STM :: * -> * не совпадают, то есть тут проверки как раз уровня «тут у нас слоны, а тут - попугаи» (подобно Int !~ String и т.п. вещам). Ну и тот факт что IO и STM — opaque и некоторые функции спрятаны в модулях тоже играет роль.

ЯП макросов выразительно равен целевому ЯП

То есть смысл макросов только в том чтобы не делать (fn ...) = (... build-ast ...) и (eval (fn (quote sub-ast) ...)), а делать (macro ...) = `(... build-ast ,...) и (macro sub-ast ...), перенеся eval в compile-time и спрятав его в экспандере?

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

У разных языков со своими системами типов есть Curry–Howard isomorphism и модели (=> «консистентность» ~ логическая непротиворечивость). А твоя верхняя граница это полное LC (модели есть) + разные костыли — quote, eval, marco-applications, macro-abstractions, expand (а тут что с моделью?) без всяких намёков на CH.

Так же как и не на макросах. В чем разница?

Ну то есть

;; over 100500 структур данных (для AST, etc.), визиторов, алгоритмов, функций,
;; общего контекста их общения и т.д. и т.п., то есть всё как у людей.
;; ...

(defmacro with-this-type-system (expr)
  (let ((it (type-check expr)))
    (if (type-checked? it)
        (gen-something expr)
        (report-error it))))

где макросы играют роль в последнюю очередь. Или не так?

quasimoto ★★★★
()
Ответ на: комментарий от tailgunner

По-моему, ты пытаешься изобрести Shen.

Shen использует KL - промежуточный низкоуровневый язык, который служит для реализации машины вывода и прочих вещей.

На нем вообще не предполагается ничего делать рядовому программисту.

ОП же, скорее, предлагает реализовать чекер на макросах, как например, в typed racket

x4DA ★★★★★
()
Ответ на: комментарий от x4DA

Что именно относительно чего? Насчёт примера с прерыванием — я предложил вместо

irqreturn_t handler(int irq, void *p)
{
    kmalloc(100, GFP_KERNEL);
    return IRQ_HANDLED;
}

писать

mm_layer<void*> kmalloc(size_t, int);

irq_layer<irqreturn_t> handler(int irq, void *p)
{
    // kmalloc(100, GFP_KERNEL); <- mm_layer != irq_layer
    return IRQ_HANDLED;
}

где curly braces block это не обычный блок в духе C/C++ в который можно поместить что угодно, а более строгая вещь вроде do из Haskell, for из Scala или computation expressions из F#.

quasimoto ★★★★
()
Ответ на: комментарий от x4DA

Shen использует KL

ЕМНИП, KL там не сразу появился - первая реализация была на макросах CL.

ОП же, скорее, предлагает реализовать чекер на макросах, как например, в typed racket

Да, с Typed Racket я торомознул. Наверное, это самое близкое к хотелкам ТС.

tailgunner ★★★★★
()
Ответ на: комментарий от quasimoto

Несмешивание IO и STM происходит именно из-за проверок типов

Нет, конечно. В динамических ЯП оно происходит безо всяких типов.

Ну и тот факт что IO и STM — opaque и некоторые функции спрятаны в модулях тоже играет роль.

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

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

То есть смысл макросов только в том чтобы не делать (fn ...) = (... build-ast ...) и (eval (fn (quote sub-ast) ...)), а делать (macro ...) = `(... build-ast ,...) и (macro sub-ast ...), перенеся eval в compile-time и спрятав его в экспандере?

Естественно.

А твоя верхняя граница это полное LC (модели есть) + разные костыли — quote, eval, marco-applications, macro-abstractions, expand

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

(а тут что с моделью?)

А какая разница? Нафиг нам сдалась эта модель? Надо ехать, а не шашечки.

Или не так?

Нет, не так. Зачем пилить 100500 структур данных и т.п., если они уже запилены внутри макросистемы? Надо реиспользовать существующую метаязыковую структуру. Тайпчек должен идти синхронно с процессом экспанда, а то, что ты предлагаешь - это унылый кодеволкер.

anonymous
()

Было уже что-то такое на SO: http://bit.ly/cux2i5

Только нах не надо. Все равно нормальной рефлексии в Си не будет (а если будет, то можно будет сказать «до свидания» кросс-компиляции), а без рефлексии никаких макросов даром не надо, хватит внешнего препроцессора а-ля M4.

anonymous
()
Ответ на: комментарий от tailgunner

Да, с Typed Racket я торомознул. Наверное, это самое близкое к хотелкам ТС.

Фигня этот Typed Racket. Правильнее всего типизация для макросов сделана в JetBrains MPS.

anonymous
()
Ответ на: комментарий от anonymous

ООП уже не нужно

Корован гавкает, собака идет.

anonymous
()
Ответ на: комментарий от tailgunner

Тем, что подавляющее большинство компиляторов написано традиционным способом, а не как набор макросов

Да ну? Bigloo, Racket, даже Guile - макрос на макросе сидит и макросом погоняет.

anonymous
()
Ответ на: комментарий от tailgunner

Но зачем без макросов, если с ними удобнее

false

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

anonymous
()

Вообще, намного полезнее всяких там шмакросов была бы возможность вставлять свои произвольные пассы в любой этап работы компилятора, а не только в низкоуровневый IR, как в LLVM или в MELT.

anonymous
()
Ответ на: комментарий от anonymous

Правильнее всего типизация для макросов сделана в JetBrains MPS.

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

anonymous
()
Ответ на: комментарий от anonymous

Но хуже для пользователя

Пользователю хуже быть не может никак т.к. он об этих макросах может даже и не знать.

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

А это то откуда следует? Сделать _медленнее_ макросы не могут никак.

anonymous
()
Ответ на: комментарий от anonymous

Пользователю хуже быть не может никак т.к. он об этих макросах может даже и не знать.

Пользователю хуже, потому как он получает менее качественный код.

А это то откуда следует? Сделать _медленнее_ макросы не могут никак.

С макросами невозможны высокоуровневые оптимизации.

anonymous
()
Ответ на: комментарий от anonymous

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

Как это «нет макросов»? Там вообще нет ничего, кроме макросов.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.