Haskell's parametric polymorphism is based on the fact that all values of types t :: * uniformly represented as a pointer to a run-time object. Thus, the same machine code works for all instances of polymorphic values.
Contrast polymorphic functions in Rust or C ++. For example, the authentication function still has a type similar to forall a. a -> a forall a. a -> a , but since values of different types a can have different sizes, compilers must generate different codes for each stat. It also means that we cannot pass polymorphic functions at runtime:
data Id = Id (forall a. a -> a)
since such a function should work correctly for objects of arbitrary size. This function requires some additional infrastructure, for example, we might require the forall a. a -> a runtime function forall a. a -> a forall a. a -> a took additional implicit arguments that carry information about the size and constructors / destructors of the values of a .
Now the problem with newtype Vec = Vec (# Float#, Float# #) is that even if Vec has a good * , the runtime code that expects a value of some t :: * cannot process it. This is a pair of floats allocated by the stacks, not a pointer to a Haskell object, and passing it to code that expects Haskell objects will result in segfaults or errors.
In the general case (# a, b #) , the size of the pointer is not necessary, so we cannot copy it to the data fields of the size of the pointer.
Type types returning # types are not allowed for appropriate reasons. Consider the following:
type family Foo (a :: *) ::
Our Box does not represent runtime since Foo a has different sizes for different a -s. As a rule, polymorphism over # requires the generation of different code for different instances, for example, in Rust, but this is poorly related to regular parametric polymorphism and makes it difficult to represent polymorphic values at run time, so the GHC does not worry about any of this.
(Not to mention that it is impossible to use a useful implementation)