Чи можливий статично набраний повний варіант Lisp? Чи навіть має сенс існувати щось подібне? Я вважаю, що однією з чеснот мови Lisp є простота її визначення. Чи буде статична введення тексту компрометувати цей основний принцип?
Чи можливий статично набраний повний варіант Lisp? Чи навіть має сенс існувати щось подібне? Я вважаю, що однією з чеснот мови Lisp є простота її визначення. Чи буде статична введення тексту компрометувати цей основний принцип?
Відповіді:
Так, це дуже можливо, хоча стандартна система типу HM як правило є неправильним вибором для більшості ідіоматичних кодів Lisp / Scheme. Дивіться " Типізована ракетка" щодо недавньої мови, яка є "повним лісом" (більше насправді як Схема) зі статичним набором тексту.
Sexpr
.
coerce :: a->b
з точки зору eval. Де безпека типу?
eval
вам потрібно протестувати результат, щоб побачити, що виходить, що не є новим у Typed Racked (така ж угода, як функція, яка приймає тип об'єднання String
і Number
). Неявним способом побачити, що це можна зробити, є той факт, що ви можете писати та використовувати динамічно набрану мову на мові, введеній HM-статикою.
Якщо все, що ви хотіли, було статично набраною мовою, схожою на Lisp, ви могли це зробити досить легко, визначивши абстрактне дерево синтаксису, яке представляє вашу мову, а потім зіставивши цей AST з S-виразами. Однак я не думаю, що я би назвав результат Ліспом.
Якщо ви хочете чогось, що насправді має характеристики Lisp-y, крім синтаксису, можна зробити це зі статично набраною мовою. Однак для Lisp є багато характеристик, з яких важко отримати набагато корисні статичні типи. Для ілюстрації давайте розглянемо саму структуру списку, яку називають мінусами , яка утворює первинний будівельний блок Lisp.
Виклик списку мінусів, хоч і (1 2 3)
схожий на один, є дещо помилковим. Наприклад, це зовсім не порівнянно зі статично типізованим списком, як, наприклад, зі списком C ++ std::list
або Haskell. Це одновимірні пов'язані списки, де всі комірки одного типу. Лисп щасливо дозволяє (1 "abc" #\d 'foo)
. Крім того, навіть якщо ви розширите свої статичні типи списків на покриття списків списків, тип цих об'єктів вимагає, щоб кожен елемент списку був підсписком. Як би ви представляли ((1 2) 3 4)
в них?
Лисп конси утворюють двійкове дерево, з листям (атомами) і гілками (конси). Крім того, листя такого дерева можуть взагалі містити будь-який атомний (без мінусів) тип Lisp! Гнучкість цієї структури - це те, що робить Lisp настільки хорошим в обробці символічних обчислень, AST та самому перетворенні коду Lisp!
Отже, як би ви змоделювали таку структуру мовою, що має статичний тип? Спробуємо це в Haskell, який має надзвичайно потужну та точну систему статичного типу:
type Symbol = String
data Atom = ASymbol Symbol | AInt Int | AString String | Nil
data Cons = CCons Cons Cons
| CAtom Atom
Вашою першою проблемою буде сфера застосування типу Atom. Зрозуміло, що ми не вибрали тип Atom, який має достатню гнучкість, щоб охопити всі типи об'єктів, за якими ми хочемо перекинутись за conses. Замість того, щоб намагатися розширити структуру даних Atom, як перераховано вище (що ви добре бачите, крихкий), скажімо, у нас був магічний клас типу, Atomic
який відрізняв усі типи, які ми хотіли зробити атомними. Тоді ми можемо спробувати:
class Atomic a where ?????
data Atomic a => Cons a = CCons Cons Cons
| CAtom a
Але це не спрацює, оскільки вимагає, щоб усі атоми в дереві були одного типу. Ми хочемо, щоб вони могли відрізнятися від листя до листя. Для кращого підходу потрібно використовувати екзистенційні кількісні показники Haskell :
class Atomic a where ?????
data Cons = CCons Cons Cons
| forall a. Atomic a => CAtom a
Але тепер ви прийшли до суті справи. Що ви можете зробити з атомами в такій структурі? З якою спільною структурою вони можуть бути змодельовані Atomic a
? Який рівень безпеки типу вам гарантований при такому типі? Зверніть увагу, що ми не додали жодних функцій до нашого класу типів, і це є вагома причина: атоми не мають нічого спільного в Lisp. Їх супертип у Ліспі називають просто t
(тобто вершиною).
Для того, щоб використовувати їх, вам доведеться придумати механізми, щоб динамічно примусити значення атома до того, що ви можете реально використовувати. І в цей момент ви в основному реалізували динамічно типізовану підсистему у вашій статично набраній мові! (Не можна не відзначити можливий наслідок Десятого правила програмування Грінспуна .)
Зауважимо, що Haskell забезпечує підтримку саме такої динамічної підсистеми з Obj
типом, що використовується в поєднанні з Dynamic
типом і класом Typeable, щоб замінити наш Atomic
клас, які дозволяють зберігати довільні значення з їх типами, і явний примус назад з цих типів. Це та система, яку вам потрібно було б використовувати для роботи зі структурами Lisp мінусів у повній загальності.
Можна також зробити інший шлях та вставити статично типову підсистему в істотно динамічно набрану мову. Це дозволяє вам скористатися перевіркою статичного типу для частин програми, які можуть скористатися більш суворими вимогами типу. Це, мабуть, такий підхід, наприклад, у обмеженій формі точної перевірки типу CMUCL .
Нарешті, існує можливість наявності двох окремих підсистем, динамічно та статично типізованих, які використовують програмування в контрактному стилі, щоб допомогти орієнтуватися на перехід між ними. Таким чином мова може пристосовувати звичаї Lisp, коли статична перевірка типу буде скоріше перешкодою, ніж допомогою, а також використанням, де статична перевірка типу буде вигідною. Це такий підхід, який використовується Типізована ракетка , як ви побачите з коментарів, які випливають.
(Listof Integer)
і (Listof Any)
. Очевидно, ви б підозрювали, що останні є марними, оскільки ви нічого не знаєте про тип, але в TR ви можете пізніше використовувати, (if (integer? x) ...)
і система дізнається, що x
це цілий число в 1-й гілці.
dynamic
типи стають популярними в статичних мовах як своєрідне вирішення, щоб отримати деякі переваги динамічно набраних мов, при цьому звичайний компроміс цих значень загортається таким чином, що типи ідентифікуються. Але і тут набрана ракетка робить дуже гарну роботу, роблячи її зручною для мови - перевіряючий тип використовує випадки предикатів, щоб дізнатися більше про типи. Наприклад, дивіться набраний приклад на сторінці ракетки і дивіться, як string?
"зменшує" список рядків і чисел до списку рядків.
Моя відповідь, без високого ступеня впевненості, мабуть . Якщо, наприклад, дивитися на мову, подібну до SML, і порівнювати її з Lisp, функціональне ядро кожного майже однакове. Як результат, не здається, що у вас виникнуть багато проблем із застосуванням якоїсь статичної типізації до ядра Lisp (додатки функції та примітивні значення).
Хоча ваше запитання відповідає повною мірою , і де я бачу, яка проблема виникає, це підхід до коду як даних. Типи існують на більш абстрактному рівні, ніж вирази. Лісп не має цього розрізнення - все "плоско" за структурою. Якщо ми розглянемо деякий вираз E: T (де T - деяке представлення його типу), і тоді ми вважаємо це вираження простим даними, то який саме тип T тут? Ну, це якийсь вид! Видом є тип вищого порядку, тому давайте просто продовжимо і скажемо щось про це в нашому коді:
E : T :: K
Ви можете побачити, куди я йду з цим. Я впевнений, виокремлюючи інформацію про тип від коду, можна було б уникнути подібного типу самореференційності типів, однак це зробило б типи не надто "бляклими" за своїм смаком. Напевно, існує багато способів цього, хоча мені не очевидно, який був би найкращий.
EDIT: О, так, трохи погуглившись, я знайшов Ци , який, схоже, дуже схожий на Лісп, за винятком того, що він статично набраний. Можливо, це гарне місце, щоб почати бачити, де вони внесли зміни, щоб отримати там статичне введення тексту.