LINUX.ORG.RU

Racket - отладчик макросов - жизнеспособен ли?

 ,


1

2

Вот читаю в мануале:

Warning: because of limitations in the way macro expansion is selectively hidden, the resulting syntax may not evaluate to the same result as the original syntax

Я воспринимаю данное утверждение как декларацию о негодности данного инструмента. У кого-нибудь есть иные мнения?

★★★★★
Ответ на: комментарий от den73

поскольку данные по ходу жизни будут обрастать и обрастать контрактами.

Только если все эти контракты разные и при этом истинные. Таких случаев достаточно мало.

можно очень быстро получить страшные тормоза,

На этот случай есть профайлер контрактов. Хотя в целом в Racket если выбор между скоростью и проверкой корректности, то всегда выбираем корректность.

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

Возможно. Я тёмен в контрактах и вот что мне не нравится:

1. Контракты в случае Racket задают одновременно неявные и ленивые ограничения на сущности в программе. Это очень плохо с точки зрения анализируемости. Такие программы должны легко становиться запутанными и выходить из под контроля. Возможно, есть какие-то ещё ограничения,которые не позволяют этому происходить, но я их пока не вижу.

2. Идеологемы типа «при выборе между иск и игрек мы всегда выбираем икс» всегда приводят к проблемам, зачастую к фатальным. Я такое не раз наблюдал в решениях разработчиков ЯП, и в Яре тоже есть такие решения, которые я пытаюсь менять по мере осознания возникающих проблем. В данном случае, я радуюсь, что не использую «грохот» - ведь это уже вторая идеологема, направленная против практичности. Сколь бы много приложений на нём не было написано - при такой идеологии это лишь академический язык.

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

Контракты в случае Racket задают одновременно неявные и ленивые ограничения на сущности в программе. Это очень плохо с точки зрения анализируемости. Такие программы должны легко становиться запутанными и выходить из под контроля.

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

Фактически, это позволяет в Racket производить отладку модулей абсолютно независимо друг от друга. И использование модуля не зависит от реализации этого модуля.

Идеологемы типа «при выборе между иск и игрек мы всегда выбираем икс» всегда приводят к проблемам, зачастую к фатальным. Я такое не раз наблюдал в решениях разработчиков ЯП, и в Яре тоже есть такие решения, которые я пытаюсь менять по мере осознания возникающих проблем

Такие идеологемы должны быть, если мы хотим иметь целостный язык. Иначе получаем как в CL: всюду поддержка переопределения на лету, разработки в образе; но кроме defstruct, defconstant (и частично defpackage); всё есть объект, CLOS часть стандарта; но elt и прочие функции для работы с произвольными последовательностями про CLOS не в курсе; в основном функции первым аргументом получают объект; но nth и gethash наоборот.

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

Смотря что понимать под практичностью. Если писать в одиночку, то контракты кажутся излишеством. Но если десять человек пилят каждый свой модуль, то возможность локализовать, кто именно накосячил через 5 секунд после появления ошибки почти бесценна.

К тому же данная идеологема является возможностью, а не обязанностью. В стандартной библиотеке контракты стоят только на входные данные и только быстрые, также есть unsafe- версии функций без проверок вообще.

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

Поэтому при корректных контрактах по любой ошибке всегда можно указать место возникновения ошибки с точностью до модуля

Это не слишком-то высокая точность. В данном конкретном случае можно было заметить проблему уже в момент, когда вставляется lambda (x) x, область определения к-рой не ограничена. Т.е. свести контракт к более простому и явному ограничению на тип функции. А для функций сделать какой-нибудь cast от lambda (х) х к её сужению на отображения целых в целые.

Идеологемы «всё можно переопределять на лету» и «всё есть объект» на мой взгляд тоже дают примеры идеологем, ведущих к проблемам. Равно как и «код - это данные». Я в данном случае не сравниваю с CL - если бы я считал его идеалом, я бы не начал делать свой язык. Я говорю лишь о проблеме, к-рую я вижу в твоём примере.

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

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

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

