Hike.Either (hike v0.1.0)

Hike.Either represents a value that can be one of two possibilities: either a Left state or a Right state. It is commonly used in error handling or when a function can return different types of results.

Creating an Either

To create an Either instance, you can use the Hike.Either.right/1 and Hike.Either.left/1 function to wrap a value in right and left state respectively :

    iex> Hike.Either.right(5)
    %Hike.Either{l_value: nil, r_value: 5, is_left?: false}
    iex> Hike.Either.left("Hello")
    %Hike.Either{l_value: "Hello", r_value: nil, is_left?: true}
  # Example can be written with `Either`
  # Define a User struct
  defmodule User do
    @derive Jason.Encoder
    defstruct [:id, :age, :name]
  end
  defmodule TestHike do
    # Import the Hike.Either module
    import Hike.Either

    # Simulating a database fetch function
    @spec fetch_user(number) :: Hike.Either.either(%User{}) | Hike.Either.either()
    def fetch_user(id) do
      # Simulating a database query to fetch a user by ID
      # Returns an Either<User, string> with left(user) if the user is found
      # Returns an Either<User, string> with right("User not found") if the user is not found
      case id do
        1 -> left(%User{id: 1, age: 30, name: "Vineet Sharma"})
        2 -> left(%User{id: 2, age: 20, name: "Jane Smith"})
        _ -> right("User not found")
      end
    end

    # Function to update the user's name to uppercase if possible
    # This function takes a User struct and returns an Either<User, string>
    def update_name_to_uppercase(user) do
      case user.name do
        nil -> right("User name is missing")
        name ->  left(%User{user | name: String.upcase(name)})
      end
    end

    # Function to increase the user's age by one
    # This function takes a User struct and returns a real data type User with updated values.
    def increase_age_by_1(user) do
      %User{user | age: user.age + 1}
    end

    # Function to print a user struct as a JSON-represented string
    def print_user_as_json(user) do
      user
      |> Jason.encode!()
      |> IO.puts()
    end

    # Example: Fetching a user from the database, updating the name, and matching the result
    def test_user() do
      user_id = 1

      # Fetch the user from the database
      fetch_user(user_id)
      # Update the name to uppercase using bind
      |> bind_left(&update_name_to_uppercase/1)
      # Increase the age by one using map
      |> map_left(&increase_age_by_1/1)
      # Print the user as a JSON string using map
      |> map_left(&print_user_as_json/1)
      # finally match the respective result with a appropriate function.
      |> match(fn x -> x end, fn err ->err end)

      user_id = 3

      # Fetch the user from the database
      fetch_user(user_id)
      # Update the name to uppercase using bind
      |> bind_left(&update_name_to_uppercase/1)
      # Increase the age by one using map
      |> map_left(&increase_age_by_1/1)
      # Print the user as a JSON string using map
      |> map_left(&print_user_as_json/1)
      # finally match the respective result with a appropriate function.
      |> match(fn x -> x end, fn err -> err end)
    end
  end
  #output
  iex> TestHike.test_user
  #user_id = 1
      {"age":31,"id":1,"name":"VINEET SHARMA"}
  #user_id = 3
      "User not found"

Link to this section Summary

Types

binder() represent a binding(mapping) function which take no parameter and return an Either of type <TR>.

binder(t) represent a binding(mapping) function which take a parameter of type <T> and return an Either of type <TR>.

represent a type of Either that could be in either Left or Right state

represent a type of Either that could be in either Left or Right state with a value of given type.

represent a type of Either in Left state.

represent a type of Either in Left state.

Elevated data type of Either struct that represents Right state.

Elevated data type of Either struct that represents Right state and have a value of type <T>.

func() represent a function which take no parameter and return value of type <TR>.

func(t) represent a function which take a parameter of type <T> and return a value of type <TR>.

mapper() represent a mapping function which take no parameter and return a value of type <TR>.

mapper(t) represent a mapping function which take a parameter of type <T> and return a value of type <TR>.

