2013-09-17 2 views
2

Меня интересуют круговые/бесконечные списки в Haskell. Я читал около let..in заявления и , где предложения, и у меня возникает ощущение, что они играют важную роль, но я до сих пор не понимаю.Три способа создания кругового списка в haskell

Чтобы быть конкретным, я написал три версии кода для бесконечного списка чередующихся 0 и 1. Я полагаю, что это то, что имеется в виду в круговом списке в Haskell.

cyclic = let x = 0 : y 
      y = 1 : x 
     in x 

cyclic' = [0,1] ++ cyclic' 

cyclic'' = [0,1] ++ x 
    where x = cyclic'' 

Второй кажется простым, коротким и наиболее естественным для меня, но, возможно, это потому, что я до сих пор не совсем комфортно с let..in и время.

Все ли эти три списка представлены одинаково?

+0

В то время как я нахожусь в этом, позвольте мне добавить еще одну версию: 'cyclic3 = let x = [0,1] ++ yy = x in x' – sitiposit

+0

Вы можете создавать бесконечные списки с функциями 'Data.List' 'repeat' и' cycle' – viorior

ответ

2

Вы можете легко проверить это самостоятельно, скомпилировав с помощью -fext-core, который напишет соответствующий файл .hrc для каждого из ваших исходных файлов, который содержит промежуточную «базовую langauge» для Haskell. В этом случае, если мы скомпилировать этот код, мы получаем довольно трудно читать код:

%module main:Main 

    %rec 
    {main:Main.cycliczqzq :: ghczmprim:GHCziTypes.ZMZN 
          ghczmprim:GHCziTypes.Int = 
    base:GHCziBase.zpzp @ ghczmprim:GHCziTypes.Int 
    (ghczmprim:GHCziTypes.ZC @ ghczmprim:GHCziTypes.Int 
     (ghczmprim:GHCziTypes.Izh (0::ghczmprim:GHCziPrim.Intzh)) 
     (ghczmprim:GHCziTypes.ZC @ ghczmprim:GHCziTypes.Int 
     (ghczmprim:GHCziTypes.Izh (1::ghczmprim:GHCziPrim.Intzh)) 
     (ghczmprim:GHCziTypes.ZMZN @ ghczmprim:GHCziTypes.Int))) 
    main:Main.cycliczqzq}; 

    %rec 
    {main:Main.cycliczq :: ghczmprim:GHCziTypes.ZMZN 
         ghczmprim:GHCziTypes.Int = 
    base:GHCziBase.zpzp @ ghczmprim:GHCziTypes.Int 
    (ghczmprim:GHCziTypes.ZC @ ghczmprim:GHCziTypes.Int 
     (ghczmprim:GHCziTypes.Izh (0::ghczmprim:GHCziPrim.Intzh)) 
     (ghczmprim:GHCziTypes.ZC @ ghczmprim:GHCziTypes.Int 
     (ghczmprim:GHCziTypes.Izh (1::ghczmprim:GHCziPrim.Intzh)) 
     (ghczmprim:GHCziTypes.ZMZN @ ghczmprim:GHCziTypes.Int))) 
    main:Main.cycliczq}; 
    arot :: ghczmprim:GHCziTypes.Int = 
    ghczmprim:GHCziTypes.Izh (0::ghczmprim:GHCziPrim.Intzh); 
    a1rou :: ghczmprim:GHCziTypes.Int = 
    ghczmprim:GHCziTypes.Izh (1::ghczmprim:GHCziPrim.Intzh); 

    %rec 
    {main:Main.cyclic :: ghczmprim:GHCziTypes.ZMZN 
         ghczmprim:GHCziTypes.Int = 
    ghczmprim:GHCziTypes.ZC @ ghczmprim:GHCziTypes.Int arot yrov; 
    yrov :: ghczmprim:GHCziTypes.ZMZN ghczmprim:GHCziTypes.Int = 
    ghczmprim:GHCziTypes.ZC @ ghczmprim:GHCziTypes.Int a1rou 
    main:Main.cyclic}; 

Если мы «очистить» это немного, и удалить некоторые из ghczmprim с и этажерки, мы получаем

{cycliczqzq :: ZMZN Int = zpzp @ Int (ZC @ Int (Izh (0 :: Intzh)) (ZC @ Int (Izh (1 :: Intzh)) (ZMZN @ Int))) cycliczqzq}; 

{cycliczq :: ZMZN Int = zpzp @ Int (ZC @ Int (Izh (0 :: Intzh)) (ZC @ Int (Izh (1 :: Intzh)) (ZMZN @ Int))) cycliczq}; 

arot :: Int = Izh (0 :: Intzh); 
a1rou :: Int = Izh (1 :: Intzh); 

{cyclic :: ZMZN Int = ZC @ Int arot yrov; 
yrov :: ZMZN Int = ZC @ Int a1rou cyclic}; 

в которой мы можем довольно легко сказать, что cycliczqzq и cycliczq имеют точно такое же определение, и мы можем сказать, что они коррелируют с cyclic'' и cyclic' соответственно. Для cyclic мы можем сказать, что он определяется другим способом.

EDIT:

Чтобы добавить четвертое определение

cyclic4 :: [Int] 
cyclic4 = 
    let xx = [1, 0] ++ yy 
     yy = xx 
    in xx 

И я переименовал их все, чтобы быть cyclic1 через cyclic4 для лучшей читаемости. Выход -fext-core со всем мусором удаляемого

