2015-09-02 53 views
2

在爲System.Console.GetOpt文檔中給出的第二個例子,在這裏重現,我無法理解或解開這一行:getopt的使用和與foldl,翻轉,ID

(o,n,[] ) -> return (foldl (flip id) defaultOptions o, n) 

這是什麼與foldl在做什麼,它是如何實現的? (flip id)的用途是什麼?到底是怎麼回事?

代碼:

import System.Console.GetOpt 
    import Data.Maybe (fromMaybe) 

    data Options = Options 
    { optVerbose  :: Bool 
    , optShowVersion :: Bool 
    , optOutput  :: Maybe FilePath 
    , optInput  :: Maybe FilePath 
    , optLibDirs  :: [FilePath] 
    } deriving Show 

    defaultOptions = Options 
    { optVerbose  = False 
    , optShowVersion = False 
    , optOutput  = Nothing 
    , optInput  = Nothing 
    , optLibDirs  = [] 
    } 

    options :: [OptDescr (Options -> Options)] 
    options = 
    [ Option ['v']  ["verbose"] 
     (NoArg (\ opts -> opts { optVerbose = True })) 
     "chatty output on stderr" 
    , Option ['V','?'] ["version"] 
     (NoArg (\ opts -> opts { optShowVersion = True })) 
     "show version number" 
    , Option ['o']  ["output"] 
     (OptArg ((\ f opts -> opts { optOutput = Just f }) . fromMaybe "output") 
       "FILE") 
     "output FILE" 
    , Option ['c']  [] 
     (OptArg ((\ f opts -> opts { optInput = Just f }) . fromMaybe "input") 
       "FILE") 
     "input FILE" 
    , Option ['L']  ["libdir"] 
     (ReqArg (\ d opts -> opts { optLibDirs = optLibDirs opts ++ [d] }) "DIR") 
     "library directory" 
    ] 

    compilerOpts :: [String] -> IO (Options, [String]) 
    compilerOpts argv = 
     case getOpt Permute options argv of 
     (o,n,[] ) -> return (foldl (flip id) defaultOptions o, n) 
     (_,_,errs) -> ioError (userError (concat errs ++ usageInfo header options)) 
    where header = "Usage: ic [OPTION...] files..." 
+0

嘗試在GHCi上鍵入':t flip id'。這也相當於'(,)(foldl(flip id)defaultOptions o)n'。 – Mephy

+0

是的,但變量o如何應用於defaultOptions變量,foldl使用的實際函數是什麼? – andro

+0

我可能會寫'flip id'作爲'flip($)',因爲後者使意圖更加明顯。 – chi

回答

4

類型的flip idb -> (b -> c) -> c,你可以在這裏找到解釋:Why does Haskell's "flip id" has this type?

foldl (flip id) defaultOptions o子表達式執行以下操作:

  1. 注意到defaultOptions作爲初始值(defaultOptions有型號Options
  2. 注意到每個元素從o(每個元素具有類型Options -> Options
  3. 褶皺使用flip id函數的所有元素(它有b -> (b -> c) -> c型)

由於所有o元件的變化在給定的配置相應的選項,結果foldl (flip id) defaultOptions o將是所有解析選項的配置。所有錯過的選項都被替換爲defaultOptions的默認值。

(o,n,[] ) -> return (foldl (flip id) defaultOptions o, n)表達式的其他部分非常簡單:

  1. (o,n,[] ) ->的解析選項列表,非選項列表和錯誤的空單
  2. return (..., n)只是把價值(..., n)成一致monad IO (Options, [String])
+0

那麼爲什麼需要反轉o和defaultOptions呢? – andro

+0

我們不需要反轉'o'和'defaultOptions'。 'foldl'接受一個類型爲'a - > b - > a'的函數作爲第一個參數,'a'作爲第二個,'[b]'作爲第三個併產生'a'。傳遞'defaultOptions'作爲第二個參數,我們將'a'定義爲'Options'。通過'o'作爲第三個參數,將'[b]'定義爲'[Options - > Options']。這就是爲什麼我們需要簽名功能'選項 - >(選項 - >選項) - >選項'。你可以寫一個lambda'\ x f - > f x',但這種方法很難看,因爲這個函數可以很容易地從內置函數中組合起來 – soon

2

(這不是嚴格的回答你的問題,但@soon讓我發帖吧)

您定義的每個命令行參數的語義由 轉換函數Options -> Options描述。由於您可以將許多 參數傳遞給程序,因此您最終得到這樣的轉換列表 函數[Options -> Options]。目標是計算這些轉換的總和 ,即每個轉換依次應用 的Options -> Options

一個特別好的實現這一目標的方法是觀察自同態a -> a的結構 任何類型a

  • id :: a -> a是身份轉換功能實際上並沒有做任何事情

  • 給定兩個轉換函數f1, f2 :: a -> a,它們的組成 f1 . f2恰好對應地按順序應用兩者。注意 也是這個組合是聯想,因爲做f2 . f3和 然後f1是做f3其次是f1 . f2

所以我們有一個monoid!

Haskell標準庫base已經包含這個幺半羣 名稱Endo。通過改變options結果類型

appEndo (fold o) defaultOptions 

利用這一點,我們可以重寫

foldl (flip id) defaultOptions o 
在一個好得多的方式,在我看來,使它立即明顯

這裏發生了什麼到[OptDescr (Endo Options)];或者,如果您不願意,您可以在組合時添加額外的線路噪音(通過編寫appEndo (foldMap Endo o) defaultOptions)。在這裏,fold o :: Endo Options是所有單個轉換函數的組合,並且appEndo (fold o) :: Options -> Options是如何將這個結果轉換函數最終應用於最初的Options

請注意,無論用於o的數據結構如何,它都可以工作:由於手頭幺半羣的關聯屬性,它可用於過渡函數或樹或Maybe的列表;和fold是多態足以揭露這一點。

+0

非常有趣,謝謝! – soon