【问题标题】:How can I avoid writing boilerplate code for functions performing pattern matching?如何避免为执行模式匹配的函数编写样板代码?
【发布时间】:2012-04-05 07:37:14
【问题描述】:

this responseanother question 中,给出了一个小Haskell 代码草图,它使用包装函数来分解出一些代码来对命令行参数进行语法检查。这是我试图简化的代码部分:

takesSingleArg :: (String -> IO ()) -> [String] -> IO ()
takesSingleArg act [arg] = act arg
takesSingleArg _   _     = showUsageMessage

takesTwoArgs :: (String -> String -> IO ()) -> [String] -> IO ()
takesTwoArgs act [arg1, arg2] = act arg1 arg2
takesTwoArgs _   _            = showUsageMessage

有没有办法(也许使用Template Haskell?)避免为每个参数数量编写额外的函数?理想情况下,我希望能够编写类似(我正在编写此语法)

generateArgumentWrapper<2, showUsageMessage>

然后扩展到

\fn args -> case args of
                 [a, b] -> fn a b
                 _      -> showUsageMessage

理想情况下,我什至可以为 generateArgumentWrapper 元函数提供可变数量的参数,这样我就可以做到

generateArgumentWrapper<2, asInt, asFilePath, showUsageMessage>

然后扩展到

\fn args -> case args of
                 [a, b] -> fn (asInt a) (asFilePath b)
                 _      -> showUsageMessage

有人知道实现这一目标的方法吗?将命令行参数 ([String]) 绑定到任意函数将是一种非常简单的方法。或者是否有完全不同的更好的方法?

【问题讨论】:

    标签: haskell


    【解决方案1】:

    Haskell 具有多变量函数。想象一下,你有一个类似

    的类型
    data Act = Run (String -> Act) | Res (IO ())
    

    有一些功能可以做你想做的事

    runAct (Run f) x = f x
    runAct (Res _) x = error "wrong function type"
    
    takeNargs' 0 (Res b) _ = b
    takeNargs' 0 (Run _) _ = error "wrong function type"
    takeNargs' n act (x:xs) = takeNargs' (n-1) (runAct act x) xs
    takeNargs' _ _ [] = error "not long enough list"
    

    现在,您只需将函数编组到这个 Act 类型中。你需要一些扩展

    {-# LANGUAGE FlexibleInstances, FlexibleContexts #-}
    

    然后你可以定义

    class Actable a where
      makeAct :: a -> Act
      numberOfArgs :: a -> Int
    
    instance Actable (String -> IO ()) where
      makeAct f = Run $ Res . f
      numberOfArgs _ = 1
    
    instance Actable (b -> c) => Actable (String -> (b -> c)) where
      makeAct f = Run $ makeAct . f
      numberOfArgs f = 1 + numberOfArgs (f "")
    

    现在你可以定义

    takeNArgs n act = takeNargs' n (makeAct act) 
    

    这样可以更轻松地定义您的原始函数

    takesSingleArg :: (String -> IO ()) -> [String] -> IO ()
    takesSingleArg = takeNArgs 1
    
    takesTwoArgs :: (String -> String -> IO ()) -> [String] -> IO ()
    takesTwoArgs = takeNArgs 2
    

    但我们可以做得更好

    takeTheRightNumArgs f = takeNArgs (numberOfArgs f) f
    

    令人惊讶的是,这有效 (GHCI)

    *Main> takeTheRightNumArgs putStrLn ["hello","world"]
    hello
    *Main> takeTheRightNumArgs (\x y -> putStrLn x >> putStrLn y)  ["hello","world"] 
    hello
    world
    

    编辑:上面的代码比它需要的要复杂得多。真的,你想要的只是

    class TakeArgs a where
       takeArgs :: a -> [String] -> IO ()
    
    instance TakeArgs (IO ()) where
       takeArgs a _ = a
    
    instance TakeArgs a => TakeArgs (String -> a) where
       takeArgs f (x:xs) = takeArgs (f x) xs
       takeArgs f [] = error "end of list"
    

    【讨论】:

    • 另见标准库中的 Text.Printf,它或多或少地做同样的事情。请注意,提供错误数量的参数是运行时错误,而不是类型错误。
    【解决方案2】:

    您可能希望利用现有库来处理命令行参数。我相信现在的实际标准是cmdargs,但也有其他选择,例如ReadArgsconsole-program

    【讨论】:

      【解决方案3】:

      组合器是你的朋友。试试这个:

      take1 :: [String] -> Maybe String
      take1 [x] = Just x
      take1 _ = Nothing
      
      take2 :: [String] -> Maybe (String,String)
      take2 [x,y] = Just (x,y)
      take2 _ = Nothing
      
      take3 :: [String] -> Maybe ((String,String),String)
      take3 [x,y,z] = Just ((x,y),z)
      take3 _ = Nothing
      
      type ErrorMsg = String
      
      with1 :: (String -> IO ()) -> ErrorMsg -> [String] -> IO ()
      with1 f msg = maybe (fail msg) f . take1
      
      with2 :: (String -> String -> IO ()) -> ErrorMsg -> [String] -> IO ()
      with2 f msg = maybe (fail msg) (uncurry f) . take2
      
      with3 :: (String -> String -> String -> IO ()) -> ErrorMsg -> [String] -> IO ()
      with3 f msg = maybe (fail msg) (uncurry . uncurry $ f) . take3
      
      foo a b c = putStrLn $ a ++ " :: " ++ b ++ " = " ++ c
      
      bar = with3 foo "You must send foo a name, type, definition"
      
      main = do
        bar [ "xs", "[Int]", "[1..3]" ]
        bar [ "xs", "[Int]", "[1..3]", "What am I doing here?" ]
      

      如果你喜欢强大的语言扩展:

      {-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, UndecidableInstances #-}
      
      foo a b c = putStrLn $ a ++ " :: " ++ b ++ " = " ++ c
      foo_msg = "You must send foo a name, type, definition"
      
      class ApplyArg a b | a -> b where
        appArg :: ErrorMsg -> a -> [String] -> IO b
      
      instance ApplyArg (IO b) b where
        appArg _msg todo [] = todo
        appArg msg _todo _ = fail msg
      
      instance ApplyArg v q => ApplyArg (String -> v) q where
        appArg msg todo (x:xs) = appArg msg (todo x) xs
        appArg msg _todo _ = fail msg
      
      quux :: [String] -> IO ()
      quux xs = appArg foo_msg foo xs
      
      main = do
        quux [ "xs", "[int]", "[1..3]" ]
        quux [ "xs", "[int]", "[1..3]", "what am i doing here?" ]
      

      【讨论】:

        猜你喜欢
        • 2011-03-22
        • 1970-01-01
        • 1970-01-01
        • 2016-12-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多