Random comments on Julia and its manual

425 views
Skip to first unread message

David Piepgrass

unread,
Jan 24, 2014, 9:09:13 PM1/24/14
to julia...@googlegroups.com
I started learning Julia today so I have some random comments/questions.

Firstly, Julia looks great. It is only the second dynamic language that I have ever seriously felt like using. The first dynamic language I liked was Ruby, until I came to appreciate the shortcomings of a dynamic language in large programs and in the general case, such as:

- no compile-time detection for common (semantic) mistakes
- no intellisense
- even if you had intellisense you couldn't tell what types are accepted by a method (when a method is undocumented, having type information is vastly better than nothing!)
- poor performance

While I'm not sure if Julia can address the first problem very well, in other respects it seems superior to other dynamic languages. I use C# a lot and one of the best things about C# is its perfect intellisense, i.e. if the IDE shows a method, then I can call it, and if the the IDE does not show a method, it does not exist or it is inaccessible (there are exceptions, but none worth mentioning). I guess Julia can't have this kind of perfection, but perhaps it could come close under some circumstances... someday... right?

Now, I'm on Windows and I noticed that pressing Ctrl+Any Arrow Key or Shift+Any Arrow Key causes the process to immediately terminate!! WTF - I hoped Shift+Left would select one character and Ctrl+Left would move one word to the left.

In the manual, in Control Flow it says: "Exceptions can be created explicitly with throw." I think this should say "Exceptions can be raised explicitly with throw." An expression like DomainError() creates an exception, yes?

The section on Tasks begged a question. What happens when I do this?

julia> function producer()
       produce("1");
       produce("2")
       end
producer (generic function with 1 method)

julia> producer()

julia> # what did I just do???


  • Only values, not variables, have types — variables are simply names bound to values. 

Huh? I'm pretty sure variables can have types, otherwise how could Julia optimize "x::Int8" into 1 byte and store it efficiently? It's confusing to claim variables do not have types when everything else I've seen tells me they do. In fact, the possibility of assigning types to variables (and not just values) is one of the greatest advantages of Julia over other dynamic languages. So to say that variables do not have types is as counterproductive as it is confusing.

We are told bits types must be a multiple of 8 bits. Why? I can see multiple reasons why Julia would internally round up sizes to multiples of 8, but it seems like a mere "implementation detail". Why require the formal definition to say, for instance, that Bool is 8 bits?

We are told about incomplete initialization in http://docs.julialang.org/en/release-0.2/manual/constructors/ :

Although it is generally a good idea to return a fully initialized object from an inner constructor, incompletely initialized objects can be returned:

type Incomplete
  xx

  Incomplete() = new()
end

julia> z = Incomplete();

While you are allowed to create objects with uninitialized fields, any access to an uninitialized field is an immediate error: [emphasis mine]

julia> z.xx
access to undefined reference
When I saw this I was puzzled because this behavior would seem to block Julia's famed optimization capabilities. I was not surprised to find out that the manual is incomplete and should be changed:

julia> type Some{T}
       value::T
       Some()=new()
       Some(v::T)=new(v)
       end

julia> Some{Int}()
Some{Int64}(0)

julia> Some{Int}()
Some{Int64}(237821312)

julia> Some{Int}()
Some{Int64}(125)

julia> Some{Integer}().value
ERROR: access to undefined reference

julia> Some{Integer}()
Some{Integer}(#undef)   # did printing that secretly involve catching an error?

That reminds me, the Julia manual doesn't mention the syntax of comments, nor does it use any comments for the first 6 chapters since it's all REPL. So I figured out the comment syntax by guessing.

I hate 1-based indexes. Eww. I know it's not going to change at this point... I'm just sayin'. I thought in modern times the question had been settled.

The way that types-of-types work in Julia strikes me as kind of bizarre. So some tuples are types and others are not types? I just have to say, wow! That's something else. It's a bit shocking to see the base language and metalanguage/reflection mixed like that. I sort of see the advantage, that you can express a tuple's type very simply as e.g. (Int, Int) rather than, say, Tuple{Int, Int}. But wouldn't there be disadvantages to this in reflective code? This particular magic just makes me feel... uneasy.

When introducing the peculiar syntax (::Type{T}, x::Number) in http://docs.julialang.org/en/release-0.2/manual/conversion-and-promotion/, I think the manual should mention that this basically means (unused::Type{T}, x::Number) except that a variable is not defined. The manual might have explained this syntax earlier, I don't know - users shouldn't have to read and remember the whole manual from the beginning. Similarly I think it bears repeating that "..." (e.g. in +(promote(x,y)...)) is the "splicing" operator.

The Types chapter neglects to mention that (Foo,) is the syntax for a one-tuple. The very concept of a one-tuple might be foreign to some readers; for those that know of it, it may not be immediately obvious that "(Real,)" is a 1-tuple, rather than a 2-tuple in which the second value is somehow missing or empty.

Why doesn't this work?

julia> x::Int = 5
ERROR: x not defined

Okay, I think that's enough random stuff for now. G'day mates! Cheerio!

Leah Hanson

unread,
Jan 25, 2014, 10:14:53 AM1/25/14
to julia...@googlegroups.com
I may miss parts of your email. It is more challenging to respond when you dump many unrelated questions into the same email. (It would be easier if you organized or numbered the different issues/comments, or reduced the number of comments/email)

Some of these, such as the arrow key thing, sound like they might be better as a github issue. I don't know what the expected behavior on windows is.

For issues with the writing/content of the manual, the most effective thing is to make a pull-request to make the changes. The source of the manual is here: https://github.com/JuliaLang/julia/tree/master/doc/manual It's convenient to edit them on github, especially if you're less comfortable with git. (The workflow on github is remarkably smooth; all you need it a github account.) I'm skipping things that sound like you just want a specific change to the manual, since you can go make those. :)

1. I'm not especially experienced with intellisense (I don't use IDEs generally), but Julia does know what methods are in scope at any given point. If you're in the REPL or IJulia, you can type something like `istext(` and then press Tab. It will show you a list of the methods of `istext`.

2. As far as the undefined reference error, what needed to be changed about the manual? It looks like it continues to behave as advertised. I haven't dug into what the default printing function's implementation looks like, but that would be where I'd look for how it gets around the undefined problem.

3. Julia has first class types, so types are values in the language. Tuple types are written as a tuple of the types, which seems fairly straight-forward. Your version, Tuple{Int,Int}, would require a new type for each size of tuple (or would have a different representation than you suggested). I don't understand what you would gain by having a separate type that basically reimplements the same functionality as a tuple. Could you offer a concrete example of a problem this could cause?

4. "..." is called "splat" in Julia.

5. `x::Int = 5` evaluates both sides - `x::Int` and `5`. `x::Int` throws that error because x is not defined yet -- it's definitely not an Int. If you make it `5::Int`, you'll probably get closer to what you expected.

More broadly, I think you have somewhat misunderstood typing/type annotations in Julia. Type annotations (such as `x::Int`) are an assertion that the preceding expression has the annotated type. These assertions are used by the compiler for optimizations/type-inference, and create run-time errors if the assertions fail.
Variables do not have types. In `x =5; x::Int`, the `::Int` annotation is not checking or setting the "type of x"; it is checking the type of the value of x. It doesn't make sense for it to work if x is not yet defined, since x would have no value, and thus not have a type.

It's possible that a longer code example may help you:
~~~
julia> x = 5
5

julia> x::Int
5

julia> x::Int = 4.6
4.6

julia> typeof(x)
Float64

julia> x::Float64
4.6

julia> x::Int
ERROR: type: typeassert: expected Int64, got Float64
~~~

6. There is only one implementation of Julia, so I'm not sure why you wouldn't want "sizes are rounded up to multiples of 8 bits" in the manual. It's useful if you care about the representation of your types in memory. Since the manual can be updated at any time (such as to reflect a change in implementation details), there isn't some lock in to the specification by doing so.

7. I'm not going to answer your Tasks/produce question because I'm not really familiar with Tasks.

Best,
Leah

Spencer Russell

