index.md (4208B)
1 --- 2 title: "Functional Programming in Ruby with Contracts" 3 date: 2015-03-31T00:00:00+00:00 4 draft: false 5 canonical_url: https://www.viget.com/articles/functional-programming-in-ruby-with-contracts/ 6 --- 7 8 I read Thomas Reynolds' [*My Weird 9 Ruby*](http://awardwinningfjords.com/2015/03/03/my-weird-ruby.html) a 10 week or two ago, and I **loved** it. I'd never heard of the 11 [Contracts](https://github.com/egonSchiele/contracts.ruby) gem, but 12 after reading the post and the [well-written 13 docs](http://egonschiele.github.io/contracts.ruby/), I couldn't wait to 14 try it out. I'd been doing some functional programming as part of our 15 ongoing programming challenge series, and saw an opportunity to use 16 Contracts to rewrite my Ruby solution to the [One-Time 17 Pad](/elsewhere/otp-a-language-agnostic-programming-challenge/) 18 problem. Check out my [rewritten `encrypt` 19 program](https://github.com/vigetlabs/otp/blob/master/languages/Ruby/encrypt): 20 21 ```ruby 22 #!/usr/bin/env ruby 23 24 require "contracts" 25 include Contracts 26 27 Char = -> (c) { c.is_a?(String) && c.length == 1 } 28 Cycle = Enumerator::Lazy 29 30 Contract [Char, Char] => Num 31 def int_of_hex_chars(chars) 32 chars.join.to_i(16) 33 end 34 35 Contract ArrayOf[Num] => String 36 def hex_string_of_ints(nums) 37 nums.map { |n| n.to_s(16) }.join 38 end 39 40 Contract Cycle => Num 41 def get_mask(key) 42 int_of_hex_chars key.first(2) 43 end 44 45 Contract [], Cycle => [] 46 def encrypt(plaintext, key) 47 [] 48 end 49 50 Contract ArrayOf[Char], Cycle => ArrayOf[Num] 51 def encrypt(plaintext, key) 52 char = plaintext.first.ord ^ get_mask(key) 53 [char] + encrypt(plaintext.drop(1), key.drop(2)) 54 end 55 56 plaintext = STDIN.read.chars 57 key = ARGV.last.chars.cycle.lazy 58 59 print hex_string_of_ints(encrypt(plaintext, key)) 60 ``` 61 62 Pretty cool, yeah? Compare with this [Haskell 63 solution](https://github.com/vigetlabs/otp/blob/master/languages/Haskell/encrypt.hs). 64 Some highlights: 65 66 ### Typechecking 67 68 At its most basic, Contracts offers typechecking on function input and 69 output. Give it the expected classes of the arguments and the return 70 value, and you'll get a nicely formatted error message if the function 71 is called with something else, or returns something else. 72 73 ### Custom types with lambdas 74 75 Ruby has no concept of a single character data type -- running 76 `"string".chars` returns an array of single-character strings. We can 77 simulate a native char type using a lambda, as seen on line #6, which 78 says that the argument must be a string and must have a length of one. 79 80 ### Tuples 81 82 If you're expecting an array of a specific length and type, you can 83 specify it, as I've done on line #9. 84 85 ### Pattern matching 86 87 Rather than one `encrypt` method with a conditional to see if the list 88 is empty, we define the method twice: once for the base case (line #24) 89 and once for the recursive case (line #29). This keeps our functions 90 concise and allows us to do case-specific typechecking on the output. 91 92 ### No unexpected `nil` 93 94 There's nothing worse than `undefined method 'foo' for nil:NilClass`, 95 except maybe littering your methods with presence checks. Using 96 Contracts, you can be sure that your functions aren't being called with 97 `nil`. If it happens that `nil` is an acceptable input to your function, 98 use `Maybe[Type]` à la Haskell. 99 100 ### Lazy, circular lists 101 102 Unrelated to Contracts, but similarly inspired by *My Weird Ruby*, check 103 out the rotating encryption key made with 104 [`cycle`](http://ruby-doc.org/core-2.1.0/Enumerable.html#method-i-cycle) 105 and 106 [`lazy`](http://ruby-doc.org/core-2.1.0/Enumerable.html#method-i-lazy) 107 on line #36. 108 109 *** 110 111 As a professional Ruby developer with an interest in strongly typed 112 functional languages, I'm totally psyched to start using Contracts on my 113 projects. While you don't get the benefits of compile-time checking, you 114 do get cleaner functions, better implicit documentation, and more 115 overall confidence about your code. 116 117 And even if Contracts or FP aren't your thing, from a broader 118 perspective, this demonstrates that **experimenting with other 119 programming paradigms makes you a better programmer in your primary 120 language.** It was so easy to see the utility and application of 121 Contracts while reading *My Weird Ruby*, which would not have been the 122 case had I not spent time with Haskell, OCaml, and Elixir.