LINUX.ORG.RU

Анализ пользователей Common Lisp и Racket

 , ,


11

7

Common Lisp разрабатывался и используется в предположении, что пользователь программы — программист. Поэтому из языка намеренно исключены сложные для понимания конструкции (пользователь не обязательно квалифицированный программист), поэтому в языке мощнейший отладчик, позволяющий без остановки программы переопределять функции и вообще делать что угодно. Но из-за этого документация по большей части библиотек Common Lisp существует только в виде docstring и комментариев в коде (некоторые вообще считают, что код сам себе документация). Из-за этого обработка ошибок почти всегда оставляется на отладчик (главное сделать рестарт «перезапустить с последней итерации», а там пользователь сам разберётся). Из-за этого в программе проверяется только happy path (пользователь ведь «тоже программист»).

Racket разрабатывался и используется в предположении, что пользователь программы не программист, а задача разработчика написать программу так, чтобы она корректно работала при любых входных данных (если данные некорректны, то сообщала об этом в том месте, где данные были введены). Поэтому в языке эффективная библиотека для написания тестов, система контрактов на уровне модулей, макимально широкий спектр инструментов программирования (разработчик должен быть профессионалом!). Также реализована идея инкапсуляции: считается, что пользователь модуля не должен знать особенности реализации и, более того, не может в своём коде изменить функцию чужого модуля если это явно не разрешено разработчиком того модуля. Исходный код разумеется доступен, но его не требуется смотреть, чтобы использовать модуль. Достаточно документации. Поэтому реализована мощнейшая система документировния Scribble, а при реализации макроса есть возможность обеспечить указание на ошибки в коде, предоставленном макросу пользователем, не показывая потроха макроса.

И поэтому в Racket нет CLOS (есть как минимум две реализации, но не используются) - провоцирует заплаточное программирование (monkey patching), поэтому отладчик намеренно ограничен (если ты отлаживаешь программу, значит ты не знаешь как она должна работать!), поэтому нет разработки в образе (image based) - она провоцирует разработку через отладку (а значит непонимание программы и проверку только happy path).

Таким образом, Racket и Common Lisp несмотря на внешнее сходство являются очень разными языками. И я рекомендую писать на Racket, если только конечными пользователями программы не являются исключительно программисты на Common Lisp.

Взято с http://racket-lang.blog.ru/#post214726099

Хотелось бы знать, что по этому поводу думают пользователи ЛОРа. А также, мне кажется, что для Java и C++ будет где-то такая же разница.

★★★★★
Ответ на: комментарий от ilammy
typedef LimitedInteger<int, 0, 100> percents;
typedef UnlimitedInteger<unsigned int> miles;

И ты утверждаешь, что после этого

percents p1, p2;
p1 + p2;
скомпилируется а
percents p;
miles m;

return m+p
выдаст ошибку, как заявлено в Анализ пользователей Common Lisp и Racket (комментарий) ?

И ошибка percents a = 60+60; тоже будет выявлена только при запуске. Так что это как раз те самые контракты, которые в нормальном виде есть в Racket.

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

Я никак не определяю, потому что пишу на Си и Python

То есть ты ратуешь за статическую типизацию, но сам ей не пользуешься? Почему?

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

Не надо так считать.

Сделать класс Percent, для которого не определены операции +/-, делов-то :)

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

А что мешает создать (в Си++, к примеру) класс «проценты»

То, что проверка ограничения всё равно будет в динамике. И зачем тогда Си++, если в Racket есть то же самое, но без необходимости писать лишний код и с большей гибкостью?

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

Я только пример привёл к тому, что если надо, то в Си++ тоже можно сделать контракты. Для того, чтобы это ловилось системой типов, нужна какая-нибудь Агда или ещё что с dependent types, а не Си++.

Вообще там должно быть наследование от подобного типа (чтобы два разных UnlimitedInteger нельзя было складывать), всё это завёрнуто в ещё большее количество шаблонов и вытянуто из Буста. Буст я не умею, вон tailgunner даже ссылочку привёл.

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

Это нормально, большинство хаскель-фанбоев такие.

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

если надо, то в Си++ тоже можно сделать контракты

как там, концепты будут в C++17? или придется C++27 ждать?

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

можно сделать

Лол, прямо общелиспом повеяло. МОЖНАСДЕЛАТЬ.

