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

[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. Old haskell(ish) talk… about Ruby ✘ Runtime errors! ✘ Chaos!

    Mayhem! ✘ Your language has too much freedom! ✘ You can’t be trusted with it!
  2. Ruby: It’s Really Not That Bad ✔Processes, tools, tests ✔Peer-review

    ✔Error tracking in prod ✔No active desire to see runtime errors :)
  3. 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 ¯\_( ツ )_/¯
  4. enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, } enum

    IpAddr { V4(u8, u8, u8, u8), V6(String), } let home = IpAddr::V4(127, 0, 0, 1);
  5. enum Option<T> { Some(T), None, } fn main() { let

    args = vec![1, 2, 3]; let third = args.get(2); // => Some(3) let missing = args.get(100); // => None }
  6. enum Option<T> { 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 :/"), } }
  7. enum Option<T> { 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); } }
  8. enum Result<T, E> { Ok(T), Err(E), } fn main() {

    // settings: Result<String, io::Error> 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"); }
  9. fn read_settings(path: &str) -> Result<String, Box<dyn Error>> { // ...

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

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

    # ... @alchemiter = ... @laserstation = ... @punch_designix = ... # ... end end
  14. enum Event { Initialization { alchemiter: ..., laserstation: ..., },

    Cascade { laserstation: ..., punch_designix: ..., }, // ... }
  15. 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
  16. Dynamic typing Implicit knowledge: Success and Error should have the

    same methods. Implicit knowledge: Result has either a value or an error
  17. (Implicit != “bad”) • Some things can be considered “common

    sense” • Potentially richer rules • Easier/faster to patch rules
  18. 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
  19. # In code class UserRegistration def facebook_client FacebookClient.new end end

    # In tests class UserRegistration # Patch it! def facebook_client FakeFacebookClient.new end end
  20. (2 + 2 == 5). if_true { print "Radiohead were

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

    if_false<F: FnOnce()>(self, F) -> Self; }
  22. 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 } }
  23. fn main() { (6 * 9 == 42). if_true(|| println!("Don't

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

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

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

    println!("{}", Inflector::to_plural("ox")); // => "oxen" } // "Use" statement only affects a limited scope
  27. 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>
  28. 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 }
  29. 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 }
  30. 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 }
  31. fn build_html() -> Result<Html, io::Error> { 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), }; // ... }
  32. macro_rules! try! { (expr:$expr) => { match $expr { Ok(string)

    => string, Err(e) => return Err(e), } } } fn build_html() -> Result<Html, io::Error> { let template = try!(fs::read_to_string("template.html")); let data = try!(fs::read_to_string("data.json")); // ... }
  33. fn get_settings() -> Result<Settings, ???> { 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), }; // ... }
  34. fn get_settings() -> Result<Settings, ???> { 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 }; // ... }
  35. impl From<io::Error> for AppError { fn from(source: io::Error) -> Self

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

    AppError { /*...*/ } fn get_settings() -> Result<Settings, AppError> { 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 }; // ... }
  37. impl From<io::Error> for AppError { /*...*/ } impl From<JsonParseError> for

    AppError { /*...*/ } macro_rules! try! { (expr:$expr) => { match $expr { Ok(string) => string, Err(e) => return Err(AppError::from(e)), } } } fn get_settings() -> Result<Settings, AppError> { let global_settings = try!(fs::read_to_string("/etc/settings")); let parsed_settings = try!(parsed_settings(global_settings)); // ... }
  38. 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) } }
  39. // 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!"); } }
  40. 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; }
  41. 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> { // ... } }
  42. 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); }
  43. 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); } }
  44. 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); } }
  45. 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, }
  46. 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(()) }
  47. macro_rules! try! { (expr:$expr) => { match $expr { Ok(string)

    => string, Err(e) => return Err(AppError::from(e)), } } } fn get_settings() -> Result<Settings, AppError> { let global_settings = try!(fs::read_to_string("/etc/settings")); let parsed_settings = try!(parsed_settings(global_settings)); // ... }
  48. impl From<io::Error> for AppError { /*...*/ } impl From<JsonParseError> for

    AppError { /*...*/ } impl From<Source> for Target { // ... } impl<...> Into<Target> for Source where Target: From<Source> { fn into(self) -> Target { Target::from(self) } } // AppError::from(e) == e.into()
  49. macro_rules! try! { (expr:$expr) => { match $expr { Ok(string)

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

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

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

    => string, Err(e) => return Err(e.into()), } } } fn get_settings() -> Result<Settings, AppError> { let global_settings = fs::read_to_string("/etc/settings")?; let parsed_settings = parsed_settings(global_settings)?; // ... }
  53. 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
  54. 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(); // ... } }
  55. 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
  56. 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 ;) ;) ;)