我遇到一种情况,其中递归函数根据命令行参数做出决定。递归函数不会直接由调用main。我想知道最好的方法是使参数对函数可用。我不想getArgs在递归函数中调用,因为这似乎会增加很多开销。
然而,尴尬的来电getArgs中main,然后通过不使用这些函数传递参数。这个例子不是递归的,但是希望您能理解这个概念。
import Data.Char
import System.Environment
main :: IO ()
main = do
args <- getArgs -- want to use these args in fun2
traverse_ fun1 ["one", "two", "three"]
fun1 :: String -> IO ()
fun1 s = traverse_ fun2 s
fun2 :: Char -> IO ()
fun2 c = do
if "-u" `elem` args then print $ toUpper c -- how to get args here?
else print $ toLower c
Run Code Online (Sandbox Code Playgroud)
传递参数似乎是一个坏主意:
import Data.Char
import System.Environment
main :: IO ()
main = do
args <- getArgs -- want to use these args in fun2
traverse_ (fun1 args) ["one", "two", "three"]
fun1 :: [String] -> String -> IO ()
fun1 args s = traverse_ (fun2 args) s
fun2 :: [String] -> Char -> IO ()
fun2 args c = do
if "-u" `elem` args then print $ toUpper c
else print $ toLower c
Run Code Online (Sandbox Code Playgroud)
在面向对象的语言中,您将只在一个类中拥有一个成员变量,或者某种全局变量。
typ*_*ern 14
将参数传递给fun1没有什么尴尬-它确实使用了它们(将参数传递给func2 就是在使用它们)。
什么是尴尬的,是让你FUN1或FUN2的行为依赖于隐变量,使他们的行为难以推理或预测。
您可以做的另一件事:将fun2设为fun1的参数(您可以在Haskell中将函数作为参数传递!):
fun1 :: (Char -> IO ()) -> String -> IO ()
fun1 f s = traverse_ f s
Run Code Online (Sandbox Code Playgroud)
然后,您可以main像这样调用它:
traverse_ (fun1 (fun2 args)) ["one", "two", "three"]
Run Code Online (Sandbox Code Playgroud)
这样,您可以将参数直接传递给fun2,然后将fun2传递给fun1 ...
对于情况下,当你真的做需要一个共享的,只读的环境中,使用的Reader单子,或在这种情况下,ReaderT单子转换。
import Data.Char
import Data.Foldable
import System.Environment
import Control.Monad.Trans
import Control.Monad.Trans.Reader
main :: IO ()
main = do
args <- getArgs
-- Pass in the arguments using runReaderT
runReaderT (traverse_ fun1 ["one", "two", "three"]) args
-- The type changes, but the body stays the same.
-- fun1 doesn't care about the environment, and fun2
-- is still a Kleisli arrow; traverse_ doesn't care if
-- its type is Char -> IO () or Char -> ReaderT [String] IO ()
fun1 :: String -> ReaderT [String] IO ()
fun1 s = traverse_ fun2 s
-- Get the arguments using ask, and use liftIO
-- to lift the IO () value produced by print
-- into monad created by ReaderT
fun2 :: Char -> ReaderT [String] IO ()
fun2 c = do
args <- ask
liftIO $ if "-u" `elem` args
then print $ toUpper c
else print $ toLower c
Run Code Online (Sandbox Code Playgroud)
顺便说一句,您可以fun2稍微重构:
fun2 :: Char -> ReaderT [String] IO ()
fun2 c = do
args <- ask
let f = if "-u" `elem` args then toUpper else toLower
liftIO $ print (f c)
Run Code Online (Sandbox Code Playgroud)
事实上,你可以选择toUpper或者toLower只要你的论点,并把那个,而不是参数本身,在环境中。
main :: IO ()
main = do
args <- getArgs
-- Pass in the arguments using runReaderT
runReaderT
(traverse_ fun1 ["one", "two", "three"])
(if "-u" `elem` args then toUpper else toLower)
fun1 :: String -> ReaderT (Char -> Char) IO ()
fun1 s = traverse_ fun2 s
fun2 :: Char -> ReaderT (Char -> Char) IO ()
fun2 c = do
f <- ask
liftIO $ print (f c)
Run Code Online (Sandbox Code Playgroud)
环境类型可以是任何值。上面的示例显示了一个字符串列表和一个Char -> Char作为环境的字符串。通常,您可能需要一种自定义产品类型,该产品类型应包含要与其余代码共享的任何值,例如,
data MyAppConfig = MyAppConfig { foo :: Int
, bar :: Char -> Char
, baz :: [Strings]
}
main :: IO ()
main = do
args <- getArgs
-- Process arguments and define a value of type MyAppConfig
runReaderT fun1 MyAppConfig
fun1 :: ReaderT MyAppConfig IO ()
fun1 = do
(MyAppConfig x y z) <- ask -- Get the entire environment and unpack it
x' <- asks foo -- Ask for a specific piece of the environment
...
Run Code Online (Sandbox Code Playgroud)
您可能需要阅读有关ReaderT设计模式的更多信息。
尽管typedfern的答案很好(建议),但编写尽可能多的纯函数,然后将效果推迟到您不能再推迟它们的时候,甚至会更加惯用。这使您可以创建数据管道,而不必传递参数。
我知道OP中显示的示例问题已经简化,可能只是微不足道的程度,但是如果将逻辑与其作用分开,则编写起来会更容易。
首先,重写fun2为纯函数:
fun2 :: Foldable t => t String -> Char -> Char
fun2 args c =
if "-u" `elem` args then toUpper c else toLower c
Run Code Online (Sandbox Code Playgroud)
如果部分应用fun2参数,则您有一个类型为的函数Char -> Char。但是,["one", "two", "three"]您想要的数据()print具有类型[[Char]]。您想将每个Char值应用于fun2 args。本质上,这就是OP fun1函数的作用。
但是,您可以改为使用(或)将[[Char]]值展平。[Char]joinconcat
*Q56438055> join ["one", "two", "three"]
"onetwothree"
Run Code Online (Sandbox Code Playgroud)
现在,您只需将展Char平列表中的每个值应用于fun2 args:
*Q56438055> args = ["-u"]
*Q56438055> fmap (fun2 args) $ join ["one", "two", "three"]
"ONETWOTHREE"
Run Code Online (Sandbox Code Playgroud)
这仍然是纯粹的结果,但是您现在可以通过打印每个字符来应用效果:
main :: IO ()
main = do
args <- getArgs
mapM_ print $ fmap (fun2 args) $ join ["one", "two", "three"]
Run Code Online (Sandbox Code Playgroud)
通过更改函数设计,以便在函数之间传递数据,通常可以简化代码。
| 归档时间: |
|
| 查看次数: |
190 次 |
| 最近记录: |