Search code examples
purescript

PureScript - All types appearing in instance declarations must be of the form T a_1 .. a_n


Consider the following block of PureScript code, which defines a showCustomer type class.

module Main where

import Prelude
import Effect 
import Effect.Console 

class Show a where
  show :: a -> String

type Customer = {
  name :: String
}

customer :: Customer
customer = {
  name : "Daniel Stern"
}

instance showCustomer :: Show Customer where
  show a = "A customer"

However this code produces the following error,

Type class instance head is invalid due to use of type
  
    ( name :: String
    )
  
All types appearing in instance declarations must be of the form T a_1 .. a_n, where each type a_i is of the same form, unless the type is fully determined by other type class arguments via functional dependencies.

Questions:

  1. How does one create a showCustomer method which logs " a customer" if the value passed to show is of type Customer?

  2. What does the error mean?


Solution

  • The error means literally what it says: an instance head must look like T x y z, whereas yours looks like { name :: String }.

    And the root reason is that PureScript just doesn't support instances for records. Sorry, tough luck. No way to do this.


    If you want your types to have instances, they must be either newtype or data, e.g.:

    newtype Customer = Customer { name :: String }
    

    But that comes with its own drawbacks: Customer is no longer a record, which means you can no longer access its fields via dot. You have to unwrap the type first to get the record wrapped inside, and only then you can access its fields:

    c = Customer { name: "John" }
    
    doesntWork = c.name
    
    works = let (Customer x) = c in x.name
    
    -- also works:
    getName :: Customer -> String
    getName (Customer x) = x.name
    

    There is also a (somewhat) shorter way to do this by using the Newtype class:

    import Data.Newtype (class Newtype, unwrap)
    
    newtype Customer = Customer { name :: String }
    derive instance Newtype Customer _
    
    c :: Customer
    c = Customer { name: "John" }
    
    john :: String
    john = (unwrap c).name