Чи можливий статично набраний повний варіант Lisp?


107

Чи можливий статично набраний повний варіант Lisp? Чи навіть має сенс існувати щось подібне? Я вважаю, що однією з чеснот мови Lisp є простота її визначення. Чи буде статична введення тексту компрометувати цей основний принцип?


10
Мені подобаються макроси Лісп у вільній формі, але мені подобається надійність системи типів Haskell. Я хотів би побачити, як виглядає статичний тип Lisp.
mcandre

4
Гарне питання! Я вважаю, що shenlanguage.org це робить. Я б хотів, щоб він став більш мейнстрімом.
Гаміш Грубіян


Як ви робите символічні обчислення з Haskell? (вирішити 'x' (= (+ xy) (* xy))). Якщо ви помістите його в рядок, немає перевірки (на відміну від Lisp, який може використовувати макроси для додавання перевірки). Якщо ви використовуєте алгебраїчні типи даних або списки ... Це буде дуже багатослівно: вирішити (Sym "x") (Eq (Plus (Sym "x") (Sym "y")) (Mult (Sym "x") (Sym "y")))
aoeu256

Відповіді:


57

Так, це дуже можливо, хоча стандартна система типу HM як правило є неправильним вибором для більшості ідіоматичних кодів Lisp / Scheme. Дивіться " Типізована ракетка" щодо недавньої мови, яка є "повним лісом" (більше насправді як Схема) зі статичним набором тексту.


1
Проблема тут полягає в тому, який тип списку складає весь вихідний код набраної ракетної програми?
Zorf

18
Це зазвичай було б Sexpr.
Елі Барзілай

Але тоді я можу писати coerce :: a->bз точки зору eval. Де безпека типу?
ssice

