copy-edit viget posts

This commit is contained in:
David Eisinger
2023-10-24 20:48:09 -04:00
parent 0438a6d828
commit f86f391e82
77 changed files with 1663 additions and 1380 deletions

View File

@@ -2,7 +2,6 @@
title: "OTP: a Functional Approach (or Three)"
date: 2015-01-29T00:00:00+00:00
draft: false
needs_review: true
canonical_url: https://www.viget.com/articles/otp-ocaml-haskell-elixir/
---
@@ -16,20 +15,22 @@ programs the same so that I could easily see their similarities and
differences. Check out the `encrypt` program in
[all](https://github.com/vigetlabs/otp/blob/master/languages/OCaml/encrypt.ml)
[three](https://github.com/vigetlabs/otp/blob/master/languages/Haskell/encrypt.hs)
[languages](https://github.com/vigetlabs/otp/blob/master/languages/Elixir/encrypt)
[languages](https://github.com/vigetlabs/otp/blob/master/languages/Elixir/apps/encrypt/lib/encrypt.ex)
and then I'll share some of my favorite parts. Go ahead, I'll wait.
## Don't Cross the Streams {#dontcrossthestreams}
## Don't Cross the Streams
One tricky part of the OTP challenge is that you have to cycle over the
key if it's shorter than the plaintext. My initial approaches involved
passing around an offset and using the modulo operator, [like
this](https://github.com/vigetlabs/otp/blob/6d607129f78ccafa9a294ca04da9e4c8bf7b7cc1/decrypt.ml#L11-L14):
let get_mask key index =
let c1 = List.nth key (index mod (List.length key))
and c2 = List.nth key ((index + 1) mod (List.length key)) in
int_from_hex_chars c1 c2
```ocaml
let get_mask key index =
let c1 = List.nth key (index mod (List.length key))
and c2 = List.nth key ((index + 1) mod (List.length key)) in
int_from_hex_chars c1 c2
```
Pretty gross, huh? Fortunately, both
[Haskell](http://hackage.haskell.org/package/base-4.7.0.2/docs/Prelude.html#v:cycle)
@@ -41,35 +42,42 @@ the [Batteries](http://batteries.forge.ocamlcore.org/) library) has the
(doubly-linked list) data structure. The OCaml code above becomes
simply:
let get_mask key =
let c1 = Dllist.get key
and c2 = Dllist.get (Dllist.next key) in
int_of_hex_chars c1 c2
```ocaml
let get_mask key =
let c1 = Dllist.get key
and c2 = Dllist.get (Dllist.next key) in
int_of_hex_chars c1 c2
```
No more passing around indexes or using `mod` to stay within the bounds
of the array -- the Dllist handles that for us.
Similarly, a naïve Elixir approach:
def get_mask(key, index) do
c1 = Enum.at(key, rem(index, length(key)))
c2 = Enum.at(key, rem(index + 1, length(key)))
int_of_hex_chars(c1, c2)
end
```elixir
def get_mask(key, index) do
c1 = Enum.at(key, rem(index, length(key)))
c2 = Enum.at(key, rem(index + 1, length(key)))
int_of_hex_chars(c1, c2)
end
```
And with streams activated:
def get_mask(key) do
Enum.take(key, 2) |> int_of_hex_chars
end
```elixir
def get_mask(key) do
Enum.take(key, 2) |> int_of_hex_chars
end
```
Check out the source code
([OCaml](https://github.com/vigetlabs/otp/blob/master/languages/OCaml/encrypt.ml),
[Haskell](https://github.com/vigetlabs/otp/blob/master/languages/Haskell/encrypt.hs),
[Elixir](https://github.com/vigetlabs/otp/blob/master/languages/Elixir/encrypt))
[Elixir](https://github.com/vigetlabs/otp/blob/master/languages/Elixir/apps/encrypt/lib/encrypt.ex))
to get a better sense of cyclical data structures in action.
## Partial Function Application {#partialfunctionapplication}
## Partial Function Application
Most programming languages have a clear distinction between function
arguments (input) and return values (output). The line is less clear in
@@ -77,16 +85,20 @@ arguments (input) and return values (output). The line is less clear in
languages like Haskell and OCaml. Check this out (from Haskell's `ghci`
interactive shell):
Prelude> let add x y = x + y
Prelude> add 5 7
12
```
Prelude> let add x y = x + y
Prelude> add 5 7
12
```
We create a function, `add`, that (seemingly) takes two arguments and
returns their sum.
Prelude> let add5 = add 5
Prelude> add5 7
12
```
Prelude> let add5 = add 5
Prelude> add5 7
12
```
But what's this? Using our existing `add` function, we've created
another function, `add5`, that takes a single argument and adds five to
@@ -97,8 +109,10 @@ argument and adds it to the argument passed to the initial function.
When you inspect the type of `add`, you can see this lack of distinction
between input and output:
Prelude> :type add
add :: Num a => a -> a -> a
```
Prelude> :type add
add :: Num a => a -> a -> a
```
Haskell and OCaml use a concept called
[*currying*](https://en.wikipedia.org/wiki/Currying) or partial function
@@ -113,13 +127,15 @@ numbers, pass the partially applied function `printf "%x"` to `map`,
[like
so](https://github.com/vigetlabs/otp/blob/master/languages/Haskell/encrypt.hs#L12):
hexStringOfInts nums = concat $ map (printf "%x") nums
```haskell
hexStringOfInts nums = concat $ map (printf "%x") nums
```
For more info on currying/partial function application, check out
[*Learn You a Haskell for Great
Good*](http://learnyouahaskell.com/higher-order-functions).
## A Friendly Compiler {#afriendlycompiler}
## A Friendly Compiler
I learned to program with C++ and Java, where `gcc` and `javac` weren't
my friends -- they were jerks, making me jump through a bunch of hoops
@@ -129,30 +145,36 @@ worked almost exclusively with interpreted languages in the intervening
languages with compilers that catch real issues. Here's my original
`decrypt` function in Haskell:
decrypt ciphertext key = case ciphertext of
[] -> []
c1:c2:cs -> xor (intOfHexChars [c1, c2]) (getMask key) : decrypt cs (drop 2 key)
```haskell
decrypt ciphertext key = case ciphertext of
[] -> []
c1:c2:cs -> xor (intOfHexChars [c1, c2]) (getMask key) : decrypt cs (drop 2 key)
```
Using pattern matching, I pull off the first two characters of the
ciphertext and decrypt them against they key, and then recurse on the
rest of the ciphertext. If the list is empty, we're done. When I
compiled the code, I received the following:
decrypt.hs:16:26: Warning:
Pattern match(es) are non-exhaustive
In a case alternative: Patterns not matched: [_]
```
decrypt.hs:16:26: Warning:
Pattern match(es) are non-exhaustive
In a case alternative: Patterns not matched: [_]
```
The Haskell compiler is telling me that I haven't accounted for a list
consisting of a single character. And sure enough, this is invalid input
that a user could nevertheless use to call the program. Adding the
following handles the failure and fixes the warning:
decrypt ciphertext key = case ciphertext of
[] -> []
[_] -> error "Invalid ciphertext"
c1:c2:cs -> xor (intOfHexChars [c1, c2]) (getMask key) : decrypt cs (drop 2 key)
```haskell
decrypt ciphertext key = case ciphertext of
[] -> []
[_] -> error "Invalid ciphertext"
c1:c2:cs -> xor (intOfHexChars [c1, c2]) (getMask key) : decrypt cs (drop 2 key)
```
## Elixir's \|\> operator {#elixirsoperator}
## Elixir's |> operator
According to [*Programming
Elixir*](https://pragprog.com/book/elixir/programming-elixir), the pipe
@@ -167,18 +189,22 @@ argument passed into the program, convert it to a list of characters,
and then turn it to a cyclical stream. My initial approach looked
something like this:
key = Stream.cycle(to_char_list(List.first(System.argv)))
```elixir
key = Stream.cycle(to_char_list(List.first(System.argv)))
```
Using the pipe operator, we can flip that around into something much
more readable:
key = System.argv |> List.first |> to_char_list |> Stream.cycle
```elixir
key = System.argv |> List.first |> to_char_list |> Stream.cycle
```
I like it. Reminds me of Unix pipes or any Western written language.
[Here's how I use the pipe operator in my encrypt
solution](https://github.com/vigetlabs/otp/blob/master/languages/Elixir/encrypt#L25-L31).
\* \* \*
***
At the end of this process, I think Haskell offers the most elegant code
and [Elixir](https://www.viget.com/services/elixir) the most potential