Here are a few simple functions:
f1 :: () -> ()
f1 () = ()
f2 :: a -> a
f2 a = a
f3 :: a -> (a, a)
f3 a = (a, a)
f4 :: (a, b) -> a
f4 (a, b) = a
All of f1
, f2
, and f3
are able to accept ()
as an input. On the other hand, of course, f4
can't accept ()
; f4 ()
is a type error.
Is it possible to type-theoretically characterize what f1
, f2
, and f3
have in common? Specifically, is it possible to define an acceptsUnit
function, such that acceptsUnit f1
, acceptsUnit f2
, and acceptsUnit f3
are well-typed, but acceptsUnit f4
is a type error -- and which has no other effect?
The following does part of the job, but monomorphizes its input (in Haskell, and I gather in Hindley-Milner), and hence has an effect beyond simply asserting that its input can accept ()
:
acceptsUnit :: (() -> a) -> (() -> a)
acceptsUnit = id
-- acceptsUnit f4 ~> error
-- acceptsUnit f3 'a' ~> error ☹️
The same monomorphizing, of course, happens in the following. In this case, the annotated type of acceptsUnit'
is its principal type.
acceptsUnit' :: (() -> a) -> (() -> a)
acceptsUnit' f = let x = f () in f