Search code examples
haskellfunctional-programmingnewtype

datatype already declared - newtype in haskell


I am struggling with understanding the newtype declaration. I am experimenting with the exercises in LYAH: http://learnyouahaskell.com/functors-applicative-functors-and-monoids#the-newtype-keyword

And wanted to use the newtype for the custom type instantiation Tofu Frank as seen below following LYAH's recipe: http://learnyouahaskell.com/making-our-own-types-and-typeclasses, but adding the newtype-part myself.

class Tofu t where
   tofu :: j a -> t a j

instance Tofu Frank where
   tofu x = Frank x

data Frank a b = Frank {frankField :: b a} deriving (Show)

newtype Frank a b = Frank{frankField :: (b a)} deriving Show

OUTPUT:

Multiple declarations of `Frank'
    Declared at: tryouts.hs:563:1   
                 tryouts.hs:572:1 

In the description of newtype in LYAH:http://learnyouahaskell.com/functors-applicative-functors-and-monoids#the-newtype-keyword it is stated that:

Instead of the data keyword, the newtype keyword is used. Now why is that? Well for one, newtype is faster. If you use the data keyword to wrap a type, there's some overhead to all that wrapping and unwrapping when your program is running. But if you use newtype, Haskell knows that you're just using it to wrap an existing type into a new type (hence the name), because you want it to be the same internally but have a different type. With that in mind, Haskell can get rid of the wrapping and unwrapping once it resolves which value is of what type.

But do I misunderstand newtype here and will the newtype not overwrite the existing data type, as long as the former has a only one field in the constructor as is the case in my example? It does seem to work if I just leave out the data-declaration part.


Solution

  • Note that the description in LYAH says that newtype is used instead of data. That's the key. Both data and newtype declare a new datatype (Frank, in your case), and you're supposed to only use only one or the other, not both for the same new datatype. So, you either write:

    data Frank a b = Frank {frankField :: b a} deriving (Show)
    

    if you want Frank to be a regular data type, or else you write:

    newtype Frank a b = Frank {frankField :: b a} deriving (Show)
    

    if you want Frank to be a newtype instead.

    In general, Haskell doesn't support "overwriting" (or redefining or redeclaraing) datatypes, whether you use data or newtype or some mixture. Other languages might be more flexible in this regard. For example, Python will let you define a class Frank, reference it in some code, and the redefine class Frank and use the new class in some more code:

    class Frank:
        def __init__(self):
            self.name = "Frank Burns"
    
    frank1 = Frank()
    
    class Frank:
        def __init__(self):
            self.name = "Frankenstein"
    
    frank2 = Frank()
    
    print(frank1.name)   # Frank Burns
    print(frank2.name)   # Frankenstein
    

    However, Haskell requires that a new datatype is defined in exactly one place. It's similar to the way Haskell also requires a (global) function to be defined in only one place. Multiple definition lines that are consecutive are considered to be part of a single definition:

    -- this defines only one "frank"
    frank 0 = "foo"
    frank 1 = "bar"
    

    and multiple definitions that are non-consecutive are rejected:

    frank n = n*2
    burns c = (c, c)
    frank x = x-5     -- rejected