Pre-GHC-Proposal: Instantiate backpack signatures when importing

I remember this message by @mpickering about Backpack:

In my understanding, if a feature is barely used but causes major pain for GHC developers, it should be ripped off.

Haskell doesn’t need a kitchen-sink of all possible features. Haskell needs a good Product Manager.

4 Likes

There’s a long list of features that I’d like removed from GHC, but strangely backpack isn’t one of them. I think it desperately needs someone to advocate for it and to help improve the existing tooling (and perhaps that might even be me).

Backpack adds something to Haskell that I think is of value - it allows me to write a library where the user of the library can supply a typeclass.

For example I can write a library of utilities for higher kinded data types, and leave it up to the user whether my functions require BTraversable from barbies or FTraversable from hkd, or some other equivalent typeclass I didn’t even know existed.

I just need (in bkp’s single file format without anything instantiating it and completely untested)

unit my-hkd-base where
  module My.HKD.Base where
    -- I supply my own typeclass
    class MyTraversableF t where
      myTraverseF :: Applicative f => (forall a . h a -> f (g a)) -> t h -> f (t g)
    -- and a newtype to wrap my things so they can be lifted into your typeclass
    newtype MyTraversable t f = MyTraversable (t f)
unit my-hkd-sig where
  dependency my-hkd-base
  signature HKDSig where
    import My.HKD.Base
    -- You give me your typeclass, as well as the function(s) I need from it
    class F (t :: (* -> *) -> *)
    traverseF :: (F t, Applicative f) => (forall a . h a -> f (g a)) -> t h -> f (t g)
    -- And supply a instance to lift my typeclass into yours (probably requires UndecidableInstances)
    instance (MyTraversableF f) => F (MyTraversable f)
  module My.HKD.Stuff where
    import Data.Functor.Identity
    import HKDSig
    import My.HKD.Base
    -- and the functions I provide use your specified typeclass
    runIO :: F t => t IO -> IO (t Identity)
    runIO = traverseF (fmap Identity)
5 Likes

Yes! Maybe it could be you! That would be so great.

3 Likes

I have a use-case for backpack at work, but it was discussed that the tooling experience would make everyone unhappy, so it probably won’t be used.

1 Like

One potential use of Backpack that I haven’t seen in the wild is to manage the size of compilation graphs in development workflows.

If the code you’re working on explicitly describes what it expects from upstream dependencies, you no longer have to compile those dependencies in order to typecheck your own code! This is incredibly effective for establishing fast and ergonomic development workflows in a language like Haskell, where dependencies can take massive amounts of time to compile and impose a drag on ergonomics.

I think Backpack (or an evolution of it) has a critical role to play in a future where Haskell code on the web can be examined and traversed semantically (i.e. with features like type on hover or go to definition).

I also agree with several other commenters here that the problem isn’t with the basic idea of having “a Backpack” in Haskell, but with various ancillary engineering details that affect how easy it is to put it to good use. For example one concrete bit of tooling that would be very useful to have in a Backpack user’s arsenal would be a signature inference tool: i.e. a tool that can take an existing package or compilation unit and infer a signature describing what it produces, as well as signatures describing what it demands from its dependencies.

4 Likes

Another way to avoid compilation and only typecheck it to use -fno-code, like --ghc-options="-fforce-recomp -fno-code"

2 Likes

Good point. But if I understand correctly that would still require you to typecheck your dependencies, even if no code is generated… To me it feels like a killer feature of signatures is that working on our own project doesn’t entail even knowing how to obtain the source code for a dependency, provided there’s a trustworthy signature that abstracts it.

Originally part of the motivation of Backpack was that it would be possible to do truly separate compilation. Then you wouldn’t have to compile your dependencies at all (until you actually want to run the program). But I believe that idea has been dropped to to support optimisations that look through the interfaces.

@jaror Hmm. Maybe I am misunderstanding something. But I took it that in theory you can take pretty much any package and divorce it from its dependencies (for the purposes of static analysis, not execution) by adding enough signatures (in signature packages or otherwise). Yes, you do need enough of the kind and type signatures, and “type level implementation”, but no term level definitions are required. Of course there are edge cases like TH consumed downstream, but wouldn’t this still work for most typical packages?

Ah sorry, nevermind my previous comment. I understand what you mean now, I assume you mean it would be possible to produce a “partial” executable or shared library (which could be linked with the other bits) to produce an executable.

Yes, It works for type checking but not for optimization and code generation.

1 Like

That makes sense. I’m probably in the minority (and pretty naive about the implementation challenges of Backpack), but I actually find some benefits to how much more limited Backpack’s module language is than, say, ML. It seems to place the focus on distributable software artifacts as the elements of composition in a way that more powerful “intra package” module systems don’t.

E.g. the ability to have separate, cached compilation at the expense of runtime performance seems like a good tradeoff in the context of a development workflow.