Julia is a very different and interesting language

Totally agreed. Ignore types while writing in the REPL. But any large project needs an organizing principle (try writing a whole project in C. I dare you to call it nice or concise. C+MPI days…). In the past that was OOP, but OOP is verbose and usually heavily slows down code. 8 years or so of OOP, 2 years of Julia’s multiple dispatch yet the latter is so much easier to use. It took about 3-6 months to really start making good use of it, but here’s a summary of some things to know:

Julia software designs are different than OOP designs, which is totally fine because it’s more concise, performant, and legible anyways. Sometimes it’s better to throw aside old paradigms when you have a better answer.

7 Likes

@davide - beautiful, I will try it out, thank you!

Julia peeps: A lot of good discussion and ideas - THANK YOU!

I haven’t grokked “multiple dispatch” yet and that is now high on my list.

What makes it seem bigger and scarier are two things: unicode and the syntax.

Unicode variable and function and operator names just totally blow my mind. The fact that you can type “\pi” and use greek letters. Wow.

Here’s an example of syntax that seems rich and yet I have a lot of questions about what all those “<:” and “{}” mean.

struct ZeroPoleGain{Z<:Number,P<:Number,K<:Number} <: FilterCoefficients
    z::Vector{Z}
    p::Vector{P}
    k::K
end

The syntax for the types I don’t get yet. Also there are numerous operators that seem very specialized like === and >>> (where is <<<??)

I think once I learn what is what, yeah it might be awesome but I am not there yet.

It has the feel of how MATLAB was when I was learning it - pretty much anything you could think of, yeah MATLAB could do it somehow.

The other thing is I am learning Julia in my free time which is pretty limited, so that also makes the learning process seem slower to me. It’s very bursty.

One final thought - yeah using the REPL seems easy. But then when looking at adding stuff to a library such as DSP.jl, there is a lot more to it.

Regards,
Tom

1 Like

For example Z<:Number just means that Z has to be a number. What does that mean? Well, look just bellow it and you’ll see z::Vector{Z}. This means that z is a Vector, and that the elements of said vector are numbers. Numbers are just what you are probably imagining. So, z can be a vector of floats, integers, or complex numbers, but it cannot be a vector of strings or anything else that is not a number. Same goes for p and P. You can probably now infer that k must itself be a number.

=== is actually quite common across many programming languages. It basically is just testing whether two references refer to exactly the same object. >>> is also common, it’s just a slightly special type of bitshift operator.

By the way, also be aware that you can access documentation by typing ? into the REPL. For instance, type in ?>>> for a full description of that operator, all in the REPL.

2 Likes

Reading through the whole manual would be a good place to start. Also, the built-in docstrings.

I was just reading through @ChrisRackauckas blog post a bit.

Here’s a wonderful quote from it (that I felt compelled to reproduce here):

"This means that, while in an object-oriented programming language you group by implementation details, in typed-dispatch programming you group by actions. "

Also, I love how traits (discussed in the same blog) which clearly started out as hacks to get more more code to compile time turn out to be a wonderful, intuitive concept in their own right.

1 Like

Beginning users don’t need to worry about the entire type system when writing hello world, sure, but they’re going to encounter it as soon as they try to understand or debug a package written by an advanced developer. You’re never more than a few usings away from this: (copied verbatim from package documentation!)

julia> timeaxis(img)

