copy-edit viget posts
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
title: "Functional Programming in Ruby with Contracts"
|
||||
date: 2015-03-31T00:00:00+00:00
|
||||
draft: false
|
||||
needs_review: true
|
||||
canonical_url: https://www.viget.com/articles/functional-programming-in-ruby-with-contracts/
|
||||
---
|
||||
|
||||
@@ -15,48 +14,50 @@ docs](http://egonschiele.github.io/contracts.ruby/), I couldn't wait to
|
||||
try it out. I'd been doing some functional programming as part of our
|
||||
ongoing programming challenge series, and saw an opportunity to use
|
||||
Contracts to rewrite my Ruby solution to the [One-Time
|
||||
Pad](https://viget.com/extend/otp-a-language-agnostic-programming-challenge)
|
||||
Pad](/elsewhere/otp-a-language-agnostic-programming-challenge)
|
||||
problem. Check out my [rewritten `encrypt`
|
||||
program](https://github.com/vigetlabs/otp/blob/master/languages/Ruby/encrypt):
|
||||
|
||||
#!/usr/bin/env ruby
|
||||
```ruby
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require "contracts"
|
||||
include Contracts
|
||||
require "contracts"
|
||||
include Contracts
|
||||
|
||||
Char = -> (c) { c.is_a?(String) && c.length == 1 }
|
||||
Cycle = Enumerator::Lazy
|
||||
Char = -> (c) { c.is_a?(String) && c.length == 1 }
|
||||
Cycle = Enumerator::Lazy
|
||||
|
||||
Contract [Char, Char] => Num
|
||||
def int_of_hex_chars(chars)
|
||||
chars.join.to_i(16)
|
||||
end
|
||||
Contract [Char, Char] => Num
|
||||
def int_of_hex_chars(chars)
|
||||
chars.join.to_i(16)
|
||||
end
|
||||
|
||||
Contract ArrayOf[Num] => String
|
||||
def hex_string_of_ints(nums)
|
||||
nums.map { |n| n.to_s(16) }.join
|
||||
end
|
||||
Contract ArrayOf[Num] => String
|
||||
def hex_string_of_ints(nums)
|
||||
nums.map { |n| n.to_s(16) }.join
|
||||
end
|
||||
|
||||
Contract Cycle => Num
|
||||
def get_mask(key)
|
||||
int_of_hex_chars key.first(2)
|
||||
end
|
||||
Contract Cycle => Num
|
||||
def get_mask(key)
|
||||
int_of_hex_chars key.first(2)
|
||||
end
|
||||
|
||||
Contract [], Cycle => []
|
||||
def encrypt(plaintext, key)
|
||||
[]
|
||||
end
|
||||
Contract [], Cycle => []
|
||||
def encrypt(plaintext, key)
|
||||
[]
|
||||
end
|
||||
|
||||
Contract ArrayOf[Char], Cycle => ArrayOf[Num]
|
||||
def encrypt(plaintext, key)
|
||||
char = plaintext.first.ord ^ get_mask(key)
|
||||
[char] + encrypt(plaintext.drop(1), key.drop(2))
|
||||
end
|
||||
Contract ArrayOf[Char], Cycle => ArrayOf[Num]
|
||||
def encrypt(plaintext, key)
|
||||
char = plaintext.first.ord ^ get_mask(key)
|
||||
[char] + encrypt(plaintext.drop(1), key.drop(2))
|
||||
end
|
||||
|
||||
plaintext = STDIN.read.chars
|
||||
key = ARGV.last.chars.cycle.lazy
|
||||
plaintext = STDIN.read.chars
|
||||
key = ARGV.last.chars.cycle.lazy
|
||||
|
||||
print hex_string_of_ints(encrypt(plaintext, key))
|
||||
print hex_string_of_ints(encrypt(plaintext, key))
|
||||
```
|
||||
|
||||
Pretty cool, yeah? Compare with this [Haskell
|
||||
solution](https://github.com/vigetlabs/otp/blob/master/languages/Haskell/encrypt.hs).
|
||||
@@ -69,7 +70,7 @@ output. Give it the expected classes of the arguments and the return
|
||||
value, and you'll get a nicely formatted error message if the function
|
||||
is called with something else, or returns something else.
|
||||
|
||||
### Custom types with lambdas {#customtypeswithlambdas}
|
||||
### Custom types with lambdas
|
||||
|
||||
Ruby has no concept of a single character data type -- running
|
||||
`"string".chars` returns an array of single-character strings. We can
|
||||
@@ -81,14 +82,14 @@ says that the argument must be a string and must have a length of one.
|
||||
If you're expecting an array of a specific length and type, you can
|
||||
specify it, as I've done on line #9.
|
||||
|
||||
### Pattern matching {#patternmatching}
|
||||
### Pattern matching
|
||||
|
||||
Rather than one `encrypt` method with a conditional to see if the list
|
||||
is empty, we define the method twice: once for the base case (line #24)
|
||||
and once for the recursive case (line #29). This keeps our functions
|
||||
concise and allows us to do case-specific typechecking on the output.
|
||||
|
||||
### No unexpected `nil` {#nounexpectednil}
|
||||
### No unexpected `nil`
|
||||
|
||||
There's nothing worse than `undefined method 'foo' for nil:NilClass`,
|
||||
except maybe littering your methods with presence checks. Using
|
||||
@@ -96,7 +97,7 @@ Contracts, you can be sure that your functions aren't being called with
|
||||
`nil`. If it happens that `nil` is an acceptable input to your function,
|
||||
use `Maybe[Type]` à la Haskell.
|
||||
|
||||
### Lazy, circular lists {#lazycircularlists}
|
||||
### Lazy, circular lists
|
||||
|
||||
Unrelated to Contracts, but similarly inspired by *My Weird Ruby*, check
|
||||
out the rotating encryption key made with
|
||||
@@ -105,7 +106,7 @@ and
|
||||
[`lazy`](http://ruby-doc.org/core-2.1.0/Enumerable.html#method-i-lazy)
|
||||
on line #36.
|
||||
|
||||
\* \* \*
|
||||
***
|
||||
|
||||
As a professional Ruby developer with an interest in strongly typed
|
||||
functional languages, I'm totally psyched to start using Contracts on my
|
||||
|
||||
Reference in New Issue
Block a user