On 12 February 2015 at 18:32, Jonathan S. Shapiro <[email protected]> wrote:
> On Thu, Feb 12, 2015 at 8:08 AM, Keean Schupke <[email protected]> wrote: > >> On 12 Feb 2015 15:54, "Jonathan S. Shapiro" <[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. > Yes, I think I came to the same conclusion. > > 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. > I see the problem. Tuples exist in most functional languages and I think they are useful. I don't think I would want to get rid of them, so this always leaves the possibility of creating different function signatures. Any arity system would add a third syntax on top of curried and tuples, which might just lead to further confusion and different implementations. > > 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. > Would you get rid of tuples then? I see what you are trying to do, but it seems problematic. What about named arguments (just pass a record). One issue with currying is that it only allows the last argument to be incomplete. With named arguments you can partially apply the arguments in any order: sort { first = collection.begin(), last = collection.end(), comp = (<) } and in the partial application case: sort_from = sort { first = collection.begin(), comp = (<) } in sort_form { last = collection.end() } Probably not what you want, but it does make for readable code, and better partial evaluation (why should arguments only be partially evaluated in application order?) > > 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.[*] > Again I don't necessarily think this has anything to do with native conventions. I think imported functions would get an 'in-language type signature' that matches the way they would be natively applied, but it does not imply any calling convention. I think I would attach a hidden mark to naive functions in the compiler that determines calling convention. We are just using tuples to prevent people calling these functions inefficiently. > > [*] 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 > I mean if you import f (x, y) The compiler might automatically declare a wrapper: f' x y = f (x, y) so the function has some name mechanically derived from the imported function (with a different name). > > ? 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? > That is the kind of thing I was suggesting. > > 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. > I kind of agree, but partial application can improve performance, where we actually start the calculation when we have some data. An example would be hoisting a partially applied function out of a loop, so that we precompute some values before entering the loop. This comes back to stable interfaces at module boundaries, but aggressive AST manipulations are possible inside a module with inlining and other 'whole module' optimisations. My intention would be that heap allocation would be a side-effect, and is controlled by the monadic-effect system. I would also want to support partial type signatures and type constrains. So you could allow a function to infer any type that fits the pattern, so you could constrain the return type to not contain any heap effects without knowing the entire type of the function. Keean.
_______________________________________________ bitc-dev mailing list [email protected] http://www.coyotos.org/mailman/listinfo/bitc-dev
