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

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

9 голосов
/ 28 апреля 2009

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

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, ...-

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

- РЕДАКТИРОВАТЬ -

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

- РЕДАКТИРОВАТЬ 2 -

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

Ответы [ 12 ]

14 голосов
/ 28 апреля 2009

Вы делаете это:

$ 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]
6 голосов
/ 28 апреля 2009

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

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 бессмысленных вариантов то же имя.

3 голосов
/ 28 апреля 2009

Ответ на вопрос edit:

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

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

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

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 вызывает себя рекурсивно, каждый раз с новым элементом, добавляемым в список. Пока пользователь не введет «выход», он перестанет вызывать себя и вернет окончательный список.


Оригинальный ответ, относящийся к ленивой оценке:

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

Из-за ленивых вычислений вы можете использовать order_list вот так:

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

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

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

2 голосов
/ 28 апреля 2009

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

let a = 1 : a

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

Если вы хотите создать список [Cash ..., Invoice], не используйте такие имена повторно.

1 голос
/ 28 апреля 2009

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

let order_list = CashOnDelivery : order_list

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

1 голос
/ 28 апреля 2009

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

1 голос
/ 28 апреля 2009

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

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

0 голосов
/ 31 марта 2011

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

Мы делаем функцию , чтобы сделать это. (Как в fun Ctional Programming)

$ let order_list = [Invoice 2345] <br/> $ let f x = x : order_list <br/> $ let order_List = f cashOnDelivery<br/> $ order_list<br/> [CashOnDelivery, [Invoice 2345]] <br/>

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

$ let f x = x : order_list <br/> $ let order_List = f cashOnDelivery<br/> $ order_list<br/> [CashOnDelivery, CashOnDelivery, [Invoice 2345]] <br/>

Ура!

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

0 голосов
/ 28 апреля 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, ...]
0 голосов
/ 28 апреля 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 s.

...