(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