62 votes

Existe-t-il une meilleure façon d'avoir des arguments de fonction optionnels en Haskell ?

J'ai l'habitude de pouvoir définir des arguments optionnels de la sorte en Python :

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

Haskell n'a pas d'arguments par défaut, mais j'ai pu obtenir quelque chose de similaire en utilisant un Maybe :

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

Cependant, cette procédure devient très vite encombrante si vous avez plus de plusieurs paramètres. Par exemple, si je veux faire quelque chose comme ceci :

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

Il me faudrait huit définitions de multiProduit pour tenir compte de tous les cas.

Au lieu de cela, j'ai décidé d'opter pour ceci :

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

Cela me semble très inélégant. Existe-t-il un moyen idiomatique plus propre de faire cela en Haskell ?

2 votes

Combien de paramètres une fonction doit-elle prendre en compte ? vraiment prendre ? ;-)

0 votes

Je ne pense pas que ce soit le exactes même question, donc je ne voterai pas trop près, mais c'est assez similaire à la question de la stackoverflow.com/questions/2790860/

1 votes

@MatrixFrog Cette question concerne les arguments des fonctions. Cette question concerne les valeurs des types de données.

93voto

luqui Points 26009

Peut-être qu'une belle notation serait plus agréable à l'œil :

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

Si vous devez utiliser les paramètres plus d'une fois, je vous suggère d'utiliser la méthode de @pat.

EDIT 6 ans plus tard

Avec ViewPatterns vous pouvez placer les valeurs par défaut à gauche.

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

16 votes

Essentiellement, (//) = flip fromMaybe :-) J'apprécie que vous ayez choisi le même opérateur que Perl.

3 votes

Je l'aime parce qu'il ressemble à || juste un peu incliné :)

4 votes

@pat, perl est mon héritage :-)

40voto

rampion Points 38697

Voici une autre façon d'utiliser les arguments optionnels en 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 -}

Dans ce cas

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

Mise à jour : Oups, j'ai été accepté. Je pense honnêtement que Réponse de luqui est le meilleur :

  • les caractères sont clairs et faciles à lire, même pour les débutants
  • même chose pour les erreurs de type
  • GHC n'a pas besoin d'indications pour faire de l'inférence de type avec (essayez opt2 replicate 3 'f' dans ghci pour voir ce que je veux dire)
  • les arguments facultatifs sont indépendants de l'ordre

1 votes

Vous pouvez certainement utiliser (5 : : Int) au lieu de définir _5 ?

2 votes

@Max : Oui, je voulais juste clarifier les choses. YMMV :)

21voto

pat Points 6761

Je ne connais pas de meilleure façon de résoudre le problème sous-jacent, mais votre exemple peut être formulé de façon plus succincte :

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

12voto

Ionuț G. Stan Points 62482

7voto

mcandre Points 6965

Lorsque les arguments deviennent trop complexes, une solution consiste à créer un type de données propre aux arguments. Vous pouvez ensuite créer un constructeur par défaut pour ce type et ne remplir que ce que vous souhaitez remplacer dans vos appels de fonction.

Exemple :

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

chien.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 votes

C'est la même chose que Ionut a suggérée. Voir mon commentaire sur sa réponse.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X