Some thoughts about the type system

251 views
Skip to first unread message

Simon Danisch

unread,
Mar 14, 2014, 1:58:45 PM3/14/14
to juli...@googlegroups.com

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

Mauro

unread,
Mar 14, 2014, 3:47:42 PM3/14/14
to juli...@googlegroups.com
I will not be able to answer your questions but maybe I can give a few pointers

> 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).

Have you seen this: https://github.com/JuliaLang/julia/issues/1090

> 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

Above type declaration should read
immutable OddNumber<:Int
...
end

right? Or are you suggesting some extra syntax here?

Anyway, I recently asked a somewhat similar question (also related to
your other post):
https://groups.google.com/d/msg/julia-dev/v8B1tI_NB5E/tk98D0iKopYJ

By the sounds of it, it will never be possible for types to inherit from
concrete types. And this is basically what you propose.

Also, I think, if you want to make sure constraints are met, then you
need to have immutable types and check them at creation.

Jeff Bezanson

unread,
Mar 14, 2014, 5:03:19 PM3/14/14
to juli...@googlegroups.com
The purpose of a type like Vec3f is that it is extremely compact and
efficient. In theory, perhaps you could express it as an Array plus a
predicate asserting that the length is 3. However it is unrealistic to
expect a compiler to use this assertion (which can contain arbitrary
code) to deduce a totally different representation for the type. But
even if we could do this, since the resulting thing has a different
representation than an Array, at least internally we'd need different
code for everything. In other words, it still behaves like a new type
in most ways. The only difference is that perhaps `getindex` would
work automatically, but that only saves you a 1-line definition.


On Fri, Mar 14, 2014 at 1:58 PM, Simon Danisch <sdan...@gmail.com> wrote:
> 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) = ...

Toivo Henningsson

unread,
Mar 15, 2014, 2:55:44 AM3/15/14
to juli...@googlegroups.com
I'm not sure exactly how it relates to what you have been thinking about, but you might be interested to check out the development branch of PaaternDispatch.jl, https://github.com/toivoh/PatternDispatch.jl/tree/new_pattern . I have an example of matching on odd, as well as matching on vectors of specific size (syntax similar to what you wrote could be added as well with an inverse function). Instead of refining the type system, it refines dispatch to match on other patterns than types.
Message has been deleted

Simon Danisch

unread,
Mar 18, 2014, 11:29:45 AM3/18/14
to juli...@googlegroups.com

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.

Reply all
Reply to author
Forward
Message has been deleted
0 new messages