LINUX.ORG.RU

Язык, в котором есть constraints'ы

 constraints, ,


0

4

Это то же самое, что типы данных, но для логики приложения, а не логики исполнителя (CPU).

Пример constraint'а: «constrX is integer, [0..55],[117..200],999»

Другой пример: «constrY is string, regexp(^fo+\s+bar$)»

По-моему по сравнению с ограничениями на внешние данные в духе «вот в этом поле число не должно быть больше 2^32-1» потому что у нас битовая разрядность такая - contraint'ы были бы гиганстким шагом вперёд, позволили бы существенно сократить «проверяющий» код и оптмизировать выполнение за счёт того, что нам не пришлось бы создавать объект только ради того, чтобы убедиться в консистентности значения.

Оптимальным видится такой вариант использования: переменная, заполняемая из внешнего источника -> наложение constraint'ов -> внутренняя переменная с мета-тегом «пройдён constraint такой-то».

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

Например, во всеми нелюбимом perl'е есть уже львиная доля такой логики: а именно taint mode, когда переменные делятся на «грязные» (из внешних источников) и «чистые». Недостаток - как раз в отсутствии простого механизма наложения ограничений, который бы и «очищал» переменную.

Речь о том, что сами по себе типы данных в компилируемых языках - это сказка о повышении производительности, но никак не о повышении стабильности кода. Потому что если в вашу переменную типа UInt8, которая не может быть за диапазоном [101..143] приехало вдруг 235, а вы это не проверили в рантайме - ну как бы тип данных вас точно не спасёт.

В общем, я же правильно понимаю, что везде подобные вещи реализованы сбоку, а не как встроенное средство самого ЯП?

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

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

Потому что если в вашу переменную типа UInt8, которая не может быть за диапазоном [101..143] приехало вдруг 235, а вы это не проверили в рантайме - ну как бы тип данных вас точно не спасёт.

Во-первых, уже спасло от того, что туда не приехало 0.93+e199. Во-вторых, можно написать класс, который будет проверять. Если язык будет делать ровно то же, что и класс, то особого смысла встраивать в язык нет (на самом деле оптимизатор иногда выиграет от знания диапазонов, но далеко не везде эти диапазоны будут известны заранее).

xaizek ★★★★★
()

Для более древней ады есть anna, гугли. Еще Eiffel, но я его не щупал, только рабинович напел про него по телефону.

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

Речь не только о диапазонах, проверки могут быть любой сложности. Суть в декларативности описания и встроенном в языке механизме очистки данных, позволяющем спокойно работать с тем, что уже точно содержит разумные значения.

DRVTiny ★★★★★
() автор топика

Гугли refinement types. Liquid Haskell вполне подходит.

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

Refinement types — это один из подходов. Можно, например, взять зависимые типы, которые тоже позволяют делать такие штуки. Но с первыми работать немножко проще чем со вторыми.

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

в императивщине, а не пролог или хачкель.

Haskell, как известно, лучший императивный язык.

Begemoth ★★★★★
()

Ну даже в джаве аннотациями и сеттерами это как один орган об асфальт. Ты вообще программировать умеешь? Хотя если кроме C ничего не видел, может для тебя это и крутая фича языка.

anonymous
()

позволили бы существенно сократить «проверяющий» код

Каким образом? Проверяющий код на входящие данные никуда не денется, иначе твоя идея не будет работать.

и оптмизировать выполнение за счёт того, что нам не пришлось бы создавать объект только ради того, чтобы убедиться в консистентности значения.

Куда ты собрался класть проверенное значение, если «объект» не создаётся?

Оптимальным видится такой вариант использования: переменная, заполняемая из внешнего источника -> наложение constraint'ов -> внутренняя переменная с мета-тегом «пройдён constraint такой-то».

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

Речь о том, что сами по себе типы данных в компилируемых языках - это сказка о повышении производительности, но никак не о повышении стабильности кода. Потому что если в вашу переменную типа UInt8, которая не может быть за диапазоном [101..143] приехало вдруг 235, а вы это не проверили в рантайме - ну как бы тип данных вас точно не спасёт.

Твоя идея описывает то же самое: проверку значений в рантайме. Я не понимаю что именно она меняет.

В общем, я же правильно понимаю, что везде подобные вещи реализованы сбоку, а не как встроенное средство самого ЯП?

