-
Notifications
You must be signed in to change notification settings - Fork 9
Implement reflection for ad-hoc interpreters #86
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
Conversation
Allows users to define ad-hoc interpretations of capabilities using the functions `Capability.Reflection.interpret_` and `Capability.Reflection.interpret` by providing a dictionary, i.e. a record of functions, that defines an implementation of the capability. The details are explained in the Haddock documentation of `Capability.Reflection`. A reference to the top-level `Capability` Haddock documentation is added as well. Examples are defined in `examples/Reflection.hs`.
aspiwack
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great.
I have a suggested wibble about removing the tag argument from the Reified data family. I think it doesn't play a role. So please try to remove it before merging.
| -- _state :: forall a. (s -> (a, s)) -> m a | ||
| -- } | ||
| -- :} | ||
| data family Reified (tag :: k) (c :: Capability) (m :: * -> *) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of feel we should generalize this data family and add it to its own package. (or to reflection possibly), with the intent that we could generate these with Template Haskell, probably:
data family Reified (c :: Constraint)(and we would transform Reified tag c m into Reified (c m).
The tag disappears, in this transformation. This is fishy, do we actually need the tag in the current presentation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reified was a convenient location to attach the tag for interpret_ and interpret to ship it into the action. The suggested transformation is possible, then we just need to find another way to transport the tag into the action of interpret.
What I mean is the following. Take this example:
example :: [()]
example =
interpret_ @"sink"
ReifiedSink { _yield = (:[]) }
(do yield @"sink" (); yield @"sink" ())
And assume
data family Reified (c :: Constraint)
interpret_ :: forall tag c m a.
(Monad m, forall s. Reifies s (Reified (c m)) => c (Reflected s c m)) =>
Reified (c m) -> (forall m'. c m' => m' a) -> m a
Then we will get the following type error on example:
• Could not deduce (HasSink "sink" () m')
arising from a use of ‘yield’
from the context: HasSink tag0 () m'
bound by a type expected by the context:
forall (m' :: * -> *). HasSink tag0 () m' => m' ()
I.e. it hasn't inferred tag0 ~ "sink" because there is no such constraint between tag and c.
A user would have to provide a type annotation to the ReifiedSink constructor to set the tag, for example
interpret_
(ReifiedSink { _yield = (:[]) } :: Reified (HasSink "sink" () []))
(do yield @"sink" (); yield @"sink" ())
That's pretty clumsy to write. Is there a way to use TypeApplications on record constructors? I tried the following, which don't work
ReifiedSink @_ @"sink" { _yield = ... }
ReifiedSink { _yield = ... } @_ @"sink"
I've found two other ways that allow the same API to interpret_.
- Be explicit about the
tagin the capability, i.e. use the following type forinterpret_:I'm not too fond of this approach because it hard codes that capabilities should have the kindinterpret_ :: forall tag c m v a. (Monad m, forall s. Reifies s (Reified (c tag v m)) => c tag v (Reflected s (c tag v) m)) => Reified (c tag v m) -> (forall m'. c tag v m' => m' a) -> m atag -> value -> (* -> *) -> Constraint. This happens to be the case so far, because all capabilities have some sort of value type parameter, e.g. therinHasReader tag ror thewinHasWriter tag w. But, this seems coincidental. Why shouldn't there be a capability with none, or two, or more value type parameters?
Maybe the shape of capabilities should have beenvalue -> tag -> (* -> *) -> Constraint, e.g.HasReader r tag, then we could usec tag mininterpret_without making assumptions about the number of value type parameters. - Define an additional constraint, say
TagOf tag c, and use it as followsThis enforces thatinterpret_ :: forall tag c m a. (TagOf tag c, Monad m, forall s. Reifies s (Reified (c m)) => c (Reflected s c m)) => Reified (c m) -> (forall m'. (TagOf tag c, c m') => m' a) -> m achas thetagwithout hard-coding the number of value type parameters.
This requires some boilerplate when defining a new capability though, e.g. instances of the form(The example takes a short cut withinstance tag ~ tag' => TagOf tag (HasReader tag' r)instance tag ~ tag' => TagOf tag (c tag' v), but that seems to conflict with instances for capabilities with different number of value type parameters.)
Out of all those, attaching tag to Reified seemed like the least bad option.
| instance | ||
| ( Monad m, | ||
| Reifies s' (Reified tag (HasState tag s) m) | ||
| ) => | ||
| HasSink tag s (Reflected s' (HasState tag s) m) | ||
| where | ||
| yield_ _ = coerce $ _yield $ _stateSink $ reified @s' | ||
| {-# INLINE yield_ #-} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes, this makes it a bit annoying to generate with template haskell (even eventually). Maybe template haskell lets us access super-classes and iterate on them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressing review comment #86 (comment)
aspiwack
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'm convinced that this is the best we can do for the time being. So let's merge.
Allows users to define ad-hoc interpretations of capabilities using the functions
Capability.Reflection.interpret_andCapability.Reflection.interpretby providing a dictionary, i.e. a record of functions, that defines an implementation of the capability.The details are explained in the Haddock documentation of
Capability.Reflection. A reference to the top-levelCapabilityHaddock documentation is added as well.Examples are defined in
examples/Reflection.hs.