簡體   English   中英

函數式編程:如何將用python編寫的fibonacci實現轉換為haskell

[英]Functional programming: How to convert an implementation of fibonacci written in python to haskell

所以,對於這個任務,我必須將python轉換為haskell。 我必須在haskell中實現的代碼是使用自頂向下迭代方法生成斐波那契序列的代碼。 問題是我在haskell上相當新,而且我不太清楚如何執行這個問題。

我在haskell中創建了一個while循環和一個函數。

Python代碼:

def fib_top_down_iter_with_cache(n, trace=False):
    fibDict = {1:1, 2: 1}
    (inp, stack) = (['fib', n], [])
    fib_top_down_iter_with_cache.function_calls = 0
    while inp:
        if trace: 
            print(fibDict, inp, stack)
        (inp, token) = (inp[:-1], inp[-1])
        if isinstance(token, int):
            stack = [token] + stack
        elif token == 'fib':
            (n, stack) = (stack[0], stack[1:])
            fib_top_down_iter_with_cache.function_calls += 1
            if n in fibDict:
                inp = inp + [fibDict[n]]
            else:
                inp = inp + ['cache', n, '+', 'fib', n - 1, 'fib', n - 2]
        elif token == '+':
            (n1, n2, stack) = (stack[0], stack[1], stack[2:])
            inp = inp + [n1 + n2]
        elif token == 'cache':
            (n1, n2, stack) = (stack[0], stack[1], stack[1:])
            fibDict[n1] = n2
        else:
            raise Exception()
    return stack[0]

我在haskell中嘗試過的:

while :: state -> (state -> Bool) -> (state -> state) -> (state -> result) -> result
while state eval bodyFn extractRes
    | eval state = while (bodyFn state) eval bodyFn extractRes
    | otherwise = extractRes state

data Input
    = Word String | Num Integer
    deriving (Eq, Show)

word :: Input -> String
word (Word x) = x

value :: Input -> Integer
value (Num x) = x

