nt/nib/types/maybe.nix
2025-12-18 12:10:38 +10:00

110 lines
3.2 KiB
Nix

{nib, ...}: let
findFirst = nib.std.findFirst;
# TODO: try get enum generation working (and other type constructors)
# Maybe = mkEnum "nib::Maybe" {
# Some = mkEnumVariant {value = "nix::String";};
# None = mkEnumVariant {};
# };
# TODO: you could even simplify this and pregenerate
# TODO: monadic operations by defining:
# mkMonad = mkEnum ... (blah blah blah)
#
# NOTE: internal view:
# NOTE: sum == enum
# NOTE: product == struct
# NOTE: terminal == literal/epsilon (in EBNF/Extended Backus-Naur form)
# Maybe = {
# # "adt" aka "algebraic data type"
# _adt_ = nib.types.adt.sum;
# _type_ = "Maybe"; # full type signature is "nib::Maybe"
# _vars_ = [
# {
# _adt_ = nib.types.adt.product;
# _type_ = "Some"; # type signature is "nib::Maybe::Some"
# _body_ = [
# # the name "value" was chosen by the user, its not inherit to nib
# value = {
# _adt_ = nib.types.adt.terminal;
# # nib.typeSig simply parses the string by splitting on "::" and forming a list
# _sig_ = nib.typeSig "nix::String";
# };
# ];
# };
# {
# _adt_ = nib.types.adt.terminal;
# _type_ = "None"; # type signature is "nib::Maybe::None"
# _sig_ = nib.typeSig "nix::Null";
# };
# ];
# None = {
#
# };
# _body_ = {
# _some_ = some; # allows _value_ to be null (yuck!!)
# _value_ = value;
# };
# };
#
# TODO: you could enforceType types as follows
# DEFINE: enforce = pred: var:
# DEFINE: assert (pred var) || throw "..."; var;
# DEFINE: enforceType = type:
# DEFINE: enforce (var: nib.typeOf var == type)
# value: let
# # NOTE: var is either a fixed-point of enforceType or fails
# # NOTE: either way you don't need to worry about a recursive definition!
# var = enforceType "nib::Maybe" var;
# in { ... }
in rec {
# Maybe (Option) Monad
Maybe = some: value: {
_some_ = some; # allows _value_ to be null (yuck!!)
_value_ = value;
};
Some = Maybe true;
None = Maybe false null;
# Pattern Matching (unsafe and safe variants)
isMaybe = T: builtins.attrNames T == ["_some_" "_value_"];
isSome' = T: isMaybe T && T._some_;
isSome = T:
assert isMaybe T || nib.panic.badType "Maybe" T;
isSome' T;
isNone' = T: isMaybe T && !T._some_;
isNone = T:
assert isMaybe T || nib.panic.badType "Maybe" T;
isNone' T;
# TODO: ensure you check isNone if isSome fails (otherwise it could be another type!)
# Unwrap (Monadic Return Operation)
unwrapMaybe = f: g: T:
if isSome T
then f T._value_
else g T._value_;
unwrapSome = unwrapMaybe (v: v);
unwrapNone = f: unwrapMaybe f (v: v);
# Map (Monadic Bind Operation)
mapMaybe = f: unwrapMaybe (v: Some (f v)) (_: None);
# NOTE: yes this does nothing, its only here so I don't forget
# NOTE: (ie when pregenerating monadic operations with a custom ADT module)
mapSome = mapMaybe;
# Conditionals
someOr = f: T:
if isSome T
then T
else f T;
noneOr = f: T:
if isNone T
then T
else f T;
firstSome = findFirst isSome' None;
nullableToMaybe = x:
if x == null
then None
else Some x;
}