Как ты предлагаешь это встроить в язык? А если я захочу встроенную в язык проверку, что в строке - имя поняши, то что мне делаеть? Повторю: в нормальных языках то, что ты хочешь, делается при помощи системы типов или какого-нибудь другого встроенного абстрактного механизма.

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)

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

Причём всё это существует только в компил тайме, никаких накладных расходов в рантайме.

hateyoufeel

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 1)

Design by contract (DbC) © спасёт «отца контрактного программирования»?

quickquest ★★★★★
()

«D» https://dlang.org/spec/contracts.html

long square_root(long x)
in
{
    assert(x >= 0);
}
out (result)
{
    assert((result * result) <= x && (result+1) * (result+1) > x);
}
do
{
    return cast(long)std.math.sqrt(cast(real)x);
}

anonymous
()

Common Lisp: (or (integer 0 55) (integer 117 200) (member 999))

В CMUCL/SBCL есть механизм, к-рый в статике выводит констрейнты из использований. Я, правда, не знаю, будет ли сложение и умножение уважать эти констрейнты, т.е будет ли сложение чисел в диапазоне давать число в соответствующем диапазоне. Возможно, что да.

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

И нужно учитывать, что если пытаться выводить диапазоны из типов по всей дороге, то сложность начинает расти экспоненциально. Поэтому в CMUCL в какой-то момент твой диапазон «эвристически» может быть заменён на просто (integer 0 999) , или просто процесс выведения констрейнтов закончится по таймауту и останется вообще просто integer, или даже «любой объект».

den73 ★★★★★
()

erlang guards [+ pattern matching]

anonymous
()

Речь о том, что сами по себе типы данных в компилируемых языках - это сказка о повышении производительности, но никак не о повышении стабильности кода. Потому что если в вашу переменную типа UInt8, которая не может быть за диапазоном [101..143] приехало вдруг 235, а вы это не проверили в рантайме - ну как бы тип данных вас точно не спасёт.

Если переменная типа UInt8 не может быть за диапазоном [101..143], то её тип — не UInt8, а MyUInt8WithBlackjackAndWhores. А если ты пытаешься вместо того, чтобы определить свой тип со встроенными контрактами, использовать встроенный, обмазывая его рантайм-проверками, то ССЗБ.

Virtuos86 ★★★★★
()
Последнее исправление: Virtuos86 (всего исправлений: 1)

Любой язык имеющий дешёвые newtypes. Особенно хорошо, если ещё и солвер прикручен, например, liquid Haskell, там это даже эффективно будет и не будет рантайм проверок на каждый чих, а только там где не доказать.

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

никаких накладных расходов в рантайме уже не получится. вот есть у тебя Int3_8 а операция + вернет уже Int6_16, не говоря уже о более сложных констрейнтах, итого во-первых нужно менять стандартные классы типов, во-вторых рантайме проверки возможно все равно нужны. Чтобы их исключить желательно уже какие солверы использовать.

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

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

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

Что там может быть сложного? Я с 0 уровня за 2 недели по книжке все изучила. До этого про лишпы только здесь слышала.

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

Что там может быть сложного?

Да всё там сложное :-) Как и в хацкеле :-) Сама функциональная парадигма, когда на ней начинают зацикливаться пионэры разных мастей, начинает выкручивать не только руки, но и мозги :-) И вместо того, чтобы изложить свою мысль естественным образом, начинаются водворяться воздушные замки строго по теориям функционального программирования и мысли записываются не на языке, близком к тому, на котором человек думает, а на языке в угоду теории ФП :-) В итоге, результат часто околонулевой :-)

Хочешь, я приведу тебе пример хорошего языка, который действительно можно довольно хорошо познать за 2 недели? :-) Этот язык называется Go :-) Простой в написании и чтении, быстрый в рантайме :-) Конечно, с ним не так можно тешить своё ЧСВ, как с цацкелем или кложуркой, но на нём можно быстро и без вывиха мозга излагать свои мысли и заставлять компьютер делать то, что тебе нужно :-)

А что тебе ещё от компьютера нужно? :-) Неужели просто цацкель с кложуркой ради цацкеля с кложуркой? :-) Или нужно, чтобы он дело делал? :-)

anonymous
()

Это то же самое, что типы данных, но для логики приложения, а не логики исполнителя (CPU).

У тебя превратное представление о типах.
Правильно было бы сказать «это такой динамический аналог зависимых типов».

Потому что если в вашу переменную типа UInt8, которая не может быть за диапазоном [101..143] приехало вдруг 235, а вы это не проверили в рантайме - ну как бы тип данных вас точно не спасёт.

