davideisinger.com

My personal website
Log | Files | Refs | README

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.