Slide 1

Slide 1 text

Sorbet Is it Really That Tasty?

Slide 2

Slide 2 text

Spoiler Alert: YES it is!

Slide 3

Slide 3 text

Working for TriumphPay (Dallas) leif.io Software Engineer since 2008 Rails since 2011 (2.1.3)

Slide 4

Slide 4 text

β€œIf it walks like a duck and it quacks like a duck, then it must be a duckβ€œ class Duck def quack "Duck is quacking" end def waddle "Duck is waddling" end end class Goose def quack "Goose is quacking" end def waddle "Goose is waddling" end end class Herder def move_flock(anatidaes) anatidaes.each { |anatidae| puts anatidae.waddle } end def talk_to_flock(anatidaes) anatidaes.each { |anatidae| puts anatidae.quack } end end flock = [ Duck.new, Duck.new, Goose.new, Duck.new ] Herder.new.move_flock(flock) puts "====" Herder.new.talk_to_flock(flock) class Dog def bark puts "Dog is barking" end def walk puts "Dog is walking" end end flock = [ Duck.new, Duck.new, Dog.new, Duck.new ] Herder.new.move_flock(flock) puts "====" Herder.new.talk_to_flock(flock) `block in move_flock': undefined method `waddle' for # (NoMethodError) anatidaes.each { |anatidae| anatidae.waddle } Dynamic Typing

Slide 5

Slide 5 text

Examples in Ruby Stdlib class FilePrinter def initialize(file) @file = file end def print_with_emojis @file.each_line do |line| puts "#{line} \u{1f525}" end end end gemfile = File.new("Gemfile.lock") FilePrinter.new(gemfile).print_with_emojis GEM πŸ”₯ remote: https://rubygems.org/ πŸ”₯ specs: πŸ”₯ πŸ”₯ PLATFORMS πŸ”₯ x86_64-darwin-22 πŸ”₯ πŸ”₯ DEPENDENCIES πŸ”₯ πŸ”₯ BUNDLED WITH πŸ”₯ 2.4.12 πŸ”₯ first_line πŸ”₯ second_line πŸ”₯ third_line πŸ”₯ require "stringio" content = "first_line\nsecond_line\nthird_line" fake_file = StringIO.new(content) FilePrinter.new(fake_file).print_with_emojis

Slide 6

Slide 6 text

β€œI don’t care what you look like or what you sound like In order to be a duck, you must have the label β€˜duckβ€™β€œ Static Typing public class Duck { public String quack() { return "Duck is quacking"; } public String waddle() { return "Duck is waddling"; } } public class Goose { public String quack() { return "Goose is quacking"; } public String waddle() { return "Goose is waddling"; } } public class Herder { public static void moveFlock(Duck[] ducks) { for (Duck duck : ducks) { System.out.println(duck.waddle()); } } public static void talkToFlock(Duck[] ducks) { for (Duck duck : ducks) { System.out.println(duck.quack()); } } public static void main(String[] args) { Duck[] ducks = new Duck[] { new Duck(), new Duck(), new Goose() }; Herder herder = new Herder(); herder.moveFlock(ducks); } } javac *.java Herder.java:16: error: incompatible types: Goose cannot be converted to Duck Duck[] ducks = new Duck[] { new Duck(), new Duck(), new Goose() }; ^ 1 error

Slide 7

Slide 7 text

Dynamic Typing Static Typing Quicker Turnaround of Code Type Errors Appear at Runtime Code (Potentially) Harder to Understand Slower Turnaround Because of Necessary Checks Type Errors Are Caught Before Runtime Automatic Documentation of Types Pick and Choose

Slide 8

Slide 8 text

