10

Вопрос по haskell – Как можно избежать написания стандартного кода для функций, выполняющих сопоставление с образцом?

Вэтот ответ вДругой вопросбыл дан небольшой набросок кода на 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

Есть ли способ (может быть, используяШаблон 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]) к произвольным функциям. Или, может быть, есть совершенно другой, лучший подход?

  • Error: User Rate Limit Exceeded

    от
  • 1

    Комбинаторы твой друг. Попробуй это:

    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?" ]
    

  • 2

    Возможно

    вы захотите использовать существующие библиотеки для работы с аргументами командной строки. Я считаю, что стандарт де-факто сейчасcmdargs, но существуют и другие варианты, такие какReadArgs а такжеКонсоль-программа.

  • 12

    У 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"