Idiomatic Haskell equivalent of "keyword arguments" to functions

I'm thinking around the design of a couple of things, and am hitting an issue which I know how I would solve in Python, but I'm not sure what a good idiomatic Haskell approach would be. The problem is that I am trying to write a function which takes a rather large number of arguments, many of which are optional (ie, have sensible defaults). The canonical example of this is creating a GUI window, which involves a lot of style options, most of which would typically be left to default. In Python, this type of interface is often handled either as a function with many keyword arguments, or as a mutable object which has attributes set, and then a method called to handle the function call. Neither of these approaches seems plausible in Haskell. I looked at wxHaskell for inspiration - its approach (button f [text := "Quit", on command := close f]) looks quite readable, but slightly unusual (to me) for Haskell. It also seems fairly complex to implement (ie, my head hurt when I tried to follow the types involved, but maybe that's just because it's getting late :-)) To make things concrete, the example I'm really thinking of is a "send an email" function, which would take a subject, a body, a list of recipients, optional lists of cc and bcc recipients, an optional mailserver (default localhost), an optional port (default 25), and possibly optional authentication details. I found a couple of Haskell modules implementing a SMTP client, but they both just used a list of positional parameters, which I'm not really happy with. At the very least, I'd like to wrap them in a nicer interface for my code. I'd appreciate any ideas about how to think of this sort of problem - I'm pretty sure that what I need to do is think differently about the issue, rather than just mechanically translating the code I'd write in Python. But I don't really know how. Any pointers would be very helpful! Thanks, Paul.

Hi Paul,
To make things concrete, the example I'm really thinking of is a "send an email" function, which would take a subject, a body, a list of recipients, optional lists of cc and bcc recipients, an optional mailserver (default localhost), an optional port (default 25), and possibly optional authentication details.
Records are your friend. data Email = Email {subject :: String, body :: String, to :: [Address], cc = [Address], bcc = [Address], mailserver :: String, port :: Int} defaultEmail = Email{subject = "No subject", body = "", to = [], cc = [], bcc = [], mailserver = "localhost", port = 25} The user can then go: sendEmail defaultEmail{subject="Subject here", body = "body here", to = ["haskell-cafe"], mailserver = "server.haskell.org"} Now things which are't specified (port) keep their default value. The other alternative is: data EmailParams = Body String | Port Int | Mailserver String ... then: sendEmail [Body "body here", To "haskell-cafe", Mailserver "server.haskell.org" ...] I prefer the first, but the second can also be done. Thanks Neil

"Neil Mitchell"
To make things concrete, the example I'm really thinking of is a "send an email" function, which would take a subject, a body, a list of recipients, optional lists of cc and bcc recipients, an optional mailserver (default localhost), an optional port (default 25), and possibly optional authentication details.
Records are your friend.
data Email = Email {subject :: String, body :: String, to :: [Address], cc = [Address], bcc = [Address], mailserver :: String, port :: Int}
defaultEmail = Email{subject = "No subject", body = "", to = [], cc = [], bcc = [], mailserver = "localhost", port = 25}
The user can then go:
sendEmail defaultEmail{subject="Subject here", body = "body here", to = ["haskell-cafe"], mailserver = "server.haskell.org"}
Now things which are't specified (port) keep their default value.
If you do this for more than one function (and consequently more than one datatype) there's a case for a class -- something like: class Defaultable t where defaults:: t instance Defaultable Email where defaults = Email{subject = "No subject", body = "", to = [], cc = [], bcc = [], mailserver = "localhost", port = 25} which would save having a defaultFoo for every Foo (at the possible expense of occasional explicit types). -- Jón Fairbairn Jon.Fairbairn@cl.cam.ac.uk

On 29/12/06, Paul Moore
I looked at wxHaskell for inspiration - its approach (button f [text := "Quit", on command := close f]) looks quite readable, but slightly unusual (to me) for Haskell. It also seems fairly complex to implement (ie, my head hurt when I tried to follow the types involved, but maybe that's just because it's getting late :-))
This is actually a really nice solution, if you think about it: 1) You don't have to memorise the order the parameters come in (you can put the parameters in any order in that list). 2) It's self-documenting, f arg1 arg2 arg3 doesn't say what arg1, arg2 and arg3 do, but this way you get an idea. 3) If you had a function in another language with 10 parameters, all of which are optional, but you want to specify the last one, you have to include the default values (which may involve looking up what they are) of the first 9; no such problems here. Try again to understand the typing, hopefully you'll see this is probably The Best Way. If you have any questions about the typing itself, just post them here. -- -David House, dmhouse@gmail.com