AxisArrays.Axis{:time,
StepRangeLen{Unitful.Quantity{Float64,Unitful.Dimensions{(Unitful.Dimension{:Time}(1//1),)},
Unitful.FreeUnits{(Unitful.Unit{:Second,Unitful.Dimensions{(Unitful.Dimension{:Time}(1//1),)}}(0, 1//1),),
Unitful.Dimensions{(Unitful.Dimension{:Time}(1//1),)}}},
Base.TwicePrecision{Unitful.Quantity{Float64,Unitful.Dimensions{(Unitful.Dimension{:Time}(1//1),)},
Unitful.FreeUnits{(Unitful.Unit{:Second,Unitful.Dimensions{(Unitful.Dimension{:Time}(1//1),)}}(0, 1//1),),
Unitful.Dimensions{(Unitful.Dimension{:Time}(1//1),)}}}},
Base.TwicePrecision{Unitful.Quantity{Float64,Unitful.Dimensions{(Unitful.Dimension{:Time}(1//1),)},
Unitful.FreeUnits{(Unitful.Unit{:Second,Unitful.Dimensions{(Unitful.Dimension{:Time}(1//1),)}}(0, 1//1),),
Unitful.Dimensions{(Unitful.Dimension{:Time}(1//1),)}}}}}}(0.03333333333333333 s:0.03333333333333333 s:10.0 s)
2 Likes

The manual is super useful, no doubt, but I think I disagree that a new user should spend many hours reading many thousands of words about Julia arcana before just…writing something.

2 Likes

I’ve taught Julia as a “first” programming language in mathematics computing. I agree it’s a lot easier to learn than OOP. The learning curve is steeper than MATLAB though, as even the most novice user needs to learn the difference between [1,2] and [1.0,2] to avoid type errors.

That said, I think it falls under the old saying “make it as simple as possible and no simpler”. MATLAB makes things so simple you don’t know what’s going on, and students are just confused.

As an aside, the best thing about teaching with Julia is the bits function: it made me realise while teaching that I didn’t actually understand negative integers or floating point.

10 Likes

Now called bitstring in 0.7 (just so wthat didn’t come as a rude awakening :wink:).

Also, yes, the devs have done an amazing job with documentation (probably largely thanks to them enforcing documentation requirements in PR’s). It’s amazing how much you can really learn from just reading the Julia manual, it is much more than just an “API docs” type of thing.

3 Likes

The beauty of Julia is that, as a user, you don’t have to. Code without any type declarations until you need them. And I really do mean none.

It is perfectly reasonable (and I would say preferred) to write user code without any fancy parametric types. With only a very few exceptions, letting the compiler figure out the types will be just as fast, and much more flexible than anything you could write on your own. Moreover, you don’t run the risk of either over- or under-typing, which can make things like AD impossible for your code. This is an issue with people coming from C or Fortran think that they like explicitly typing things and do it wrong in Julia as it is a huge step from explicit declaration of concrete types to generic ones.

Of course, as a certain point you are going to want to start writing more generic functions that can dispatch based on the types of parameters - though it happens later than you might think, and by that point you will already have figured out much of the type system from using packaged. Until then, it will mostly just work as you would expect. Library code gets complicated with all sorts of generic programming patterns, but there is no choice there. For users, there is a choice.

If you ask me, one of the biggest issues with introducing to novice-programmers and scientists in Julia is introductory example code which uses complicated types, generic programming, and/or lisp-style meta-programming. Simple Julia code is far more succinct than Python and Matlab, and there is no reason to make it look artificially more difficult. There is a clash of cultures where package writers tend to have a different sense of what is reasonable than general users, but nothing in the language requires the code to look complicated.

7 Likes

I totally agree.
One measure, which is good in my opinion, for the simplicity (In the right sense of meaning) of a language is how fast can you understand code written by someone else which is an expert in the language.

For instance, even in Python, looking on code written by experts make it like a good few hours riddle.

But again, it has to with me.
Programming is a just a tool for me to express thoughts and knowledge usually form Signal / Image Processing world which means the simple model of Input Output is perfect (Not OOP). So from that point of view Julia not being OOP is a plus on my side.

Anyhow, I think one of Julia’s real strength is its community because discussions here are always great as people always open to other opinions and welcome them kindly.

Anyhow, The real question is where Julia 1.0.
Can’t wait try it for real :-).

3 Likes

While this is an ideal to strive for, in practice there are some idioms which are common and require at least some cursory attention to types. An example is working with preallocated containers, for which the type needs to be figured out at some point. In simple cases one can use similar, in more involved cases zero & friends with a type parameter, but occasionally the output type needs to be computed.

I agree that one should avoid this whenever possible. But occasionally it isn’t, and it is very easy to run into this.

2 Likes

No idea if you’re serious about it having a “steep learning curve”. I literally picked it up in a day and ported a Python project within a week. It’s basically Python with all the functional programming/static language goodies. I find it wonderful so far.

6 Likes

Remember that you can always ask the REPL for any symbols or operators that you come across, even <:

help?> <:
search: <:

  <:(T1, T2)

  Subtype operator, equivalent to issubtype(T1, T2).

  julia> Float64 <: AbstractFloat
  true
  
  julia> Vector{Int} <: AbstractArray
  true
  
  julia> Matrix{Float64} <: Matrix{AbstractFloat}
  false

help?> >>>
search: >>> >> .>>

  >>>(x, n)

  Unsigned right bit shift operator, x >>> n. For n >= 0, the result is x shifted right by n
  bits, where n >= 0, filling with 0s. For n < 0, this is equivalent to x << -n.

  For Unsigned integer types, this is equivalent to >>. For Signed integer types, this is
  equivalent to signed(unsigned(x) >> n).

  julia> Int8(-14) >>> 2
  60
  
  julia> bits(Int8(-14))
  "11110010"
  
  julia> bits(Int8(60))
  "00111100"

  BigInts are treated as if having infinite size, so no filling is required and this is
  equivalent to >>.

  See also >>, <<.

  >>>(B::BitVector, n) -> BitVector

  Unsigned right bitshift operator, B >>> n. Equivalent to B >> n. See >> for details and
  examples.

In this sense, <: is a built-in function that is aliased to issubtype.

3 Likes