Hi,
I have been coding with Julia for some months by now and I want to share some of my thoughts.
I’m not a very experienced programmer, but I hope my suggestions are still valuable and not mentioned before!
One of my main concerns are readable function signature, which calls for very concise types.
How I see function signatures is, that they are not just there for multiple dispatch, but that they also form a contract.
The signature tells the programmer: I can take parameter a,b,c of type x,y,z and give back for example a String.
This is important for readability, testing, writing predictable code and understanding code from other people.
One doesn’t want to read through the function body, to find out the return type and the parameter types.
When I read through the mailing list, it looked like return types are already well discussed.
So I don’t want to talk about them here (even though, that I really would like to have some assertions on the return type in the signature).
What I want to talk about are ways, to make the type system more concise.
Take a look at this function: function x(t::Int) = @assert t % 2 != 0 …
The programmer already knows what numbers he needs, but it’s hidden in the function body.
Better would be:function x(t::OddNumber) = …
And here we finally come to the shortcomings of Julia (actually it was very hard to find anything bad about Julia so far =) ).
If I’m informed correctly, you can’t really implement this in a nice manner in Julia.
What one would need here is a constructor to an already defined primitive type. Something like:
immutable OddNumber::Int
function OddNumber(x::Int)
@assert x % 2 == 0
new(x)
end
end
Having a composite type with one anonymous field would be very nice for a lot of reasons!
First of all, you can do the checks in just one place, instead of having them spread over many function calls.
Its a way of giving a bundle of assertions a name and a semantic meaning.
All in all, you have the possibility, to treat types exactly like the underlying DataType
, but you can enforce certain properties.
It’s basically an addition to the typealias
feature.
One more example:
immutable Vector{Cardinality, T}::Array{T, 1}
function Vector(x::Array{T,1})
@assert Cardinality == length(x)
new(x)
end
end
Like this you can assure, that a vector has a certain cardinality, but still use all the array functions on it.
This Vector type makes the annoying Point, Vec3f, Vec4d (which all need their own functions) madness completely unnecessary.
If you also have generator functions for every type, we’d be one step closer to automated testing.
Also you can do nice things like:for i in OddNumber(1:50)
Problems:
This works very well for immutable types, but would need some complicated rules for mutable types.
Push! and pop! would need to change the cardinality in the Vector example.
Other types could be a lot more complicated.
Also, if you want to put an Integer into a function that only takes an Oddnumber
, either the compiler needs to put it through the constructor as a type check, or you need to wrap it yourself (which would be quite ugly)
But I think the advantages outweigh the problems and the problems should be solvable in some way.
What are your thoughts on this?
Kind regards,
Simon
Hi,
I guess I wasn’t really clear about what I want to achieve by this.
I didn’t really thought about performance or dispatch, but rather the readability of the code and better ways of defining safe to use interfaces.
I had the idea for this quite a long time ago, when I worked with OpenCV.
The functions often returned error messages with long nested assertion printouts, defining what kind of integer or Matrix I should have inputted.
I guess you could say that it’s not that bad, and one can just read the docs or try to understand all the implications of the assertion.
But I think what I have in mind solves this kind of problem in a nice and readable manner.
@Jeff Bezanson
I did forget, that Vec3f and the like can be represented in memory better (But aren’t arrays quite compact as well, or should I really avoid them for a Vec3f like datatype?).
But I think what I have in mind still has some valid use cases (especially as I didn’t intended to use it for compiler optimization)
@Mauro
I don’t think it should be expressed as an inheritance. It is fairly similar to an inheritance, but I think it should be rather seen as a subset of a certain type.
I’m no mathematician or language expert, but I think that this is a different thing.
The operations on the subset should be identical to the ones on the superset. The only difference is, that you can make certain assumptions about the results of the operation.% 2 on the OddNumbers
is always one,length(Vec4)
is always 4 and so forth.
Another example for parametric type subsets:typealias UnitInterval Between{0,1}
So all in all, I just want a way to group these different properties of a certain type under a name.
One way to implement this could be as a composite type with exactly one anonymous field, that automatically gets called when you use the object.
This would actually also have the nice property, that the attribute is transient. As soon as you do an operation on it, the returned value would have the type of the superset type.
Like this it really will be just a couple of assertions grouped under a name.
Ideally there will be also a generator function, that can generate objects that passes these assertions. Like this, you can automate tests better, and for comprehensions and the like can become more readable as well.
This would also beef up the random generator a little bit:
Odds(amount::Union(Range, Integer))
UnitInterval(amount::Union(Range, Integer))
Where a range would yield an ordered Range of that type and an Integer would yield an amount of Randomly generated objects.