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

Instantiate backpack signatures when importing #599

Open
noughtmare opened this issue Jun 28, 2023 · 6 comments
Open

Instantiate backpack signatures when importing #599

noughtmare opened this issue Jun 28, 2023 · 6 comments

Comments

@noughtmare
Copy link
Contributor

noughtmare commented Jun 28, 2023

Backpack is not used enough. I think there are two main reasons for this which we can address with a GHC proposal:

  1. Backpack is not supported by Stack
  2. Backpack is often not at hand

I think both these points can be addressed by integrating Backpack more tightly into the language. In particular, I'd propose we allow instantiating Backpack signatures when importing modules. The syntax could look like this:

-- A.hsig
signature A where
foo :: String

-- B.hs
module B where
import A
someFun = putStrLn (foo ++ ", World!")

-- Hello.hs
module Hello where
foo = "Hello"

-- Goodbye.hs
module Goodbye where
foo = "Goodbye"

-- Main.hs
import qualified Hello
import qualified Goodbye
import qualified B as BHello   where A = Hello
import qualified B as BGoodbye where A = Goodbye
main = do
  BHello.someFun
  BGoodbye.someFun

As you might expect, this program would print:

Hello, World!
Goodbye, World!

Notes:

  1. Signature dependencies propagate transitively. The only requirement is that they are eventually instantiated.
    E.g. in the example above you could create a module C that imports B without instantiating A, as long as the module that imports C does instantiate A (or if there is again another module above C that instantiates A).
  2. Signatures can be instantiated by other signatures, as long as those are eventually instantiated too.
    E.g. we could create a new signature C.hsig and write import qualified B as BC where A = C as long as C is then eventually instantiated too.
  3. Modules (or signatures) need to be imported before they can be used to instantiate signatures.
    E.g. import qualified Hello and import qualified Goodbye in the example above.
  4. Multiple signatures can be instantiated in one import ... where ... block.
    E.g. import W where X = Foo; Y = Bar; Z = Baz or over multiple lines following the layout rules.
  5. Signatures can be instantiated by modules which provide more than what the signature require or even by modules that provide less than what the signature requires if only a subset of the signature is used. This follows the standard Backpack instantiation mechanism.
  6. The where ... part must always be at the end of the import ... line. This is intended to make it easier to parse.

These changes are meant to make it easier for Stack to support a minimally usable version of Backpack, because under this proposal GHC can handle most of the instantiation on its own (as long as the proper modules and signatures are supplied). Furthermore, this makes Backpack much more at hand. Using a signature becomes as simple as writing a .hsig file, importing that in a module, and instantiating that module when it is imported. No more messing around with local libraries in cabal files or mixins.

@alt-romes
Copy link

alt-romes commented Jun 28, 2023

Great to see this, I really like backpack and also want it to thrive more.

I'd add to the list of reasons blocking it from being more widely used:

  • Having a signature in a cabal project breaks HLS

@noughtmare
Copy link
Contributor Author

That's a good point. I haven't ever tried using Backpack with HLS. That might also be a good thing to open a bug report about. I can only find this one relating to .hsig files: haskell/haskell-language-server#3088, but that's not about signatures in .cabal files.

Also, I should say that I don't think what I'm proposing removes the need for specifying something like:

  signatures: A

In the cabal file. So this may not solve that issue with HLS directly.

@danidiaz
Copy link

danidiaz commented Jun 28, 2023

-- B.hs
module B where
import A
someFun = putStrLn foo

