Current section

Files

Jump to
funx lib monad maybe just.ex
Raw

lib/monad/maybe/just.ex

defmodule Funx.Monad.Maybe.Just do
@moduledoc """
[![Run in Livebook](https://livebook.dev/badge/v1/black.svg)](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2FJKWA%2Ffunx%2Fblob%2Fmain%2Flivebooks%2Fmonad%2Fmaybe%2Fjust.livemd)
Represents the `Just` variant of the `Maybe` monad, used to model the presence of a value.
A `Just` wraps a single value and participates in functional composition by propagating the contained value through monadic operations.
This module implements the following protocols:
- `Funx.Monad`: Implements `bind/2`, `map/2`, and `ap/2` for monadic composition.
- `Funx.Foldable`: Provides `fold_l/3` and `fold_r/3` to fold over the wrapped value.
- `Funx.Filterable`: Supports filtering with `filter/2`, `filter_map/2`, and `guard/2`.
- `Funx.Eq`: Enables equality checks between `Just` and other `Maybe` values.
- `Funx.Ord`: Defines ordering behavior between `Just` and `Nothing`.
- `Funx.Tappable`: Executes side effects on the wrapped value via `Funx.Tappable.tap/2`.
These protocol implementations allow `Just` to participate in structured computation, validation, filtering, and comparison within the `Maybe` context.
"""
@enforce_keys [:value]
defstruct [:value]
@type t(value) :: %__MODULE__{value: value}
@doc """
Creates a new `Just` value.
The `pure/1` function wraps a value in the `Just` monad, representing the presence of the value.
## Examples
iex> Funx.Monad.Maybe.Just.pure(5)
%Funx.Monad.Maybe.Just{value: 5}
### Raises
- `ArgumentError` if `nil` is provided.
iex> Funx.Monad.Maybe.Just.pure(nil)
** (ArgumentError) Cannot wrap nil in a Just
"""
@spec pure(value) :: t(value) when value: term()
def pure(nil), do: raise(ArgumentError, "Cannot wrap nil in a Just")
def pure(value), do: %__MODULE__{value: value}
end
defimpl String.Chars, for: Funx.Monad.Maybe.Just do
alias Funx.Monad.Maybe.Just
def to_string(%Just{value: value}), do: "Just(#{value})"
end
defimpl Funx.Monad, for: Funx.Monad.Maybe.Just do
alias Funx.Monad.Maybe.{Just, Nothing}
@spec map(Just.t(value), (value -> result)) :: Just.t(result)
when value: term(), result: term()
def map(%Just{value: value}, func), do: Just.pure(func.(value))
@spec ap(Just.t((value -> result)) | Nothing.t(), Just.t(value) | Nothing.t()) ::
Just.t(result) | Nothing.t()
when value: term(), result: term()
def ap(%Just{value: func}, %Just{value: value}),
do: Just.pure(func.(value))
def ap(%Just{}, %Nothing{}), do: %Nothing{}
@spec bind(Just.t(value), (value -> Just.t(result))) :: Just.t(result)
when value: term(), result: term()
def bind(%Just{value: value}, func), do: func.(value)
end
defimpl Funx.Foldable, for: Funx.Monad.Maybe.Just do
alias Funx.Monad.Maybe.Just
@spec fold_l(Just.t(value), (value -> result), (-> result)) :: result
when value: term(), result: term()
def fold_l(%Just{value: value}, just_func, _nothing_func) do
just_func.(value)
end
@spec fold_r(Just.t(value), (value -> result), (-> result)) :: result
when value: term(), result: term()
def fold_r(%Just{} = just, just_func, nothing_func) do
fold_l(just, just_func, nothing_func)
end
end
defimpl Funx.Filterable, for: Funx.Monad.Maybe.Just do
alias Funx.Monad.Maybe
alias Funx.Monad.Maybe.Just
alias Funx.Monad
@spec guard(Funx.Monad.Maybe.Just.t(value), boolean()) :: Funx.Monad.Maybe.t(value)
when value: var
def guard(%Just{} = maybe, true), do: maybe
def guard(%Just{}, false), do: Maybe.nothing()
@spec filter(Funx.Monad.Maybe.Just.t(value), (value -> boolean())) :: Funx.Monad.Maybe.t(value)
when value: var
def filter(%Just{} = maybe, predicate) do
Monad.bind(maybe, fn value ->
if predicate.(value) do
Maybe.pure(value)
else
Maybe.nothing()
end
end)
end
@spec filter_map(Funx.Monad.Maybe.Just.t(value), (value -> Funx.Monad.Maybe.t(result))) ::
Funx.Monad.Maybe.t(result)
when value: var, result: var
def filter_map(%Just{value: value}, func) do
case func.(value) do
%Just{} = just -> just
_ -> Maybe.nothing()
end
end
end
defimpl Funx.Eq.Protocol, for: Funx.Monad.Maybe.Just do
alias Funx.Monad.Maybe.{Just, Nothing}
alias Funx.Eq.Protocol
def eq?(%Just{value: v1}, %Just{value: v2}), do: Protocol.eq?(v1, v2)
def eq?(%Just{}, %Nothing{}), do: false
def not_eq?(%Just{value: v1}, %Just{value: v2}), do: not Protocol.eq?(v1, v2)
def not_eq?(%Just{}, %Nothing{}), do: true
end
defimpl Funx.Ord.Protocol, for: Funx.Monad.Maybe.Just do
alias Funx.Monad.Maybe.{Just, Nothing}
alias Funx.Ord.Protocol
def lt?(%Just{value: v1}, %Just{value: v2}), do: Protocol.lt?(v1, v2)
def lt?(%Just{}, %Nothing{}), do: false
def le?(%Just{value: v1}, %Just{value: v2}), do: Protocol.le?(v1, v2)
def le?(%Just{}, %Nothing{}), do: false
def gt?(%Just{value: v1}, %Just{value: v2}), do: Protocol.gt?(v1, v2)
def gt?(%Just{}, %Nothing{}), do: true
def ge?(%Just{value: v1}, %Just{value: v2}), do: Protocol.ge?(v1, v2)
def ge?(%Just{}, %Nothing{}), do: true
end
defimpl Funx.Summarizable, for: Funx.Monad.Maybe.Just do
def summarize(%{value: value}), do: {:maybe_just, Funx.Summarizable.summarize(value)}
end
defimpl Funx.Tappable, for: Funx.Monad.Maybe.Just do
alias Funx.Monad.Maybe.Just
@spec tap(Just.t(value), (value -> any())) :: Just.t(value) when value: term()
def tap(%Just{value: value} = just, func) when is_function(func, 1) do
func.(value)
just
end
end