This is the mail archive of the
kawa@sourceware.org
mailing list for the Kawa project.
Re: Kawa type-conversions
- From: Jamison Hope <jrh at theptrgroup dot com>
- To: Kawa mailing list <kawa at sourceware dot org>
- Date: Fri, 20 Mar 2015 20:05:42 -0400
- Subject: Re: Kawa type-conversions
- Authentication-results: sourceware.org; auth=none
- References: <550C7D9C dot 7030102 at bothner dot com>
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