Вообще-то, функция с телом (lambda (x) x) соответствует типу integer -> integer. В данном случае код, вызвавший ошибку (g 'fail).

А для функций сделать какой-нибудь cast от lambda (х) х к её сужению на отображения целых в целые.

Это и есть контракт. Или какой должен быть текст этой функции (например, в CL)?

Идеологемы «всё можно переопределять на лету» и «всё есть объект» на мой взгляд тоже дают примеры идеологем, ведущих к проблемам.

Они полезны в определённых обстоятельствах. Первая позволяет разработку в образе. Вторая — обеспечивает мощность CLOS. Собственно, эти две идеологемы обеспечивают 90% преимущества CL перед C++.

Отладка модулей независимо друг от друга подразумевает создание оснасток, имитирующих партнёров модуля.

Без контрактов — да. А с контрактами я подключаю бета-версии всех используемых модулей и пишу тесты. Если тест провален, то контракт покажет, в каком модуле и в какой функции ошибка.

Хотя я не исключаю, что контракты - это здорово, просто я вижу, что опасность велика, и что нужно пользоваться ими аккуратно, чтобы не навредить.

Единственная потенциальная проблема --- по моему мнению, надо делать две версии контрактов. Типа safe и release. В safe максимально точные версии контрактов (например, для сортировки проверять, что результат отсортирован и является перестановкой исходной последовательности), в release проверку на входные данные. Тогда нет проблемы с производительностью, но на стадии отладки по сути имеем встроенные тесты на каждый запуск.

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

Единственная потенциальная проблема --- по моему мнению, надо делать две версии контрактов. Типа safe и release.

Контракты же можно вешать на стадии экспорта модуля, так что тут вроде проблем нет. С-но так обычно импортируются две версии - с контрактами и без контрактов.

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

Хотя я не исключаю, что контракты - это здорово, просто я вижу, что опасность велика, и что нужно пользоваться ими аккуратно, чтобы не навредить.

А в чем опасность-то и как навредить? Единственное - возможные просадки по производительности. В остальном хуже контракты никак сделать не смогут.

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

Контракты же можно вешать на стадии экспорта модуля, так что тут вроде проблем нет. С-но так обычно импортируются две версии - с контрактами и без контрактов.

У большинства библиотек нет двух версий. Я у себя сделал вообще три: есть binary-class, (submod binary-class safe) и (submod binary-class unsafe). Но в идеале была бы возможность выбирать (отключать контракты) на стадии импорта. К сожалению, моя идея не встретила понимания. Говорят, что худшее, что можно сделать — это убрать все проверки перед передачей в эксплуатацию. Возможно они более правы чем я.

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

Ладно, нужно будет потом посмотреть, что это, или предложить тебе написать спецификацию для контрактов для Яра :)

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

При наличии типов контракты обязаны являться расширением типов. Фактически, в SBCL есть контракты в виде declare type и ftype.

Для контрактов в стиле Racket достаточно разрешить зависимые типы. А также позволить нормально делать параметрические типы. В CL например сделать «список целых» или «список чётных» можно только через deftype на каждый такой тип. В Racket просто (listof integer) и (listof (and/с integer? (lambda (x) (= (mod x 2) 0)))).

Подумай, может в Яре лучше идти по пути «тип = функция», а не по CL-ному «тип = макрос, раскрывающийся в предопределённые типы»?

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

Подумаю со временем, не знаю, скоро ли оно настанет.

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

Ну это уже вопрос желания разработчика.

Так и я про то же. В отличие от CL и Ruby, в Racket можно использовать модуль только так, как пожелал разработчик. Сказал параметр не менять и контракты не отключать, значит так будет. А не нравится, делай форк.

Может так и правильней. Меньше привязки к реализации.

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

Меня в Ракетке добило отсутствие библиотеки для ldap. Пришлось лезть в ldap.h и рисовать оттуда биндинги через cffi. Основную работу сделал, данные с лдапа читаются. Теперь руки чешутся написать биндинг к ldap_modify_ext (пока не удалось), реализация замороченная, а как прокидывать ракетные списки и вектора в сишные массивы пока не разобрался. В irc тоже толком ничего не посоветовали, сказали, разбирайся сам, в доках всё есть. Надо cast попробовать.

У ffi, оказывается, есть серьёзный изъян: если писать биндинг для библиотеки для работы с сетью (а libldap это и есть), то любая блокировка, созданная кодом библиотеки вешает весь рантайм, все треды, пока блокировка не будет снята. Костыль: запускать отдельный place, и в нём уже дёргать ffi. Придётся греть голову чтением лдаповых спеков и пытаться сделать какое-то подобие библиотеки на чистой ракетке, что, по идее, куда правильнее, но и тяжелее.

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

Меня в Ракетке добило отсутствие библиотеки для ldap.

https://pkgs.racket-lang.org/package/ldap имеет ldap-authenticate

как прокидывать ракетные списки и вектора в сишные массивы пока не разобрался

Списки — _list, вектора — _vector

Костыль: запускать отдельный place, и в нём уже дёргать ffi.

Это не костыль. В любом другом языке также пришлось бы явно запускать отдельный поток.

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

Если нужна помощь, обращайся.

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

В принципе, пока всё неплохо идёт с напилингом биндинга для сишной либы. Пару часов назад победил ldap_modify_ext_s, которая порождала бесконечный цикл. Пока её сырцы не прочитал, не понял почему. Оказалось, что ей нужно не просто массив передавать с экземплярами структуры LDAPMod, а null-terminated array. #f передавал, который, как в документации написано, на стороне С будет рассматриваться как NULL. Нифига. NULL передаваться не хочет. Решил так.

