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>
.
generic input type <T>
.
generic input type <T_Left>
represent a type of value on Left
state.
generic input type <T_Right>
represent a type of value on Right
state.
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
binder()
@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)
binder(t)
@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
@opaque either()
represent a type of Either
that could be in either Left
or Right
state
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.
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.
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.
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.
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>
.
func()
@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
func(t)
@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
mapper()
@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
mapper(t)
@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>
.
t_left()
@type t_left() :: any()
generic input type <T_Left>
represent a type of value on Left
state.
t_right()
@type t_right() :: any()
generic input type <T_Right>
represent a type of value on Right
state.
tr()
@type tr() :: any()
generic return type <TR>
.
Link to this section 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.
l_value
: the left value (ifis_left?
is true)r_value
: the right value (ifis_left?
is false)is_left?
: a boolean flag indicating whether the value is a left value (true
) or a right value (false
)
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}
apply_left_async(wrapped_task, func)
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}
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}
apply_right_async(wrapped_task, func)
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}
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}
bind_left_async(wrapped_task, binder)
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}
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}
bind_right_async(wrapped_task, binder)
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}
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.
is_left?(either)
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
is_right?(either)
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
left(value)
@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}
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}
map_left_async(wrapped_task, mapper)
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}
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}
map_right_async(wrapped_task, mapper)
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}
match(either, left_fn, right_fn)
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
match_async(wrapped_task, left_fn, right_fn)
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."
right(value)
@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}
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}
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}