akonsu
akonsu

Reputation: 29536

data type with a default field and that needs a function that works with it

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.

Upvotes: 3

Views: 245

Answers (3)

duplode
duplode

Reputation: 34378

(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 a FooBar a (or a Maybe (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 and Barvalues in similar ways most of the time (i.e. the difference between having the Char field and the Int one is a minor detail), it makes sense to factor out the similarities and use a single constructor:

data FooBar a = FooBar String FooBarTag [a]
data FooBarTag = Foo Char | Bar Int

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).

data FooBar a = FooBar
    { fooBarName :: String
    , fooBarTag :: FooBarTag
    , fooBarList :: [a]
    }

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 beyond mkFoo-like constructors and define a default value.

defaultFooBar :: FooBar a
defaultFooBar = FooBar
    { fooBarName = ""
    , fooBarTag = Bar 0
    , fooBarList = []
    }

You don't need records to use a default, but they allow overriding default fields conveniently.

myFooBar = defaultFooBar
    { fooBarTag = Foo 'x'
    }

If you ever get tired of typing long names for the defaults over and over, consider the data-default package:

instance Default (FooBar a) where
    def = defaultFooBar

myFooBar = def { fooBarTag = Foo 'x' }

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:

{-# 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 is a gentle introduction to lens which focuses on aspects I have not demonstrated here, specially in how nicely lenses can be composed.

Upvotes: 1

Jon Purdy
Jon Purdy

Reputation: 54971

The idiomatic thing is to take those parameters of a function or constructor that you commonly want to partially apply, and move them toward 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 []

Similarly, reordering the parameters to add lets you partially apply add to get functions of type FooBar a -> FooBar a, which can be easily composed:

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

Upvotes: 2

Clay Thomas
Clay Thomas

Reputation: 198

I'll address your questions one by one.

  1. Default arguments do not exist in Haskell. They are simply not worth the added complexity and loss of compositionally. Being a functional language, you do a lot more function manipulation in Haskell, so funkiness like default arguments would be tough to handle.

  2. One thing I didn't realize when I started Haskell is that data constructors are functions just like everything else. In your example,

    Foo :: String -> Char -> [a] -> FooBar a
    

    Thus you can write functions for filling 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 []
    
  3. The lens package provides elegant solutions to questions like this. However, you can tell at a glance that this package is enormously complicated. Here is the net result of how you would call the lens package:

    _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: the lens function produces a Lens type when given a getter and a setter for some type. Lens s t a b is a type that says "s holds an a and t holds a b. Thus, if you give me a function a -> b, I can give you a function s -> t". That is exactly what over does: you provide it a lens and a function (in our case, (3:) was a function that adds 3 to the front of a List) and it applies the function "where the lens indicates". This is very similar to a functor, however, we have significantly more freedom (in this example, the functor instance would be obligated to change every element of the lists, not operate on the lists themselves).

    Note that our new _list lens is very generic: it works equally well over Foo and Bar and the lens package provides many functions other than over for doing magical things.

Upvotes: 2

Related Questions