unread,
Jan 25, 2014, 11:14:54 AM1/25/14
to julia...@googlegroups.com
Regarding the `x::Int = 5` behavior, I just had a conversation with Jeff and Stefan yesterday where they helped me understand better how that works. My understanding certainly still may be flawed or incomplete, in which case I welcome corrections.

The syntax `x::Int` can mean a few different things depending on where it is:

LHS of an Assignment
When a type annotation is on the left-hand-side of an assignment, such as `x::Int = 5`, it declares x to be the given type, so all the code following that can be optimized with that assumption, and attempt to assign it to an incompatible value will throw an error. Additionally, julia attempts to convert the value on the RHS to the annotated type. The gotcha (and what I think David ran into), is that you can't do type annotations in the global scope (e.g. at the REPL). If you do it inside a function it works as expected. This limitation is mentioned at the end of this section, but it's easy to miss and is definitely a gotcha.

The snippet of the manual "Only values, not variables, have types — variables are simply names bound to values" seems wrong to me as well. It seems that in this usage the variable x is typed. Perhaps some heavier-hitting CS folks can shed some light?

Within an expression
Otherwise, within an expression x::Int is a type assertion, so for instance if I'm calling a function and I want to make sure that what I get back is a certain type, I can use `x = somefunc(10)::Int64`. 

Note that in this usage there is no conversion that happens, it just throws an error if the type doesn't match. This means you should make these annotations as liberal as you can (e.g. use Integer or Real, etc.), because if `somefunc(10)` returns an Int32 the above code would throw an error.

In a method declaration
The third meaning is within a method declaration, where you're providing information to the method dispatcher on when to call the given method.

Note that this does NOT enforce the typed-ness of the variable as in the 1st syntax above. You're free to re-assign an argument variable and julia won't do any type checks or conversion. As far as I know this doesn't hurt performance though, because the code-gen knows what the type of the argument is, so as long as you don't reassign it the generated code is still able to assume the type.

Also note that there is no conversion that happens here, either. For a method declared `function specificfunc(x::Int64)`, The method will simply not get called unless the argument is type Int64. For a method declared `function moregeneralfunc(x::Integer)`, The method will be called if the argument is any subtype of Integer, but the code will be generated on-demand to handle that specific data type.

David Piepgrass

unread,
Jan 25, 2014, 12:36:59 PM1/25/14
to julia...@googlegroups.com
You're right, I'm sorry for not numbering my thoughts or anything.


Some of these, such as the arrow key thing, sound like they might be better as a github issue. I don't know what the expected behavior on windows is.
 
The issue is process termination, but don't Shift+Arrow and Ctrl+Arrow work the same way on Windows and Linux nowadays? Not sure about the Mac... in any case I'm not surprised if the keys simply don't work in a terminal (Windows terminals suck).

For issues with the writing/content of the manual, the most effective thing is to make a pull-request to make the changes. The source of the manual is here: https://github.com/JuliaLang/julia/tree/master/doc/manual It's convenient to edit them on github, especially if you're less comfortable with git. (The workflow on github is remarkably smooth; all you need it a github account.) I'm skipping things that sound like you just want a specific change to the manual, since you can go make those. :)

I'll keep that in mind, thanks.

1. I'm not especially experienced with intellisense (I don't use IDEs generally), but Julia does know what methods are in scope at any given point. If you're in the REPL or IJulia, you can type something like `istext(` and then press Tab. It will show you a list of the methods of `istext`.

2. As far as the undefined reference error, what needed to be changed about the manual? It looks like it continues to behave as advertised. I haven't dug into what the default printing function's implementation looks like, but that would be where I'd look for how it gets around the undefined problem.

Accessing uninitialized Some{Int}().value doesn't throw an error either.
 
3. Julia has first class types, so types are values in the language. Tuple types are written as a tuple of the types, which seems fairly straight-forward. Your version, Tuple{Int,Int}, would require a new type for each size of tuple (or would have a different representation than you suggested). I don't understand what you would gain by having a separate type that basically reimplements the same functionality as a tuple. Could you offer a concrete example of a problem this could cause?
 
The point is that a tuple is sometimes a type-of-type and other times it is just an ordinary value. So if there were a superclass of all types of types (as is the case in most languages with reflection), some tuples would be subclasses of it and others would not be. It's just ... so ... weird. I'm not claiming it's problematic, I'm asking if it is ever problematic.
 
4. "..." is called "splat" in Julia.

The manual (0.2 at least) calls it "splicing" and I don't think it uses that word.

5. `x::Int = 5` evaluates both sides - `x::Int` and `5`. `x::Int` throws that error because x is not defined yet -- it's definitely not an Int. If you make it `5::Int`, you'll probably get closer to what you expected.

The manual specifically mentions this syntax as a special case. See Spencer's reply.
 
6. There is only one implementation of Julia, so I'm not sure why you wouldn't want "sizes are rounded up to multiples of 8 bits" in the manual. It's useful if you care about the representation of your types in memory. Since the manual can be updated at any time (such as to reflect a change in implementation details), there isn't some lock in to the specification by doing so.

I think you misunderstand. Rounding up to 8 bits makes sense; not allowing the user to define, say, a 23-bit type is what I question.

Jameson Nash

unread,
Jan 25, 2014, 12:47:42 PM1/25/14
to julia...@googlegroups.com
>> Some of these, such as the arrow key thing, sound like they might be better as a github issue. I don't know what the expected behavior on windows is.

>The issue is process termination, but don't Shift+Arrow and Ctrl+Arrow work the same way on Windows and Linux nowadays? Not sure about the Mac... in any case I'm not surprised if the keys simply don't work in a terminal (Windows terminals suck).

Leah meant that you should file an issue on GitHub. It may not be able
to do anything useful, but it shouldn't crash. (alternatively,
@lolodiro, when's REPL.jl getting merged?)

> I think you misunderstand. Rounding up to 8 bits makes sense; not allowing the user to define, say, a 23-bit type is what I question.

If it is rounding anyways, it is probably better to force the user to
acknowledge that it is rounding. I'm not aware of any demand for a
23-bits type, so adding support for it would be tricky, without any
useful benefit. Perhaps this section should just mention that the
ability to define custom bitstypes has been (almost) entirely
superseded by the ability to make immutable types.

Ivar Nesje

unread,
Jan 25, 2014, 12:58:09 PM1/25/14
to julia...@googlegroups.com
References for splicing vs splatting in the latest manual:

http://docs.julialang.org/en/latest/manual/style-guide/?highlight=splicing#don-t-overuse

Also, if Julia were to support 23 bits types, what would it mean? Should the implementation just silently round up to 32 and make it behave as if it was declared 32, or should it implement some special overflow rules that would make things slower, because a programmer tried to save some bits?

Stefan Karpinski

unread,
Jan 25, 2014, 3:47:22 PM1/25/14
to Julia Users
In theory, LLVM supports integer types of an arbitrary number of bits. But in our experience oddball integer sizes that C doesn't use typically don't work very well. This may improve over time and make it more reasonable to have a user-defined 23-bit integer type. In principle, we could certainly allow arbitrarily sized bits types, but you couldn't really do anything with them without a significant amount of work. Since it's rather poor form to leave unfinished features around that people can try that simply crash the system, we don't allow a number of bytes that's not a multiple of 8. Bits types that aren't a multiple of 8 just aren't a very high priority.

Tim Holy

unread,
Jan 25, 2014, 4:56:59 PM1/25/14
to julia...@googlegroups.com
On Saturday, January 25, 2014 09:36:59 AM David Piepgrass wrote:
> > I think you misunderstand. Rounding up to 8 bits makes sense; not
> > allowing
>
> the user to define, say, a 23-bit type is what I question.

