Category Archives: Julia

Julia – 120 – conclusione: Julia è OK

Continuo da qui. Come accennato nel post precedente ho iniziato uno script non troppo lungo e impegnativo in Julia. È una cosa che tipicamente faccio con Python (o, se richiesto, con Octave). È quasi pronto, vedrò se raccontarne in futuro ma per adesso un paio di impressioni.

Il manuale di Julia insiste sui tipi delle variabili. Nelle dichiarazioni e –soprattutto– nelle funzioni (bello come implementa le funzioni 😁). Per adesso ho ignorato la raccomandazione, lo script deve, per il momento, rimanere pythonico. E sì, sembra quasi Python. Anche se, nota di colore qui, ci sono gli end, mi ci devo riabituare.

La REPL è ottima per lo sviluppo, provo lì le istruzioni dubbiose. Ho sempre tre terminali aperti. E il browser sulla pagina della Standard Library.

Uh! il post è troppo corto, lo allungo con qualche prova a complemento di quello di ieri, cose che devo verificare se funzionano anche con Windows. Ecco test-env.jl:

println("Nome del programma: ", PROGRAM_FILE)
println("Nome assoluto del programma:\n", abspath(PROGRAM_FILE))
println("Lista degli aromenti sulla linea di comando:\n", ARGS)
println("Sistema operativo: ", Sys.KERNEL, " - ", Sys.MACHINE)
println("Nome del computer: ", gethostname())
println("Nome utente: ", ENV["USER"])
println("Directory corrente: ", pwd())

OK, per adesso basta così, poi chissà le previsioni circa il futuro… (inizio-cit.).

🤩

Annunci

Julia – 119 – Standard Library – con l’abbozzo di una prima prova

Continuo da qui, copio qui.

Il manuale –ottimo– a questo punto ha un capitolo che è una lunga lista di codici Unicode. OK, io quando posso li evito, tranne le vocali accentate, e pochi altri, p.es ç e . E non si usano nella programmazione (quasi mai). Comunque là c’è parecchio, quasi tutto.

Poi si passa alla descrizione dettagliata di tutti i componenti del linguaggio, cominciando dalla Standard Library.

The Julia standard library contains a range of functions and macros appropriate for performing scientific and numerical computing, but is also as broad as those of many general purpose programming languages. Additional functionality is available from a growing collection of available packages. Functions are grouped by topic below.

Questa è una pagina da tener sempre a un click di distanza nel browser, a complemento dell’help presente nella REPL.

Come tutta la documentazione è fatta in modo esemplare, mica devo aggiungere altro vero? 😊

Anzi no, alcune considerazioni mie –solo mie. Sto provando a implementare in Julia un programmino non troppo lungo e relativamente privo di difficoltà. È una cosa che si dovrebbe fare con Python; nelle organizzazioni molto piccole conviene standardizzare, non duplicare i tools (in questo caso i linguaggi) a meno di condizioni che lo richiedano. E Julia attualmente può essere considerato un doppione di Python (o di altri linguaggi). Inoltre è giovane, presumibilmente in rapida evoluzione, forse cambieranno cose che renderanno oneroso l’aggiornamento (questo capita da sempre con tutti i linguaggi). Ma ci sto provando, magari ne riparlerò.

Julia ha, rispetto a Python, delle particolarità interessanti, per esempio la gestione (lo stress) dei tipi. Interessantissima la gestione delle funzioni, oltre alle cose nuove ce ne sono di ereditate da altri linguaggi (Lisp e Fortran).

Per me che sono vecchio devo perdere vecchie abitudini (Fortran e Basic), per esempio come gestire l’I/O.

Il modo antico è di aprire i files di input e output, e ciclare a leggere quanto serve, processarlo e scrivere il risultato e infine chiudere i files aperti. Esempio, file io-seq.jl:

fname = "testo" # file di input
fout = "out"    # file di output

id = open(fname, "r")
od = open(fout, "w")

while !eof(id)
    st = readline(id)
    println(od, uppercase(st))
end

close(id)
close(od)

Codice vecchio, vero? in particolare la riga while !eof(id) che cicla se non si è raggiunto l’end-of-file di id. Lo stesso codice come minimo si dovrebbe scrivere così (io-seq-n.l):

fname = "testo" # file di input
fout = "out"    # file di output

id = open(fname, "r")
od = open(fout, "w")

for line in eachline(id)
    println(od, uppercase(line))
end

close(id)
close(od)

ottenendo lo stesso risultato.

Ma si può fare di meglio. Salvo che il caso in cui il file dei dati sia davvero enorme conviene leggerlo in blocco e poi elaborarne i dati (ovviamente non sempre ma spesso conviene). Ecco io-blocco.jl:

fname = "testo" # file di input
fout = "out"    # file di output

id = open(fname, "r")
txt = readlines(id)
close(id)

println("Lette ", length(txt), " righe")

od = open(fout, "w")
println(od, "prima riga: " * txt[1])
println(od, "terza riga, maiuscola: " * uppercase(txt[3]))
close(od)

OK, mi ero dimenticato che per concatenare le stringhe si usa *.

Questo è solo l’inizio, bisogna prenderci la mano ma si può fare! 😁

Ah! ancora una cosa, grazie (10^3) a tutti gli autori, qui, qui e qui 😁

🤩

Julia – 118 – differenze rispetto altri linguaggi – C/C++

Continuo da qui, copio qui.

Julia arrays are indexed with square brackets, and can have more than one dimension A[i,j]. This syntax is not just syntactic sugar for a reference to a pointer or address as in C/C++. See the Julia documentation for the syntax for array construction (it has changed between versions).

In Julia, indexing of arrays, strings, etc. is 1-based not 0-based.