2
@ssice: коли ви використовуєте нетипізовану функцію, на зразок evalвам потрібно протестувати результат, щоб побачити, що виходить, що не є новим у Typed Racked (така ж угода, як функція, яка приймає тип об'єднання Stringі Number). Неявним способом побачити, що це можна зробити, є той факт, що ви можете писати та використовувати динамічно набрану мову на мові, введеній HM-статикою.
Елі Барзілай

37

Якщо все, що ви хотіли, було статично набраною мовою, схожою на 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, коли статична перевірка типу буде скоріше перешкодою, ніж допомогою, а також використанням, де статична перевірка типу буде вигідною. Це такий підхід, який використовується Типізована ракетка , як ви побачите з коментарів, які випливають.


16
Ця відповідь страждає від принципової проблеми: ви припускаєте, що системи статичного типу повинні бути в HM-стилі. Основне поняття, яке неможливо виразити там і є важливою особливістю коду Ліспа, - це підтипування. Якщо ви подивитеся на набрану ракетку, ви побачите, що вона може легко висловити будь-який список списку - включаючи такі речі, як (Listof Integer)і (Listof Any). Очевидно, ви б підозрювали, що останні є марними, оскільки ви нічого не знаєте про тип, але в TR ви можете пізніше використовувати, (if (integer? x) ...)і система дізнається, що xце цілий число в 1-й гілці.
Елі Барзілай

5
О, і це погана характеристика типізованої ракетки (яка відрізняється від систем беззвучного типу, які ви знайдете в деяких місцях). Набрана ракетка - це статично набрана мова, без накладних витрат для введеного коду. Ракетка все ще дозволяє записати деякий код на TR, а частину на звичайній нетипізованій мові - і в цих випадках контракти (динамічні перевірки) використовуються для захисту введеного коду від потенційно неправильного поведінки нетипізованого коду.
Елі Барзілай

1
@Eli Barzilay: Я збрехав, є чотири частини: 4. Мені цікаво, як прийнятий у галузі стиль кодування C ++ поступово відходить від підтипу до генерики. Слабка слабкість полягає в тому, що мова не допомагає оголосити інтерфейс загальною функцією, що типи класів, безумовно, можуть допомогти. Крім того, C ++ 0x може додавати умовиводи. Я не гадаю, але гадаю, що в цьому напрямку?
Оуен С.

1
Оуен: (1) головний момент полягає в тому, що вам потрібні підтипи, щоб виразити тип запису листокодів коду - і ви просто не можете цього мати з системами HM, тому ви змушені використовувати спеціальні типи та конструктори для кожного використання, які робить всю справу набагато незручнішою у використанні. У типізованій ракетці використання системи з підтипами було наслідком навмисного дизайнерського рішення: щоб результат мав змогу виражати типи такого коду без зміни коду чи створення спеціальних типів.
Елі Барзілай

1
(2) Так, dynamicтипи стають популярними в статичних мовах як своєрідне вирішення, щоб отримати деякі переваги динамічно набраних мов, при цьому звичайний компроміс цих значень загортається таким чином, що типи ідентифікуються. Але і тут набрана ракетка робить дуже гарну роботу, роблячи її зручною для мови - перевіряючий тип використовує випадки предикатів, щоб дізнатися більше про типи. Наприклад, дивіться набраний приклад на сторінці ракетки і дивіться, як string?"зменшує" список рядків і чисел до списку рядків.
Елі Барзілай

10

Моя відповідь, без високого ступеня впевненості, мабуть . Якщо, наприклад, дивитися на мову, подібну до SML, і порівнювати її з Lisp, функціональне ядро ​​кожного майже однакове. Як результат, не здається, що у вас виникнуть багато проблем із застосуванням якоїсь статичної типізації до ядра Lisp (додатки функції та примітивні значення).

Хоча ваше запитання відповідає повною мірою , і де я бачу, яка проблема виникає, це підхід до коду як даних. Типи існують на більш абстрактному рівні, ніж вирази. Лісп не має цього розрізнення - все "плоско" за структурою. Якщо ми розглянемо деякий вираз E: T (де T - деяке представлення його типу), і тоді ми вважаємо це вираження простим даними, то який саме тип T тут? Ну, це якийсь вид! Видом є тип вищого порядку, тому давайте просто продовжимо і скажемо щось про це в нашому коді:

E : T :: K

Ви можете побачити, куди я йду з цим. Я впевнений, виокремлюючи інформацію про тип від коду, можна було б уникнути подібного типу самореференційності типів, однак це зробило б типи не надто "бляклими" за своїм смаком. Напевно, існує багато способів цього, хоча мені не очевидно, який був би найкращий.

EDIT: О, так, трохи погуглившись, я знайшов Ци , який, схоже, дуже схожий на Лісп, за винятком того, що він статично набраний. Можливо, це гарне місце, щоб почати бачити, де вони внесли зміни, щоб отримати там статичне введення тексту.


Здається, наступна ітерація після Ци - Шень , розроблена тією ж людиною.
Діагон

4

Посилання мертва. Але в будь-якому випадку Ділан не набраний статично.
Бьорн Ліндквіст

@ BjörnLindqvist: це посилання було тезою про додавання поступового набору тексту Ділану.
Rainer Joswig

1
@ BjörnLindqvist: Я пов’язаний з оглядовим документом.
Rainer Joswig

Але поступове введення тексту не вважається статичним набором тексту. Якщо так, Pypy буде статично набраний Python, оскільки він також використовує поступове введення тексту.
Бьорн Ліндквіст

2
@ BjörnLindqvist: якщо ми додаємо статичні типи за допомогою поступового набору тексту, і вони перевіряються під час компіляції, то це статичне введення тексту. Просто не вся програма статично набрана, а частини / регіони. homes.sice.indiana.edu/jsiek/what-is-gradual-typing "Поступове введення тексту - це система типів, розроблена разом з Валідом Таха в 2006 році, яка дозволяє динамічно набирати частини програми, а інші частини статично вводити."
Райнер Йосвіг
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.