See BitArrays, which store logical values using a single bit. In the end
you're always going to have to have the complete container be an integer
number of bytes, but within the container you can do anything you want (if
you're writing the code to support it).

--Tim

Toivo Henningsson

unread,
Jan 26, 2014, 8:51:24 AM1/26/14
to julia...@googlegroups.com


On Saturday, 25 January 2014 18:36:59 UTC+1, David Piepgrass wrote:

3. Julia has first class types, so types are values in the language. Tuple types are written as a tuple of the types, which seems fairly straight-forward. Your version, Tuple{Int,Int}, would require a new type for each size of tuple (or would have a different representation than you suggested). I don't understand what you would gain by having a separate type that basically reimplements the same functionality as a tuple. Could you offer a concrete example of a problem this could cause?
 
The point is that a tuple is sometimes a type-of-type and other times it is just an ordinary value. So if there were a superclass of all types of types (as is the case in most languages with reflection), some tuples would be subclasses of it and others would not be. It's just ... so ... weird. I'm not claiming it's problematic, I'm asking if it is ever problematic.

It is actually a bit problematic, occasionally. The Tuple{Int,Int} variant has been mentioned at some point in the discussion, a good while back.
It probably doesn't come up very often in practice, but at least for me who does a lot of metaprogramming, I have sometimes had to work around the fact that I can't specify the type of a type. I think that this is something that would be nice to fix, but my impression is that it's pretty low priority and possibly not considered to be worth the investment.

Leah Hanson

unread,
Jan 26, 2014, 10:44:37 AM1/26/14
to julia...@googlegroups.com
What do you mean by "the type of a type"?

I wanted recently to be able to write a type annotation that would cover all the types of all the arguments to (exported) functions in Base. The following currently works:

~~~
Types = Union(DataType,UnionType,TypeVar,TypeConstructor,())
AtomicType = Union(Types,(Types,))
AType = Union(AtomicType,(AtomicType,),
              (AtomicType,AtomicType),
              (AtomicType,AtomicType,AtomicType),
              (AtomicType,AtomicType,AtomicType,AtomicType),
              (AtomicType,AtomicType,AtomicType,AtomicType,AtomicType),
              (AtomicType,AtomicType,AtomicType,AtomicType,AtomicType,AtomicType),
              (AtomicType,AtomicType,AtomicType,AtomicType,AtomicType,AtomicType,AtomicType))
~~~

where AType is the type actually used to annotate.

Tuple{Int,Int} would not help at all with specifying a type that covers all types, since I'd still need Tuple{AtomicType}, Tuple{Tuple{AtomicType}}, Tuple{AtomicType,AtomicType}, etc.

-- Leah

Toivo Henningsson

unread,
Jan 26, 2014, 3:00:30 PM1/26/14
to julia...@googlegroups.com
The type of a type would e.g. describe which values are legal as the second argument of isa.
It turns out that Type almost works. If I do

julia> f(::Any)=false
f
(generic function with 1 method)

julia
> f(::Type)=true
f
(generic function with 2 methods)

Then

julia> f(Int)
true

julia
> f((Int,String))
true

julia
> f((Int,String,2))
false

julia
> [f(T) for T in (UnionType, DataType, TypeVar, TypeConstructor)]'
1x4 Array{Bool,2}:
 true  true  true  true

But it doesn't seem to catch all types:

julia> f((Int,))
false

julia
> f(((Int,String),String))
false

julia
> f(((Int,String),String,Int))
false

On the other hand, this definition seems to catch all the cases that I could come up with:

julia> g(::Any)=false
g
(generic function with 1 method)

julia
> g{T}(::Type{T}) = true
g
(generic function with 2 methods)

julia
> g((Int,))
true

julia
> g(((Int,String),String))
true

It is pretty weird that adding the type parameter makes the method that returns true more general (I guess that I should file an issue...)

Anyway, if the type of a tuple were <: Type and not <: Tuple, then these things would not be hairy at all, since you wouldn't have to discern between tuples that are types and tuples that aren't, and being a type would behave as plainly as other subtyping relationships.

Stefan Karpinski

unread,
Jan 27, 2014, 12:21:56 PM1/27/14
to Julia Users
That is weird and may be  a dispatch bug. Perhaps an issue should be opened.

Toivo Henningsson

unread,
Jan 27, 2014, 1:56:06 PM1/27/14
to julia...@googlegroups.com
This was stranger than I thought, seem to depend on the order of invocations to f. Issue filed: https://github.com/JuliaLang/julia/issues/5577
Reply all
Reply to author
Forward
0 new messages