* Kawa type-conversions @ 2015-03-20 20:06 Per Bothner 2015-03-21 0:05 ` Jamison Hope 0 siblings, 1 reply; 4+ messages in thread From: Per Bothner @ 2015-03-20 20:06 UTC (permalink / raw) To: Kawa mailing list [Some thoughts about Kawa type-checking and type-conversions. I don't know when any of this will be implemented, but this is what I'm currently thinking makes most sense - though some details would be need to be worked out in practice. Maybe we can think of this as a sketch for a Kawa Enhancement Proposal.] Right now Kawa has multiple syntaxes for type conversion: (as TYPE VALUE) (->TYPE VALUE) (define VAR ::TYPE VALUE) ;; and other variants using type specifiers These mostly allow the same set of conversions, but using 'as' is more "lenient" than '->TYPE' in that it allows also conversions of the the raw types are the same. (This is made use of in converting between the tring-cursor type and int, for example.) We also have type tests: (? VAR ::TYPE VALUE) ;; generalizable to patterns: (? PATTERN VALUE) (instance? VALUE TYPE) These aren't really consistent: (if (? x::int 3.4) x #f) ==> 3 (instance? 3.4 int) ==> 3 At some point I'd like to experiment with a --strict-typing option, so it's desirable to come up consistent and useful model. So what I'm thinking we shoudl aim for: * (as TYPE VALUE) would be short-hand for (let ((TMP ::TYPE VALUE)) TMP) and so the set of allowable conversions would be the same. * Using 'as' or a type-specifier would invoke an *implicit* conversion. Using (->TYPE VALUE) would be an *explicit* conversion, and would allow more conversions. * As a general rule implicit conversion of VALUE to TYPE should be OK if (instance? VALUE TYPE) or if it's a loss-less conversion to semantically more-or-less the same type. We may need to fine-tune this. * As a general rule (? VAR::TYPE VALUE) should succeed if (as TYPE VALUE) doesn't throw an exception. We may need to fine-tune this. * It seems reasonable to introduce: (convert TYPE VALUE) as equivalent to: (->TYPE VALUE) * As an example (->list SEQUENCE) would convert any SEQUENCE value to a list. It would be equivalent to (list @SEQUENCE) except it wouldn't necessarily make a fresh copy if the SEQUENCE is a list. (->list #(3 4 5)) ==> (3 4 5) (as list #(3 4 5)) ==> ERROR (define x::list #(3 4 5)) ==> ERROR * Assume we a new type sequence, optionally parameterized as sequence[ELEMENT-TYPE]. This would match any type allowed for splicing or the new generalized map, including strings and native Java arrays. Since all of these types are "instances" of the sequence types, all of these types can be *implicitly* converted to sequence - even if we have to allocate a wrapper object. In general, special types that aren't just native Java types might require custom conversion methods - and may need different methods for implicit and explicit conversions. * As an incompatible change, converting float to int should (IMO) no longer be allowed for implicit conversions: (->int 3.4) ==> 3 (as int 3.4) ==> ERROR ;; incompatible change (define x::int 3.4) ==> ERROR ;; incompatible change * In general static type-checking wouldn't change much from today, except for some corner cases. For example assigning a double to an int using be dis-allowed if using an implicit conversion but allowed if using an explicit conversion. * If we implement strict type-checking, it would be only be enabled by a flag like --strict-typing. In that case implicit conversions change so that the *type* of the VALUE expression needs to be a sub-type of the required TYPE. For example example you can't convert an expression of type java.util.List to a required type java.util.ArrayList. This would be ok with non-strict type-checking as long as the expression type and the required type have a non-empty intersection. Explicit conversion should work the same with strict and non-strict type-checking: It would be allowed as long as the types have a non-empty intersection. * If we implement strict type-checking, we'd also define a new 'dynamic' type, similar to C#. A expression that has type dynamic is allowed at compile-time to be converted to any type; of course you might get a run-time exception. The default type of a variable would no type-specifier should probably be 'dynamic'. Of course the implementation type for dynamic is java.lang.Object; the only difference is static type-checking. This would actually allow *more* programs to type-check without warnings than today, which may or may not be desirable. -- --Per Bothner per@bothner.com http://per.bothner.com/ ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: Kawa type-conversions 2015-03-20 20:06 Kawa type-conversions Per Bothner @ 2015-03-21 0:05 ` Jamison Hope 2015-03-28 2:24 ` Per Bothner 0 siblings, 1 reply; 4+ messages in thread From: Jamison Hope @ 2015-03-21 0:05 UTC (permalink / raw) To: Kawa mailing list Hi Per, On Mar 20, 2015, at 4:05 PM, Per Bothner <per@bothner.com> wrote: > [Some thoughts about Kawa type-checking and type-conversions. > I don't know when any of this will be implemented, but this is > what I'm currently thinking makes most sense - though some details > would be need to be worked out in practice. Maybe we can think > of this as a sketch for a Kawa Enhancement Proposal.] > > Right now Kawa has multiple syntaxes for type conversion: > > (as TYPE VALUE) > > (->TYPE VALUE) > > (define VAR ::TYPE VALUE) ;; and other variants using type specifiers > > These mostly allow the same set of conversions, but using 'as' is more > "lenient" than '->TYPE' in that it allows also conversions of the the > raw types are the same. (This is made use of in converting between the > tring-cursor type and int, for example.) > > We also have type tests: > > (? VAR ::TYPE VALUE) ;; generalizable to patterns: (? PATTERN VALUE) > > (instance? VALUE TYPE) > > These aren't really consistent: > > (if (? x::int 3.4) x #f) ==> 3 > (instance? 3.4 int) ==> 3 I think you meant "==> #f" for the second one (otherwise they seem pretty consistent…). > At some point I'd like to experiment with a --strict-typing option, > so it's desirable to come up consistent and useful model. > > So what I'm thinking we shoudl aim for: > > * (as TYPE VALUE) would be short-hand for > (let ((TMP ::TYPE VALUE)) TMP) > and so the set of allowable conversions would be the same. > > * Using 'as' or a type-specifier would invoke an *implicit* > conversion. Using (->TYPE VALUE) would be an *explicit* conversion, > and would allow more conversions. > > * As a general rule implicit conversion of VALUE to TYPE should be > OK if (instance? VALUE TYPE) or if it's a loss-less conversion to > semantically more-or-less the same type. We may need to fine-tune this. > > * As a general rule (? VAR::TYPE VALUE) should succeed if > (as TYPE VALUE) doesn't throw an exception. We may need to fine-tune this. > > * It seems reasonable to introduce: > (convert TYPE VALUE) > as equivalent to: > (->TYPE VALUE) > > * As an example (->list SEQUENCE) would convert any SEQUENCE value > to a list. It would be equivalent to (list @SEQUENCE) except it > wouldn't necessarily make a fresh copy if the SEQUENCE is a list. > > (->list #(3 4 5)) ==> (3 4 5) > (as list #(3 4 5)) ==> ERROR > (define x::list #(3 4 5)) ==> ERROR > > * Assume we a new type sequence, optionally parameterized as > sequence[ELEMENT-TYPE]. This would match any type allowed > for splicing or the new generalized map, including strings > and native Java arrays. Since all of these types are "instances" > of the sequence types, all of these types can be *implicitly* > converted to sequence - even if we have to allocate a wrapper > object. > > In general, special types that aren't just native Java types > might require custom conversion methods - and may need different > methods for implicit and explicit conversions. > > * As an incompatible change, converting float to int should (IMO) no > longer be allowed for implicit conversions: > > (->int 3.4) ==> 3 > (as int 3.4) ==> ERROR ;; incompatible change > (define x::int 3.4) ==> ERROR ;; incompatible change Agreed, those last two just look weird. But what about (as int 3.0)? In other words, is it an error to try to coerce *any* floating point number to an int, or just when it isn't already integer-valued? (in the R6RS sense). We already have `exact', so I don't have strong feelings either way, as long as (as int (exact 3.0)) works. > * In general static type-checking wouldn't change much from today, > except for some corner cases. For example assigning a double to an int > using be dis-allowed if using an implicit conversion but allowed if > using an explicit conversion. > > * If we implement strict type-checking, it would be only be enabled > by a flag like --strict-typing. In that case implicit conversions > change so that the *type* of the VALUE expression needs to be a sub-type > of the required TYPE. For example example you can't convert an > expression of type java.util.List to a required type java.util.ArrayList. > This would be ok with non-strict type-checking as long as the expression > type and the required type have a non-empty intersection. > > Explicit conversion should work the same with strict and non-strict type-checking: > It would be allowed as long as the types have a non-empty intersection. > > * If we implement strict type-checking, we'd also define a new 'dynamic' type, > similar to C#. A expression that has type dynamic is allowed at compile-time > to be converted to any type; of course you might get a run-time exception. > > The default type of a variable would no type-specifier should probably be > 'dynamic'. Of course the implementation type for dynamic is java.lang.Object; > the only difference is static type-checking. This would actually allow *more* > programs to type-check without warnings than today, which may or may not > be desirable. Here's one other feature I would like to see, which I *think* fits in with your description of (convert TYPE VALUE) and (->TYPE VALUE). I would like a way to extend the system with custom conversion functions for particular from/to type pairs. This would be a big help when interacting with different Java libraries. For example, AWT provides a class representing a two-dimensional point with coordinates stored as doubles, namely java.awt.geom.Point2D.Double. Vecmath also provides a 2D point with double-valued coordinates, javax.vecmath.Point2d. And Kawa, of course, has complex numbers, which are 2D points in the complex plane. It would be super handy if I could write a couple of functions: (define (javax.vecmath.Point2d->complex p::javax.vecmath.Point2d) ::complex (make-rectangular p:x p:y)) (define (java.awt.geom.Point2D->complex p::java.awt.geom.Point2D) ::complex (make-rectangular (p:getX) (p:getY))) and then elsewhere, if I've got a heterogenous mix of points, just use ->complex to turn them all into Kawa complex numbers. So perhaps (->TYPE VALUE) and (convert TYPE VALUE) could work something like this: 1. If VALUE is an instance of TYPE, just return it. 2. If VALUE has a "toTYPE" method, invoke that. 3. If TYPE has a one-argument constructor which works on VALUE, then return new TYPE(VALUE). 4. Otherwise, starting with VALUE.getClass() and working up to java.lang.Object, look for a function "FQCN->TYPE". If one is found, then invoke it. Implemented interfaces should probably be in that search list, too, and I'm not sure what to do about type aliases. I used "xxx->complex" for the function names in that example, but maybe it should be "xxx->gnu.math.Complex". Does that fit with what you're thinking? -- Jamison Hope The PTR Group www.theptrgroup.com ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: Kawa type-conversions 2015-03-21 0:05 ` Jamison Hope @ 2015-03-28 2:24 ` Per Bothner 2015-03-30 16:32 ` Jamison Hope 0 siblings, 1 reply; 4+ messages in thread From: Per Bothner @ 2015-03-28 2:24 UTC (permalink / raw) To: kawa On 03/20/2015 05:05 PM, Jamison Hope wrote: > >> These aren't really consistent: >> >> (if (? x::int 3.4) x #f) ==> 3 >> (instance? 3.4 int) ==> 3 > > I think you meant "==> #f" for the second one (otherwise they seem > pretty consistentÂ…). Right. (instance? 3.4 int) returns #f - which is fine, but (if (? x::int 3.4) x #f) currently returns 3 but should return #f. >> * As an incompatible change, converting float to int should (IMO) no >> longer be allowed for implicit conversions: >> >> (->int 3.4) ==> 3 >> (as int 3.4) ==> ERROR ;; incompatible change >> (define x::int 3.4) ==> ERROR ;; incompatible change > > Agreed, those last two just look weird. But what about (as int 3.0)? > In other words, is it an error to try to coerce *any* floating point > number to an int, or just when it isn't already integer-valued? (in > the R6RS sense). > > We already have `exact', so I don't have strong feelings either way, > as long as (as int (exact 3.0)) works. Likewise, I don't feel strongly about it, but it's easier to implement if (as int 3.0) is an ERROR. One important reason: If instead of a constant you have an inexact-valued expression you want to complain at compile-time. So I think a reasonable specification for (as int XXX) is that it works if and only if XXX has an exact integral type. (It might be reasonable to do range-checking, at run-time if needed.) > Here's one other feature I would like to see, which I *think* fits in with > your description of (convert TYPE VALUE) and (->TYPE VALUE). > > I would like a way to extend the system with custom conversion functions > for particular from/to type pairs. This would be a big help when > interacting with different Java libraries. I agree that could be useful. Of course, if we're distinguishing between explicit and implicit conversions we need to specify which custom conversions are explicit and which are implicit. You discuss an extra search mechanism (convention) for explicit conversion, but I don't understand how that so much more convenient than just re-defining the ->TYPE function. Customization of *implicit* conversion seems more interesting. Since we want static type-checking, that seems to imply that the custom conversion functions must be visible at compile-time. For example something like: (define-conversion (name::T1)::T2 (convert-T1-to-T2 name)) This would get compiled to a static method: public static T2 $convert_to$T2(T1 name) {convert_T1_to_T2(name); } When needing to do an implicit type conversion, the compiler picks "the most specific applicable" (in the sense of method overload resolution) conversion method and compiles in a call to the corresponding static method. I don't know how practical and safe this is. It could certainly easily be abused. > So perhaps (->TYPE VALUE) and (convert TYPE VALUE) could work > something like this: > > 1. If VALUE is an instance of TYPE, just return it. > > 2. If VALUE has a "toTYPE" method, invoke that. > > 3. If TYPE has a one-argument constructor which works on VALUE, > then return new TYPE(VALUE). Actually, we want to prefer factory methods over constructors. So perhaps: > 3a. If TYPE has a static one-argument method which works on VALUE, > named 'valueOf' then return TYPE.valueOf(VALUE). Likewise for 'make'. > > 4. Otherwise, starting with VALUE.getClass() and working up to > java.lang.Object, look for a function "FQCN->TYPE". If one > is found, then invoke it. > > Implemented interfaces should probably be in that search list, too, > and I'm not sure what to do about type aliases. I used > "xxx->complex" for the function names in that example, but maybe it > should be "xxx->gnu.math.Complex". -- --Per Bothner per@bothner.com http://per.bothner.com/ ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: Kawa type-conversions 2015-03-28 2:24 ` Per Bothner @ 2015-03-30 16:32 ` Jamison Hope 0 siblings, 0 replies; 4+ messages in thread From: Jamison Hope @ 2015-03-30 16:32 UTC (permalink / raw) To: kawa@sourceware.org list On Mar 27, 2015, at 10:23 PM, Per Bothner <per@bothner.com> wrote: > You discuss an extra search mechanism > (convention) for explicit conversion, but I don't understand how > that so much more convenient than just re-defining the ->TYPE function. If all of the source is being compiled together, then perhaps it isn't, except that it avoids having to explicitly group all of the possible conversions together into a big case-lambda or whatever. The search mechanism would make ->TYPE more akin to a CLOS generic function, where the backing methods could be in separate modules. Anyway, my thought was for separately-compiled libraries. For example, suppose I've got a textbook shape class library with a bunch of useful functions to do things like calculate the minimum distances between pairs of shapes (sphere/sphere, prism/cylinder, etc). Everything is working fine, and then the boss says that I need to support JBullet shape primitives. Knowing how these things go (next week it'll be ode4j or some other package instead of JBullet), instead of making a bunch of huge changes to the guts of my library, I change my top-level functions: (define (calculate-distance s1::com.example.shape s2::com.example.shape) ::real ...) becomes (define (calculate-distance o1 o2) ::real (let ((s1 (->com.example.shape o1)) (s2 (->com.example.shape o2))) ...)) I then compile that to a JAR and never touch it again. To support JBullet, I then write a helper library which does the conversion between JBullet shapes and com.example.shape, and then make sure that JAR gets loaded any time I have to support JBullet shapes. Likewise for ode4j and the next third-party library that comes along. If I just redefine ->TYPE, then I end up needing 2^n different re-definitions, for all of the possible combinations of "from" types. In this example, I would need one that just handles JBullet shapes, another which just handles ode4j shapes, a third which handles both, etc. (Again, the problem is the big generic procedure with everything all in one place. That means to compile/load that one function, I would need to load the classes of all types mentioned by that function.) Does all that make sense? Maybe this isn't the job of ->TYPE, maybe it's some other COERCE-like function which does runtime (not compile-time) lookup to enable the kind of modularity I've described? -- Jamison Hope The PTR Group www.theptrgroup.com ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2015-03-30 16:32 UTC | newest] Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2015-03-20 20:06 Kawa type-conversions Per Bothner 2015-03-21 0:05 ` Jamison Hope 2015-03-28 2:24 ` Per Bothner 2015-03-30 16:32 ` Jamison Hope
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).