Hi,

While currying is inherited from nice academic languages such as Haskell and 
OCaml, they have never gotten much traction in systems languages. I believe the 
reason is that there’s a distaste for “magic”, in other words – transformations 
of code that leads to unknown amounts of extra assembly instructions and/or 
memory allocations. For instance, in my field – it is okay to allocate memory, 
but if you do it takes quite a bit of setup (you need to specify alignment, 
which memory pool it is in, etc.).

I can see why curried syntax is nice in a theoretical sense (and admittedly 
also some practical sense), but as you have observed, it has real associated 
costs. My opinion is that in a systems programming language, that you should 
not support currying syntax. For instance, closures needs memory allocation 
(whether it is stack or heap), and therefore the language needs to have hooks 
in place so that the developer can specify where the allocation should take 
place. If given `add : int -> int -> int`, and you write a term `add x` and 
nothing more, you have left out some important information - where do you store 
the captured x?  Even if it was a constant, where do you store `add 1`? Before 
you say “the stack”, remember – the stack isn’t free either, especially in 
systems with thousands of light-weight threads.

My 2 cents? Just say no to currying!

Thanks,

PKE

From: bitc-dev [mailto:[email protected]] On Behalf Of Jonathan S. 
Shapiro
Sent: Thursday, February 12, 2015 10:33 AM
To: Discussions about the BitC language
Subject: Re: [bitc-dev] current status of currying

On Thu, Feb 12, 2015 at 8:08 AM, Keean Schupke 
<[email protected]<mailto:[email protected]>> wrote:

On 12 Feb 2015 15:54, "Jonathan S. Shapiro" 
<[email protected]<mailto:[email protected]>> wrote:
Primitive functions use tuples for their arity like:

primitive_add_int32 :: (int32, int32) -> int32

These get wrapped into an overloaded type class, but we can omit that detail 
and  have simply:

(+) a b = primitive_add (a, b)

I think that the root of our disconnect is a little clearer now. What you 
suggest above is very similar to what F# does, and it is certainly workable. It 
has the very real advantage that arity is being explicitly encoded in the tuple 
syntax, which is also a simplifying thing.

One effect of this in F# is that programmers seem to get divided into two 
camps. One camp comes from the functional programming world. That camp is 
comfortable with the curried application syntax, and will use it 
preferentially. The other camp comes from the world of C. That camp will also 
gravitate toward what is familiar, which is to say the tuple syntax. The two 
groups will tend to build libraries in mutually incompatible style, which 
raises a concern that the user base will ultimately bifurcate.

One purpose of the present discussion about arity-aware function types was to 
see if we could satisfy the needs of both groups with a single surface syntax.

Just incidentally, the tuple approach violates your view that types and 
implementation should be kept separate. While the tuple remains a product type 
here, the rationale for this design choice is that the compiler is going to 
treat "function accepting tuple" as a distinct implementation case. And note, 
*not* "function being *passed* a tuple. that is:

   f( : 'a -> 'b) (1, 'c')

is not called using the native calling convention merely because the argument 
type is a tuple, but

  (g : ('a, 'b) -> 'c) (1, 'c')

IS called using the native convention.[*]

  [*] Another option is to decide the calling convention after specialization,
     in which case f actually *would* get called using the native convention.

Anyway, the purpose of the current discussion was to see if we might be able to 
successfully cary arity *explicitly* on the function type without breaking the 
type system. Given that we lose most general types in the process, I'm 
concluding that the answer is "no", and that we will probably need to adopt the 
tuple story.

I don't see any reason the compiler to not provide the curried versions 
automatically.

I'm not entirely sure what you mean by this. Do you mean that given

  def f (x, y) = ...

the compiler will automatically know what to do with an expression

  f 1

? That would be equivalent to inserting an automatic coercion, which will play 
havoc with type inference.

Do you instead mean that in your example above the + method is curried while 
the primitive_add function is not?

Do you perhaps mean a third thing?


The compiler uncurrying pass knows the original primitive type (or imported 
function type) and it would be a compile error if we cannot sufficiently 
uncurry.
If we proceed on the assumption that uncurrying never increases allocations 
(which may not be right), then uncurrying can only make things better. But the 
problem here is the adoption of curried syntax in the first place. The problem 
with curried syntax is that it places the programmer in the habit of preferring 
an idiom that (by default) *inserts* non-obvious allocations and then applying 
best-effort to get rid of them again. Both parts of that statement are bad for 
systems codes.


shap
_______________________________________________
bitc-dev mailing list
[email protected]
http://www.coyotos.org/mailman/listinfo/bitc-dev

Reply via email to