Функциональное программирование: что такое "неправильный список"?

Может кто-нибудь объяснить, что такое "неправильный список"?

Примечание: Спасибо всем! Все вы, ребята, рок!

Ответ 1

Я думаю, что ответ @Vijay - лучший, и я просто намерен Erlangify его.

Пары (cons cells) в Erlang записываются как [Head|Tail], а nil - как []. Нет никаких ограничений относительно того, что у головы и хвоста, но если вы используете хвост для создания целых клеток, вы получаете список. Если конечный хвост [], тогда вы получите правильный список. Существует специальная синтаксическая поддержка списков в том, что правильный список

[1|[2|[3|[]]]]

записывается как

[1,2,3]

и неправильный список

[1|[2|[3|4]]]

записывается как

[1,2,3|4]

чтобы вы могли видеть разницу. Соответствие с правильными/неправильными списками соответственно легко. Таким образом, функция длины len для правильных списков:

len([_|T]) -> 1 + len(T);
len([]) -> 0.

где мы явно сопоставляем завершающий []. Если задан неправильный список, это приведет к ошибке. Хотя функция last_tail, которая возвращает последний хвост списка, может обрабатывать и неправильные списки:

last_tail([_|T]) -> last_tail(T);
last_tail(Tail) -> Tail.                 %Will match any tail

Обратите внимание, что создание списка или сопоставление с ним, как вы обычно делаете с [Head|Tail], выполняет не, проверяет ли хвост список, поэтому нет проблем с обработкой неправильных списков. Существует редко необходимость в неправильных списках, хотя вы можете делать с ними крутые вещи.

Ответ 2

Я думаю, что это проще объяснить с помощью Схемы.

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

(a . (b . (c . (d . (e . ()))))) 

;; same as

(a b c d e)

Цепочка пар, которая не заканчивается в пустом списке, называется неправильным списком. Обратите внимание, что неправильный список не является списком. Список и пунктирные обозначения могут быть объединены для представления неправильных списков, поскольку следующие эквивалентные обозначения показывают:

 (a b c . d)
 (a . (b . (c . d)))

Примером обычной ошибки, приводящей к построению неправильного списка, является:

scheme> (cons 1 (cons 2 3))
(1 2 . 3)

Обратите внимание на точку в (1 2. 3) --- как точку в (2. 3), говоря, что cdr пары указывает на 3, а не на другую пару или '(). То есть, это неправильный список, а не только список пар. Это не соответствует рекурсивному определению списка, потому что, когда мы переходим ко второй паре, его cdr не является списком - это целое число.

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

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

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

Scheme>(list 1 2 3 4)
(1 2 3 4)

Предоставлено: Введение в схему

Ответ 3

Определение списка в Erlang дается в руководстве - в частности, раздел 2.10

В Erlang единственное, что вам действительно нужно знать о неправильных списках, - это то, как их избежать, и способ сделать это очень просто - все сводится к первой "вещи", что вы собираетесь создать свой список на. Следующие правила создают соответствующие списки:

A = [].
B = [term()].
C = [term(), term(), term()].

Во всех этих случаях синтаксис гарантирует, что есть скрытый "пустой" хвост, который соответствует типу "[]" в конце....

Итак, из них следующие операции производят правильный список:

X = [term() | A].
Y = [term() | B].
Z = [term() | C]. 

Это все операции, которые добавляют новый заголовок в правильный список.

Полезно то, что вы можете подавать каждый из X, Y или Z в функцию типа:

func([], Acc)      -> Acc;
func([H | T], Acc) -> NewAcc = do_something(H),
                      func(T, [NewAcc | Acc]).

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

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

D = [term1() | term2()]. % term2() is any term except a list

В этом списке нет пустого списка скрытый в качестве хвоста терминала, у него есть термин...

Отсюда сверху вниз - фарс, как заметил Роберт Вирддинг в комментариях

Итак, как вы пишете для него предложение терминала?

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

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

Извиняюсь