Julia arrays are assigned by reference. After A=B, changing elements of B will modify A as well. Updating operators like += do not operate in-place, they are equivalent to A = A + B which rebinds the left-hand side to the result of the right-hand side expression.

Julia arrays are column major (Fortran ordered) whereas C/C++ arrays are row major ordered by default. To get optimal performance when looping over arrays, the order of the loops should be reversed in Julia relative to C/C++.

Julia values are passed and assigned by reference. If a function modifies an array, the changes will be visible in the caller.

In Julia, whitespace is significant, unlike C/C++, so care must be taken when adding/removing whitespace from a Julia program.

In Julia, literal numbers without a decimal point (such as 42) create signed integers, of type Int, but literals too large to fit in the machine word size will automatically be promoted to a larger size type, such as Int64 (if Int is Int32), Int128, or the arbitrarily large BigInt type. There are no numeric literal suffixes, such as L, LL, U, UL, ULL to indicate unsigned and/or signed vs. unsigned. Decimal literals are always signed, and hexadecimal literals (which start with 0x like C/C++), are unsigned. Hexadecimal literals also, unlike C/C++/Java and unlike decimal literals in Julia, have a type based on the length of the literal, including leading 0s. For example, 0x0 and 0x00 have type UInt8, 0x000 and 0x0000 have type UInt16, then literals with 5 to 8 hex digits have type UInt32, 9 to 16 hex digits type UInt64 and 17 to 32 hex digits type UInt128. This needs to be taken into account when defining hexadecimal masks, for example ~0xf == 0xf0 is very different from ~0x000f == 0xfff0. 64 bit Float64 and 32 bit Float32 bit literals are expressed as 1.0 and 1.0f0 respectively. Floating point literals are rounded (and not promoted to the BigFloat type) if they can not be exactly represented. Floating point literals are closer in behavior to C/C++. Octal (prefixed with 0o) and binary (prefixed with 0b) literals are also treated as unsigned.

String literals can be delimited with either " or """, """ delimited literals can contain " characters without quoting it like "\"" String literals can have values of other variables or expressions interpolated into them, indicated by $variablename or $(expression), which evaluates the variable name or the expression in the context of the function.

// indicates a Rational number, and not a single-line comment (which is # in Julia)

#= indicates the start of a multiline comment, and =# ends it.

Functions in Julia return values from their last expression(s) or the return keyword. Multiple values can be returned from functions and assigned as tuples, e.g. (a, b) = myfunction() or a, b = myfunction(), instead of having to pass pointers to values as one would have to do in C/C++ (i.e. a = myfunction(&b).

Julia does not require the use of semicolons to end statements. The results of expressions are not automatically printed (except at the interactive prompt, i.e. the REPL), and lines of code do not need to end with semicolons. println() or @printf() can be used to print specific output. In the REPL, ; can be used to suppress output. ; also has a different meaning within [ ], something to watch out for. ; can be used to separate expressions on a single line, but are not strictly necessary in many cases, and are more an aid to readability.

In Julia, the operator (xor) performs the bitwise XOR operation, i.e. °^ in C/C++. Also, the bitwise operators do not have the same precedence as C/++, so parenthesis may be required.
Unicode per -> 8891 | 0x22bb.

Julia’s ^ is exponentiation (pow), not bitwise XOR as in C/C++ (use , or xor, in Julia).

Julia has two right-shift operators, >> and >>>. >>> performs an arithmetic shift, >> always performs a logical shift, unlike C/C++, where the meaning of >> depends on the type of the value being shifted.

Julia’s -> creates an anonymous function, it does not access a member via a pointer.

Julia does not require parentheses when writing if statements or for/while loops: use for i in [1, 2, 3] instead of for (int i=1; i <= 3; i++) and if i == 1 instead of if (i == 1).

Julia does not treat the numbers 0 and 1 as Booleans. You cannot write if (1) in Julia, because if statements accept only booleans. Instead, you can write if true, if Bool(1), or if 1==1.

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.

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.

Julia macros operate on parsed expressions, rather than the text of the program, which allows them to perform sophisticated transformations of Julia code. Macro names start with the @ character, and have both a function-like syntax, @mymacro(arg1, arg2, arg3), and a statement-like syntax, @mymacro arg1 arg2 arg3. The forms are interchangable; the function-like form is particularly useful if the macro appears within another expression, and is often clearest. The statement-like form is often used to annotate blocks, as in the parallel for construct: @parallel for i in 1:n; #= body =#; end. Where the end of the macro construct may be unclear, use the function-like form.

Julia now has an enumeration type, expressed using the macro @enum(name, value1, value2, ...) For example: @enum(Fruit, banana=1, apple, pear).

By convention, functions that modify their arguments have a ! at the end of the name, for example push!.

In C++, by default, you have static dispatch, i.e. you need to annotate a function as virtual, in order to have dynamic dispatch. On the other hand, in Julia every method is “virtual” (although it’s more general than that since methods are dispatched on every argument type, not only this, using the most-specific-declaration rule).

🤩

Julia – 117 – differenze rispetto altri linguaggi – Python

Continuo da qui, copio qui.

Salto la sezione di R, linguaggio a me sconosciuto 😊

Per quanto riguarda Python occorre tener presente le differenze tra le versioni 2 e 3. Alle volte capitano inconvenienti. E la 2.x è ancora usata.

Julia requires end to end a block. Unlike Python, Julia has no pass keyword.

In Julia, indexing of arrays, strings, etc. is 1-based not 0-based.

Julia’s slice indexing includes the last element, unlike in Python. a[2:3] in Julia is a[1:3] in Python.

Julia does not support negative indexes. In particular, the last element of a list or array is indexed with end in Julia, not -1 as in Python.

Julia’s for, if, while, etc. blocks are terminated by the end keyword. Indentation level is not significant as it is in Python.

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.

Julia arrays are column major (Fortran ordered) whereas NumPy arrays are row major (C-ordered) by default. To get optimal performance when looping over arrays, the order of the loops should be reversed in Julia relative to NumPy.

Julia’s updating operators (e.g. +=, -=, ...) are not in-place whereas NumPy’s are. This means A = ones(4); B = A; B += 3 doesn’t change values in A, it rather rebinds the name B to the result of the right- hand side B = B + 3, which is a new array. Use B[:] += 3, explicit loops, or InplaceOps.jl.

Julia evaluates default values of function arguments every time the method is invoked, unlike in Python where the default values are evaluated only once when the function is defined. For example, the function f(x=rand()) = x returns a new random number every time it is invoked without argument. On the other hand, the function g(x=[1,2]) = push!(x,3) returns [1,2,3] every time it is called as g().

In Julia % is the remainder operator, whereas in Python it is the modulus.

🤩

 

Julia – 116 – differenze rispetto altri linguaggi – Octave

Continuo da qui, copio qui.

Il manuale parla di MATLAB (e non correggerò) ma io uso Octave che, tranne per qualche particolarità molto specifica, è intercambiabile. E FOSS 😁

Although MATLAB users may find Julia’s syntax familiar, Julia is not a MATLAB clone. There are major syntactic and functional differences. The following are some noteworthy differences that may trip up Julia users accustomed to MATLAB.

Julia arrays are indexed with square brackets, A[i,j].

Julia arrays are assigned by reference. After A=B, changing elements of B will modify A as well.

Julia values are passed and assigned by reference. If a function modifies an array, the changes will be visible in the caller.

Julia does not automatically grow arrays in an assignment statement. Whereas in MATLAB a(4) = 3.2 can create the array a = [0 0 0 3.2] and a(5) = 7 can grow it into a = [0 0 0 3.2 7], the corresponding Julia statement a[5] = 7 throws an error if the length of a is less than 5 or if this statement is the first use of the identifier a. Julia has push!() and append!(), which grow Vectors much more efficiently than MATLAB’s a(end+1) = val.

The imaginary unit sqrt(-1) is represented in Julia as im, not i or j as in MATLAB.

In Julia, literal numbers without a decimal point (such as 42) create integers instead of floating point numbers. Arbitrarily large integer literals are supported. As a result, some operations such as 2^-1 will throw a domain error as the result is not an integer (see the FAQ entry on domain errors for details).

In Julia, multiple values are returned and assigned as tuples, e.g. (a, b) = (1, 2) or a, b = 1, 2. MATLAB’s nargout, which is often used in MATLAB to do optional work based on the number of returned values, does not exist in Julia. Instead, users can use optional and keyword arguments to achieve similar capabilities.

Julia has true one-dimensional arrays. Column vectors are of size N, not Nx1. For example, rand(N) makes a 1-dimensional array.

In Julia, [x,y,z] will always construct a 3-element array containing x, y and z.
To concatenate in the first (“vertical”) dimension use either vcat(x,y,z) or separate with semicolons ([x; y; z]).
To concatenate in the second (“horizontal”) dimension use either hcat(x,y,z) or separate with spaces ([x y z]).
To construct block matrices (concatenating in the first two dimensions), use either hvcat() or combine spaces and semicolons ([a b; c d]).

In Julia, a:b and a:b:c construct Range objects. To construct a full vector like in MATLAB, use collect(a:b). Generally, there is no need to call collect though. Range will act like a normal array in most cases but is more efficient because it lazily computes its values. This pattern of creating specialized objects instead of full arrays is used frequently, and is also seen in functions such as linspace, or with iterators such as enumerate, and zip. The special objects can mostly be used as if they were normal arrays.

Functions in Julia return values from their last expression or the return keyword instead of listing the names of variables to return in the function definition.

A Julia script may contain any number of functions, and all definitions will be externally visible when the file is loaded. Function definitions can be loaded from files outside the current working directory.

In Julia, reductions such as sum(), prod(), and max() are performed over every element of an array when called with a single argument, as in sum(A), even if A has more than one dimension.

In Julia, functions such as sort() that operate column-wise by default (sort(A) is equivalent to sort(A,1)) do not have special behavior for 1xN arrays; the argument is returned unmodified since it still performs sort(A,1). To sort a 1xN matrix like a vector, use sort(A,2).

In Julia, if A is a 2-dimensional array, fft(A) computes a 2D FFT. In particular, it is not equivalent to fft(A,1), which computes a 1D FFT acting column-wise.

In Julia, parentheses must be used to call a function with zero arguments, like in tic() and toc().

Julia discourages the used of semicolons to end statements. The results of statements are not automatically printed (except at the interactive prompt), and lines of code do not need to end with semicolons. println() or @printf() can be used to print specific output.

In Julia, if A and B are arrays, logical comparison operations like A == B do not return an array of booleans. Instead, use A .== B, and similarly for the other boolean operators like <, > and =.

In Julia, the operators &, |, and (xor) perform the bitwise operations equivalent to and, or, and xor respectively in MATLAB, and have precedence similar to Python’s bitwise operators (unlike C). They can operate on scalars or element-wise across arrays and can be used to combine logical arrays, but note the difference in order of operations: parentheses may be required (e.g., to select elements of A equal to 1 or 2 use (A .== 1) | (A .== 2)).
Unicode per -> 8891 | 0x22bb.

In Julia, the elements of a collection can be passed as arguments to a function using the splat operator ..., as in xs=[1,2]; f(xs...).

Julia’s svd() returns singular values as a vector instead of as a dense diagonal matrix.

In Julia, ... is not used to continue lines of code. Instead, incomplete expressions automatically continue onto the next line.

In both Julia and MATLAB, the variable ans is set to the value of the last expression issued in an interactive session. In Julia, unlike MATLAB, ans is not set when Julia code is run in non-interactive mode.

Julia’s types do not support dynamically adding fields at runtime, unlike MATLAB’s classes. Instead, use a Dict.

In Julia each module has its own global scope/namespace, whereas in MATLAB there is just one global scope.

In MATLAB, an idiomatic way to remove unwanted values is to use logical indexing, like in the expression x(x>3) or in the statement x(x>3) = [] to modify x in-place. In contrast, Julia provides the higher order functions filter() and filter!(), allowing users to write filter(z->z>3, x) and filter!(z->z>3, x) as alternatives to the corresponding transliterations x[x.>3] and x = x[x.>3]. Using filter!() reduces the use of temporary arrays.

The analogue of extracting (or “dereferencing”) all elements of a cell array, e.g. in vertcat(A{:}) in MATLAB, is written using the splat operator in Julia, e.g. as vcat(A...).

C’è da aggiungere che oltre alla REPL Octave ha un’ottima IDE.

🤢

Julia – 115 – domande tipiche, FAQ

Continuo da qui, copio qui.

Le FAQ si usano solo quando non si sa più cosa fare, imho. Preventivamente non mi sembra che abbiano senso. Ma quelle davvero tipiche –cioè che mi sembrano suggerimenti a prescindere– le riporto. Non si sa mai. E comunque c’è sempre Stack Overflow.

Come faccio a cancellare un oggetto in memoria?
Julia does not have an analog of MATLAB’s clear function; once a name is defined in a Julia session (technically, in module Main), it is always present.

If memory usage is your concern, you can always replace objects with ones that consume less memory. For example, if A is a gigabyte-sized array that you no longer need, you can free the memory with A = 0. The memory will be released the next time the garbage collector runs; you can force this to happen with gc().

Come faccio a ridefinire un tipo?
Types in module Main cannot be redefined. Segue spiegazione lunga e specifica.

Posso usare using o import in una funzione?
No, you are not allowed to have a using or import statement inside a function. If you want to import a module but only use its symbols inside a specific function or set of functions, you have two options:

1. Use import:

import Foo
function bar(...)
    # ... refer to Foo symbols via Foo.baz ...
end

This loads the module Foo and defines a variable Foo that refers to the module, but does not import any of the other symbols from the module into the current namespace. You refer to the Foo symbols by their qualified names Foo.bar etc.

2. Wrap your function in a module:

module Bar
export bar
using Foo
function bar(...)
    # ... refer to Foo.baz as simply baz ....
end
end
using Bar

This imports all the symbols from Foo, but only inside the module Bar.

Qual è la differenza tra using e importall?
There is only one difference, and on the surface (syntax-wise) it may seem very minor. The difference between using and importall is that with using you need to say function Foo.bar(.. to extend module Foo‘s function bar with a new method, but with importall or import Foo.bar, you only need to say function bar(... and it automatically extends module Foo‘s function bar.

If you use importall, then function Foo.bar(... and function bar(... become equivalent. If you use using, then they are different.

Posso usare una versione release, beta o nightly di Julia?
You may prefer the release version of Julia if you are looking for a stable code base. Releases generally occur every 6 months, giving you a stable platform for writing code. Sicuro, io non leggo nemmeno il resto.

Quando vengono rimosse le funzioni deprecate?
Deprecated functions are removed after the subsequent release. For example, functions marked as deprecated in the 0.1 release will not be available starting with the 0.2 release.

🤢

Julia – 114 – guida allo stile – 2

Continuo da qui, copio qui.

Continuo con i consigli, ce ne sono che mi piacciono, altri li considero miei, usati da sempre 😊

Non abusare try-catch
It is better to avoid errors than to rely on catching them.

Non mettere le condizioni tra parentesi
Julia doesn’t require parens around conditions in if and while. Write:

if a == b

instead of:

if (a == b)

Non abusare di ...
Splicing function arguments can be addictive. Instead of [a..., b...], use simply [a; b], which already concatenates arrays. collect(a) is better than [a...], but since a is already iterable it is often even better to leave it alone, and not convert it to an array.

Non usare parametri statici non necessari
A function signature:

foo(x::T) where {T<:Real} = ...

should be written as:

foo(x::Real) = ...

instead, especially if T is not used in the function body. Even if T is used, it can be replaced with typeof(x) if convenient. There is no performance difference. Note that this is not a general caution against static parameters, just against uses where they are not needed.

Note also that container types, specifically may need type parameters in function calls.

Evitare confusioni tra istanze e tipi
Sets of definitions like the following are confusing:

foo(::Type{MyType}) = ...
foo(::MyType) = foo(MyType)

Decide whether the concept in question will be written as MyType or MyType(), and stick to it.

The preferred style is to use instances by default, and only add methods involving Type{MyType} later if they become necessary to solve some problem.

If a type is effectively an enumeration, it should be defined as a single (ideally immutable struct or primitive) type, with the enumeration values being instances of it. Constructors and conversions can check whether values are valid. This design is preferred over making the enumeration an abstract type, with the “values” as subtypes.

Non abusare delle macros
Be aware of when a macro could really be a function instead.

Calling eval() inside a macro is a particularly dangerous warning sign; it means the macro will only work when called at the top level. If such a macro is written as a function instead, it will naturally have access to the run-time values it needs.

Non usare operazioni non sicure (unsafe) nell’interfaccia
If you have a type that uses a native pointer:

mutable struct NativeType
    p::Ptr{UInt8}
    ...
end

don’t write definitions like the following:

getindex(x::NativeType, i) = unsafe_load(x.p, i)

The problem is that users of this type can write x[i] without realizing that the operation is unsafe, and then be susceptible to memory bugs.

Such a function should either check the operation to ensure it is safe, or have unsafe somewhere in its name to alert callers.

Non sovraccaricare (overload) metodi di tipo container di base
It is possible to write definitions like the following:

show(io::IO, v::Vector{MyType}) = ...

This would provide custom showing of vectors with a specific new element type. While tempting, this should be avoided. The trouble is that users will expect a well-known type like Vector() to behave in a certain way, and overly customizing its behavior can make it harder to work with.

Evitare “piracy type”
“Type piracy” refers to the practice of extending or redefining methods in Base or other packages on types that you have not defined. In some cases, you can get away with type piracy with little ill effect. In extreme cases, however, you can even crash Julia (e.g. if your method extension or redefinition causes invalid input to be passed to a ccall). Type piracy can complicate reasoning about code, and may introduce incompatibilities that are hard to predict and diagnose.

As an example, suppose you wanted to define multiplication on symbols in a module:

module A
import Base.*
*(x::Symbol, y::Symbol) = Symbol(x,y)
end

The problem is that now any other module that uses Base.* will also see this definition. Since Symbol is defined in Base and is used by other modules, this can change the behavior of unrelated code unexpectedly. There are several alternatives here, including using a different function name, or wrapping the Symbols in another type that you define.

Sometimes, coupled packages may engage in type piracy to separate features from definitions, especially when the packages were designed by collaborating authors, and when the definitions are reusable. For example, one package might provide some types useful for working with colors; another package could define methods for those types that enable conversions between color spaces. Another example might be a package that acts as a thin wrapper for some C code, which another package might then pirate to implement a higher-level, Julia-friendly API.

OK, cosa molto specifica, non credo di arrivare a tanto 😊

Attenzione ai tipi di ugualianza
You generally want to use isa() and <: (issubtype()) for testing types, not ==. Checking types for exact equality typically only makes sense when comparing to a known concrete type (e.g. T == Float64), or if you really, really know what you’re doing.

Non scrivere x->f(x)
Since higher-order functions are often called with anonymous functions, it is easy to conclude that this is desirable or even necessary. But any function can be passed directly, without being “wrapped” in an anonymous function. Instead of writing map(x->f(x), a), write map(f, a).

Evitare, quando possibile, di usare float in costanti numeriche
If you write generic code which handles numbers, and which can be expected to run with many different numeric type arguments, try using literals of a numeric type that will affect the arguments as little as possible through promotion.

For example,

while

Questa la considero una mia vittoria (molto tardiva) 😁

As you can see, the second version, where we used an Int literal, preserved the type of the input argument, while the first didn’t. This is because e.g. promote_type(Int, Float64) == Float64, and promotion happens with the multiplication. Similarly, Rational literals are less type disruptive than Float64 literals, but more disruptive than Ints:

Thus, use Int literals when possible, with Rational{Int} for literal non-integer numbers, in order to make it easier to use your code.

🤢

Julia – 113 – guida allo stile – 1

Continuo da qui, copio qui.

The following sections explain a few aspects of idiomatic Julia coding style. None of these rules are absolute; they are only suggestions to help familiarize you with the language and to help you choose among alternative designs.

Scrivi funzioni non solo scripts
Writing code as a series of steps at the top level is a quick way to get started solving a problem, but you should try to divide a program into functions as soon as possible. Functions are more reusable and testable, and clarify what steps are being done and what their inputs and outputs are. Furthermore, code inside functions tends to run much faster than top level code, due to how Julia’s compiler works.

It is also worth emphasizing that functions should take arguments, instead of operating directly on global variables (aside from constants like pi).

Evitare tipi troppo specifici
Code should be as generic as possible. Instead of writing:

convert(Complex{Float64}, x)

it’s better to use available generic functions:

complex(float(x))

The second version will convert x to an appropriate type, instead of always the same type.

This style point is especially relevant to function arguments. For example, don’t declare an argument to be of type Int or Int32 if it really could be any integer, expressed with the abstract type Integer. In fact, in many cases you can omit the argument type altogether, unless it is needed to disambiguate from other method definitions, since a MethodError will be thrown anyway if a type is passed that does not support any of the requisite operations. (This is known as duck typing.)

For example, consider the following definitions of a function addone that returns one plus its argument:

addone(x::Int) = x + 1                 # works only for Int
addone(x::Integer) = x + oneunit(x)    # any integer type
addone(x::Number) = x + oneunit(x)     # any numeric type
addone(x) = x + oneunit(x)             # any type supporting + and oneunit

The last definition of addone handles any type supporting oneunit (which returns 1 in the same type as x, which avoids unwanted type promotion) and the + function with those arguments. The key thing to realize is that there is no performance penalty to defining only the general addone(x) = x + oneunit(x), because Julia will automatically compile specialized versions as needed. For example, the first time you call addone(12), Julia will automatically compile a specialized addone function for x::Int arguments, with the call to oneunit replaced by its inlined value 1. Therefore, the first three definitions of addone above are completely redundant with the fourth definition.

Gestire la diversità degli argomenti nella chiamata
Instead of:

function foo(x, y)
    x = Int(x); y = Int(y)
    ...
end
foo(x, y)

use:

function foo(x::Int, y::Int)
    ...
end
foo(Int(x), Int(y))

This is better style because foo does not really accept numbers of all types; it really needs Ints.

One issue here is that if a function inherently requires integers, it might be better to force the caller to decide how non-integers should be converted (e.g. floor or ceiling). Another issue is that declaring more specific types leaves more “space” for future method definitions.

Aggiungere ! alle funzioni che modificano i loro argomenti
Instead of:

function double(a::AbstractArray{<:Number})
    for i = 1:endof(a)
        a[i] *= 2
    end
    return a
end

use:

function double!(a::AbstractArray{<:Number})
    for i = 1:endof(a)
        a[i] *= 2
    end
    return a
end

The Julia standard library uses this convention throughout and contains examples of functions with both copying and modifying forms (e.g., sort() and sort!()), and others which are just modifying (e.g., push!(), pop!(), splice!()). It is typical for such functions to also return the modified array for convenience.

Evitare tipi di unioni strane
Types such as Union{Function,AbstractString} are often a sign that some design could be cleaner.

Evitare tipi di unioni nei campi
When creating a type such as:

mutable struct MyType
    ...
    x::Union{Void,T}
end

ask whether the option for x to be nothing (of type Void) is really necessary. Here are some alternatives to consider:

  • Find a safe default value to initialize x with
  • Introduce another type that lacks x
  • If there are many fields like x, store them in a dictionary
  • Determine whether there is a simple rule for when x is nothing. For example, often the field will start as nothing but get initialized at some well-defined point. In that case, consider leaving it undefined at first.
  • If x really needs to hold no value at some times, define it as ::Nullable{T} instead, as this guarantees type-stability in the code accessing this field.

Evitare tipi di containers elaborati
It is usually not much help to construct arrays like the following:

a = Array{Union{Int,AbstractString,Tuple,Array}}(n)

In this case Array{Any}(n) is better. It is also more helpful to the compiler to annotate specific uses (e.g. a[i]::Int) than to try to pack many alternatives into one type.

Usare nomi consistenti con base/ di Julia

  • modules and type names use capitalization and camel case: module SparseArrays, struct UnitRange.
  • functions are lowercase (maximum(), convert()) and, when readable, with multiple words squashed together (isequal(), haskey()). When necessary, use underscores as word separators. Underscores are also used to indicate a combination of concepts (remotecall_fetch() as a more efficient implementation of fetch(remotecall(...))) or as modifiers (sum_kbn()).
  • conciseness is valued, but avoid abbreviation (indexin() rather than indxin()) as it becomes difficult to remember whether and how particular words are abbreviated.

If a function name requires multiple words, consider whether it might represent more than one concept and might be better split into pieces.

Uh! ancora tante raccomandazioni, pausa 😊

🤢

Julia – 112 – suggerimenti operativi

Continuo da qui, copio qui.

Here are some tips for working with Julia efficiently.

Con la REPL
As already elaborated in Interacting With Julia [qui], Julia’s REPL provides rich functionality that facilitates an efficient interactive workflow. Here are some tips that might further enhance your experience at the command line.

Un editor nella REPL
The most basic Julia workflows involve using a text editor in conjunction with the julia command line. A common pattern includes the following elements:

  • Put code under development in a temporary module. Create a file, say Tmp.jl, and include within it
    module Tmp
    
     <your definitions here>
    
    end

    Put your test code in another file. Create another file, say tst.jl, which begins with

  • import Tmp
    and includes tests for the contents of Tmp. The value of using import versus using is that you can call reload("Tmp") instead of having to restart the REPL when your definitions change. Of course, the cost is the need to prepend Tmp. to uses of names defined in your module. (You can lower that cost by keeping your module name short.)
    Alternatively, you can wrap the contents of your test file in a module, as

    module Tst
        using Tmp
    
        <scratch work>
    
    end

    The advantage is that you can now do using Tmp in your test code and can therefore avoid prepending Tmp. everywhere. The disadvantage is that code can no longer be selectively copied to the REPL without some tweaking.

  • Lather. Rinse. Repeat. Explore ideas at the julia command prompt. Save good ideas in tst.jl. Occasionally restart the REPL, issuing
    reload("Tmp")
    include("tst.jl")

Semplificare l’inizializzazione
To simplify restarting the REPL, put project-specific initialization code in a file, say _init.jl, which you can run on startup by issuing the command:

julia -L _init.jl

If you further add the following to your .juliarc.jl file

isfile("_init.jl") && include(joinpath(pwd(), "_init.jl"))

then calling julia from that directory will run the initialization code without the additional command line argument.

Nel browser
It is also possible to interact with a Julia REPL in the browser via IJulia. See the package home for details.

🤢

Julia – 111 – suggerimenti di performance – 6

Continuo da qui, copio qui.

Annotazioni sulla prestazione
Sometimes you can enable better optimization by promising certain program properties.

Use @inbounds to eliminate array bounds checking within expressions. Be certain before doing this. If the subscripts are ever out of bounds, you may suffer crashes or silent corruption.

Use @fastmath to allow floating point optimizations that are correct for real numbers, but lead to differences for IEEE numbers. Be careful when doing this, as this may change numerical results. This corresponds to the -ffast-math option of clang.

Write @simd in front of for loops that are amenable to vectorization. This feature is experimental and could change or disappear in future versions of Julia.

Note: While @simd needs to be placed directly in front of a loop, both @inbounds and @fastmath can be applied to several statements at once, e.g. using begin ... end, or even to a whole function.

Here is an example with both @inbounds and @simd markup:

function inner(x, y)
    s = zero(eltype(x))
    for i=1:length(x)
        @inbounds s += x[i]*y[i]
    end
    s
end

function innersimd(x, y)
    s = zero(eltype(x))
    @simd for i=1:length(x)
        @inbounds s += x[i]*y[i]
    end
    s
end

function timeit(n, reps)
    x = rand(Float32,n)
    y = rand(Float32,n)
    s = zero(Float64)
    time = @elapsed for j in 1:reps
        s+=inner(x,y)
    end
    println("GFlop/sec        = ",2.0*n*reps/time*1E-9)
    time = @elapsed for j in 1:reps
        s+=innersimd(x,y)
    end
    println("GFlop/sec (SIMD) = ",2.0*n*reps/time*1E-9)
end

timeit(1000,1000)

(GFlop/sec measures the performance, and larger numbers are better.) The range for a @simd for loop should be a one-dimensional range. A variable used for accumulating, such as s in the example, is called a reduction variable. By using @simd, you are asserting several properties of the loop:

It is safe to execute iterations in arbitrary or overlapping order, with special consideration for reduction variables.

Floating-point operations on reduction variables can be reordered, possibly causing different results than without @simd.

No iteration ever waits on another iteration to make forward progress.

A loop containing break, continue, or @goto will cause a compile-time error.

Using @simd merely gives the compiler license to vectorize. Whether it actually does so depends on the compiler. To actually benefit from the current implementation, your loop should have the following additional properties:

The loop must be an innermost loop.

The loop body must be straight-line code. This is why @inbounds is currently needed for all array accesses. The compiler can sometimes turn short &&, ||, and ?: expressions into straight-line code, if it is safe to evaluate all operands unconditionally. Consider using ifelse() instead of ?: in the loop if it is safe to do so.

Accesses must have a stride pattern and cannot be “gathers” (random-index reads) or “scatters” (random-index writes).

The stride should be unit stride.

In some simple cases, for example with 2-3 arrays accessed in a loop, the LLVM auto-vectorization may kick in automatically, leading to no further speedup with @simd.

Here is an example with all three kinds of markup. This program first calculates the finite difference of a one-dimensional array, and then evaluates the L2-norm of the result:

wave.jl

function init!(u)
    n = length(u)
    dx = 1.0 / (n-1)
    @fastmath @inbounds @simd for i in 1:n
        u[i] = sin(2pi*dx*i)
    end
end

function deriv!(u, du)
    n = length(u)
    dx = 1.0 / (n-1)
    @fastmath @inbounds du[1] = (u[2] - u[1]) / dx
    @fastmath @inbounds @simd for i in 2:n-1
        du[i] = (u[i+1] - u[i-1]) / (2*dx)
    end
    @fastmath @inbounds du[n] = (u[n] - u[n-1]) / dx
end

function norm(u)
    n = length(u)
    T = eltype(u)
    s = zero(T)
    @fastmath @inbounds @simd for i in 1:n
        s += u[i]^2
    end
    @fastmath @inbounds return sqrt(s/n)
end

function main()
    n = 2000
    u = Array{Float64}(n)
    init!(u)
    du = similar(u)

    deriv!(u, du)
    nu = norm(du)

    @time for i in 1:10^6
        deriv!(u, du)
        nu = norm(du)
    end

    println(nu)
end

main()

Here, the option --math-mode=ieee disables the @fastmath macro, so that we can compare results.

In this case, the speedup due to @fastmath is a factor of about 3.7. This is unusually large – in general, the speedup will be smaller. (In this particular example, the working set of the benchmark is small enough to fit into the L1 cache of the processor, so that memory access latency does not play a role, and computing time is dominated by CPU usage. In many real world programs this is not the case.) Also, in this case this optimization does not change the result – in general, the result will be slightly different. In some cases, especially for numerically unstable algorithms, the result can be very different.

The annotation @fastmath re-arranges floating point expressions, e.g. changing the order of evaluation, or assuming that certain special cases (inf, nan) cannot occur. In this case (and on this particular computer), the main difference is that the expression 1 / (2*dx) in the function deriv is hoisted out of the loop (i.e. calculated outside the loop), as if one had written idx = 1 / (2*dx). In the loop, the expression ... / (2*dx) then becomes ... * idx, which is much faster to evaluate. Of course, both the actual optimization that is applied by the compiler as well as the resulting speedup depend very much on the hardware. You can examine the change in generated code by using Julia’s code_native() function.

Trattare subnormal numbers come zero
Subnormal numbers, formerly called denormal numbers, are useful in many contexts, but incur a performance penalty on some hardware. A call set_zero_subnormals(true) grants permission for floating-point operations to treat subnormal inputs or outputs as zeros, which may improve performance on some hardware. A call set_zero_subnormals(false) enforces strict IEEE behavior for subnormal numbers.

Below is an example where subnormals noticeably impact performance on some hardware:

function timestep(b::Vector{T}, a::Vector{T}, Δt::T) where T
    @assert length(a)==length(b)
    n = length(b)
    b[1] = 1                            # Boundary condition
    for i=2:n-1
        b[i] = a[i] + (a[i-1] - T(2)*a[i] + a[i+1]) * Δt
    end
    b[n] = 0                            # Boundary condition
end

function heatflow(a::Vector{T}, nstep::Integer) where T
    b = similar(a)
    for t=1:div(nstep,2)                # Assume nstep is even
        timestep(b,a,T(0.1))
        timestep(a,b,T(0.1))
    end
end

heatflow(zeros(Float32,10),2)           # Force compilation
for trial=1:6
    a = zeros(Float32,1000)
    set_zero_subnormals(iseven(trial))  # Odd trials use strict IEEE arithmetic
    @time heatflow(a,1000)
end

This example generates many subnormal numbers because the values in a become an exponentially decreasing curve, which slowly flattens out over time.

Treating subnormals as zeros should be used with caution, because doing so breaks some identities, such as x-y == 0 implies x == y:

In some applications, an alternative to zeroing subnormal numbers is to inject a tiny bit of noise. For example, instead of initializing a with zeros, initialize it with:

a = rand(Float32,1000) * 1.f-9

@code_warntype
The macro @code_warntype (or its function variant code_warntype()) can sometimes be helpful in diagnosing type-related problems. Here’s an example:

Ecco l’applicazione di @code_warntype a f(3.2):

@code_warntype f(3.2)
Variables:
  #self#::#f
  x::Float64
  y::Union{Float64, Int64}
  fy::Float64
  #temp#@_5::Union{Float64, Int64}
  #temp#@_6::Core.MethodInstance
  #temp#@_7::Float64
  #temp#@_8::Float64

Body:
  begin 
      $(Expr(:inbounds, false))
      # meta: location REPL[8] pos 1
      # meta: location float.jl < 491
      fy::Float64 = (Base.sitofp)(Float64, 0)::Float64
      # meta: pop location
      unless (Base.or_int)((Base.lt_float)(x::Float64, fy::Float64)::Bool, (Base.and_int)((Base.and_int)((Base.eq_float)(x::Float64, fy::Float64)::Bool, (Base.lt_float)(fy::Float64, 9.223372036854776e18)::Bool)::Bool, (Base.slt_int)((Base.fptosi)(Int64, fy::Float64)::Int64, 0)::Bool)::Bool)::Bool goto 9
      #temp#@_5::Union{Float64, Int64} = 0
      goto 11
      9: 
      #temp#@_5::Union{Float64, Int64} = x::Float64
      11: 
      # meta: pop location
      $(Expr(:inbounds, :pop))
      y::Union{Float64, Int64} = #temp#@_5::Union{Float64, Int64} # line 3:
      unless (y::Union{Float64, Int64} isa Int64)::Bool goto 19
      #temp#@_6::Core.MethodInstance = MethodInstance for *(::Int64, ::Float64)
      goto 28
      19: 
      unless (y::Union{Float64, Int64} isa Float64)::Bool goto 23
      #temp#@_6::Core.MethodInstance = MethodInstance for *(::Float64, ::Float64)
      goto 28
      23: 
      goto 25
      25: 
      #temp#@_7::Float64 = (y::Union{Float64, Int64} * x::Float64)::Float64
      goto 30
      28: 
      #temp#@_7::Float64 = $(Expr(:invoke, :(#temp#@_6), :(Main.*), :(y), :(x)))
      30:
      SSAValue(0) = (Base.add_float)(#temp#@_7::Float64, (Base.sitofp)(Float64, 1)::Float64)::Float64
      $(Expr(:inbounds, false))
      # meta: location math.jl sin 419
      SSAValue(2) = $(Expr(:foreigncall, ("sin", "libopenlibm"), Float64, svec(Float64), SSAValue(0), 0))
      # meta: location math.jl nan_dom_err 300
      unless (Base.and_int)((Base.ne_float)(SSAValue(2), SSAValue(2))::Bool, (Base.not_int)((Base.ne_float)(SSAValue(0), SSAValue(0))::Bool)::Bool)::Bool goto 39
      #temp#@_8::Float64 = (Base.Math.throw)($(QuoteNode(DomainError())))::Union{}
      goto 41
      39: 
      #temp#@_8::Float64 = SSAValue(2)
      41: 
      # meta: pop location
      # meta: pop location
      $(Expr(:inbounds, :pop))
      return #temp#@_8::Float64
  end::Float64

Interpreting the output of @code_warntype, like that of its cousins @code_lowered, @code_typed, @code_llvm, and @code_native, takes a little practice. Your code is being presented in form that has been partially digested on its way to generating compiled machine code. Most of the expressions are annotated by a type, indicated by the ::T (where T might be Float64, for example). The most important characteristic of @code_warntype is that non-concrete types are displayed in red.

The top part of the output summarizes the type information for the different variables internal to the function. You can see that y, one of the variables you created, is a Union{Int64,Float64}, due to the type-instability of pos. There is another variable, _var4, which you can see also has the same type.

The next lines represent the body of f. The lines starting with a number followed by a colon (1:, 2:) are labels, and represent targets for jumps (via goto) in your code. Looking at the body, you can see that pos has been inlined into f–everything before 2: comes from code defined in pos.

Starting at 2:, the variable y is defined, and again annotated as a Union type. Next, we see that the compiler created the temporary variable _var1 to hold the result of y*x. Because a Float64 times either an Int64 or Float64 yields a Float64, all type-instability ends here. The net result is that f(x::Float64) will not be type-unstable in its output, even if some of the intermediate computations are type-unstable.

How you use this information is up to you. Obviously, it would be far and away best to fix pos to be type-stable: if you did so, all of the variables in f would be concrete, and its performance would be optimal. However, there are circumstances where this kind of ephemeral type instability might not matter too much: for example, if pos is never used in isolation, the fact that f‘s output is type-stable (for Float64 inputs) will shield later code from the propagating effects of type instability. This is particularly relevant in cases where fixing the type instability is difficult or impossible: for example, currently it’s not possible to infer the return type of an anonymous function. In such cases, the tips above (e.g., adding type annotations and/or breaking up functions) are your best tools to contain the “damage” from type instability.

The following examples may help you interpret expressions marked as containing non-leaf types:

  • Function body ending in end::Union{T1,T2})
    Interpretation: function with unstable return type
    Suggestion: make the return value type-stable, even if you have to annotate it
  • f(x::T)::Union{T1,T2}
    Interpretation: call to a type-unstable function
    Suggestion: fix the function, or if necessary annotate the return value
  • (top(arrayref))(A::Array{Any,1},1)::Any
    Interpretation: accessing elements of poorly-typed arrays
    Suggestion: use arrays with better-defined types, or if necessary annotate the type of individual element accesses
  • (top(getfield))(A::ArrayContainer{Float64},:data)::Array{Float64,N}
    Interpretation: getting a field that is of non-leaf type. In this case, ArrayContainer had a field data::Array{T}. But Array needs the dimension N, too, to be a concrete type.
    Suggestion: use concrete types like Array{T,3} or Array{T,N}, where N is now a parameter of ArrayContainer.

🤢