Row polymorphism with newtype


#1

Can row polymorphism be used with ‘newtype’, something like this (this doesn’t actually work)

newtype Obj a = Obj { x :: Number, y :: Number | a }

newtype ObjRect = ObjRect { x   :: Number, y   :: Number
                          , wid :: Number, hgt :: Number }

getX :: forall a. Obj a -> Number
getX (Obj o) = o.x

testFunc :: Number
testFunc = getX $ ObjRect {x: 1.0, y: 2.0, wid: 3.0, hgt: 4.0 } -- this gets an error


#2

(this post appears before yours because I started working on it yesterday… but it is a response to the posts that follow it)

Thanks for that! I think I understand.

However, I just realized that polymorphism isn’t going to help me! Because the “master” data type has to be stored in a list or map, which requires that all records be the same type. And I have to use a newtype to avoid loops in the type-checker: some of my fields refer recursively to the “master” type.

So I think I had better say more about my goal, specifically.

I’m creating a program for laying out and animating “objects.” Objects can be several types which have different data to represent them.

I started by doing something like this:

type Object = { commonData1 :: Number
              , commonData2 :: Number
              , specificData :: ObjectSpecific }

data ObjectSpecific = ObjRectangle Color Number Number Number
                    | ObjText Number Number String
                    | ObjCircle Color Number Number Number

But this makes operating on ‘Object’ with lenses difficult. Say there’s code specific to an ObjRectangle. I need to protect the code that accesses rectange-specific state with a guarantee that the specific type is present (check the Nothing output from a prism).

doSomethingWithRectange :: Object -> Effect Object
soSomethingWithRectange obj = do
  -- update a common field
  let obj2 = over _commonData1 2.0 -- a lens for commonData1
      -- at this point we might like to do something with the specific data
      rectSpecific = case preview _objRectange obj2 of -- prism
         Just s  -> s
         Nothing -> throw error
      newRectSpecific = doWithRectangeSpecific rectSpecific
      obj3 = obj2 { specificData = newRectSpecific }
  pure obj3

Making changes to both the common and specific parts of an object with similar semantics is not really possible.

So now I’m thinking it would be better to define the whole Object as a sum type, then use row polymorphism to operate on individual fields.

data Object = ObjRect ObjRectR
            | ObjText ObjTextR


type CommonRecord a = { commonData1 :: Number
                      , commonData2 :: Number
                        | a }

type ObjRectR = { commonData1 :: Number
                , commonData2 :: Number
                , rectData    :: Number }

type ObjTextR = { commonData1 :: Number
                , commonData2 :: Number
                , textData    :: Number }


operateOnCommonRecord :: forall a. CommonRecord a -> CommonRecord a

-- an implementation of Rectangles will look like this
operateOnRectangle :: ObjRectR -> ObjRectR
operateOnRectangle r = (do something else) $ operateOnCommonRecord r



#3

If a type is a record, type Obj a = { x :: Number, y :: Number, a }, it has structural type-checking.

If a type is data type or newtype, newtype Obj a = Obj { x :: Number, y :: Number | a), it has nominal type-checking.

From a high-level, two structural types are equivalent (called unifying in type-checking), if the type’s label names & types specified by the two types are the same. Two nominal types are equivalent if the type’s names are the same.

If you want structural type equivalence, you need to use a structural types, which means type-aliasing a record, like type Obj a = { x :: Number, y :: Number | a }.

If you want to put constraints on the contents of a nominal type, like newtype Obj a = Obj { x :: Number, y :: Number | a}, you can use type classes and constraints on the function type signature which you want to make re-usable, like getX :: forall a. HasX a => a -> Number or getX :: forall a. IsRecordNewtype a rec => HasX rec => a -> Number.

You’ll have to decide which manner of type-checking, or which combination of them, would best serve your app’s domain language.


#4

I think some of the things we discussed before may apply, in that you might be able to use unwrap and over to achieve what you’re trying to do.


#5

Not sure if you got the notification. I started editing a post before you posted yours, so when I finally posted it, it showed up before yours in this thread. But note that it’s a new reply.


#6

Oh I see it now. Yeah! That second design is great! CommonRecord a and ObjRectR and ObjTextR. Nice job! :smiley: