Overload di funzioni in Rust - Come ho imparato a vivere felicemente senza
1. Overload di funzioni in Rust - Come ho
imparato a vivere felicemente senza
Nicola Musatti
nicola.musatti@gmail.com
@NMusatti
Rust Milano Meetup - 24 gennaio 2018
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
2. Chi sono
• Un appassionato di C++ di vecchia data, disilluso dalla complessità sempre
maggiore del linguaggio
• Lavoro in Java e su attività non di programmazione
• Un principiante Rust di lungo corso
• Adoro anche Python!
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
3. Cosa ci facciamo qui
• Cos’è l’overload di funzioni e cosa c’è di buono
• Cosa si può fare quando non è supportato
• Quali funzionalità di Rust possiamo usare per affrontare gli stessi problemi
• Alcune implicazioni di portata più ampia
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
4. Un esempio semplice
• Un tipo semplice: Element
• Un tipo collezione di Element: Collection
• Vogliamo aggiungere singoli elementi a una collezione
• Vogliamo aggiungere ad una collezione tutti gli elementi di un’altra
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
5. Come l’avremmo scritto in C
#define MAX_SIZE 100
struct Element {
};
struct Collection {
struct Element * elements[MAX_SIZE];
int size;
};
void add_element(struct Collection * self, struct Element * el) {
if (self->size < MAX_SIZE)
self->elements[self->size++] = el;
}
void add_collection(struct Collection * self, struct Collection * cl) {
int i;
for (i = 0; i < cl->size && self->size < MAX_SIZE; i++)
self->elements[self->size++] = cl->elements[i];
}
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
6. Lo stesso in Rust
pub struct Element {
}
pub struct Collection {
pub elements: Vec<Element>
}
impl Collection {
pub fn add_element(& mut self, e: Element) {
self.elements.push(e);
}
pub fn add_collection(& mut self, c: Collection) {
self.elements.extend(c.elements);
}
}
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
7. Qual è il problema
• Espressività ridotta: devo ripetere i nomi dei tipi solo per disambiguare
• I nomi sono più distanti dal dominio del problema
• Le funzioni non possono essere invocate in modo generico (problema specifico
C++)
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
8. Una versione C++
#include <vector>
class Element {
};
class Collection {
public:
void add(Element const& e) {
elements.push_back(e);
}
void add(Collection const& c) {
elements.insert(elements.end(), c.elements.begin(), c.elements.end());
}
std::vector<Element> elements;
};
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
9. Cos’è l’overload di funzioni
• La possibilità di definire due o più funzioni con lo stesso nome che prendono
parametri che differiscono per tipo e/o numero
• La possibilità di chiamare una «funzione» con tipi e/o numero di parametri diversi,
indipendentemente da come ciò è ottenuto
Chiamare è la cosa importante se definire non è troppo complicato
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
10. Costrutti collegati
• Generici o template
• Argomenti di default
• «Duck typing»
• Ereditarietà (?)
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
11. Un esempio con template C++
template <typename T> void add(Collection & c, T const & t) {
c.add(t);
}
int main() {
Element e;
Collection c;
add(c, e);
Collection c1;
c1.elements.insert(c1.elements.end(), 5, Element());
add(c, c1);
return c.elements.size() == 6 ? 0 : 1;
}
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
12. Un esempio di «duck typing» in Python
class Element:
pass
class Collection:
def __init__(self):
self.elements = []
def add(self, arg):
if isinstance(arg, Collection):
self.elements.extend(arg.elements)
else:
self.elements.append(arg)
def test(self):
e = Element()
c = Collection()
c.add(e)
c1 = Collection()
c1.elements.append(Element())
c.add(c1)
self.assertTrue(len(c.elements) == 2)
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
13. Un esempio in Python con argomenti di default
class Element:
pass
class Collection:
def __init__(self):
self.elements = []
def add(self, element=None, collection=None):
if collection is not None:
self.elements.extend(collection.elements)
else:
self.elements.append(element)
def test(self):
e = Element()
c = Collection()
c.add(e)
c1 = Collection()
c1.elements.append(Element())
c.add(collection=c1)
self.assertTrue(len(c.elements) == 2)
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
14. Bé…
L’overload è meglio!
• E” più pulito
• Rispetta il principio Aperto/Chiuso
• Gli switch sui tipi sono malvisti nei linguaggi che non hanno gli enum di Rust
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
15. E Rust?
• Una forma limitata di overload può essere implementata coi trait:
• Il numero di parametri non può variare
• L’implementazione è un po” più complicata
• C’è una discrepanza concettuale: i trait forniscono un livello aggiuntivo di
astrazione, l’overload non dovrebbe
• Non supporta gli argomenti di default
• Non supporta il «duck typing»: Rust scoraggia o proibisce il completamento
casuale
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
16. Esempio di overload in Rust
pub struct Element {
}
pub struct Collection {
pub elements: Vec<Element>
}
trait Addable<T> {
fn add(& mut self, t: T);
}
impl Addable<Element> for Collection {
fn add(& mut self, t: Element) {
self.elements.push(t);
}
}
impl Addable<Collection> for Collection {
fn add(& mut self, t: Collection) {
self.elements.extend(t.elements);
}
}
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
17. Esempio di overload in Rust (cont.)
fn test() {
let e = Element{};
let mut c = Collection{ elements: Vec::new() };
let c1 = Collection{ elements: Vec::new() };
c.add(e);
c.add(c1);
}
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
18. Anche l’overload ha i suoi problemi!
Supponiamo di voler implementare un tipo Point che possa essere costruito:
• Da coordinate cartesiane
• Da coordinate polari
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
19. Un esempio (errato) in C++
#include <cmath>
class Point {
public:
Point(double x, double y) : x(x), y(y) {
}
Point(double r, double t) : x(r * std::cos(t)), y(r * std::sin(t)) {
}
private:
double x, y;
};
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
20. Gli argomenti di default di Python sono di aiuto
class Point:
def __init__(self, x=None, y=None, r=None, t=None):
if x is not None and y is not None:
self.x = x
self.y = y
else:
self.x = r * math.cos(t)
self.y = r * math.sin(t)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def test(self):
p1 = Point(x=2.0, y=0.0)
p2 = Point(r=2.0, t=0.0)
self.assertTrue(p1 == p2)
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
21. Rust viene in soccorso
…o come per me dovrebbero essere implementate le funzioni sovraccaricate in Rust:
Un’unica funzione con un unico parametro enum che elenchi tutte le
combinazioni di argomenti da supportare
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
22. L’esempio del tipo Point in Rust
pub enum Coordinates {
Cartesian(f64, f64),
Polar(f64, f64)
}
#[derive(PartialEq)]
pub struct Point {
pub x: f64,
pub y: f64
}
impl Point {
pub fn new(c: Coordinates) -> Point {
use self::Coordinates::*;
match c {
Cartesian(x, y) => Point{x,y},
Polar(r, t) => Point{ x: r * t.cos(), y: r * t.sin() }
}
}
}
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
23. Estendiamo l’esempio Point in Rust
pub enum Angle {
Degrees(f64),
Radians(f64)
}
pub enum Coordinates {
Cartesian(f64, f64),
Polar(f64, Angle)
}
#[derive(PartialEq)]
pub struct Point {
pub x: f64,
pub y: f64
}
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
24. Estendiamo l’esempio Point in Rust (cont.)
impl Point {
pub fn new(c: Coordinates) -> Point {
use self::Coordinates::*;
use std::f64::consts::*;
match c {
Cartesian(x, y) => Point{x,y},
Polar(r, a) => {
let t = match a {
Angle::Degrees(d) => d * 2. * PI / 360.,
Angle::Radians(r) => r
};
Point{ x: r * t.cos(), y: r * t.sin() }
}
}
}
}
fn test() {
let c = Point::new(Coordinates::Cartesian(2.,0.));
let p = Point::new(Coordinates::Polar(2.,Angle::Radians(0.)));
assert!(c == p);
}
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
25. L’esempio originale in Rust
pub struct Element {
}
pub struct Collection {
pub elements: Vec<Element>
}
pub enum Args {
Elem(Element),
Coll(Collection)
}
impl Collection {
pub fn add(&mut self, args: Args) {
match args {
Args::Elem(e) => self.elements.push(e),
Args::Coll(c) => self.elements.extend(c.elements)
}
}
}
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
26. Cosa non va
• Chiamare la funzione è un po” più prolisso dell’overload vero e proprio
• Viola il principio Aperto/Chiuso: aggiungere combinazioni di parametri richiede di
modificare codice esistente
• L’enum degli argomenti può risultare un po” artificiale
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
27. Cosa c’è di buono
• Le alternative disponibili sono descritte in un unico posto
• L’intenzione è espressa esplicitamente attraverso la scelta dell’enumerazione
appropriata
• Rust impone che tutti i casi dichiarati siano gestiti
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
28. C’è di più
• Un aspetto fondamentale della tipizzazione statica è l’imposizione di vincoli sui
parametri
• E” meglio fare i controlli subito in modo che il type system inoltri i vincoli
automaticamente
• L’enum degli argomenti sposta il controllo dei parametri fuori dalla funzione
• Questo si potrebbe estendere ad altri controlli:
• Range di validità
• Vincoli combinati
• Purtroppo la mancanza di costruttori standard rende la sintassi non proprio ideale
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
29. Un ultimo esempio
use num::Float;
pub struct Positive<T: Float> {
pub value: T
}
impl<T: Float> Positive<T> {
pub fn new(t: T) -> Positive<T> {
assert!(t >= T::zero());
Positive{ value : t }
}
}
pub fn sqrt<T: Float>(v: Positive<T>) -> T {
v.value.sqrt()
}
fn test() {
assert!(sqrt(Positive::new(4.0)) == 2.0);
}
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018
30. Fine
Domande o pizza, questo è il dilemma
Nicola Musatti - Function Overloading in Rust - Rust Milano Meetup - Mikamai - 24 January 2018