pErLM for the Perl Weekly Challenge 100
This week marks the 100th edition of the Perl Weekly Challenge a fantastic project started back in March 2019. Since the start it has garnered a lot of support from Perl developers and other languages.
This week, so far; I have solved the first solution in two languages. Firstly Perl and secondly in Elm. I like both languages a lot and decided to coin the term pErLM to describe my use of Perl as my back-end language and Elm as my front-end language.
Perl is the mature, super stable, well proven technology that runs almost anywhere and is flexible and allows many ways of doing things. Great library support and "boring" in the good sense of the term. Perl just works for me.
Elm on the other hand is new, very constrained and serves one purpose... creating web applications.
The combination or Perl and Elm (pErLM) provides a fantastic combination. Elm provides the interactivity on the front-end and Perl the flexibility on the back end. Where Elm provides types and constraints to work within, Perl provides options for those "side effects". Using a web framework like Dancer2 makes scaffolding up a back-end for a Elm app a breeze.
Both language have a great formatter and static analysis... sort of. Perl has Perl::Critic and Elm has well it's amazing compiler.
I recommend giving it a try, let me know how you get on. I'll be posting about how I have started adopting this pErLM shape on the side project I have been renovating from CGI to PSGI/Dancer2 to a JSON back-end and Elm front-end.
But that's the future... today lets look at how I implemented the challenge for week 100 in both languages.
Challenge 100
This weeks challenge is a time converter. When given a time in 24 hour format; return it in 12 hour format and vice versa.
Perl version
So I did this challenge on my normal style; starting with a .t test file:
use Test2::V0 -target => "Fun";
is $CLASS->convert("05:15 pm"), '17:15', 'Example 1-1';
is $CLASS->convert("05:15pm"), '17:15', 'Example 1-2';
is $CLASS->convert("19:15"), '07:15pm', 'Example 2-1';
is $CLASS->convert("09:15"), '09:15am', '24hr AM';
done_testing;
Four simple tests (yes I did them one at a time in a TDD-esque style), the code I ended up with looks like this:
package Fun;
use Moo;
sub convert {
my ( $self, $time ) = @_;
if ( $time =~ m/([ap]m)/ ) {
my $am_pm = $1;
$time =~ m/^(\d+):(\d+)/;
my $hours = $1;
my $minutes = $2;
if ( $am_pm eq 'pm' ) {
$hours += 12;
}
return "$hours:$minutes";
}
else {
$time =~ m/^(\d+):(\d+)/;
my $hours = $1;
my $minutes = $2;
if ( $hours > 12 ) {
$hours -= 12;
return sprintf( "%02d", $hours ) . ":$minutes" . 'pm';
}
else {
return sprintf( "%02d", $hours ) . ":$minutes" . 'am';
}
}
}
1;
Nothing too clever to talk about here; this is a un-refined solution; but works and provably works.
I also created a small script file to meet the requirement to use this from the command line:
use strict;
use warnings;
use lib './lib';
use Fun;
my $fun = Fun->new;
print $fun->convert($ARGV[0]);
print "\n";
Short and to the point. I have a confession to make; Mohammad if you are reading this I saw the "Ideally we expect a one-liner." instruction... and ignored it completely! :-)
Why?
Because I don't have an interest in one-liners; it is partially as I am too lazy to put the work in and partially because I don't like to promote Perl in that context. Perl is a graceful language; but can also be a code golfers paradise. One liners and code golfing is, to use a common refrain, "considered harmful" for the Perl community I think broadly.
The main reason I say that is the external perspective of Perl, I read too many tweets and blog posts that when they talk about Perl it's not positive often because of the perception created by the "clever" Perl one-liner. To be clear, the clever one-liners are superbly powerful and useful in the right hands. But I feel that they should be what we discover later in our Perl journey and not be the think we place front and centre.
That's just a personal perspective. Give it some thought and tell me what you think.
Elm
My Elm solution is... ugly and incomplete. It's not a functional web app just a some code that does the conversion. I did the least effort possible, its not DRY, it's not thought through... you have been warned.
So again, I start with a test; the final version here (again I started with a single test):
module Example exposing (..)
import Expect exposing (Expectation)
import Fun exposing (convert)
import Fuzz exposing (Fuzzer, int, list, string)
import Test exposing (..)
suite : Test
suite =
describe "Fun"
[ test "convert 07:15" <|
\_ ->
let
got =
convert "07:15"
in
Expect.equal got "07:15am"
, test "convert 05:15pm" <|
\_ ->
let
got =
convert "05:15 pm"
in
Expect.equal got "17:15"
, test "convert 19:15" <|
\_ ->
let
got =
convert "19:15"
in
Expect.equal got "07:15pm"
, test "convert 09:15" <|
\_ ->
let
got =
convert "09:15 am"
in
Expect.equal got "09:15"
]
And the (ugly... I have warned you) code looks like this:
module Fun exposing (convert)
convert : String -> String
convert time =
if String.contains "am" time then
String.slice 0 5 time
else if String.contains "pm" time then
let
hourMin =
String.split ":" time
in
case List.head hourMin of
Just a ->
let
hour =
String.toInt a
in
case hour of
Just h ->
let
hx =
h + 12
in
String.fromInt hx ++ String.slice 2 5 time
Nothing ->
""
Nothing ->
""
else
let
hourMin =
String.split ":" time
in
case List.head hourMin of
Just a ->
let
hour =
String.toInt a
in
case hour of
Just h ->
if h > 12 then
let
hx =
h - 12
in
let
hourStr =
String.fromInt hx
in
if String.length hourStr > 1 then
hourStr ++ String.slice 2 5 time ++ "pm"
else
"0" ++ hourStr ++ String.slice 2 5 time ++ "pm"
else
String.slice 0 5 time ++ "am"
Nothing ->
""
Nothing ->
""
As you can see lots of ugly repetition and some naive approaches. I've not broken the code out into sub functions there is not typing etc. But it works and it provably works.
Thoughts?
As you can see neither solution wins any prizes for conciseness; but interestingly the Perl is tidier. This is partially because I naturally write better Perl that Elm at the moment. Especially "first pass" code like these two example. The other reason in my opinion is that Perl is well suited to the task and Elm less so.
Elm is designed to create a interactive web application, handling human interactions (via messages) and representing these changes in a web page. Being a functional language side effects are intentional discouraged and immutable data is what we want. It is ideal for interactivity, sending and receiving data from an API and that sort of thing.
Perl is really well suited to handling text, massaging it into something else and returning it. Even my simplistic implementation is smaller and makes more sense than the Elm equivalent. I think after some iterations and refactoring the Elm would be a lot tidier... but again it's pushing against the flow of what Elm wants to be.
So I like this as an example of why I like my new "pErLM" stack. Elm does one thing and does it well, front-end. Perl does not work here at all; but is ideal for a stable back-end. Where Perl is flexible and able to do a wide variety of tasks well; Elm is tightly constrained and does not make doing things other than a web front-end easy.
So doing this dual implementation is for me an example of using the right tool for the right job.
Next?
So after this, I want to do the challenge in another language. Clojure (Lisp) I think; a language I have never worked in. Maybe PHP (that's an easy one I know) or perhaps Node if time/energy permits. But those are easy choices; so I want to try this in a lisp as it's very different... what other languages do you think I could try?
Drop me a note and let me know.