LINUX.ORG.RU

Равные длины векторов


0

0

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

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

Нам хочется, чтобы компилятор гарантировал нам, что длины этих векторов одинаковы. Чтобы если мы где-то наглючили и породили вектора разной длины, программа не скомпилировалась бы.

Пример того, как подобные вещи могут делаться в Хаскеле:

module Test where
data Nil = Nil
data Cons a = Cons Integer a
class ScalarProduct a where scalarProduct :: a -> a -> Integer
instance ScalarProduct Nil where scalarProduct Nil Nil = 0
instance ScalarProduct a => ScalarProduct (Cons a) where scalarProduct (Cons n1 a1) (Cons n2 a2) = n1 * n2 + scalarProduct a1 a2
main :: Integer -> Integer
main n = main' n 0 Nil Nil where
  main' :: ScalarProduct a => Integer -> Integer -> a -> a -> Integer
  main' 0 _ as bs = scalarProduct as bs
  main' n i as bs = main' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)
Здесь, получается, ТИП списка содержит в некотором роде его ДЛИНУ, то есть списки разной длины будут разных типов, списки одной длины - одного типа.

Я попытался воспроизвести то же самое в плюсах, используя шаблоны вместо Cons и main'. Наткнулся на бесконечное разворачивание шаблонов. Товарищи плюсисты, как делаются подобные вещи?

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

>Вы спросили, как компилятор может гарантировать что-то в рантайме. Я вам ответил: при помощи статической типизации. Статическая типизация и есть способ гарантировать корректное поведение значений в рантайме.
Блин, не в рантайме происходит проверка типов.

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

> Задавайте длину массива константно, тогда компилятор будет знать его размер и можно будет проверить.

Один из вариантов. Но это очень сильное ограничение, которое меня не устраивает.

Конечно, но если решите её, то решите и свою.

Несомненно, но я могу решить свою задачу, не решая вашу. Поэтому я буду решать свою, а вашу предоставлю вам. Удачи.

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

> Блин, не в рантайме происходит проверка типов.

Я знаю. И?

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

Разве тему начинал я? Только не понимаю как это на хаскеле сделано. Я Вас в первом посте просил объяснить, но Вы мне так и не ответили. Ответ статическая типизация не принимается. Я так могу объявить два объекта одного типа и сказать - вот оно.

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

> Только не понимаю как это на хаскеле сделано. Я Вас в первом посте просил объяснить, но Вы мне так и не ответили.

Вообще-то вы задали гораздо более общий вопрос: «как компилятор может гарантировать то, что происходит в рантайме?» На него правильный ответ именно таков, какой и был дан.

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

Вы и сами можете соорудить нечто подобное в C++, например, так: [code] template<int n> struct Vector { int value; Vector<n-1> tail; } [/code] При этом вы столкнётесь с двумя трудностями: 1) техническая трудность - внутри шаблона идентификатор Vector на самом деле означает Vector<n> (убил бы за такую придумку), и 2) принципиальная трудность - вы не сможете написать шаблонную функцию, которая в варианте для Vector<n> может использовать вариант для Vector<n+1>, поскольку тогда получите бесконечное разворачивание шаблонов. Эта трудность в Хаскеле не существует, поскольку там вместо шаблонов (представляющих собой, по сути, макросы на стероидах) имеется полноценный параметрический полиморфизм на уровне системы типов.

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

То что я могу соорудить я и так знаю, я спрашиваю как вы решили свою задачу на хаскеле? А именно, проверку неконстантного значения на этапе компиляции. Размер у вас на этапе компиляции не известен, у вас в стартовом посте именно так написано. Мы оба вроде на русском языке пишем, а вы никак понять не можете, что я от Вас хочу.

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

То что я могу соорудить я и так знаю, я спрашиваю как вы решили свою задачу на хаскеле?

Именно это я вам и объяснял.

А именно, проверку неконстантного значения на этапе компиляции.

Ещё раз, для тупых: такая задача не ставилась в этом топике никем, кроме вас. Если не верите, можете сделать Ctrl-F и поискать в моём начальном посте слово «проверка».

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

> И Ваши слова, что вы её решили, так что будь-те добры поделитесь сим сокральным знанием.

«сакральный» пишется через «а». </grammar-nazi>

Уже поделился выше по топику:

Чтобы программа, допускающая, что массивы могут оказаться разной длины, не скомпилировалась. Я показал, как это делается, а мой оппонент www_linux_org_ru показал строчку, нарушающую этот инвариант, и убедился, что она не компилируется.

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

Хорошо, давайте так:

Нам хочется, чтобы компилятор гарантировал нам, что длины этих векторов одинаковы. Чтобы если мы где-то наглючили и породили вектора разной длины, программа не скомпилировалась бы.

Как Вы это сделали на хаскеле, если размер заранее не известен? Хаскель я не знаю, и код понять не могу. Объяснить можете на конец?

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

Как Вы это сделали на хаскеле, если размер заранее не известен?

Так же, как и в плюсах программа, содержащая функцию

template<int n> int ScalarProduct(Vector<n> first, Vector<n> second) {...}
не могла бы быть скомпилирована, если бы на вход ScalarProduct были бы поданы значения, например, типов Vector<1> и Vector<2>.

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

Ничерта вы не поделились, размер константный.

Размер НЕ константный; функция main имеет тип Integer -> Integer, и она как раз использует число, приходящее ей на вход, для того, чтобы построить два массива соответствующей длины (а потом посчитать их скалярное произведение).

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

