Haskell: позвольте внутреннему пониманию списка получить неожиданный результат

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

Мой вопрос: почему дубликат создает [Int], а дубликат создает [[Int]] и возможно ли вообще создать [Int] с помощью let внутри понимания списка?

Спасибо за ваше время и помощь :).

--list comprehension
duplicate' xs = [y | x <- xs, y <- [x,x]]
input => [1,2,3,4]
output => [1,1,2,2,3,3,4,4]
expected? => yes

--list comprehension with let
duplicate'' xs = [y | x <- xs, let y = [x,x]]
input => [1,2,3,4]
output => [[1,1],[2,2],[3,3],[4,4]]
expected? => yes

person BARJ    schedule 21.09.2013    source источник
comment
[y | x <- xs, let y = [x,x]] == [[x,x] | x <- xs] видишь ли, это не то, что ты хотел   -  person Sassa NF    schedule 22.09.2013


Ответы (2)


<- и let просто означают разные вещи.

Когда вы пишете y <- [x,x], вы говорите «дайте y каждое из значений в списке [x,x] по очереди».

Когда вы пишете let y = [x,x], вы говорите «дать y значение [x,x]».

person GS - Apologise to Monica    schedule 21.09.2013

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

[ y | x <- xs, let y = [x,x] ] ≡ [ [x,x] | x <- xs ]

Таким образом, любое такое выражение только с одним <- имеет форму

[ f x | x<-xs ]

что эквивалентно map f xs. Таким образом, список результатов всегда должен иметь ту же длину, что и xs, что делает невозможным достижение желаемого поведения duplicate: если вы хотите иметь дубликаты, вам нужно инкапсулировать их как внутренние списки, чтобы они не считались дополнительными элементами.

Чтобы объединить эти вложенные списки обратно в «плоский», вы можете использовать тот факт, что списки являются монадами:

join :: Monad m => m (m a) -> m a

Прелюдия Control.Monad> присоединиться к [[1,1], [2,2]]
[1,1,2,2]

На самом деле join — это то, как теоретики категорий предпочитают определять монады, но, как вы, возможно, знаете, Haskell делает это немного по-другому:

(>>=) :: Monad m => m a -> (a -> m b) -> m b
a >>= f = join (fmap f a)

или, как это фактически определяется наоборот,

join a = a >>= id

Поместите это в модифицированную let версию duplicate:

join [y | x <- xs, let y = [x,x] ]
  ≡ [y | x <- xs, let y = [x,x] ] >>= id
  ≡ map (\x -> [x,x]) xs >>= id
  ≡ xs >>= id . (\x -> [x,x])
  ≡ xs >>= (\x -> [x,x])
  ≡ do { x<-xs; [x,x] }
  ≡ do { x<-xs; y<-[x,x]; return y }  -- by the monad laws

Итак, выражение do { a<-p; b<-q; ... return x } — это включение монады, обобщение спискового включения. Его можно переписать [x | a<-q, b<-q, ...]. Для нашей проблемы,

join [y | x <- xs, let y = [x,x] ] ≡ [y | x<-xs, y<-[x,x]]

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

Конечно, вы все еще можете использовать также let в любой момент...

[y | x<-xs, y<-[x,x]]
     ≡ [y | x<-xs, let z=[x,x], y<-z]
     ≡ [a | x<-xs, let z=[x,x], y<-z, let a=y]
     ≡ [a | x<-xs, let z=let w=[let q=x in q, let r=x in r] in w, y<-z, let a=y]
     ≡ ...
person leftaroundabout    schedule 21.09.2013
comment
Я правильно понимаю, что concat — это специализированное соединение для списков? (Или, может быть, наоборот: соединение для монады списка реализовано с помощью concat?) - person kqr; 22.09.2013
comment
@kqr join (определяется с помощью (>>=), который определяется с помощью foldr для [a]) и concat (определяется с помощью foldr) определяются отдельно, но оба делают то же самое для списков. - person wit; 22.09.2013
comment
Спасибо за подробное объяснение, очень помогло. - person BARJ; 22.09.2013