Being humbled by Unison and Exercism
My first exercise with Unison and its Exercism track was both humbling and rewarding.
I’m not including the “Hello World” exercise here because its goal is more to get you set up with the exercism pipeline and not to actually teach you anything or test real knowledge. The next problem I attempted was called Acronym. The following is the description of the exercise:
Convert a phrase to its acronym. Techies love their TLA (Three Letter Acronyms)! Help generate some jargon by writing a program that converts a long name like Portable Network Graphics to its acronym (PNG). Punctuation is handled as follows: hyphens are word separators (like whitespace); all other punctuation can be removed from the input.
The sample conversions showing what will pass the tests are (I added the last one to reinforce the idea that extraneous punctuation cannot have an effect on the acronym):
|As Soon As Possible||ASAP|
|Thank George It’s Friday||TGIF|
|Portable _network_ graphics||PNG|
At first blush, I think that I’ll want to strip all non-splitting punctuation from the input. Then, uppercase it all. Then, split on either whitespace or a hyphen, take the first character of each word and then finally rejoin into a string.
My single biggest difficulty was in finding the various functions on the
Text type. Using unison share was confusing for me and I had to ask for help in their slack a number of times to work my way through it.
Here is what I came up with after multiple iterations and discussions with folks on the Unison slack. I think this was my fourth or fifth revision:
abbreviate : Text -> Text abbreviate input = let input |> dropWhile (is (not (anyOf [?\s, ?-] + alphanumeric))) |> segmentBy ((c -> (c Char.== ?\s) || (c Char.== ?-)) >> not) |> List.map (elem -> Text.toUppercase (Text.take 1 elem)) |> Text.join ""
The functional pipeline basically goes:
drop(not space, -, or alphanumeric)
split(on space or -)
take 1st char
The function call
anyOf [?\s, ?-] + alphanumeric uses some DSL-like functionality (
anyOf) in the
The final version that I got working on Exercism looks like this:
abbreviate : Text -> Text abbreviate input = (+) = or input |> Text.segmentBy (is (in "'" + not (whitespace + punctuation))) |> map (Text.take 1) |> Text.join "" |> Text.toUppercase
(+) = or is required because that particular alias isn’t in the current version of the base library. That can be removed shortly after the next update happens (I’m assuming).
What I think helps with readability here is that I’m not using any of the special “monad-ish” functions like
<| that I had in a few intermediate solutions. I think this code is readable enough for someone who doesn’t sling Unison all day to understand.
⚠️ Fun fact: both
isPunct consider the apostrophe punctuation, but we don’t want to split on apostrophes, so it has to be explicitly ignored
It’s been a long time since I’ve stumbled and failed so miserably to build out such a simple solution. I think there are a couple of factors, the largest of which is that I just don’t get to play with Unison frequently enough to build up the same kind of muscle memory that I have with Rust and Go and Elixir.
I greatly enjoyed learning and “failing with purpose” and am eagerly looking forward to the next exercise that I can find time to work through.