Optional Parameters in Other Languages
Optional parameters are in a number of other languages, and come in a variety of flavours. Ada and Visual Basic both provide named and positional optional parameters. For example, given the definition:
Sub Foo(b as Boolean = True, i as Integer = 0, s as String = "Hello")
We can make the calls:
Call Foo(s = "Goodbye", b = False)
Call Foo(False, 1)
In the first case we give named parameters, in the second we give all the parameters up to a certain position.
In some languages, such as GP+, you can say which parameters should take their default values:
Call Foo(_, 42, _)
Optional Parameters in Haskell
Haskell doesn't have built-in optional parameters, but using the record syntax, it is simple to encode named optional parameters.
data Foo = Foo {b :: Bool, i :: Integer, s :: String}
defFoo = Foo True 0 "Hello"
foo :: Foo -> IO ()
Now we can pass arguments by name, for example:
foo defFoo{s = "Goodbye", b = False}
This syntax takes the value defFoo, and replaces the fields s and b with the associated values. Using a type class, we can abstract this slightly:
class Def a where
def :: a
instance Def Foo where
def = defFoo
Now we can make all functions taking default arguments use the def argument as a basis, allowing type inference and type classes to choose the correct default type. Even still, optional parameters in Haskell are not quite as neat as in other languages, but the other features of Haskell mean they are required less often.
This technique has been used in TagSoup, particularly for the parseTagOptions function. I've also seen this technique used in cpphs with the runCpphs function.
Surely using this is going to give you severe namespacing headaches?
ReplyDeleteDavid: If you use s, b and i - then definitely! For TagSoup I deliberately use long names, such as "optTagPosition". The namespace issue is a general problem with records, which as yet has no good solution.
ReplyDeleteRight. But given that it's a general problem, introducing a large number of new record fields doesn't seem like a good idea, so this is something to be used sparingly at best. :-)
ReplyDeleteI realised that you didn't intend things like s, b, and i to be actually used for this, but I find that most cases where one would want default arguments it would be natural to have fairly brief descriptive names.
It's still a nice trick though. I've used a very similar one in other languages.
I use this for hvac too -- picked up the idiom from the hughespj prettyprint library. namespacing isn't so bad though, just a bit ugly with all the qualifiers.
ReplyDeleteI've use records for defaults a fair bit in my chart library. The typeclass is new to me, and nice. Next API change, I think I'll make use of that also.
ReplyDeleteI should also note that the Def class is exactly like Hughes' Sat class from Restricted Datatypes in Haskell. I wonder if like Box (Box a = Box a) and seal (Seal = Seal a), Sat/Def isn't so general it really belongs in some standard library instance.
ReplyDeleteHello, I'm new to haskell but isn't the definition of Def should be with class and not instance?
ReplyDeleteclass Def a where
def :: a
sclv: I'd make it One, data One a = One a, and add it to Data.Tuple - then it fits neatly in the standard libraries. Having Def in the standard libraries would be useful, but I'm not sure where you'd put it.
ReplyDeleteanon: You are indeed correct, I have updated the blog with your modification.
{- Thanks, I did not know this is possible! For those who like to copy and paste, here is a working demonstration (ghc wants "Bool" not "Boolean"): -}
ReplyDeleteclass Def a where def :: a
instance Def Foo where def = defFoo
data Foo = Foo {b :: Bool, i :: Integer, s :: String} deriving Show
defFoo = Foo True 0 "Hello"
foo :: Foo -> IO ()
foo = putStrLn . show
main = do { foo def; foo def {s = "Goodbye", b = False}}
-- It's a pity that comments here disallow pre/code blocks...
Anton: woops, I typed Boolean for VB, then forgot that its Bool in Haskell - now fixed. Thanks for making the code available as a long snippet.
ReplyDeleteThere is also a different implementation of named parameters (keyword arguments) (which is more sophisticated in the implementation mechanism) described there.
ReplyDeleteQuotation:
Also in marked contrast with records, keyword labels may be reused throughout the code with no restriction; the same label may be associated with arguments of different types in different functions. Labels of Haskell records may not be re-used.
Fascinatingly, the code example in Unknown's 12:33 PM comment runs correctly when compiled with GHC and when wrapped in multi-line delimiters in GHCI (":{...}:"), but not entered on individual lines in GHCI.
ReplyDelete