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'. Наткнулся на бесконечное разворачивание шаблонов. Товарищи плюсисты, как делаются подобные вещи?

★★★★★

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

этап компиляции как бы предшествует рантайму.. шаблоны обрабатываются на этапе компиляции..

то есть ты хочешь чтобы был чек в рантайме.. Который как бы сгенерировал автоматически компилятор.. Степень этого «автоматически» имхо довольно субьективна.. Кто-то скажет что достаточно if (). Можно макрос какой-то определить.. можно воспользоваться массивами переменного размера из C99 — так чтобы кол-во элементов было равно нулю iff размеры массивов не равны..

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

> то есть ты хочешь чтобы был чек в рантайме..

Нет, я не хочу чек в рантайме. Я хочу статическую гарантию. Которую я получаю в хаскеле. Рантаймовые чеки, пусть и автоматические, меня мало волнуют.

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

> Товарищи плюсисты, как делаются подобные вещи?

template<int n> blablabla

а твой код я не понял — 1. где ввод-вывод? 2. где закоментированная ошибочная строчка, которую отсеивает твоя система типов?

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

Короче, похоже, я хочу невозможного. Длина вектора может быть любой, поэтому типов должно быть потенциально бесконечное количество, а такое C++ не умеет.

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

> 1. где ввод-вывод?

Это всего лишь пример, нафиг его переусложнять?

2. где закоментированная ошибочная строчка, которую отсеивает твоя система типов?

[code] -- main' n i as bs = main' (n-1) (i+1) as (Cons (i^2) bs) [/code] например.

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

> Короче, похоже, я хочу невозможного. Длина вектора может быть любой, поэтому типов должно быть потенциально бесконечное количество, а такое C++ не умеет.

Твое мнение «что с++ не умеет» можно вообще не рассматривать.

Хочешь аналог твоего кода на плюсах — пиши ввод-вывод, пример использования с входными данными, в комментарии «эта строчка компилируется, эта отвергается» (а лучше ifdef и строку компиляции хаскеля с вызовом препроцессора). Тогда будет плюсовый аналог или я скажу «не умею».

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

> Это всего лишь пример, нафиг его переусложнять?

без полноценного запускаемого примера тебя легко можно понять неправильно

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

Ну дык. Загружаешь это дело в ghci, и затем даёшь команду main <длина массива>. Один массив заполняется числами 1,3,5,7,9..., другой - числами 0,1,4,9,16..., одной и той же длины.

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

