Slide 1

Slide 1 text

The Freedom of Static Typing

Slide 2

Slide 2 text

@AndrewRadev

Slide 3

Slide 3 text

Rad Dev

Slide 4

Slide 4 text

@AndrewRadev Static typing (in Rust)

Slide 5

Slide 5 text

@AndrewRadev Old haskell(ish) talk Return-type polymorphism? Algebraic data types? Modeling event loops as infinite lists? M Mo on na ad ds s? ?

Slide 6

Slide 6 text

@AndrewRadev Old haskell(ish) talk Runtime errors! “Mathematically impossible to have errors in ” Your language has too much freedom! You can’t be trusted with it!

Slide 7

Slide 7 text

@AndrewRadev Ruby: It’s Really Not That Bad # This is good: Person.walk_the_dog(Dog.new) # But WHAT IF? Person.walk_the_dog(Cat.new) # Solution: don’t do that ¯\_( ツ )_/¯

Slide 8

Slide 8 text

@AndrewRadev Ruby: It’s Really Not That Bad Processes, tools, tests Peer-review Error tracking in prod

Slide 9

Slide 9 text

@AndrewRadev Some things really are dangerous https://twitter.com/thegrugq/status/1141945136122740736 free() free()

Slide 10

Slide 10 text

@AndrewRadev Static typing as a… necessary limitation? :/

Slide 11

Slide 11 text

@AndrewRadev Static typing as a form of freedom! :)

Slide 12

Slide 12 text

@AndrewRadev Enums (Algebraic data types) (Sum types)

Slide 13

Slide 13 text

@AndrewRadev enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, } enum IpAddr { V4(u8, u8, u8, u8), V6(String), }

Slide 14

Slide 14 text

@AndrewRadev enum Option { Some(T), None, } fn main() { let args = vec![1, 2, 3]; if let Some(value) = args.get(2) { println!("Third arg is: {}", value); } else { println!("Nope, nothing here"); } }

Slide 15

Slide 15 text

@AndrewRadev enum Result { Ok(T), Err(E), } fn read_file(path: &str) -> Result { // ... } fn main() { // settings: Result let settings = read_file("~/settings.json"); // But why not a pair (T, E)? let (settings, error) = read_file("~/settings.json"); }

Slide 16

Slide 16 text

@AndrewRadev fn read_file(path: &str) -> Result> { // ... } fn parse_settings(json: &str) -> Result> { // ... } fn main() { let settings = read_file("~/settings.json"). or_else(|_| read_file("~/.config/settings.json")). and_then(|json| parse_settings(&json)). unwrap_or_else(|_| Settings::default()); println!("{:?}", settings); }

Slide 17

Slide 17 text

@AndrewRadev class Result attr_reader :value, :error def initialize(value: nil, error: nil) @value = value @error = error end end

Slide 18

Slide 18 text

@AndrewRadev class Result def and_then(&block) if value block.call(value) else self end end def or_else(&block) if error block.call(error) else self end end end

Slide 19

Slide 19 text

@AndrewRadev # Successful result: Result.new(value: "Pumpkin spice latte") # Errorful result: Result.new(error: "Ran out of pumpkins!") # Is this possible? :/ What do we do here? Result.new( value: "Everything is great!", error: "Everything is broken!" )

Slide 20

Slide 20 text

@AndrewRadev class Result def and_then(&block) if value # ... end end def or_else(&block) if error # ... end end end # Add runtime checks?

Slide 21

Slide 21 text

@AndrewRadev class Event def initialize(...) @type = "initialization" @type = "cascade" # ... @alchemiter = ... @laserstation = ... @punch_designix = ... # ... end end

Slide 22

Slide 22 text

@AndrewRadev enum Event { Initialization { alchemiter: ..., laserstation: ..., }, Cascade { laserstation: ..., punch_designix: ..., }, // ... }

Slide 23

Slide 23 text

@AndrewRadev Types as containers Classes/Structs Pairs/Tuples Lists Dictionaries

Slide 24

Slide 24 text

@AndrewRadev Enums: not just containers Enums express exclusivity

Slide 25

Slide 25 text

@AndrewRadev class Success def and_then(&block) block.call(value) end def or_else # do nothing end end class Error def and_then(&block) # do nothing end def or_else block.call(error) end end

Slide 26

Slide 26 text

@AndrewRadev Dynamic typing Implicit knowledge: Success and Error should have the same methods. Implicit knowledge: Result has either a value or an error

Slide 27

Slide 27 text

@AndrewRadev Static typing Explicit rule: Result is either an Ok or an Err

Slide 28

Slide 28 text

@AndrewRadev Implicit knowledge vs Explicit rules

Slide 29

Slide 29 text

@AndrewRadev (Implicit != “bad”) (“common sense”) (potentially richer rules) (easier/faster to patch rules)

Slide 30

Slide 30 text

@AndrewRadev Monkey-patching

Slide 31

Slide 31 text

@AndrewRadev Monkey-patching ✨F Fr re ee ed do om m-patching!

Slide 32

Slide 32 text

@AndrewRadev # In code class UserRegistration def facebook_client FacebookClient.new end end # In tests class UserRegistration def facebook_client FakeFacebookClient.new end end

Slide 33

Slide 33 text

@AndrewRadev class TrueClass def if_true(&block) block.call self end def if_false self end end

Slide 34

Slide 34 text

@AndrewRadev class FalseClass def if_true self end def if_false(&block) block.call self end end

Slide 35

Slide 35 text

@AndrewRadev (2 + 2 == 5). if_true { print "Radiohead were right!\n" }. if_false { print "Math is still boring, I guess\n" }

Slide 36

Slide 36 text

@AndrewRadev trait FancyIf { fn if_true(self, F) -> Self; fn if_false(self, F) -> Self; }

Slide 37

Slide 37 text

@AndrewRadev impl FancyIf for bool { fn if_true(self, action: F) -> Self { if self { action() } self } fn if_false(self, action: F) -> Self { if !self { action() } self } }

Slide 38

Slide 38 text

@AndrewRadev fn main() { (6 * 9 == 42). if_true(|| println!("Don't forget your towel!")). if_false(|| println!("More boring math.")); }

Slide 39

Slide 39 text

@AndrewRadev Monkey-patching

Slide 40

Slide 40 text

@AndrewRadev Core Extensions Can’t override existing methods Can’t add new fields Modular, encapsulated

Slide 41

Slide 41 text

@AndrewRadev require 'active_support/core_ext/string/inflections' "table".pluralize # => "tables" "ox".pluralize # => "oxen" # String#pluralize is now *everywhere* # (Refinements are a possible solution)

Slide 42

Slide 42 text

@AndrewRadev use inflector::Inflector; fn main() { println!("{}", "table".to_plural()); // => "tables" println!("{}", "ox".to_plural()); // => "oxen" } // "Use" statement only affects a limited scope

Slide 43

Slide 43 text

@AndrewRadev Itertools

Slide 44

Slide 44 text

@AndrewRadev The Iterator trait "The quick brown Firefox...".chars() // => Chars "jumps over the lazy Netscape".bytes() // => Bytes vec!["Veni", "Vidi", "Vec!"].into_iter() // => vec::IntoIter let mut passwords = HashMap::new(); passwords.insert("root", "admin123"); passwords.values() // => hash_map::Values

Slide 45

Slide 45 text

@AndrewRadev impl Itertools for T where T: Iterator { }

Slide 46

Slide 46 text

@AndrewRadev impl Itertools for T where T: Iterator { }

Slide 47

Slide 47 text

@AndrewRadev impl Itertools for T where T: Iterator { }

Slide 48

Slide 48 text

@AndrewRadev impl Itertools for T where T: Iterator { }

Slide 49

Slide 49 text

@AndrewRadev Itertools fn main() { let letter_table = "Chunky raisins".chars(). chunks(7).into_iter(). map(|chunk| chunk.intersperse('|').collect()). intersperse("-+-+-+-+-+-+-".to_string()). join("\n"); println!("{}", letter_table); // C|h|u|n|k|y| // -+-+-+-+-+-+- // r|a|i|s|i|n|s }

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

@AndrewRadev Macros?

Slide 53

Slide 53 text

@AndrewRadev Macros?

Slide 54

Slide 54 text

@AndrewRadev Return-type Polymorphism (Generic Returns)

Slide 55

Slide 55 text

@AndrewRadev use regex::Regex; struct Article { title: String, body: String, } impl Article { fn new(title: &str, body: &str) -> Self { // ... } fn search(&self, r: &Regex) -> bool { r.is_match(&self.title) || r.is_match(&self.body) } }

Slide 56

Slide 56 text

@AndrewRadev // use regex::Regex; // struct Article {...} // impl Article {...} fn main() { let input = Article::new( "Hello!", "My email is [email protected]!" ); let pattern = Regex::new(r"\S+@\S+\.\S+").unwrap(); if input.search(&pattern) { println!("It's a match!"); } }

Slide 57

Slide 57 text

search search_and_get?

Slide 58

Slide 58 text

@AndrewRadev use regex::Regex; struct Article { title: String, body: String, } impl Article { fn new(title: &str, body: &str) -> Self { // ... } } trait Search { fn search(&self, r: &Regex) -> T; }

Slide 59

Slide 59 text

@AndrewRadev impl Search for Article { fn search(&self, r: &Regex) -> bool { // ... } } impl Search> for Article { fn search(&self, r: &Regex) -> Option { // ... } } impl Search> for Article { fn search(&self, r: &Regex) -> Vec { // ... } }

Slide 60

Slide 60 text

@AndrewRadev fn main() { let pattern = Regex::new(r"\S+@\S+\.\S+").unwrap(); let input = Article::new( "Hello!", r#" My email is [email protected]! I've got another one at [email protected]! "# ); let is_match: bool = input.search(&pattern); let actual_match: Option = input.search(&pattern); let all_the_matches: Vec = input.search(&pattern); }

Slide 61

Slide 61 text

@AndrewRadev fn main() { let pattern = Regex::new(r"\S+@\S+\.\S+").unwrap(); let input = Article::new( "Hello!", r#" My email is [email protected]! I've got another one at [email protected]! "# ); if input.search(&pattern) { println!("Yes, a match!"); } if let Some(m) = input.search(&pattern) { println!("Yes, a match: {}!", m); } }

Slide 62

Slide 62 text

@AndrewRadev fn main() { let pattern = Regex::new(r"\S+@\S+\.\S+").unwrap(); let input = Article::new( "Hello!", r#" My email is [email protected]! I've got another one at [email protected]! "# ); let all_the_matches: Vec = input.search(&pattern); for m in all_the_matches { println!("One of the matches is: {}", m); } }

Slide 63

Slide 63 text

@AndrewRadev Serialization/Deserialization

Slide 64

Slide 64 text

@AndrewRadev use serde::Deserialize; #[derive(Debug, Deserialize)] struct Article { title: String, body: String, } #[derive(Debug, Deserialize)] struct CommentList { comments: Vec, } #[derive(Debug, Deserialize)] struct Comment { author_name: String, body: String, }

Slide 65

Slide 65 text

@AndrewRadev fn example() -> Result<()> { let raw_data = r#" { "title": "Bonjour le monde", "body": "Look at moi, I am speaking Française!", "comments": [ { "author_name": "A Troll", "body": "l8me!!!!!!!!" } ] } "#; let article: Article = serde_json::from_str(raw_data)?; let comments: CommentList = serde_json::from_str(raw_data)?; println!("{:#?}", article); println!("{:#?}", comments); Ok(()) }

Slide 66

Slide 66 text

@AndrewRadev Return-type polymorphism It’s magic!

Slide 67

Slide 67 text

@AndrewRadev let article: Article = serde_json::from_str(raw_data)?;

Slide 68

Slide 68 text

@AndrewRadev Return values Dynamic typing: Functions can return different types Static typing: Callers can request different types

Slide 69

Slide 69 text

@AndrewRadev Return-type polymorphism It’s magic :/ Can hurt understanding It’s magic! Extra degree of freedom in interface design

Slide 70

Slide 70 text

@AndrewRadev GTK-rs let label1 = gtk::Label::new(Some("Hello")); let label2 = gtk::Label::new(None); impl Label { fn new(s: Option<&str>) -> Label { // ... } }

Slide 71

Slide 71 text

@AndrewRadev GTK-rs let label1 = gtk::Label::new("Hello"); let label2 = gtk::Label::new(None); impl Label { fn new(s: impl Into>) -> Label { let s = s.into(); // ... } }

Slide 72

Slide 72 text

@AndrewRadev GTK-rs There are two reasons behind going back on this feature: The first one is about [...] error messages. [...] annoying situations where you had to try to figure out what was going on. The second one is to go back to some Rust fundamentals: being explicit. […] – The GTK-rs team

Slide 73

Slide 73 text

@AndrewRadev The freedom to make mistakes fn schroedingers_cat() -> Box; // This makes sense: fn schroedingers_cat() -> Box { } fn schroedingers_cat() -> Box { } // But WHAT IF? fn schroedingers_cat() -> Box { } // Don’t do that ;) ;) ;)

Slide 74

Slide 74 text

@AndrewRadev Freedom... ...means allowing you to express concepts in code ...means allowing you to write bad code :) ...is not just about type signatures ...is not one dimensional

Slide 75

Slide 75 text

The Freedom of Static Typing