DEV Community

vitorhead
vitorhead

Posted on

Runic writing in Clojure

Background

As a fan of norse mythology, I always wondered if there is a way that we could write in runic at the browser. I've stumbled upon many translators that would print images of your text in runes, but few that would write it, with the possibility of selecting it and copying, so I decided to give it a try and attempt to build my own. I'm no professional when it comes to old languages, but I know that there are some schools of thought about the translation of old runic alphabets (like elder futhark) to english. While some say that the runes had specific meanings, other say that each and every had a unique matching letter, thus leaving the oportunity to match 1-1 with the english alphabet.

I needed two things: a programming language and a good source to help me with the runes. For the first, I choose Clojure: an amazing, data-driven functional programming language, which is based on LISP. I do not plan on entering the basics (like a clojure 101 post) here, but this is a very good book for those who want to dive into the Clojure world. For the second one, I choose Google. The wise search bar led me to a website which had a powerful tool: a runescriber.

At this point I had the two tools that I needed to craft the translator. Without further ado, let's start!

Hands on

Clojure's structure is pretty neat, and there are many tools that help you manage your solution. We will be using Leiningen to create, run and build our project. Make sure you have it installed before anything (there is a guide at the website on how to install in your specific O.S.). To create a project, go ahead and type the following command at the terminal: lein new app runiclojure.

After that, you will have a structure that looks like this:

| .gitignore
| doc
| | intro.md
  | project.clj
| README.md
  | resources
| src
| | runiclojure
  | | | core.clj
  | test
| | runiclojure
| | | core_test.clj

This is your project skeleton. You can edit the file project.clj to add some details (description, repository, url and others) into your project. It is also where you add dependencies and other libraries (but we will not have to do that for now!)

With the structure created and everythng, you can now open src/core.clj, our main namespace and where we will be working for the rest of the post.

The idiomatic way to code and organize our project would be to create a module (separate files) for each functionality. To keep it short and simple, from now on everything will be at the same file (project.clj) but feel free to create other files in order to keep it neat and organized.

Code!

To begin with we can define the latin alphabet and the runes.

(def runes "ᚨᛒᚲᛞᛖᚠᚷᚺᛇᚲᛚᛗᚾᛟᛈᛩᚱᛋᛏᚢᚡᚹᛪᛃᛋᛎ")
(def alphabet "abcdefghijklmnopqrstuvwxyz")

We need a function to look up each letter in the alphabet in a runic chart. I'm sure that there are plenty of ways of doing that (pattern matching, case and others) but the one I choose was to create a dictionary. Using clojure's built-in zipmap I could create a dictionary where the keys are the latin letters and the values are the runes.

(def runes "ᚨᛒᚲᛞᛖᚠᚷᚺᛇᚲᛚᛗᚾᛟᛈᛩᚱᛋᛏᚢᚡᚹᛪᛃᛋᛎ")
(def alphabet "abcdefghijklmnopqrstuvwxyz")
(def values (zipmap alphabet runes))

With values in hand, it is possible to boot up lein repl and start to write some translations. For instance, if I wanted to translate the letter a, I could eval (values "a") and the output would be . One way of getting values out of a clojure's dictionary is to call the dictionary with the key as a parameter. With that in mind, we can write a function:

(defn translate
    [s]
    (values s))

But we do not want to translate single characters, we want to translate whole words, right?!

Think of a word as a string. A string is just a collection of chars (a list of chars). So, we could bump up our expression using the famous map function:

(map translate "the almighty bread")

In this expression, the translate function will be applied to every element in the string, handling back a new, translated, string. This is exactly what we want!

However, the translation core is not yet done. What if we're provided with a char that is not in the alphabet that we defined earlier? In Clojure, when we try to get a dictionary value using a key that doesn't exists, it returns nil. With that in mind, we could write:

(defn translate-text
  [text]
  (map #(or (translate %) %) text))

Clojure's or evaluates from left to right and returns the first logical true value. Doing so, if the inputted text has a character that we haven't mapped, it won't try to translate, instead it will return the character itself.

Prompt user input

Our translator is now ready. We could easily import our namespace in a lein repl and play with the expressions. But that would be too simple!

Let's try to write a function which will allow the user to write at the terminal.

Clojure's read-line function read the next line from the stream and returns it as a string.

user=> (read-line)
hello dev community!      ;type text
"hello dev community!"

We could easily take this string and apply our translate function. However, read-line just prompts the user for one input. If we want more, we will have to write our function withrepeatedly and loop:

(loop [lines (repeatedly read-line)]
  (let [line (first lines)]
      (println line)))

repeatedly takes a zero-arg function (often with side-effects) and returns an infinite (or n) lazy sequence of it. lines is basically a lazy collection of the read-line function. Combining with the loop and let structure, we get the first element of that collection and the user is prompted for input. After that, we just print to the terminal in order to see if everything's alright.

(defn -main
    [& args]
    (println "Runic translation!")
    (loop [lines (repeatedly read-line)]
      (let [line (first lines)]
        (when (validate line)
          (println (map translate (clojure.string/lower-case line)))
          (recur (next lines))))))

This function has a lot of side effects (sorry!). Most of it you probably saw in the last example. validate's purpose is to control the lifecycle of the translator. It just checks if the text send by the user is equal to "exit" and if it is, it breaks the loop.

Using the translator

We can now run our code using lein run inside the project's folder and check for translations at the terminal.

vitorhead:~/workspace/runiclojure $ lein run
Runic translation!
hello dev community 
(ᚺ ᛖ ᛗ ᛗ ᛈ   ᛞ ᛖ ᚹ   ᚲ ᛈ ᚾ ᚾ ᚡ ᛟ ᛇ ᚢ ᛋ)

Final considerations

The complete source can be found in this gist.
Here you can check my repository (it has a live demo aswell) about this little project made in ClojureScript, which allows you to translate your runes in the browser!

Coding the translator was a simple yet fun way of studying Clojure for me. This is a very basic and small program, however it is possible to see how powerful Clojure's implementations are, and I hope it has shown you how easy and neat is the code!

Top comments (0)