Extracting the row type from a record and using in the constraints for a merge


#1

Hi! I’m not sure the title is very clear, but here’s a better explanation.

I want to build a function that allows to build a Aff.Request String overriding fields of the defaultRequest.

For example:
buildRequest { url, method }

Asking on slack, I was suggested to use this class to get the row out of a record:

class RecordRow (t :: Type) (r :: # Type) | t -> r
instance recordRowInst :: RecordRow (Record r) r
else
instance recordRowFail :: Fail (Beside (Text "RecordRow applied to non record type: ") (Quote t)) => RecordRow t r

I added it to this function:

buildRequest ::
  forall r s t x.
  RecordRow (Request String) s =>
  Row.Nub t s =>
  Row.Union r s t =>
  Row.Union r x s =>
  { | r } ->
  Request String
buildRequest r = Record.merge r defaultSimpleRequest

The compiler complains that there’s a missing instance for Union r x t, where x is Request String's row.
I must be doing something wrong, but I’m not sure what :slight_smile:


#2

I think you want

buildRequest ::
  forall rsub rall rx.
  RecordRow (Request String) rall =>
  Row.Union rsub rall rx =>
  Row.Nub rx rall =>
  { | r } ->
  Request String

That is you want to merge the fields of rsub and rall, and then assert that the nubbed labels are equivalent to rall.


#3

I get the same error. I tried some variations (not sure if you meant r and rsub to be different, tried Record.union instead of Record.merge), but the problem is still that the compiler doesn’t seem to understand that rall corresponds to the row of Request String.

My solution works when I can explicitly pass { | rall } instead of Request String, but it’s not what I need.


#4

The type system doesn’t automatically propagate equivalences like that, so you’d have to add some evidence to your class.

defaults ::
  forall rall rsub rx.
  Row.Union rsub rall rx =>
  Row.Nub rx rall =>
  { | rall } ->
  { | rsub } ->
  { | rall }
defaults = flip Record.merge

type Request a =
  { foo :: a
  , bar :: Int
  }

class RecordRows a (r :: # Type) | a -> r where
  toRows :: a -> { | r }

instance recordRows :: RecordRows { | r } r where
  toRows = identity

buildRequest ::
  forall r rall rx.
  RecordRows (Request String) rall =>
  Row.Union r rall rx =>
  Row.Nub rx rall =>
  { | r } ->
  { | rall }
buildRequest = defaults $ toRows { foo: "", bar: 42 }

If at all possible, the easiest thing is to just have a synonym of of the rows in Request so you can avoid the class. I know you probably want to use the same synonym from whatever library, but I would just define my own to avoid this nastiness.


#5

I see, I assumed that having the constraint was enough. This is the problem of learning by just copying and and trying to do stuff without undestanding it :smiley:

This solution works great btw. Why do you call it nasty? I feel it’s less elegant and clear to copy and paste Request's rows manually, but maybe I’m missing something obvious.

Thanks again for your help!


#6

Because I find it annoying to have to deal with equivalence coercions. :slight_smile: