2011-01-22 1 views
2

Сегодня я начал изучать Haskell. Я вроде как новый с функциональными языками, и мне очень нравится Хаскелл.Haskell: доступ к спискам со спины

Однако у меня есть вопрос о его дизайне, который прослушивает меня: из того, что я понял до сих пор, похоже, что доступ к элементу в конце списка намного сложнее, чем доступ к элементу на передний план (что-то вроде xs:x, где xs::[a] и x::a не представляется возможным).

(Из того, что я понял), можно добавить список в другой (xs++[a]), но это будет стоить дороже во время выполнения (для этого требуется пройти весь список), и он не может использоваться для сопоставления шаблонов.

Почему Haskell не хватает такой операции?

ответ

7

Список типов данных

data [a] = [] | a : [a] 

определяется как выше. Есть только два шаблона, которые вы можете найти: [] и x : xs, где x - это голова, а xs - это хвост.

Предварение к списку

a = 1 : 2 : [] 
b = 0 : a 
 
(:) <-- b 
/\ 
0 (:) <-- a 
/\ 
    1 (:) 
    /\ 
    2 [] 

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

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

a = 1 : 2 : [] 
b = a ++ [3] 
 
(:) <-- a  (:) <-- b 
/\   /\ 
1 (:)   1 (:) 
/\   /\ 
    2 []   2 (:) 
        /\ 
        3 [] 

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

В самом деле, рассмотрим

a = 0 : a 
b = 0 : [ x+1 | x <- b ] 
 
(:) <-- a   (:) <-- b 
/\    /\ 
0 (:) <-- a  0 (:) <-- [ x+1 | x <- b ] 
/\    /\ 
    0 (:) <-- a  1 (:) <-- [ x+1 | x <- [ x+1 | x <- b ] ] 
    ...    ... 

Как бы вы когда-нибудь получить последний элемент списка или добавить в конце концов?

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

+0

Правильно! Я не считал, что списки должны быть неизменными, и по этой причине они могут быть расширены только на лицевой стороне ИЛИ сзади, но не на обеих сторонах. Благодаря! – peoro

1

Это не проблема с языком, а только тип данных List, который имеет некоторый специальный синтаксис, но в остальном не является «неотъемлемой частью Haskell». У вас такая же проблема на C с односвязными списками, но это, очевидно, не проблема языка программирования C.

Если вам нужен двойной список с указателем хвоста, тогда создайте такой тип данных и используйте это! Возможно, вам захочется узнать больше типов данных (см. Пакет containers, vector и dlist).

+0

Список - это тип _built-in_, описанный самим языком Haskell, не так ли? Если список в Haskell определяется так, как он есть из-за дизайна языка, точно так же, как встроенные типы C определяются языком. Список в C не является частью языка, так же как неродные типы данных Haskell не являются частью языка Haskell. – peoro

+1

@peoro Степень, в которой Списка встроена, является чисто синтаксической. Представьте, что список был определен как 'data List a = Nul | Минус a (Список a) '. Степень, в которой он встроен, ортогонален вашей проблеме - если вы хотите получить доступ O (1) к хвосту, используйте структуру данных, которая отслеживает хвост. –

+0

сегодня я узнал, как объявлять новые типы и понимать, что вы мне сказали: '-)' – peoro

3

Это проблема с чистой структурой данных List, а не с самим haskell. Вы можете прочитать статью Purely Functional Data Structures, чтобы узнать больше о других чистых структурах данных, которые могут иметь лучшую производительность при таких операциях.

+0

все зависит. Список - это тип _built-in_, описанный самим языком Haskell, не так ли? Если это так, списки haskell зависят от дизайна языка. – peoro

+2

@peoro Вы продолжаете указывать на это, но то, что вам говорят, это «вы используете неправильный инструмент», у Haskell есть инструменты, которые наполняют ваши потребности, но вам это не кажется интересным. Аналогичные жалобы были бы: «C-массивы не могут делать O (1) cons» и «Python» - это не O (log (n)) lookup ». Очевидно, что эти жалобы сомнительны, так как разработчик неправильно использует определенную структуру данных. –

+0

@TomMD, я не «продолжаю указывать на это», я прокомментировал это только один раз (на ваш и на этот ответ). Я согласен, что если в C мне нужен список, массивы не подходят для моих целей. С моим вопросом (и с этими комментариями) я не хотел сказать, что списки Haskell плохие (так же, как массивы C неплохие), и я не жалуюсь на это! Я просто хотел знать, почему они разработаны таким образом; они являются частью языка Haskell: таким образом, дизайн списков зависит от дизайна haskell. Подводя итог, я не нуждаюсь в другом списке, как вы и Юрас предлагаете, я просто хотел знать, почему встроенные списки работают так. – peoro

5

Тип данных списка в Haskell является связанным списком, поэтому в поиске используется время O (n). Если вам нужен частый доступ к задней части списка, вы можете взглянуть на Data.Sequence , который добавляет O (1) к началу и концу.

Чтобы ответить на вопрос, почему Haskell использует эту структуру данных как «стандартный контейнер» (например, C и массивы), это потому, что Haskell является чистым функциональным языком и поэтому предпочитает чисто функциональные структуры данных (неизменяемые и постоянные). Для дальнейшего чтения смотрите this wiki page. Для использования нефункциональных структур данных в Haskell потребуется, чтобы он находился внутри монады IO или ST.

+1

Ваше второе предложение - это то, что я искал. Вместе с @emhemient answer это заставило меня понять, почему списки haskell были разработаны таким образом. Благодаря! ':-)' – peoro

+1

Я не уверен, что это правильно, что Data.Sequence имеет схожую сложность с двусвязным списком, так как Data.Sequence должна выполнять некоторую работу балансировки при добавлении элементов. JoinList, иногда называемый Monoidial, не выполняет никаких балансировок, поэтому должен быть более эффективным для построения. Не используйте JoinList в Hackage, хотя его довольно плохо написано и нуждается в обновлении (я автор) - на практике я ожидал бы, что Data.Sequence будет лучше работать даже над тем, что должен выиграть JoinList. –

+0

@ Stephen, я согласен, поэтому я изменил его, чтобы быть более конкретным в отношении того, что время работы, которое нас интересует здесь. Я не знаю, о чем думал, когда сказал, что он похож на двусвязный список, потому что это, очевидно, совсем не так! – HaskellElephant

2

Не бойтесь переделать() свой список, когда вам нужно. Нередко можно отменить список, прежде чем дать ему рекурсивную функцию, или изменить окончательный результат fold().