A data type with a default field, and it needs a function to work with it
Let's 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 an 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 somehow omit the empty part of the list and not have the empty list assigned implicitly. Is it 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 this? 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 the way it is done idiomatically? Just a general purpose function?
As you can see, I'm a beginner, so maybe these questions don't make any sense. I hope no.
source to share
(2) and (3) are perfectly normal and idiomatic ways of doing such things. About (2) in particular, one expression that you sometimes hear is "smart constructor". This means that it is a function mkFoo
/ mkBar
that creates FooBar a
(or Maybe (FooBar a)
, etc.) with some extra logic to ensure that only sensible values can be created.
Here are some additional tricks that may (or may not!) Make sense, depending on what you are trying to do with FooBar
.
If you use values Foo
and Bar
values in a similar way most of the time (i.e. the difference between having a field Char
and Int
being a minor detail), it makes sense to factor from the general lines and use a single constructor:
data FooBar a = FooBar String FooBarTag [a]
data FooBarTag = Foo Char | Bar Int
Apart from preventing the parsing of cases where you don't care FooBarTag
, that allows for the safe use of the notation syntax (records and types with multiple constructors don't mix well).
data FooBar a = FooBar
{ fooBarName :: String
, fooBarTag :: FooBarTag
, fooBarList :: [a]
}
Records allow you to use fields without a pattern to match everything.
If there are reasonable defaults for all fields in FooBar
, you can go one step beyond the constructors mkFoo
and define a default.
defaultFooBar :: FooBar a
defaultFooBar = FooBar
{ fooBarName = ""
, fooBarTag = Bar 0
, fooBarList = []
}
You don't need records to use the default, but they allow you to conveniently override the default fields.
myFooBar = defaultFooBar
{ fooBarTag = Foo 'x'
}
If you've ever gotten tired of typing long names by default, consider the data-default
package:
instance Default (FooBar a) where
def = defaultFooBar
myFooBar = def { fooBarTag = Foo 'x' }
Note that a significant number of people dislike the classDefault
, and for good reason. However, for types that are very specific to your application (like config settings) Default
works fine IMO.
Finally, updating record fields can be messy. If you end up annoyed by this, you will find it lens
very helpful. Note that this is a large library and can be a bit overwhelming for a beginner, so take a deep breath beforehand. Here's a small example:
{-# LANGUAGE TemplateHaskell #-} -- At the top of the file. Needed for makeLenses.
import Control.Lens
-- Note the underscores.
-- If you are going to use lenses, it is sensible not to export the field names.
data FooBar a = FooBar
{ _fooBarName :: String
, _fooBarTag :: FooBarTag
, _fooBarList :: [a]
}
makeLenses ''FooBar -- Defines lenses for the fields automatically.
defaultFooBar :: FooBar a
defaultFooBar = FooBar
{ _fooBarName = ""
, _fooBarTag = Bar 0
, _fooBarList = []
}
-- Using a lens (fooBarTag) to set a field without record syntax.
-- Note the lack of underscores in the name of the lens.
myFooBar = set fooBarTag (Foo 'x') defaultFooBar
-- Using a lens to access a field.
myTag = view fooBarTag myFooBar -- Results in Foo 'x'
-- Using a lens (fooBarList) to modify a field.
add :: a -> FooBar a -> FooBar a
add x fb = over fooBarList (x :) fb
-- set, view and over have operator equivalents, (.~). (^.) and (%~) respectively.
-- Note that (^.) is flipped with respect to view.
Here's a gentle introduction to lens
, focusing on aspects that I haven't demonstrated here, especially how to create lenses beautifully.
source to share
I will solve your questions one by one.
-
There are no default arguments in Haskell. They just aren't worth the added complexity and loss of composition. As a functional language, you do a lot more function manipulation in Haskell, so fan coil units like default arguments will be difficult to handle.
-
One thing I didn't understand when I started Haskell is that data constructors are functions like everything else. In your example
Foo :: String -> Char -> [a] -> FooBar a
This way you can write functions to fill in various arguments of other functions, and then those functions will work with Foo or Bar or whatever.
fill1 :: a -> (a -> b) -> b fill1 a f = f a --Note that fill1 = flip ($) fill2 :: b -> (a -> b -> c) -> (a -> c) --Equivalently, fill2 :: b -> (a -> b -> c) -> a -> c fill2 b f = \a -> f a b fill3 :: c -> (a -> b -> c -> d) -> (a -> b -> d) fill3 c f = \a b -> f a b c fill3Empty :: (a -> b -> [c] -> d) -> (a -> b -> d) fill3Empty f = fill3 [] f --Now, we can write > fill3Empty Foo x y Foo x y []
-
The lens package provides elegant solutions to such questions. However, you can tell at a glance that this package is extremely complex. Here is what you would call a lens kit:
_list :: Lens (FooBar a) (FooBar b) [a] [b] _list = lens getter setter where getter (Foo _ _ as) = as getter (Bar _ _ as) = as setter (Foo s c _) bs = Foo s c bs setter (Bar s i _) bs = Bar s i bs
Now we can do
> over _list (3:) (Foo "ab" 'c' [2,1]) Foo "ab" 'c' [3,2,1]
Some explanation: a function
lens
creates a typelens
if a getter and setter is given for some type.Lens s t a b
is a type that says "s
containsa
andt
contains ab
. So if you give me a functiona -> b
, I can give you a functions -> t
." This is what it doesover
: you provide it with a lens and a function (in our case(3:)
was a function that adds 3 to the front of the list), and it applies the "where the lens points" function. This is very similar to a functor, but we have a lot more freedom (in this example, the functor instance would have to modify every element of the lists, rather than work with the lists themselves).Note that our new lens _list is very generic: it works the same on
Foo
andBar
, and the lens package provides a lot of other than on functionsover
to do magic things.
source to share
The idiomatic thing is to take those function or constructor parameters that you usually want to partially apply and move them to the beginning:
data FooBar a = Foo [a] String Char
| Bar [a] String Int
foo :: String -> Char -> FooBar a
foo = Foo []
bar :: String -> Int -> FooBar a
bar = Bar []
Likewise, reordering of parameters before add
allows partial application add
to obtain functions of type FooBar a -> FooBar a
that are easy to compile:
add :: a -> FooBar a -> FooBar a
add x (Foo xs u v) = Foo (x:xs) u v
add123 :: FooBar Int -> FooBar Int
add123 = add 1 . add 2 . add 3
add123 (foo "bar" 42) == Foo [1, 2, 3] "bar" 42
source to share