t()

generic input type <T>.

generic input type <T_Left> represent a type of value on Leftstate.

generic input type <T_Right>represent a type of value on Rightstate.

generic return type <TR>.

Functions

%Hike.Either{l_value: t_left(), r_value: t_right(), is_left?:boolean()} is a struct that represents an "either/or" value. It can contain either a left value or a right value, but not both.

apply_left applies a given function to a given Either if Either is in left state and return a new Either with new transformed value in Left state else return new Either in Right state with existing right value.

Applies the provided function func to the left value of Either wrapped in a Task and returns a new task with the result.

apply_right applies a given function to a given Either if Either is in right state and return a new Either with new transformed value in Right state else return new Either in Left state with existing left value.

Applies the provided function func to the right value of Either wrapped in a Task and returns a new task with the result.

Binds a function that returns an Either value for an Either in the left state. If the input Either is in the right state, the function is not executed and the input Either is returned as is.

Applies the provided function binder to the left value of Either wrapped in a Task and returns a new task with the result.

Binds a function that returns an Either value to an Either in the right state. If the input Either is in the left state, the function is not executed and the input Either is returned as is.

Applies the provided function binder to the right value of Either wrapped in a Task and returns a new task with the result.

Create new Either from result. if result is {:ok, val} Either will be in right state. else if result is {:error, val} Either will be in left state. with respective value val in respective side.

Check whether aneither is in Left state or not .

Check whether aneither is in Right state or not .

Creates a new Left Either object with a given value for the left side.

Maps the value of the Either from left state using the given function.

Applies the provided function mapper to the left value of Either wrapped in a Task and returns a new task with the result.

Maps the value of theEither from right state using the given function.

Applies the provided function mapper to the right value of Either wrapped in a Task and returns a new task with the result.

Matches an Either value and applies the corresponding function.

Matches an Either value wrapped in a Task and applies the corresponding function asynchronously.

Creates a new Either in Right state with a given value.

convert Either form Right state to MayFail Success state and Either from Left state to MayFail Failure state.

convert Either from Right state to Option Some state and Either from Left state to Option None state.

Link to this section Types

@type binder() :: (() -> either_left(tr()) | either_right(tr()))

binder() represent a binding(mapping) function which take no parameter and return an Either of type <TR>.

example

Example

@spec whatever() :: Hike.Either.either_right(atom)
def whatever(), do: right(:ok)

@spec whatever() :: Hike.Either.either_left(:error)
def whatever(), do: left(:error)
@type binder(t) :: (t -> either_left(tr()) | either_right(tr()))

binder(t) represent a binding(mapping) function which take a parameter of type <T> and return an Either of type <TR>.

example

Example

@spec square(pos_number) :: Hike.Either.either_right(number)
def square(x) when x > 0, do: x * x |> right()

@spec square(number) :: Hike.Either.either_left(:error) | Hike.Either.either_right(number)
def square(x) do
  case x when x < 0 do
    true -> left({:error, "negative number"})
    false -> square(x)
  end
end
Link to this opaque

either()

(opaque)
@opaque either()

represent a type of Either that could be in either Left or Right state

Link to this type

either(t_left, t_right)

@type either(t_left, t_right) :: %Hike.Either{
  is_left?: boolean(),
  l_value: t_left,
  r_value: t_right
}

represent a type of Either that could be in either Left or Right state with a value of given type.

Link to this type

either_left()

@type either_left() :: %Hike.Either{is_left?: true, l_value: t_left(), r_value: nil}

represent a type of Either in Left state.

Link to this type

either_left(t)

@type either_left(t) :: %Hike.Either{is_left?: true, l_value: t, r_value: nil}

represent a type of Either in Left state.

Link to this type

either_right()

@type either_right() :: %Hike.Either{
  is_left?: false,
  l_value: nil,
  r_value: t_right()
}

Elevated data type of Either struct that represents Right state.

Link to this type

either_right(t)

@type either_right(t) :: %Hike.Either{is_left?: false, l_value: nil, r_value: t}

Elevated data type of Either struct that represents Right state and have a value of type <T>.

@type func() :: (() -> tr())

func() represent a function which take no parameter and return value of type <TR>.

example

Example

@spec add_one(number) :: number
def add_one(x), do: x + 1
@type func(t) :: (t -> tr())

func(t) represent a function which take a parameter of type <T> and return a value of type <TR>.

example

Example

@spec add_one(number) :: number
def add_one(x), do: x + 1
@type mapper() :: (() -> tr())

mapper() represent a mapping function which take no parameter and return a value of type <TR>.

example

Example

@spec whatever() :: atom
def whatever(), do: :ok
@type mapper(t) :: (t -> tr())

mapper(t) represent a mapping function which take a parameter of type <T> and return a value of type <TR>.

example

Example

@spec square(number) :: number
def square(x), do: x * x
@type t() :: any()

generic input type <T>.

@type t_left() :: any()

generic input type <T_Left> represent a type of value on Leftstate.

@type t_right() :: any()

generic input type <T_Right>represent a type of value on Rightstate.

@type tr() :: any()

generic return type <TR>.

Link to this section Functions

Link to this function

%Hike.Either{}

(struct)

%Hike.Either{l_value: t_left(), r_value: t_right(), is_left?:boolean()} is a struct that represents an "either/or" value. It can contain either a left value or a right value, but not both.

  • l_value: the left value (if is_left? is true)
  • r_value: the right value (if is_left? is false)
  • is_left?: a boolean flag indicating whether the value is a left value (true) or a right value (false)
Link to this function

apply_left(either, func)

@spec apply_left(either_left(t_left()), (t_left() -> tr())) :: either_left(tr())
@spec apply_left(either_right(t_right()), (t_left() -> tr())) ::
  either_right(t_right())

apply_left applies a given function to a given Either if Either is in left state and return a new Either with new transformed value in Left state else return new Either in Right state with existing right value.

examples

Examples

iex> (Either.left 5) |> Either.apply_left(fn num -> num + num end)
%Hike.Either{l_value: 10, r_value: nil, is_left?: true}

iex> (Either.right 5) |> Either.apply_left(fn num -> num + num end)
%Hike.Either{l_value: nil, r_value: 5, is_left?: false}
Link to this function

apply_left_async(wrapped_task, func)

@spec apply_left_async(Task.t(), func(t())) :: Task.t()

Applies the provided function func to the left value of Either wrapped in a Task and returns a new task with the result.

examples

Examples

iex> task =  Task.async(fn -> Hike.Either.left("Hello") end)
...> applied_task = apply_left_async(task, &String.upcase/1)
...> Task.await(applied_task)
%Hike.Either{l_value: "HELLO", r_value: nil, is_left?: true}


iex> task = Task.async(fn -> Hike.Either.right(10) end)
...> applied_task = apply_left_async(task, &String.upcase/1)
...> Task.await(applied_task)
%Hike.Either{l_value: nil, r_value: "Hello", is_left?: false}
Link to this function

apply_right(either, func)

@spec apply_right(either_left(t_left()), (t_right() -> tr())) :: either_left(t_left())
@spec apply_right(either_right(t_right()), (t_right() -> tr())) :: either_right(tr())

apply_right applies a given function to a given Either if Either is in right state and return a new Either with new transformed value in Right state else return new Either in Left state with existing left value.

examples

Examples

iex> (Either.right "hello") |> Either.apply_right(fn str -> String.upcase(str) end)
%Hike.Either{l_value: nil, r_value: "HELLO", is_left?: false}

iex> (Either.left "hello") |> Either.apply_right(fn str -> String.upcase(str) end)
%Hike.Either{l_value: "hello", r_value: nil, is_left?: true}
Link to this function

apply_right_async(wrapped_task, func)

@spec apply_right_async(Task.t(), func(t())) :: Task.t()

