Exposed # kinded variables + polykinded Prelude classes?

Zemyla zemyla at gmail.com
Sun Oct 27 13:47:09 UTC 2019


I'm wondering if there would be a benefit, if not to the average
programmer, then to the ones working on deeper/faster code, to allow some
of the # kinded types (mostly Int#, Word#, Char#, Float#, Double#) to be
used in Safe code, and to have typeclasses able to work with them.

For instance, the definition of Show would become:

class Show (a :: TYPE r) where
  show :: a -> String
  default show :: (r ~ 'LiftedRep) => a -> String
  show x = showsPrec 0 x ""

  showsPrec :: Int -> a -> ShowS
  default showsPrec :: (r ~ 'LiftedRep) => Int -> a -> ShowS
  showsPrec _ x s = show x ++ s

  showList :: (r ~ 'LiftedRep) => [a] -> ShowS
  showList ls s = showList__ shows ls s

The fact that the defaults only work when the type is a LiftedRep is a
nonissue, because there's only a finite number of non-lifted types we'd be
defining it for.

You could do the same with Eq, Ord, Num, Real, Integral, Fractional,
Floating, RealFrac, RealFloat, Semigroup, Monoid, Bits, FiniteBits, and
probably several others I can't think of right now. However, with the
functions that return pairs, you'd need a version that returns an unboxed
pair instead. Assuming you changed ReadPrec, you could even do the same
with Read:

newtype ReadP (a :: RuntimeRep r) = ReadP (forall b. (a -> R b) -> R b)
newtype ReadPrec (a :: RuntimeRep r) = ReadPrec (Int -> ReadP a)

IO, ST, and STM could be made polykinded the same way, and would open up
Storable. However, how to do a definition for Monad that works for
polykinded monads is an issue. I do know that RebindableSyntax handles it
easily when there's just one monad that can operate on multiple kinds,
though.

As for which # types could be exposed, I'm thinking that Char#, Int#,
Word#, Float#, Double#, and Proxy# wouldn't be able to break out of Safe
code. Int64# and Word64# would work as well, and for 64-bit machines would
just be type aliases for Int# and Word# respectively. For types which have
functions with undefined behavior for some arguments, you can just make
wrappers that check the arguments and error out for the bad values.
MutVar#, MVar#, TVar#, and StableName# don't open up any functions that
would be unsuitable for safe code, either. I'm pretty sure that Array# and
MutableArray# would also be safe, as long as all functions were
length-checked and threw errors instead of having undefined behavior.

As for why this would be a desirable thing? Mostly for the sake of
convenience and generality, I think. I find myself working with unboxed
values from time to time, and it's a pain to always remember to use (+#)
for Int# and plusWord# for Word#. In addition, having typeclasses that can
return unboxed values (like a hypothetical sized# :: Sized a => a -> Int#
vs sized :: Sized a => a -> Int) can improve the generated code when the
code using the typeclass doesn't get specialized.

The module to import these would have to explain the differences between #
kinded types and * kinded ones: # values aren't lazy; they can't be
top-level definitions; you can't use unboxed tuples or sums with GHCi; and
with a few exceptions, you can't place them in containers (you can't have
an [Int#], for instance).
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/libraries/attachments/20191027/00612299/attachment.html>


More information about the Libraries mailing list