Considering that many Fortran programmers would be interested in Julia, I decided to start compiling a list and share it with everybody:
Noteworthy differences from Fortran
Julia is a dynamic language, so you do not have to declare variable types—it is optional. Apart from giving hints to the compiler, type annotations (or lack thereof) play a crucial role in Julia. When made concrete allow selection of specific algorithms, and when kept general permit composability. To declare the type, one uses the :: operator, like in a::MyType = MyType(arg) (see Types).
Julia code is automatically compiled. No need to worry about calling the compiler, linking, etc.
Julia arrays are indexed with square brackets, A[i,j].
In Julia, the adjoint function performs conjugate transposition; in Fortran, adjoint provides the “adjugate” or classical adjoint, which is the transpose of the matrix of cofactors.
Indentation in Julia is used for readability and stylistic convention but does not affect parsing and functionality.
Julia is case sensitive.
Julia does not support negative indices, by default. You would be interested in the package OffsetArrays for that purpose.
Julia has no line continuation syntax: if, at the end of a line, the input so far is a complete expression, it is considered done; otherwise the input continues. One way to force an expression to continue is to wrap it in parentheses.
Be careful with non-constant global variables in Julia, especially in tight loops. The effect of globals can be drastic (see Performance Tips).
Single line comments are indicated with #. #= indicates the start of a multiline comment, and =# ends it.
Julia uses end to denote the end of conditional blocks, like if, loop blocks, like while/ for, and functions. In lieu of the one-line if ( cond ) statement, Julia allows statements of the form if cond; statement; end, cond && statement and !cond || statement. Assignment statements in the latter two syntaxes must be explicitly wrapped in parentheses, e.g. cond && (x = value), because of the operator precedence.
By convention, functions that modify their arguments have a ! at the end of the name, for example push!.
Julia comes with a standard library to perform common operations like string manipulation, filesystem interaction, linear algebra algorithms, … For example, BLAS and LAPACK routines are already bundled under LinearAlgebra.BLAS/LAPACK.
A package manager (Pkg) is included to manage dependencies.
I tried to recycle as many items as possible to be accurate and in line with the rest of the content of the above mentioned documentation section.
Actually, I got bitten by assuming that adjoint was the same in both languages. As a bonus, I add a simple routine to calculate the adjugate:
function adjugate(A::AbstractMatrix{T}) where T
C_eltype = typeof(det(A[1:end .!= 1, 1:end .!= 1]))
C = similar(A, C_eltype ) # cofactor matrix
for i in 1:size(A,1), j in 1:size(A,2)
C[i,j] = (-1)^(i+j) * det(@view A[1:end .!= i, 1:end .!= j])
end
transpose(C)
end
I probably misunderstood what author intended to say, as I meant that indents are significant in Julia’s coding style (but not as much as in Fortran, or e.g. Python). Indents do not play the role in the parsing of Julia code (unless there is some extreme case where this isn’t true?).
Ok, I thought you meant that there were situations where indents were significant to the compiler / runtime, as they are in for example python or in old school FORTRAN where the first few columns of each line are reserved for use in punch card machines
I usually use the ternary operator ? for single line if statements in Julia e.g. conditional ? if_true : if_false.
As for indentation, better phrasing may be something similar to “Indentation in Julia is used for readability and stylistic convention but does not affect parsing and functionality.”
There is also the ifelse(<condition>, <when true>, <when false>) function in Julia. On the other hand, Fortran has merge(<when true>, <when false>, <condition>). Although I have never seen it being used.
Thanks for writing this up! I think the first point could mention the use of type annotations for dispatch and for concretely typed structs, where these are actually significant. I am wondering, whether it would perhaps be easier to make this into a PR, so people can leave suggestions directly on your text.
I wonder if other style conventions like lowercase function names should be included with the mention of push! and its exclamation point.
Since Fortran is so often used for High Performance Computing, I wonder if parallel methodologies should be included here too. What are the MPI and OpenMP equivalents in Julia? (I don’t actually know the answer to that and am curious.)
Fortran users might indeed be tempted to declare types out of habit, but I think this should be discouraged. Julia’s great composability largely relies on not typing very specifically, which e.g. allows automatic differentiation with ForwardDiff.jl to mesh with DifferentialEquations.jl seamlessly. Might help to mention such composability, and reserve specific typing to its particular applications, e.g. ccall interop.
I don’t think these are correct examples of type declaration in Julia. May be you can define a struct and a simple function with typed signature to emphasize its role in multiple dispatch.
I think it is more that it is confusing. But the middle example is actually a perfect example of defining the type of a binding.
5::Int64 is a typeassert in a literal, what is kinda confusing because this does not guarantee that 5 is Int64, this will just do nothing in 64-bit machines, and will throw an error in 32-bit machines. Not sure if OP knows this.
a::Float64 = 5.3 is actually defining the type of a binding (not sure if using the term variable is a good idea). If the code tries to assign a value of another type to a Julia will try to convert the value, and if it is not possible, Julia will throw an error.
a::T where T<:AbstractMatrix, this is the same of above, but will probably confuse people because is more often seen in method signatures, and not in variable declarations.
Note also that the second example is not a supported Julia syntax (unless it’s an optional argument, e.g., which cannot be inferred from the OP explanation):
julia> a::Float64 = 5.3
ERROR: syntax: type declarations on global variables are not yet supported
The second example is valid Julia syntax, just not in the global scope, put it inside a function and it will work:
julia> function f(a)
b :: Int = a
b
end
julia> f("abc")
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int64
...
julia> f(100.0)
100
julia> function g(a)
(b :: T where T<:AbstractMatrix) = a
b
end
julia> g([1 2; 3 4])
2×2 Array{Int64,2}:
1 2
3 4
julia> g(10)
ERROR: MethodError: Cannot `convert` an object of type
Int64 to an object of type
AbstractArray{T,2} where T
Those examples do not make it clear, but any assignments of new values to a “type-constant” binding in that scope will try to auto-convert, not just the assignment in the declaration.
julia> function f(a)
b :: Int = a
@show b
b = "abc"
b
end
julia> f(10.0)
b = 10
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int64
...
I guess the first problem is to wean people coming from “strongly-typed” languages, to not obsess about Float64 and such in Julia. That would cut down on questions like “why doesn’t auto-diff work on my function?”
Then there’s structured data, where multiple dispatch comes in. That’s the next thing to learn, and even then it’s not about Float64. It’s confusing because ::MyStruct and ::Float64 look similar, but a struct is more useful to dispatch on, and leaving an argument untyped can improve composability.
Maybe it should be a two-step process, to first wean off of Float64, then learn about dispatch and composability.