Consider the following fragment:
data File
= NoFile
| FileInfo {
path :: FilePath,
modTime :: Data.Time.Clock.UTCTime
}
| FileFull {
path :: FilePath,
modTime :: Data.Time.Clock.UTCTime,
content :: String
}
deriving Eq
That duplication is a bit of a "wart", though in this one-off instance not particularly painful. In order to further improve my understanding of Haskell's rich type system, what might be preferred "clean"/"idiomatic" approaches for refactoring other than either simply creating a separate data record type for the 2 duplicate fields (then replacing them with single fields of that new data type) or replacing the FileFull record notation with something like | FileFull File String, which wouldn't be quite clean either (as here one would only want FileInfo in there for example, not NoFile)?
(Both these "naive" approaches would be somewhat intrusive/annoying with respect to having to then fix up many modules manually throughout the rest of the code-base here.)
One thing I considered would be parameterizing like so:
data File a
= NoFile
| FileMaybeWithContent {
path :: FilePath,
modTime :: Data.Time.Clock.UTCTime
content :: a
}
deriving Eq
Then for those "just info, not loaded" contexts a would be (), otherwise String. Seems too general anyway, we want either String or nothing, leading us to Maybe, doing once again away with the a parameter.
Of course we've been there before: content could just be done with Maybe String of course, then "refactor any compile errors away" and "done". That'll probably be the order of the day, but knowing Haskell and the many funky GHC extensions.. who knows just what exotic theoretic trick/axiom/law I've been missing, right?! See, the differently-named "semantic insta-differentiator" between a "just meta-data info" value and a "file content with meta info" value does work well throughout the rest of the code-base as far as eased comprehension.
(And yes, I perhaps should have removed NoFile and used Maybe Files throughout, but then... not sure whether there's really a solid reason to do so and a different question altogether anyway..)
All of the following are equivalent/isomorphic, as I think you've discovered:
data F = U | X A B | Y A B C
data F = U | X AB | Y AB C
data AB = AB A B
data F = U | X A B (Maybe C)
So the color of the bike shed really depends on the context (e.g. do you have use for an AB elsewhere?) and your own aesthetic preferences.
It might clarify things and help you understand what you're doing to have some sense of the algebra of algebraic data types
We call types like Either "sum types" and types like (,) "product types" and they are subject to the same kinds of transformations you're familiar with like factoring
f = 1 + (a * b) + (a * b * c)
= 1 + ((a * b) * ( 1 + c))
As others have noted, the NoFile constructor is probably not necessary, but you can keep it if you want. If you feel your code is more readable and/or better understood with it, then I say keep it.
Now the trick with combining the other two constructors is by hiding the content field. You were on the right track by parameterizing File, but that alone isn't enough since then we can have File Foo, File Bar, etc. Fortunately, GHC has some nifty ways to help us.
I'll write out the code here and then explain how it works.
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}
import Data.Void
data Desc = Info | Full
type family Content (a :: Desc) where
Content Full = String
Content _ = Void
data File a = File
{ path :: FilePath
, modTime :: UTCTime
, content :: Content a
}
There are a few things going on here.
First, note that in the File record, the content field now has type Content a instead of just a. Content is a type family, which is (in my opinion) a confusing name for type-level function. That is, the compiler replaces Content a with some other type based on what a is and how we've defined Content.
We defined Content Full to be String, so that when we have a value f1 :: File Full, its content field will have a String value. On the other hand, f2 :: File Info will have a content field with type Void which has no values.
Cool right? But what's preventing us from having File Foo now?
That's where DataKinds comes to the rescue. It "promotes" the data type Desc to a kind (the type of types in Haskell) and type constructors ,Info and Full, to types of kind Desc instead of merely values of type Desc.
Notice in the declaration of Content that I have annotated a. It looks like a type annotation, but a is already a type. This is a kind annotation. It forces a to be something of kind Desc and the only types of kind Desc are Info and Full.
By now you're probably totally sold on how awesome this is, but I should warn you there's no free lunch. In particular, this is a compile-time construction. Your single File type becomes two different types. This can cause other related logic (producers and consumers of File records) to become complicated. If your use case doesn't mix File Info records with File Full records, then this is the way to go. On the other hand, if you want to do something like have a list of File records which can be a mixture of both types, then you're better off just making the type of your content field Maybe String.
Another thing is, how exactly do you make a File Info since there's no value of Void to use for the content field? Well, technically it should be ok to use undefined or error "this should never happen" since it is (morally) impossible to have a function of type Void -> a, but if that makes you feel uneasy (and it probably should), then just replace Void with (). Unit is almost as useless and doesn't require 'values' of bottom.
Related
Again stuck on something probably theoretical. There are many libraries in Haskell, i'd like to use less as possible. If I have a type like this:
data Note = Note { _noteID :: Int
, _noteTitle :: String
, _noteBody :: String
, _noteSubmit :: String
} deriving Show
And use that to create a list of [Note {noteID=1...}, Note {noteID=2...}, ] et cetera. I now have a list of type Note. Now I want to write it to a file using writeFile. Probably it ghc will not allow it considering writeFile has type FilePath -> String -> IO (). But I also want to avoid deconstructing (writeFile) and constructing (readFile) the types all the time, assuming I will not leave the Haskell 'realm'. Is there a way to do that, without using special libs? Again: thanks a lot. Books on Haskell are good, but StackOverflow is the glue between the books and the real world.
If you're looking for a "quick fix", for a one-off script or something like that, you can derive Read in addition to Show, and then you'll be able to use show to convert to String and read to convert back, for example:
data D = D { x :: Int, y :: Bool }
deriving (Show, Read)
d1 = D 42 True
s = show d1
-- s == "D {x = 42, y = True}"
d2 :: D
d2 = read s
-- d2 == d1
However, please, please don't put this in production code. First, you're implicitly relying on how the record is coded, and there are no checks to protect from subtle changes. Second, the read function is partial - that is, it will crash if it can't parse the input. And finally, if you persist your data this way, you'll be stuck with this record format and can never change it.
For a production-quality solution, I'm sorry, but you'll have to come up with an explicit, documented serialization format. No way around it - in any language.
I find myself running up against the same pattern in my designs where I start with a type with a few data constructors, eventually want to be able to type against those data constructors and thus split them into their own types, just to then have to increase the verbosity of other parts of the program by needing to use Either or another tagged-union for situations where I still need to represent multiple of these types (namely collections).
I am hoping someone can point me to a better way of accomplishing what I'm trying to do. Let me start with a simple example. I am modeling a testing system, where you can have nested test suites which eventually end in tests. So, something like this:
data Node =
Test { source::string }
Suite { title::string, children::[Node] }
So, pretty simple so far, essentially a fancy Tree/Leaf declaration. However, I quickly realize that I want to be able to make functions that take Tests specifically. As such, I'll now split it up as so:
data Test = Test { source::string }
data Suite = Suite { title::string, children::[Either Test Suite] }
Alternatively I might roll a "custom" Either (especially if the example is more complicated and has more than 2 options), say something like:
data Node =
fromTest Test
fromSuite Suite
So, already its pretty unfortunate that just to be able to have a Suite that can have a combination of Suites or Tests I end up with a weird overhead Either class (whether it be with an actual Either or a custom one). If I use existential type classes, I could get away with making both Test and Suite derive "Node_" and then have Suite own a List of said Nodes. Coproducts would allow something similar, where I'd essentially do the same Either strategy without the verbosity of the tags.
Allow me to expand now with a more complex example. The results of the tests can be either Skipped (the test was disabled), Success, Failure, or Omitted (the test or suite could not be run due to a previous failure). Again, I originally started with something like this:
data Result = Success | Omitted | Failure | Skipped
data ResultTree =
Tree { children::[ResultTree], result::Result } |
Leaf Result
But I quickly realized I wanted to be able to write functions that took specific results, and more importantly, have the type itself enforce the ownership properties: A successful suite must only own Success or Skipped children, Failure's children can be anything, Omitted can only own Omitted, etc. So now I end up with something like this:
data Success = Success { children::[Either Success Skipped] }
data Failure = Failure { children::[AnyResult] }
data Omitted = Omitted { children::[Omitted] }
data Skipped = Skipped { children::[Skipped] }
data AnyResult =
fromSuccess Success |
fromFailure Failure |
fromOmitted Omitted |
fromSkipped Skipped
Again, I now have these weird "Wrapper" types like AnyResult, but, I get type enforcement of something that used to only be enforced from runtime operation. Is there a better strategy to this that doesn't involve turning on features like existential type classes?
The first thing that came to my mind reading your sentence: "I quickly realized I wanted to be able to write functions that took specific results" is Refinement Types.
They allow to take only some values from a type as input, and make those constraints compile-time check/error.
There is this video from a talk at HaskellX 2018, that introduces LiquidHaskell, which allows the use of Refinement Types in Haskell:
https://skillsmatter.com/skillscasts/11068-keynote-looking-forward-to-niki-vazou-s-keynote-at-haskellx-2018
You have to decorate your haskell function signature, and have LiquidHaskell installed:
f :: Int -> i : Int {i | i < 3} -> Int would be a function which could only accept as second parameter an Int with a value < 3, checked at compile time.
You might as well put constraints on your Result type.
I think what you may be looking for is GADTs with DataKinds. This lets you refine the types of each constructor in a data type to a particular set of possible values. For example:
data TestType = Test | Suite
data Node (t :: TestType) where
TestNode :: { source :: String } -> Node 'Test
SuiteNode :: { title :: String, children :: [SomeNode] } -> Node 'Suite
data SomeNode where
SomeNode :: Node t -> SomeNode
Then when a function operates only on tests, it can take a Node 'Test; on suites, a Node 'Suite; and on either, a polymorphic Node a. When pattern-matching on a Node a, each case branch gets access to an equality constraint:
useNode :: Node a -> Foo
useNode node = case node of
TestNode source -> {- here it’s known that (a ~ 'Test) -}
SuiteNode title children -> {- here, (a ~ 'Suite) -}
Indeed if you took a concrete Node 'Test, the SuiteNode branch would be disallowed by the compiler, since it can’t ever match.
SomeNode is an existential that wraps a Node of an unknown type; you can add extra class constraints to this if you want.
You can do a similar thing with Result:
data ResultType = Success | Omitted | Failure | Skipped
data Result (t :: ResultType) where
SuccessResult
:: [Either (Result 'Success) (Result 'Skipped)]
-> Result 'Success
FailureResult
:: [SomeResult]
-> Result 'Failure
OmittedResult
:: [Result 'Omitted]
-> Result 'Omitted
SkippedResult
:: [Result 'Skipped]
-> Result 'Skipped
data SomeResult where
SomeResult :: Result t -> SomeResult
Of course I assume in your actual code there’s more information in these types; as it is, they don’t represent much. When you have a dynamic computation such as running a test that may produce different kinds of result, you can return it wrapped in SomeResult.
In order to work with dynamic results, you may need to prove to the compiler that two types are equal; for that, I direct you to Data.Type.Equality, which provides a type a :~: b which is inhabited by a single constructor Refl when the two types a and b are equal; you can pattern-match on this to inform the typechecker about type equalities, or use the various combinators to carry out more complicated proofs.
Also useful in conjunction with GADTs (and ExistentialTypes, less generally) is RankNTypes, which basically enables you to pass polymorphic functions as arguments to other functions; this is necessary if you want to consume an existential generically:
consumeResult :: SomeResult -> (forall t. Result t -> r) -> r
consumeResult (SomeResult res) k = k res
This is an example of continuation-passing style (CPS), where k is the continuation.
As a final note, these extensions are widely used and largely uncontroversial; you needn’t be wary of opting in to (most) type system extensions when they let you express what you mean more directly.
For the representation of a DSL syntax tree I have data types that represent this tree. At several places, within this tree I get quite a number of subelements that are optional and/or have a "*" multiplicity. So one data type might look something like
data SpecialDslExpression = MyExpression String [Int] Double [String] (Maybe Bool)
What I am looking for is a possibility to construct such a type without having to specify all of the parameters, assuming I have a valid default for each of them. The usage scenario is such that I need to create many instances of the type with all kinds of combinations of its parameters given or omitted (most of the time two or three), but very rarely all of them. Grouping the parameters into subtypes won't get me far as the parameter combinations don't follow a pattern that would have segmentation improve matters.
I could define functions with different parameter combinations to create the type using defaults for the rest, but I might end up with quite a number of them that would become hard to name properly, as there might be no possibility to give a proper name to the idea of createWithFirstAndThirdParameter in a given context.
So in the end the question boils down to: Is it possible to create such a data type or an abstraction over it that would give me something like optional parameters that I can specify or omit at wish?
I would suggest a combinations of lenses and a default instance. If you are not already importing Control.Lens in half of your modules, now is the time to start! What the heck are lenses, anyway? A lens is a getter and a setter mashed into one function. And they are very composable. Any time you need to access or modify parts of a data structure but you think record syntax is unwieldy, lenses are there for you.
So, the first thing you need to do – enable TH and import Control.Lens.
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
The modification you need to do to your data type is adding names for all the fields, like so:
data SpecialDslExpression = MyExpression { _exprType :: String
, _exprParams :: [Int]
, _exprCost :: Double
, _exprComment :: [String]
, _exprLog :: Maybe Bool
} deriving Show
The underscores in the beginning of the field names are important, for the following step. Because now we want to generate lenses for the fields. We can ask GHC to do that for us with Template Haskell.
$(makeLenses ''SpecialDslExpression)
Then the final thing that needs to be done is constructing an "empty" instance. Beware that nobody will check statically that you actually fill all the required fields, so you should preferably add an error to those fields so you at least get a run-time error. Something like this:
emptyExpression = MyExpression (error "Type field is required!") [] 0.0 [] Nothing
Now you are ready to roll! You cannot use an emptyExpression, and it will fail at run-time:
> emptyExpression
MyExpression {_exprType = "*** Exception: Type field is required!
But! As long as you populate the type field, you will be golden:
> emptyExpression & exprType .~ "Test expression"
MyExpression { _exprType = "Test expression"
, _exprParams = []
, _exprCost = 0.0
, _exprComment = []
, _exprLog = Nothing
}
You can also fill several fields at once, if you want to.
> emptyExpression & exprType .~ "Test expression"
| & exprLog .~ Just False
| & exprComment .~ ["Test comment"]
MyExpression { _exprType = "Test expression"
, _exprParams = []
, _exprCost = 0.0
, _exprComment = ["Test comment"]
, _exprLog = Just False
}
You can also use lenses to apply a function to a field, or look inside a field of a field, or modify any other existing expression and so on. I definitely recommend taking a look at what you can do!
Alright I'll actually expand upon my comment. Firstly, define your data type as a record (and throw in a few type synonyms).
data Example = E {
one :: Int,
two :: String,
three :: Bool,
four :: Double
}
next you create a default instance
defaultExample = Example 1 "foo" False 1.4
and then when a user wants to tweak a field in the default to make their own data they can do this:
myData = defaultExample{four=2.8}
Finally, when they want to pattern match just one item, they can use
foo MyData{four=a} = a
I am learning Haskell from learnyouahaskell.com. I am having trouble understanding type constructors and data constructors. For example, I don't really understand the difference between this:
data Car = Car { company :: String
, model :: String
, year :: Int
} deriving (Show)
and this:
data Car a b c = Car { company :: a
, model :: b
, year :: c
} deriving (Show)
I understand that the first is simply using one constructor (Car) to built data of type Car. I don't really understand the second one.
Also, how do data types defined like this:
data Color = Blue | Green | Red
fit into all of this?
From what I understand, the third example (Color) is a type which can be in three states: Blue, Green or Red. But that conflicts with how I understand the first two examples: is it that the type Car can only be in one state, Car, which can take various parameters to build? If so, how does the second example fit in?
Essentially, I am looking for an explanation that unifies the above three code examples/constructs.
In a data declaration, a type constructor is the thing on the left hand side of the equals sign. The data constructor(s) are the things on the right hand side of the equals sign. You use type constructors where a type is expected, and you use data constructors where a value is expected.
Data constructors
To make things simple, we can start with an example of a type that represents a colour.
data Colour = Red | Green | Blue
Here, we have three data constructors. Colour is a type, and Green is a constructor that contains a value of type Colour. Similarly, Red and Blue are both constructors that construct values of type Colour. We could imagine spicing it up though!
data Colour = RGB Int Int Int
We still have just the type Colour, but RGB is not a value – it's a function taking three Ints and returning a value! RGB has the type
RGB :: Int -> Int -> Int -> Colour
RGB is a data constructor that is a function taking some values as its arguments, and then uses those to construct a new value. If you have done any object-oriented programming, you should recognise this. In OOP, constructors also take some values as arguments and return a new value!
In this case, if we apply RGB to three values, we get a colour value!
Prelude> RGB 12 92 27
#0c5c1b
We have constructed a value of type Colour by applying the data constructor. A data constructor either contains a value like a variable would, or takes other values as its argument and creates a new value. If you have done previous programming, this concept shouldn't be very strange to you.
Intermission
If you'd want to construct a binary tree to store Strings, you could imagine doing something like
data SBTree = Leaf String
| Branch String SBTree SBTree
What we see here is a type SBTree that contains two data constructors. In other words, there are two functions (namely Leaf and Branch) that will construct values of the SBTree type. If you're not familiar with how binary trees work, just hang in there. You don't actually need to know how binary trees work, only that this one stores Strings in some way.
We also see that both data constructors take a String argument – this is the String they are going to store in the tree.
But! What if we also wanted to be able to store Bool, we'd have to create a new binary tree. It could look something like this:
data BBTree = Leaf Bool
| Branch Bool BBTree BBTree
Type constructors
Both SBTree and BBTree are type constructors. But there's a glaring problem. Do you see how similar they are? That's a sign that you really want a parameter somewhere.
So we can do this:
data BTree a = Leaf a
| Branch a (BTree a) (BTree a)
Now we introduce a type variable a as a parameter to the type constructor. In this declaration, BTree has become a function. It takes a type as its argument and it returns a new type.
It is important here to consider the difference between a concrete type (examples include Int, [Char] and Maybe Bool) which is a type that can be assigned to a value in your program, and a type constructor function which you need to feed a type to be able to be assigned to a value. A value can never be of type "list", because it needs to be a "list of something". In the same spirit, a value can never be of type "binary tree", because it needs to be a "binary tree storing something".
If we pass in, say, Bool as an argument to BTree, it returns the type BTree Bool, which is a binary tree that stores Bools. Replace every occurrence of the type variable a with the type Bool, and you can see for yourself how it's true.
If you want to, you can view BTree as a function with the kind
BTree :: * -> *
Kinds are somewhat like types – the * indicates a concrete type, so we say BTree is from a concrete type to a concrete type.
Wrapping up
Step back here a moment and take note of the similarities.
A data constructor is a "function" that takes 0 or more values and gives you back a new value.
A type constructor is a "function" that takes 0 or more types and gives you back a new type.
Data constructors with parameters are cool if we want slight variations in our values – we put those variations in parameters and let the guy who creates the value decide what arguments they are going to put in. In the same sense, type constructors with parameters are cool if we want slight variations in our types! We put those variations as parameters and let the guy who creates the type decide what arguments they are going to put in.
A case study
As the home stretch here, we can consider the Maybe a type. Its definition is
data Maybe a = Nothing
| Just a
Here, Maybe is a type constructor that returns a concrete type. Just is a data constructor that returns a value. Nothing is a data constructor that contains a value. If we look at the type of Just, we see that
Just :: a -> Maybe a
In other words, Just takes a value of type a and returns a value of type Maybe a. If we look at the kind of Maybe, we see that
Maybe :: * -> *
In other words, Maybe takes a concrete type and returns a concrete type.
Once again! The difference between a concrete type and a type constructor function. You cannot create a list of Maybes - if you try to execute
[] :: [Maybe]
you'll get an error. You can however create a list of Maybe Int, or Maybe a. That's because Maybe is a type constructor function, but a list needs to contain values of a concrete type. Maybe Int and Maybe a are concrete types (or if you want, calls to type constructor functions that return concrete types.)
Haskell has algebraic data types, which very few other languages have. This is perhaps what's confusing you.
In other languages, you can usually make a "record", "struct" or similar, which has a bunch of named fields that hold various different types of data. You can also sometimes make an "enumeration", which has a (small) set of fixed possible values (e.g., your Red, Green and Blue).
In Haskell, you can combine both of these at the same time. Weird, but true!
Why is it called "algebraic"? Well, the nerds talk about "sum types" and "product types". For example:
data Eg1 = One Int | Two String
An Eg1 value is basically either an integer or a string. So the set of all possible Eg1 values is the "sum" of the set of all possible integer values and all possible string values. Thus, nerds refer to Eg1 as a "sum type". On the other hand:
data Eg2 = Pair Int String
Every Eg2 value consists of both an integer and a string. So the set of all possible Eg2 values is the Cartesian product of the set of all integers and the set of all strings. The two sets are "multiplied" together, so this is a "product type".
Haskell's algebraic types are sum types of product types. You give a constructor multiple fields to make a product type, and you have multiple constructors to make a sum (of products).
As an example of why that might be useful, suppose you have something that outputs data as either XML or JSON, and it takes a configuration record - but obviously, the configuration settings for XML and for JSON are totally different. So you might do something like this:
data Config = XML_Config {...} | JSON_Config {...}
(With some suitable fields in there, obviously.) You can't do stuff like this in normal programming languages, which is why most people aren't used to it.
Start with the simplest case:
data Color = Blue | Green | Red
This defines a "type constructor" Color which takes no arguments - and it has three "data constructors", Blue, Green and Red. None of the data constructors takes any arguments. This means that there are three of type Color: Blue, Green and Red.
A data constructor is used when you need to create a value of some sort. Like:
myFavoriteColor :: Color
myFavoriteColor = Green
creates a value myFavoriteColor using the Green data constructor - and myFavoriteColor will be of type Color since that's the type of values produced by the data constructor.
A type constructor is used when you need to create a type of some sort. This is usually the case when writing signatures:
isFavoriteColor :: Color -> Bool
In this case, you are calling the Color type constructor (which takes no arguments).
Still with me?
Now, imagine you not only wanted to create red/green/blue values but you also wanted to specify an "intensity". Like, a value between 0 and 256. You could do that by adding an argument to each of the data constructors, so you end up with:
data Color = Blue Int | Green Int | Red Int
Now, each of the three data constructors takes an argument of type Int. The type constructor (Color) still doesn't take any arguments. So, my favorite color being a darkish green, I could write
myFavoriteColor :: Color
myFavoriteColor = Green 50
And again, it calls the Green data constructor and I get a value of type Color.
Imagine if you don't want to dictate how people express the intensity of a color. Some might want a numeric value like we just did. Others may be fine with just a boolean indicating "bright" or "not so bright". The solution to this is to not hardcode Int in the data constructors but rather use a type variable:
data Color a = Blue a | Green a | Red a
Now, our type constructor takes one argument (another type which we just call a!) and all of the data constructors will take one argument (a value!) of that type a. So you could have
myFavoriteColor :: Color Bool
myFavoriteColor = Green False
or
myFavoriteColor :: Color Int
myFavoriteColor = Green 50
Notice how we call the Color type constructor with an argument (another type) to get the "effective" type which will be returned by the data constructors. This touches the concept of kinds which you may want to read about over a cup of coffee or two.
Now we figured out what data constructors and type constructors are, and how data constructors can take other values as arguments and type constructors can take other types as arguments. HTH.
As others pointed out, polymorphism isn't that terrible useful here. Let's look at another example you're probably already familiar with:
Maybe a = Just a | Nothing
This type has two data constructors. Nothing is somewhat boring, it doesn't contain any useful data. On the other hand Just contains a value of a - whatever type a may have. Let's write a function which uses this type, e.g. getting the head of an Int list, if there is any (I hope you agree this is more useful than throwing an error):
maybeHead :: [Int] -> Maybe Int
maybeHead [] = Nothing
maybeHead (x:_) = Just x
> maybeHead [1,2,3] -- Just 1
> maybeHead [] -- None
So in this case a is an Int, but it would work as well for any other type. In fact you can make our function work for every type of list (even without changing the implementation):
maybeHead :: [t] -> Maybe t
maybeHead [] = Nothing
maybeHead (x:_) = Just x
On the other hand you can write functions which accept only a certain type of Maybe, e.g.
doubleMaybe :: Maybe Int -> Maybe Int
doubleMaybe Just x = Just (2*x)
doubleMaybe Nothing= Nothing
So long story short, with polymorphism you give your own type the flexibility to work with values of different other types.
In your example, you may decide at some point that String isn't sufficient to identify the company, but it needs to have its own type Company (which holds additional data like country, address, back accounts etc). Your first implementation of Car would need to change to use Company instead of String for its first value. Your second implementation is just fine, you use it as Car Company String Int and it would work as before (of course functions accessing company data need to be changed).
The second one has the notion of "polymorphism" in it.
The a b c can be of any type. For example, a can be a [String], b can be [Int]
and c can be [Char].
While the first one's type is fixed: company is a String, model is a String and year is Int.
The Car example might not show the significance of using polymorphism. But imagine your data is of the list type. A list can contain String, Char, Int ... In those situations, you will need the second way of defining your data.
As to the third way I don't think it needs to fit into the previous type. It's just one other way of defining data in Haskell.
This is my humble opinion as a beginner myself.
Btw: Make sure that you train your brain well and feel comfortable to this. It is the key to understand Monad later.
It's about types: In the first case, your set the types String (for company and model) and Int for year. In the second case, your are more generic. a, b, and c may be the very same types as in the first example, or something completely different. E.g., it may be useful to give the year as string instead of integer. And if you want, you may even use your Color type.
I'm aware of partial updates for records like :
data A a b = A { a :: a, b :: b }
x = A { a=1,b=2 :: Int }
y = x { b = toRational (a x) + 4.5 }
Are there any tricks for doing only partial initialization, creating a subrecord type, or doing (de)serialization on subrecord?
In particular, I found that the first of these lines works but the second does not :
read "A {a=1,b=()}" :: A Int ()
read "A {a=1}" :: A Int ()
You could always massage such input using a regular expression, but I'm curious what Haskell-like options exist.
Partial initialisation works fine: A {a=1} is a valid expression of type A Int (); the Read instance just doesn't bother parsing anything the Show instance doesn't output. The b field is initialised to error "...", where the string contains file/line information to help with debugging.
You generally shouldn't be using Read for any real-world parsing situations; it's there for toy programs that have really simple serialisation needs and debugging.
I'm not sure what you mean by "subrecord", but if you want serialisation/deserialisation that can cope with "upgrades" to the record format to contain more information while still being able to process old (now "partial") serialisations, then the safecopy library does just that.
You cannot leave some value in Haskell "uninitialized" (it would not be possible to "initialize" it later anyway, since Haskell is pure). If you want to provide "default" values for the fields, then you can make some "default" value for your record type, and then do a partial update on that default value, setting only the fields you care about. I don't know how you would implement read for this in a simple way, however.