I'm a complete newbie to Elixir, but I managed to bang together this working example of Ascii85 for a project I'm working on in my spare time.

I find some awkward repetition in here which I have tried to replace with iteration but it just made the code harder to read. There is also some lop-sided behaviour which I'm not 100% happy with. For instance, to convert the number to a base 85 string, I have to track the number of digits manually, whereas decoding it turns out to look a lot more elegant.

Maybe someone who has a better sense for the language will point out something I'm doing completely wrong.

I do want to keep a certain level of understandability to the code because one of the points of this project is to be able to produce an explanation of what logic was performed, in addition to the answer.

defmodule Fiddlybits.Ascii85 do

  @doc """
  Encodes binary to Ascii85 text.

  ## Examples

    iex> Fiddlybits.Ascii85.encode("")

    iex> Fiddlybits.Ascii85.encode("Man sure.")

  @spec encode(binary) :: binary
  def encode(<<>>), do: <<>>
  def encode(<< partial::bitstring-size(8) >>) do
    (partial <> << 0, 0, 0 >>) |> encode |> :binary.part(0, 5-3)
  def encode(<< partial::bitstring-size(16) >>) do
    (partial <> << 0, 0 >>) |> encode |> :binary.part(0, 5-2)
  def encode(<< partial::bitstring-size(24) >>) do
    (partial <> << 0 >>) |> encode |> :binary.part(0, 5-1)
  def encode(<< chunk::big-unsigned-integer-size(32), rest::binary >>) do
    encode_chunk(chunk) <> encode(rest)

  # 32-bit integer to base-85 ASCII
  defp encode_chunk(_n, 0), do: <<>>
  defp encode_chunk(n, digits \\ 5) do
    last = rem(n, 85)
    rest = div(n, 85)
    # This is the original Ascii85 algorithm where you just use a contiguous block of ASCII.
    #todo use a lookup table instead to make implementing other variants easier
    encode_chunk(rest, digits - 1) <> << (last + 33) >>

  @doc """
  Decodes Ascii85 text to binary.

  ## Example

    iex> Fiddlybits.Ascii85.decode("")

    iex> Fiddlybits.Ascii85.decode("9jqo^F*2M7/c")
    "Man sure."
  @spec decode(binary) :: binary
  def decode(<<>>), do: <<>>
  def decode(<< partial::bitstring-size(16) >>) do
    (partial <> "uuu") |> decode |> :binary.part(0, 4-3)
  def decode(<< partial::bitstring-size(24) >>) do
    (partial <> "uu") |> decode |> :binary.part(0, 4-2)
  def decode(<< partial::bitstring-size(32) >>) do
    (partial <> "u") |> decode |> :binary.part(0, 4-1)
  def decode(<< chunk::bitstring-size(40), rest::binary >>) do
    << decode_chunk(chunk)::big-unsigned-integer-size(32) >> <> decode(rest)

  # base-85 ASCII to 32-bit integer.
  defp decode_chunk(chunk) do
    |> Enum.map(&(&1 - 33))
    |> Enum.reduce(0, fn(x, acc) -> acc * 85 + x end)

(Oh yes, I already know about starting private functions with underscores. In my defence, Elixir itself doesn't appear to be doing it either...)

  • \$\begingroup\$ In my opinion defp is enough, especially since underscore is also used for throwaway variables; using underscore for private functions could be visually confusing. \$\endgroup\$ – stoft Apr 5 '15 at 8:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.