In Levity Polymorphism a principled way to represent unboxed strict is introduced and personally, I think it’s brilliant. Levity polymorphism is something I’ve deeply integrated into Aith, my programming language. However, I’m now of the opinion that only the representation polymorphism fragment is ideal and that levity itself (whether a term is strict or not) should be on the arrow similar to linear types.
For any given type there’s one to natural way to represent it’s values. For example: for Int#
, it’s values are represented with machine words, while for [a]
it’s values are represented with a pointer. However, how values are represented doesn’t give the full picture of how they should be passed to functions. You could easily imagine a scenario where you have an unboxed value you want to pass lazily or a boxed value you want to pass strictly. So there’s no natural way to represent how a type should be passed to functions, so using kinds for (as is done) in levity polymorphism is less then ideal.
Lets take this example function:
multiply :: Int# -> Int# -> Int#
multiply 0# _ = 0#
multiply x y = x *# y
Since the there are cases where the second argument isn’t used, it’s plausible to want to pass it in with call by need or even call by name.
multiply' :: Int# -> Int -> Int#
mulitply' 0# _ = 0#
multiply' x (I# y) = x *# y
multiply'' :: Int# -> (() -> Int#) -> Int#
multiply'' 0# _ = 0#
multiply'' x y = x *# y()
It would be nice if we could say that the second argument is a lazy but with values that are machine ints while the first argument a normal plain strict machine int argument.
Lets imagine that function arrows are parameterized by what they want the strictness of their arguments to be. Where ~σ -> τ
is a normal haskell function and !σ -> τ
is a function arrow that expects it’s argument to be in whnf. The example can now be written as:
multiply''' :: !Int# -> ~Int# -> Int#
multiply''' 0# _ = 0#
multiply''' x y = x *# y
This definition would effectively be isomorphic to multiply'
.
I can imagine some {-# Language StrictTypes #-}
extension that allows you parameterize functions with strictness in a similar manner that {-# Language LinearTypes #-}
allow you to parameterize functions with linearity. Putting aside representation polymorphism for a minute, such an extension would be very useful. It would bring some much need ability to reason about strictness in the type system.
Lastly, there’s likely some theory behind this. In Call-by-name, call-by-value, call-by-need, and the linear lambda calculus it’s explained that there’s two different ways to embed unrestricted types into linear types. One corresponding to call by name and one corresponding to call by value. If you squint at the call by name encoding, it corresponds very closely to how you would embed lazyness into a strict language.
for completeness here’s a similar translation of the by value encoding:
In some sense, the by name encoding corresponds to parameterizing arrows while the by value encoding corresponds to parameterizing the kinds. Since GHC already has the by name encoding of linear types, why not go with the by name encoding of lazyness too?