Functional Programming in Elixir with Witchcraft

48
Functional Programming in Elixir with Witchcraft

Whereas Elixir is a functional programming language, it’s varied from loads of the more than a few standard functional languages fancy Haskell, Scala, OCaml, and F#.

Elixir pragmatically handles concurrent programs with high fault tolerance. In varied phrases, Elixir is an FP language on legend of this naturally suits it, and no longer for its dangle sake. So, porting idioms blindly from Haskell to Elixir can lead to undesired outcomes.

At the identical time, Elixir users must nonetheless more continually consult with the complete pleasant universe of functors, monads, and varied curiosities. Good-looking out libraries were presented for working with algebraic structures.

On this article, I want to introduce you to a library known as Witchcraft and conceal tips on how to utilize it to emulate Haskell-model programming in Elixir.

String Validation and Processing with Witchcraft

We are in a position to plow via a bank card validation exercise.

Given a string that must safe a bank card number, we want to make certain that that:

  • it comprises 16 digits
  • it has at least two varied digits
  • the final digit is even
  • the sum of the digits is better than 16

We are in a position to carry out this exercise with Both, undoubtedly one of the predefined info kinds that the Witchcraft complex presents.

Region Up Witchcraft

First, add the mandatory libraries to your mix.exs file and speed mix deps.glean.

{: witchcraft, git:  "https://github.com/doma-engineering/witchcraft.git", ref:  "9c31c75"},
{: algae, git:  "https://github.com/doma-engineering/algae.git", ref:  "e9b8f88"},
{: quark, git:  "https://github.com/witchcrafters/quark.git", ref:  "b05a8a0"},

Parsing the String

First, form a recent module and import Witchcraft at the cease of the module.

defmodule Card carry out
  use Witchcraft
 
discontinue

Then, write a fair to parse the string accurate into a listing of digits and score away all non-digit gadgets.

