Синтаксис для построения списка/конкатенации

Я уже был в Haskell уже два дня, и мне было интересно, какая разница между двумя определениями функций ниже:

Prelude> let swap (x1:x2:xs) = x2:x1:xs
Prelude> swap [1..5]
[2,1,3,4,5]
Prelude> let swap' (x1:x2:xs) = [x2] ++ [x1] ++ xs
Prelude> swap' [1..5]
[2,1,3,4,5]

То есть, что делает x2: x1: xs отличным от [x2] ++ [x1] ++ xs? Пожалуйста, спасибо.

Ответ 1

Подписи типов - это хорошее место для начала:

(:) :: a -> [a] -> [a]
(++) :: [a] -> [a] -> [a]

Вы можете найти их с помощью :type (:) и :type (++) в ghci.

Как видно из сигнатур типа, оба используются для создания списков.

Оператор : используется для построения списков (и для их раздельного разделения). Чтобы создать список [1,2,3], вы просто создаете его с помощью 1 : 2 : 3 : []. Первым элементом : является элемент, который нужно добавить в начале списка, а второй элемент - либо список (также созданный с помощью :, либо пустой список, обозначенный []).

Оператор ++ представляет собой конкатенацию списка. Он берет два списка и присоединяет их вместе. [1,2,3] ++ [4,5,6] является законным, тогда как 1 ++ [1,2,3] не является.

Ответ 2

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

Теперь мы переходим к синтаксису: обозначение

[x2]

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

x2:[]

Итак, что вы действительно сделали во втором примере:

(x2:[]) ++ (x1:[]) ++ xs

Поэтому при построении списка вы не можете избежать (:), это, в сущности, единственный способ сделать это. Обратите внимание, что вы должны создавать промежуточные списки, чтобы иметь возможность использовать (++).