Почему Haskell не принимает мое комбинаторное определение zip?

Это функция zip учебника:

zip :: [a] -> [a] -> [(a,a)]
zip [] _ = []
zip _ [] = []
zip (x:xs) (y:ys) = (x,y) : zip xs ys

Ранее я спрашивал на #haskell, может ли «zip» быть реализован с использованием только «foldr», без рекурсии, без сопоставления с образцом. Немного подумав, мы заметили, что рекурсию можно устранить с помощью продолжений:

zip' :: [a] -> [a] -> [(a,a)]
zip' = foldr cons nil
    where
        cons h t (y:ys) = (h,y) : (t ys)
        cons h t []     = []
        nil             = const []

У нас все еще остается сопоставление с образцом. После еще нескольких нейронных тостов я пришел к неполному ответу, который мне показался логичным:

zip :: [a] -> [a] -> [a]
zip a b = (zipper a) (zipper b) where
    zipper = foldr (\ x xs cont -> x : cont xs) (const [])

Он возвращает плоский список, но выполняет застежку-молнию. Я был уверен, что это имеет смысл, но Haskell жаловался на тип. Я начал тестировать его на нетипизированном лямбда-калькуляторе, и он сработал. Почему Haskell не может принять мою функцию?

Ошибка:

zip.hs:17:19:
    Occurs check: cannot construct the infinite type:
      t0 ~ (t0 -> [a]) -> [a]
    Expected type: a -> ((t0 -> [a]) -> [a]) -> (t0 -> [a]) -> [a]
      Actual type: a
                   -> ((t0 -> [a]) -> [a]) -> (((t0 -> [a]) -> [a]) -> [a]) -> [a]
    Relevant bindings include
      b ∷ [a] (bound at zip.hs:17:7)
      a ∷ [a] (bound at zip.hs:17:5)
      zip ∷ [a] -> [a] -> [a] (bound at zip.hs:17:1)
    In the first argument of ‘foldr’, namely ‘cons’
    In the expression: ((foldr cons nil a) (foldr cons nil b))

zip.hs:17:38:
    Occurs check: cannot construct the infinite type:
      t0 ~ (t0 -> [a]) -> [a]
    Expected type: a -> (t0 -> [a]) -> t0 -> [a]
      Actual type: a -> (t0 -> [a]) -> ((t0 -> [a]) -> [a]) -> [a]
    Relevant bindings include
      b ∷ [a] (bound at zip.hs:17:7)
      a ∷ [a] (bound at zip.hs:17:5)
      zip ∷ [a] -> [a] -> [a] (bound at zip.hs:17:1)
    In the first argument of ‘foldr’, namely ‘cons’
    In the fourth argument of ‘foldr’, namely ‘(foldr cons nil b)’

person MaiaVictor    schedule 26.04.2015    source источник
comment
Какую ошибку выдает Haskell?   -  person JP Moresmau    schedule 26.04.2015
comment
Если все, на чем вы застряли, это сопоставление с образцом, просто используйте (,) как функцию, а head и tail вместо (y:ys) и, возможно, if/else?   -  person Benjamin Gruenbaum    schedule 26.04.2015
comment
Он жалуется на невозможность построить бесконечный тип. Возможно, это связано с тем, что я создаю продолжение, которое ожидает продолжение.   -  person MaiaVictor    schedule 26.04.2015
comment
кстати: некоторое время назад был задан очень похожий вопрос: questions/235148/implement-zip-using-foldr   -  person Random Dev    schedule 26.04.2015
comment
У Олега получилось, у Олега получилось!   -  person Daniel Wagner    schedule 26.04.2015
comment
К вашему сведению, я добавил ответ на дополнительный вопрос, который технически может считаться ответом и на этот.   -  person András Kovács    schedule 29.04.2015
comment
Что ж, я могу только надеяться, что кто-то, кто понимает Агду (один из остальных 11 из вас), выиграет от этого...! Хе. Конечно, выше моей досягаемости сейчас. Но я все еще поражен. Спасибо!   -  person MaiaVictor    schedule 29.04.2015
comment
@DanielWagner, я думаю, что лыжи тоже сделали это независимо. И не так давно была статья о таких вещах.   -  person dfeuer    schedule 18.05.2015


Ответы (3)


Что касается того, почему ваше определение не принято: посмотрите на это:

λ> :t \ x xs cont -> x : cont xs
 ... :: a -> r -> ((r -> [a]) -> [a])

λ> :t foldr
foldr :: (a' -> b' -> b') -> b' -> [a'] -> b'

поэтому, если вы хотите использовать первую функцию в качестве аргумента для foldr, вы получите (если вы соответствуете типам первого аргумента foldrs):

a' := a
b' := r
b' := (r -> [a]) -> [a]

что, конечно, является проблемой (поскольку r и (r -> [a]) -> [a] взаимно рекурсивны и должны быть равны b' )

Это то, что компилятор говорит вам

как это починить

Вы можете исправить свою идею, используя

newtype Fix a t = Fix { unFix :: Fix a t -> [a] }

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

При этом вы можете написать:

zipCat :: [a] -> [a] -> [a]
zipCat a b = (unFix $ zipper a) (zipper b) where
  zipper = foldr foldF (Fix $ const [])
  foldF x xs = Fix (\ cont -> x : (unFix cont $ xs))

и вы получаете:

λ> zipCat [1..4] [5..8]
[1,5,2,6,3,7,4,8]

что (я думаю) вы хотели.

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

person Random Dev    schedule 26.04.2015
comment
Использование Fix эквивалентно использованию общей рекурсии или комбинатора с фиксированной точкой, поэтому я думаю, что это противоречит критерию отсутствия рекурсии OP. - person András Kovács; 27.04.2015
comment
@ AndrásKovács вполне возможно, да (хотя вы могли бы поспорить о типах и функциях или о том, что foldr тоже может быть рекурсивным, ...) - вы видите другой способ заставить компилятор принять OP zip, которые не слишком отклоняются из кода? - person Random Dev; 27.04.2015
comment
Я еще не смотрел код OP ... Что касается foldr, он настолько нерекурсивен, насколько это возможно, поскольку он соответствует как спискам, закодированным Черчем в Системе F (и все доказуемо там заканчивается), так и правилу вычисления для списков в теории типов. - person András Kovács; 27.04.2015
comment
@ AndrásKovács tbh: я думаю, что это не место для споров, но, насколько мне известно, foldr все еще реализован в рекурсивной манере (на Haskell) - но, опять же, дело не в этом, не так ли? - person Random Dev; 27.04.2015
comment
На самом деле вроде так, но я впечатлен, что тебе удалось это напечатать. - person MaiaVictor; 28.04.2015
comment
@ AndrásKovács (и другие ...) Привет, я добавил новый ответ, может быть, вы захотите его проверить. Он использует более простой рекурсивный тип (t на самом деле избыточен), а в Haskell уже есть рекурсивные типы, так что мне не нужно думать о Fix самому! - person Will Ness; 29.04.2015

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

Вот ваш код снова, для вашего "ткацкого почтового индекса" (я пишу tr для "типа r", аналогично tq для "типа q"; я всегда использую "r" для рекурсивного результата аргумент функции объединения в foldr определениях, как мнемонический прием):

zipw :: [a] -> [a] -> [a]
zipw xs ys = (zipper xs) (zipper ys) where
    zipper xs q = foldr (\ x r q -> x : q r) (const []) xs q
                        --- c -------------- --- n ----

 -- zipper [x1,x2,x3] (zipper ys) =
 -- c x1 (c x2 (c x3 n)) (zipper ys)
         --- r --------  --- q -----  tr ~ tq ; q r :: [a]
                                      --     => r r :: [a]
                                      -- => r :: tr -> [a] 
                                      --   tr ~  tr -> [a]    

Итак, это бесконечный тип. Haskell не допускает этого для произвольного типа (это то, что означают переменные типа).

Но типы данных Haskell действительно допускают рекурсию. Списки, деревья и т. д. — все обычные типы рекурсивны. Это разрешено:

data Tree a = Branch (Tree a) (Tree a)

Здесь у нас действительно один и тот же тип в обеих частях уравнения, точно так же, как у нас есть tr в обеих частях эквивалентности типов, tr ~ tr -> [a]. Но это особый тип, а не произвольный.

Поэтому мы просто объявляем это так, следуя приведенному выше «уравнению»:

newtype TR a = Pack { unpack :: TR a -> [a] } 
           -- unpack :: TR a -> TR a -> [a]

Что такое тип Tree a? Это «что-то», что входит в Branch, то есть в Tree a. Данное дерево не обязательно должно строиться бесконечно, потому что undefined тоже имеет тип Tree a.

Что такое тип TR a? Это «что-то», что входит в TR a -> [a], то есть TR a. Заданное TR a не обязательно должно конструироваться бесконечно, потому что const [] может быть и типа TR a.

Наш подражатель рекурсивному типу tr ~ tr -> [a] стал настоящим определением рекурсивного типа newtype TR a = Pack { TR a -> [a] }, скрывающимся за конструктором данных Pack (от которого компилятор избавится благодаря использованию ключевого слова newtype, но это лишняя деталь; он работает с data слишком).

Здесь Haskell обрабатывает рекурсивность. Теоретики шрифтов любят заниматься этим сами, с Fix и еще чем-то; но пользователь Haskell уже имеет это на языке. Нам не нужно понимать, как он реализован, чтобы уметь его использовать. Не нужно изобретать велосипед, пока мы сами не захотим построить его.

Итак, zipper xs имел тип tr; теперь он становится TR a, так что это то, что новое zipper xs должно возвращать "упакованную" функцию создания списка. Комбинирующая функция foldr должна возвращать то, что возвращает вызов zipper (в силу определения foldr). Чтобы применить упакованную функцию, нам нужно сначала ее unpack:

zipw :: [a] -> [a] -> [a]
zipw xs ys = unpack (zipper xs) (zipper ys)
    where
    zipper :: [a] -> TR a
    zipper = foldr (\ x r -> Pack $ \q -> x : unpack q r)
                   (Pack $ const [])
person Will Ness    schedule 29.04.2015
comment
хорошо - да, t избыточен - tbh: я просто использовал быстрый copy&paste вывод компилятора и Fix это решение;) - person Random Dev; 29.04.2015
comment
@CarstenKönig спасибо; :) Я подумал использовать это понимание в другой задаче; пока не повезло. типы более сложные... - person Will Ness; 29.04.2015
comment
да, думаю, да, но это должно быть очень легко перевести [a,b,c,d,e] -> [(a,b),(c,d),...] и смешать с некоторым ADT (либо приходит на ум) - person Random Dev; 29.04.2015
comment
@CarstenKönig Я понял! Я определяю два взаимно рекурсивных типа: ideone.com/tgAK0A. Скоро опубликую ответ!! - person Will Ness; 29.04.2015
comment
хорошо - хорошая работа - но вам действительно не следует добавлять еще один ответ - просто добавьте его к тому, который вы получили - не поймите меня неправильно, вы получите еще один голос от меня, поместите сюда некоторых людей ТАК действительно злитесь на это вещи - person Random Dev; 29.04.2015
comment
@CarstenKönig Я думаю, можно добавить второй ответ, который отличается; Я думаю, что мой ответ достаточно отличается от вашего, в основном в понимании превращения эквивалентности типов в определение рекурсивного типа. Я не мог понять, что это за штуки с Fix. Кроме того, это длинный ответ. (и если вы имели в виду мой другой ответ, то он тоже длинный) :) :) -- Но то, что я имел в виду, я добавляю не здесь, конечно; но на мой дополнительный вопрос. - person Will Ness; 29.04.2015
comment
без обид - у меня нет проблем, если вы добавите еще один ответ - просто странно иметь 4 ответа, где 3 от одного и того же человека - но поздравляю - хорошая работа - person Random Dev; 29.04.2015
comment
@CarstenKönig, вы абсолютно правы насчет трех ответов. и спасибо! это было мило. :) - person Will Ness; 29.04.2015
comment
@CarstenKönig Как я писал там, перевод эквивалентностей типов в определения новых типов был чисто механическим. Может быть, компилятор должен сделать это за нас, как вы думаете? (хотя пространство имен типов было бы замусорено... но там могли бы использоваться некоторые искаженные имена...) - person Will Ness; 29.04.2015

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

