From b07a7fb9a83130307e613b27411200e6d7e6339a Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 17:46:25 +1000 Subject: [PATCH] define Maybe monad --- nib/types/default.nix | 2 + nib/types/maybe.nix | 100 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 nib/types/maybe.nix diff --git a/nib/types/default.nix b/nib/types/default.nix index d1c309f..3cb8350 100644 --- a/nib/types/default.nix +++ b/nib/types/default.nix @@ -2,6 +2,7 @@ attrs = import ./attrs.nix args; fault = import ./fault.nix args; lists = import ./lists.nix args; + maybe = import ./maybe.nix args; res = import ./res.nix args; in attrs.mergeAttrsList [ @@ -9,6 +10,7 @@ in attrs fault lists + maybe res # submodule content is accessible first by submodule name diff --git a/nib/types/maybe.nix b/nib/types/maybe.nix new file mode 100644 index 0000000..3114415 --- /dev/null +++ b/nib/types/maybe.nix @@ -0,0 +1,100 @@ +{nib, ...}: +with builtins; +with nib.types; let + # 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 = value: Res true value; + None = Maybe false null; + + # Pattern Matching + isMaybe = T: attrNames T == ["_some_" "_value_"]; + isSome = T: isMaybe T && T._some_; + isNone = T: isMaybe T && !T._some_; + + # Unwrap (Monadic Return Operation) + unwrapMaybe = f: g: T: + if isSome T + then f T._value_ + else g T._value_; + + # Map (Monadic Bind Operation) + mapMaybe = f: T: + if isSome T + then Some (f T._value_) + else 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 = f: mapMaybe f; + + # 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; +}