{cyclic4 :: ZMZN Int = zpzp @ Int (ZC @ Int (Izh (1::Intzh)) (ZC @ Int (Izh (0::Intzh)) (ZMZN @ Int))) cyclic4}; 
{cyclic3 :: ZMZN Int = zpzp @ Int (ZC @ Int (Izh (0::Intzh)) (ZC @ Int (Izh (1::Intzh)) (ZMZN @ Int))) cyclic3}; 
{cyclic2 :: ZMZN Int = zpzp @ Int (ZC @ Int (Izh (0::Intzh)) (ZC @ Int (Izh (1::Intzh)) (ZMZN @ Int))) cyclic2}; 

aroR :: Int = Izh (0::Intzh); 
a1roS :: Int = Izh (1::Intzh); 
{cyclic1 :: ZMZN Int = ZC @ Int aroR yroT; 
yroT :: ZMZN Int = ZC @ Int a1roS cyclic1}; 

Итак, мы видим, что последние три определения фактически заводятся в тот же байт-код.

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

8

Я хотел бы отметить важное различие:

cyclic' = [0,1] ++ cyclic' 

cyclic'' = [0,1] ++ x 
    where x = cyclic'' 

Эти две функции рекурсивны в том смысле, что определение ссылок самой функции. Но

cyclic = let x = 0 : y 
      y = 1 : x 
     in x 

нет! Он использует x внутренне, что является рекурсивным, но целого cyclic нет - в его определении нет ссылки на себя. Именно поэтому они отличаются друг от друга при компиляции на основной язык.

Это имеет некоторые важные практические последствия, а именно, что рекурсивные функции не могут быть inlined, но не рекурсивные могут. Вот почему вы часто видите определения как

fix :: (a -> a) -> a 
fix f = let x = f x in x 

(от source из Data.Function) вместо более прямой

fix f = f (fix f) 

(я не совсем уверен, почему GHC не делает это автоматически.)

Просто для полноты картины, есть и другие короткие пути, как определить cyclic:

-- recursive: 
cyclic = 0 : 1 : cyclic 
-- non-recursive: 
cyclic = let x = 0 : 1 : x in x 
cyclic = cycle [0,1] 
cyclic = fix ([0,1] ++) 

Update: Приведем пример: Давайте определим

-- The `const` combinator, defined explicitly so that 
-- it gets inlined. 
k :: a -> b -> a 
k x y = x 

fix, fix' :: (a -> a) -> a 
fix f  = let x = f x in x 
fix' f = f (fix' f) 

main = do 
    print $ fix (k 1) 
    print $ fix' (k 2) 

Так fix' рекурсивно, а fix не является (определение fix копируется из Data.Function). Что происходит, когда мы используем fix'? Компилятор не может встроить его, потому что после вложения он снова получит выражение, которое содержит fix'. Должен ли он включить его во второй раз? А потом в третий раз? Поэтому рекурсивные функции никогда не создаются по дизайну. С другой стороны, fix не является рекурсивным, поэтому fix (k 1) вставляется в let x = k 1 x in x. Затем компилятор строит k, что приводит к let x = 1 in x, который заменяется просто на 1.

Мы можем проверить выше требование сбросов скомпилированного кода на языке ядра:

$ ghc -ddump-simpl -dsuppress-all Main.hs 
[1 of 1] Compiling Main    (Main.hs, Main.o) 

==================== Tidy Core ==================== 
Result size of Tidy Core = {terms: 24, types: 27, coercions: 0} 

Rec { 
fix'_reJ 
fix'_reJ = \ @ a_c f_aeR -> f_aeR (fix'_reJ f_aeR) 
end Rec } 

main 
main = 
    >> 
    $fMonadIO 
    ($ (print $fShowInteger) (__integer 1)) 
    ($ (print $fShowInteger) 
     (fix'_reJ 
      (let { 
      x_aeN 
      x_aeN = __integer 2 } in 
      \ _ -> x_aeN))) 

main 
main = runMainIO main 
+0

Различие очень важно. Функционально вы получаете то же самое, но вы должны знать о том, что происходит внутри машины. –

+1

Объяснение слишком короткое. Не могли бы вы рассказать? x = f x рекурсивна. Итак, что вы покупаете? Вложение исправления? Но «внутренность» исправления в любом случае не может быть встроена, поскольку она рекурсивна. –

+0

О, круто! x = f x означает, что fix g компилируется в g, вызывающий себя; но определение fix g = g (fix g) скомпилируется в g-вызов fix, добавляя косвенность на каждом этапе рекурсии. –

1

расширяющегося @PetrPudlak пример проливает:

fix f = let x = f x in x 

fix' f = f (fix' f) 

k :: (Int -> Int) -> Int -> Int 
k f 0 = 1 
k f i = f $ i-1 

main = do 
     print $ fix k 10 
     print $ fix' k 10 

Compile:

ghc -ddump-simpl -dsuppress-all c.hs 

==================== Tidy Core ==================== 
Result size = 59 

k_ra0 
k_ra0 = 
    \ f_aa4 ds_dru -> 
    case ds_dru of wild_X6 { I# ds1_drv -> 
    case ds1_drv of _ { 
     __DEFAULT -> $ f_aa4 (- $fNumInt wild_X6 (I# 1)); 
     0 -> I# 1 
    } 
    } 

main 
main = 
    >> 
    $fMonadIO 
    ($ (print $fShowInt) 
     (letrec { 
      x_ah6 
      x_ah6 = k_ra0 x_ah6; } in 
     x_ah6 (I# 10))) 
    ($ (print $fShowInt) 
     (letrec { 
      fix'_ah0 
      fix'_ah0 = \ f_aa3 -> f_aa3 (fix'_ah0 f_aa3); } in 
     fix'_ah0 k_ra0 (I# 10))) 

main 
main = runMainIO main 

Здесь ясно, что первый случай, fix, позволяет построить con один раз, который повторно используется в рекурсии, но второй случай, fix', должен построить новую заглушку на каждом этапе рекурсии.

 Смежные вопросы

  • Нет связанных вопросов^_^