是否有更好的方法在Haskell中有可选的参数?

我习惯于能够像在Python中那样定义可选参数:

def product(a, b=2): return a * b 

Haskell没有默认参数,但是我可以通过使用Maybe获得类似的结果:

 product a (Just b) = a * b product a Nothing = a * 2 

如果你有多个参数,这很快就变得很麻烦。 例如,如果我想要做这样的事情:

 def multiProduct (a, b=10, c=20, d=30): return a * b * c * d 

我将不得不有八个multiProduct的定义来说明所有情况。

相反,我决定这样做:

 multiProduct req1 opt1 opt2 opt3 = req1 * opt1' * opt2' * opt3' where opt1' = if isJust opt1 then (fromJust opt1) else 10 where opt2' = if isJust opt2 then (fromJust opt2) else 20 where opt3' = if isJust opt3 then (fromJust opt3) else 30 

这对我来说很不雅观。 在Haskell中有一种习惯的方法可以做到这一点吗?

下面是在Haskell中做可选参数的另一种方法:

 {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-} module Optional where class Optional1 abr where opt1 :: (a -> b) -> a -> r instance Optional1 abb where opt1 = id instance Optional1 ab (a -> b) where opt1 = const class Optional2 abcr where opt2 :: (a -> b -> c) -> a -> b -> r instance Optional2 abcc where opt2 = id instance (Optional1 bcr) => Optional2 abc (a -> r) where opt2 f _ b = \a -> opt1 (fa) b {- Optional3, Optional4, etc defined similarly -} 

然后

 {-# LANGUAGE FlexibleContexts #-} module Main where import Optional foo :: (Optional2 Int Char String r) => r foo = opt2 replicate 3 'f' _5 :: Int _5 = 5 main = do putStrLn $ foo -- prints "fff" putStrLn $ foo _5 -- prints "fffff" putStrLn $ foo _5 'y' -- prints "yyyyy" 

更新 :哎呀,我被接受了。 我真的认为卢奎的答案是最好的答案 :

  • types清晰,易于阅读,即使是初学者
  • types错误也一样
  • GHC不需要提示来进行types推断(尝试在ghci中selectopt2 replicate 3 'f'来查看我的意思)
  • 可选参数是顺序无关的

也许一些很好的符号会更容易在眼睛上:

 (//) :: Maybe a -> a -> a Just x // _ = x Nothing // y = y -- basically fromMaybe, just want to be transparent multiProduct req1 opt1 opt2 opt3 = req1 * (opt1 // 10) * (opt2 // 20) * (opt3 // 30) 

如果您不止一次需要使用这些参数,我build议您使用@ pat的方法。

编辑6年后

使用ViewPatterns你可以把默认值放在左边。

 {-# LANGUAGE ViewPatterns #-} import Data.Maybe (fromMaybe) def :: a -> Maybe a -> a def = fromMaybe multiProduct :: Int -> Maybe Int -> Maybe Int -> Maybe Int -> Int multiProduct req1 (def 10 -> opt1) (def 20 -> opt2) (def 30 -> opt3) = req1 * opt1 * opt2 * opt3 

我不知道解决潜在问题的更好方法,但是你的例子可以写得更简洁:

 multiProduct req1 opt1 opt2 opt3 = req1 * opt1' * opt2' * opt3' where opt1' = fromMaybe 10 opt1 opt2' = fromMaybe 20 opt2 opt3' = fromMaybe 30 opt3 

这里有一个来自Neil Mitchell的成语 ,这似乎也得到了Brent Yorgey的赞同 。

当参数变得太复杂的时候,一个解决scheme就是为参数创build一个数据types。 然后你可以为这个types创build一个默认构造函数,并且只填写你想在你的函数调用中replace的内容。

例:

 $ runhaskell dog.hs Snoopy (Beagle): Ruff! Snoopy (Beagle): Ruff! Wishbone (Terrier): Ruff! Wishbone (Terrier): Ruff! Wishbone (Terrier): Ruff! 

dog.hs:

 #!/usr/bin/env runhaskell import Control.Monad (replicateM_) data Dog = Dog { name :: String, breed :: String, barks :: Int } defaultDog :: Dog defaultDog = Dog { name = "Dog", breed = "Beagle", barks = 2 } bark :: Dog -> IO () bark dog = replicateM_ (barks dog) $ putStrLn $ (name dog) ++ " (" ++ (breed dog) ++ "): Ruff!" main :: IO () main = do bark $ defaultDog { name = "Snoopy", barks = 2 } bark $ defaultDog { name = "Wishbone", breed = "Terrier", barks = 3 }