Для того, чтобы это ловилось системой типов, нужна какая-нибудь Агда или ещё что с dependent types, а не Си++.

Лол да не надо ничего этого. Вы ребята рли упоротые. Достаточно просто определить интерфейс, в котором все ф-и на Percent (типа +/-) принимает санку с веткой для случая ошибки. И ВСЕ. Гораздо проще чем с зависимыми типами, проще в использовании, более гибко - при этом гарантии точно такие же, лол.

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

Память тебя подвела, сорь.

Нет. Я мог ошибиться или неправильно истолковать слова «Typed Racket'ish», но помню я правильно.

То есть ты ратуешь за статическую типизацию, но сам ей не пользуешься? Почему?

Даже убогая статическая типизация Си (которой я пользуюсь) имеет вполне очевидные преимущества перед динамической типизацией Python. И к тому же я знаком не только с Python и Си.

А что мешает создать (в Си++, к примеру) класс «проценты»

То, что проверка ограничения всё равно будет в динамике

Она будет в статике.

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

класс Percent, для которого не определены операции +/-

Так я и в Racket могу сделать (struct percent (val)). И навесить на него любые дополнительные контракты. Хотя да, наличие операции + для заданного типа в C++ будет при компиляции, а в Racket в рантайме. Зато нет проблемы в одной переменной хранить разные типы (в C++ есть union, но он требует явного указания имени поля или все типы должны быть наследниками одного класса).

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

Она будет в статике.

Сделай мне класс percents, который скомпилирует

percents p(90);

Но не скомпилирует

percents p(60+60);

и выведет предупреждение на

percents *f(char a, char b)
{ return new percents(a+b); }

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

Сделай мне класс percents, который скомпилирует

Я уже сказал, что _это_ нарушение отследить в статике невозможно (по крайней мере, средствами Си++). Но предотвратить сложение процентов с километрами - легко.

Хотя... возможно, при помощи constexpr-магии можно отслеживать, что литералы находятся в нужных пределах. Но от динамических проверок при В/В или арифметических операциях это всё равно не избавит.

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

в котором все ф-и на Percent (типа +/-) принимает санку с веткой для случая ошибки.

Зачем санку-то? Исключения вообще-то ровно для этого придумали. Другое дело, что такая схема ничем не отличается от динамического языка. Но в динамическом языке (racket) я могу контракт выписать вверху кода в интерфейсе, а в C++ придётся смешивать код класса с кодом проверки. Не говоря уже о многословном описании самого класса.

На самом деле мне в C++ нравится то, что тип определяет пространство имён. Таким образом, определив переменные можно неявно указать, какие функции доступны. Удобно для IDE. Но негибко. Для каждого типа приходится копипастить реализацию (особенно для всяких operator+, operator-, operator+=, operator-=, ...). Куча бойлерплейта. Если где-то ошибка, надо не забыть исправить во всех скопированных местах. Надо вручную отслеживать семантику операторов (чтобы a += b, и a = a + b; и a = b + a; давало одно и то же).

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

Лол, прямо общелиспом повеяло. МОЖНАСДЕЛАТЬ.

На брейнфаке тоже можно. Вон там кто-то уже СДЕЛАЛ для Буста.

Лол да не надо ничего этого. Вы ребята рли упоротые. Достаточно просто определить интерфейс, в котором все ф-и на Percent (типа +/-) принимает санку с веткой для случая ошибки. И ВСЕ. Гораздо проще чем с зависимыми типами, проще в использовании, более гибко - при этом гарантии точно такие же, лол.

Что, по-твоему, вон тот LimitedInteger должен был делать? Именно что переопределять все operator+() и бросаться исключениями, если значение выходит за диапазон. Но все эти контракты будут проверяться динамически. Вон те адовы типы могут (в принципе) выразить эту штуку статически; вот только я думаю, это вызовет адов геморрой для окружающей программы, если вообще окажется вычислимым.

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

пфффф

#lang racket
(require syntax/parse/define
         (for-syntax syntax/parse))

