有没有更好的方法在Haskell中拥有可选参数?

Vla*_*ala 47 haskell

我习惯于在Python中定义可选参数:

def product(a, b=2):
    return a * b
Run Code Online (Sandbox Code Playgroud)

Haskell没有默认参数,但我可以通过使用Maybe来获得类似的东西:

product a (Just b) = a * b
product a Nothing = a * 2
Run Code Online (Sandbox Code Playgroud)

如果您有多个参数,这会很快变得麻烦.例如,如果我想做这样的事情怎么办:

def multiProduct (a, b=10, c=20, d=30):
    return a * b * c * d
Run Code Online (Sandbox Code Playgroud)

我必须有八个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
Run Code Online (Sandbox Code Playgroud)

这看起来非常不优雅.在Haskell中有一种惯用的方法可以做得更干净吗?

luq*_*qui 77

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

(//) :: 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)
Run Code Online (Sandbox Code Playgroud)

如果您需要多次使用这些参数,我建议使用@ 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
Run Code Online (Sandbox Code Playgroud)

  • 基本上,`(//)= flip fromMaybe` :-)我喜欢你选择与Perl相同的运算符. (15认同)
  • 我喜欢它,因为它看起来像`||`只是倾斜了一点:) (3认同)
  • @pat,perl是我的遗产:-) (3认同)
  • 出于兴趣,在Control.Error.Util中将其定义为“?:”,类似于coffeescript的“?”。算子 (2认同)

ram*_*ion 35

这是在Haskell中执行可选参数的另一种方法:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-}
module Optional where

class Optional1 a b r where 
  opt1 :: (a -> b) -> a -> r

instance Optional1 a b b where
  opt1 = id

instance Optional1 a b (a -> b) where
  opt1 = const

class Optional2 a b c r where 
  opt2 :: (a -> b -> c) -> a -> b -> r

instance Optional2 a b c c where
  opt2 = id

instance (Optional1 b c r) => Optional2 a b c (a -> r) where
  opt2 f _ b = \a -> opt1 (f a) b

{- Optional3, Optional4, etc defined similarly -}
Run Code Online (Sandbox Code Playgroud)

然后

{-# 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"
Run Code Online (Sandbox Code Playgroud)

更新:哎呀,我被接受了.老实说,我认为luqui的答案是最好的答案:

  • 类型清晰,易于阅读,即使是初学者也是如此
  • 同样的类型错误
  • GHC不需要提示用它进行类型推断(尝试opt2 replicate 3 'f'用ghci来看看我的意思)
  • 可选参数与顺序无关

  • @Max:是的 - 我只是为了清楚起见。YMMV :) (2认同)

pat*_*pat 17

我不知道解决潜在问题的更好方法,但您的示例可以更简洁地写成:

multiProduct req1 opt1 opt2 opt3 = req1 * opt1' * opt2' * opt3'
    where opt1' = fromMaybe 10 opt1
          opt2' = fromMaybe 20 opt2
          opt3' = fromMaybe 30 opt3
Run Code Online (Sandbox Code Playgroud)


mca*_*dre 7

当参数变得过于复杂时,一种解决方案是仅为参数创建数据类型.然后,您可以为该类型创建默认构造函数,并仅在函数调用中填写要替换的内容.

例:

$ runhaskell dog.hs 
Snoopy (Beagle): Ruff!
Snoopy (Beagle): Ruff!
Wishbone (Terrier): Ruff!
Wishbone (Terrier): Ruff!
Wishbone (Terrier): Ruff!
Run Code Online (Sandbox Code Playgroud)

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
        }
Run Code Online (Sandbox Code Playgroud)