[Haskell-cafe] typeclasses considered unnecessary/the power of the @

Anthony Clayden anthony_clayden at clear.net.nz
Wed May 16 02:09:06 UTC 2018

Haskell 98/2010 keeps a strict distinction between term-level syntax and
tokens vs type-level.

With ExplicitTypeApplications that's being eased: there's a lightweight way
to sneak a type expression into a term. And ghc's internal term language
'core' has had explicit type applications for many years.

Then consider a lightweight syntax using type applications in patterns to
define typeclass instances by (pattern) 'matching' on types:

> (==) @Int x y = primEqInt x y         -- or eta-reduced
> (==) @Int     = primEqInt             -- shorthand for
> instance Eq Int where
>   (==) = primEqInt

Given that many typeclasses have a single method (or one base method with
others defined in terms of it) [Note **], having to invent a typeclass name
is a bit of a pain. Here's a lighweight method decl:

> meth :: @a @b. => a -> b -> Bool      -- typevar binding to left of =>
(there might also be superclass constraints)
>                                       -- shorthand for
> class Classfor_meth a b where         -- generated class name
>   meth :: a -> b -> Bool

Do we need class names at all? We could sneak term-level names into types.
Just use the method name in constraints (with some handy syntactic marking
-- what could be better than prefix @):

> meth2 :: (@meth a b, @show b) => a -> b -> String
> meth2 x y = if (meth x y) then show y else "blah"

Of course we can infer the constraints on `meth2`, given its equation.

Note **:  for some of the Prelude's classes with many methods, like Num,
there's long-standing complaints that the methods are too much tangled up
and should be refactored into a hierarchy with a single method in each
class: Additive, Multiplicative, Divisive, etc.

With type families we can tackle the classic collections class (with
methods empty, insert) as a superclass constraint , er, supermethod method:

> type family Elem c :: *
> insert @c. => c -> Elem c -> c
> empty :: @c. (@insert c) => c         -- entangle empty with insert

Likewise for Monad:

> (>>=) :: @m. => (m a) -> (a -> m b) -> (m b)
> return :: @m. (@>>= m) => a -> m a

The soon-to-arrive Quantified Constraints extension will provide further
ways to entangle methods/constraints. That might be an alternative way to
express collections:

> insert @c @e. (e ~ Elem c) => c -> e -> c
> empty @c. (forall e. @insert c e => @empty c) => c

Of course all the above syntax is speculative/bikesheddable. The syntax
needs to differentiate tyvars that are parameters/overloadable to the
class/method vs parametric free tyvars. With explicit forall:

> (>>=) :: forall m a b. @m. => (m a) -> (a -> m b) -> (m b)
> return :: forall m a. @m. (@>>= m) => a -> m a

Or perhaps method signatures should look more like methods-as-constraints
-- at cost of a bit of repetition

> (>>=) :: forall m a b. (@>>= m) => (m a) -> (a -> m b) -> (m b)
> return :: forall m a. (@return m, @>>= m) => a -> m a

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20180516/1621c83a/attachment.html>

More information about the Haskell-Cafe mailing list