Write and publish your first Elixir library
Table of Contents
As some of you have heard lately, Elixir is the new hotness. Is it just hype? Well, I thought so at first, but I told myself “heck, even if it’s a waste of time, at least I’ll broaden my horizons”. Which, if you think about it, it not really is a waste of time.
Long story short, after couple of weeks of fiddling with the language, mostly by playing with it’s web framework I am delighted to say that it’s a really cool language that you should try out and also, I published a really small API wrapper for Elixir.
So today, I am going to walk you through writing a simple Elixir library and publishing it to Hex.pm, so it will be available for the whole world to use.
Got a yes/no question? #
I am very sure all of you have heard about the Magic 8 Ball. It’s this toy that looks like a black and white 8 ball (hence the name). When you ask it a yes/no question it will (magically) answer the question.
Why don’t we create a small Elixir library that one can ask questions to and
get the question answered? We’ll call it eight_ball
, paying our respect to
the Magic 8 Ball.
Let’s mix it up! #
Mix
is Elixir’s build tool, that allows you to easily create projects, manage
tasks, run tests and much more. Mix
also can manage dependencies and
integrates very nicely with Hex. Hex is a package manager that
provides dependency resolution and the ability to remotely fetch packages. We
will publish our project to Hex.pm at the end of this tutorial.
But first, let’s create a new Elixir project. Project skeletons are created with the command:
mix new <project-name-here>
mix new eight_ball
When you run the command, you will get an output like this:
➜ mix new eight_ball
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/eight_ball.ex
* creating test
* creating test/test_helper.exs
* creating test/eight_ball_test.exs
Your mix project was created successfully.
You can use mix to compile it, test it, and more:
cd eight_ball
mix test
Run `mix help` for more commands.
If you open the project directory with your favourite text editor, you can see all of the files that were generated. If you have seen any other Elixir projects, the file structure is pretty much the same.
What is what? #
Just in case anyone gets confused by all of the generated files, let’s see what is what here:
- README.md -> This is just the README of the project
- .gitignore -> .gitignore file
- mix.exs -> the Mixfile. It’s basically the project definition. If you have any experience with Ruby, this is simillar to the .gemspec file
- config -> configuration files directory
- config/config.exs -> Application and it’s dependencies configuration
- lib -> Where the code of our library will live
- lib/eight_ball.ex -> The EightBall module
- test -> Tests directory
- test/test_helper.exs -> Test helper file
- test/eight_ball_test.exs -> The test file for the EightBall module
The answers #
According to the Wikipedia page for the Magic 8 Ball, the 20 answers inside a standard Magic 8 Ball are:
1. It is certain2. It is decidedly so
3. Without a doubt
4. Yes, definitely
5. You may rely on it
6. As I see it, yes
7. Most likely
8. Outlook good
9. Yes
10. Signs point to yes
11. Reply hazy try again
12. Ask again later
13. Better not tell you now
14. Cannot predict now
15. Concentrate and ask again
16. Don't count on it
17. My reply is no
18. My sources say no
19. Outlook not so good
20. Very doubtful
Let’s make a list from these answers and add them as a module variable in the
EightBall
module.
defmodule EightBall do
# Found at https://en.wikipedia.org/wiki/Magic_8-Ball#Possible_answers
@answers [
"It is certain",
"It is decidedly so",
"Without a doubt",
"Yes, definitely",
"You may rely on it",
"As I see it, yes",
"Most likely",
"Outlook good",
"Yes",
"Signs point to yes",
"Reply hazy try again",
"Ask again later",
"Better not tell you now",
"Cannot predict now",
"Concentrate and ask again",
"Don't count on it",
"My reply is no",
"My sources say no",
"Outlook not so good",
"Very doubtful"
]
end
Our next step will be to introduce a ask/1
function to our module. It will
receive the question as a argument and return an answer. Now, we are faced with
a problem. The Magic 8 Ball magically answers questions, because, it’s a magic
ball. But, although programming often looks like magic to other people, we
know that you can’t program “magic”. So, we’ll go with a random answer to a
question.
We will know it’s not magic, but atleast, it will look like it is!
defmodule EightBall do
def ask(_question) do
@answers |> Enum.shuffle |> List.first
end
end
@answers
list is intentionally omitted.
The function takes the @answers
list, shuffles it using Enum.shuffle/1
(docs) and
pipes it into List.first/1
(docs) which
will take the first item from the shuffled list and return it.
Starting Elixir v1.1, the core team added the Enum.take_random/2
(docs)
function, that takes n
random items from a list.
If you prefer to use v1.1 with Enum.take_random/2
:
defmodule EightBall do
def ask(_question) do
@answers |> Enum.take_random(1) |> List.first
end
end
What about the question? #
In the EightBall.ask/1
function, we don’t use the actual question. As you can
see, we ignore the question argument by prepending the argument name with an
underscore, or _question
.
Why don’t we do something with the question? For example, let’s validate it? Every question is compiled of words and every question ends with e a question mark. So, we want a string as an argument, which ends with a question mark. Any other type of argument should be ignored. Or, rather, inform the user of our library that it expects a question.
Let’s add some validation to our little ask/1
function. I will wish the
interface of the validator first and we’ll write the implementation after that.
defmodule EightBall do
def ask(question) do
EightBall.QuestionValidator.validate!(question)
@answers |> Enum.shuffle |> List.first
end
end
Now, the EightBall.QuestionValidator.validate!/1
function will take the
question as an argument and throw an error if the question does not have the
format we expect.
QuestionValidator #
Let’s write our simple validator.
defmodule EightBall.QuestionValidator do
# Question must end with a '?'
@validation_regex ~r/\?$/
def validate!(question) when is_binary(question) do
unless String.strip(question) =~ @validation_regex, do: throw_validation_error
end
def validate!(question) do
throw_validation_error
end
defp throw_validation_error do
throw "Question must be a string, ending with a question mark."
end
end
Looking at the code, top to bottom, there are couple of key points. First, the
@validation_regex
variable, is a regular expression. It will match any
strings that end with a question mark.
Second, the validate!/1
function. The first function clause will match when
the question is a binary. Why binary? Well, Elixir uses UTF8 encoding for
string, which basically makes strings UTF8 encoded
binaries.
If the first function clause matches, it will strip the unnecessary whitespace
using String.strip/1
and match the @validation_regex
. If it does not match,
it will throw an error.
Also, if the first function clause does not match, it will throw a validation error.
Testing with IEx #
Let’s see how our library works in IEx (Interactive EliXir). In the project directory, run:
iex -S mix
IEx will compile and load our library in the IEx session, so we can start using it right away. Try running:
iex(1)> EightBall.ask("Can I fly?")
"Without a doubt"
When I asked a question, the library returned “Without a doubt”. Nice! Let’s check our validation:
iex(2)> EightBall.ask("This should fail.")
** (throw) "Question must be a string, ending with a question mark."
(eight_ball) lib/eight_ball/question_validator.ex:14: EightBall.QuestionValidator.throw_validation_error/0
(eight_ball) lib/eight_ball.ex:27: EightBall.ask/1
When we sent a statement instead of a question, the library threw an error. Good. Another (and better) way to test this is by writing actual tests, but, we’ll leave that for another day.
Publishing EightBall
to Hex #
Hex has a very good documentation on publishing packages. If you want to read and understand the details, head over to the documentation. Here, we’ll just cover the basic steps needed to publish this libraty to Hex.pm.
Registration #
If you do not have a user registered, you can do it via the command line:
mix hex.user register
Hex will prompt for your username, email and password and then it will create an
API key that will be stored in the ~/.hex
directory.
Defining the package #
The package is configured in the project
function in the project’s mix.exs
file.
Let’s create a private function in our EightBall.Mixfile
module and call it
package
:
defp package do
[
files: ["lib", "mix.exs", "README", "LICENSE*"],
maintainers: ["Ilija Eftimov"],
licenses: ["Apache 2.0"],
links: %{"GitHub" => "https://github.com/fteem/eight_ball"}
]
end
These properties will define the files of the package and some metadata like
maintainers’ name and licences. Then, in the EightBall.Mixfile.project/0
function, we need to include the package definiton:
defmodule EightBall.Mixfile do
use Mix.Project
def project do
[app: :eight_ball,
version: "0.0.1",
elixir: "~> 1.0",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps,
package: package ]
end
*snip*
end
Also, make sure the version is set properly. In our case, we can leave it as
0.0.1
. The last step after you are happy with the package definition is the
publish step itself. Publishing a package is done by running:
➜ eight_ball git:(master) mix hex.publish
Publishing eight_ball v0.0.1
Dependencies:
Files:
lib/eight_ball.ex
lib/eight_ball/question_validator.ex
mix.exs
README.md
LICENSE
App: eight_ball
Name: eight_ball
Version: 0.0.1
Build tools: mix
Description: Library that acts like a real life Magic 8 Ball.
Licenses: Apache 2.0
Maintainers: Ilija Eftimov
Links:
GitHub: https://github.com/fteem/eight_ball
Elixir: ~> 1.0
Before publishing, please read Hex Code of Conduct: https://hex.pm/docs/codeofconduct
Proceed? [Yn] y
[#########################] 100%
Published at https://hex.pm/packages/eight_ball/0.0.1
Don't forget to upload your documentation with `mix hex.docs`
Easy as that. After publishing your package, it’s available at it’s Hex.pm page.
Outro #
That’s it. As you can see, publishing the library to Hex.pm is quite straight forward. Also, generating the project skeleton is really easy with Mix. All it takes is one command and the project is set up.
If you are looking for more challenge, you can add some tests for the validator module. Also, adding documentation is quite important, so if you feel like writing some documentation - do it! But, whatever you decide to do, go and share your library with the world. Push it to Hex.pm and make it available for everyone to download and use.
Did you follow along this tutorial? Did you have any hiccups or did it all go smooth? Let me know in the comments below!
P.S. You can see the code for this post [here](https://github.com/fteem/eight_ball). The Hex package page is [here](https://hex.pm/packages/eight_ball).