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

Error Handling Without Exceptions

Error Handling Without Exceptions

A quick jaunt through functional error handling patterns using Ruby

Ben Darfler

May 06, 2015
Tweet

More Decks by Ben Darfler

Other Decks in Programming

Transcript

  1. A Guiding Example ['1','2','3'].map{ |x| Integer(x) }.reduce(:+) => 6 ['1','2','3','zero'].map{

    |x| Integer(x) }.reduce(:+) ArgumentError: invalid value for Integer(): "zero"
  2. “ Errors aren’t exceptions because they’re not exceptional - they’re

    real possible outcomes with their own information - treat them as such - Some Guy I Know
  3. Can We Do Better? def safe_to_i(value) Integer(value) rescue nil end

    ['1','2','3','zero'].map{ |x| safe_to_i(x) }
  4. Can We Do Better? def safe_to_i(value) Integer(value) rescue nil end

    ['1','2','3','zero'].map{ |x| safe_to_i(x) } => [1, 2, 3, nil]
  5. Can We Do Better? def safe_to_i(value) Integer(value) rescue nil end

    ['1','2','3','zero'].map{ |x| safe_to_i(x) } => [1, 2, 3, nil] ['1','2','3','zero'].map{ |x| safe_to_i(x) }.reduce(:+)
  6. Can We Do Better? def safe_to_i(value) Integer(value) rescue nil end

    ['1','2','3','zero'].map{ |x| safe_to_i(x) } => [1, 2, 3, nil] ['1','2','3','zero'].map{ |x| safe_to_i(x) }.reduce(:+) TypeError: nil can't be coerced into Fixnum
  7. Take Three def safer_to_i(value) [Integer(value)] rescue [] end ['1','2','3','zero'].map{ |x|

    safer_to_i(x) } => [[1], [2], [3], []] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.flatten
  8. Take Three def safer_to_i(value) [Integer(value)] rescue [] end ['1','2','3','zero'].map{ |x|

    safer_to_i(x) } => [[1], [2], [3], []] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.flatten => [1, 2, 3]
  9. Take Three def safer_to_i(value) [Integer(value)] rescue [] end ['1','2','3','zero'].map{ |x|

    safer_to_i(x) } => [[1], [2], [3], []] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.flatten => [1, 2, 3] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.flatten.reduce(:+)
  10. Take Three def safer_to_i(value) [Integer(value)] rescue [] end ['1','2','3','zero'].map{ |x|

    safer_to_i(x) } => [[1], [2], [3], []] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.flatten => [1, 2, 3] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.flatten.reduce(:+) => 6
  11. But I Wanted To Fail! class Array def sequence self.reduce([[]]){

    |a,b| a.flat_map{ |aa| b.map{ |bb| aa << bb }}} end end
  12. But I Wanted To Fail! class Array def sequence self.reduce([[]]){

    |a,b| a.flat_map{ |aa| b.map{ |bb| aa << bb }}} end end ['1','2','3'].map{ |x| safer_to_i(x) } => [[1], [2], [3]]
  13. But I Wanted To Fail! class Array def sequence self.reduce([[]]){

    |a,b| a.flat_map{ |aa| b.map{ |bb| aa << bb }}} end end ['1','2','3'].map{ |x| safer_to_i(x) } => [[1], [2], [3]] ['1','2','3'].map{ |x| safer_to_i(x) }.sequence
  14. But I Wanted To Fail! class Array def sequence self.reduce([[]]){

    |a,b| a.flat_map{ |aa| b.map{ |bb| aa << bb }}} end end ['1','2','3'].map{ |x| safer_to_i(x) } => [[1], [2], [3]] ['1','2','3'].map{ |x| safer_to_i(x) }.sequence => [[1, 2, 3]]
  15. But I Wanted To Fail! class Array def sequence self.reduce([[]]){

    |a,b| a.flat_map{ |aa| b.map{ |bb| aa << bb }}} end end ['1','2','3','zero'].map{ |x| safer_to_i(x) } => [[1], [2], [3], []]
  16. But I Wanted To Fail! class Array def sequence self.reduce([[]]){

    |a,b| a.flat_map{ |aa| b.map{ |bb| aa << bb }}} end end ['1','2','3','zero'].map{ |x| safer_to_i(x) } => [[1], [2], [3], []] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.sequence
  17. But I Wanted To Fail! class Array def sequence self.reduce([[]]){

    |a,b| a.flat_map{ |aa| b.map{ |bb| aa << bb }}} end end ['1','2','3','zero'].map{ |x| safer_to_i(x) } => [[1], [2], [3], []] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.sequence => []
  18. But I Wanted To Fail! ['1','2','3'].map{ |x| safer_to_i(x) }.sequence =>

    [[1, 2, 3]] ['1','2','3'].map{ |x| safer_to_i(x) }.sequence.map{ |x| x.reduce(:+) }
  19. But I Wanted To Fail! ['1','2','3'].map{ |x| safer_to_i(x) }.sequence =>

    [[1, 2, 3]] ['1','2','3'].map{ |x| safer_to_i(x) }.sequence.map{ |x| x.reduce(:+) } => [6]
  20. But I Wanted To Fail! ['1','2','3'].map{ |x| safer_to_i(x) }.sequence =>

    [[1, 2, 3]] ['1','2','3'].map{ |x| safer_to_i(x) }.sequence.map{ |x| x.reduce(:+) } => [6] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.sequence
  21. But I Wanted To Fail! ['1','2','3'].map{ |x| safer_to_i(x) }.sequence =>

    [[1, 2, 3]] ['1','2','3'].map{ |x| safer_to_i(x) }.sequence.map{ |x| x.reduce(:+) } => [6] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.sequence => []
  22. But I Wanted To Fail! ['1','2','3'].map{ |x| safer_to_i(x) }.sequence =>

    [[1, 2, 3]] ['1','2','3'].map{ |x| safer_to_i(x) }.sequence.map{ |x| x.reduce(:+) } => [6] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.sequence => [] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.sequence .map{ |x| x.reduce(:+) }
  23. But I Wanted To Fail! ['1','2','3'].map{ |x| safer_to_i(x) }.sequence =>

    [[1, 2, 3]] ['1','2','3'].map{ |x| safer_to_i(x) }.sequence.map{ |x| x.reduce(:+) } => [6] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.sequence => [] ['1','2','3','zero'].map{ |x| safer_to_i(x) }.sequence .map{ |x| x.reduce(:+) } => []
  24. Where To Next? def saferToInt(str: String): Option[Int] = try {

    Some(str.toInt) } catch { case e: Exception => None } class ListOps[A](list: List[Option[A]]) { def sequence[A](a: List[Option[A]]): Option[List[A]] = a.foldRight[Option[List[A]]](Some(Nil))((a,b)=>a.flatMap(aa=>b.map(bb=>aa::bb))) def sequence: Option[List[A]] = sequence(list) } implicit def listToListOps[A](list: List[Option[A]]) = new ListOps(list)
  25. Where To Next? List("1","2","3").map(saferToInt) res12: List[Option[Int]] = List(Some(1), Some(2), Some(3))

    List("1","2","3").map(saferToInt).sequence res13: Option[List[Int]] = Some(List(1, 2, 3)) List("1","2","3").map(saferToInt).sequence.map(_.reduce(_+_)) res14: Option[Int] = Some(6)
  26. Where To Next? List("1","2","3","zero").map(saferToInt) res16: List[Option[Int]] = List(Some(1), Some(2), Some(3),

    None) List("1","2","3","zero").map(saferToInt).sequence res17: Option[List[Int]] = None List("1","2","3","zero").map(saferToInt).sequence.map(_.reduce(_+_)) res18: Option[Int] = None