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 04.12.2009 16:46:19  

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

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

**** ()
[#]  
www_linux_org_ru

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

template<int n> blablabla

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

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 17:02:26  

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

**** ()
[#] Ответ на: комментарий от www_linux_org_ru 04.12.2009 17:04:59  

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

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

> 2. где закоментированная ошибочная строчка, которую отсеивает твоя система типов? [code] -- main' n i as bs = main' (n-1) (i+1) as (Cons (i^2) bs) [/code] например.

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 17:13:14  
www_linux_org_ru

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

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

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

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 17:14:58  
www_linux_org_ru

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

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

**** ()
[#] Ответ на: комментарий от www_linux_org_ru 04.12.2009 17:23:21  

Так он запускаемый. Я же запускал.

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 17:27:10  
www_linux_org_ru

читать можно не весь массив (хотя хотелось бы), а его длину, и дальше заполнять чем-нить типа i или i*i

**** ()
[#] Ответ на: комментарий от www_linux_org_ru 04.12.2009 17:32:47  

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

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 17:35:45  
www_linux_org_ru

[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 давать такую сигнатуру?

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 17:35:45  
www_linux_org_ru
$ 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 04.12.2009 17:58:40  

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

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

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 17:35:45  
www_linux_org_ru

> 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 04.12.2009 18:05:59  

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

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

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

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

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 18:14:01  
www_linux_org_ru

ага, уже вижу возражения компилятора.

интересно стало. подумаю.

**** ()
[#] Ответ на: комментарий от www_linux_org_ru 04.12.2009 18:27:43  

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

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 18:31:55  
www_linux_org_ru

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

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

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

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 18:31:55  
www_linux_org_ru

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

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

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

**** ()
[#] Ответ на: комментарий от www_linux_org_ru 04.12.2009 19:26:28  

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

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

**** ()
[#] Ответ на: комментарий от www_linux_org_ru 04.12.2009 19:48:31  

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

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

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 18:05:11  

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

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

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

anonymous ()
[#] Ответ на: комментарий от anonymous 04.12.2009 22:17:01  
jtootf
>>-----Цитата---->>

ghci это же интерпретатор

<<-----Цитата----<<

нет

***** ()
[#] Ответ на: комментарий от jtootf 04.12.2009 22:23:39  

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

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

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

anonymous ()
[#] Ответ на: комментарий от anonymous 04.12.2009 22:17:01  

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

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

**** ()
[#] Ответ на: комментарий от anonymous 04.12.2009 22:35:06  

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

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

**** ()
[#] Ответ на: комментарий от anonymous 04.12.2009 22:17:01  

Кстати:

> экзешник

То есть, я для тебя на винду должен пересесть?

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 22:57:11  

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

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

anonymous ()
[#] Ответ на: комментарий от anonymous 04.12.2009 23:06:18  

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

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

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

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 23:18:17  

"g++ -o hello.exe hello.cpp && ./hello.exe" какбэ работает

По делу что будет?

anonymous ()
[#] Ответ на: комментарий от anonymous 04.12.2009 23:49:18  

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

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

**** ()
[#] Ответ на: комментарий от Miguel 04.12.2009 23:18:17  
Legioner

экзешник это executable file имхо

***** ()
[#]  

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

** ()
[#]  

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

** ()
[#] Ответ на: комментарий от Booster 05.12.2009 17:48:52  

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

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

**** ()
[#] Ответ на: комментарий от Miguel 05.12.2009 18:06:43  

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

** ()
[#] Ответ на: комментарий от Booster 05.12.2009 18:13:48  

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

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

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

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

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

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

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

**** ()
[#] Ответ на: комментарий от Miguel 05.12.2009 18:35:47  

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

** ()
[#] Ответ на: комментарий от Booster 05.12.2009 18:37:45  

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

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

**** ()
[#] Ответ на: комментарий от Miguel 05.12.2009 18:35:47  

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

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

***** ()
[#] Ответ на: комментарий от tailgunner 05.12.2009 18:47:50  

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

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

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

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

**** ()
[#] Ответ на: комментарий от Miguel 05.12.2009 18:46:30  

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

** ()
[#] Ответ на: комментарий от Miguel 05.12.2009 18:51:37  

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

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

Это пример.

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

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

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

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

***** ()
[#] Ответ на: комментарий от Booster 05.12.2009 18:52:29  

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

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

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

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

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

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

**** ()
[#] Ответ на: комментарий от tailgunner 05.12.2009 18:57:10  

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

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

**** ()
[#] Ответ на: комментарий от Miguel 05.12.2009 19:06:21  

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

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

***** ()
[#] Ответ на: комментарий от Miguel 05.12.2009 19:05:20  

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

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

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

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

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

** ()