Оператор Haskell Cons (:)

Я действительно новичок в Haskell (на самом деле я видел «Real World Haskell» от O'Reilly и подумал: «Хм, я думаю, что вчера изучу функциональное программирование»), и мне интересно: я могу использовать оператор конструкции для добавления элемента в начало списка:

1 : [2,3]
[1,2,3]

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

--in a file
data BillingInfo = CreditCard Int String String
| CashOnDelivery
| Invoice Int
deriving (Show)

--in ghci
 $ let order_list = [Invoice 2345]
 $ order_list
[Invoice 2345]
 $ let order_list = CashOnDelivery : order_list
 $ order_list
[CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, CashOnDelivery, ...-

и т.д... он просто повторяется вечно, потому что он использует ленивую оценку?

-- ИЗМЕНИТЬ --

ладно, так что мне в голову вбивается, что let order_list = CashOnDelivery:order_list не добавляет CashOnDelivery в исходный список order_list, а затем устанавливает результат в order_list, а вместо этого является рекурсивным и создает бесконечный список, навсегда добавляя CashOnDelivery в начало себя. Конечно, теперь я вспомнил, что Haskell — это функциональный язык, и я не могу изменить значение исходного списка order_list, так что же мне делать для простого «прикрепить это к концу (или началу, как угодно) этого списка?» Сделать функцию, которая принимает список и BillingInfo в качестве аргументов, а затем возвращает список?

-- ИЗМЕНИТЬ 2 --

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


person Carson Myers    schedule 27.04.2009    source источник
comment
Вы всегда будете создавать новый список. Почему вы хотите использовать старое имя для ссылки на новый список?   -  person ephemient    schedule 28.04.2009
comment
Прежде чем вы сможете понять ответ, вы должны сначала понять свой вопрос.   -  person Paul Johnson    schedule 01.05.2009


Ответы (12)


Ты делаешь это:

$ let order_list = [Invoice 2345]
$ let order_list = CashOnDelivery : order_list

Здесь важно отметить, что вы не просто добавляете элемент CashOnDelivery к своему первому элементу order_list. Вы определяете новую переменную order_list, которая не имеет ничего общего с первой. Это рекурсивное определение, order_list справа относится к order_list, которое вы определяете слева, а не к тому, что определено в предыдущей строке. Из-за этой рекурсии вы получаете бесконечный список.

Я подозреваю, что вы действительно хотели сделать что-то вроде этого:

$ let order_list = [Invoice 2345]
$ order_list
[Invoice 2345]
$ let order_list2 = CashOnDelivery : order_list
$ order_list2
[CashOnDelivery, Invoice 2345]
person sth    schedule 27.04.2009

Как выздоравливающий программист машинного обучения, я постоянно попадаюсь на эту удочку. Это одна из немногих неприятностей в Haskell, когда вы не можете легко перепривязать имя в предложениях let или where. Если вы хотите использовать let или where, вы должны придумать новые имена. В цикле чтения-оценки-печати верхнего уровня, если вы хотите привязать одно имя за раз, у вас нет другого выбора. Но если вы хотите вкладывать конструкции, вы можете злоупотреблять нотацией do с монадой identity:

import Control.Monad.Identity

let order_list = runIdentity $ do
       order_list <- return [Invoice 2345]
       order_list <- return $ CashOnDelivery : order_list
       return order_list

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

person Norman Ramsey    schedule 28.04.2009
comment
Хех, я бы просто закончил с order_list, order_list', order_list'', order_list''' и т. д... - person ephemient; 29.04.2009
comment
Хе-хе, я иногда делаю это в монаде IO, чтобы дать мне приятное императивное чувство ;-) - person Tom Lokhorst; 29.04.2009

Ответ на вопрос редактирования:

так что я должен сделать для простого «прикрепить это к концу (или началу, что угодно) этого списка?» Сделать функцию, которая принимает список и BillingInfo в качестве аргументов, а затем возвращает список?

Ах, но уже есть «функция» для добавления элемента в список: это конструктор cons (:) :-)

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

ghci> let first_order_list = [Invoice 2345]
ghci> first_order_list
[Invoice 2345]
ghci> let second_order_list = CashOnDelivery : first_order_list
ghci> second_order_list
[CashOnDelivery, Invoice 2345]

Относительно второго редактирования:

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

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

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

main = do
  orderList <- collectBillingInfos
  putStrLn ("You entered these billing infos:\n" ++ show orderList)

collectBillingInfos :: IO [BillingInfo]
collectBillingInfos = loop []
  where
    loop xs = do
      putStrLn "Enter billing info (or quit)"
      line <- getLine
      if line /= "quit"
        then loop (parseBillingInfo line : xs)
        else return xs

parseBillingInfo :: String -> BillingInfo
parseBillingInfo _ = CashOnDelivery -- Don't want to write a parser here

Резюмировать; функция loop вызывает себя рекурсивно, каждый раз добавляя в список новый элемент. Пока пользователь не введет "quit", он перестанет вызывать себя и вернет окончательный список.


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

Как уже говорили другие, это рекурсивное определение, делающее order_list бесконечным списком, содержащим только CashOnDelivery значений. Хотя ленивое вычисление не является причиной этого, оно делает его полезным.

Из-за ленивой оценки вы можете использовать order_list следующим образом:

ghci> take 3 order_list
[CashOnDelivery, CashOnDelivery, CashOnDelivery]

Если бы у вас не было ленивых вычислений, вызов take завершился бы сбоем, потому что он сначала попытался бы вычислить order_list (что бесконечно).

