$30 off During Our Annual Pro Sale. View Details »

[OpenFest 2020] The Freedom of Static Typing

[OpenFest 2020] The Freedom of Static Typing

Static typing in Rust, a bit of dynamic typing in Ruby, and what "freedom" means for a programming language.

Andrew Radev

November 07, 2020
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. Static typing (in Rust)

    View Slide

  5. 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. Old haskell(ish) talk… about Ruby
    ✘ Runtime errors!
    ✘ Chaos! Mayhem!
    ✘ Your language has too much freedom!
    ✘ You can’t be trusted with it!

    View Slide

  7. Ruby: It’s Really Not That Bad
    ✔Processes, tools, tests
    ✔Peer-review
    ✔Error tracking in prod
    ✔No active desire to see runtime errors :)

    View Slide

  8. 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

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

    View Slide

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

    View Slide

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

    View Slide

  12. Positive examples

    View Slide

  13. Enums
    (Algebraic data types)
    (Sum types)

    View Slide

  14. enum Weekday {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    }
    enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
    }
    let home = IpAddr::V4(127, 0, 0, 1);

    View Slide

  15. Option and Result

    View Slide

  16. enum Option {
    Some(T),
    None,
    }
    fn main() {
    let args = vec![1, 2, 3];
    let third = args.get(2); // => Some(3)
    let missing = args.get(100); // => None
    }

    View Slide

  17. enum Option {
    Some(T),
    None,
    }
    fn main() {
    let args = vec![1, 2, 3];
    // Pattern matching with `match`:
    match args.get(2) {
    Some(value) => println!("Third arg is: {}", value),
    None => println!("No third arg :/"),
    }
    }

    View Slide

  18. enum Option {
    Some(T),
    None,
    }
    fn main() {
    let args = vec![1, 2, 3];
    // Pattern matching with `if let`:
    if let Some(value) = args.get(2) {
    println!("Third arg is: {}", value);
    }
    }

    View Slide

  19. enum Result {
    Ok(T),
    Err(E),
    }
    fn main() {
    // settings: Result
    let settings = fs::read_to_string("~/settings.json");
    // But why not a pair (String, io::Error)?
    let (settings, error) = fs::read_to_string("~/settings.json");
    }

    View Slide

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

    View Slide

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

    View Slide

  22. 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

  23. # Successful result:
    Result.new(value: "Pumpkin spice latte")
    # Errorful result:
    Result.new(error: "Ran out of pumpkins!")
    result.
    or_else { |e| ... }.
    and_then { |v| ... }
    # Is this possible? :/ What do we do here?
    Result.new(
    value: "Everything is great!",
    error: "Everything is broken!"
    )

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  27. Types as containers
    ● Classes/Structs
    ● Pairs/Tuples
    ● Lists
    ● Dictionaries

    View Slide

  28. Enums: not just containers
    Enums express exclusivity

    View Slide

  29. Enums in dynamic languages?

    View Slide

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

    View Slide

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

    View Slide

  32. Static typing
    Explicit rule:
    Result is either an Ok or an Err

    View Slide

  33. Implicit knowledge
    vs
    Explicit rules

    View Slide

  34. (Implicit != “bad”)

    View Slide

  35. (Implicit != “bad”)
    ● Some things can be considered “common sense”
    ● Potentially richer rules
    ● Easier/faster to patch rules

    View Slide

  36. result = Result.new(value: [], error: [])
    ComplicatedProcess.each do |individual_result|
    if individual_result.success?
    result.value << individual_result
    else
    result.error << "Something went wrong with #{individual_result}"
    end
    end
    # (Basic usage of `Result`, no `and_then`/`or_else`)
    # Temporary/intermediate step – a patch

    View Slide

  37. Monkey-patching

    View Slide

  38. Monkey-patching
    F
    Fr
    re
    ee
    ed
    do
    om
    m-patching!

    View Slide

  39. # In code
    class UserRegistration
    def facebook_client
    FacebookClient.new
    end
    end
    # In tests
    class UserRegistration
    # Patch it!
    def facebook_client
    FakeFacebookClient.new
    end
    end

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

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

    View Slide

  46. Monkey-patching

    View Slide

  47. Core Extensions
    ✘ Can’t override existing methods
    ✘ Can’t add new fields
    ✔Modular, encapsulated

    View Slide

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

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

    View Slide

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

    View Slide

  51. Itertools

    View Slide

  52. 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

  53. impl Itertools for T where T: Iterator { }

    View Slide

  54. impl Itertools for T where T: Iterator { }

    View Slide

  55. impl Itertools for T where T: Iterator { }

    View Slide

  56. impl Itertools for T where T: Iterator { }

    View Slide

  57. 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

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

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

  60. View Slide

  61. View Slide

  62. Macros?

    View Slide

  63. Macros:
    Not exactly a static typing thing

    View Slide

  64. fn build_html() -> Result {
    let template = match fs::read_to_string("template.html") {
    Ok(string) => string,
    Err(e) => return Err(e),
    };
    let data = match fs::read_to_string("data.json") {
    Ok(string) => string,
    Err(e) => return Err(e),
    };
    // ...
    }

    View Slide

  65. macro_rules! try! {
    (expr:$expr) => {
    match $expr {
    Ok(string) => string,
    Err(e) => return Err(e),
    }
    }
    }
    fn build_html() -> Result {
    let template = try!(fs::read_to_string("template.html"));
    let data = try!(fs::read_to_string("data.json"));
    // ...
    }

    View Slide

  66. fn get_settings() -> Result {
    let settings = match fs::read_to_string("/etc/settings") {
    Ok(string) => string,
    Err(e) => return Err(e),
    };
    let parsed_settings = match parse_settings(settings) {
    Ok(settings) => settings,
    Err(e) => return Err(e),
    };
    // ...
    }

    View Slide

  67. fn get_settings() -> Result {
    let settings = match fs::read_to_string("/etc/settings") {
    Ok(string) => string,
    Err(e) => return Err(e), // => io::Error
    };
    let parsed_settings = match parse_settings(settings) {
    Ok(settings) => settings,
    Err(e) => return Err(e), // => JsonParseError
    };
    // ...
    }

    View Slide

  68. impl From for AppError {
    fn from(source: io::Error) -> Self {
    // ...
    }
    }
    impl From for AppError {
    fn from(source: JsonParseError) -> Self {
    // ...
    }
    }

    View Slide

  69. impl From for AppError { /*...*/ }
    impl From for AppError { /*...*/ }
    fn get_settings() -> Result {
    let settings = match fs::read_to_string("/etc/settings") {
    Ok(string) => string,
    Err(e) => return Err(AppError::from(e)), // => AppError
    };
    let parsed_settings = match parse_settings(global_settings) {
    Ok(settings) => settings,
    Err(e) => return Err(AppError::from(e)), // => AppError
    };
    // ...
    }

    View Slide

  70. impl From for AppError { /*...*/ }
    impl From for AppError { /*...*/ }
    macro_rules! try! {
    (expr:$expr) => {
    match $expr {
    Ok(string) => string,
    Err(e) => return Err(AppError::from(e)),
    }
    }
    }
    fn get_settings() -> Result {
    let global_settings = try!(fs::read_to_string("/etc/settings"));
    let parsed_settings = try!(parsed_settings(global_settings));
    // ...
    }

    View Slide

  71. Return-type Polymorphism
    (Generic Returns)

    View Slide

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

  73. // 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

  74. search
    search_and_get?

    View Slide

  75. 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

  76. 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

  77. 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

  78. 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

  79. 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

  80. Serialization/Deserialization

    View Slide

  81. 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

  82. fn example() -> Result<()> {
    let raw_data = r#" {
    "title": "OpenFest 2020",
    "body": "This year, it’s gotta be online :/",
    "comments": [
    { "author_name": "Z", "body": "That’s rough, buddy" }
    ]
    } "#;
    let article: Article = serde_json::from_str(raw_data)?;
    let comments: CommentList = serde_json::from_str(raw_data)?;
    // ...
    Ok(())
    }

    View Slide

  83. Return-type polymorphism
    ✔It’s magic!

    View Slide

  84. let article: Article = serde_json::from_str(raw_data)?;
    let comments: CommentList = serde_json::from_str(raw_data)?;

    View Slide

  85. Return values
    Dynamic typing:
    Functions can return different types
    Static typing:
    Callers can request different types

    View Slide

  86. Return-type polymorphism
    ✔It’s magic!
    ✔Extra degree of freedom in interface design

    View Slide

  87. macro_rules! try! {
    (expr:$expr) => {
    match $expr {
    Ok(string) => string,
    Err(e) => return Err(AppError::from(e)),
    }
    }
    }
    fn get_settings() -> Result {
    let global_settings = try!(fs::read_to_string("/etc/settings"));
    let parsed_settings = try!(parsed_settings(global_settings));
    // ...
    }

    View Slide

  88. impl From for AppError { /*...*/ }
    impl From for AppError { /*...*/ }
    impl From for Target {
    // ...
    }
    impl<...> Into for Source
    where Target: From
    {
    fn into(self) -> Target {
    Target::from(self)
    }
    }
    // AppError::from(e) == e.into()

    View Slide

  89. macro_rules! try! {
    (expr:$expr) => {
    match $expr {
    Ok(string) => string,
    Err(e) => return Err(AppError::from(e)),
    }
    }
    }

    View Slide

  90. macro_rules! try! {
    (expr:$expr) => {
    match $expr {
    Ok(string) => string,
    Err(e) => return Err(e.into()),
    }
    }
    }

    View Slide

  91. macro_rules! try! {
    (expr:$expr) => {
    match $expr {
    Ok(string) => string,
    Err(e) => return Err(e.into()),
    }
    }
    }
    fn get_settings() -> Result {
    let global_settings = try!(fs::read_to_string("/etc/settings"));
    let parsed_settings = try!(parsed_settings(global_settings));
    // ...
    }

    View Slide

  92. macro_rules! try! {
    (expr:$expr) => {
    match $expr {
    Ok(string) => string,
    Err(e) => return Err(e.into()),
    }
    }
    }
    fn get_settings() -> Result {
    let global_settings = fs::read_to_string("/etc/settings")?;
    let parsed_settings = parsed_settings(global_settings)?;
    // ...
    }

    View Slide

  93. Return-type polymorphism
    ✔It’s magic!
    ✔Extra degree of freedom in interface design
    ✔Control over the “shape” of code
    ✘ It’s magic :/
    ✘ Can hurt understanding

    View Slide

  94. 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

  95. 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

  96. 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

  97. 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

  98. Freedom...
    ✔...means allowing you to express concepts in code
    ✔...means allowing you to write bad code :)

    View Slide

  99. Freedom...
    ✔...to return whatever type you want
    ✔...to request whatever type you want

    View Slide

  100. Freedom...
    ✔...to patch global types
    ✔...to distribute extensions as libraries

    View Slide

  101. Freedom...
    ✔...is easier to see in a dynamically-typed language

    View Slide

  102. The Freedom of
    Static Typing

    View Slide

  103. The Freedom of Static Typing
    @AndrewRadev https://andrewra.dev

    View Slide