Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best Practice on design and usage of data type in Haskell [closed]

Tags:

haskell

My question is related to a more general question on Haskell program design. But I would like to focus on a specific use case.

I defined a data type (e.g. Foo), and used it in a function (e.g. f) through pattern matching. Later, I realized that the type (Foo) requires some additional field to support new functionalities. However, adding the field would change how the type can be used; i.e. the existing functions depending on the type could be affected. Adding new functionalities to existing code, however unappealing, is hard to avoid. I am wondering what are the best practices at the Haskell language level to minimize the impact of such kind of modifications.

For example, the existing code is:

data Foo = Foo {
  vv :: [Int]
}

f :: Foo -> Int
f (Foo v) = sum v

The function f will be syntax wrong if I add another field to Foo:

data Foo = Foo {
  vv :: [Int]
  uu :: [Int]
}

However, if I had defined function f as the following in the first place:

f :: Foo -> Int
f foo = sum $ vv foo

, then even with the modification on Foo, f would still be correct.

like image 293
Causality Avatar asked Jan 03 '14 06:01

Causality


People also ask

How do you define data types in Haskell?

The Data Keyword and Constructors In general, we define a new data type by using the data keyword, followed by the name of the type we're defining. The type has to begin with a capital letter to distinguish it from normal expression names. To start defining our type, we must provide a constructor.

Does Haskell check types at runtime?

So no, Haskell types do not exist at runtime, in any form.

What is the difference between type and data in Haskell?

Type and data type refer to exactly the same concept. The Haskell keywords type and data are different, though: data allows you to introduce a new algebraic data type, while type just makes a type synonym. See the Haskell wiki for details.

How do types work in Haskell?

In Haskell, every statement is considered as a mathematical expression and the category of this expression is called as a Type. You can say that "Type" is the data type of the expression used at compile time.


2 Answers

Lenses solve this problem well. Just define a lens that points to the field of interest:

import Control.Lens

newtype Foo = Foo [Int]

v :: Lens' Foo [Int]
v k (Foo x) = fmap Foo (k x)

You can use this lens as a getter:

view v :: Foo -> [Int]

... a setter:

set v :: [Int] -> Foo -> Foo

... and a mapper:

over v :: ([Int] -> [Int]) -> Foo -> Foo

The best part is that if you later change your data type's internal representation, all you have to do is change the implementation of v to point to the new location of the field of interest. If your downstream users only used the lens to interact with your Foo then you won't break backwards compatibility.

like image 59
Gabriella Gonzalez Avatar answered Sep 18 '22 22:09

Gabriella Gonzalez


The best practice for processing types that might get new fields added that you want to ignore in existing code is indeed to use record selectors as you've done.

I would say that you should always define any type that might change using record notation, and you should never pattern match on a type defined with record notation using the first style with positional arguments.

Another way of expressing the above code is:

f :: Foo -> Int
f (Foo { vv = v }) = sum v

This is arguably more elegant, and it also works better in the case where Foo has multiple data constructors.

like image 25
GS - Apologise to Monica Avatar answered Sep 20 '22 22:09

GS - Apologise to Monica