Skip to content

Represent class dictionaries as newtypes#4125

Merged
rhendric merged 1 commit intopurescript:masterfrom
rhendric:rhendric/typeclasses-are-newtypes
Jul 19, 2021
Merged

Represent class dictionaries as newtypes#4125
rhendric merged 1 commit intopurescript:masterfrom
rhendric:rhendric/typeclasses-are-newtypes

Conversation

@rhendric
Copy link
Copy Markdown
Member

@rhendric rhendric commented Jun 28, 2021

Class dictionaries are now represented as newtype wrappers of records.
This drops the awkward TypeClassDictionaryConstructorApp and
TypeClassDictionaryAccessor expressions in favor of regular newtype
constructors and record accessors.

Description of the change

This is mostly motivated by an attempt-in-progress at #4086, but I think it stands on its own merits.

Previously: #3567 and #781. I'm tempted to say that this PR closes those, but maybe there are reasons to prefer the data representation over the newtype one I've implemented? I quite like how simple the generated code for the newtype representation is, and how it means that the newtype constructors can be completely erased (with other newtypes, the constructors still have to be exported in case some code references them in a way that isn't an erased application, but with typeclass newtypes that's not possible). But adapting this PR to use data instead of newtype would be easy and would also suffice for my #4086 purposes.

This newly-updated comment describes the details:

{- Desugar type class and type class instance declarations
--
-- Type classes become newtypes for their dictionaries, and type instances become dictionary declarations.
-- Additional values are generated to access individual members of a dictionary, with the appropriate type.
--
-- E.g. the following
--
-- module Test where
--
-- class Foo a where
-- foo :: a -> a
--
-- instance fooString :: Foo String where
-- foo s = s ++ s
--
-- instance fooArray :: (Foo a) => Foo [a] where
-- foo = map foo
--
-- {- Superclasses -}
--
-- class (Foo a) <= Sub a where
-- sub :: a
--
-- instance subString :: Sub String where
-- sub = ""
--
-- becomes:
--
-- <TypeClassDeclaration Foo ...>
--
-- newtype Foo$Dict a = Foo$Dict { foo :: a -> a }
--
-- -- this following type is marked as not needing to be checked so a new Abs
-- -- is not introduced around the definition in type checking, but when
-- -- called the dictionary value is still passed in for the `dict` argument
-- foo :: forall a. (Foo$Dict a) => a -> a
-- foo (Foo$Dict dict) = dict.foo
--
-- fooString :: Foo$Dict String
-- fooString = Foo$Dict { foo: \s -> s ++ s }
--
-- fooArray :: forall a. (Foo$Dict a) => Foo$Dict [a]
-- fooArray = Foo$Dict { foo: map foo }
--
-- {- Superclasses -}
--
-- <TypeClassDeclaration Sub ...>
--
-- newtype Sub$Dict a = Sub$Dict { sub :: a
-- , "Foo0" :: {} -> Foo$Dict a
-- }
--
-- -- As with `foo` above, this type is unchecked at the declaration
-- sub :: forall a. (Sub$Dict a) => a
-- sub (Sub$Dict dict) = dict.sub
--
-- subString :: Sub$Dict String
-- subString = Sub$Dict { sub: "",
-- , "Foo0": \_ -> <DeferredDictionary Foo String>
-- }
--
-- and finally as the generated javascript:
--
-- var foo = function (dict) {
-- return dict.foo;
-- };
--
-- var fooString = {
-- foo: function (s) {
-- return s + s;
-- }
-- };
--
-- var fooArray = function (dictFoo) {
-- return {
-- foo: map(foo(dictFoo))
-- };
-- };
--
-- var sub = function (dict) {
-- return dict.sub;
-- };
--
-- var subString = {
-- sub: "",
-- Foo0: function () {
-- return fooString;
-- }
-- };
-}


Checklist:

  • Added the change to the changelog's "Unreleased" section with a reference to this PR (e.g. "- Made a change (#0000)")
  • Added myself to CONTRIBUTORS.md (if this is my first contribution)
  • Linked any existing issues or proposals that this pull request should close
  • Updated or added relevant documentation
  • Added a test for the contribution (if applicable)

@rhendric rhendric force-pushed the rhendric/typeclasses-are-newtypes branch from d8e67ea to cd869eb Compare June 28, 2021 20:13
@garyb
Copy link
Copy Markdown
Member

garyb commented Jun 28, 2021

I wanted to use data constructors as their implementation is more optimisable by JS runtimes*, and it makes the representation less record-dependent for backends that have to implement records by means that may be less efficient than static data types.

I was also thinking that perhaps it'd be easier to use annotations on the data constructor form of this to use for later optimisations, but you've already made good progress on a major part of that without needing it (#3915)!

* In light of some benchmarks Nate tried recently, there probably isn't much of a distinction between object and "class" performance anymore, so the JS concern isn't really there anymore. Property access used to be significantly faster on "classes" than on standard objects.

I'm in favour of this PR - even if later it seems that the data approach is better again for some reason, for we still get to eliminate the TypeClassDictionary... stuff in the mean time, which is totally worth it!

@rhendric rhendric force-pushed the rhendric/typeclasses-are-newtypes branch from cd869eb to a0d482e Compare June 28, 2021 23:03
@JordanMartinez
Copy link
Copy Markdown
Contributor

@garyb So, would you be willing to approve this PR? Or are there other things you think need to be addressed?

@garyb
Copy link
Copy Markdown
Member

garyb commented Jul 9, 2021

I haven't reviewed it thoroughly yet, but it looks good from a glance! Will try to remember to come back to it this weekend for a proper 👍

Class dictionaries are now represented as newtype wrappers of records.
This drops the awkward TypeClassDictionaryConstructorApp and
TypeClassDictionaryAccessor expressions in favor of regular newtype
constructors and record accessors.
@rhendric rhendric force-pushed the rhendric/typeclasses-are-newtypes branch from a0d482e to 880b6b0 Compare July 12, 2021 21:45
Copy link
Copy Markdown
Member

@garyb garyb left a comment

Choose a reason for hiding this comment

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

👍 Sorry it took ages to get to!

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