WedX - журнал о программировании и компьютерных науках

Haskell: исправлять или не исправлять

Недавно я узнал о Data.Function.fix, и теперь я хочу применять везде. Например, всякий раз, когда я вижу рекурсивную функцию, я хочу ее «fix». Так что в основном мой вопрос в том, где и когда мне его использовать.

Чтобы сделать его более конкретным:

1) Предположим, у меня есть следующий код для факторизации n:

f n = f' n primes
  where
    f' n (p:ps) = ...
    -- if p^2<=n: returns (p,k):f' (n `div` p^k) ps for k = maximum power of p in n
    -- if n<=1: returns []
    -- otherwise: returns [(n,1)]

Если я его перепишу в терминах fix, я что-нибудь получу? Что-то теряете? Возможно ли, что, переписав явную рекурсию в fix-версию, я разрешу или наоборот создам переполнение стека?

2) При работе со списками есть несколько решений: рекурсия / исправление, foldr / foldl / foldl 'и, возможно, что-то еще. Есть ли какое-либо общее руководство / совет о том, когда использовать каждый из них? Например, не могли бы вы переписать приведенный выше код, используя foldr над бесконечным списком простых чисел?

Есть, наверное, другие важные вопросы, не затронутые здесь. Также приветствуются любые дополнительные комментарии, связанные с использованием fix.


  • Я недавно узнал о Data.Function.fix, и теперь мне кажется, что я хочу применять его везде. Это делает вас тогда программистом Boy Scout Haskell - willamette.edu/~fruehr/haskell/evolution.html#boyscout 11.02.2014
  • Вы должны использовать foldr и foldl', если можете, fix или явную рекурсию, если необходимо. Последние менее эффективны, поэтому читатель вашего кода может вывести из него больше свойств. 11.02.2014
  • @stephentetley Это отличная ссылка, но я это уже видел! Собственно, в первый раз, когда я это увидел (и изучил!), У меня был еще один вопрос о паре этих реализаций, но, может быть, в другой раз ... В любом случае, реализация бойскаута - это именно то, что я обычно делаю сейчас в большинстве случаев. мой код. :) 11.02.2014
  • @TomEllis Если бы вы нашли время, чтобы подробнее рассказать об этом, я был бы очень признателен. Джозеф уже дал мне очень хороший совет относительно моего первого вопроса, и я все еще надеюсь составить более общие рекомендации, основанные на опыте мастеров. 11.02.2014
  • остерегайтесь разницы между _Y f = f (_Y f) (рекурсия, значение - копирование) и fix f = x where x = f x (corecursion, ссылка - совместное использование). 11.02.2014
  • @WillNess Спасибо за этот момент. Об этом обязательно нужно помнить. Я думаю, что если вы опубликуете это как ответ с простым примером, это также будет отличным ответом на мой вопрос. 12.02.2014

Ответы:


1

Одна вещь, которую можно получить, написав в явно fixed форме, - это оставить рекурсию «открытой».

factOpen :: (Integer -> Integer) -> Integer -> Integer
factOpen recur 0 = 1
factOpen recur n = n * recur (pred n)

Мы можем использовать fix, чтобы регулярно fact возвращаться

fact :: Integer -> Integer
fact = fix factOpen

Это работает, потому что fix эффективно передает саму функцию в качестве первого аргумента. Однако, оставив рекурсию открытой, мы можем изменить, какая функция будет «передана обратно». Лучшим примером использования этого свойства является использование чего-то вроде _ 7_ из memoize пакета.

factM :: Integer -> Integer
factM = memoFix factOpen

И теперь factM имеет встроенную память.

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

10.02.2014
  • Очень интересно. И мощный! 11.02.2014

  • 2

    Я хотел бы упомянуть еще одно использование fix; Предположим, у вас есть простой язык, состоящий из литералов сложения, отрицательных и целых чисел. Возможно, вы написали синтаксический анализатор, который принимает String и выводит Tree:

    data Tree = Leaf String | Node String [Tree]
    parse :: String -> Tree
    
    -- parse "-(1+2)" == Node "Neg" [Node "Add" [Node "Lit" [Leaf "1"], Node "Lit" [Leaf "2"]]]
    

    Теперь вы хотите оценить свое дерево до единственного целого числа:

    fromTree (Node "Lit" [Leaf n]) = case reads n of {[(x,"")] -> Just x; _ -> Nothing}
    fromTree (Node "Neg" [e])      = liftM negate (fromTree e) 
    fromTree (Node "Add" [e1,e2])  = liftM2 (+) (fromTree e1) (fromTree e2)
    

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

    fromTree' (Node "Mul" [e1, e2]) = ...
    fromTree' e                     = fromTree e
    

    Но тогда Mul может появиться только один раз, на верхнем уровне выражения, поскольку вызов fromTree не будет знать о случае Node "Mul". Tree "Neg" [Tree "Mul" a b] не будет работать, поскольку исходный fromTree не имеет шаблона для "Mul". Однако, если та же функция написана с использованием fix:

    fromTreeExt :: (Tree -> Maybe Int) -> (Tree -> Maybe Int)
    fromTreeExt self (Node "Neg" [e]) = liftM negate (self e)
    fromTreeExt .... -- other cases
    
    fromTree = fix fromTreeExt
    

    Тогда возможно расширение языка:

    fromTreeExt' self (Node "Mul" [e1, e2]) = ...
    fromTreeExt' self e                     = fromTreeExt self e
    
    fromTree' = fix fromTreeExt'
    

    Теперь расширенный fromTree' будет правильно оценивать дерево, поскольку self в fromTreeExt' относится ко всей функции, включая случай "Mul".

    Этот подход используется здесь (приведенный выше пример представляет собой адаптированную версию использования в статье).

    11.02.2014
  • Здорово. Это и ответ Джозефа - два отличных примера того, когда исправление кажется полезным. 12.02.2014
  • Разве это не fromTreeExt self (Node "Neg" [e]) = liftM negate (self e)? Использование self вместо fromTree. 14.02.2014
  • @DanielVelkov Да, опечатка. 14.02.2014

  • 3

    Обратите внимание на разницу между _Y f = f (_Y f) (рекурсия, значение - копирование) и fix f = x where x = f x (corecursion, ссылка - совместное использование).

    Привязки let и where Haskell рекурсивны: одно и то же имя на LHS и RHS относится к одному и тому же объекту. Ссылка является общедоступной.

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

    Пример, простые числа, вычисленные

    2 : _Y ((3:) . gaps 5 . _U . map (\p-> [p*p, p*p+2*p..]))
    
    -- gaps 5 == ([5,7..] \\)
    -- _U     == sort . concat
    

    либо повторно использовать свой собственный вывод (с fix, let g = ((3:)...) ; ps = g ps in 2 : ps), либо создать отдельный источник простых чисел для себя (с _Y, let g () = ((3:)...) (g ()) in 2 : g ()).

    Смотрите также:


    Или, используя обычный пример факториальной функции,

    gen rec n = n<2 -> 1 ; n * rec (n-1)            -- "if" notation
    
    facrec   = _Y gen 
    facrec 4 = gen (_Y gen) 4 
             = let {rec=_Y gen} in (\n-> ...) 4
             = let {rec=_Y gen} in (4<2 -> 1 ; 4*rec 3)
             = 4*_Y gen 3
             = 4*gen (_Y gen) 3
             = 4*let {rec2=_Y gen} in (3<2 -> 1 ; 3*rec2 2) 
             = 4*3*_Y gen 2                         -- (_Y gen) recalculated
             .....
    
    fac      = fix gen 
    fac 4    = (let f = gen f in f) 4             
             = (let f = (let {rec=f} in (\n-> ...)) in f) 4
             = let {rec=f} in (4<2 -> 1 ; 4*rec 3)  -- f binding is created
             = 4*f 3
             = 4*let {rec=f} in (3<2 -> 1 ; 3*rec 2)  
             = 4*3*f 2                              -- f binding is reused
             .....
    
    12.02.2014

    4

    1) fix - это просто функция, она улучшает ваш код, когда вы используете рекурсию. Это делает ваш код красивее. Например, посетите страницу Haskell Wikibook - Исправление и рекурсия.

    2) Вы знаете, что такое foldr? Похоже, что foldr бесполезна при факторизации (или я не понял, что вы имеете в виду). Вот простое разложение без исправления:

    fact xs = map (\x->takeWhile (\y->y/=[]) x) . map (\x->factIt x) $ xs
     where factIt n = map (\x->getFact x n []) [2..n]
       getFact i n xs
        | n `mod` i == 0 = getFact i (div n i) xs++[i]
        | otherwise      = xs
    

    и с исправлением (это работает точно так же, как и предыдущее):

    fact xs = map (\x->takeWhile (\y->y/=[]) x) . map (\x->getfact x) $ xs
      where getfact n  = map (\x->defact x n) [2..n]
           defact i n  = 
            fix (\rec j k xs->if(mod k j == 0)then (rec j (div k j) xs++[j]) else xs ) i n []
    

    Это некрасиво, потому что в данном случае исправление - не лучший выбор (но всегда есть кто-то, кто может написать его лучше).

    10.02.2014
    Новые материалы

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..

    Учебные заметки: создание моего первого пакета Node.js
    Это мои обучающие заметки, когда я научился создавать свой самый первый пакет Node.js, распространяемый через npm. Оглавление Глоссарий I. Новый пакет 1.1 советы по инициализации..

    Забудьте о Matplotlib: улучшите визуализацию данных с помощью умопомрачительных функций Seaborn!
    Примечание. Эта запись в блоге предполагает базовое знакомство с Python и концепциями анализа данных. Привет, энтузиасты данных! Добро пожаловать в мой блог, где я расскажу о невероятных..

    ИИ в аэрокосмической отрасли
    Каждый полет – это шаг вперед к великой мечте. Чтобы это происходило в их собственном темпе, необходима команда астронавтов для погони за космосом и команда технического обслуживания..


    Для любых предложений по сайту: [email protected]