Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 930
akonsu Avatar asked Nov 26 '25 17:11

akonsu


2 Answers

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.

like image 187
Clay Thomas Avatar answered Nov 28 '25 14:11

Clay Thomas


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
like image 45
Jon Purdy Avatar answered Nov 28 '25 13:11

Jon Purdy