On 12/29/06, Neil Mitchell
Records are your friend. [...] The other alternative is:
data EmailParams = Body String | Port Int | Mailserver String
Neat ideas. Thanks. I guess there could there be disadvantages
involved in using a list of arguments, rather than a curried function
(can't easily do partial application, for example) - but I don't know
if these would be a big deal in any practical program.
As I mentioned, I'm interested in trying to expand my brain, as well
as just solve a particular problem :-)
Thanks for the ideas - I'll ponder on them.
On 12/29/06, David House
On 29/12/06, Paul Moore
wrote: I looked at wxHaskell for inspiration
This is actually a really nice solution, if you think about it: [...]
Oh, in principle I agree (your 3 reasons are exactly why I like keyword parameters in Python). I'm not particularly sure if it's a bit over-complex for the sorts of things I have in mind, but it certainly looks nice in wxHaskell.
Try again to understand the typing, hopefully you'll see this is probably The Best Way. If you have any questions about the typing itself, just post them here.
I'm sure I can follow the typing with a bit of thinking, and it should be educational to work it through. Thanks to both of you for some useful ideas. Paul.

On 29/12/06, Paul Moore
I looked at wxHaskell for inspiration - its approach (button f [text := "Quit", on command := close f]) looks quite readable, but slightly unusual (to me) for Haskell. It also seems fairly complex to implement (ie, my head hurt when I tried to follow the types involved, but maybe that's just because it's getting late :-))
I forgot one - it's extensible, so if you want to add extra parameters you don't need to change the type of the function, and you don't need to change every single call site either. -- -David House, dmhouse@gmail.com

Paul Moore wrote:
I'm thinking around the design of a couple of things, and am hitting an issue which I know how I would solve in Python, but I'm not sure what a good idiomatic Haskell approach would be.
The problem is that I am trying to write a function which takes a rather large number of arguments, many of which are optional (ie, have sensible defaults).
There's an interesting solution to this in section 15 (starting page 53) of Magnus Carlsson and Thomas Hallgren's Fudgets thesis available online at: http://www.cs.chalmers.se/~hallgren/Thesis/ (Fudgets main page is at http://www.cs.chalmers.se/ComputingScience/Research/Functional/Fudgets/ ). Basically the idea is that for any function which takes lots of params, the params are gathered together into a datatype whose details are hidden from the user of the function. For each parameter, the user is given a function called a "Customiser" in the thesis, which takes a value of the hidden datatype and modifies it by setting the relevant parameter. Multiple params can therefore be specified by chaining Customisers together in any order with the usual function composition. The original function, instead of taking lots of params, now just takes a single parameter: a Customiser, which is used to turn the default params into the customised params. This is similar to the idea of using records but has the advantage that by judicious use of typeclasses of which the param data is an instance, you don't need to keep on inventing different names for the same thing (eg to specify colour for the background of a label control in a GUI or colour for a font you could use the name "setColour" in both cases). (A possible disadvantage is the overhead of having to create a whole new modified version of the parameter record for each Customiser in the composition so if efficiency were an issue you'd have to see if the compiler could inline the composition of the customiser functions to make it as efficient as the built in multiple field record update using rec{a=a', c=c'} syntax)
To make things concrete, the example I'm really thinking of is a "send an email" function, which would take a subject, a body, a list of recipients, optional lists of cc and bcc recipients, an optional mailserver (default localhost), an optional port (default 25), and possibly optional authentication details. I found a couple of Haskell modules implementing a SMTP client, but they both just used a list of positional parameters, which I'm not really happy with. At the very least, I'd like to wrap them in a nicer interface for my code.
-- hidden from clients data EmailParams = EmailParams { subject :: String , body :: String , recipients :: [Recipient] , cc :: [Recipient] , bcc :: [Recipient] , mailserver :: Mailserver , port :: Port , authentication :: Authentication } -- record syntax elided to save space defaultEmailParams = EmailParams "" "" [] [] [] defaultMailserver defaultPort defaultAuthentication --a "Customiser" visible to clients setSubject :: String -> EmailParams -> EmailParams setSubject s ep = ep{subject = s} -- etc send :: (EmailParams -> EmailParams) -> IO () send f = sendInternal (f defaultEmailParams) where sendInternal :: EmailParams -> IO () sendInternal = ... -- In user code: main = send (setSubject "Test" . setBody "A test email") Brian. -- http://www.metamilk.com
participants (5)
-
Brian Hulley
-
David House
-
Jón Fairbairn
-
Neil Mitchell
-
Paul Moore