[code] $ cat miguel.hs 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) $ ghc miguel.hs /usr/lib/ghc-6.6/libHSrts.a(Main.o): In function `main': (.text+0x16): undefined reference to `__stginit_ZCMain' /usr/lib/ghc-6.6/libHSrts.a(Main.o): In function `main': (.text+0x3b): undefined reference to `ZCMain_main_closure' collect2: ld returned 1 exit status [/code]

а разве можно main давать такую сигнатуру?

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от Miguel
$ cat miguel.hs
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)
$ ghc miguel.hs 
/usr/lib/ghc-6.6/libHSrts.a(Main.o): In function `main':
(.text+0x16): undefined reference to `__stginit_ZCMain'
/usr/lib/ghc-6.6/libHSrts.a(Main.o): In function `main':
(.text+0x3b): undefined reference to `ZCMain_main_closure'
collect2: ld returned 1 exit status

а ты уверен, что main :: Integer -> Integer это нормально?

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

Ты попытался сделать сразу целую программу. Для этого нужен модуль Main и функция Main.main :: IO().

Вместо ghc используй ghci, и будет тебе щасте.

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

> main' n i as bs = main' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)

и еще я подозреваю, что на

main' n i as bs = main' (n-1) (i+1) (Cons (2*i) (Cons (2*i+1) as)) (Cons (i^2) bs)

у компилятора возражений не будет, не?

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

> и еще я подозреваю, что на

main' n i as bs = main' (n-1) (i+1) (Cons (2*i) (Cons (2*i+1) as)) (Cons (i^2) bs)

у компилятора возражений не будет, не?

Будут. Типы не совпадут.

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

Проблема в том, что подобный подход требует параметрического полиморфизма в системе типов. В C++ этого нет; параметрический полиморфизм эмулируется МЕТАПРОГРАММИРОВАНИЕМ (т.е., в случае C++ - шаблонами). По сути дела - макросами на стероидах. Но система типов о нём ничего не знает. Потому говно и получается.

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

я все-таки продолжу неторопливо

мне не понятно, зачем нужна твоя функция main, точнее, зачем строить внутри рекурсии такие типы: чтобы потом обратно эти типы разобрать/разрушить *строго последовательно*? это бессмысленно. эти типы не видны снаружи, так что все это без потери читабельности преобразуется в более простой с точки зрения типов вариант.

так что это (наличие очевидного упрощения) мне мешает пересказать твой подвиг на плюсах (хотя я подумаю), видимо задачу надо как-то усложнить

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

> Проблема в том, что подобный подход требует параметрического полиморфизма в системе типов. В C++ этого нет; параметрический полиморфизм эмулируется МЕТАПРОГРАММИРОВАНИЕМ

в плюсах в таком случае есть 2 варинта: 1. написать свою типобезопасную реализацию vtbl с ужасным синтаксисом 2. хитрожопо юзать предоставляемую языком vtbl

видимо, я начну с п.2, правда сама по себе задача «максимально дословно пересказать код» мне не нравится — он явно будет раздутый

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

> мне не понятно, зачем нужна твоя функция main, точнее, зачем строить внутри рекурсии такие типы: чтобы потом обратно эти типы разобрать/разрушить *строго последовательно*? это бессмысленно. эти типы не видны снаружи, так что все это без потери читабельности преобразуется в более простой с точки зрения типов вариант.

Ну вот ты сам попробовал нарушить эту типизацию, и получил ошибку компиляции.

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

> в плюсах в таком случае есть 2 варинта: 1. написать свою типобезопасную реализацию vtbl с ужасным синтаксисом 2. хитрожопо юзать предоставляемую языком vtbl

Опять за рыбу деньги... Ну как, КАК рантаймовая фича должна решить вопрос с компиляцией?

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

ghci это же интерпретатор, как вы им умудряетесь компилировать?

Вот как соберете с помощью ghc экзешник без рантайм проверок — можете сравнивать с по-настоящему компилируемым c++.

Хаскель не знаю.

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

>GHCi is GHC's interactive environment, in which Haskell expressions can be interactively evaluated and programs can be interpreted.

Ну не важно есть у этого интерпретатора JIT или нету, это все равно интерпретатор. Программа интерактивно перекомпилируется.

У него там не одна рантайм проверка, а over 9000 (или сколько там современные компиляторы содержат).

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

> Вот как соберете с помощью ghc экзешник без рантайм проверок — можете сравнивать с по-настоящему компилируемым c++.

ghci - компилятор. Он компилирует выданный файл, после чего позволяет вызывать топлевел-функции из него.

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

> У него там не одна рантайм проверка

Ещё раз: рантайм проверок типизации у него НЕТ.

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

Так и запишем, компилятор для линукса недопортирован и исполнимые файлы делать не умеет.

Да, придется. Иначе это не равные условия. Можете найти интерактивный C++ компилятор, я не возражаю.

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

> Так и запишем, компилятор для линукса недопортирован и исполнимые файлы делать не умеет.

Палишься, родимый. Для сведения: экзешник - это файл с расширением .exe, каковое является стандартным для винды (а ранее - доса), но практически не используется в линуксе или макоси.

Или погоди, может, ты хочешь экзешник для Mono? Haskell.NET, вроде, пока не существует.

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

ghc --make -o main main.hs && ./main тоже.

По делу что будет? Напоминаю, делом является обсуждение возможностей двух языков - Haskell и C++.

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

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

Booster ★★
()

Переменные m и n инициализируются в рантайме. На стадии компиляции нужно проверить равны ли они. Или это бред или условие не полное.

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

> Не понимаю как компилятор может гарантировать то, что происходит в рантайме?

Это называется «статическая типизация».

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

>статическая типизация
Вы хотите запутать?
Статическая типизация это когда переменная получает тип во время объявления: int i;
Динамическая в момент присваивания: i = 5;
При чём здесь это?

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

> Статическая типизация это когда переменная получает тип во время объявления: int i;

Динамическая в момент присваивания: i = 5;

При чём здесь это?

Ни фига себе каша у вас в голове.

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

Так вот, при статической типизации компилятор гарантирует, что в рантайме тип данного выражения будет именно таким. Точнее, он это гарантирует при сильной статической типизации. При слабой типизации, вроде как в C++, компилятор вместо «да, точно так» говорит «наверное, так».

К тому, есть ли в программе объявления переменных, это особого отношения не имеет.

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

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

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

> Теже яйца, только в профиль. Вы по-моему уходите от темы.

Напротив. Вы каким-то образом связали статическую типизацию с наличием объявления переменной, после чего тихой сапой пропустили в моём ответе слова «компилятор гарантирует, что в рантайме...»

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

> Статическая типизация - это когда тип любого выражения (в частности, любой переменной) известен при компиляции.

Думаю, он спрашивает примерно следующее: если у нас есть _тип_ «число от 1 до 5», как компилятор проверит, что значение, которое вернула процедура ввода, действительно содержит число от 1 до 5?

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

> если у нас есть _тип_ «число от 1 до 5»

А у нас есть такой тип? Я не так много языков знаю, где подобный тип действительно возможен.

как компилятор проверит, что значение, которое вернула процедура ввода, действительно содержит число от 1 до 5?

Опять же: например, если мы введём с клавы любое число, а потом сделаем if. Но это уже к зависимым типам, о которых в данном топике речь не идёт.

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

Вы несёте бред. Не константное(изменяемое) значение ни какой комплятор ни когда не сможет проверить. При чём здесь типы - понятно только вам.

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

>> если у нас есть _тип_ «число от 1 до 5»

А у нас есть такой тип?

Это пример.

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

В Си++ делается тривиально, но за счет динамической проверки.

например, если мы введём с клавы любое число, а потом сделаем if. Но это уже к зависимым типам

Понятно. Лично меня такое устроило бы.

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

> Вы несёте бред.

Просто я знаю больше вас.

Не константное(изменяемое) значение ни какой комплятор ни когда не сможет проверить. При чём здесь типы - понятно только вам.

Ещё раз, по буквам.

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

В своём первом сообщении данного топика я, фактически, привёл пример того, как массивы разной длины оказываются разных типов, а массивы одной длины - одного типа.

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

> В Си++ делается тривиально, но за счет динамической проверки.

В C++ от компилятора получить какие-либо гарантии невозможно вообще, там типизация довольно слабенькая.

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

> В C++ от компилятора получить какие-либо гарантии невозможно вообще

Это тема холивора, но я недостаточно хорошо понимаю, что делает unsafePerformIO, чтобы участвовать в таком холиворе :)

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

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

В своём первом сообщении данного топика я, фактически, привёл пример того, как массивы разной длины оказываются разных типов, а массивы одной длины - одного типа.

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

Просто я знаю больше вас.

Это ваше субъективное мнение.

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