gem "sorbet-static" gem ”sorbet-runtime” # frozen_string_literal: true source "https://rubygems.org" gem "sorbet-static-and-runtime" gem "tapioca" Gemfile CLI Type Checker (srb tc) Syntax for type annotations + data structures # typed: strict require "sorbet-runtime" class Duck extend T::Sig sig { returns(String) } def quack "Duck is quacking" end sig { returns(String) } def waddle "Duck is waddling" end end # typed: strict require β€œsorbet-runtime" class Goose extend T::Sig sig { returns(String) } def quack "Goose is quacking" end sig { returns(String) } def waddle "Goose is waddling" end end # typed: strict class Herder extend T::Sig sig { params(duck: Duck).void } def talk(duck) puts duck.quack end end # typed: strict Herder.new.talk(Goose.new) bundle exec srb (typecheck|tc) Expected Duck but found Goose for argument duck https://srb.help/7002 12 |Herder.new.talk(Goose.new) ^^^^^^^^^ Expected Duck for argument duck of method Herder#talk: herder.rb:6: 6 | sig { params(duck: Duck).void } ^^^^ Got Goose originating from: run.rb:12: 12 |Herder.new.talk(Goose.new) ^^^^^^^^^ lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/configuration.rb:296:in `call_validation_error_handler_default’: Parameter 'duck': Expected type Duck, got type Goose with hash 2285691517925918546 (TypeError) Caller: run.rb:12 Definition: /Users/leifg/Documents/tech_talks/sorbet/code/sorbet/herder.rb:7 raise TypeError.new(opts[:pretty_message]) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/configuration.rb:303:in `call_validation_error_handler' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/call_validation.rb:286:in `report_error' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/call_validation.rb:204:in `block in validate_call' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/signature.rb:201:in `each_args_value_type' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/call_validation.rb:201:in `validate_call' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/_methods.rb:275:in `block in _on_method_added' from run.rb:12:in `'

Slide 9

Slide 9 text

Now What? # typed: strict require "sorbet-runtime" class Anatidae extend T::Sig sig { returns(String) } def quack raise "Not Implemetned" end sig { returns(String) } def waddle raise "Not Implemetned" end end # typed: strict class Duck < Anatidae extend T::Sig sig { override.returns(String) } def quack "Duck is quacking" end sig { override.returns(String) } def waddle "Duck is waddling" end end class Goose < Anatidae extend T::Sig sig { override.returns(String) } def quack "Goose is quacking" end sig { override.returns(String) } def waddle "Goose is waddling" end end # typed: strict Herder.new.talk(Goose.new) # typed: strict require "./inherited_types" class Herder extend T::Sig sig { params(anatidae: Anatidae).void } def talk(anatidae) puts anatidae.quack end sig { params(anatidaes: T::Array[Anatidae]).void } def move_flock(anatidaes) anatidaes.each { |anatidae| puts anatidae.waddle } end sig { params(anatidaes: T::Array[Anatidae]).void } def talk_to_flock(anatidaes) anatidaes.each { |anatidae| puts anatidae.quack } end end

Slide 10

Slide 10 text

Now What? # typed: strict Herder.new.talk(Goose.new) # typed: strict require β€œ./abstract_class” class Herder extend T::Sig sig { params(anatidae: Anatidae).void } def talk(anatidae) puts anatidae.quack end sig { params(anatidaes: T::Array[Anatidae]).void } def move_flock(anatidaes) anatidaes.each { |anatidae| puts anatidae.waddle } end sig { params(anatidaes: T::Array[Anatidae]).void } def talk_to_flock(anatidaes) anatidaes.each { |anatidae| puts anatidae.quack } end end # typed: ignore require "sorbet-runtime" class Anatidae extend T::Sig extend T::Helpers abstract! sig { abstract.returns(String) } def quack; end sig { abstract.returns(String) } def waddle; end end class Duck < Anatidae extend T::Sig sig { override.returns(String) } def quack "Duck is quacking" end sig { override.returns(String) } def waddle "Duck is waddling" end end class Goose < Anatidae extend T::Sig sig { override.returns(String) } def quack "Goose is quacking" end sig { override.returns(String) } def waddle "Goose is waddling" end end

Slide 11

Slide 11 text

