Треки
/
Factor
Factor
/
Вправи
/
Сигнальні трафарети
Сигнальні трафарети

Сигнальні трафарети

Навчальна вправа

Вступ

Ми вже вміємо будувати цитату з окремих частин і запускати її. Цитата - це просто послідовність коду, тож знайомі нам слова для роботи з послідовностями працюють і з нею:

USING: kernel math prettyprint quotations sequences ;

{ [ 1 ] [ 2 + ] [ 3 * ] } concat >quotation call .   ! => 9
[ 1 + ] dup append .                                 ! => [ 1 + 1 + ]

append поєднує дві цитати, concat розгортає масив із них, map перетворює дані на цитати, а >quotation робить результат таким, що його можна викликати. MACRO: бере саме цю навичку й переносить її на час компіляції.

Що таке макрос

Визначення MACRO: має вигляд звичайного слова, але його тіло виконується тоді, коли наша програма компілюється, а не тоді, коли вона працює. Тіло має залишити на стеку одну цитату. Далі компілятор вставляє цю цитату в місце виклику: він замінює виклик макроса кодом, який створило тіло. Під час виконання макроса вже немає, є лише згенерований код.

USING: kernel macros sequences ;

MACRO: twice ( quot -- spliced )
    dup append ;

Де б ми не написали [ 1 + ] twice, компілятор:

  1. виконує тіло, коли на стеку лежить [ 1 + ];
  2. dup append будує цитату [ 1 + 1 + ];
  3. вставляє її код, 1 + 1 +, на це місце.

Тож слово, яке викликає twice, компілюється так, ніби ми написали 1 + 1 + власноруч:

: inc-twice ( x -- y ) [ 1 + ] twice ;

5 inc-twice .   ! => 7

Ключова відмінність: звичайне слово, яке приймає цитату, викликає її (call) під час виконання. Макрос не викликає нічого: він віддає компілятору новий код, щоб той скомпілював його замість виклику.

Вхідні дані мають бути літералами

Оскільки тіло виконується під час компіляції, вхідні дані макроса мають бути відомі саме тоді. У місці виклику вони мають бути літералами: цитата [ … ], число, масив, рядок тексту (англ. string). Значення, яке існує лише під час виконання, макросу не передати.

! DOCTEST: SKIP — illustrative call sites, not standalone code
[ 1 + ] twice          ! ok: the quotation is a literal
[ 1 + ] 3 repeat-quot  ! ok: the quotation and the number are literals

Інструментарій

Тіло макроса - звичайний код, який повертає цитату. Більшість роботи роблять слова, які ми вже зустрічали:

щоб побудувати… використовуємо
[ A B ] з [ A ] і [ B ] append
одну цитату з масиву цитат concat >quotation
цитату для кожного елемента послідовності map, потім concat >quotation

Ще два слова, з sequences і kernel, доповнюють цей набір.

<repetition> (з sequences) створює послідовність із n копій одного елемента. Її стековий ефект - ( length elt -- repetition ): кількість стоїть першою. Результат - віртуальна послідовність, тож розгортаємо її звичним способом:

3 [ 1 + ] <repetition> concat >quotation .   ! => [ 1 + 1 + 1 + ]

curry (з kernel) додає значення на початок цитати й створює нову цитату, яка кладе це значення на стек першим:

3 [ + ] curry .          ! => [ 3 + ]
{ 1 2 3 } [ . ] curry .  ! => [ { 1 2 3 } . ]

curry дає змогу вбудувати в цитату один конкретний елемент: саме він перетворює { 10 20 30 } і [ + ] на [ 10 + ] [ 20 + ] [ 30 + ].

Чому це простіше, ніж здається

У деяких мовах макросам потрібен спеціальний синтаксис quote/unquote та додаткові механізми, щоб уникати конфліктів імен. У Factor нічого цього не потрібно: цитата - це вже послідовність розпізнаних слів, адже парсер знайшов кожне dup, + й append, коли прочитав [ … ]. Тож тіло макроса - це просто операції з послідовностями, які ми вже виконуємо, і які до того ж повертають цитату.

Близький родич: $[ … ]

Макрос повертає цитату, щоб її вставили у виклик. Коли ж нам потрібне звичайне значення, обчислене під час парсингу, $[ code ] виконує code, поки читається файл, і вкладає результат у код як літерал:

! DOCTEST: SKIP — compile-time value, shown for contrast
USING: calendar literals ;

CONSTANT: build-time $[ now ]

$[ … ] походить із модуля literals, який збирає слова для підстановки результатів, обчислених під час парсингу, у літерали. Згадуємо його для повноти. Усі чотири трафарети, які ми напишемо (twice, repeat-quot, compose-many і each-literal), - це оголошення MACRO:.

Вказівки

Ми підтримуємо набір інструментів для генерації коду на сигнальній станції. Кожен макрос - це трафарет, невеликий шаблон, який компілятор використовує, щоб відтискати спеціалізований код у кожному місці виклику.

Ми пишемо лише чотири макроси. Файл тестів визначає невеликі слова-обгортки, які викликають наші макроси (макрос можна спостерігати лише через код, який він генерує), а потім перевіряє результати виконання цих обгорток. Обгортки залишаємо без змін.

Для кожного завдання тіло отримує літеральні вхідні дані місця виклику на стеку й повинно залишити один quotation. Рядки «compiles to» показують лінійний код, який має згенерувати наша quotation.

1. Двічі підряд

twice приймає літеральну quotation і повертає quotation, що запускає оригінал двічі підряд.

! : inc-twice ( x -- y ) [ 1 + ] twice ;
! body receives [ 1 + ]  ->  must build [ 1 + 1 + ]
5 inc-twice .   ! => 7

2. N повторень

repeat-quot приймає літеральну quotation і літеральну кількість n та повертає quotation, що запускає оригінал n разів підряд. n = 0 повертає порожню quotation [ ].

! : inc-thrice ( x -- y ) [ 1 + ] 3 repeat-quot ;
! body receives [ 1 + ] 3  ->  must build [ 1 + 1 + 1 + ]
5 inc-thrice .   ! => 8

3. Поєднання багатьох

compose-many приймає літеральний масив quotation і повертає одну quotation, що запускає кожну з них по черзі. Порожній масив повертає порожню quotation.

! : pipeline ( x -- y ) { [ 1 + ] [ 2 * ] [ 1 - ] } compose-many ;
! body receives { [ 1 + ] [ 2 * ] [ 1 - ] }  ->  must build [ 1 + 2 * 1 - ]
5 pipeline .   ! => 11

4. Розгорнута ітерація

each-literal приймає літеральну послідовність і quotation. Він повертає quotation, що для кожного елемента послідовності кладе цей елемент на стек, а потім запускає quotation. Ітерація відбувається під час компіляції, тож згенерований код буде лінійним.

! : sum-three ( -- n ) 0 { 10 20 30 } [ + ] each-literal ;
! body receives { 10 20 30 } [ + ]  ->  must build [ 10 + 20 + 30 + ]
sum-three .   ! => 60

Це найструктурніше із чотирьох завдань: і довжина послідовності, і значення її елементів визначають згенерований код. У підказках ми покроково розберемо, як його побудувати.

Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
Factor Exercism

Час розпочати Сигнальні трафарети?

Зареєструйтеся на Exercism, щоб вивчати й опановувати Factor, а також 47 концепцій163 вправи та справжнє наставництво від людей, і все це безкоштовно.