Za trzy dni będzie premiera Javy 9. Dlatego właśnie czas napisać coś o Elixirze. Dziś na tapecie ląduje jeden z ciekawszych elementów języka, czyli wykonywalna dokumentacja.

Chwila teorii

Gdy dokumentujemy jakiś fragment kodu, to często aż prosi się dołączyć jakiś przykład. Coś w rodzaju dla takich danych otrzymasz taki rezultat. Wszystko fajnie, bo taki przykładowy happy path pozwala na zrozumienie kodu. Z drugiej strony przykład zazwyczaj kopiujemy z testów, bo tam jest gotowe, a później, jak się testy zmienią, to już niekoniecznie aktualizujemy dokumentację.
A gdyby tak w dokumentacji zaszyć testy i niech narzędzia martwią się jak to uruchomić?

Chwila praktyki

Na tapetę wziąłem algorytm Slowsort W. Z kilku powodów. Po pierwsze jestem w trakcie pisania kursu ze struktur danych na potrzeby naszego centrum szkoleniowego i implementacja slowsorta jest całkiem fajnym ćwiczeniem. Proste, a jednocześnie nietypowe. Po drugie chcę wykorzystać ten kod w czasie mojej prezentacji na JDD. Po trzecie, jak już implementować coś głupiego, to niech to będzie naprawdę głupie 😀

Kod wygląda tak:

Listing 1. implementacja Slowsort w Elixirze

defmodule Slowsort do
  @moduledoc """
  Documentation for Slowsort.
  """

  @doc """
  Example implementation of Slowsort algorithm.

  ## Examples

      iex> Slowsort.sort([0])
      [0]

      iex> Slowsort.sort([1,0])
      [0, 1]

      iex> Slowsort.sort([3, 11,  1, 0, 22, 2])
      [0, 1, 2, 3, 11, 22]
  """
  def sort(numbers) do
    l = length numbers
    slowsort(numbers, 0, l - 1) # we need maximum index that is length - 1
  end

  defp slowsort(numbers, i, j) when i>=j, do: numbers

  defp slowsort(numbers, i, j) do
    m = Integer.floor_div((i + j), 2)
    slowsort(numbers, i, m)
              |> slowsort(m + 1, j)
              |> swap(j, m)
              |> slowsort(i, j - 1)
  end

  defp swap(numbers, j, m) do
       max_left = Swap.nth(numbers, m);
       max_rigth = Swap.nth(numbers, j);
       swap(numbers, j, m, max_rigth, max_left)
   end

   defp swap(numbers, j, m, mr, ml) when mr = ml, do: numbers
end

Do tego oczywiście są jeszcze testy:

Listing 2. Zestaw testów

defmodule SlowsortTest do
  use ExUnit.Case
  @moduletag timeout: 1000
  doctest Slowsort

  test "the empty array" do
    sorted  = Slowsort.sort []
    assert sorted === []
  end

  test "the simple array of two elements" do
    sorted  = Slowsort.sort [1, 0]
    assert sorted === [0, 1]
  end

  test "the array of many elements" do
    sorted  = Slowsort.sort [3, 11,  1, 0, 22, 2]
    assert sorted === [0, 1, 2, 3, 11, 22]
  end
end

Technicznie są tu dwa zestawy testów. Zwróćcie uwagę na dokumentację funkcji sort. Zdefiniowana jest tam sekcja Examples, która wygląda jak fragment kodu w markdownie. I rzeczywiście, jeżeli wygenerujemy dokumentację, to otrzymamy jakiś nagłówek. Znacznie ciekawiej jest, jeżeli uruchomimy testy za pomocą mix test.

Listing 4. Zrzut z testów

Compiling 1 file (.ex)
......

Finished in 0.1 seconds
6 tests, 0 failures

Uruchomiono sześć testów. Trzy zdefiniowanie w slowsort_test i trzy zdefiniowane w dokumentacji.

Podsumowanie

Możliwość zdefiniowania prostych testów bezpośrednio w dokumentacji jest bardzo fajnym rozwiązaniem. Pozwala to na jednoczesną aktualizację testów, jak i dokumentacji. Szkoda, że w Javie nie ma takiego narzędzia. Oj szkoda…

Kod dostępny jest tutaj.