Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Homogeneous and HomogeneousRowList for homogeneous rows #20

Merged
merged 8 commits into from
Dec 10, 2017

Conversation

paulyoung
Copy link
Contributor

This is my first attempt at type-level programming. Hopefully it's pretty self explanatory, but the idea is to make sure that every field in a record has the same type.

Something similar can be achieved using StrMap, but it's not quite the same.

This initial version is available to play with at http://try.purescript.org/?gist=bec330aab7a6c0b21e466e37483dfba9. It was originally adapted from http://try.purescript.org/?gist=2720825b0476c8924717437fb6f6eefb.

I think it would be great to support polymorphism and open rows, but I haven't had any success with that.

Any and all feedback is welcome.

:: ( RowToList record fields
, FieldOf fields fieldType
)
=> RecordOf (Record record) fieldType
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think (Record record) is the right thing to do here (as opposed to just record) but it has the consequence of changing the type of a function beyond simply adding the type constraint.

For example, this:

foo :: forall a
     . Record a
    -> Record a

becomes:

foo :: forall a
     . RecordOf a Boolean
    => a
    -> a

@paf31
Copy link
Contributor

paf31 commented Aug 10, 2017

Looks good, thanks! A few notes though:

  • I'd rather call this something like Homogeneous, and have it be a predicate on the row type, not the record type.
  • It should use TypeEquals, to force the types to be equal by unification.
  • I think you can add a fundep from the row to the type in the labels, which might be useful.

@LiamGoodacre
Copy link
Member

@paf31 is the motivation for the TypeEquals approach, that you get a nicer error and don't see the RowList implementation artifact?

@paf31
Copy link
Contributor

paf31 commented Aug 10, 2017

Yes, since the type is already determined if the fundep is added anyway.

@paulyoung
Copy link
Contributor Author

Thanks for the feedback so far. Good call on moving this to Row.

Here's what I have so far:

class Homogeneous row fieldType
instance homogeneous
  :: ( RowToList row fields
     , FieldOf fields fieldType
     )
  => Homogeneous (? row) fieldType -- not sure what to do here

class FieldOf (rowList :: RowList) fieldType
instance fieldOfCons
  :: (FieldOf tail fieldType)
  => FieldOf (Cons symbol fieldType tail) fieldType
instance fieldOfNil :: FieldOf Nil fieldType

Does this seems like I'm on the right track?

I'm not quite sure about the other 2 bullet points but eager to understand.

@LiamGoodacre
Copy link
Member

LiamGoodacre commented Aug 11, 2017

@paulyoung
The Homogeneous type class will need a kind annotation of # Type on the row parameter.
For the instance, all the arguments can be left as type variables. I.e:

instance homogeneous
  :: ( RowToList row fields
     , FieldOf fields fieldType
     )
  => Homogeneous row fieldType

Phil's point about TypeEquals is referring to the FieldOf (Cons symbol fieldType tail) fieldType instance. The fieldType appears twice across multiple instance arguments. This isn't necessarily bad, it's just that if there is a mismatch the error will mention that it couldn't find an instance for FieldOf .... We'd rather have an error that two types weren't equal. So the action here is to rename one of the two usages of fieldType and add a new instance constraint requiring that those two types be equal.

The functional dependencies for FieldOf should be | rowList -> fieldType. Because once we know which instance to select - which we do by looking at the rowList, i.e whether or not it is Nil or Cons - and the constraints have been satisfied, the fieldType should be fully determined. The same sort of functional dependencies could be used on Homogeneous.

For future reading, the following is incorrect. If you require multiple constraints with the same type class they will conflict and give you overlapping instances.

In cases where there is only one instance with all the arguments being type variables, I usually have both parameters always determined | -> row fieldType - but I think Phil sometimes disagrees with doing that? @paf31

@paf31
Copy link
Contributor

paf31 commented Aug 11, 2017

I don't think we can have both parameters determined, since that says you can predict the row given no information.

* Add `TypeEquals` constraint on field type
* Add funcitonal dependency for `FieldOf`
@paulyoung
Copy link
Contributor Author

Thanks for the helpful feedback so far. I pushed some changes.

@paulyoung paulyoung changed the title Add RecordOf and FieldOf for homogeneous records Add Homogeneous and FieldOf for homogeneous rows Aug 30, 2017
@paulyoung
Copy link
Contributor Author

Is there anything else I can do here?

@@ -70,3 +74,17 @@ instance listToRowCons
:: ( ListToRow tail tailRow
, RowCons label ty tailRow row )
=> ListToRow (Cons label ty tail) row


class Homogeneous (row :: # Type) fieldType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also missing a functional dependency here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I correct in thinking that the right thing to do here is | row -> fieldType?

@@ -70,3 +74,17 @@ instance listToRowCons
:: ( ListToRow tail tailRow
, RowCons label ty tailRow row )
=> ListToRow (Cons label ty tail) row


class Homogeneous (row :: # Type) fieldType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a comment?

@@ -70,3 +74,17 @@ instance listToRowCons
:: ( ListToRow tail tailRow
, RowCons label ty tailRow row )
=> ListToRow (Cons label ty tail) row


class Homogeneous (row :: # Type) fieldType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a value-level function fieldType :: RProxy row -> Proxy fieldType would be handy as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

@paf31
Copy link
Contributor

paf31 commented Aug 31, 2017

@LiamGoodacre Any thoughts on this? What do you think about creating some submodules here?

@LiamGoodacre
Copy link
Member

Just been thinking about the fundeps and I'm thinking that this might suffer from the same issue I mentioned about multiple constraints. I'll try to come up with a demonstration tonight.

@LiamGoodacre
Copy link
Member

Actually, I am mistaken. Ignore my previous concern.

@LiamGoodacre
Copy link
Member

Submodules sound good. What do you suggest here? @paf31

@@ -70,3 +74,17 @@ instance listToRowCons
:: ( ListToRow tail tailRow
, RowCons label ty tailRow row )
=> ListToRow (Cons label ty tail) row

-- | Ensure that every field in a row has the same type.
class Homogeneous (row :: # Type) fieldType
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I though I'd asked this already but can't see it now.

Is | row -> fieldType missing here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think so

@paulyoung
Copy link
Contributor Author

Should I put this in a Type.Row.Homogeneous module?

@paf31
Copy link
Contributor

paf31 commented Nov 19, 2017

Should I put this in a Type.Row.Homogeneous module?

Sorry for the delay. Yes please.

, class Homogeneous
) where

-- | Ensure that every field in a row has the same type.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment should be removed, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Strange things like this sometimes happen as a result of imports being managed by the PureScript Emacs layer.

, FieldOf fields fieldType )
=> Homogeneous row fieldType

class FieldOf (rowList :: RowList) fieldType | rowList -> fieldType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me (without reading the docs quite a bit) which one of these should be used in regular code. I'd suggest naming this one something like HomogeneousRowList to just be explicit about it.

class FieldOf (rowList :: RowList) fieldType | rowList -> fieldType
instance fieldOfCons
:: ( FieldOf tail fieldType
, TypeEquals fieldType fieldType2 )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need TypeEquals? Can you just use

instance fieldOfCons :: FieldOf tail fieldType => FieldOf (Cons symbol fieldType tail) fieldType

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did that in 8b39b00#diff-86491d4ab8759f2c9d61332f39afdd9bR15 but you suggested using TypeEquals in #20 (comment) 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

D'oh. Thanks :)

That was incorrectly copied by the PureScript Emacs layer.
To better communicate which class should be used in regular code.
@paulyoung
Copy link
Contributor Author

I think I've addressed everything in the latest round of feedback.

:: ( HomogenousRowList tail fieldType
, TypeEquals fieldType fieldType2 )
=> HomogenousRowList (Cons symbol fieldType tail) fieldType2
instance fieldOfNil :: HomogenousRowList Nil fieldType
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose I should rename these to homogenousRowListCons and homogenousRowListNil

@paulyoung paulyoung changed the title Add Homogeneous and FieldOf for homogeneous rows Add Homogeneous and HomogeneousRowList for homogeneous rows Dec 6, 2017
@paf31 paf31 merged commit 979f06a into purescript:master Dec 10, 2017
@paf31
Copy link
Contributor

paf31 commented Dec 10, 2017

Thanks!

@paulyoung paulyoung deleted the paulyoung/homogeneous-records branch December 11, 2017 06:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants