Hike.Option (hike v0.1.0)

The Hike.Option module provides an implementation of the Optional data type. It defines a struct Option with a single field value which can either be @none nil or any other value of type <T>. This implementation provides functions to work with Optional data including

  • mapping,
  • binding
  • filtering,
  • applying and many more functions to the value inside the Optional data.

example-usage

Example Usage

iex> option = %Hike.Option{value: 42}
%Hike.Option{value: 42}

iex> Hike.Option.map(option, &(&1 * 2))
%Hike.Option{value: 84}

iex> Hike.Option.filter(option, &(rem(&1, 2) == 0))
%Hike.Option{value: 42}

iex> Hike.Option.apply(option, &(&1 + 10))
%Hike.Option{value: 52}
# Define a User struct
defmodule User do
@derive {Jason.Encoder, only: [:id,:age, :name]}
defstruct [:id, :age, :name]
end

defmodule TestHike do
# Import the Hike.Option module
import Hike.Option

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

# Function to update the user's name to uppercase
# This function takes a user, a real data type, and returns an elevated data type Option
# if `%User{}` could transform successfully return `Option` in `some` state or else in `none` state.
def update_name_to_uppercase(user) do
  case user.name do
   nil -> none()
   name -> %User{user | name: String.upcase(user.name)} |> some
  end
end


#   for above function another version could be like this where user take real data type %User{}, and return update %User{}
#   def update_name_to_uppercase(user) do
#    uppercase_name = String.upcase(user.name)
#    %User{user | name: uppercase_name}
#  end
# for this case map function will be used like it's been used for `increase_age_by1`


# Function to increase the user's age by one
# This function takes a user, a real data type, and returns a real data type user
def increase_age_by1(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
  Jason.encode!(user) |> IO.puts
end

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

  # 1. Expressiveness: Using Hike's Option type to handle optional values
  fetch_user(user_id)
  |> bind(&update_name_to_uppercase/1)
  |> map(&increase_age_by1/1)
  |> match(&print_user_as_json/1, fn -> IO.puts("User not found") end)

  user_id = 3

  # 2. Safer and more predictable code: Handling all possible cases explicitly
  fetch_user(user_id)
  |> bind(&update_name_to_uppercase/1)
  |> map(&increase_age_by1/1)
  |> match(&print_user_as_json/1, fn -> IO.puts("User not found") end)
end
end

output

Output

iex> TestHike.test_user
# User ID: 1
{"id":1,"age":31,"name":"JOHN DOE"}
# User ID: 3
User not found

For more information on how to use this module, please see the documentation for the individual functions.

Link to this section Summary

Types

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

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

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>.

Elevated data type of Option struct that represents None state.

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

t()

generic input type <T>.

generic return type <TR>.

Functions

Applies a given function to the value of an Option struct and returns the result as a new Option.

async version on apply applies the func function to a value wrapped in a Task and returns a new task with the transformed value.

Transforms an Option<T> struct with a non-nil value, using a binder function that returns another Option option in none state or Option<TR>option in some state.

async version on bind applies the binder function to a value wrapped in a Task and returns a new task with the transformed value.

Applies the given function to the value of the provided option, returning a new option containing the original value if the function returns a truthy value, otherwise returning an empty option. If the provided option has no value, this function simply returns the empty option.

Returns true if the Option is in None state, otherwise false.

Returns true if the Option is in Some state, otherwise false.

Applies the given mapping function to the value inside the Option struct and returns a new Option struct containing the transformed value. If the input Option struct is @none, the function returns a new Option struct in none state.

async version on map Applies the mapper function to a value wrapped in a Task and returns a new task with the mapped value.

Pattern matches on an option and returns the result of the respective matching function. Calls some_fun with the value of the Option if the Option is in some state, or calls none_fun if the Option is in none state.

async version on match pattern matches on an Option value wrapped in a Task and returns the result of the matching function.

Creates an Option in None state.

Creates an Option in Some state

convert Option into respective Either. if Option is in None state Either will be return in Left state. and Option from Some state will convert to Either Right state.

convert Option into respective MayFail. if Option is in None state MayFail will be return in Failure state. and Option from Some state will convert to MayFail Success state.

Link to this section Types

@type binder() :: (() -> option(tr()))

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

example

Example

@spec whatever() :: Hike.Option.option(atom)
def whatever(), do: some(:ok)

@spec whatever() :: Hike.Option.option()
def whatever(), do: none()
@type binder(t) :: (t -> option(tr()))

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

example

Example

@spec square(pos_number) :: Hike.Option.option(number)
def square(x) when x > 0, do: x * x |> some()

@spec square(number) :: Hike.Option.option()
def square(x) do
  case x when x < 0 do
    true -> none()
    false -> square(x)
  end
end
@type func() :: (() -> tr())

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

example

Example

@spec whatever() :: atom
def whatever(), do: :ok
@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 option() :: %Hike.Option{value: nil}

Elevated data type of Option struct that represents None state.

example

Example

%Hike.Option{value: nil, is_some?: false}
@type option(t) :: %Hike.Option{value: t}

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

example

Example

%Hike.Option{value: 40, is_some?: true}
@type t() :: any()

generic input type <T>.

@type tr() :: any()

generic return type <TR>.

Link to this section Functions

Link to this function

apply(option, func)

@spec apply(option(t()), func(t())) :: option(tr())
@spec apply(option(), func()) :: option()

Applies a given function to the value of an Option struct and returns the result as a new Option.

examples

Examples

iex> option = %Option{value: 42}
iex> add_one = fn x -> x + 1 end
iex> Option.apply(option, add_one)
%Option{value: 43}

iex> none_option = %Option{value: nil}
iex> Option.apply(none_option, add_one)
%Option{value: nil}

iex> option = %Option{value: "hello"}
iex> upcase_string = fn str -> String.upcase(str) end
iex> Option.apply(option, upcase_string)
%Option{value: "HELLO"}
Link to this function

apply_async(wrapped_task, func)

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

async version on apply applies the func function to a value wrapped in a Task and returns a new task with the transformed value.

examples

Examples

iex> task = Task.async(fn -> Hike.Option.some(10) end)
...> mapped_task = apply_async(task, &(&1 * 2))
...> Task.await(mapped_task)
%Hike.Option{value: 20}

iex> task = Task.async(fn -> Hike.Option.none() end)
iex> mapped_task = apply_async(task, &(&1))
iex> Task.await(mapped_task)
%Hike.Option{value: nil}
Link to this function

bind(opt, func)

@spec bind(option(t()), binder(t())) :: option(tr())
@spec bind(option(), binder()) :: option()

Transforms an Option<T> struct with a non-nil value, using a binder function that returns another Option option in none state or Option<TR>option in some state.

** If the input Option has a nil value, returns a new Option struct with a nil value. if you have a function that return Option<TR> and you want to apply mapping then use bind function to avoid double elevation

examples

Examples

iex> option = %Option{value: 42}
iex> add_one = fn x -> Option.some(x + 1) end
iex> Option.bind(option, add_one)
%Option{value: 43}

iex> Option.bind(Option.some("hello"), fn x -> Option.some(String.upcase(x)) end)
%Option{value: "HELLO"}

iex> Option.bind(Option.none() )
%Option{value: nil}
Link to this function

bind_async(wrapped_task, func)

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

async version on bind applies the binder function to a value wrapped in a Task and returns a new task with the transformed value.

examples

Examples

iex> task = Task.async(fn -> Hike.Option.some(10) end)
...> mapped_task = bind_async(task, fn x -> Hike.Option.some(x + 1) end)
...> Task.await(mapped_task)
%Hike.Option{value: 20}

iex> task = Task.async(fn -> Hike.Option.none() end)
iex> mapped_task = bind_async(task, fn x -> Hike.Option.some(x + 1) end)
iex> Task.await(mapped_task)
%Hike.Option{value: nil}
Link to this function

filter(opt, func)

@spec filter(option(), func()) :: option()
@spec filter(option(t()), func(t())) :: option(tr())

Applies the given function to the value of the provided option, returning a new option containing the original value if the function returns a truthy value, otherwise returning an empty option. If the provided option has no value, this function simply returns the empty option.

examples

Examples

iex> Option.filter(%Option{value: 42}, fn x -> rem(x, 2) == 0 end)
%Option{value: 42}

iex> Option.filter(%Option{value: 42}, fn x -> rem(x, 2) == 1 end)
%Option{value: nil}

iex> Option.filter(%Option{value: nil}, fn x -> rem(x, 2) == 0 end)
%Option{value: nil}

iex> list_opt = Option.some([1,2,3])
iex> list_filter = fn (lst) -> Enum.count(lst) > 5  end
iex> Option.filter(list_opt, list_filter)
%Hike.Option{value: nil}
Link to this function

is_none?(option)

@spec is_none?(option() | option(t())) :: boolean()

Returns true if the Option is in None state, otherwise false.

examples

Examples

iex> Option.is_none?(Option.none())
true

iex> Option.is_none?(Option.some("hello"))
false
Link to this function

is_some?(option)

@spec is_some?(option() | option(t())) :: boolean()

Returns true if the Option is in Some state, otherwise false.

examples

Examples

iex> Option.is_some?(Option.some("hello"))
true

iex> Option.is_some?(Option.none())
false
Link to this function

map(option, func)

@spec map(option(t()), mapper(t())) :: option(tr())
@spec map(option(), mapper()) :: option()

Applies the given mapping function to the value inside the Option struct and returns a new Option struct containing the transformed value. If the input Option struct is @none, the function returns a new Option struct in none state.

examples

Examples

  iex> Option.map(Option.some("hello"), fn x -> String.upcase(x) end)
  %Option{value: "HELLO"}

  iex> Option.map(Option.none(), fn x -> String.upcase(x) end)
  %Option{value: nil}
Link to this function

map_async(wrapped_task, mapper)

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

async version on map Applies the mapper function to a value wrapped in a Task and returns a new task with the mapped value.

examples

Examples

iex> task = Task.async(fn -> Hike.Option.some(10) end)
...> mapped_task = map_async(task, &(&1 * 2))
...> Task.await(mapped_task)
%Hike.Option{value: 20}

iex> task = Task.async(fn -> Hike.Option.none() end)
iex> mapped_task = map_async(task, &(&1))
iex> Task.await(mapped_task)
%Hike.Option{value: nil}
Link to this function

match(arg1, some_fun, none_fun)

@spec match(option(t()) | option(), func(t()), func()) :: tr()
@spec match(Task.t(), func(t()), func()) :: tr()

Pattern matches on an option and returns the result of the respective matching function. Calls some_fun with the value of the Option if the Option is in some state, or calls none_fun if the Option is in none state.

examples

Examples

iex> import Hike.Option

# Match on `some` value with a matching function and on `none` value with a none-matching function
iex> match(some("hello"), &(&1 <> " world"), fn -> "no value" end)
# => "hello world"

# Match on `none` value with a none-matching function even if a matching function is provided
iex> match(none(), &(&1 * 2), fn -> "no value" end)
# => "no value"

iex> Option.match(Option.some("hello"), fn x -> String.upcase(x) end, fn -> "nothing" end )
"HELLO"

iex> Option.match(Option.none(), fn x -> String.upcase(x) end,   fn -> "nothing" end)
"nothing"
Link to this function

match_async(wrapped_task, some_fun, none_fun)

async version on match pattern matches on an Option value wrapped in a Task and returns the result of the matching function.

examples

Examples

iex> task = Task.async(fn -> Hike.Option.some(10) end)
iex> mapped_task = map_async(task, &(&1 * 2))
iex> match_async(mapped_task , fn x -> "value is : " <> x <> "."  end, fn ()-> "No Result Found."  end)
 "value is : 20."

iex> task = Task.async(fn -> Hike.Option.none() end)
iex> mapped_task = map_async(task, &(&1))
iex> match_async(mapped_task , fn x -> "value is : " <> x <> "."  end, fn ()-> "No Result Found."  end)
 "No Result Found."
@spec none() :: option()

Creates an Option in None state.

examples

Examples

iex> Option.none()
%Option{value: :nil}
@spec some(t()) :: option(t())

Creates an Option in Some state

examples

Examples

iex> Option.some("hello")
%Option{value: "hello"}
Link to this function

to_either(option)

@spec to_either(option()) :: Hike.Either.either_left(:error)
@spec to_either(option(t())) :: Hike.Either.either_right()

convert Option into respective Either. if Option is in None state Either will be return in Left state. and Option from Some state will convert to Either Right state.

example

Example

iex> Hike.option(9) |> Hike.Option.to_either
%Hike.Either{l_value: nil, r_value: 9, is_left?: false}

iex> Hike.option() |> Hike.Option.to_either
%Hike.Either{l_value: :error, r_value: nil, is_left?: true}
Link to this function

to_mayfail(option)

@spec to_mayfail(option()) :: Hike.MayFail.mayfail_failure(:error)
@spec to_mayfail(option(t())) :: Hike.MayFail.mayfail_success(t())

convert Option into respective MayFail. if Option is in None state MayFail will be return in Failure state. and Option from Some state will convert to MayFail Success state.

example

Example

iex(13)> Hike.option() |> Hike.Option.to_mayfail
%Hike.MayFail{failure: :error, success: nil, is_success?: false}

iex(14)> Hike.option(3) |> Hike.Option.to_mayfail
%Hike.MayFail{failure: nil, success: 3, is_success?: true}