fibTopDownIterWithCache :: Integer -> Integer
fibTopDownIterWithCache n = fibTopDownIterWithCache []
fibTopDownIterWithCache n cache = while ([Word "fib", Num n], [])
                                        (-- Just don't know how to implement the rest)

因此,緩存必須實現為Data.Map數據類型,並且我必須將緩存作為函數的屬性附加(我想我已經完成了)。 然后我必須將緩存作為附加參數傳遞。

預期值只是第n個斐波納契值。

回想起來,我有點尷尬,因為你非常清楚地詢問“自上而下的迭代”技術,我說的都不是。 哦,我希望這在某種程度上仍然有用。

python代碼中的許多工作都是使用顯式堆棧和while循環來模擬遞歸。 在Haskell中,我們只使用常規遞歸; 你不會得到堆棧溢出或類似的東西1 斐波那契的遞歸定義當然是:

fib :: Int -> Integer
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

這很小。 我們所要做的就是為它添加緩存。

顯式緩存傳遞

如果我們試圖保持接近原始代碼(但沒有完全模擬遞歸),我們應該將緩存作為參數傳遞。 也就是說,我們的函數將獲取緩存,並返回更新的緩存。

type Cache = Map.Map Int Integer

fib :: Int -> Cache -> (Integer, Cache)
fib 0 cache = (0, cache)
fib 1 cache = (1, cache)
fib n cache = 
    let (a, cache') = fib (n-1) cache in 
     ... -- Left as exercise
         -- Since we call fib twice, remember to pass the
         -- *updated* cache, not the original, to the second call

我確實推薦這個練習,即使在下一部分它會變得過時。

插話

事實證明,我們的Cache -passing fib的模式正是State monad捕獲的模式。 因此,我們可以這樣寫fib

 fib :: Int -> State Cache Integer 

實際上, State的定義基本上是:

 State sa = s -> (a, s) 

一些模newtype廢話。 如果我們在State Cache Integer替換,我們就會發現

 fib :: Int -> State Cache Integer :: Int -> Cache -> (Integer, Cache) 

就像我們原來的一樣! 所以,如果你做了這個練習,你基本上就知道State做了什么。

純記憶

傳遞緩存很好,但我們可以做得更好。 能不能輕易地看到原始fib定義的核心邏輯,而不用擔心線程狀態? 這就是Memoization頁面上的例子所說的:

memoized_fib :: Int -> Integer
memoized_fib = (map fib [0 ..] !!)
   where fib 0 = 0
         fib 1 = 1
         fib n = memoized_fib (n-2) + memoized_fib (n-1)

這種語言memoized_fib :我們將“真正的” fib定義隱藏在memoized_fibwhere子句中,而“real”定義則回調到其父memoized_fib 但原始功能仍然非常清晰,並沒有被細節所淹沒。

第一行包含一個操作符部分 ,以防您以前沒有看到過。 它只是語法糖,與memoization技術無關。

這種方式的工作方式是第一行創建一個(單個,程序全局2 )無限列表

[ fib 0, fib 1, fib 2, fib 3, fib 4, fib 5, ... ]

由於懶惰,沒有評估(這需要很長時間,不是嗎?)。 然后,當我們需要知道特定的斐波納契數時,比如4 ,我們索引到列表中,因此只評估該元素。 這會更新相應的thunk (延遲暫停計算)

 [ fib 0, fib 1, fib 2, fib 3, 3, fib 5, ... ]

因此,如果我們再次訪問第4個元素,它已經被評估過了。 當然,我們要求斐波納契數的原因之一是計算其他斐波納契數(因為fib遞歸到memoized_fib ),所以現在中間結果也將緩存在此列表中,因此計算得到指數加速。

索引到列表是O(n),因為Haskell列表本質上是鏈接列表。 所以備忘錄表有O(n)查找; 我們可以通過使用trie做得更好。 這就是我的圖書館數據 - memocombinators提供的,以及一些其他庫,如MemoTrie ,基本相同的東西略有不同的觀點。 使用這些庫,您可以使用與O(log n)備忘錄表相同的純memoization技術。 更多關於細節的信息

那么,這就是26行Python變成5行3的Haskell。 快樂哈斯!


1 Haskell中可能發生堆棧溢出,但這並不是因為你的遞歸太深,通常是因為你有點太懶,需要在某處強制某些東西。

2技術詞是Constant Applicative Form或CAF,如果您想了解更多信息。

3還是4太多了。

fibs = 0 : scanl (+) 1 fibs

我認為這個問題的答案確實顯示了Haskell閃耀的地方:在特定領域語言的設計和實現中。 在Python使用字符串和反射的地方,我們將使用代數數據類型來表示語言中的命令,使得實現清晰且易於檢查完整性(如果您在其中一個中輸入了拼寫錯誤,編譯器會告訴您命令,不像Python版本!)。

首先讓我們定義我們正在解釋的語言。 由於Fibonacci數字變得很快,我們只會在任何地方使用bignums以避免考慮整數算術溢出。

import Data.Map (Map)
import qualified Data.Map as M

data Command = Push Integer | Plus | Fib | Cache  deriving (Eq, Ord, Read, Show)

(我也很想給FibCache提供Integer參數,而不是在每次調用之前強制Push進入堆棧,但我們會保持這種方式與原始Python代碼保持一致。)

我們的堆棧機器只使用一個普通列表作為其堆棧,環境將是一個Map 我們的堆棧計算機的狀態將包括要運行的命令,堆棧和緩存。

data Machine = Machine
    { program :: [Command]
    , cache   :: Map Integer Integer
    , stack   :: [Integer]
    } 
    deriving (Eq, Ord, Read, Show)

通過預備程序,我們可以編寫一個機器步驟的功能。

step :: Machine -> Machine
step m = case program m of
    []   -> m
    c:cs -> case (c, stack m) of
        (Push n, ns)   -> m { program = cs, stack = n:ns }
        (Plus, a:b:ns) -> m { program = cs, stack = a+b:ns }
        (Fib, n:ns) -> case M.lookup n (cache m) of
            Just fibn -> m { program = cs, stack = fibn:ns }
            Nothing   -> m { stack = ns, program
                = Push (n-2) : Fib
                : Push (n-1) : Fib
                : Plus
                : Push n : Cache
                : cs
                }
        (Cache, n:ns@(fibn:_)) -> m
            { program = cs
            , stack = ns
            , cache = M.insert n fibn (cache m)
            }
        _ -> error $ "stack too short for command " ++ show c

完全運行程序很簡單:只需繼續步進,直到程序為[] 所以:

interpret :: Machine -> Machine
interpret = until (null . program) step

現在我們可以設置一個小包裝器來創建一個具有大部分空緩存的機器,設置它運行,並在完成后查看堆棧。

fibMachine :: Integer -> Machine
fibMachine n = Machine
    { program = [Fib]
    , cache = M.fromList [(1,1), (2,1)]
    , stack = [n]
    }

fib :: Integer -> Integer
fib = head . stack . interpret . fibMachine

給它一個旋轉:

> fib 10
55

請注意,我們沒有將跟蹤結合到interpret 但那沒關系; 我們可以完全外部添加跟蹤,如下所示:

tracingInterpret :: Machine -> IO Machine
tracingInterpret m = mapM_ print unfinished >> return finished 
    where 
    (unfinished, finished:_) = break (null . program) (iterate step m)

試試吧:

> tracingInterpret (fibMachine 4)
Machine {program = [Fib], cache = fromList [(1,1),(2,1)], stack = [4]}
Machine {program = [Push 2,Fib,Push 3,Fib,Plus,Push 4,Cache], cache = fromList [(1,1),(2,1)], stack = []}
Machine {program = [Fib,Push 3,Fib,Plus,Push 4,Cache], cache = fromList [(1,1),(2,1)], stack = [2]}
Machine {program = [Push 3,Fib,Plus,Push 4,Cache], cache = fromList [(1,1),(2,1)], stack = [1]}
Machine {program = [Fib,Plus,Push 4,Cache], cache = fromList [(1,1),(2,1)], stack = [3,1]}
Machine {program = [Push 1,Fib,Push 2,Fib,Plus,Push 3,Cache,Plus,Push 4,Cache], cache = fromList [(1,1),(2,1)], stack = [1]}
Machine {program = [Fib,Push 2,Fib,Plus,Push 3,Cache,Plus,Push 4,Cache], cache = fromList [(1,1),(2,1)], stack = [1,1]}
Machine {program = [Push 2,Fib,Plus,Push 3,Cache,Plus,Push 4,Cache], cache = fromList [(1,1),(2,1)], stack = [1,1]}
Machine {program = [Fib,Plus,Push 3,Cache,Plus,Push 4,Cache], cache = fromList [(1,1),(2,1)], stack = [2,1,1]}
Machine {program = [Plus,Push 3,Cache,Plus,Push 4,Cache], cache = fromList [(1,1),(2,1)], stack = [1,1,1]}
Machine {program = [Push 3,Cache,Plus,Push 4,Cache], cache = fromList [(1,1),(2,1)], stack = [2,1]}
Machine {program = [Cache,Plus,Push 4,Cache], cache = fromList [(1,1),(2,1)], stack = [3,2,1]}
Machine {program = [Plus,Push 4,Cache], cache = fromList [(1,1),(2,1),(3,2)], stack = [2,1]}
Machine {program = [Push 4,Cache], cache = fromList [(1,1),(2,1),(3,2)], stack = [3]}
Machine {program = [Cache], cache = fromList [(1,1),(2,1),(3,2)], stack = [4,3]}
Machine {program = [], cache = fromList [(1,1),(2,1),(3,2),(4,3)], stack = [3]}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM