One of the things I want to challenge myself to do is learn some more useful things, and one of the languages they’re using at work is Elixir. It’s a functional language, not a procedural language like Perl, so this is not only learning a new language but it’s learning a new way to think about code.
Since I can’t really learn by just reading about a language or watching a bunch of excellent videos one of my coworkers produced, I decided that I needed to start doing the Perl Weekly Challenge tasks in Elixir. Today, I’m tackling PWC 267 Task 1.
One of the first things that wound up biting me was trying to do something like this:
if product == 0 do
sign = 0
else
sign = product / abs(product)
done
sign
But what I was getting were warnings:
warning: variable "sign" is unused (if the variable is not meant to be used, prefix it with an underscore)
│
7 │ sign = product / abs(product)
│ ~
│
└─ elixir/ch-1.exs:7:7: PWC.productSign/1
warning: variable "sign" is unused (if the variable is not meant to be used, prefix it with an underscore)
│
5 │ sign = 0
│ ~
│
└─ elixir/ch-1.exs:5:7: PWC.productSign/1
error: undefined variable "sign"
│
9 │ sign
│ ^^^^
│
└─ elixir/ch-1.exs:9:5: PWC.productSign/1
This confused me for a while, but then I found out what was happening:
This is also a good opportunity to talk about variable scoping in Elixir. If any variable is declared or changed inside
Elixir: case, cond, and ifif
,case
, and similar constructs, the declaration and change will only be visible inside the construct.
I finally wound up with the following implementation for the task:
# You are given an array of @ints.
# Write a script to find the sign of product of all integers in
# the given array. The sign is 1 if the product is positive,
# -1 if the product is negative and 0 if product is zero.
# Example 1
# Input: @ints = (-1, -2, -3, -4, 3, 2, 1)
# Output: 1
#
# The product -1 x -2 x -3 x -4 x 3 x 2 x 1 => 144 > 0
# Example 2
# Input: @ints = (1, 2, 0, -2, -1)
# Output: 0
#
# The product 1 x 2 x 0 x -2 x -1 => 0
# Example 3
# Input: @ints = (-1, -1, 1, -1, 2)
# Output: -1
#
# The product -1 x -1 x 1 x -1 x 2 => -2 < 0
defmodule PWC do
def productSign(ints) do
product = Enum.reduce(ints, &( &1 * &2 ))
sign = if product == 0, do: 0,
else: div(product, abs(product))
explain = "The product "
<> Enum.join(ints, " × ")
<> " => "
<> to_string(product)
explain = cond do
sign > 0 -> explain <> " > 0"
sign < 0 -> explain <> " < 0"
true -> explain
end
{sign, explain}
end
def solution(ints) do
IO.puts("Input: @ints = (" <> Enum.join(ints, ", ") <> ")")
{sign, explain} = PWC.productSign(ints)
IO.puts("Output: " <> to_string(sign) )
IO.puts("\n" <> explain)
end
end
IO.puts("Example 1:")
PWC.solution([-1, -2, -3, -4, 3, 2, 1])
IO.puts("")
IO.puts("Example 2:")
PWC.solution([1, 2, 0, -2, -1])
IO.puts("")
IO.puts("Example 3:")
PWC.solution([-1, -1, 1, -1, 2])
$ elixir elixir/ch-1.exs
Example 1:
Input: @ints = (-1, -2, -3, -4, 3, 2, 1)
Output: 1
The product -1 × -2 × -3 × -4 × 3 × 2 × 1 => 144 > 0
Example 2:
Input: @ints = (1, 2, 0, -2, -1)
Output: 0
The product 1 × 2 × 0 × -2 × -1 => 0
Example 3:
Input: @ints = (-1, -1, 1, -1, 2)
Output: -1
The product -1 × -1 × 1 × -1 × 2 => -2 < 0
This still feels a lot like the solutions I put together in Raku, Perl, and Python, though. 🤔
Today I showed my coworker Doug this post, and he had some suggestions: he said he was a fan of “little guard functions”:
defp sign(n) when n > 0, do: " > 0"
defp sign(n) when n < 0, do: " < 0"
defp sign(_), do: ""
This defined different functions that depend on the values of the parameters, which is a pattern that I’m seeing a lot in examples, but hadn’t thought to use. And though he liked my use of Enum.reduce
, he pointed me at Enum.product
, which already did what I needed Enum.reduce
to do. He also modeled the proper way to do string interpolation:
def productSign(ints) do
product = Enum.product(ints)
sign =
case product do
0 -> 0
_ -> div(product, abs(product))
end
explain =
"The product #{Enum.join(ints, " × ")} => #{to_string(product)}#{sign(sign)}"
{sign, explain}
end
So, with that feedback, I was able to refine my solution
function that I use to call productSign
and format the output:
defmodule PWC do
defp sign(n) when n > 0, do: " > 0"
defp sign(n) when n < 0, do: " < 0"
defp sign(_), do: ""
def productSign(ints) do
product = Enum.product(ints)
sign =
case product do
0 -> 0
_ -> div(product, abs(product))
end
explain =
"The product #{Enum.join(ints, " × ")} => #{to_string(product)}#{sign(sign)}"
{sign, explain}
end
def solution(ints) do
IO.puts("Input: @ints = (#{Enum.join(ints, ", ")})")
{sign, explain} = PWC.productSign(ints)
IO.puts("Output: #{to_string(sign)}\n#{explain}")
end
end
That feels a bit more functional and less like my earlier procedural solutions in Raku and Perl. Tonight, I’ll tackle this week’s Task 2 and see how functional I can get it right out of the gate.
The code can be found on GitHub at https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-267/packy-anderson/elixir