Applies the provided function func to the right value of Either wrapped in a Task and returns a new task with the result.

examples

Examples

iex> task = Task.async(fn -> %Hike.Either{right: "Hello"} end)
...> applied_task = apply_right_async(task, &String.upcase/1)
...> Task.await(applied_task)
%Hike.Either{left: nil, right: "HELLO", is_left?: false}

iex> task = Task.async(fn -> %Hike.Either{left: 10} end)
...> applied_task = apply_right_async(task, &String.upcase/1)
...> Task.await(applied_task)
%Hike.Either{left: 10, right: nil, is_left?: true}
Link to this function

bind_left(either, func)

@spec bind_left(either_left(t_left()), binder(t_left())) ::
  either_left(tr()) | either_right(tr())
@spec bind_left(either_right(t_right()), binder(t_left())) :: either_right(t_right())

Binds a function that returns an Either value for an Either in the left state. If the input Either is in the right state, the function is not executed and the input Either is returned as is.

examples

Examples

iex> (Either.left "hello") |> Either.bind_left(fn str -> Either.left(String.upcase(str)) end)
%Hike.Either{l_value: "HELLO", r_value: nil, is_left?: true}

iex> (Either.left "hello") |> Either.bind_left(fn str -> Either.right(String.upcase(str)) end)
%Hike.Either{l_value: nil, r_value: "HELLO", is_left?: false}

iex> (Either.right "hello") |> Either.bind_left(fn str -> Either.left(String.upcase(str)) end)
%Hike.Either{l_value: nil, r_value: "hello", is_left?: false}
Link to this function

bind_left_async(wrapped_task, binder)

@spec bind_left_async(Task.t(), binder(t())) :: Task.t()

Applies the provided function binder to the left value of Either wrapped in a Task and returns a new task with the result.

examples

Examples

iex> task =  Task.async(fn -> Hike.Either.left("Hello") end)
...> applied_task =bind_left_async(task, fn str -> Either.left(String.upcase(str)) end)
...> Task.await(applied_task)
%Hike.Either{l_value: "HELLO", r_value: nil, is_left?: true}

iex> task =  Task.async(fn -> Hike.Either.right("Hello") end)
...> applied_task =bind_left_async(task, fn str -> Either.left(String.upcase(str)) end)
...> Task.await(applied_task)
%Hike.Either{l_value: nil, r_value: "Hello", is_left?: true}

iex> task = Task.async(fn -> Hike.Either.right(10) end)
...> applied_task = bind_left_async(task, fn num -> num * num end)
...> Task.await(applied_task)
%Hike.Either{l_value: nil, r_value: 10, is_left?: false}
Link to this function

bind_right(either, func)

@spec bind_right(either_right(t_right()), binder(t_right())) ::
  either_right(tr()) | either_left(tr())
@spec bind_right(either_left(t_left()), binder(t_right())) :: either_left(t_left())

Binds a function that returns an Either value to an Either in the right state. If the input Either is in the left state, the function is not executed and the input Either is returned as is.

examples

Examples

iex> (Either.right "hello") |> Either.bind_right(fn str -> Either.right(String.upcase(str)) end)
%Hike.Either{l_value: nil, r_value: "HELLO", is_left?: false}

iex> (Either.right "hello") |> Either.bind_right(fn str -> Either.left(String.upcase(str)) end)
%Hike.Either{l_value: "HELLO", r_value: nil, is_left?: true}

iex> (Either.left "hello") |> Either.bind_right(fn str -> Either.right(String.upcase(str)) end)
%Hike.Either{l_value: "hello", r_value: nil, is_left?: true}
Link to this function

bind_right_async(wrapped_task, binder)

@spec bind_right_async(Task.t(), binder(t())) :: Task.t()

Applies the provided function binder to the right value of Either wrapped in a Task and returns a new task with the result.

examples

Examples

