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

Allow type applications to specify type of SPECIALISE pragma #15

Closed
wants to merge 3 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
127 changes: 127 additions & 0 deletions proposals/0000-specialise-pragma-with-type-applications.rst
@@ -0,0 +1,127 @@
.. proposal-number:: Leave blank. This will be filled in when the proposal is
accepted.

.. trac-ticket:: Leave blank. This will eventually be filled with the Trac
ticket number which will track the progress of the
implementation of the feature.

.. implemented:: Leave blank. This will be filled in with the first GHC version which
implements the described feature.

.. highlight:: haskell

Proposal title
==============

GHC's ``SPECIALISE`` pragma allows the user to instruct the compiler specialise
a polymorphic function at a concrete type. However, its current syntax is quite
unwieldy, requiring that the user repeat the entire type of the the specialised
function, including those parts that are shared with its unspecialised type.

Here we exploit the ``TypeApplications`` syntax added in GHC 8.0 to write many
uses of ``SPECIALISE`` more concisely.

Motivation
----------

The ``SPECIALISE`` pragma is a means of guiding the optimizer towards the types
a function will most commonly be used at. For instance, consider this
polymorphic function from the ``postgresql-simple`` database bindings, ::

foldWithParser :: ( MonadIO m, MonadMask m, ToRow params )
=> RowParser row -> Connection
-> Query -> params
-> a -> (a -> row -> m a)
-> m a
foldWithParser parser conn template qs a f = ...

Here we have a function with a non-trivial, polymorphic type. If we know that
this function may often be used at the ``IO`` monad then we might instruct
GHC to derive an appropriate specialisation, ::

{-# SPECIALISE
foldsAndParser
:: RowParser row -> Connection
-> Query -> params
-> a
-> (a -> row -> IO a) -> IO a
#-}

This is, of course, quite a mouthful! Moreover, if we would at some point like
to change the type of ``foldWithParser``, we would need to updae each of the
Copy link
Contributor

Choose a reason for hiding this comment

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

updae

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks!

specialisations.

We would ideally like to use the fact that we are merely instantiating the
function's ``m`` type argument at ``IO``.

Proposed Change
---------------

Here we propose to allow use of the ``TypeApplications`` syntax introduced in
GHC 8.0.1 in place of the type in a ``SPECIALISE`` pragma. For instance, in the
case of ``foldWithParser`` above we might first add an explicit ``forall`` to
make the order of type variables clear, ::


foldWithParser :: forall m params row a. ( MonadIO m, MonadMask m, ToRow params )
=> RowParser row -> Connection
-> Query -> params
-> a -> (a -> row -> m a)
-> m a
foldWithParser parser conn template qs a f = ...

and then write its ``SPECIALISE`` pragma as a unsaturated type application, ::

{-# SPECIALISE foldsAndParser @IO #-}

Of course, this raises the question of how we might quantify over ``m`` while
instantiating a later variable, for instance ``params ~ ()``. Here we propose a
very restricted use of explicit ``forall`` syntax to introduce type binders
which can then be used to fill in the type arguments of a function which we'd
like to quantify over, ::

{-# SPECIALISE forall m. foldsAndParser @m @() #-}
Copy link
Contributor

Choose a reason for hiding this comment

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

can we write

foo :: forall a b. (Ord a, Ord b) => Foo a -> Bar b
{-# SPECIALISE forall a. foo @a @a #-}

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, I believe that should be fine. The only question is whether we want to allow the introduction of new constraints, I believe. That being said, I haven't thought terribly hard about what subtleties the forall syntax might introduce.


Proposed syntax
~~~~~~~~~~~~~~~

The syntax of ``SPECIALISE`` would become,

Choose a reason for hiding this comment

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

Looks like this sentence is incomplete -- unfinished section?


.. productionlist::
specialise_pragma: "{-# SPECIALISE" activation specialise_body
specialise_body: bndr "::" type
| ["forall" tybndr+ "." bndr type_app*
type_app: '@' type

bndr: (a Haskell value-level binder)
tybndr: (a Haskell type-level binder)
type: (A Haskell type)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sadly it seems that the .. productionlist directive isn't recognized by GitHub's renderer.


Drawbacks
---------

This introduces yet another dependency on the often-implicit order of type
variables in a function definition, although we are already quite far down this
road with the introduction of ``TypeApplications``.

It can be hard to see at first glance what the type of a specialisation written
in ``TypeApplications``\-form is.

Alternatives
------------

We could be more liberal in the sorts of types we allow to be written with
``forall``. For instance, we might one to constrain ``forall``\-bound types
(ignore the silly choice of syntax), ::

{-# SPECIALISE forall params. AClass params => foldsAndParser @IO @params #-}

That being said, it's not clear that this is a solution to a problem that users
actually feel. It seems the simple approach proposed above would give us 90% of
the benefit for a fraction of the complexity.

Unresolved Questions
--------------------

Are there any parts of the design that are still unclear? Hopefully this section
will be empty by the time the proposal is brought up for a final decision.