Комбинатор Y, бесконечные типы и анонимная рекурсия в Haskell

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

msss :: (Ord a, Num a) => [a] -> a
msss = f 0 0

f gmax _ [] = gmax
f gmax lmax (x:xs) = 
  let g = max (lmax + x)
  in  f (g gmax) (g 0) xs

Вы вызываете функцию-оболочку msss, которая затем вызывает f, который, в свою очередь, фактически выполняет всю работу. Решение хорошее и афаик работает правильно. Если бы по какой-то причине мне пришлось решать задачу максимальной суммы подпоследовательностей в производственном коде, я бы сделал это именно так.

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

Теперь я столкнулся с классической проблемой: как сделать анонимную рекурсию? Как вы делаете рекурсию, если вы не можете давать имена функциям? К счастью, отцы компьютеров решили эту проблему много лет назад, открыв комбинаторы с фиксированной точкой, наиболее популярными из которых являются Y Combinator.

Я делал различные попытки настроить комбинатор Y, но они не могли пройти мимо компилятора.

msss' :: [Int] -> Int
msss' = (\y f x -> f (y y f) x) 
        (\y f x -> f (y y f) x) 
        (\g' gmax lmax list -> if list == [] 
                               then gmax 
                               else g' (max gmax lmax + head list) 
                                       (max 0    lmax + head list) 
                                       tail list)

просто дает

Prelude> :l C:\maxsubseq.hs
[1 of 1] Compiling Main             ( C:\maxsubseq.hs, interpreted )

C:\maxsubseq.hs:10:29:
    Occurs check: cannot construct the infinite type:
      t0 = t0 -> (([Int] -> Int) -> [Int] -> Int) -> [Int] -> Int
    In the first argument of `y', namely `y'
    In the first argument of `f', namely `(y y f)'
    In the expression: f (y y f) x

C:\maxsubseq.hs:11:29:
    Occurs check: cannot construct the infinite type:
      t0 = t0 -> (([Int] -> Int) -> [Int] -> Int) -> [Int] -> Int
    In the first argument of `y', namely `y'
    In the first argument of `f', namely `(y y f)'
    In the expression: f (y y f) x

C:\maxsubseq.hs:12:14:
    The lambda expression `\ g' gmax lmax list -> ...'
    has four arguments,
    but its type `([Int] -> Int) -> [Int] -> Int' has only two
    In the second argument of `\ y f x -> f (y y f) x', namely
      `(\ g' gmax lmax list
          -> if list == [] then
                 gmax
             else
                 g' (max gmax lmax + head list) (max 0 lmax + head list) tail list)'
    In the expression:
      (\ y f x -> f (y y f) x)
        (\ y f x -> f (y y f) x)
        (\ g' gmax lmax list
           -> if list == [] then
                  gmax
              else
                  g' (max gmax lmax + head list) (max 0 lmax + head list) tail list)
    In an equation for `msss'':
        msss'
          = (\ y f x -> f (y y f) x)
              (\ y f x -> f (y y f) x)
              (\ g' gmax lmax list
                 -> if list == [] then
                        gmax
                    else
                        g' (max gmax lmax + head list) (max 0 lmax + head list) tail list)
Failed, modules loaded: none.

Переход с f (y y f) на f (y f) просто дает

C:\maxsubseq.hs:11:29:
    Couldn't match expected type `[Int] -> Int'
                with actual type `[Int]'
    Expected type: (([Int] -> Int) -> t1 -> t0) -> t2 -> t0
      Actual type: ([Int] -> Int) -> t1 -> t0
    In the first argument of `y', namely `f'
    In the first argument of `f', namely `(y f)'
Failed, modules loaded: none.

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

y f = f (y f)

msss' :: [Int] -> Int
msss' = y (\g' gmax lmax list -> if list == [] 
                                 then gmax 
                                 else g' (max gmax lmax + head list) 
                                         (max 0    lmax + head list) 
                                         tail list)

Вы можете заметить, что не так в том, что я делаю? Я в растерянности. Жалобы на создание бесконечных типов действительно раздражают меня, потому что я думал, что Haskell был посвящен именно этому. У него бесконечные структуры данных, так почему проблема с бесконечными типами? Я подозреваю, что это как-то связано с парадоксом, который показал, что нетипизированное лямбда-исчисление непоследовательно. Хотя я не уверен. Было бы хорошо, если бы кто-нибудь мог уточнить.

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


person TheIronKnuckle    schedule 29.11.2011    source источник
comment
Что не так со вторым определением (в котором y-комбинатор определяется отдельно)? вам просто нужно исправить определение типа или добавить 0 0 в конце   -  person is7s    schedule 30.11.2011


Ответы (3)


Вы не можете определить комбинатор Y таким образом в Haskell. Как вы заметили, это приводит к бесконечному типу. К счастью, он уже доступен в Data.Function как fix, где он определен с помощью let привязки:

fix f = let x = f x in x
person hammar    schedule 29.11.2011
comment
Конечно, есть НЕКОТОРЫЙ способ определить + использовать комбинатор Y, встроенный в haskell - person TheIronKnuckle; 29.11.2011
comment
@TheIronKnuckle: Это возможно, если вы введете newtype. - person hammar; 29.11.2011
comment
@TheIronKnuckle: Конечно, просто вставьте определение исправления: (\f -> let x = f x in x) - person opqdonut; 30.11.2011

Поскольку комбинатору Y требуются бесконечные типы, вам потребуются обходные пути, вроде этого.

Но я бы написал вашу msss функцию в виде однострочника, например:

msss = fst . foldr (\x (gmax, lmax) -> let g = max (lmax + x) in (g gmax, g 0)) (0, 0)
person Sjoerd Visscher    schedule 29.11.2011
comment
Обратите внимание, что бесконечные типы появляются только в слабых подмножествах систем типов Haskell, таких как Система F, в которой отсутствуют как рекурсивные типы, так и рекурсивные типы. - person nponeccop; 29.11.2011

Что ж, давайте подумаем об этом на минутку. Какой тип у этого лямбда-выражения?

(\y f x -> f (y y f) x)

Ну f - это функция (a -> b) -> a -> b, а x - какое-то значение b. Что это значит y? Учитывая то, что мы только что сказали о f,

(y y f) :: (a -> b)

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

Итак, y - это некая волшебная функция высшего порядка. И в качестве входных данных он принимает две функции. Так что это вроде как y :: f1 -> f2 -> f3. f2 имеет форму f, а f3 имеет тип результата, упомянутый выше.

y :: f1 -> ((a -> b) -> a -> b) -> (a -> b)

Вопрос в том ... что такое f1? Он должен быть таким же, как тип y. Вы видите, как это выходит за рамки возможностей системы типов Haskell? Тип определяется сам по себе.

f1 = f1 -> ((a -> b) -> a -> b) -> (a -> b)

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

msss' = (\f -> let x = f x in x) 
        (\g' gmax lmax list -> case list of
            [] -> gmax
            (x:xs) -> g' (max gmax lmax + x) (max 0 lmax + x) xs
        ) 0 0

Хотя imho, если max допустимо, то fix из Data.Function также должен быть допустимым. Если только вы не участвуете в каком-либо конкурсе, предназначенном только для прелюдии.

person Dan Burton    schedule 29.11.2011