簡體   English   中英

有沒有更好的方法在 Haskell 中有可選的函數參數?

[英]Is there a better way to have optional function arguments in 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 中是否有一種更清潔的慣用方法?

也許一些漂亮的符號在眼睛上會更容易:

(//) :: 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)

如果您需要多次使用參數,我建議使用@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

這是在 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 -}

然后

{-# 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"

更新:哎呀,我被接受了。 老實說,我認為luqui 的答案是這里最好的答案

  • 字體清晰,易於閱讀,即使是初學者
  • 類型錯誤相同
  • GHC 不需要提示來使用它進行類型推斷(在 ghci 中嘗試opt2 replicate 3 'f'以了解我的意思)
  • 可選參數與順序無關

我不知道解決潛在問題的更好方法,但是您的示例可以更簡潔地編寫為:

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

當參數變得過於復雜時,一種解決方案是為參數創建一個數據類型。 然后,您可以為該類型創建一個默認構造函數,並僅填寫您要在函數調用中替換的內容。

例子:

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

狗.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
        }

mcandre 和 Ionuț 提到的記錄方法的一個可能的改進/修改是使用鏡頭:

{-# LANGUAGE -XTemplateHaskell #-}

data Dog = Dog {
  _name :: String,
  _breed :: String,
  _barks :: Int
}

makeLenses ''Dog

defaultDog :: Dog
defaultDog = Dog {
  _name = "Dog",
  _breed = "Beagle",
  _barks = 2
}

bark :: (Dog -> Dog) -> IO ()
bark modDog = do
  let dog = modDog defaultDog
  replicateM_ (barks dog) $ putStrLn $
    (name dog) ++ " (" ++ (breed dog) ++ "): Ruff!"

main :: IO ()
main = do
  bark $ (name .~ "Snoopy") . (barks .~ 2)
  bark $ (name .~ "Wishbone") . (breed .~ "Terrier") . (barks .~ 3)

或者替代地

bark :: Dog -> IO ()
bark dog = do
  replicateM_ (barks dog) $ putStrLn $
    (name dog) ++ " (" ++ (breed dog) ++ "): Ruff!"

main :: IO ()
main = do
  bark $ name .~ "Snoopy" $ barks .~ 2 $ defaultDog
  bark $ name .~ "Wishbone" $ breed .~ "Terrier" $ barks .~ 3 $ defaultDog

有關(.~)的含義,請參見此處

這是一種使隱式參數看起來像可選參數的方法:

{-# LANGUAGE Rank2Types, ImplicitParams #-}

multiProduct :: (Num x) => x -> ((?b::x) => x) -> ((?c::x) => x) -> ((?d::x) => x) -> x
multiProduct a b c d = let ?b=10 ; ?c=20 ; ?d=30 
    in  a * b * c * d

test1 = multiProduct 1 ?b ?c ?d  -- 6000
test2 = multiProduct 2 3 4 5     -- 120

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM