2011-10-15 31 views
42

我已經習慣了能夠在Python定義,像這樣的可選參數:在Haskell中有更好的方法可選參數嗎?

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

Haskell沒有默認參數,但我可以通過使用也許得到類似的東西:

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 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中有沒有一種習慣的方式可以做到這一點?

+2

那麼有多少參數時的功能* *真的需要? ;-) – 2011-10-15 22:40:54

+0

我不認爲這是*確切*相同的問題,所以我不會投票關閉,但這是非常相似的http://stackoverflow.com/questions/2790860/optional-arguments-in-haskell – MatrixFrog

+1

@MatrixFrog這個問題是關於函數的參數。這個問題是關於數據類型的值。 –

回答

28

下面是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's answer是這裏最好的一個:

  • 類型清晰,易讀,即使是初學者
  • 相同類型的錯誤
  • GHC不需要提示做類型推斷它(嘗試opt2 replicate 3 'f'在ghci中看到我的意思)
  • 可選的參數是順序無關
+1

當然你可以使用(5 :: Int)而不是定義_5? – Max

+1

@Max:是的 - 我只是想澄清。 YMMV :) – rampion

14

我不知道一個更好的辦法來解決根本問題,但你的例子可以更簡潔書面:

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

不錯,但你如何處理部分應用程序? – pat

+2

我看到了這個想法,但IMO更糟。每個函數都有新的數據類型,沒有函數currying,並且全局命名空間不必要地被污染...... yuck。 –

+9

@Goose,如果你正在考慮將這種技術用於「每個函數」,那麼你並不是真的在寫Haskell。對於某些模塊接口來說這是一種體面的技術(參見[parsec.token](http://hackage.haskell.org/packages/archive/parsec/3.0.0/doc/html/Text-Parsec-Token.html#t :TokenParser)),但在此之後,你將會想要投入到慣用的Haskell中 - 許多小巧的組合器,比如說1-3個簡潔的參數(例如,不是bools)。我喜歡用python編寫Haskell,但是通過這種方式你會發現更多的滿足。 – luqui

66

也許一些不錯的符號將在眼睛容易:

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

如果你需要使用的參數不止一次,我建議用輕拍@的方法去。

編輯6年後

隨着​​你可以把左邊的默認值。

{-# 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 
+11

基本上,'(//)=翻轉fromMaybe' :-)我喜歡你選擇與Perl相同的操作符。 – pat

+2

我喜歡它,因爲它看起來像'||'只是傾斜了一點:) –

+1

@pat,perl是我的遺產:-) – luqui

6

當論據太複雜,一個解決方案是創建一個數據類型強制t爲參數。然後,您可以爲該類型創建一個默認構造函數,並且只填寫您想在函數調用中替換的內容。

例子:

$ 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 
     } 
+0

這是Ionuţ建議的同樣的事情。看到我對他的回答的評論。 –

相關問題