def get_digits(cardnumber) carry out
  cardnumber
  |> String.spoil up("", properly-organized:  correct)
  |> Enum.scheme(fn x -> Integer.parse(x) discontinue)
  |> Enum.filter(fn x -> x != : error discontinue)
  |> Enum.scheme(fn {int, _rest} -> int discontinue
discontinue

In the code above, we spoil up the card number into characters and scheme Integer.parse (profitable integer conversion) over them. Integer.parse returns both {int, rest_of_binary} or :error. We filter out the errors and unpack the tuples with the final two capabilities.

iex(1)> Card.get_digits("3456-3233-5689-4445")
[3, 4, 5, 6, 3, 2, 3, 3, 5, 6, 8, 9, 4, 4, 4, 5]

After that, we can write our first validation fair with Both.

Rapid Intro to the Both Knowledge Form

Both is a form that comprises undoubtedly one of two varied choices: Left or Upright. Every of these wraps one other kind. Left retail outlets failures, and Upright retail outlets successes.

-- Both info kind as outlined in Haskell
info Both a b = Left a | Upright b

Generally, we return it from computations that would perchance perchance fail whenever you happen to pray to know what more or much less error made them fail.

When working with Witchcraft, you presumably can use the predefined Algae.Both to create recent cases of the Both info kind. We are in a position to carry out that by the use of undoubtedly one of two structs: %Algae.Both.Left{left: model} or %Algae.Both.Upright{staunch: model}.

Seemingly, you salvage already speed into it someplace. In varied languages, it’s most regularly known as Result (in Rust, shall we embrace). It’s also structurally identical to the tuple continually archaic in Elixir.

{: okay, end result} -> {: staunch, end result} -> %Algae.Both.Upright{staunch:  end result}
{: error, motive} -> {: left, motive} -> %Algae.Both.Left{left:  motive}

In actuality, we would perchance perchance well technically use the tuple for our exercise. I’ve constructed it this contrivance on motive, so that you presumably can bump into the equipment of Witchcraft while staying on roughly right floor.

Instance of Both

Let’s see at an example of Both.

We salvage parsed a listing of digits from the given string, and now we want to know whether or no longer there are 16 gadgets in the checklist. If we took the easy contrivance out, we would perchance perchance well write one thing fancy this:

def sixteen_digits(cardnumber), carry out:  Enum.depend(cardnumber) == 16

Unfortunately, the fair above is no longer very composable: the return model loses the card number, so we won’t be ready to pipe the to carry out additional computations on it.

Which capacity of this truth, we need the to be a more complex constructing similar to Both.

  • If there are 16 digits in the checklist, we want to return an Both.Upright with the checklist.
  • If there would possibly well be a certain series of digits, we want to return an Both.Left with :not_16_digits.

And here’s the fair to carry out that:

def sixteen_digits(cardnumber) carry out
  case Enum.depend(cardnumber) carry out
    16 -> %Algae.Both.Upright{staunch:  cardnumber}
    _ -> %Algae.Both.Left{left:  : not_16_digits}
  discontinue
discontinue

Let’s strive it out in iex:

iex(1)> right_number = Card.get_digits("3456-3233---5689-4445")
[3, 4, 5, 6, 3, 2, 3, 3, 5, 6, 8, 9, 4, 4, 4, 5]
iex(2)> Card.sixteen_digits(right_number)
%Algae.Both.Upright{staunch: [3, 4, 5, 6, 3, 2, 3, 3, 5, 6, 8, 9, 4, 4, 4, 5]}
iex(3)> wrong_number = Card.get_digits("444")
[4, 4, 4]
iex(4)> Card.sixteen_digits(wrong_number)
%Algae.Both.Left{left: :not_16_digits}

Other Validation Features

In the identical contrivance, we can form a validation rule for every requirement.

I counsel you are attempting doing it your self first since they are going to be the same to the first example.

To remind you, here are the stipulations we want to ascertain:

  • The number has at least two varied digits.
  • The closing digit of the number is even.
  • The sum of the digits is better than 16.

Every of the stipulations would possibly want to salvage its dangle fair. Every of the capabilities must nonetheless score a listing of numbers and return:

  • %Algae.Both.Upright{} with the checklist inside of if the condition is fulfilled
  • %Algae.Both.Left{} with the motive inside of if the condition is no longer fulfilled

Whereas you’re ready to proceed, here are the capabilities that I’ll use additional on listed here:

def two_unique_digits(cardnumber) carry out
  ordinary =
    cardnumber
    |> Enum.uniq()
    |> Enum.depend()
 
  case ordinary >= 2 carry out
    correct -> %Algae.Both.Upright{staunch:  cardnumber}
    _ -> %Algae.Both.Left{left:  : not_2_unique_digits}
  discontinue
discontinue
 
def final_even(cardnumber) carry out
  last_digit = Enum.at(cardnumber, -1)
 
  case rem(last_digit, 2) carry out
    0 -> %Algae.Both.Upright{staunch:  cardnumber}
    _ -> %Algae.Both.Left{left:  : last_not_even}
  discontinue
discontinue
 
def sum_greater_than_16(cardnumber) carry out
  case Enum.sum(cardnumber) >= 16 carry out
    correct -> %Algae.Both.Upright{staunch:  cardnumber}
    _ -> %Algae.Both.Left{left:  : sum_smallr_than_16}
  discontinue
discontinue

Connecting the Features

However how carry out we be half of these capabilities together? Every of them takes a listing but returns a struct. We are in a position to’t pipe them into every varied on legend of their kinds don’t match up.

To chain them, we need one thing that knows tips on how to unwrap Algae.Both and apply varied capabilities to what’s inside of, and Witchcraft has lawful the ingredient we need.

definst Witchcraft.Chain, for:  Algae.Both.Left carry out
  def chain(left, _), carry out:  left
discontinue
 
definst Witchcraft.Chain, for:  Algae.Both.Upright carry out
  def chain(%Upright{staunch:  info}, link), carry out:  link.(info)
discontinue

From Algae.Both.

chain, which is ready to even be written in Witchcraft as >>> (or bind), is the precious bind operator from Haskell: >>=. Let’s impress what it does.

As outlined for Both, this can score a model — both Left or Upright — and a fair from a conventional model to Both.

In the case of Left, this can ignore the fair and pass the model additional. In the case of Upright, this can apply the fair to the model inside of Upright.

In our code, we can conveniently use >>> so that it looks pipe-y.

def parse_number(cardnumber) carry out
  cardnumber
  |> get_digits()
  |> sixteen_digits()
    >>> fn x -> two_unique_digits(x) discontinue
    >>> fn x -> final_even(x) discontinue
    >>> fn x -> sum_greater_than_16(x) discontinue
discontinue

Unfortunately, the opt syntax (&two_unique_digits/1) doesn’t work here. (The macro will magnify the code to nested captures, which Elixir doesn’t enable you carry out via &.)

Combining the Both info kind and its outlined chain fair lets us construct up a chain of capabilities. Even supposing they score a listing and return an Both, the capabilities would perchance perchance well furthermore be chained to come at a end result. The complete chain will return the checklist in case of success and the first encountered error in the case of failure.

We are in a position to strive it out in iex.

iex(1)> Card.parse_number("4444-444-222-1")
%Algae.Both.Left{left: :not_16_digits}
iex(2)> Card.parse_number("4444-4444-4444-4444")
%Algae.Both.Left{left: :not_2_unique_digits}
iex(3)> Card.parse_number("4444-4444-4444-4435")
%Algae.Both.Left{left: :last_not_even}
iex(4)> Card.parse_number("1020-0000-0000-0000")
%Algae.Both.Left{left: :sum_smaller_than_16}
iex(5)> Card.parse_number("4545-3232-5423-6788")
%Algae.Both.Upright{staunch: [4, 5, 4, 5, 3, 2, 3, 2, 5, 4, 2, 3, 6, 7, 8, 8]}

From this point onward, you presumably can content to salvage written monadic code in Elixir.

Tinkering With the Codebase

Listed below are some minor changes we can form to the code so it looks nicer. These are no longer wanted to your working out but can present you more practice with the code example in request.

First off, Witchcraft presents a extraordinarily at hand ~> operator that does the identical as piping into Enum.scheme.

We are in a position to use it in our get_digits fair.

def get_digits(cardnumber) carry out
  cardnumber
  |> String.spoil up("", properly-organized:  correct)
  ~> fn x -> Integer.parse(x) discontinue
  |> Enum.filter(fn x -> x != : error discontinue)
  ~> fn {int, _rest} -> int discontinue
discontinue

After that, empty lists will trigger some speed-time errors with the Enum.at that we archaic, and having empty inputs doubtlessly isn’t half of the plan, so we can reject empty strings staunch at the originate.

def get_digits(cardnumber) carry out
  digits =
    cardnumber
    |> String.spoil up("", properly-organized:  correct)
    ~> fn x -> Integer.parse(x) discontinue
    |> Enum.filter(fn x -> x != : error discontinue)
    ~> fn {int, _rest} -> int discontinue
 
  case digits carry out
    [] -> %Algae.Both.Left{left:  : empty_input}
    _ -> %Algae.Both.Upright{staunch:  digits}
  discontinue
discontinue

And at final, there would possibly well be a macro that you presumably can use in parse_number to simulate a more Haskell-fancy contrivance of writing a series of actions in a definite context. This model is most regularly known as the carry out-notation.

def parse_number(cardnumber) carry out
 
  monad %Algae.Both.Upright{} carry out
    digits <- get_digits(cardnumber)
    a <- sixteen_digits(digits)
    b <- two_unique_digits(a)
    c <- final_even(b)
    d <- sum_greater_than_16(c)
    return(d)
  end
end

In the code sample above, we provide the kind of container that we will operate with — %Algae.Either.Right{}. Then we can do actions inside it, and the monad macro will automatically chain our functions. In the end, it will return whatever we put in the return statement (but wrapped in the container).

It’s very powerful, but also very confusing at first. If it is hard to understand what’s happening here, I encourage you to try out monad [] first since it is very similar to list comprehensions. In fact, if you think of this macro as generalized list comprehensions, you won’t err too much.

Either and the Result Tuple in Elixir

Either is very similar to the result tuple in Elixir, and I’ve done that on purpose to keep you on somewhat familiar ground.

In fact, you can implement the same thing without using Witchcraft.

defmodule Card2 do
 
  def get_digits(cardnumber) do
    digits =
      cardnumber
      |> String.spoil up("", properly-organized:  correct)
      |> Enum.scheme(fn x -> Integer.parse(x) discontinue)
      |> Enum.filter(fn x -> x != : error discontinue)
      |> Enum.scheme(fn {int, _rest} -> int discontinue)
  discontinue
 
  def sixteen_digits(cardnumber) carry out
    case Enum.depend(cardnumber) carry out
      16 -> {: okay, cardnumber}
      _ -> {: error, : not_16_digits}
    discontinue
  discontinue
 
  def two_unique_digits(cardnumber) carry out
    ordinary =
      cardnumber
      |> Enum.uniq()
      |> Enum.depend()
 
    case un


NOW WITH OVER +8500 USERS. other folks can Be a half of Knowasiak for free. Register on Knowasiak.com
Read More

Ava Chan
WRITTEN BY

Ava Chan

I'm a researcher at Utokyo :) and a big fan of Ava Max