(begin-for-syntax
  (define-syntax-class p
    #:description "number in range from 0 to 100"
    (pattern x:number
             #:when (and (>= (syntax->datum #'x) 0)
                         (<= (syntax->datum #'x) 100)))))

(define-simple-macro (make-percent x:p)
  x)

(define-simple-macro (percent-plus x y)
  #:do [(raise-syntax-error 'percent-plus "maybe illegal result")]
  #'(void))

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

Но предотвратить сложение процентов с километрами - легко.

Честно скажу, на ошибку «несоответствие типов» от компилятора нарывался всего пару раз за двадцать лет (опечатки). Гораздо чаще «пропущена ;», «пропущена )».

И даже разрабатывая на Python и 1C все ошибки, которые приходилось отлавливать — с типами не связаны. Либо пропущена ветка на проверку на null, либо просто алгоритмическая ошибка, либо опечатка в имени колонки SQL результата.

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

Честно скажу, на ошибку «несоответствие типов» от компилятора нарывался всего пару раз за двадцать лет (опечатки)

Твой опыт... реально исключителен. Первое сообщение об ошибке Turbo C, которое я перевел, было «type mismatch».

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

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

Ты не понял. Тот факт что ты выразишь эту штуку статически например в агде приведет лишь к тому, что агда заставит тебя проверять аргументы на корректность результата. С-но у тебя необходимо будет ветка выполнения в которой все ок (якобы результат не выходит за ренж) и ветка обработчика в которой все плохо (результат выходит за ренж). ТОЧНО того же самого можно добиться, если просто потребовать от +- указания false-ветки.

В результате типов вообще не нужно, но гарантии точно такие же, но результат в точности совпадает с тем, как если бы ты писал в агде какой-нибудь (не забудешь укзаать false-ветку)

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

Твой опыт... реально исключителен.

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

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

не забудешь указать false-ветку

«Программист вечером ставит два стакана: с водой, на случай если захочет пить, и пустой, на случай если не захочет» (с) анекдот. А я-то думал откуда это...

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

(make-percent (+ 60 60)) не работает.

Подобного требования не было. Но ок:

#lang racket
(require syntax/parse/define
         (for-syntax syntax/parse))

(begin-for-syntax
  (define-syntax-class p
    #:description "number in range from 0 to 100"
    (pattern x:expr
             #:do [(define val (syntax->datum (local-expand #'x 'expression #f)))]
             #:attr value val
             #:when (and (number? val)
                         (>= val 0)
                         (<= val 100)))))

(define-simple-macro (percent x:p)
  x)

(define-simple-macro (percent-plus x:p y:p)
  #:with res #`#,(+ (attribute y.value)(attribute x.value))
  (percent res))

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

Альтернативная вселенная - это та, где таилганеры пердолят километры с жирафами. Умственно здоровый человек такой код осознанно написать _в принципе_ не способен. То есть вообще.

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

сначала всякая мелочь клепается, по сто раз исполняясь прямо во время написания, а потом сшивается во что-то большее.

Справедливости ради, такой подход возможен и в каком-нибудь хаскеле, где тоже есть репл. Типы этому никак не мешают.

Можно зевнуть и использовать левую функцию с такой же сигнатурой, и компилятор это скомпилирует.

Такое (у меня, по крайней мере) происходит ещё реже, чем опечатки.

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

Альтернативная вселенная - это та, где таилганеры пердолят километры с жирафами.

Естетственно, для тебя наша вселенная является альтернативной.

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

Умственно здоровый человек такой код осознанно написать _в принципе_ не способен. То есть вообще.

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

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

Моя вселенная - вселенная нормальных, здоровых людей. Для альтернативно одаренных носителей 47 хромосомы - эта вселенная, без сомнения, альтернативна.

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

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

То есть сперва кошка умела мяукать, а потом мы ее этой возможности лишили, и теперь функция, принимающая кошек, не работает? Типа такого?

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

Моя вселенная - вселенная нормальных, здоровых и богатых людей

/fixed

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

Мешают - до того, как ф-я дописана, она тайпчек не пройдет.

В смысле нельзя с «недописанной» функцией запуститься? Ну это да.

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

То есть сперва кошка умела мяукать, а потом мы ее этой возможности лишили, и теперь функция, принимающая кошек, не работает? Типа такого?

Бывает и так, но редко, обычно наоборот - функция требуется больше чем раньше.

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

а в каком-нибудь C++ указание типа переменной MyClass x лишь сужает возможный набор типов для x, но не определяет тип однозначно, поскольку у MyClass есть потомки.

Немного придираюсь - в данном случае тип как раз определяется однозначно. Вот если это была бы передача по ссылке/указателю (MyClass &x/MyClass *x), тогда другое дело. То есть во многих случаях тип известен абсолютно точно.

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

Сделай мне класс percents, который скомпилирует

percents p(90);

Но не скомпилирует

percents p(60+60);

Класс писать лениво, но, теоретически, возможно. Примерно так:

#define RN(v) ({ static_assert(0 <= (v) && (v) <= 100, "foo"); v; })

void foo()
{
	int p1 = RN(1), p2 = RN(101), p3 = RN(100 + 1);
}
$ c++ -c -std=gnu++11 a.cpp 
a.cpp: In function ‘void foo()’:
a.cpp:6:23: error: static assertion failed: foo
a.cpp:6:37: error: static assertion failed: foo
tailgunner ★★★★★
()
Ответ на: комментарий от monk

Но у нас в языке нет 0..100

А если есть?

В то время как очевидно, что для a = b = 60 уже нарушение контракта.

Такое вполне может проверяться. Даже в каком-нибудь С++ проверяется (правда в результате будет всего-лишь предупреждение). Другое дело, что а и б, чаще всего, формируются в рантайме. Но и это кое-где проверяется. Ну или делается руками.

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

Подобного требования не было. Но ок.

Вот! На динамическом языке получили гарантии времени компиляции, которые не может дать система типов. В принципе, чуть поправить, чтобы на невычислимые при компиляции выражения он давал предупреждение, но работал и можно использовать как замену «статическим типам» любителям этих типов

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

На динамическом языке получили гарантии времени компиляции

Это можно сделать на любом языке, у которого есть CFE.

которые не может дать система типов

См. выше.

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

В смысле нельзя с «недописанной» функцией запуститься? Ну это да.

Ну так смысл именно в этом.

anonymous
()

Камрады, сами мы не местные и тему не читамши, но скажите, какая сейчас самая правильная scheme/lisp для встраивания в сишечку? Задача самая простая -> протянуть пару/тройку вызовов/интерфейсов во встроенный скрипт. Сейчас метаюсь между lua и scheme. хочу вот попробовать и сравнить.

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

Это можно сделать на любом языке, у которого есть CFE.

Ну нет. Надо уметь выполнять произвольный код в компайлтайме.

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

Бывает и так, но редко

Именно!

обычно наоборот - функция требуется больше чем раньше.

Это значит, что мы саму функцию переписали.

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

Надо уметь выполнять произвольный код в компайлтайме.

Надо уметь делать ровно то, что сделано в примере // К.О.

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

(в C++ есть union, но он требует явного указания имени поля или все типы должны быть наследниками одного класса).

Ещё есть всяческие варианты (variant). Правда тоже не без ограничений: работают или с ограниченным набором типов или требуют явного перечисления поддерживаемых при создании. Впрочем, это не слишком часто требуется.

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

Для каждого типа приходится копипастить реализацию (особенно для всяких operator+, operator-, operator+=, operator-=, ...)

И насколько часто такое делать приходится?..

Надо вручную отслеживать семантику операторов (чтобы a += b, и a = a + b; и a = b + a; давало одно и то же).

Это решается библиотеками (тем же бустом).

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

Это значит, что мы саму функцию переписали.

Мзменили, и это рядовое событие для долгоживущих проектов, в которых наращивают функциональность и/или проводят рефакторинг.

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

чтобы на невычислимые при компиляции выражения он давал предупреждение

Как-то так:

#lang racket
(require syntax/parse/define
         (for-syntax racket
                     syntax/parse))

(begin-for-syntax
  (define-namespace-anchor nsa)
  (define ns (namespace-anchor->namespace nsa))
  
  (define-syntax-class p
    #:description "number in range from 0 to 100"
    (pattern x:expr
             #:do [(define expanded (local-expand #'x 'expression #f))
                   (define val (with-handlers ([(const #t) (λ (e)
                                                             (displayln (format "WARNING! At ~a" #'x))
                                                             #f)])
                                 (eval expanded ns)))]
             #:attr value val
             #:with exp expanded
             #:when (or  (not val)
                         (and (number? val)
                              (>= val 0)
                              (<= val 100))))))

(define-simple-macro (percent x:p)
  x)

(define-simple-macro (percent-plus x:p y:p)
  #:with res (if (and (attribute y.value) (attribute x.value))
                 #`#,(+ (attribute y.value)(attribute x.value))
                 #'(+ x.exp y.exp))
  (percent res))

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