>Так же, как и в плюсах программа, содержащая функцию
template<int n> int ScalarProduct(Vector<n> first, Vector<n> second) {...}
n можно задать пользовательским вводом?

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

>Размер НЕ константный; функция main имеет тип Integer -> Integer, и она как раз использует число, приходящее ей на вход, для того, чтобы построить два массива соответствующей длины (а потом посчитать их скалярное произведение).
Ещё раз спрашиваю, как? В С++ параметры шаблонов константы по определению. И код С++ не в кассу. Вы пишите, что на Хаскеле это возможно. Как?

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

n можно задать пользовательским вводом?

Напрямую - нет. А, например, досчитать индукцией - можно (что и сделано в хаскелевском исходнике). Приблизительно так:

int main(int n){
  Vector<0> first;
  Vector<0> second;
  return _main(n,first,second);
}
template<int k> int _main(int n, Vector<k> first, Vector<k> second){
  if (n == 0) {
    return ScalarProduct<k>(first, second);
  } else {
    return _main(n-1, Vector<k+1>(2*k+1, first), Vector<k+1>(k*k, second));
  }
}
Другое дело, что в плюсах хрен с два что-то такое скомпилится - мы получим то самое пресловутое бесконечное разворачивание шаблонов.

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

>А, например, досчитать индукцией - можно (что и сделано в хаскелевском исходнике).
Cгенерировать всё множество типов и радоваться, что у нас ffffffff функций и классов. Извращение.

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

Всем же понятно, что это не более чем академический интерес

нет

Всерьёз эту лабуду никто не воспринимает.

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

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

Cгенерировать всё множество типов и радоваться, что у нас ffffffff функций и классов.

НЕТ. Сгенерировать ОДИН полиморфный тип.

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

не худший вариант на самом деле

Да я так просто спросил.

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

>Сгенерировать ОДИН полиморфный тип.

То есть все эти проверки будут всеже рантаймовыми хотя и неявными?

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

Про бигнумы подкол проехали, не интересно. Про полиморфные типо поподробнее, как оно будет реализовано?

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

То есть все эти проверки будут всеже рантаймовыми хотя и неявными?

НЕТ. Параметрический полиморфизм не требует рантаймовых проверок. Рантаймовые проверки (неявные) требуются в случае ad-hoc полиморфизма.

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

И всё же поподробнее про реализацию. Сколько вызовов, методов, полей и т.д? Это нормально для функционального программирования?

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

Про полиморфные типо поподробнее, как оно будет реализовано?

Примерно так же. См. исходный пост:

data Cons a = Cons Integer a 
Это значит, что тип (один!) Cons имеет параметр - тип a - который может быть любым вообще. В грубой эмуляции, принятой в плюсах, это означает примерно
template<class a> class Cons {
public:
  int ...;
  a ...;
}
При этом типы Cons a и Cons b совпадают тогда и только тогда, когда совпадают a и b. Поскольку это именно ТИПЫ, а не шаблоны типов, и поскольку рантайму на типы вообще плевать, никакого «разворачивания» не требуется.

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

Сколько вызовов, методов, полей и т.д?

Вызовов чего?

Методы - термин из ООП и к данному случаю неприменим.

Полей - в данном случае два.

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

>Другое дело, что в плюсах хрен с два что-то такое скомпилится - мы получим то самое пресловутое бесконечное разворачивание шаблонов.

Может всеже рассуждать с позиций конструктивизма: что мы вообще можем проверить в статике. Если мы считаем сумму чисел с размерностями m и n бит, то мы должны требовать размерности max(m,n) + 1 бит для гарантированного влезания туда результата. Для вычитания это будут те же max(m,n) + 1 бит, но дополнительный бит тут будет битом знака. Умножение - m + n бит. Извлечение квадратного корня потребует чтобы результат включал в себя класс комплексных чисел, а деление - рациональных. Итд в том же роде - результат любой операции всегда требует более широкий класс чисел чем любой из ее аргументов, то есть для реализации этой идеи мы будем вынуждены либо заужать типы входных данных до степени практической неприменимости либо экспоненициально расширять типы результатов тоже до практической неприменимости. Иначе гарантировать непротиворечивость программерских представлений о типах данных *в статике* мы не можем.

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

Итд в том же роде - результат любой операции всегда требует более широкий класс чисел чем любой из ее аргументов,

А, вы тоже про бигнумы не слышали.

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

>Параметрический полиморфизм не требует рантаймовых проверок

Как ты будешь инстанциировать параметрический тип значением переменной а не константы?

Рантаймовые проверки (неявные) требуются в случае ad-hoc полиморфизма.

Вызов виртуального метода - это статика или ad-hoc полиморфизм?

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

Давайте всё же не переходить на личности

да не вопрос. а давайте вы ответной мерой доброй воли перестанете обобщать своё мнение на всех присутствующих?

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

Как ты будешь инстанциировать параметрический тип значением переменной а не константы?

Никак не буду. И не делаю, как легко видеть.

Вызов виртуального метода - это статика или ad-hoc полиморфизм?

Само понятие «виртуальный метод» означает, что используется ООП, причём в самом уродском варианте (C++). Следовательно - только ad-hoc.

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

>> Итд в том же роде - результат любой операции всегда требует более широкий класс чисел чем любой из ее аргументов,

А, вы тоже про бигнумы не слышали.

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

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

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

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

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

Как правило, проще с них начинать. Использование не-бигнумов есть premature optimization.

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

>Вызовов чего?
Вызовов функций конечно.

Методы - термин из ООП и к данному случаю неприменим.

Допустим, но полиморфизм как правил реализуется через некое множество функций/методов.

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

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