Now What? # typed: strict require "sorbet-runtime" module MakesQuack extend T::Sig extend T::Helpers interface! sig { abstract.returns(String) } def quack; end end module MakesWaddle extend T::Sig extend T::Helpers interface! sig { abstract.returns(String) } def waddle; end end class Duck extend T::Sig include MakesQuack include MakesWaddle sig { override.returns(String) } def quack "Duck is quacking" end sig { override.returns(String) } def waddle "Duck is waddling" end end class Goose extend T::Sig include MakesQuack include MakesWaddle sig { override.returns(String) } def quack "Goose is quacking" end sig { override.returns(String) } def waddle "Goose is waddling" end end # typed: strict require "./interfaces" class Herder extend T::Sig sig { params(anatidae: MakesQuack).void } def talk(anatidae) puts anatidae.quack end sig { params(anatidaes: T::Array[MakesWaddle]).void } def move_flock(anatidaes) anatidaes.each { |anatidae| puts anatidae.waddle } end sig { params(anatidaes: T::Array[MakesQuack]).void } def talk_to_flock(anatidaes) anatidaes.each { |anatidae| puts anatidae.quack } end end # typed: strict Herder.new.talk(Goose.new) Strategy Design Pattern

Slide 12

Slide 12 text

class FilePrinter extend T::Sig sig { params(file: T.any(File, StringIO)).void } def initialize(file) @file = file end sig { void } def print_with_emojis @file.each_line do |line| puts "#{line} \u{1f525}" end end end

Slide 13

Slide 13 text

Static Type Checker that Analyzes Files && Runtime Environment that does type checks on execution Sorbet is …

Slide 14

Slide 14 text

class User < ActiveRecord::Base end How Many Methods Does this Class Have? irb(main):001:0> User.new.public_methods.count => 581

Slide 15

Slide 15 text

Some Examples [:clear_email_change, :email, :email=, :email?, :email_before_last_save, :email_before_type_cast, :email_came_from_user?, :email_change, :email_change_to_be_saved, :email_changed?, :email_for_database, :email_in_database, :email_previous_change, :email_previously_changed?, :email_previously_was, :email_was, :email_will_change!, :restore_email!, :saved_change_to_email, :saved_change_to_email?, :will_save_change_to_email?] [:update, :update!, :save, :save!, :update_attribute, :update_column, :update_columns, :new_record?, :previously_new_record?, :saved_chanes, :saved_changes? ] [:after_create_commit, :after_create_commit, :around_create, :before_create, :create, :create!, :create_or_find_by, :create_or_find_by!, :create_with, :find_or_create_by, :find_or_create_by!, :first_or_create, :first_or_create!, :timestamp_attributes_for_create_in_model]

Slide 16

Slide 16 text