iex> task =  Task.async(fn -> Hike.Either.left("Hello") end)
...> applied_task =bind_right_async(task, fn str -> Either.left(String.upcase(str)) end)
...> Task.await(applied_task)
%Hike.Either{l_value: "Hello", r_value: nil, is_left?: true}

iex> task =  Task.async(fn -> Hike.Either.right("Hello") end)
...> applied_task =bind_right_async(task, fn str -> Either.left(String.upcase(str)) end)
...> Task.await(applied_task)
%Hike.Either{l_value: "HELLO", r_value: nil, is_left?: true}

iex> task = Task.async(fn -> Hike.Either.right(10) end)
...> applied_task = bind_right_async(task, fn num -> num * num end)
...> Task.await(applied_task)
%Hike.Either{l_value: nil, r_value: 100, is_left?: false}
Link to this function

from_result(arg)

@spec from_result({:ok, t()}) :: either_right(t())
@spec from_result({:error, t()}) :: either_left(t())

Create new Either from result. if result is {:ok, val} Either will be in right state. else if result is {:error, val} Either will be in left state. with respective value val in respective side.

Link to this function

is_left?(either)

@spec is_left?(either()) :: boolean()

Check whether aneither is in Left state or not .

examples

Examples

iex> right("foo bar") |> Either.is_left?()
 false
iex> left("foo bar") |> Either.is_left?()
 true
Link to this macro

is_not_nil(value)

(macro)
Link to this function

is_right?(either)

@spec is_right?(either()) :: boolean()

Check whether aneither is in Right state or not .

examples

Examples

iex> right("foo bar") |> Either.is_right?()
 true
iex> left("foo bar") |> Either.is_right?()
 false
@spec left(t_left()) :: either_left(t_left())

Creates a new Left Either object with a given value for the left side.

examples

Examples

iex> left("foo bar")
%Hike.Either{l_value: "foo bar", r_value: nil, is_left?: true}
Link to this function

map_left(e, func)

@spec map_left(either_left(t_left()), (t_left() -> tr())) :: either_left(tr())
@spec map_left(either_right(t_right()), (t_left() -> tr())) :: either_right(t_right())

Maps the value of the Either from left state using the given function.

If the Either is in the right state, the function returns the Either unchanged. If the Either is in the left state, the function applies the given function to the value of the left state, and returns a new Either with the transformed value.

examples

Examples

iex> (Either.left 5) |> Either.map_left(fn num -> num + num end)
%Hike.Either{l_value: 10, r_value: nil, is_left?: true}

iex> (Either.right 5) |> Either.map_left(fn num -> num + num end)
%Hike.Either{l_value: nil, r_value: 5, is_left?: false}

iex> either = %Either{l_value: "hello", is_left?: true}
...> new_either = Either.map_left(either, &String.upcase/1)
%Hike.Either{l_value: "HELLO", is_left?: true}

iex> either = %Either{r_value: 10, is_left?: false}
...> new_either = Either.map_left(either, &String.downcase/1)
%Hike.Either{r_value: 10, is_left?: false}
Link to this function

map_left_async(wrapped_task, mapper)

@spec map_left_async(Task.t(), mapper(t())) :: Task.t()

Applies the provided function mapper to the left value of Either wrapped in a Task and returns a new task with the result.

examples

Examples

iex> task =  Task.async(fn -> Hike.Either.left("Hello") end)
...> applied_task =map_left_async(task, &String.upcase/1)
...> Task.await(applied_task)
%Hike.Either{l_value: "HELLO", r_value: nil, is_left?: true}


iex> task = Task.async(fn -> Hike.Either.right(10) end)
...> applied_task = map_left_async(task, &String.upcase/1)
...> Task.await(applied_task)
%Hike.Either{l_value: nil, r_value: "Hello", is_left?: false}
Link to this function

map_right(either, func)

@spec map_right(either_right(t_right()), (t_right() -> tr())) :: either_right(tr())
@spec map_right(either_left(t_left()), (t_right() -> tr())) :: either_left(t_left())

