Can fields in a record be optional?

Hi Folks, Can fields in a record be made "optional"? Let me motivate my question: I have a data type called "Contract". It is a record. It has one field that holds a currency value, a second field that holds payments, and a third field to hold sub-Contracts. data Contract = Contract { currency :: Currency , payments :: Double , contracts :: [Contract] } deriving (Show) Here is a function that creates a Contract with currency c and payments = 1: one :: Currency -> Contract one c = Contract { currency = c, payments = 1, contracts = [] } Since there are no sub-Contracts I'd rather not specify that last field. Here is a function that AND's two Contracts, c1 and c2, and returns one Contract: and :: Contract -> Contract -> Contract (and) c1 c2 = Contract { currency = undefined, payments = undefined, contracts = [c1, c2] } I'd rather not specify those first two fields. See why I desire optional fields? Perhaps I am going about it completely wrong and there is a better way to express Contract? I am eager to learn! /Roger

The way I often do this is to create an "ur" instance where all the fields have default values. Then to create an instance, I just do a "record update" of this instance. For example: data Contract = Contract { currency :: Currency , payments :: Double , contracts :: [Contract] } deriving (Show) urContract = Contract { currency = undefined, payments = undefined, contracts = [] } one :: Currency -> Contract one c = urContract { currency = c, payments = 1} and :: Contract -> Contract -> Contract (and) c1 c2 = urContract { contracts = [c1, c2] } ____________________ David Place Owner, Panpipes Ho! LLC http://panpipesho.com d@vidplace.com On Jul 17, 2011, at 6:03 PM, Costello, Roger L. wrote:
Hi Folks,
Can fields in a record be made "optional"?

On 18 July 2011 00:18, David Place
The way I often do this is to create an "ur" instance where all the fields have default values. Then to create an instance, I just do a "record update" of this instance.
For this there is Data.Default in data-default: http://hackage.haskell.org/packages/archive/data-default/0.2.0.1/doc/html/Da...

Christopher Done wrote:
For this there is Data.Default in data-default: http://hackage.haskell.org/packages/archive/data-default/0.2.0.1/doc/html/Da...
You don't need the added complexity of a type class for this - just define the default value. The case where you need a class for default values is when you're using generics. See, for example, System.Console.CmdArgs.Default in Neil Mitchell's cmdards package[1]. Regards, Yitz [1] http://hackage.haskell.org/package/cmdargs

Oops, sorry about the typo. It's the *cmdargs* package. http://hackage.haskell.org/package/cmdargs -Yitz

On Mon, Jul 18, 2011 at 9:44 AM, Yitzchak Gale
Christopher Done wrote:
For this there is Data.Default in data-default: http://hackage.haskell.org/packages/archive/data-default/0.2.0.1/doc/html/Da...
You don't need the added complexity of a type class for this - just define the default value.
I don't think it's a case of necessity. It just happens to be very convenient to be able to use the three letters "def" instead of "defaultFrobnicatorSettings" or something like that. Michael
The case where you need a class for default values is when you're using generics. See, for example, System.Console.CmdArgs.Default in Neil Mitchell's cmdards package[1].
Regards, Yitz
[1] http://hackage.haskell.org/package/cmdargs
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners

I wrote:
You don't need the added complexity of a type class for this - just define the default value.
Michael Snoyman wrote:
I don't think it's a case of necessity. It just happens to be very convenient to be able to use the three letters "def" instead of "defaultFrobnicatorSettings" or something like that.
You can use whatever name you'd like for a default value, short or descriptive. Type classes are a beautiful and powerful technique in Haskell, but they do add some underlying complexity to your types. In my experience, when building and maintaining large code bases in Haskell, things are much easier when you limit the use of type classes to when they are really needed. I create type classes when I need non-trivial polymorphism. Even then, I often prefer simpler techniques such as data types with parameters or passing functions as parameters. There are also advanced techniques using type classes which are needed for building some kinds of libraries. In particular, I have come to the conclusion that type classes are not a good tool for simple namespace control in a typical Haskell program. It is a common mistake for beginners who come to Haskell with a background in OOP to massively overuse type classes. Style is a matter of taste, of course. But much of the power and beauty of Haskell lies away from type classes, and it's a shame to miss out on it. So I think it is important for beginners, especially when coming from OOP, to start out by avoiding defining type classes whenever there is a good alternative (i.e. almost always). Regards, Yitz

Thanks for the pointer to Data.Default. I don't see how it applies in this case, though. The goal is to avoid mentioning the defaulted fields when constructing a record instance. Example? ____________________ David Place Owner, Panpipes Ho! LLC http://panpipesho.com d@vidplace.com On Jul 18, 2011, at 1:45 AM, Christopher Done wrote:
On 18 July 2011 00:18, David Place
wrote: The way I often do this is to create an "ur" instance where all the fields have default values. Then to create an instance, I just do a "record update" of this instance.
For this there is Data.Default in data-default:
http://hackage.haskell.org/packages/archive/data-default/0.2.0.1/doc/html/Da...

It's effectively the exact same thing as your example but with the
default record put into a typeclass for convenience. Refactoring your
original post:
data Contract = Contract {
currency :: Currency
, payments :: Double
, contracts :: [Contract]
}
deriving (Show)
instance Default Contract where
def = Contract { currency = undefined, payments = undefined, contracts = [] }
one :: Currency -> Contract
one c = def { currency = c, payments = 1}
and :: Contract -> Contract -> Contract
(and) c1 c2 = def { contracts = [c1, c2] }
It's a very simple package and provides some default instances for
your convenience. Use it if you like.
On Mon, Jul 18, 2011 at 3:47 PM, David Place
Thanks for the pointer to Data.Default. I don't see how it applies in this case, though. The goal is to avoid mentioning the defaulted fields when constructing a record instance. Example?
____________________ David Place Owner, Panpipes Ho! LLC http://panpipesho.com d@vidplace.com
On Jul 18, 2011, at 1:45 AM, Christopher Done wrote:
On 18 July 2011 00:18, David Place
wrote: The way I often do this is to create an "ur" instance where all the fields have default values. Then to create an instance, I just do a "record update" of this instance.
For this there is Data.Default in data-default:
http://hackage.haskell.org/packages/archive/data-default/0.2.0.1/doc/html/Da...
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners
-- Regards, Austin

Thanks, Austin, for taking the time to explain. I guess I don't find it gives any advantage over the way I currently do it.
_____________________
David F. Place
Owner, Panpipes Ho!, LLC
http://panpipesho.com
On Jul 18, 2011, at 5:33 PM, austin seipp
It's effectively the exact same thing as your example but with the default record put into a typeclass for convenience.

On 18:18 Sun 17 Jul , David Place wrote:
The way I often do this is to create an "ur" instance where all the fields have default values. Then to create an instance, I just do a "record update" of this instance. For example:
urContract = Contract { currency = undefined, payments = undefined, contracts = [] }
Out of curiosity, what's the etymology for your 'ur'? -- Mats Rauhala MasseR

On Jul 20, 2011, at 8:07 AM, Mats Rauhala wrote:
On 18:18 Sun 17 Jul , David Place wrote:
The way I often do this is to create an "ur" instance where all the fields have default values. Then to create an instance, I just do a "record update" of this instance. For example:
urContract = Contract { currency = undefined, payments = undefined, contracts = [] }
Out of curiosity, what's the etymology for your 'ur'?
A prefix denoting the earliest or original form of something.
-- Mats Rauhala MasseR _______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners

I'm not sure I would model your datatype this way, I don't like the idea to put unnecessary undefined values in the case of subcontracts. I would instead data Contract = Contract { currency :: Currency, payments :: Double } | SubContract { contracts :: [Contract] } one c = Contract { currency = c, payments = 1 } and :: Contract -> Contract -> Contract (and) c1 c2 = Subcontract { contracts = [c1, c2] }

Roger L. Costello wrote:
data Contract = Contract { currency :: Currency , payments :: Double , contracts :: [Contract] } deriving (Show)
David Virebayre wrote:
I'm not sure I would model your datatype this way, I don't like the idea to put unnecessary undefined values in the case of subcontracts.
data Contract = Contract { currency :: Currency, payments :: Double } | SubContract { contracts :: [Contract] }
I think Roger's original design is just fine. There is nothing "undefined" about the empty list. Roger is saying that every contract has a list of subcontracts. Lists can be empty, and that is legitimate. In effect, Roger has defined his collection of contracts to be a rose tree, and this is the classic way to do that in Haskell. Another option would be to be more explicit about that and use Data.Tree from the containers[1] package, which is included in the Haskell Platform. Tree algorithms are often quite elegant. We haven't seen the rest of Roger's program, but I'll bet it looks really nice with his original definition. Regards, Yitz

2011/7/18 Yitzchak Gale
Roger L. Costello wrote:
data Contract = Contract { currency :: Currency , payments :: Double , contracts :: [Contract] } deriving (Show)
David Virebayre wrote:
I'm not sure I would model your datatype this way, I don't like the idea to put unnecessary undefined values in the case of subcontracts.
data Contract = Contract { currency :: Currency, payments :: Double } | SubContract { contracts :: [Contract] }
I think Roger's original design is just fine.
There is nothing "undefined" about the empty list. Roger is saying that every contract has a list of subcontracts. Lists can be empty, and that is legitimate.
I'm not sure you read his post entirely. Here's his and function :
(and) c1 c2 = Contract { currency = undefined, payments = undefined, contracts = [c1, c2] }
Notice how he's setting currency and payments to undefined, since he needs only the contracts field for subcontracts. David.

David Virebayre wrote:
I'm not sure you read his post entirely.
Here's his and function :
(and) c1 c2 = Contract { currency = undefined, payments = undefined, contracts = [c1, c2] }
Notice how he's setting currency and payments to undefined, since he needs only the contracts field for subcontracts.
Ah, right, I didn't notice that. Not a good idea. Your design is better then. Another possibility would be Maybe Currency and Maybe Double. That often comes out simpler - but not always. If the case with Currency and Payments is a totally separate case than when there are subcontracts, with different logic, then David's design is best. If the cases are usually handled pretty much in the same way with special handling for when one or more fields is missing, then Maybe is best. -Yitz
participants (8)
-
austin seipp
-
Christopher Done
-
Costello, Roger L.
-
David Place
-
David Virebayre
-
Mats Rauhala
-
Michael Snoyman
-
Yitzchak Gale