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. The Freedom of
    Static Typing

    View full-size slide

  2. @AndrewRadev

    View full-size slide

  3. @AndrewRadev
    Static typing (in Rust)

    View full-size slide

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

    View full-size slide

  5. @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!

    View full-size slide

  6. @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 ¯\_( ツ )_/¯

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  13. @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");
    }
    }

    View full-size slide

  14. @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");
    }

    View full-size slide

  15. @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);
    }

    View full-size slide

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

    View full-size slide

  17. @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

    View full-size slide

  18. @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!"
    )

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  23. @AndrewRadev
    Enums: not just containers
    Enums express exclusivity

    View full-size slide

  24. @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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  27. @AndrewRadev
    Implicit knowledge
    vs
    Explicit rules

    View full-size slide

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

    View full-size slide

  29. @AndrewRadev
    Monkey-patching

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  36. @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
    }
    }

    View full-size slide

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

    View full-size slide

  38. @AndrewRadev
    Monkey-patching

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  42. @AndrewRadev
    Itertools

    View full-size slide

  43. @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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  48. @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
    }

    View full-size slide

  49. @AndrewRadev
    Macros?

    View full-size slide

  50. @AndrewRadev
    Macros?

    View full-size slide

  51. @AndrewRadev
    Return-type Polymorphism
    (Generic Returns)

    View full-size slide

  52. @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)
    }
    }

    View full-size slide

  53. @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!");
    }
    }

    View full-size slide

  54. search
    search_and_get?

    View full-size slide

  55. @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;
    }

    View full-size slide

  56. @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 {
    // ...
    }
    }

    View full-size slide

  57. @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);
    }

    View full-size slide

  58. @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);
    }
    }

    View full-size slide

  59. @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);
    }
    }

    View full-size slide

  60. @AndrewRadev
    Serialization/Deserialization

    View full-size slide

  61. @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,
    }

    View full-size slide

  62. @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(())
    }

    View full-size slide

  63. @AndrewRadev
    Return-type polymorphism
    It’s magic!

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  68. @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();
    // ...
    }
    }

    View full-size slide

  69. @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

    View full-size slide

  70. @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 ;) ;) ;)

    View full-size slide

  71. @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

    View full-size slide

  72. The Freedom of
    Static Typing

    View full-size slide