(define-cstruct _ldapmod
  ([mod_op      _int]
   [mod_type    _string]
   [mod_values  (_or-null _pointer)]
 #;[mod_bvalues _pointer]))

(define _c-ldap-mod (_or-null _ldapmod-pointer))
Помог _or-null. Теперь всё нормально отрабатывает. Доволен как слон.

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

Тем не менее, #f в конце массива обязателен. Иначе бесконечный цикл.

Если такое встречается не один раз, то лучше обёртку сделать. Или ещё лучше _ctype.

Выложи пакет на github и дай ссылку.

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

Не за что! Обращайся, если что. Мне на Racket практики не хватает, а писать в стол (ни для кого) психологически очень тяжело.

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

Вопрос. В С мы имеем константы вида:

#define LDAP_OPT_SUCCESS        0                                 
#define LDAP_OPT_ERROR          (-1)
Есть в Racket способ определить такие константы для проброса в foreign functions?
Примерно как мы исполняем define-cstruct для сишных структур. Или достаточно этого?
(define ldap-opt-success 0)
(define ldap-opt-error  -1)

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

Или достаточно этого?

Достаточно. Но в лиспе/схеме традиционно используются символы для перечислимых значений. Для символов в FFI есть _enum

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

Я только разницу между _enum и _bitmask не уловил.

Наглядно:

(define test-enum (_enum '(n1 n2 n3)))
(define test-mask (_bitmask '(n1 n2 n3)))

> (cast 'n1 test-enum _int)
0
> (cast 'n2 test-enum _int)
1
> (cast 1 _int test-enum)
'n2
> (cast 3 _int test-enum)
. . enum:int->test-enum: expected a known #<ctype:ufixint>, got: 3
> (cast 3 _int test-mask)
'(n1 n2)
> (cast 1 _int test-mask)
'(n1)

_enum даёт преобразование символ<->число, _bitmask из числа всегда делает список символов (по установленным битам) и принимает, соответственно либо список либо один символ (трактует как список из одного элемента).

И нумерация по-умолчанию для _enum 0,1,2,3,..., а для _bitmask — 1,2,4,8,...

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

Можно ещё вопрос? Тут дело похуже.
Тут пробую писать минимальный интерфейс к libgegl, и в gegl-node.h увидел такие кружева:

GeglNode    * gegl_node_new_child        (GeglNode      *parent,
                                          const gchar   *first_property_name,
                                          ...) G_GNUC_NULL_TERMINATED;
Суть в том, что функция принимает переменное число аргументов и NULL в качестве последнего.
/*< The image nodes representing operations we want to perform */
    GeglNode *display    = gegl_node_create_child (gegl, "gegl:ff-save");
    GeglNode *crop       = gegl_node_new_child (gegl,
                                 "operation", "gegl:crop",
                                 "width", 512.0,
                                 "height", 384.0,
                                  NULL);
    GeglNode *over       = gegl_node_new_child (gegl,
                                 "operation", "gegl:over",
                                 NULL);
    GeglNode *text       = gegl_node_new_child (gegl,
                                 "operation", "gegl:text",
                                 "size", 10.0,
                                 "color", gegl_color_new ("rgb(1.0,1.0,1.0)"),
                                 NULL);
    GeglNode *mandelbrot = gegl_node_new_child (gegl,
                                "operation", "gegl:fractal-explorer",
                                "shiftx", -256.0,
                                NULL);

У нас есть возможность определить в Racket что-то подобное?

(defgegl gegl-node-new-child (_fun _pointer _string ... _pointer -> _pointer)
  #:c-id gegl_node_new_child)

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

«size», 10.0,

Тут даже не _string ...

Можно сочинить что-то такое:

(define (node-new-child gegl params)
  (define (get-type x)
    (cond
     [(integer? x) _int]
     [(flonum? x) _float]
     [(string? x) _string]
     [else _pointer]))
  (define-values (args arg-types)
    (let loop ([args null] [arg-types null] [params params])
      (if (null? params)
          (values (reverse (cons #f args) (reverse (cons _pointer arg-types)))
          (let ([param (car params)])
            (loop (list* (cdr param) (symbol->string (car param)) args)
                  (list* (get-type (cdr param)) _string arg-types)
                  (cdr params))))))
  (defgegl gegl-node-new-child (_cprocedure arg-types _pointer))
  (apply gegl-node-new-child gegl args))

И использовать как

(node-new-child gegl `((operation "gegl:text")
                       (size 10.0)
                       (color ,(gegl-color-new "rgb(1.0,1.0,1.0)"))))

Кстати, если в defgegl имя и c-id отличаются только дефисами, то c-id не нужен.

А можно сделать как в Haskell. Там стоит

(defgegl gegl-node-new-child (_fun _pointer (_string = "operation") _string (_pointer = #f) -> _pointer)

А дальнейшие поля заполняются через gegl-node-set.

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

Кстати, если в defgegl имя и c-id отличаются только дефисами, то c-id не нужен.

Если сделать так, то да. Кстати, спасибо за наводку. ) Хоть ldap-ffi подчищу от хлама. На работе уже вовсю использую.

#lang racket/base

(require ffi/unsafe
         ffi/unsafe/define
         ffi/unsafe/define/conventions)

(provide defgegl)

(define-ffi-definer defgegl (ffi-lib "libgegl-0.3")
  #:make-c-id convention:hyphen->underscore)
Hertz ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.