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 Slide

  2. @AndrewRadev

    View Slide

  3. Rad Dev

    View Slide

  4. @AndrewRadev
    Static typing (in Rust)

    View Slide

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

  6. @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 Slide

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

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  14. @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 Slide

  15. @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 Slide

  16. @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 Slide

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

    View Slide

  18. @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 Slide

  19. @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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  25. @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 Slide

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

    View Slide

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

    View Slide

  28. @AndrewRadev
    Implicit knowledge
    vs
    Explicit rules

    View Slide

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

    View Slide

  30. @AndrewRadev
    Monkey-patching

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  37. @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 Slide

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

    View Slide

  39. @AndrewRadev
    Monkey-patching

    View Slide

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

    View Slide

  41. @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 Slide

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

    View Slide

  43. @AndrewRadev
    Itertools

    View Slide

  44. @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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  49. @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 Slide

  50. View Slide

  51. View Slide

  52. @AndrewRadev
    Macros?

    View Slide

  53. @AndrewRadev
    Macros?

    View Slide

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

    View Slide

  55. @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 Slide

  56. @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 Slide

  57. search
    search_and_get?

    View Slide

  58. @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 Slide

  59. @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 Slide

  60. @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 Slide

  61. @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 Slide

  62. @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 Slide

  63. @AndrewRadev
    Serialization/Deserialization

    View Slide

  64. @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 Slide

  65. @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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  70. @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 Slide

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

  72. @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 Slide

  73. @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 Slide

  74. @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 Slide

  75. The Freedom of
    Static Typing

    View Slide