I've addressed this feature multiple times, but never really got feedback on it,… so I decided to open an issue for it, as I keep coming back to it as a very elegant solution to many problems.
I hope you can answer at least, how feasible this is, what needs to be done for it (so that I can maybe implement it myself, if no one has the time), or explain me why this is a silly idea.
##### What I want:
``` Julia
# Introducing a new type, in a typealias like fashion.
# first syntax idea
hardtypealias RGB{T <: FloatingPoint} NTuple{3, T}
# second syntax idea
immutable Meter{T <: Real} <: T
# third syntax idea
concrete FixedMatrix{T, M, N} <: NTuple{NTuple{M, T}, N} # in constrast to abstract
```
Intended semantic: The first type (e.g. RGB) is a either subtype of the second (e.g NTuple), or has the type Union(RGB, NTuple).
I'm not sure what makes more sense. But it should solve the following problem:
> There is an already existing type and one wants to add functions to it, without adding them to the original type.
This behavior is wanted in a myriad of cases and I've seen workarounds for this situation in many packages (including mine).
### Examples
A few examples, which can be elegantly implemented with this feature, which assume, that NTuple implements common, simd accelerated, vector operations
#### Adding functions to an existing type:
``` Julia
# under the assumption, that NTuple implements common vector operations
immutable RGB{T} <: NTuple{3, T}
# under the assumption, that getfield can be extended
Base.getfield(c::RGB, ::Field{:r}) = c[1]
Base.getfield(c::RGB, ::Field{:g}) = c[2]
Base.getfield(c::RGB, ::Field{:b}) = c[3]
# which gives you the access to RGB which you'd expect from it.
# could also be implemented with a macro: @accessors RGB :r => 1, :g => 2, :b => 3
# So this works:
a = RGB(0.1,0.2,0.3)
a+a # is implemented by NTuple
a.r # implemented by RGB
#Should not work:
(1,2,3).r
```
To further dwell on the point, that we can have a diversity of simd accelerated Vector types, with minimal re-implementation of common functionality, here is another example:
#### Adding Semantic to generic Types
``` Julia
immutable Point{T, N} <: NTuple{N, T}
immutable RGB{T, N} <: NTuple{N, T}
immutable Red{T <: Real} <: T
immutable Green{T <: Real} <: T
immutable Blue{T <: Real} <: T
red = Red(1f0) # -> this will behave exactly like a normal Float32 value
# making it unnecessary, to re-implement all the operations on it
immutable X{T <: Real} <: T
immutable Y{T <: Real} <: T
immutable Z{T <: Real} <: T
Base.getfield(c::RGB, field::Type{Red}) = Red(c[1])
...
Base.getfield(p::Point, field::Type{X}) = X(p[1])
...
Base.getfield(p::Matrix{RGB}, field::Type{Red}) = ... # -> Matrix{Red}
Base.getfield(p::Matrix{Point}, field::Type{X}) = ... # -> Matrix{X}
...
image = [RGB(1f0, 0f0,0f0) for i=1:512, j=1:512]
points = [Point(1f0, 0f0,0f0) for i=1:512, j=1:512]
redchannel = image.Red
zplane = image.Z
```
###### Now the big magic, won by more type information
``` Julia
visualize(image) # -> rgb image display
visualize(points) # -> point cloud
visualize(redchannel) # I can actually infer what the user wants to see: a red intensity image
visualize(zplane) # Again, it's relatively clear what is expected here: a heightfield
```
This would be pretty much the paradise for any visualization library. Also, visual debugging is made a lot easier, as the debugger doesn't need any magic to infer how it needs to visualize some random variable.
But this matters for more than that, as it becomes easier to do the correct thing with any value.
I you want to see something else, for example the pixel of the image in RGB space, you can simply reinterpret the RGB to Point (which has O(1)), which can be useful to make out color clusters.
#### Let function bodies only do, what is implied by their function name
Compare these two implementations:
``` Julia
#First implementation of matrix multiplication
function (*)(a::Matrix, b::Matrix)
@assert size(a, 2) == size(b, 1) #This isn't part of the multiplication code
#matrix multiplication code
end
# This is probably a matter of taste, but I like this implementation more:
FixedSizeArray{SZ, T, N} <: Array{T,N}
Base.call{T,N}(::Type{FixedSizeArray}, x::Array{T,N}) = FixedSizeArray{size(x), T,N}(x)
function (*)(a::Matrix, b::Matrix)
return (FixedSizeArray(a) * FixedSizeArray(b))
end
# Like this, in the function body is only, what is really part of the matrix multiplication:
function (*)(a::FixedMatrix{T, N, M}, b::FixedMatrix{T, M, P})
# code for multiplication of matrices with well suited dimensions
end
function (*)(a::FixedMatrix, b::FixedMatrix)
# code for multiplication of matrices with arbitrary dimensions
# which usually throws an error
end
```
This all is mostly graphic related, which isn't a big surprise considering my background. But I'm pretty sure, that there are good use cases for other fields ;)
But I better not iterate more use cases here, as this would turn even more into a novel.
Hope we can realize this in some way!
Best,
Simon