Julia: modèle de programmation et cas d'usage

Alternative title: the sound, the hacky, the mathy
Mathieu Besançon

20-09-2018 - LilleFP meetup

  1. Julia, types et modèle de programmation
  2. Compilation à la volée
  3. Exemple d'interface idiomatique: tableaux
  4. Bonus: build, macros

Bio

Doctorant @ INRIA Lille & Polytechnique Montréal
Optimisation mathématique (programmation mathématique) pour les smart grids
Code au quotidien: créer des modèles, manipuler des données, lancer des calculs, connecter des solveurs

Ex-Equisense @ Euratech --> data science, traitement de signal, back-end
Autres langages utilisés: Rust, Python, R, Go

Twitter/Github @matbesancon

Julia est...

  • Compilé + REPL rapide
  • Dynamique, typé (avec une partie statiquement garantie)
  • Inspiré par Ruby, Matlab, Python, Lisp/Scheme, Fortran (entre autres)
  • Haut-niveau avec accès et manipulation bas-niveau
  • Backend LLVM

Typage optionnel

(Demo live)

Compilation à la volée

Un compilateur c'est bien...
Mais avec des cas d'usage dynamiques, feedback rapide essentiel

In [1]:
function line(x; a = 3.0, b = π)
    return a*x + b
end
Out[1]:
line (generic function with 1 method)
In [2]:
@time line(42)
  0.009094 seconds (22.09 k allocations: 1.216 MiB)
Out[2]:
129.14159265358978
In [3]:
@time line(42)
  0.000003 seconds (5 allocations: 176 bytes)
Out[3]:
129.14159265358978
In [4]:
@time line(4//3)
  0.054816 seconds (79.88 k allocations: 4.005 MiB)
Out[4]:
7.141592653589793
In [5]:
@time line(4//3)
  0.000004 seconds (6 allocations: 208 bytes)
Out[5]:
7.141592653589793

Les types de Julia

  • Hiérarchie des type en arborescence
  • Tout type s'attache à un seul type abstrait
In [6]:
abstract type ValueWrapper{T} end

struct FloatWrapper <: ValueWrapper{Float64}
    v::Float64
end

struct NumWrapper{T<:Number} <: ValueWrapper{T}
    v::T
end
  • Types abstraits: permettent de définir la "nature" d'un type
  • Aucun type abstrait implémentable
  • Tous les types concrets sont finaux
In [7]:
const 😱 = "one inheritence == one bullet";

Type abstrait nécéssaire pour peu de cas.
SimpleTraits.jl permet la création de traits hors de la hiérarchie des types.

In [8]:
import Plots: plot

struct Line
    a::Float64
    b::Float64
end

function plot(l::Line)
    xs = 0.0:0.01:10.0
    ys = map(xs) do x
        l.a * x + l.b
    end
    plot(xs,ys)
end
Out[8]:
plot (generic function with 4 methods)

Exemple d'interface informelle

Array definition: {Type, Dimension}

AbstractArray{T,N}

Créons un nouveau type représentant un array non mutable

In [9]:
struct OneArray{T<:Number,N} <: AbstractArray{T,N}
    s::NTuple{N,Int}
end
In [10]:
import Base: getindex, size

# consider we don't need boundchecking
getindex(::OneArray{T,N}, idxs...) where {T,N} = one(T)
size(oa::OneArray) = oa.s

const oa = OneArray{Int,2}((10,10))
Out[10]:
10×10 OneArray{Int64,2}:
 1  1  1  1  1  1  1  1  1  1
 1  1  1  1  1  1  1  1  1  1
 1  1  1  1  1  1  1  1  1  1
 1  1  1  1  1  1  1  1  1  1
 1  1  1  1  1  1  1  1  1  1
 1  1  1  1  1  1  1  1  1  1
 1  1  1  1  1  1  1  1  1  1
 1  1  1  1  1  1  1  1  1  1
 1  1  1  1  1  1  1  1  1  1
 1  1  1  1  1  1  1  1  1  1
In [11]:
using BenchmarkTools: @btime
@btime sum(oa)
  158.551 ns (0 allocations: 0 bytes)
Out[11]:
100
In [12]:
import Base: sum
sum(oa::OneArray{T,N}) where {T,N} = one(T) * prod(oa.s)
Out[12]:
sum (generic function with 23 methods)
In [13]:
@btime sum(oa)
  1.691 ns (0 allocations: 0 bytes)
Out[13]:
100

Généralement le times-up moment

I miss Elixir, gimme my pipe

In [14]:
using Pipe: @pipe
In [15]:
# default behavior
rand() |> x -> 2x |> sqrt |> println

@pipe (1.5,2.) |> 2*_[1] + _[2] |> abs2 |> println
1.1752558371383286
25.0
In [16]:
# macros <==> expressions as data
const ex = :(2 + 3 + 7)
Out[16]:
:(2 + 3 + 7)
In [17]:
# macros <==> expressions as data
eval(ex)
Out[17]:
12

Pkg, outil de build & gestion de dépendances

Compromis nécessaire:

  • Développement efficace de packages
  • Interactivité

Inspiration: Cargo (Rust), Virtualenv (Python)
À voir: Pkg3 talk @JuliaCon2018

In [18]:
abstract type  Bit end
struct Zero <: Bit end
struct One  <: Bit end

import Base: &, |
(&)(::One, ::One) = One()
(&)(::Bit, ::Bit) = Zero()
(|)(::Zero, ::Zero) = Zero()
(|)(::Bit,  ::Bit)  = One()
Out[18]:
| (generic function with 19 methods)
In [19]:
@code_native One() & (Zero() | (One() & Zero()))
	.text
; Function & {
; Location: In[18]:7
	movq	%rsi, -8(%rsp)
	movabsq	$139910743260088, %rax  # imm = 0x7F3F8226FBB8
	retq
;}
In [20]:
unsafe_load(Base.unsafe_convert(Ptr{Ptr{Nothing}}, pointer_from_objref(Zero) + 0x28))
Out[20]:
Ptr{Nothing} @0x00007f3f8226fbb8

Pour plus sur la méta-programmation: Andy Ferris, Juliacon 2018

Questions, remarques?

N'hésitez pas à regarder les talks de la dernière JuliaCon2018

Slides disponibles bientôt sur https://matbesancon.github.io/talk