Maps the value of theEither from right state using the given function.

If the Either is in the left state, the function returns the Either unchanged. If the Either is in the right state, the function applies the given function to the value of the right state, and returns a new Either with the transformed value.

examples

Examples

iex> (Either.right "hello") |> Either.map_right(fn str -> String.upcase(str) end)
%Hike.Either{l_value: nil, r_value: "HELLO", is_left?: false}

iex> (Either.left "hello") |> Either.map_right(fn str -> String.upcase(str) end)
%Hike.Either{l_value: "hello", r_value: nil, is_left?: true}
Link to this function

map_right_async(wrapped_task, mapper)

@spec map_right_async(Task.t(), mapper(t())) :: Task.t()

Applies the provided function mapper to the right value of Either wrapped in a Task and returns a new task with the result.

examples

Examples

iex> task = Task.async(fn -> %Hike.Either{right: "Hello"} end)
...> applied_task = map_right_async(task, &String.upcase/1)
...> Task.await(applied_task)
%Hike.Either{left: nil, right: "HELLO", is_left?: false}

iex> task = Task.async(fn -> %Hike.Either{left: 10} end)
...> applied_task = map_right_async(task, &String.upcase/1)
...> Task.await(applied_task)
%Hike.Either{left: 10, right: nil, is_left?: true}
Link to this function

match(either, left_fn, right_fn)

@spec match(either(t_left(), t_right()), (t_left() -> tr()), (t_right() -> tr())) ::
  tr()

Matches an Either value and applies the corresponding function.

examples

Examples

 iex> (Either.left "hello") |> Either.match(fn str -> String.upcase(str) end, fn ()-> "NOT FOUND" end)
 "HELLO"
 iex> (Either.right 4) |> Either.match(fn num-> num * num end, fn num-> num + num end)
 8
Link to this function

match_async(wrapped_task, left_fn, right_fn)

@spec match_async(Task.t(), (t() -> term()), (t() -> term())) :: Task.t()

Matches an Either value wrapped in a Task and applies the corresponding function asynchronously.

examples

Examples

iex> task =  Task.async(fn -> Hike.Either.left("Hello") end)
...> applied_task =bind_left_async(task, fn str -> Either.left(String.upcase(str)) end)
...> match_async(applied_task , fn x -> "value is : " <> x <> "."  end, fn (_)-> "No Result Found."  end)
 "value is : HELLO."


iex> task = Task.async(fn -> Hike.Either.right("Hello") end)
...> applied_task = map_left_async(task, &String.upcase/1)
...> match_async(applied_task , fn x -> x  end, fn (y)-> "value is : " <> y <> "."  end)
 "value is : Hello."
@spec right(t_right()) :: either_right(t_right())

Creates a new Either in Right state with a given value.

examples

Examples

iex> right("foo bar")
%Hike.Either{l_value: nil, r_value: "foo bar", is_left?: false}
Link to this function

to_mayfail(either)

@spec to_mayfail(either_right(t())) :: Hike.MayFail.mayfail_success(t())
@spec to_mayfail(either_left(t())) :: Hike.MayFail.mayfail_failure(t())

convert Either form Right state to MayFail Success state and Either from Left state to MayFail Failure state.

example

Example

iex> Hike.left(":error") |> Hike.Either.to_mayfail()
%Hike.MayFail{failure: ":error", success: nil, is_success?: false}
iex> Hike.right(9) |> Hike.Either.to_mayfail
%Hike.MayFail{failure: nil, success: 9, is_success?: true}
Link to this function

to_option(either)

@spec to_option(either_right(t())) :: Hike.Option.option(t())
@spec to_option(either_left(t())) :: Hike.Option.option()

convert Either from Right state to Option Some state and Either from Left state to Option None state.

example

Example

iex> Hike.right(9) |> Hike.Either.to_option()
%Hike.Option{value: 9}
iex> Hike.left(":error") |> Hike.Either.to_option()
%Hike.Option{value: nil}