Suppose there were modules C.hs and D.hs which also imported the abstract signature A (let's say they live in the same library as A and B). Now suppose Main.hs also wanted to make use of instantiated versions of C and D.

Would we need to replicate the same instantiations (the wheres) for B, C and D?

In the current cabal-based syntax for mixins, that wouldn't be needed. It would be something like

mixins:
   myabstractlib (B as BHello, C as CHello, D as DHello) requires (A as Hello)
   myabstractlib (B as BGoodbye, C as CGoodbye, D as DGoodbye) requires (A as Goodbye)

In Backpack, instantiation works at the level of entire libraries, not individual modules.

@noughtmare
Copy link
Contributor Author

noughtmare commented Jun 28, 2023

That's right. One way to get close at an import level would be to create a stub module:

module X (module B, module C, module D) where
import B
import C
import D

And then instantiate A when importing that stub:

import qualified X as XHello   where A = Hello
import qualified X as XGoodbye where A = Goodbye

Of course now you don't have the fine-grained BHello, CHello, etc.

You could consider a syntax like:

instantiate A = Hello in
  import qualified B as BHello
  import qualified C as CHello
  import qualified D as DHello

But I don't know if that is worth it. The duplication does not seem that bad to me.

Edit: And I should mention that I'm not proposing to remove mixins. This import ... where ... syntax would be an additional mechanism for instantiating signatures. The interaction between mixins and this proposal would be something to work out before making it a real proposal.

@noughtmare
Copy link
Contributor Author

noughtmare commented Jun 29, 2023

I'm actually coming around to the let-style syntax instead of where-style, e.g.:

let A = Hello in
  import qualified B as BHello

Being able to instantiate multiple modules at the same time is a big advantage. For example @alt-romes' https://github.com/alt-romes/ghengin/ could use this syntax in Main.hs:

let
  Ghengin.Core.Renderer.Kernel        = Ghengin.Vulkan.Renderer.Kernel
  Ghengin.Core.Renderer.DescriptorSet = Ghengin.Vulkan.Renderer.DescriptorSet
  Ghengin.Core.Renderer.Buffer        = Ghengin.Vulkan.Renderer.Buffer
  Ghengin.Core.Renderer.Pipeline      = Ghengin.Vulkan.Renderer.Pipeline
  Ghengin.Core.Renderer.RenderPass    = Ghengin.Vulkan.Renderer.RenderPass
  Ghengin.Core.Renderer.Texture       = Ghengin.Vulkan.Renderer.Texture
  Ghengin.Core.Renderer.Sampler       = Ghengin.Vulkan.Renderer.Sampler
in
  import Ghengin
  import Ghengin.Core.Mesh
  import Ghengin.Component.Camera
  import Ghengin.Component.Transform
  import Ghengin.Component.UI
  import Ghengin.Core.Render.Packet
  import Ghengin.Scene.Graph
  import Ghengin.Core.Render.Property

I expect having such a block in Main will become quite a common pattern.

@noughtmare
Copy link
Contributor Author

noughtmare commented Jun 30, 2023

An alternative to the let-style syntax is to allow mix-in style matching, so that when you write import <concrete module> as <signature name> it will instantiate the signature with that module. The example in my previous comment can then be rewritten as:

import qualified Ghengin.Vulkan.Renderer.Kernel        as Ghengin.Core.Renderer.Kernel
import qualified Ghengin.Vulkan.Renderer.DescriptorSet as Ghengin.Core.Renderer.DescriptorSet
import qualified Ghengin.Vulkan.Renderer.Buffer        as Ghengin.Core.Renderer.Buffer
import qualified Ghengin.Vulkan.Renderer.Pipeline      as Ghengin.Core.Renderer.Pipeline
import qualified Ghengin.Vulkan.Renderer.RenderPass    as Ghengin.Core.Renderer.RenderPass
import qualified Ghengin.Vulkan.Renderer.Texture       as Ghengin.Core.Renderer.Texture
import qualified Ghengin.Vulkan.Renderer.Sampler       as Ghengin.Core.Renderer.Sampler

import Ghengin
import Ghengin.Core.Mesh
import Ghengin.Component.Camera
import Ghengin.Component.Transform
import Ghengin.Component.UI
import Ghengin.Core.Render.Packet
import Ghengin.Scene.Graph
import Ghengin.Core.Render.Property

By itself this means you can't easily instantiate the same signature with two different modules, but in that case you can use the import ... where ... syntax.

Edit: Or a combination:

let
  import Ghengin.Vulkan.Renderer.Kernel        as Ghengin.Core.Renderer.Kernel
  import Ghengin.Vulkan.Renderer.DescriptorSet as Ghengin.Core.Renderer.DescriptorSet
  import Ghengin.Vulkan.Renderer.Buffer        as Ghengin.Core.Renderer.Buffer
  import Ghengin.Vulkan.Renderer.Pipeline      as Ghengin.Core.Renderer.Pipeline
  import Ghengin.Vulkan.Renderer.RenderPass    as Ghengin.Core.Renderer.RenderPass
  import Ghengin.Vulkan.Renderer.Texture       as Ghengin.Core.Renderer.Texture
  import Ghengin.Vulkan.Renderer.Sampler       as Ghengin.Core.Renderer.Sampler
in
  import Ghengin
  import Ghengin.Core.Mesh
  import Ghengin.Component.Camera
  import Ghengin.Component.Transform
  import Ghengin.Component.UI
  import Ghengin.Core.Render.Packet
  import Ghengin.Scene.Graph
  import Ghengin.Core.Render.Property

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants