Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[DevFest Toulouse 2019] The Freedom of Static Typing

[DevFest Toulouse 2019] The Freedom of Static Typing

It’s a common idea: Dynamic typing gives you freedom to do whatever you want, but then you suffer mistakes and runtime errors. Static typing limits your freedom, but it leads to fewer bugs, even if it’s harder to actually write code.

This isn’t entirely false. Yes, it can be quite hard to write a Rust or Haskell program that compiles, enough that it’s a running gag in their communities. “But once you get it to compile, it’s probably correct!”, the joke goes. On the other hand, when writing Ruby or Javascript, you’re free to call functions with all sorts of things, and then runtime errors can easily slip into production. That said, it’s not as simple as a sliding scale between “freedom” and “safety”. Neither of these terms is one-dimensional – a language feature that restricts your freedom in one way can actually free you in different ways.

I’d like to demonstrate some interesting language features in Rust that make it easier to write code and to express concepts compared to dynamic languages. Features that don’t just protect you from shooting your foot, but give you power that’s only available within the rules and “limitations” of a statically typed language. Even if you’re not familiar with Rust, I hope to focus the examples on the features themselves rather than on the specifics of the language, so come by and let’s talk about being (type-safely) free.

Andrew Radev

October 03, 2019
Tweet

More Decks by Andrew Radev

Other Decks in Programming

Transcript

  1. @AndrewRadev Old haskell(ish) talk Runtime errors! “Mathematically impossible to have

    errors in <language>” Your language has too much freedom! You can’t be trusted with it!
  2. @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 ¯\_( ツ )_/¯
  3. @AndrewRadev enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, }

    enum IpAddr { V4(u8, u8, u8, u8), V6(String), }
  4. @AndrewRadev enum Option<T> { 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"); } }
  5. @AndrewRadev enum Result<T, E> { Ok(T), Err(E), } fn read_file(path:

    &str) -> Result<String, io::Error> { // ... } fn main() { // settings: Result<T, E> let settings = read_file("~/settings.json"); // But why not a pair (T, E)? let (settings, error) = read_file("~/settings.json"); }
  6. @AndrewRadev fn read_file(path: &str) -> Result<String, Box<dyn Error>> { //

    ... } fn parse_settings(json: &str) -> Result<Settings, Box<dyn Error>> { // ... } 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); }
  7. @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
  8. @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!" )
  9. @AndrewRadev class Result def and_then(&block) if value # ... end

    end def or_else(&block) if error # ... end end end # Add runtime checks?
  10. @AndrewRadev class Event def initialize(...) @type = "initialization" @type =

    "cascade" # ... @alchemiter = ... @laserstation = ... @punch_designix = ... # ... end end
  11. @AndrewRadev enum Event { Initialization { alchemiter: ..., laserstation: ...,

    }, Cascade { laserstation: ..., punch_designix: ..., }, // ... }
  12. @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
  13. @AndrewRadev Dynamic typing Implicit knowledge: Success and Error should have

    the same methods. Implicit knowledge: Result has either a value or an error
  14. @AndrewRadev # In code class UserRegistration def facebook_client FacebookClient.new end

    end # In tests class UserRegistration def facebook_client FakeFacebookClient.new end end
  15. @AndrewRadev (2 + 2 == 5). if_true { print "Radiohead

    were right!\n" }. if_false { print "Math is still boring, I guess\n" }
  16. @AndrewRadev trait FancyIf { fn if_true<F: FnOnce()>(self, F) -> Self;

    fn if_false<F: FnOnce()>(self, F) -> Self; }
  17. @AndrewRadev impl FancyIf for bool { fn if_true<F: FnOnce()>(self, action:

    F) -> Self { if self { action() } self } fn if_false<F: FnOnce()>(self, action: F) -> Self { if !self { action() } self } }
  18. @AndrewRadev fn main() { (6 * 9 == 42). if_true(||

    println!("Don't forget your towel!")). if_false(|| println!("More boring math.")); }
  19. @AndrewRadev require 'active_support/core_ext/string/inflections' "table".pluralize # => "tables" "ox".pluralize # =>

    "oxen" # String#pluralize is now *everywhere* # (Refinements are a possible solution)
  20. @AndrewRadev use inflector::Inflector; fn main() { println!("{}", "table".to_plural()); // =>

    "tables" println!("{}", "ox".to_plural()); // => "oxen" } // "Use" statement only affects a limited scope
  21. @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<T> let mut passwords = HashMap::new(); passwords.insert("root", "admin123"); passwords.values() // => hash_map::Values<K, V>
  22. @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 }
  23. @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) } }
  24. @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!"); } }
  25. @AndrewRadev use regex::Regex; struct Article { title: String, body: String,

    } impl Article { fn new(title: &str, body: &str) -> Self { // ... } } trait Search<T> { fn search(&self, r: &Regex) -> T; }
  26. @AndrewRadev impl Search<bool> for Article { fn search(&self, r: &Regex)

    -> bool { // ... } } impl Search<Option<String>> for Article { fn search(&self, r: &Regex) -> Option<String> { // ... } } impl Search<Vec<String>> for Article { fn search(&self, r: &Regex) -> Vec<String> { // ... } }
  27. @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<String> = input.search(&pattern); let all_the_matches: Vec<String> = input.search(&pattern); }
  28. @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); } }
  29. @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<String> = input.search(&pattern); for m in all_the_matches { println!("One of the matches is: {}", m); } }
  30. @AndrewRadev use serde::Deserialize; #[derive(Debug, Deserialize)] struct Article { title: String,

    body: String, } #[derive(Debug, Deserialize)] struct CommentList { comments: Vec<Comment>, } #[derive(Debug, Deserialize)] struct Comment { author_name: String, body: String, }
  31. @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(()) }
  32. @AndrewRadev Return values Dynamic typing: Functions can return different types

    Static typing: Callers can request different types
  33. @AndrewRadev GTK-rs let label1 = gtk::Label::new("Hello"); let label2 = gtk::Label::new(None);

    impl Label { fn new(s: impl Into<Option<&str>>) -> Label { let s = s.into(); // ... } }
  34. @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
  35. @AndrewRadev The freedom to make mistakes fn schroedingers_cat<T>() -> Box<T>;

    // This makes sense: fn schroedingers_cat() -> Box<LivingCat> { } fn schroedingers_cat() -> Box<DeadCat> { } // But WHAT IF? fn schroedingers_cat() -> Box<Dog> { } // Don’t do that ;) ;) ;)
  36. @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