Say, I have a data type
data FooBar a = Foo String Char [a]
| Bar String Int [a]
I need to create values of this type and give empty list as the second field:
Foo "hello" 'a' []
or
Bar "world" 1 []
1) I do this everywhere in my code and I think it would be nice if I could omit the empty list part somehow and have the empty list assigned implicitly. Is this possible? Something similar to default function arguments in other languages.
2) Because of this []
"default" value, I often need to have a partial constructor application that results in a function that takes the first two values:
mkFoo x y = Foo x y []
mkBar x y = Bar x y []
Is there a "better" (more idiomatic, etc) way to do it? to avoid defining new functions?
3) I need a way to add things to the list:
add (Foo u v xs) x = Foo u v (x:xs)
add (Bar u v xs) x = Bar u v (x:xs)
Is this how it is done idiomatically? Just a general purpose function?
As you see I am a beginner, so maybe these questions make little sense. Hope not.
(2) and (3) are perfectly normal and idiomatic ways of doing such things. About (2) in particular, one expression you will occasionally hear is "smart constructor". That just means a function like your
mkFoo
/mkBar
that produces aFooBar a
(or aMaybe (FooBar a)
etc.) with some extra logic to ensure only reasonable values can be constructed.Here are some additional tricks that might (or might not!) make sense, depending on what you are trying to do with
FooBar
.If you use
Foo
values andBar
values in similar ways most of the time (i.e. the difference between having theChar
field and theInt
one is a minor detail), it makes sense to factor out the similarities and use a single constructor:Beyond avoiding case analysis when you don't care about the
FooBarTag
, that allows you to safely use record syntax (records and types with multiple constructors do not mix well).Records allow you to use the fields without having to pattern match the whole thing.
If there are sensible defaults for all fields in a
FooBar
, you can go one step beyondmkFoo
-like constructors and define a default value.You don't need records to use a default, but they allow overriding default fields conveniently.
If you ever get tired of typing long names for the defaults over and over, consider the
data-default
package:Do note that a significant number of people do not like the
Default
class, and not without reason. Still, for types which are very specific to your application (e.g. configuration settings)Default
is perfectly fine IMO.Finally, updating record fields can be messy. If you end up annoyed by that, you will find
lens
very useful. Note that it is a big library, and it might be a little overwhelming to a beginner, so take a deep breath beforehand. Here is a small sample:Here is a gentle introduction to
lens
which focuses on aspects I have not demonstrated here, specially in how nicely lenses can be composed.