";str(chr 34)^it;(print(it^it);fn x=>print(x^it^x^it))";str(chr 34)^it;(print(it^it);fn x=>print(x^it^x^it))
Версія без котирування : Спробуйте це на кодування.
Версія, що цитується: Спробуйте на кодування.
Зауважте, що результат виглядає приблизно так
> val it = "{some string}" : string
> val it = "{some string}" : string
{output to stdout}> val it = fn : string -> unit
оскільки код інтерпретується декларацією декларацією (кожен ;
закінчує декларацію) і показує значення та тип кожної декларації.
Фон
У SML є квітка форми <code>"<code in quotes>"
:
str(chr 34);(fn x=>print(x^it^x^it))"str(chr 34);(fn x=>print(x^it^x^it))"
і один у формі "<code in quotes>"<code>
:
";str(chr 34)^it;print(it^it)";str(chr 34)^it;print(it^it)
Обидва покладаються на той факт, що <code>
-part не містить жодних лапок і тому може бути цитується з необхідністю уникнути чого-небудь, "
необхідні для виведення лайки str(chr 34)
.
Вони також сильно покладаються на неявний ідентифікатор, it
який використовується, коли в декларації не вказано явного ідентифікатора.
У першому quine str(chr 34);
прив'язується it
до рядка, що містить "
, fn x=>
запускає анонімну функцію, приймаючи один аргумент x
, потім з'єднує x^it^x^it
і друкує отриману рядок. Ця анонімна функція безпосередньо застосовується до рядка, що містить програмний код, тому конкатенація x^it^x^it
дає вихід <code>"<code>"
.
Друга квітина починається з просто програмного коду як рядка, до ";str(chr 34)^it;print(it^it)";
якого прив’язана it
. Потім str(chr 34)^it;
з'єднує цитату до початку рядка і оскільки знову не вказано явного ідентифікатора, результуюча рядок "<code>
пов'язана з it
. Нарешті print(it^it)
поєднує рядок із самим собою, отримуючи "<code>"<code>
який потім друкується.
Пояснення
Редагувати: більше не оновлюється 108-байтна версія, однак ви також можете зрозуміти її, прочитавши це пояснення.
Квітна безпечна квітка поєднує в собі обидва вищезазначені підходи і є самою формою "<code>"<code>
. Повторюючи це знову в лапках ""<code>"<code>"
, ми отримуємо порожню рядок, а потім і лайку іншої форми.
Це означає, що програмі або надається власне джерело у формі "<code>
за допомогою ідентифікатора it
, або it
просто, "
і нам дається власне джерело <code>
як аргумент, і, таким чином, повинна бути функцією, яка обробляє такий аргумент.
(if size it>1then(print(it^it);fn _=>())else fn x=>print(it^it^x^it^x^it))
Щоб визначити, у якому випадку ми знаходимось, ми перевіряємо, чи розмір it
більше 1. Якщо ні, то it
є, "
і ми знаходимось у другому випадку, тому else
-part повертає анонімну функцію, fn x=>print(it^it^x^it^x^it)
яка потім викликається, тому що за нею йде джерело як рядок . Зверніть увагу на ведучий, it^it^
який необхідний для порожнього рядка на початку програми.
Якщо size it
більший за 1, ми знаходимось у частині then
і просто виконуємо print(it^it)
, правда? Не зовсім, тому що я нехтував сказати вам, що SML сильно набраний, а це означає, що умовний if <cond> then <exp_1> else <exp_2>
завжди повинен мати той самий тип, що знову ж таки означає, що вирази <exp_1>
і <exp_2>
потрібно мати один і той же тип. Ми вже знаємо тип else
деталі: анонімна функція, яка приймає рядок, а потім викликає, print
має тип string -> <return type of print>
, і print
має тип string -> unit
( unit
певним чином схожий з void
іншими мовами), тому отриманий тип знову string -> unit
.
Отже, якщо then
частина була саме такою, print(it^it)
яка має тип unit
, ми отримаємо помилку невідповідності типу. То як же fn _=>print(it^it)
? ( _
це підстановка для аргументу, який не використовується) Ця анонімна функція сама по собі має тип, 'a -> unit
де 'a
позначається довільний тип, тому в контексті нашого умовного, що примушує string -> unit
тип, це буде працювати. (Змінна типу 'a
інстанціюється з типом string
.) Однак у цьому випадку ми б нічого не друкували, оскільки анонімна функція ніколи не викликається! Пам'ятайте, що коли ми йдемо в then
-part, загальний код є "<code>"<code>
, тому <code>
-part оцінює функцію, але, оскільки після неї нічого не відбувається, його не викликають.
Замість цього ми використовуємо sequentialisation , яка має вигляд , (<exp_1>; ...; <exp_n>)
де <exp_1>
до <exp_n-1>
може мати довільні типи і тип <exp_n>
забезпечує тип всієї sequentialisation. З функціональної точки зору значення <exp_1>
для <exp_n-1>
просто відкидається, однак SML також підтримує імперативні конструкції , так що вираження можуть мати побічні ефекти. Коротше кажучи, ми беремо (print(it^it);print)
як then
-part, таким чином спочатку друкуємо, а потім повертаємо функцію, print
що має правильний тип.