Это обман? Нет, если maybe и bool разрешены как есть; тогда мы также должны разрешить list (также в разделе Data.List.Extra extra),

list :: b -> (a -> [a] -> b) -> [a] -> b 
list n c []     = n
list n c (x:xs) = c x xs

все равно; так что мы можем иметь, в вашем zip' определении,

cons h t = list [] (\y ys -> (h,y) : t ys)

or e.g.

         = list [] (uncurry ((:).(h,).fst <*> t.snd))
         = list [] (curry $ uncurry (:) . ((h,) *** t))
         = list [] (flip ((.) . (:) . (h,)) t)
         = list [] ((. t) . (:) . (h,))

если вы предпочитаете такие вещи.

Что касается вашей ошибки, бесконечный тип часто указывает на самоприменение; действительно, независимо от того, что возвращает ваш zipper, вы применяете его к себе, в своем

zip a b = (zipper a) (zipper b)  where ....

Я попытался настроить ваше определение и придумал

zipp :: [a] -> [b] -> [(a,b)]
zipp xs ys = zip1 xs (zip2 ys)
  where
     -- zip1 :: [a] -> tq -> [(a,b)]          -- zip1 xs :: tr ~ tq -> [(a,b)]
     zip1 xs q = foldr (\ x r q -> q x r ) n xs q 
                       -------- c --------
     n    q  = []

     -- zip2 :: [b] -> a -> tr -> [(a,b)]     -- zip2 ys :: tq ~ a -> tr -> [(a,b)]
     zip2 ys x r = foldr (\ y q x r -> (x,y) : r q ) m ys x r  
                         ---------- k --------------
     m  x r  = []

{-
  zipp [x1,x2,x3] [y1,y2,y3,y4]

= c x1 (c x2 (c xn n)) (k y1 (k y2 (k y3 (k y4 m))))
       ---------------       ----------------------
        r                     q

= k y1 (k y2 (k y3 (k y4 m))) x1 (c x2 (c xn n))
       ----------------------    ---------------
        q                         r
-}

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

Сейчас нет (сразу очевидного) самоприложения, но тип продолжения, которое получает первый zip, зависит от типа самого этого первого zip; так что все еще есть круговая зависимость: tq находится по обе стороны от эквивалентности типа в tq ~ a -> tr -> [(a,b)] ~ a -> (tq -> [(a,b)]) -> [(a,b)].

Действительно, это две ошибки типа, которые я получаю (первая относится к типу tr),

Occurs check: cannot construct the infinite type:
  t1 ~ (a -> t1 -> [(a, b)]) -> [(a, b)]          -- tr

Occurs check: cannot construct the infinite type:
  t0 ~ a -> (t0 -> [(a, b)]) -> [(a, b)]          -- tq

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

person Will Ness    schedule 26.04.2015