Introducing Tapioca $ bin/tapioca dsl Loading Rails application... Done Loading DSL compiler classes... Done Compiling DSL RBI files... ActiveRecord::SchemaMigration Pluck (1.7ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC ... create sorbet/rbi/dsl/devise_controller.rbi create sorbet/rbi/dsl/generated_url_helpers_module.rbi create sorbet/rbi/dsl/ generated_path_helpers_module.rbi create sorbet/rbi/dsl/has_scope.rbi create sorbet/rbi/dsl/user.rbi Done Checking generated RBI files... Done No errors found All operations performed in working directory. Please review changes and commit them.

Slide 17

Slide 17 text

$ tree -A sorbet/ sorbet/ └── rbi └── dsl β”œβ”€β”€ devise β”‚ β”œβ”€β”€ confirmations_controller.rbi β”‚ β”œβ”€β”€ failure_app.rbi β”‚ β”œβ”€β”€ mailer.rbi β”‚ β”œβ”€β”€ models β”‚ β”‚ └── authenticatable.rbi β”‚ β”œβ”€β”€ omniauth_callbacks_controller.rbi β”‚ β”œβ”€β”€ passwords_controller.rbi β”‚ β”œβ”€β”€ registrations_controller.rbi β”‚ β”œβ”€β”€ sessions_controller.rbi β”‚ └── unlocks_controller.rbi β”œβ”€β”€ devise_controller.rbi β”œβ”€β”€ fast_jsonapi β”‚ └── object_serializer.rbi β”œβ”€β”€ generated_path_helpers_module.rbi β”œβ”€β”€ generated_url_helpers_module.rbi β”œβ”€β”€ has_scope.rbi β”œβ”€β”€ sessions_controller.rbi └── user.rbi

Slide 18

Slide 18 text

# typed: true # DO NOT EDIT MANUALLY # This is an autogenerated file for dynamic methods in `User`. # Please instead update this file by running `bin/tapioca dsl User`. class User include GeneratedAttributeMethods extend CommonRelationMethods extend GeneratedRelationMethods module GeneratedAttributeMethods sig { returns(::String) } def email; end sig { params(value: ::String).returns(::String) } def email=(value); end wc -l sorbet/rbi/dsl/user.rbi 1337

Slide 19

Slide 19 text

β€’ Gems β€’Annotations β€’ Validators So much More

Slide 20

Slide 20 text

Congratulations You turned Ruby into Java

Slide 21

Slide 21 text

# typed: strict class Money < T::Struct extend T::Sig const :amount, Integer # amount in cents const :currency, String # ISO 4217 currency code end Typed Structs irb(main):001:0> Money.new(amount: 499_95, currency: "USD") => irb(main):002:0> money.with(currency: "EUR") => irb(main):003:0> money.with(amount: money.amount + 5) =>

Slide 22

Slide 22 text

Enums irb(main):013:0> open_status = Status::Open => # irb(main):014:0> Status.deserialize("open") => # # typed: strict class Status < T::Enum enums do Open = new Rejected = new Completed = new end end

Slide 23

Slide 23 text

irb(main):001:0> Status::Completed.final? => true class Status < T::Enum extend T::Sig enums do Open = new Rejected = new Completed = new end sig { returns(T::Boolean) } def final? case self when Rejected, Completed true when Open false else T.absurd(self) end end end Exhaustive Checking

Slide 24

Slide 24 text

class Status < T::Enum extend T::Sig enums do Draft = new Open = new Rejected = new Completed = new end sig { returns(T::Boolean) } def final? case self when Rejected, Completed true when Open false else T.absurd(self) end end end Exhaustive Checking status.rb:21: Control flow could reach T.absurd because the type Status::Draft wasn't handled https://srb.help/7026 21 | T.absurd(self) ^^^^^^^^^^^^^^ Got Status::Draft originating from: status.rb:16: 16 | when Rejected, Completed ^^^^^^^^^ status.rb:18: 18 | when Open ^^^^

Slide 25

Slide 25 text

Exercise! SurveyUser Anonymous Email Only Email and Phone Phone Only

Slide 26

Slide 26 text

The Ruby Approach class SurveyUser < T::Struct extend T::Sig const :name, String const :email, T.nilable(String) const :phone, T.nilable(String) sig { returns(T::Boolean) } def valid? return true if email.nil? && phone.nil? return true if email && phone return true if email && phone.nil? false end sig { void } def send_confirmation_email raise ArgumentError, "Invalid SurveyUser" unless valid? user_email = email return if user_email.nil? ConfirmationEmail.new(user_email).deliver_later end end

Slide 27

Slide 27 text

Discriminated Unions module ContactDetails extend T::Helpers sealed! class Anonymous < T::Struct include ContactDetails end class EmailOnly < T::Struct include ContactDetails const :email, String end class EmailAndPhone < T::Struct include ContactDetails const :email, String const :phone, String end end

Slide 28

Slide 28 text

class SurveyUser < T::Struct extend T::Sig const :name, String const :contact_details, ContactDetails sig { void } def send_confirmation_email user_contact_details = contact_details case user_contact_details when ContactDetails::Anonymous # do nothing when ContactDetails::EmailOnly, ContactDetails::EmailAndPhone ConfirmationEmail.new(user_contact_details.email).deliver_later else T.absurd(user_contact_details) end end end Exhaustive Checking "Making Impossible States Impossible" by Richard Feldman

Slide 29

Slide 29 text

The Dark Side

Slide 30

Slide 30 text

class Herder extend T::Sig sig { params(duck: Duck).void } def talk(duck) puts duck.quack end sig { params(ducks: T::Array[Duck]).void } def talk_to_flock(ducks) ducks.each { |duck| talk(duck) } end end Generics flock = [ Duck.new, Duck.new, Goose.new ] Herder.new.talk_to_flock(flock) run.rb:8: Expected T::Array[Duck] but found [Duck, Duck, Goose] for argument ducks https://srb.help/7002 8 |Herder.new.talk_to_flock(flock) ^^^^^ Expected T::Array[Duck] for argument ducks of method Herder#talk_to_flock: herder.rb:13: 13 | sig { params(ducks: T::Array[Duck]).void } ^^^^^ Got [Duck, Duck, Goose] (3-tuple) originating from: run.rb:6: 6 |flock = [ Duck.new, Duck.new, Goose.new ] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/configuration.rb:296:in `call_validation_error_handler_default’: Parameter 'duck': Expected type Duck, got type Goose with hash 2285691517925918546 (TypeError) Caller: run.rb:12 Definition: /Users/leifg/Documents/tech_talks/sorbet/code/sorbet/herder.rb:7 raise TypeError.new(opts[:pretty_message]) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/configuration.rb:303:in `call_validation_error_handler' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/call_validation.rb:286:in `report_error' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/call_validation.rb:204:in `block in validate_call' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/signature.rb:201:in `each_args_value_type' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/call_validation.rb:201:in `validate_call' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/_methods.rb:275:in `block in _on_method_added' from run.rb:12:in `'

Slide 31

Slide 31 text

class Herder extend T::Sig sig { params(duck: Duck).void } def talk(anatidae) puts anatidae.quack end sig { params(ducks: T::Array[Duck]).void } def talk_to_flock(ducks) ducks.each { |duck| talk(duck) } end end ❯ srb No errors! Great job. Generics flock = Array.new(3) { Goose.new } Herder.new.talk_to_flock(flock) lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/configuration.rb:296:in `call_validation_error_handler_default’: Parameter 'duck': Expected type Duck, got type Goose with hash 2285691517925918546 (TypeError) Caller: run.rb:12 Definition: /Users/leifg/Documents/tech_talks/sorbet/code/sorbet/herder.rb:7 raise TypeError.new(opts[:pretty_message]) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/configuration.rb:303:in `call_validation_error_handler' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/call_validation.rb:286:in `report_error' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/call_validation.rb:204:in `block in validate_call' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/signature.rb:201:in `each_args_value_type' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/call_validation.rb:201:in `validate_call' lib/ruby/gems/3.2.0/gems/sorbet-runtime-0.5.10793/lib/types/private/methods/_methods.rb:275:in `block in _on_method_added' from run.rb:12:in `'

Slide 32

Slide 32 text

# typed: strict module FetchUserId extend T::Sig extend self sig { params(email: String).returns(Integer) } def by_email(email) user = User.find_by!(email: email) user.id end end ❯ srb app/helpers/testing.rb:10: Expected Integer but found T.nilable(Integer) for method result type https://srb.help/7005 10 | user.id ^^^^^^^ Expected Integer for result type of method fetch_id: app/helpers/testing.rb:7: 7 | def fetch_id(email) ^^^^^^^^^^^^^^^^^^^ Got T.nilable(Integer) originating from: app/helpers/testing.rb:10: 10 | user.id ^^^^^^^ Autocorrect: Use -a to autocorrect app/helpers/testing.rb:10: Replace with T.must(user.id) 10 | user.id Dynamic Ecosystem

Slide 33

Slide 33 text

# typed: strict module Testing extend T::Sig sig { params(email: String).returns(Integer) } def fetch_id(email) user = User.find_by!(email: email) T.must(user.id) end end Dynamic Ecosystem

Slide 34

Slide 34 text

Serialization # typed: strict class Money < T::Struct extend T::Sig const :amount, Integer const :currency, String end irb(main):001:0> m = Money.new(amount: 100_00, currency: "USD") 
 => irb(main):002:0> serialized = m.serialize 
 => {"amount"=>10000, β€œcurrency"=>"USD"} irb(main):003:0> Money.from_hash(serialized) 
 => πŸ‘

Slide 35

Slide 35 text

Serialization # typed: strict class Money < T::Struct extend T::Sig const :amount, Integer const :currency, String end irb(main):080:0> serialized = {amount: 100_00, currency: "USD"} 
 => {:amount=>10000, :currency=>"USD"} irb(main):003:0> Money.from_hash(serialized) gems/sorbet-runtime-0.5.10793/lib/types/props/serializable.rb:70:in `rescue in deserialize': Error in Money#__t_props_generated_deserialize: Tried to deserialize a required prop from a nil value. It's possible that a nil value exists in the database, so you should provide a `default: or factory:` for this prop (see go/optional for more details). If this is already the case, you probably omitted a required prop from the `fields:` option when doing a partial load. (RuntimeError) πŸ‘Ž

Slide 36

Slide 36 text

class Order < T::Struct const :id, String const :amount, Money const :approved_at, T.nilable(Time) const :refundable, T::Boolean end irb(main):001:1* o = Order.new( irb(main):002:1* id: "1", irb(main):003:1* amount: m, irb(main):004:1* approved_at: Time.now, irb(main):005:1* refundable: false, irb(main):005:0> ) => approved_at=2023-04-29 11:24:30.872848 -0700 id="1" refundable=false> irb(main):001:0> serialized = o1.serialize.to_json 
 => {β€œid":"1","amount" {"amount":10000,"currency":"USD"}, 
 "approved_at":"2023-04-29 11:24:30 -0700","refundable":false} irb(main):002:0> o = Order.from_hash(JSON.parse(serialized)) 
 => approved_at="2023-04-29 11:24:30 -0700" id="1" refundable=false> irb(main):003:0> o.approved_at.class 
 => String Serialization πŸ‘Ž

Slide 37

Slide 37 text

class Order < T::Struct const :id, String const :amount, Money const :approved_at, T.nilable(Time) const :refundable, T::Boolean end Serialization irb(main):001:0> require "sorbet-coerce" irb(main):002:0> o = TypeCoerce[Order].new.from(JSON.parse(serialized)) irb(main):003:0> o.approved_at.class => Time

Slide 38

Slide 38 text

Community class Money < T::Struct const :amount, Integer const :currency, String end class Order < T::Struct const :value, Money end o = Order.new(value: Money.new(amount: 100_00, currency: "USD")) o.with(value: Money.new(amount: 200_00, currency: "USD")) /gems/ruby/3.1.0/gems/sorbet-runtime-0.5.10526/lib/types/props/serializable.rb:72:in `rescue in rescue in deserialize': Error in Order#__t_props_generated_deserialize: provided to from_hash (TypeError) at line 8 in: found = 1 val = hash["value"] @value = if val.nil? found -= 1 unless hash.key?("value") self.class.decorator.raise_nil_deserialize_error("value") else begin Money.from_hash(val) rescue NoMethodError => e raise_deserialization_error( :value, val, e, ) val end /gems/ruby/3.1.0/gems/sorbet-runtime-0.5.10526/lib/types/props/serializable.rb:70:in `rescue in deserialize': Error in Order#__t_props_generated_deserialize: provided to from_hash (ArgumentError) at line 8 in: found = 1 val = hash["value"] @value = if val.nil?

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

sorbet/sorbet#6535

Slide 41

Slide 41 text

In Conclusion β€’ Net Positive β€’ Limitations because of Ecosystem β€’ T::Struct needs some Love

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

Existing Codebases T::Configuration.call_validation_error_handler = lambda do |signature, opts| if Rails.env.production? Honeybadger.notify(opts[:pretty_message]) else raise TypeError.new(opts[:pretty_message]) end end Runtime Con fi guration Sorbet Docs

Slide 44

Slide 44 text

In Conclusion β€’ Net Positive β€’ Limitations because of Ecosystem β€’ T::Struct needs some Love leif.io

Slide 45

Slide 45 text

- Slide 1: Photo by Anton on Unsplash - Slide 3: Photo by Timothy Dykes on Unsplash - Slide 6: Image by Fathromi Ramdlon from Pixabay - Slide 11: Image by Nikin from Pixabay - Slide 12: Image by Tom from Pixabay - Slide xx: Photo by Joanna Kosinska on Unsplash - Slide 19: Photo by Siddharth Salve on Unsplash - Slide 31: Photo by MongeDraws Attributions