In implementing new kinds of numbers, I've found it difficult to know just
how many functions I need to implement for the general library to "just
work" on them. Take as an example a byte-swapped, e.g. big-endian, integer.
This is handy when doing memory-mapped I/O on a file with data written in
network order. It would be nice to just implement, say, Int32BigEndian and
have it act like a real number. (Then I could just reinterpret a mmaped
array and work directly off it) In general, we'd convert to Int32 at the
earliest opportunity we had. For instance the following macro introduces a
new type which claims to be derived from $base_type, and implements
conversions and promotion rules to get it into a native form ($n_type)
whenever it's used.
macro encoded_bitstype(name, base_type, bits_type, n_type, to_n, from_n)
quote
immutable $name <: $base_type
bits::$bits_type
end
Base.bits(x::$name) = bits(x.bits)
Base.bswap(x::$name) = $name(bswap(x.bits))
Base.convert(::Type{$n_type}, x::$name) = $to_n(x.bits)
Base.convert(::Type{$name}, x::$n_type) = $name($from_n(x))
Base.promote_rule(::Type{$name}, ::Type{$n_type}) = $n_type
Base.promote_rule(::Type{$name}, ::Type{$base_type}) = $n_type
end
end
I can use it like this
@encoded_bitstype(Int32BigEndian, Signed, Int32, Int32, bswap, bswap)
But unfortunately, it doesn't work out of the box because the conversions
need to be explicit. I noticed that many of the math functions promote
their arguments to a common type, but the following trick doesn't work,
presumably because the promotion algorithm doesn't ask to promote types
that are already identical.
Base.promote_rule(::Type{$name}, ::Type{$name}) = $n_type
It seems like there are a couple of issues this raises, and I know I've
seen similar questions on this list as people implement new kinds of
things, e.g. exotic matrices.
1. One possibility would be to allow an implicit promotion, perhaps
expressed as the self-promotion above. I say I'm a Int32BigEndian, or
CompressedVector, or what have you, and provide a way to turn me into an
Int32 or Vector implicitly to take advantage of all the functions already
written on those types. I'm not sure this is a great option for the
language since it's been explicitly avoided elsewhere. but I'm curious if
there have been any discussions in this direction
2. If instead I want to say "this new type acts like an Integer", there's
no canonical place for me to find out what all the functions are I need to
implement. Ultimately, these are like Haskell's typeclasses, Ord, Eq, etc.
By trial and error, we can determine many of them and implement them this
way
macro as_number(name, n_type)
quote
global +(x::$name, y::$name) = +(convert($n_type, x),
convert($n_type, y))
global *(x::$name, y::$name) = *(convert($n_type, x),
convert($n_type, y))
global -(x::$name, y::$name) = -(convert($n_type, x),
convert($n_type, y))
global -(x::$name) = -convert($n_type, x)
global /(x::$name, y::$name) = /(convert($n_type, x),
convert($n_type, y))
global ^(x::$name, y::$name) = ^(convert($n_type, x),
convert($n_type, y))
global ==(x::$name, y::$name) = (==)(convert($n_type, x),
convert($n_type, y))
global < (x::$name, y::$name) = (< )(convert($n_type, x),
convert($n_type, y))
Base.flipsign(x::$name, y::$name) = Base.flipsign(convert($n_type,
x), convert($n_type, y))
end
end
But I don't know if I've found them all, and my guesses may well change as
implementation details inside the base library change. Gradual typing is
great, but with such a powerful base library already in place, it would be
good to have a facility to know which functions are associated with which
named behaviors.
Since we already have abstract classes in place, e.g. Signed, Number, etc.,
it would be natural to extract a list of functions which operate on them,
or, even better, allow the type declarer to specify which functions
*should* operate on that abstract class, typeclass or interface style?
Are there any recommendations in place, or updates to the language planned,
to address these sorts of topics?