Теперь, для order_list это не очень полезно, но есть много других мест, где очень удобно программировать с бесконечными (или просто очень большими) структурами данных.

person Tom Lokhorst    schedule 27.04.2009

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

let a = 1 : a

создает бесконечный список единиц, и вы можете взять столько из них, сколько хотите, с помощью функции взятия или при попытке распечатать ее. Обратите внимание, что вы используете один и тот же идентификатор в левой и правой частях уравнения, и это работает: order_list равен CashOnDelivery:order_list, теперь подставляя: order_list = CashOnDelivery:(CashOnDelivery:order_list) = Cash... и т. д.

Если вы хотите создать список [Наличные..., Счет-фактура], не используйте повторно такие имена.

person aidenvdh    schedule 27.04.2009

CDR только что созданных cons указывает на себя: определение order_list, используемое во втором let, является создаваемым определением. Используйте другое имя переменной, чтобы полностью обойти проблему рекурсии, и код также будет менее запутанным.

РЕДАКТИРОВАТЬ: После прочтения ответа Джоэла кажется, что я говорю здесь с Лиспом. Ленивая оценка или нет, в любом случае вы создали рекурсивное определение...

person Jeffrey Hantin    schedule 27.04.2009

Haskell использует ленивую оценку... ничего не оценивается до тех пор, пока это не понадобится, поэтому order_list сохраняется как cons, содержащий CashOnDelivery и другую, неоцененную ячейку, снова ссылающуюся на order_list.

person Joel Spolsky    schedule 27.04.2009

Я думаю, что важным вопросом здесь является не лень, а масштаб. Выражение let x = ... вводит новое определение x, которое заменит любое предыдущее определение. Если справа появится x, то определение будет рекурсивным. Кажется, вы ожидали использования order_list в правой части

let order_list = CashOnDelivery : order_list

для обозначения первого определения order_list (т. е. [Invoice 2345]). Но выражение let представило новую область видимости. Вместо этого вы определили бесконечный список из CashOnDelivery элементов.

person Chris Conway    schedule 27.04.2009

Я думаю, вы имеете в виду «это потому, что он использует ленивую оценку»? Ответ да:

let ol = CashOnDelivery : ol

Это говорит нам о том, что ol содержит элемент CashOnDelivery, а затем результат выражения ol. Это выражение не вычисляется до тех пор, пока это необходимо (отсюда: лень). Таким образом, при печати ol сначала будет напечатан CashOnDelivery. Только тогда будет определен следующий элемент списка, что приведет к бесконечному поведению.

person Stephan202    schedule 27.04.2009

X:L означает «Создать список, начинающийся с X, за которым следуют элементы списка, определенные L».

Затем вы определяете order_list как CashOnDelivery, за которым следуют элементы в списке, определенном order_list. Это определение является рекурсивным, поэтому оценка списка продолжает возвращать CashOnDelivery. Фактически ваш список содержит бесконечное количество значений CashOnDelivery, за которыми следует одно значение Invoice.

person Richard    schedule 27.04.2009

order_list в этой строке:

let order_list = [Invoice 2345]

это переменная, отличная от order_list в этой строке

let order_list = CashOnDelivery : order_list

Вторая строка не изменяет значение order_list. Он вводит новую переменную с тем же именем, но с другим значением.

order_list в правой части второй строки такое же order_list, что и в левой части второй строки; это не связано с order_list в первой строке. Вы получаете бесконечный список, полный CashOnDelivery --- во втором списке нет Invoice.

person dave4420    schedule 27.04.2009

В ML val не является рекурсивным. Вы должны указать val rec для рекурсивных значений.

val fin = fn _ => 0
val fin = fn x => fin x + 1
(* the second `fin` is calling the first `fin` *)

val rec inf = fn x => inf x + 1
(* ML must be explicitly told to allow recursion... *)
fun inf' x = inf' x + 1
(* though `fun` is a shortcut to define possibly recursive functions *)

В Haskell все привязки верхнего уровня, let и where являются рекурсивными — нерекурсивных привязок не существует.

let inf = \_ -> 0
let inf = \x -> inf x + 1
-- the second `inf` completely shadows the first `inf`

Исключение: в do привязка <- не является рекурсивной. Однако если вы используете {-# LANGUAGE RecursiveDo #-} и импортируете Control.Monad.Fix, вы получаете mdo, в котором привязка <- является рекурсивной.

foo :: Maybe [Int]
foo = do
    x <- return [1]
    x <- return (0 : x)  -- rhs `x` refers to previous `x`
    return x
-- foo == Just [0, 1]

bar :: Maybe [Int]
bar = mdo
    y <- return (0 : y)  -- rhs `x` refers to lhs `x`
    return y
-- bar == Just [0, 0, 0, ...]
person ephemient    schedule 28.04.2009

Может быть, попробовать это

Для этого мы создаем функцию (как в функциональномпрограммировании).

$ let order_list = [Invoice 2345]
$ let f x = x : order_list
$ let order_List = f cashOnDelivery
$ order_list
[CashOnDelivery, [Invoice 2345]]

~Обратите внимание, что нам нужно переделывать функцию let f x = x : order_list каждый раз, когда мы добавляем order_list, чтобы мы прикрепляли ее к последнему order_list.

$ let f x = x : order_list
$ let order_List = f cashOnDelivery
$ order_list
[CashOnDelivery, CashOnDelivery, [Invoice 2345]]

Ваше здоровье!

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

person Joseph    schedule 31.03.2011