OberonCore
https://forum.oberoncore.ru/

Haskell подсчитать длину списка.
https://forum.oberoncore.ru/viewtopic.php?f=72&t=1707
Страница 2 из 5

Автор:  Валерий Лаптев [ Вторник, 23 Июнь, 2009 11:58 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Info21 писал(а):
Alexey Veselovsky писал(а):
... Заодно попробовал немножно с ним поэкспериментировать.
Обязательно нужно. Для усиления любви к Оберону.

Там есть предмет для улучшения.
:)
У меня почти сварилось в голове, но пока некогда выписывать в файцл... :)
Накропаю для конференции статью - тут положу.

Автор:  Alexey Veselovsky [ Понедельник, 06 Июль, 2009 11:07 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Кстати, а почему в prelude функция length вот такая вот:
Код:
length           :: [a] -> Int
length []        =  0
length (_:l)     =  1 + length l


Вроде ж выяснили что это не оптмально и может привести к бяке.
Или же вот тут : http://www.haskell.ru/standard-prelude.html приводится не её реальная реализация, а её наивная реализация?

Автор:  Geniepro [ Понедельник, 06 Июль, 2009 11:40 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Функция Prelude.length компилятора GHC точно не приводит к бяке. Вот как она там реализована:
Код:
-- | 'length' returns the length of a finite list as an 'Int'.
-- It is an instance of the more general 'Data.List.genericLength',
-- the result type of which may be any kind of number.
length                  :: [a] -> Int
length l                =  len l 0#
  where
    len :: [a] -> Int# -> Int
    len []     a# = I# a#
    len (_:xs) a# = len xs (a# +# 1#)
(решётки после констант, операций, типов и их конструкторов означают, что используются низкоуровневые типы данных и операции над ними).

В описании стандарта Хаскелла-98 этот вариант приведён, видимо, просто для того, что бы показать максимально простое и понятное (наивное) опеределение этой функции...

ЗЫ. Кстати, вот то, что они использовали для списка имя l -- я считаю крайне неудачным, так как трудно отличить от единицы...

Автор:  Alexey Veselovsky [ Вторник, 07 Июль, 2009 14:15 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Ещё вопрос про списки. Конкотенация. Допустим хотим склеить два списка. В императивщине голимой для этого нам потребуется лишь изменить одну (в случае односвязного) или две (в случае двусвязного) ссылки. Т.е. операция очень дешева. Ну и вообще вставка чего-либо в произвольное место списка крайне дешева.

Как с этим у хаскеля? Операция ++ там не приведёт ли к созданию нового списка путем дублирования старых двух?

Насколько это дешево?

Автор:  Geniepro [ Вторник, 07 Июль, 2009 15:38 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Вообще, вопрос интересный, и, как обычно, всё сильно зависит от задачи.

Во-первых, в Хаскелле функция concat склеивает подсписки в списке, содержащем списки, т.е. concat :: [[a]] -> [a]
Операция (++) (append) склеивает два списка, т.е. (++) :: [a] -> [a] -> [a]

Если так подумать, то даже в строгом языке типа OCaml нет совершенно никакой нужды выделять под список zs память, такую же, как у обоих списков xs и ys. В худшем случае -- столько же памяти, как у xs, в неё копируется содержимое списка xs, но в последнем элементе записывается не NULL, а указатель на начало списка ys...

В ленивых языка всё не так просто. Вот такой простейший пример:
Код:
xs = [1..500000]
ys = [500001..1000000]
zs = xs ++ ys

main = do
    print $ length zs
Несмотря на то, что тут три списка, два из которых по полмиллиона элементов и занимают в памяти примерно 4 МБт каждый, в памяти программа занимает стандартные 2 МБт, а профилировщик говорит, что реально в памяти сидело полезных данных этой задачи 2 кБт. Т.е. тут компилятор соптимизировал и вообще не создавал никаких списков.

Стоит усложнить задачу:
Код:
xs = [1..500000]
ys = [500001..1000000]
zs = xs ++ ys

main = do
    print $ length xs
    print $ length ys
    print $ length zs
    print $ last   xs
    print $ last   ys
    print $ last   zs
Потребление памяти резко подскакивает (списки вынуждены остаться в памяти), но эксперименты показывают, что на список zs дополнительной памяти не тратится...

Тут как получается: список zs на самом деле является не списком, а функцией, которая вычисляется по необходимости:
Код:
(++) :: [a] -> [a] -> [a]
[]     ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
Понадобились элементы, которые попали в zs из первого списка -- используются элементы первого списка, т.е. xs. Кончился xs -- начались использоваться элементы второго списка, ys.
Если по каким-то причинам список xs является бесконечным, то до ys дело так и не дойдёт:
Код:
xs = [1..]
ys = [500001..1000000]
zs = xs ++ ys

main = do
    print $ head $ drop 1000000 zs
Память на списки опять не выделяется.
В таком варианте
Код:
xs = [1..]
ys = [500001..1000000]
zs = xs ++ ys

main = do
    print $ head $ drop 1000000 zs
    print $ head $ tail $ drop 1000000 zs
выделяется память под первые 1000001 элементов списка xs, дополнительных затрат нет...

Автор:  Geniepro [ Вторник, 07 Июль, 2009 15:52 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Alexey Veselovsky писал(а):
Допустим хотим склеить два списка. В императивщине голимой для этого нам потребуется лишь изменить одну (в случае односвязного) или две (в случае двусвязного) ссылки. Т.е. операция очень дешева.
Однако при этом Вы теряете оригинальное содержимое первого списка (или даже обоих в случе двусвязных списков). Я разделяю мнение, что большая часть ошибок возникает от неправильного изменения данных -- запороли не вовремя какие-то переменные и попали не на Луну, а неизвестно куда вообще...
Alexey Veselovsky писал(а):
Ну и вообще вставка чего-либо в произвольное место списка крайне дешева.
Вот тут уже придётся создавать новый список, уоторый отличается от оригинального тем, что в нём в середине новые данные. Но опять же ленивость может самортизировать расходы памяти на такой новый список. It depends...

Вот это вот наши оберонщики и не любят в Хаскелле -- то, что они не могут сходу предсказать поведение программы. Хорошо это или плохо -- спорный вопрос. Я считаю, что не стоит суетиться над решением проблемы, пока она не возникнет, иначе получим premature optimization...

Автор:  Alexey Veselovsky [ Вторник, 07 Июль, 2009 16:20 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Ну, тут штука такая... Лично мне хочется бОльшую часть задачи решать на высоком уровне. Аля тот же хаскель, но иметь возможность в некоторых случаях явно указать что где и как.

Кстати, видимо благодаря возможности (и легкости!) вот такого комбинированного подхода отлично приживаются решения в виде связок python + c++. Хотя каждый из этих языков по отдельности, мягко говоря, не блещет. Ибо тут сразу ясно что в случае чего отдельные функции и куски программы можно быстро и довольно просто переписать на быстром С++ (где явно использовать некие структуры данных) не ломая при этом общей структуры программы.

С хаскелем же как-то не понятно. Вот не занимаемся мы преждевременной оптимизацией. Гут. Пишем пишем. Развиваем. Всё хорошо. И тут бац. Уперлись. В ту же ленивость или представление списков. Да в квиксорт тот же (где быстрая реализация квиксорта на хаскеле?). И что делать?

Автор:  Alexey Veselovsky [ Вторник, 07 Июль, 2009 17:31 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Geniepro писал(а):
Вот тут уже придётся создавать новый список, уоторый отличается от оригинального тем, что в нём в середине новые данные. Но опять же ленивость может самортизировать расходы памяти на такой новый список. It depends...


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

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

Автор:  Geniepro [ Вторник, 07 Июль, 2009 17:33 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Alexey Veselovsky писал(а):
С хаскелем же как-то не понятно. Вот не занимаемся мы преждевременной оптимизацией. Гут. Пишем пишем. Развиваем. Всё хорошо. И тут бац. Уперлись. В ту же ленивость или представление списков. Да в квиксорт тот же (где быстрая реализация квиксорта на хаскеле?). И что делать?

Ну на худой конец, поступите так же как и с Питоном -- перепишите на Си/С++ и прикомпилируйте с основной программой на Хаскелле. Придётся только дополнительную обёртку на Хаскелле сделать для маршаллинга данных...

В архиваторе FreeArc(который на сайте http://www.metacompressor.com/ признан лучшим по показателю степень сжатия/время сжатия) именно так и сделано: половина исходников -- готовые алгоритмы сжатия на Си/С++ (зачем изобретать готовое, когда вот оно, бери и пользуйся), а вся логика анализа данных и выбора наиболее подходящего алгоритма сжатия -- на Хаскелле...

Автор:  Geniepro [ Вторник, 07 Июль, 2009 17:37 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Alexey Veselovsky писал(а):
Гм. А зачем создавать новый список? У нас будет же ж (по аналогии со вторым примером) просто функция которая будет оперировать сразу тремя списками -- начало списка, середина (вставленый элемент или несколько элементов), конец списка. Всё. Зачем делать дубль существующих данных?
В энергичном языке придётся дублировать, в ленивом, как я уже упомянул, это дублирование будет амортизировано вот такой вот функцией...

Автор:  Alexey Veselovsky [ Вторник, 07 Июль, 2009 17:49 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Geniepro писал(а):
Alexey Veselovsky писал(а):
Гм. А зачем создавать новый список? У нас будет же ж (по аналогии со вторым примером) просто функция которая будет оперировать сразу тремя списками -- начало списка, середина (вставленый элемент или несколько элементов), конец списка. Всё. Зачем делать дубль существующих данных?
В энергичном языке придётся дублировать, в ленивом, как я уже упомянул, это дублирование будет амортизировано вот такой вот функцией...


Эмм... С++ енергичный язык? При желании там дублировать не нужно будет.

Автор:  Geniepro [ Вторник, 07 Июль, 2009 17:51 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Alexey Veselovsky писал(а):
Ну, тут штука такая... Лично мне хочется бОльшую часть задачи решать на высоком уровне. Аля тот же хаскель, но иметь возможность в некоторых случаях явно указать что где и как.

Как вариант, попробуйте те же ОКамль/F#, это энергичные ФЯ с полноценной поддержкой ООП.

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

ICFPC'09 и ускорение интерпретатора

Хотя, уважаемый в российском геймдеве Владимир Шабанов так заявилв своём блоге:
Цитата:
А пока, если вам дорога ваша жизнь, и не хочется тратить ее попусту, используйте Хаскел. Точка.
И это после нескольких лет успешного использования в производстве игр связки из Окамля, Питона и С++...

Автор:  Geniepro [ Вторник, 07 Июль, 2009 17:52 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Alexey Veselovsky писал(а):
Эмм... С++ енергичный язык? При желании там дублировать не нужно будет.
Каким образом? Разрушив оригинальные списки?

Автор:  Alexey Veselovsky [ Вторник, 07 Июль, 2009 18:27 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Geniepro писал(а):
Alexey Veselovsky писал(а):
Эмм... С++ енергичный язык? При желании там дублировать не нужно будет.
Каким образом? Разрушив оригинальные списки?


Зачем же? Будет просто храниться дополнительно список (или другая удобная структура данных) переходников/адаптеров. Если расходны на содержание этой структуры становятся сопоставимыми с расходами на дублирование списка, то делается новый список из этих фрагментов. Число переходников становится равным нулю.

Вот у нас был список: a1->a2->a3->a4->a5->a6
Вставляем после a3 некий элемент b, нет, пусть, для общности, мы туда вставляем целый готовый (и использующийся где-то ещё) списко b1->b2. Мы можем либо создать новый список:
a1'->a2'->a3'->b1'->b2'->a4'->a5'->a6', либо составить новый список из трех списков:
a1->a2->a3
b1->b2
a4->a5->a6

плюс некая структура которая их склеивает.
Т.о. нам нужна структурка имеющая ссылки на все ключевые точки списка, а именно: на голову списка (a1) + точки склейки т.е. пары (a3, b1), (b2,a4) (тут a3 и b4 назовем скажем ключем, а b1 и a4 -- значениями).
Алгоритм очень простой. Хотим пройти весь наш составной список из начала в конец. Ок. Начинаем с a1, идем проверяя не является ли текущий элемент списка ключём из следующей пары (т.о. при каждом шаге у нас максимум двасравнения вне зависимости от количества точек склейки -- одно это проверка есть ли у нас очередная пара вообще, второе -- если есть, то проверка на совпадение), если да (добрались до a3), то следующий элемент будет не то на что ссылается a3 а значение из пары (a3,b1), т.е. собственно b1. Дальше повторяется то же самое. Доходим до b2, видим что текущий элемент совпадает с очередным ключем, и поэтому понимаем что у нас тут не конец списка (ибо b2 ни на кого не ссылается), и идем дальше, a4,a5,a6... Дошли до a6. Поскольку очередной пары у нас нет, а a6 у нас ни на кого не ссылается (null таки), то это конец списка.

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

PS. Кстати, в хаскеле же ж списки не меняются (ибо переменных как бы нет), так почему/зачем после создания списка где-то не хранят его длину? Зачем заставлять машину бегать из конца в конец списка, если длина его известна на момент создания и уже никогда не будет изменена?

Автор:  Geniepro [ Вторник, 07 Июль, 2009 19:11 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Alexey Veselovsky писал(а):
Зачем же? Будет просто храниться дополнительно список (или другая удобная структура данных) переходников/адаптеров. Если расходны на содержание этой структуры становятся сопоставимыми с расходами на дублирование списка, то делается новый список из этих фрагментов. Число переходников становится равным нулю.
Во-первых, это естественно можно реализовать и на функциональных языках, если такая необходимость возникнет, а во-вторых, самое главное-то то, что это уже не список! Это структура данных совершенно другого типа и к ней уже не применимы операции над списками! Придётся делать либо самодельные операции, либо как-то реализовать списочный интерфейс, что лучше (если такая возможность есть, конечно)...

Alexey Veselovsky писал(а):
PS. Кстати, в хаскеле же ж списки не меняются (ибо переменных как бы нет), так почему/зачем после создания списка где-то не хранят его длину? Зачем заставлять машину бегать из конца в конец списка, если длина его известна на момент создания и уже никогда не будет изменена?
Вопрос хороший.
Во-первых, это детали реализации -- вполне можно сделать реализацию, в которой так и будет сделано, в описании языка Хаскелл это никак не регламентировано.

А во-вторых, ну вот сделали мы так для этой довольно редкоиспользуемой операции length -- каким способом и какой ценой? (Вопрос цены реализации -- самый животрепещущий на данном форуме :lol: )
Вспомните, в Хаскелле при работе со списками очень активно используется паттерн-матчинг. При этом получается, что исходный список постоянно дробится на всё более мелкие, и для каждого мы должны будем указать его длину, а то вдруг понадобится! Получится, что каждый элемент списка кроме собственно значения и указателя на следующий элемент списка должен ещё хранить и длину? И при конструировании списка мы кроме создания нужного значения и прописывания в его указателе адрес следующего элемента должны будем ещё и записать в отдельное поле новый размер списка!
В большинстве случаев это не нужно, хотя никто не мешает сделать самому. Вот только опять же придётся реализовывать интерфейсы классов группы ListLike и пользоваться их методами, стандартные функции уже не подойдут. (Хотя это уже недоработка текущего варианта Хаскелла)...

Автор:  Alexey Veselovsky [ Среда, 08 Июль, 2009 08:28 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Да, вот такая вот простая программа:
Код:
xs = [1..10000000] :: [Int]

main = do
    print $ length xs
    print $ last   xs
    c <- getChar
    print c


Кушает не 40 Мб памяти (как было бы в случае массива из int'ов, и не 80 Мб (как было бы в случае односвязного списка), и даже не 120ть (как было бы в случае двусвязного списка), а аж целых 225 мегабайт. Что-то мне кажется что xs в данном случае это нифига не список, по кр. мере это НЕ ТОЛЬКО список, но и ещё какие-то сильно дополнительные структуры.

Ну и эффективность использования памяти тоже хм... впечатляет.

Автор:  Geniepro [ Среда, 08 Июль, 2009 09:32 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Alexey Veselovsky писал(а):
Ну и эффективность использования памяти тоже хм... впечатляет.

Мне тоже непонятно, почему GHC так неэффективно тратит память под списки. Со старой версией (6.8.3) расход памяти немного меньше, но время работы этой программы заметно больше.

Автор:  Alexey Veselovsky [ Среда, 08 Июль, 2009 09:38 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Geniepro писал(а):
Alexey Veselovsky писал(а):
Ну и эффективность использования памяти тоже хм... впечатляет.

Мне тоже непонятно, почему GHC так неэффективно тратит память под списки. Со старой версией (6.8.3) расход памяти немного меньше, но время работы этой программы заметно больше.


Дык этта... У меня тут вот что:
Код:
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 6.8.2


То что было в репозитории то и поставил ;-)

Автор:  Geniepro [ Среда, 08 Июль, 2009 09:44 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Вы текущую версию 6.10.2 поставьте, увидите, что расход памяти станет ещё больше метров на 50... :lol:
ЗЫ. Ничего, щас скачаю Clean -- он весьма похож на Хаскелл, только вместо монад у него уникальные типы используются, посмотрю, сколько там израсходуется памяти для этой программки...

Автор:  Alexey Veselovsky [ Среда, 08 Июль, 2009 09:55 ]
Заголовок сообщения:  Re: Haskell подсчитать длину списка.

Geniepro писал(а):
Вы текущую версию 6.10.2 поставьте, увидите, что расход памяти станет ещё больше метров на 50... :lol:

Текущая версия таки 6.10.3. Уже несколько месяцев как.

Geniepro писал(а):
ЗЫ. Ничего, щас скачаю Clean -- он весьма похож на Хаскелл, только вместо монад у него уникальные типы используются, посмотрю, сколько там израсходуется памяти для этой программки...

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

Страница 2 из 5 Часовой пояс: UTC + 3 часа
Powered by phpBB® Forum Software © phpBB Group
https://www.phpbb.com/