How do I set default values in a record
Asked Answered
F

2

11

I'm trying to set default values in a record object that can be overwritten.

data Car c y = Car {
    company :: c,
    year :: y
    }

What I'd like to do is set a default:

data Car c y = Car {
    company :: c -- OR "Ford"
    year :: y
    }

So far I've tried doing this by setting the type of c to a Maybe type:

data Car = Car {
    company:: Maybe String
    year :: Maybe Int
    }

However I get this predictable error:

Fields of `Car' not initialised: year

Ironically, this is exactly the thing I'm trying to get around. I want to produce a new record that has the values I'm not setting already initialized. One way I've found is to partially apply the Car type:

data Car c y = {
    company :: c,
    year :: y
    }

let ford = Car "Ford" 

-- This produces a function (y -> Car [Char] y)

However this produces 2 new problems:

  1. In the event my data type has 100+ field types, I'll end up with 100 factorial curried functions

  2. The partially applied functions I can create are dependent on the order of variables in the declaration. You'll notice that I cant produce a car1988 function, for example.

How can I write a function that allows me to create records with default values? Please help.

Fabrice answered 12/8, 2015 at 12:46 Comment(0)
G
15

That's the kind of thing Haskell prefers to handle in a library, rather than hacking up some hard-wired language support (which would probably cause all kinds of problems, just like it does in C++).

import Data.Default

instance Default Car where
  def = Car Nothing Nothing
          -- or whatever you want as the defaults

ford :: Car
ford = def { company = Just "Ford" }

GHCi> ford
Car {company = Just "Ford", year = Nothing}

Rather than using the Default class you can also just define a defaultCar.

You may need to use cabal to install data-default, if it's not installed in your system already.

Garmaise answered 12/8, 2015 at 12:58 Comment(8)
To add on to this, "smart constructors" (this style of construction) are always the best way to go here. You can have as many as you like with whatever arguments you like, have some exported from the module and some not, and if your type is polymorphic you have the option to have a different constructor for each more specific type you want to handle.Telemetry
Very nice. But what do you mean "rather than use the Default class"? To define defaultCar I'd need to use this Default construct right?Fabrice
no, you can just define a normal value defaultCar and then modify that when you need a non-standard car. also, if you want to use Default, then you should just use the data-default-class package, since its a little more lightweightBurned
I understand this defaultCar to be something like let defaultCar c y = Car {company = Just c, year = Just y} but this produces the same problems i outlines above.Fabrice
@Fabrice Nah, just write defaultCar = Car Nothing Nothing. Then ford = defaultCar { company = Just "Ford" } works just as in the answer, but with the name defaultCar instead of def.Laughable
So if i understand right, def = Car Nothing Nothing is still accepting arguments in a particular order right? what if i wanted to make a car1988 function, that outputs the year as 1988 for all inputs?Fabrice
@dopatraman, you should probably start by reading up on Haskell record update syntax.Impressive
@ReidBarton do you have a link apart from the Haskell wiki?Fabrice
M
0

I would use a slightly verbose method while utilising Data.Text.

{-#Language OverloadedString#-}

import Data.Text as T

data Car c y = Car {
    company :: Text 
    year :: Double
    }

toCar :: [T.Text] -> Car
toCar inp = case inp of 
              [] -> fail "Give me an input"
              [v:h] -> Car { company = justifyRight 1 "Ford" h 
                           , year = read $ T.unpack $ last inp
                           }

This will take in inputs such as inp ["8"] and inp ["Ashburn", "8"]

Michey answered 22/3, 2019 at 19:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.