This is the mail archive of the kawa@sourceware.org mailing list for the Kawa project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: Kawa type-conversions


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




Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]