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.
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