## 遞迴讀檔

> 想要知道自己這個 `Module` 叫什麼名字的話，看 `ModuleHead` ，想要看 import 出些什麼的話，看 `[ImportDecl]` 。
>
> 在上面還推薦大家要看的一個 library 叫做 Data.Map.Strict 。這個是我用來記錄 `Module` 到底讀過了沒有。你也可以用 `List` ，但這個效率會比較好一點。只是用起來比較複雜一點。

在遞迴讀檔前，花了不少時間整理思路，最後的想法是：

1. 我需要一個 `Map` 來蒐集 module name 與 module AST 的對應關係。
2. 我希望得到的結果在 `IO` 裡面，因為 `readFile` 會給我 `IO` ，我也需要 `IO` 才能 pretty print 。
3. 我希望可以遞迴地處理所有 import 進來的檔案。

於是 type 長這樣：

```Haskell
collectModule :: IO (M.Map String (Module SrcSpanInfo)) -> Module SrcSpanInfo -> IO (M.Map String (Module SrcSpanInfo))
```

程式一開始先處理 `Module` ：

```Haskell
collectModule ioMap mod =
  case mod of
    Module _ mModuleHead _ imports _ -> -- 略
    _ -> ioMap
```

然後問問 Map 裡面是不是已經有現在要加入的 `Module` 了：

```Haskell
collectModule ioMap mod =
  case mod of
    Module _ mModuleHead _ imports _ -> do
      let
        modName = case mModuleHead of
          Just (ModuleHead _ (ModuleName _ name)) -> name
          Nothing -> "Main"
      map' <- ioMap
      let
        map'' = case M.member modName map' of
          False -> M.insert modName mod map'
          True -> map'
        -- 略
    _ -> ioMap
```

在那個 `do` 之後就是 `IO` 的領域了，於是 `let` 後面不用 `in` ， `map'` 則是把 type 是 `IO (M.Map String (Module SrcSpanInfo))` 的 `ioMap` 裡的 `(M.Map String (Module SrcSpanInfo))` 拿出來。

奇怪的是那兩個 `let` 與 `case ... of` 的組合，如果寫：

```Haskell
let modName = case mModuleHead of
  Just (ModuleHead _ (ModuleName _ name)) -> name
  Nothing -> "Main"
```

是會得到 parsing error 的，但是寫：

```Haskell
let modName = case mModuleHead of Just (ModuleHead _ (ModuleName _ name)) -> name
                                  Nothing -> "Main"
```

會過，像前文那樣寫也會過。覺得前文那種寫法比較美觀，故沒有把 `let` 和 `case ... of` 寫在一起。

---

最後則是讀檔，再遞迴地呼叫自己：

```Haskell
collectModule ioMap mod =
  case mod of
    Module _ mModuleHead _ imports _ -> do
      let
        modName = case mModuleHead of
          Just (ModuleHead _ (ModuleName _ name)) -> name
          Nothing -> "Main"
      map' <- ioMap
      let
        map'' = case M.member modName map' of
          False -> M.insert modName mod map'
          True -> map'
        go acc [] = acc
        go acc (m : ms) = go modMap ms where
          modMap = do
            let (ModuleName _ name) = importModule m
            case name of
              "Prelude" -> acc
              _ -> do
                mMod <- readModule name
                case mMod of
                  Just mm -> collectModule acc mm
                  Nothing -> acc
        go (return map'') imports
    _ -> ioMap
```

`go` 的 type 其實是：

```Haskell
go :: IO (M.Map String (Module SrcSpanInfo)) -> [ImportDecl] -> IO (M.Map String (Module SrcSpanInfo))
```

當 `go` 吃到的 `[ImportDecl]` 空了（`[]`）時，就把 `acc` 原封不動地還回去。

當 `[ImportDecl]` 中還有東西時，就先把裡面第一個東西處理完，放到 `modMap` 中，再把剩下的交給 go 繼續處理。而 `modMap` 是什麼呢？是先看看這第一個叫 `m` 的 `ImportModule` 的名字，如果不是預設會自動 import 的 `Prelude` 的話，才交給 `readModule` 把 module 讀進來，看看有沒有讀成功（是 `Just mm` 還是 `Nothing`），成功就交給 `collectModule acc mm` 遞迴處理，失敗就傳回本來的 `acc` 。不用先把 `acc` 這個 `IO (M.Map ...)` 裡的 `Map` 拆出來，那在下一次遞迴時會被 `collectModule` 處理好。

最後 `go (return map'') imports` 之所以要加上 `return` ，是為了把沒è¢«放在 `IO` 裡的 `map''` 放進去，這樣才能交給 `go` 處理。

---

然後 `main` 只要這樣寫：

```Haskell
main :: IO ()
main = do
  inputStr <- getContents
  let res = parseModule inputStr
  allMods <- case res of
    ParseOk mod -> collectModule (return M.empty) mod
    ParseFailed _ msg -> return M.empty
  mapM_ putStrLn $ fmap prettyPrint allMods
```

最後那行 `mapM_ putStrLn $ fmap prettyPrint allMods` 可以讀作：把 `prettyPrint` 套到所有 modules 上面，然後一個一個印（`putStrLn`）出來， `mapM_` 最後會還給我們 `IO ()` ，功德圓滿。

只剩處理檔案路徑，就完成這次的進度了。
