[bitc-dev] Opinions wanted: Infix shift operators
wren ng thornton
wren at freegeek.org
Sat Aug 7 17:22:23 PDT 2010
Jonathan S. Shapiro wrote:
> On Fri, Aug 6, 2010 at 11:33 AM, wren ng thornton <wren at freegeek.org> wrote:
>
>> ... GHC has shown that compilers can be smart enough to
>> automatically uncurry the implementations when appropriate...
>
> I have seen this same argument used to explain why unboxing should not be
> part of the language semantics. The problem is: this argument is only sound
> when uncurrying is elective.
I would only apply the argument to unboxing when it's elective. When
it's non-elective, the question remains: how much of the un/boxing
boilerplate should be forced upon the programmer? Given type signatures
that specify the boxyness of values, having the compiler infer where to
insert un/boxing coercions is perfectly reasonable IMO.
> When the uncurrying is mandatory (that is:
> prescriptive), it is an *error* for the compiler to *ever* emit the
> uncurried form. When this is the case, the two types
>
> int -> int -> int
> int X int -> int
No, it's not an error, because it's trivial to define the higher-order
functions necessary to coerce between them; if functions can return
functions, then any time you define one of those functions the other is
guaranteed to be definable. Certainly, we should not directly link the
code of one to a call-site of the other (because that's a type error),
but there is absolutely nothing prohibiting an indirect link via
conversion functions.
I get the feeling we're talking past one another here. I've never
claimed that the curried and uncurried types are identical. The only
claim I've made (and I only made it in the previous paragraph) is that
the types are isomorphic, and therefore there exist trivial translations
between them.
It's perfectly fine for the FFI to use uncurried types in order to
support direct linking against foreign code. But arity is no more a
special part of the type here than it is for the curried form. You're
just passing in a tuple instead of passing arguments individually; the
number of arguments passed is the same regardless. And you always have
all the arguments by the time the function is executed (unless you plan
to support partial evaluation).
> Concerning, syntax, I would actually prefer the currying-style of syntax
> myself. But I think it's worth noting that there is a failure of parallelism
> in the Haskell syntax design: functions do not have arity as part of type,
> but type constructors and value constructors *do* have arity. This is
> actually fairly strange when you think about it.
That's not true. Constructor functions behave the same way as other
functions. Partial application is just fine. You cannot pattern match on
partially applied constructors, but you cannot pattern match on any
other functions either.
The only situation where Haskell forbids partial application is with
type aliases, because they are viewed in the Haskell98 report as macros
rather than as type-level lambdas. GHC has an extension which relaxes
this one exception.
> OTOH, the fact that it does work suggests that perhaps the problem lies in
> the arity-oblivious specification of function types rather than in the
> surface syntax of application.
It works because typed lambda calculi are in correspondence with
Cartesian closed categories (via Curry--Howard--Lambek), and CCCs have
natural transformations ensuring that curried and uncurried versions of
all functions exist.
> More seriously, though, it was starting to look like there might be contexts
> in which both the type parameters and the actual parameters might be needed
> in certain function calls. If that turns out to be the case, you want the
> respective bracketing syntax to be different. It would be better, of course,
> to avoid this entirely.
All the dependently typed languages I'm familiar with use whitespace for
type-level application without problems. You just have to generalize
arrows to pi-types and products to sigma-types:
A -> B ==> (a:A) -> B where a takes scope over B
A * B ==> (a:A) * B where a takes scope over B
For deconstructing type terms you'd need a form of case analysis at the
type level, since types can have multiple data constructors. E.g.,
(x : A+B) -> case x of { Left a -> a ; Right b -> b }
And you'll have to beef up value-level case analysis in order to handle
the local type equality constraints induced by analyzing the scrutinee.
The best manner for doing this is still somewhat open for research.
Coq's approach is far too weak; Agda's is the current state of the art.
>> Even if you offer curried type constructors, there's no efficiency tradeoff
>> between using curried vs uncurried, because the types are erased at
>> runtime.
Again, talking past one another. What I meant was, if we have the code
f : C<A,B> -> D
f = \cab -> ...
vs if we have the code
f' : C' A B -> D
f' = \cab -> ...
Then there is no performance difference between them, because the
type-level application C<A,B> vs (C' A B) will be erased by runtime.
Thus, there is no performance reason for preferring (C : Type*Type ->
Type) over (C' : Type -> Type -> Type).
The only time there could be any performance difference in how we decide
to name the types is if there's something prohibiting full type erasure.
(The usual thing prohibiting full erasure is dynamic reflection, but by
that point you've already given up on any hopes of performance, so the
argument is moot.)
> But before I get into this any deeper, let me go read the G-machine paper
> again.
Type-level evaluation isn't really discussed there. Type-level
evaluation only becomes interesting once you've moved to full dependent
types or to staged computation.
>> Moreover, because types are erased, there are no constraints
>> from native calling conventions; we can call native types whatever we
>> like.
>
> That's the problem! There *need* to be constraints imposed from the native
> calling conventions!
Whether we choose to call a type C<A,B> vs (C A B) should not have any
impact whatsoever on native calling conventions. The name, and our
choice of that name, is erased before we get to the point where native
calling exists.
--
Live well,
~wren
More information about the bitc-dev
mailing list