https://www.idris-lang.org/docs/current/prelude_doc/docs/Prelude.Nat.html#Pre...

quantum-troll ★★★★★
()
Ответ на: комментарий от Virtuos86

Как низко ты пал — уже советуешь Go как хороший язык.

Не принимай близко к сердцу :-) Это всего лишь язык :-) А объективность факта в том, Go прост со всех сторон и довольно быстр :-) Этим он действительно хорош :-)

Вот скажи, зачем сознательно писать что-то на сложных хаскелях с кложурками, когда можно то же самое, но гораздо быстрее и понятнее изложить на го? :-) Ну ладно цепепе, который адепты изо всех сил отличают от всего остального с помощью заезженной пластинки про отсутствие GC :-) Но здесь то :-) Можешь мне объяснить, что именно у тебя против Go, что ты не хочешь «так низко упасть»? :-)

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

lack of compile-time generics? Да это сразу в топку такие недоязычки, в 21 веке живем или где? Даже у убогого во всех отношениях С++ есть шаблоны.

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

Куда туда

В измененный базовый пит

Типы данных, используемые в программе, должны отвечать некой предметной области, определяемой программой. Есть термин DSL, по аналогии можно сказать, что типам данных в программе желательно быть DST. Патчить встроенные типы, сужая их область применения от обобщенной до какой-то конкретной, имеет смысл только в каких-то особенных случаях. Рубистам нравится, ну это их дело.

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

lack of compile-time generics? Да это сразу в топку такие недоязычки, в 21 веке живем или где?

Живём в 21 веке :-) Почему из-за «lack of compile-time generics» нужно «сразу в топку такие недоязычки»? :-)

Даже у убогого во всех отношениях С++ есть шаблоны.

Лол :-) А можно узнать, как убогие во всех отношениях шаблоны цепепе помогают лично тебе решать конкретные проблемы? :-)

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

что именно у тебя против Go, что ты не хочешь «так низко упасть»?

Если бы ты отрекомендовал его как хороший, практичный инструмент, вопросов бы не возникло. Но именно как ЯП он убог по современным меркам. Все твои комплименты относятся именно к качествам Go как инструмента. Попробуй похвалить его как ЯП.

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

Если бы ты отрекомендовал его как хороший, практичный инструмент, вопросов бы не возникло.

Я уже неоднократно говорил здесь, что язык - это набор синтаксических конструкций, скрепляемых в соответствии с грамматикой, для изложения технического задания (или фантазий), чтобы его выполнял компьютер :-) Мне непонятно, когда язык называют инструментом :-) Инструментами можно назвать IDE, редакторы, линтеры, форматтеры - в общем, утилиты :-)

Но именно как ЯП он убог по современным меркам.

Не мог бы ты меня просветить насчёт современных мерок в части языков программирования? :-)

Попробуй похвалить его как ЯП.

Хвалю Го как ЯП - хороший язык, пользуйтесь на здоровье, не стесняйтесь :-)

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

Не убогие шаблоны, а убогий С++. А шаблоны там нормальные.

А, ну всё понятно :-) «Ева, я любила тебя, твои пластинки слушала я» :-) Лол :-)

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

Слив засчитан.

Ну, как бы, да :-) Объяснить то ты толком ничего не смогла, кроме как «в топку недоязычки», «убогий C++», «но шаблоны в C++ нормальные» :-) Кто его знает, что там у тебя на уме :-) Так что засчитан, правильно заметила :-) Лол :-)

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

Убочий он в том смысле, что неоправдано переусложнен.

Сравни:

(reduce f coll)(reduce f val coll)
f should be a function of 2 arguments. If val is not supplied,
returns the result of applying f to the first 2 items in coll, then
applying f to that result and the 3rd item, etc. If coll contains no
items, f must accept no arguments as well, and reduce returns the
result of calling f with no arguments.  If coll has only 1 item, it
is returned and f is not called.  If val is supplied, returns the
result of applying f to val and the first item in coll, then
applying f to that result and the 2nd item, etc. If coll contains no
items, returns val and f is not called.
и

std::reduce
  C++  Algorithm library 
Defined in header <numeric>
template<class InputIt>
typename std::iterator_traits<InputIt>::value_type reduce(
    InputIt first, InputIt last);
(1)	(since C++17)
template<class ExecutionPolicy, class ForwardIt>
typename std::iterator_traits<ForwardIt>::value_type reduce(
    ExecutionPolicy&& policy,
    ForwardIt first, ForwardIt last);
(2)	(since C++17)
template<class InputIt, class T>
T reduce(InputIt first, InputIt last, T init);
(3)	(since C++17)
template<class ExecutionPolicy, class ForwardIt, class T>
T reduce(ExecutionPolicy&& policy,
         ForwardIt first, ForwardIt last, T init);
(4)	(since C++17)
template<class InputIt, class T, class BinaryOp>
T reduce(InputIt first, InputIt last, T init, BinaryOp binary_op);
(5)	(since C++17)
template<class ExecutionPolicy, class ForwardIt, class T, class BinaryOp>
T reduce(ExecutionPolicy&& policy,
         ForwardIt first, ForwardIt last, T init, BinaryOp binary_op);
(6)	(since C++17)
1) same as reduce(first, last, typename std::iterator_traits<InputIt>::value_type{})
3) same as reduce(first, last, init, std::plus<>())
5) Reduces the range [first; last), possibly permuted and aggregated in unspecified manner, along with the initial value init over binary_op.
2,4,6) Same as (1,3,5), but executed according to policy. This overload only participates in overload resolution if std::is_execution_policy_v<std::decay_t<ExecutionPolicy>> is true
The behavior is non-deterministic if binary_op is not associative or not commutative.
The behavior is undefined if binary_op modifies any element or invalidates any iterator in [first; last], including the end iterator.
Parameters
first, last	-	the range of elements to apply the algorithm to
init	-	the initial value of the generalized sum
policy	-	the execution policy to use. See execution policy for details.
binary_op	-	binary FunctionObject that will be applied in unspecified order to the result of dereferencing the input iterators, the results of other binary_op and init.
Type requirements
-InputIt must meet the requirements of InputIterator.
-ForwardIt must meet the requirements of ForwardIterator.
-T must meet the requirements of MoveConstructible. and binary_op(init, *first), binary_op(*first, init), binary_op(init, init), and binary_op(*first, *first) must be convertible to T.
Return value
Generalized sum of init and *first, *(first+1), ... *(last-1) over binary_op,
where generalized sum GSUM(op, a
1, ..., a
N) is defined as follows:
if N=1, a
1
if N > 1, op(GSUM(op, b
1, ..., b
K), GSUM(op, b
M, ..., b
N)) where
b
1, ..., b
N may be any permutation of a1, ..., aN and
1 < K+1 = M ≤ N
in other words, reduce behaves like std::accumulate except the elements of the range may be grouped and rearranged in arbitrary order

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

А go - это с++ из которого выкинули все нужные вещи, типа шаблонов, метапрограммирования, ооп, исключений и т.д, вкорячив сборщик мусора сомнительной нужности для языка такого низкого уровня.

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

Убочий он в том смысле, что неоправдано переусложнен.

цепепе не переусложнён :-) цепепе просто сложный :-) Из того, что ты показала, не следует, что кложурка легче, чем цепепе :-) Как и не видна вся убогость цепепе :-) А сравнивать API для итераторов статически типизированного цепепе с API кложурки - это такое :-) Понятное дело, что кложурка будет выглядеть более лаконично в данном случае, но это не делает из кложурки лёгкий для понимания или сопровождения язык :-)

anonymous
()

STEP EXPRESS

anonymous
()

Пример constraint'а: «constrX is integer, [0..55],[117..200],999»

Ada, в языке реализовано

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

А go - это с++ из которого выкинули все нужные вещи, типа

шаблонов, метапрограммирования, ооп, исключений Тебе обманули :-) Go - это новый язык, который был спроектирован с нуля :-) А вся вышеперечисленная параша сомнительной нужности была добавлена в C (на котором, к слову, успешно пишут тонны проектов до сих пор), чтобы получился цепепе :-)

Кстати, мне не понятно, какое отношение метапрограммирование имеет к цепепе :-) Что можно наметапрограммировать на цепепе? :-) Если ты под метапрограммированием понимаешь выбор алгоритма по параметру шаблона (в простонародье это называется «использовать трэитсы аля структуры со статическими функциями-членами»), то это не метапрограммирование, а трюк для полиморфизма компайл-тайма :-) Что ещё можно наметапрограммировать на цепепе, кроме пары-тройки вставок constexpr - не ведомо :-) Ты, конечно, можешь мне рассказать про boost.hana, но это никак не пример метапрограммирования :-)

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