Difference between revisions of "Type"
(Determining the type of an expression) 
m (Reverted edits by Tomjaguarpaw (talk) to last revision by Spookylukey) 

(11 intermediate revisions by 9 users not shown)  
Line 5:  Line 5:  
==Data declarations== 
==Data declarations== 

−  One introduces, or declares, 
+  One introduces, or declares, a type in Haskell via the <code>data</code> statement. In general a data declaration looks like: 
−  data [context =>] type tv1 ... tvi = con1 c1t1 
+  data [context =>] type tv1 ... tvi = con1 c1t1 c1t2... c1tn  
...  conm cmt1 ... cmtq 
...  conm cmt1 ... cmtq 

[deriving] 
[deriving] 

Line 13:  Line 13:  
which probably explains nothing if you don't already know Haskell! 
which probably explains nothing if you don't already know Haskell! 

−  The essence of the above statement is that you use the keyword <hask>data</hask>, 

⚫  The essence of the above statement is that you use the keyword <hask>data</hask>, supply an optional context, give the type name and a variable number of [[type variable]]s. This is then followed by a variable number of [[constructor]]s, each of which has a list of [[type variable]]s or [[type constant]]s. At the end, there is an optional <code>deriving</code>. 

−  supply an optional context, give the type name and a variable number of 

⚫  
−  There are a number of other subtleties associated with this, such as requiring 
+  There are a number of other subtleties associated with this, such as requiring parameters to the data constructors to be [[eager]], what [[Type classclass]]es are allowed in the [[deriving]], use of [[field]] names in the constructors 
−  parameters to the data constructors to be [[eager]], what [[class]]es are 

−  allowed in the [[deriving]], use of [[field]] names in the constructors 

and what the [[context]] actually does. Please refer to the specific articles for more on each of those. 
and what the [[context]] actually does. Please refer to the specific articles for more on each of those. 

Line 24:  Line 22:  
data Maybe a = Just a  Nothing 
data Maybe a = Just a  Nothing 

</haskell> 
</haskell> 

−  What this means is that the type '''Maybe''' has one type variable, represented by the ''a'' and two [[constructor]]s '''Just''' and '''Nothing'''. (Note that Haskell requires type names and constructor names to begin with an uppercase letter). The '''Just''' constructor takes one parameter, ''a''. 
+  What this means is that the type '''Maybe''' has one type variable, represented by the ''a'' and two [[constructor]]s '''Just''' and '''Nothing'''. (Note that Haskell requires type names and constructor names to begin with an uppercase letter). The '''Just''' constructor takes one parameter, of type ''a''. 
As another example, consider binary [[Tree]]s. They could be represented by: 
As another example, consider binary [[Tree]]s. They could be represented by: 

Line 31:  Line 29:  
</haskell> 
</haskell> 

Here, one of the constructors, '''Branch''' of '''Tree''' takes two trees as 
Here, one of the constructors, '''Branch''' of '''Tree''' takes two trees as 

−  parameters to the constructor, while '''Leaf''' takes 
+  parameters to the constructor, while '''Leaf''' takes a value of type ''a''. This type of recursion is a very common [[:Category:Idioms pattern]] in Haskell. 
==Type and newtype== 
==Type and newtype== 

−  The other two ways one may introduce types to Haskell programs are via the 
+  The other two ways one may introduce types to Haskell programs are via the <hask>type</hask> and <hask>newtype</hask> statements. 
−  <hask>type</hask> and <hask>newtype</hask> statements. 

−  <hask>type</hask> introduces a synonym for a type and uses the same data 
+  <hask>type</hask> introduces a synonym for a type and uses the same data constructors. <hask>newtype</hask> introduces a renaming of a type and requires you to provide new constructors. 
−  +  
−  +  When using a <hask>type</hask> declaration, the type synonym and its base type are interchangeble almost everywhere (There are some restrictions when dealing with [[instance]] declarations). For example, if you had the declaration: 

−  When using a <hask>type</hask> declaration, the type synonym and its base type 

−  are interchangeble almost everywhere (There are some restrictions when dealing with [[instance]] declarations). For example, if you had the declaration: 

<haskell> 
<haskell> 

type Name = String 
type Name = String 

</haskell> 
</haskell> 

−  then any [[function]] you had declared that had <hask>String</hask> in its 

+  
−  signature could be used on any element of type <code>Name</code> 
+  then any [[function]] you had declared that had <hask>String</hask> in its signature could be used on any element of type <code>Name</code> 
However, if one had the declaration: 
However, if one had the declaration: 

+  
<haskell> 
<haskell> 

newtype FirstName = FirstName String 
newtype FirstName = FirstName String 

</haskell> 
</haskell> 

−  this would no longer be the case. Functions would have to be declared that 

+  
−  actually were defined on '''FirstName'''. Often, one creates a deconstructor 
+  this would no longer be the case. Functions would have to be declared that actually were defined on '''FirstName'''. Often, one creates a deconstructor at the same time which helps alleviate this requirement. e.g.: 
−  at the same time which helps alleviate this requirement. e.g.: 

<haskell> 
<haskell> 

unFirstName :: FirstName > String 
unFirstName :: FirstName > String 

unFirstName (FirstName s) = s 
unFirstName (FirstName s) = s 

</haskell> 
</haskell> 

−  This is often done by the use of [[field]] 
+  This is often done by the use of [[fieldfield labels]] in the <code>newtype</code>. (Note that many consider the Haskell field implementation suboptimal, while others use it extensively. See [[Programming guidelines#RecordsProgramming guidelines]] and [[Future of Haskell]]) 
−  that many consider the Haskell field implementation suboptimal, while 

−  others use it extensively. See [[Programming guidelines]] and [[Future of Haskell]]) 

==A simple example== 
==A simple example== 

Line 75:  Line 72:  
deriving (Read, Show, Enum, Eq, Ord) 
deriving (Read, Show, Enum, Eq, Ord) 

</haskell> 
</haskell> 

−  Each of these uses a [[deriving]] clause to allow us to convert them from / to [[String]] and Int, test them for equality and ordering. With types like this, 
+  Each of these uses a [[deriving]] clause to allow us to convert them from / to [[String]] and Int, test them for equality and ordering. With types like this, where there are no [[type variable]]s, equality is based upon which constructor is used and order is determined by the order you wrote them, e.g. <code>Three</code> is less than <code>Queen</code>. 
−  where there are no [[type variable]]s, equality is based upon which constructor is used and order by the order you wrote them. e.g. <code>Three</code> is less than <code>Queen</code>. 

Now we define an actual <code>Card</code> 
Now we define an actual <code>Card</code> 

<haskell> 
<haskell> 

−  data Card = Card {value::CardValue, 
+  data Card = Card {value :: CardValue, 
−  suit::Suit} 
+  suit :: Suit} 
deriving (Read, Show, Eq) 
deriving (Read, Show, Eq) 

</haskell> 
</haskell> 

−  In this definition, we use [[field]]s, which give us ready made functions to 
+  In this definition, we use [[field]]s, which give us ready made functions to access the two parts of a <code>Card</code>. Again, [[type variables]] were not used, but the data [[constructor]] requires its two parameters to be of specific types, <code>CardValue</code> and <code>Suit</code>. 
−  access the two parts of a <code>Card</code>. Again, [[type variables]] were not 

−  used, but the data [[constructor]] requires its two parameters to be of 

−  specific types, <code>CardValue</code> and <code>Suit</code>. 

−  The deriving clause here only specifies three of our desired [[ 
+  The deriving clause here only specifies three of our desired [[Type classclasses]], we supply [[instance]] declarations for [[Ord]] and [[Enum]]: 
<haskell> 
<haskell> 

instance Ord Card where 
instance Ord Card where 

−  compare c1 c2 
+  compare c1 c2 = compare (value c1, suit c1) (value c2, suit c2) 
−   otherwise = compare (value c1) (value c2) 

instance Enum Card where 
instance Enum Card where 

−  toEnum n = 
+  toEnum n = let (v,s) = n`divMod`4 in Card (toEnum v) (toEnum s) 
−  fromEnum c = 
+  fromEnum c = fromEnum (value c) * 4 + fromEnum (suit c) 
</haskell> 
</haskell> 

Finally, we alias the type <code>Deck</code> to a list of <code>Card</code>s 
Finally, we alias the type <code>Deck</code> to a list of <code>Card</code>s 

Line 108:  Line 105:  
==See also== 
==See also== 

−  Read the 
+  Read the articles about data [[constructor]]s and [[Type classclass]]es. As well the [http://haskell.org/definition/haskell98report.pdf Haskell 98 report] and your chosen implementation (e.g. [[GHC/Documentation]]) have the latest words. 
−  [http://haskell.org/definition/haskell98report.pdf Haskell 98 report] and 

−  your chosen implementation (e.g. [[GHC/Documentation]]) have the latest words. 

*[[Determining the type of an expression]]  Let the Haskell compiler compute the type of expression 
*[[Determining the type of an expression]]  Let the Haskell compiler compute the type of expression 

Line 120:  Line 117:  
*[[Reified type]], [[Nontrivial type synonyms]], [[Abstract data type]], [[Concrete data type]], [[Algebraic data type]]. 
*[[Reified type]], [[Nontrivial type synonyms]], [[Abstract data type]], [[Concrete data type]], [[Algebraic data type]]. 

*[[Research_papers/Type_systems]] allow the curious to delve deeper. 
*[[Research_papers/Type_systems]] allow the curious to delve deeper. 

+  
+  Languages: [[型ja]] 
Latest revision as of 15:20, 6 February 2021
In Haskell, types are how you describe the data your program will work with.
Data declarations
One introduces, or declares, a type in Haskell via the data
statement. In general a data declaration looks like:
data [context =>] type tv1 ... tvi = con1 c1t1 c1t2... c1tn  ...  conm cmt1 ... cmtq [deriving]
which probably explains nothing if you don't already know Haskell!
The essence of the above statement is that you use the keyword data
, supply an optional context, give the type name and a variable number of type variables. This is then followed by a variable number of constructors, each of which has a list of type variables or type constants. At the end, there is an optional deriving
.
There are a number of other subtleties associated with this, such as requiring parameters to the data constructors to be eager, what classes are allowed in the deriving, use of field names in the constructors and what the context actually does. Please refer to the specific articles for more on each of those.
Let's look at some examples. The Haskell standard data type Maybe is typically declared as:
data Maybe a = Just a  Nothing
What this means is that the type Maybe has one type variable, represented by the a and two constructors Just and Nothing. (Note that Haskell requires type names and constructor names to begin with an uppercase letter). The Just constructor takes one parameter, of type a.
As another example, consider binary Trees. They could be represented by:
data Tree a = Branch (Tree a) (Tree a)  Leaf a
Here, one of the constructors, Branch of Tree takes two trees as parameters to the constructor, while Leaf takes a value of type a. This type of recursion is a very common pattern in Haskell.
Type and newtype
The other two ways one may introduce types to Haskell programs are via the type
and newtype
statements.
type
introduces a synonym for a type and uses the same data constructors. newtype
introduces a renaming of a type and requires you to provide new constructors.
When using a type
declaration, the type synonym and its base type are interchangeble almost everywhere (There are some restrictions when dealing with instance declarations). For example, if you had the declaration:
type Name = String
then any function you had declared that had String
in its signature could be used on any element of type Name
However, if one had the declaration:
newtype FirstName = FirstName String
this would no longer be the case. Functions would have to be declared that actually were defined on FirstName. Often, one creates a deconstructor at the same time which helps alleviate this requirement. e.g.:
unFirstName :: FirstName > String
unFirstName (FirstName s) = s
This is often done by the use of field labels in the newtype
. (Note that many consider the Haskell field implementation suboptimal, while others use it extensively. See Programming guidelines and Future of Haskell)
A simple example
Suppose you want to create a program to play bridge. You need something to represent cards. Here is one way to do that.
First, create data types for the suit and card number.
data Suit = Club  Diamond  Heart  Spade
deriving (Read, Show, Enum, Eq, Ord)
data CardValue = Two  Three  Four
 Five  Six  Seven  Eight  Nine  Ten
 Jack  Queen  King  Ace
deriving (Read, Show, Enum, Eq, Ord)
Each of these uses a deriving clause to allow us to convert them from / to String and Int, test them for equality and ordering. With types like this, where there are no type variables, equality is based upon which constructor is used and order is determined by the order you wrote them, e.g. Three
is less than Queen
.
Now we define an actual Card
data Card = Card {value :: CardValue,
suit :: Suit}
deriving (Read, Show, Eq)
In this definition, we use fields, which give us ready made functions to access the two parts of a Card
. Again, type variables were not used, but the data constructor requires its two parameters to be of specific types, CardValue
and Suit
.
The deriving clause here only specifies three of our desired classes, we supply instance declarations for Ord and Enum:
instance Ord Card where
compare c1 c2 = compare (value c1, suit c1) (value c2, suit c2)
instance Enum Card where
toEnum n = let (v,s) = n`divMod`4 in Card (toEnum v) (toEnum s)
fromEnum c = fromEnum (value c) * 4 + fromEnum (suit c)
Finally, we alias the type Deck
to a list of Card
s
and populate the deck with a list comprehension
type Deck = [Card]
deck::Deck
deck = [Card val su  val < [Two .. Ace], su < [Club .. Spade]]
Please add
Further illustrative examples would be most appreciated.
See also
Read the articles about data constructors and classes. As well the Haskell 98 report and your chosen implementation (e.g. GHC/Documentation) have the latest words.
 Determining the type of an expression  Let the Haskell compiler compute the type of expression
 Language extensions  many language extensions are to do with changes to the type system.
 Smart constructors shows some interesting examples including a nontrivial usage of
newtype
.  Unboxed type shows ways to have values closer to the bare metal :).
 Phantom type discusses types without constructors.
 Type witness gives an example of GADTs, a GHC extension.
 Existential type shows how to implement a common OO programming paradigm.
 Type arithmetic implements the Peano numbers.
 Reified type, Nontrivial type synonyms, Abstract data type, Concrete data type, Algebraic data type.
 Research_papers/Type_systems allow the curious to delve deeper.
Languages: ja