Следуя комментарию Роберта, я попробовал распечатать неправильный список и, вот и вот, это очевидно:

([email protected])5>A = [1, 2, 3, 4].
[1,2,3,4]
([email protected])5> B = [1, 2, 3 | 4].
[1,2,3|4]
([email protected])6> io:format("A is ~p~nB is ~p~n", [A, B]).
A is [1,2,3,4]
B is [1,2,3|4]

Я потратил некоторое время на поиски некорректного списка один раз и убедился, что это было невозможно, хорошо, а кен-ноо!

Ответ 4

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

В частности, "опрятное открытие" списков состоит в том, что вы можете представлять список, используя только формы с фиксированным количеством элементов, а именно:

;; a list is either 
;; - empty, or
;; - (cons v l), where v is a value and l is a list.

Это "определение данных" (с использованием условий "Как программировать программы" ) имеет все виды хорошие свойства. Один из самых приятных заключается в том, что если мы определяем поведение или значение функции на каждой "ветки" определения данных, мы гарантированно не пропустим случай. Более того, такие структуры, как правило, приводят к хорошим чистым рекурсивным решениям.

Пример классической длины:

(define (length l)
  (cond [(empty? l) 0]
        [else (+ 1 (length (rest l))]))

Конечно, в Haskell все красивее:

length []    = 0
length (f:r) = 1 + length r

Итак, что это связано с неправильными списками?

Ну, неправильный список использует это определение данных, вместо этого:

;; an improper list is either
;; - a value, or
;; - (cons v l), where v is a value and l is an improper list

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

(define (length l)
  (cond [(cons? l) (+ 1 (length (rest l)))]
        [else 1]))

Проблема в том, что я уничтожил свойство nice, если я беру два значения и помещаю их в неправильный список с (cons a b), результат имеет длину два. Чтобы понять, почему, допустим, я считаю значения (cons 3 4) и (cons 4 5). Результатом является (cons (cons 3 4) (cons 4 5)), которое может быть истолковано как неправильный список, содержащий (cons 3 4) и (cons 4 5), или как неправильный список, содержащий (cons 3 4), 4 и 5.

На языке с более ограничительной системой типов (например, Haskell) понятие "неправильного списка" не имеет особого смысла; вы можете интерпретировать его как тип данных, базовый регистр которого содержит две вещи, которые, вероятно, также не то, что вы хотите.

Ответ 5

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

Например, скажем, вы вызываете следующий sum в Erlang в неправильном списке:

sum([H|T]) -> H + sum(T);
sum([]) -> 0.

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

Ответ 6

Я думаю, возможно, это относится к "пунктирной паре" в LISP, например. список, чья окончательная ячейка cons содержит атом, а не ссылку на другую ячейку cons или NIL, в cdr.

ИЗМЕНИТЬ

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

http://en.wikipedia.org/wiki/Lisp_(programming_language)

и найдите "неправильное" и проверьте сноски.

Ответ 7

В Common Lisp неправильные списки определяются как:

  • пунктирные списки, у которых есть не содержащий NIL "атом".

Пример

  (a b c d . f)

или

  • круговые списки

Пример

  #1=(1 2 3 . #1#)

Ответ 8

Список состоит из ячеек, каждая ячейка состоит из двух указателей. Сначала указываем на элемент данных, второй - на следующую ячейку, или nil в конце списка.

Если второй не указывает на ячейку (или нуль), список неверен. Функциональные языки, скорее всего, позволят вам создавать ячейки, поэтому вы должны иметь возможность генерировать неверные списки.

В Erlang (и, возможно, на других языках FP) вы можете сохранить некоторую память, сохранив ваши 2-кортежи как ненадлежащие списки:

2> erts_debug:flat_size({1,2}).
3
3> erts_debug:flat_size([1|2]).
2

Ответ 9

В Erlang правильный список - это тот, где [H|T].

H является заголовком списка, а T является остальной частью списка в качестве другого списка.

Неправильный список не соответствует этому определению.