haskellnewtype

Understanding 'newtype' keyword


For a uni assignment, we have been given a line of Haskell code which shows:

newtype TC a = TC ([Id] -> Either TypeError ([Id], a))

Firstly, TypeError is something which needs to be implemented by us for the assignment so I can't post the data declaration here, but my question is this. How do I read the code above? What is the a right after the newtype TC? I also don't understand how TC is being reused to the right of the equals sign.

I think a here is a type variable since newtype works similarly to data. I don't know how knowing this will help my understanding.


Solution

  • What is the a right after the newtype TC?

    The a in the newtype declaration

    newtype TC a = ...
    

    expresses much the same as the x in a function declaration

    f x = ...
    

    a is a type parameter. So you'll be able to use TC as, for example, TC Int or TC Bool, similar to how you're able to use f like f 1 or f "bla bla" (depending on its type).

    The case TC Int is equivalent to the following alternative:

    newtype TCInt = TCInt ([Id] -> Either TypeError ([Id], Int))
    

    I also don't understand how TC is being reused to the right of the equals sign.

    That is a bit of a confusing quirk in Haskell. Actually TC is not reused, rather you're declaring two separate entities which are both called TC. You could also call them differently:

    newtype TC_T a = TC_V ([Id] -> Either TypeError ([Id], a))
    

    So for example, you might write these:

    tci :: TC_T Int
    tci = TC_V (\ids -> Right (ids, 37))
    
    tcb :: TC_T Bool
    tcb = TC_V (\ids -> Right (reverse ids, False))
    

    With your original version it looks like this:

    tci :: TC Int
    tci = TC (\ids -> Right (ids, 37))
    
    tcb :: TC Bool
    tcb = TC (\ids -> Right (reverse ids, False))
    

    ...but it's still two separate things both called TV here. Most newtypes in Haskell call the type- and value constructors the same, but often only the type constructor is exported from a module and the value constructor left as an implementation detail.

    Most of this is the same for data as for newtype. You could as well have

    data TC a = TC ([Id] -> Either TypeError ([Id], a))
    

    ... the only difference to the newtype version is a subtle indirection: if it's data, then the compiler inserts an indirection which allows a bit more lazyness, but in this case there's hardly a point to doing that.

    In practice, you generally use data only when you need multiple constructors and/or a constructor with multiple fields, which newtype does not support.