`-XQualifiedSyntax` - Haskells eDSL capabilties on another level - without type defaulting or ambiguous type errors

Desugaring Haskell Syntax to type classes seems to have become the norm. While this is powerful, it also has a number of limitations, e.g.

  • type classes have to be globally coherent
  • GHC sometimes cannot guess your intentions (ambiguous types errors)
  • type classes constrain your implementation (e.g. IsList)

I think that -XQualifiedDo is a great extension to show a middle way on the spectrum between -XRebindableSyntax and globally coherent desugaring to type classes and I think we should build on this; the idea: -XQualifiedSyntax which makes haskell syntax qualifyable. Examples:

-- Data.Vector and Data.Monoid implement fromList
import Data.Vector qualified as Vec 
import Data.Monoid qualified as Monoid 
-- Data.ByteString implements fromString
import Data.ByteString qualified as BS
-- Data.Int implements fromIntegral 
import Data.Int qualified as Int
-- I have a weird eDSL where it makes sense to represent 
-- bits as Bools but I don't want to make sure the user 
-- is aware when using this partial syntax + I don't want the
-- definition that is intended only for syntax to be infective 
import MyModule.Bits qualified as Bits 
-- I'm super into singletons and want to have a version of `if then else` that 
-- works with type level booleans
import Data.SBool qualified as S
import Data.SOP.NP qualified as NP

x = V.[Int.1, 2, 3] 

y = Monoid.[BS."hello", "world"]

z :: Bool
z = and Bits.101001 

a b = S.if b then "hello" else 42 

x :: NP I [Int, Bool, String
x = NP.[3, I False, "what"]

A non-exhaustive list of syntax this would effect

  • numeral syntax (fromInteger, fromRational)
  • string syntax
  • list syntax
  • negate
  • conditionals (if then else)
  • do (everything included into -XQualifiedDo today
  • arrow syntax
  • overloaded labels

More patholical syntaxes:

  • overloaded record dot
  • overloaded record update (I’m not sure about this actually, maybe it’s fine)

What do you think about this?

8 Likes

Not the first time I see this idea popping up, and it definitely could work. I can see using it to construct heterogeneous lists as H.[42, True, f] instead of 42 :& True :& f :& HNil

4 Likes

I think I’d usually prefer setting the default interpretation for a whole module at once (or even multiple modules). You can kind of already do that with RebindableSyntax and ExtendedDefaultRules, however then you still can’t do things like @int-index’s example of using list syntax for heterogeneous lists.

The overloaded plugin can do heterogeneous lists (see the instance for NP) by desugaring to typeclasses in a more flexible way than OverloadedLists + RebindableSyntax.