Saturday 30 April 2016

Haskell: newtype keyword


Just like how you define new data types using data keyword, you can also define new data types using newtype keyword, only restriction is the type has exactly one constructor with exactly one field inside it.

For example,
Sample.hs
type FirstName = String
type LastName = String

type NamePair = (FirstName, LastName)

newtype Name = Name NamePair
 
Observe above snippet, the type Name has exactly one constructor and one fields NamePair inside it.


A newtype declaration may use field-naming syntax, though of course there may only be one field. Thus:

newtype Name = Name {myName :: NamePair}

Why newtype?
Suppose you want to define a new type, where the representation is same as existing one, but has some variation, then you can go for newtype.

For example, you can define PositveInteger, NegativeIntegers like below.

newtype PositiveInteger = PositiveInteger Integer
newtype NegativeInteger = NegativeInteger Integer

Above definitions creates completely two new types:
PositiveInteger, to represent positive numbers.
NegativeInteger, to represent negative numbers.

Sample.hs
newtype PositiveInteger = PositiveInteger Integer deriving Show

toInteger :: PositiveInteger -> Integer
toInteger (PositiveInteger i) = i

toPositiveInteger :: Integer -> PositiveInteger
toPositiveInteger x
    | (x < 0) = error "Not applicable to negative numbers"
    | otherwise = PositiveInteger x

Prelude> :load Sample.hs
[1 of 1] Compiling Main             ( Sample.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> toInteger (PositiveInteger 10)

<interactive>:4:1:
    Ambiguous occurrence toInteger
    It could refer to either Main.toInteger, defined at Sample.hs:4:1
                          or Prelude.toInteger,
                             imported from Prelude at Sample.hs:1:1
                             (and originally defined in GHC.Real)

Oops, got an error. Observe above error message, it is telling, toInteger function is defined in two places, one is in Sample.hs and other is in Prelude.hs, So we should call like Main.toInteger.
*Main> Main.toInteger (PositiveInteger 10)
10
*Main> toPositiveInteger 10
PositiveInteger 10
*Main> 
*Main> toPositiveInteger (-10)
PositiveInteger *** Exception: Not applicable to negative numbers


Can I perform Arithmetic operations on PositiveInteger?
Let me try some experiment.
*Main> PositiveInteger 10 + PositiveInteger 20

<interactive>:15:20:
    No instance for (Num PositiveInteger) arising from a use of +
    In the expression: PositiveInteger 10 + PositiveInteger 20
    In an equation for it:
        it = PositiveInteger 10 + PositiveInteger 20


Oops, I got an error again. Let me look into it.  It is clearly telling “No instance for (Num PositiveInteger)”. To solve this, I need to give an instance definition for PositiveInteger to the Num class.

Sample.hs
newtype PositiveInteger = PositiveInteger Integer deriving Show

fromPositiveInteger :: PositiveInteger -> Integer
fromPositiveInteger (PositiveInteger i) = i

toPositiveInteger :: Integer -> PositiveInteger
toPositiveInteger x
    | (x < 0) = error "Not applicable to negative numbers"
    | otherwise = PositiveInteger x

instance Num PositiveInteger where
    fromInteger         = toPositiveInteger
    x + y               = toPositiveInteger (fromPositiveInteger x + fromPositiveInteger y)
    x - y               = let r = fromPositiveInteger x - fromPositiveInteger y in
                            if r < 0 then error "Unnatural subtraction"
                                     else toPositiveInteger r
    x * y               = toPositiveInteger (fromPositiveInteger x * fromPositiveInteger y)

*Main> PositiveInteger 10 + PositiveInteger 20
PositiveInteger 30
*Main> 
*Main> PositiveInteger 100 * PositiveInteger 19
PositiveInteger 1900


What is the difference between newtype and data?
a.   First of all newtype has exactly one constructor and one field inside it, where as the data type defined using data keyword can have any number of value constructors with any number of fields.                    

b.   Let me try to show an example.

Sample.hs         
data MyData = MyData Bool

getVal (MyData _) = "Hello Haskell"


Try to call getVal by passing undefined as argument.
Prelude> :load Sample.hs
[1 of 1] Compiling Main             ( Sample.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> getVal undefined
"*** Exception: Prelude.undefined


Oops, why I got this exception. It is because, A type defined using data keyword, can have multiple value constructors, so Haskell has to evaluate the expression undefined (‘getVal undefined’), when haskell tries to evaluate undefined, error is thrown.

Now update Sample.hs like below.

Sample.hs
newtype MyData = MyData Bool

getVal (MyData _) = "Hello Haskell"

Prelude> :load Sample.hs
[1 of 1] Compiling Main             ( Sample.hs, interpreted )
Ok, modules loaded: Main.
*Main> 
*Main> getVal undefined
"Hello Haskell"


Strange isn’t it? this time we don’t get any exception. Haskell knows that types made with the newtype keyword can only have one constructor, it doesn't have to evaluate the value passed to the function to make sure that it conforms to the (MyData _) pattern because newtype types can only have one possible value constructor and one field. So Haskell don’t need to evaluate undefined.

Previous                                                 Next                                                 Home

No comments:

Post a Comment