local Option = {} export type Option = typeof(Option) & { _optValue: T?, typeId: "Option", } function None(): Option return Option.new(nil) :: Option end function Some(val: T): Option return Option.new(val) :: Option end function Option.new(val: T?) return setmetatable( { _optValue = val, typeId = "Option", } :: Option, { __index = Option, __tostring = function(self: Option) if self._optValue == nil then return `{self.typeId}::None` else return `{self.typeId}::Some({self._optValue})` end end, __eq = function(self: Option, other: Option): boolean return self._optValue == other._optValue end, __lt = function(self: Option, other: Option): boolean if self:isSome() and other:isSome() then return (self._optValue :: any) < (other._optValue :: any) end return false end, __le = function(self: Option, other: Option): boolean if self:isSome() and other:isSome() then return (self._optValue :: any) <= (other._optValue :: any) end return false end, } -- TODO: Implement __iter, once iterators traits exist ) end function Option.isSome(self: Option): boolean return self._optValue ~= nil end function Option.isSomeAnd(self: Option, op: (val: T) -> boolean): boolean -- We know that it's not None, so this is fine return self:isSome() and op(self._optValue :: T) end function Option.isNone(self: Option): boolean return not self:isSome() end function Option.expect(self: Option, msg: string): T | never if self:isSome() then return self._optValue :: T end return error(`panic: {msg}; expected Option to be type Option::Some(T), got Option::None`) end function Option.unwrap(self: Option): T | never if self:isSome() then return self._optValue :: T end return error("called `Option:unwrap()` on a `None` value") end function Option.unwrapOr(self: Option, default: T): T if self:isSome() then return self._optValue :: T end return default end function Option.unwrapOrElse(self: Option, default: () -> T): T if self:isSome() then return self._optValue :: T end return default() end function Option.map(self: Option, op: (x: T) -> U?): Option if self:isSome() then local val = op(self._optValue :: T) if val == nil then return None() end return Some(val) end return None() end function Option.inspect(self: Option, op: (x: T) -> nil): Option if self:isSome() then op(self._optValue :: T) end return self end function Option.mapOr(self: Option, default: U, op: (val: T) -> U) if self:isSome() then return op(self._optValue :: T) end return default end function Option.mapOrElse(self: Option, default: () -> U, op: (val: T) -> U): U if self:isSome() then return op(self._optValue :: T) end return default() end -- TODO: Iterator traits function Option.iter() end function Option.and_(self: Option, optb: Option): Option if self:isSome() then return optb end return None() end function Option.andThen(self: Option, op: (val: T) -> Option): Option if self:isSome() then return op(self._optValue :: T) end return None() end function Option.filter(self: Option, predicate: (val: T) -> boolean): Option if self:isSome() then if predicate(self._optValue :: T) then return self end end return None() end function Option.or_(self: Option, optb: Option): Option if self:isSome() then return self end return optb end function Option.orElse(self: Option, op: () -> Option): Option if self:isSome() then return self end return op() end function Option.xor(self: Option, optb: Option): Option if self:isSome() and optb:isSome() then return None() end if self:isSome() then return self elseif optb:isSome() then return optb end return None() end function Option.insert(self: Option, val: T): T self._optValue = val return self._optValue :: T end function Option.getOrInsert(self: Option, val: T): T if self:isNone() then self._optValue = val end return self._optValue :: T end function Option.take(self: Option): Option if self:isSome() then local val = self._optValue :: T self._optValue = nil return Some(val) end return None() end function Option.replace(self: Option, val: T): Option local current: Option = self self._optValue = val if current:isNone() then return current end return Some(current._optValue :: T) end function Option.contains(self: Option, val: T): boolean if self:isSome() then return self._optValue == val end return false end function Option.zip(self: Option, other: Option): Option<{ T | U }> if self:isSome() and other:isSome() then return Some({ self._optValue, other._optValue }) end return None() end function Option.zipWith(self: Option, other: Option, op: (x: T, y: U) -> R?): Option if self:isSome() and other:isSome() then local computed = op(self._optValue :: T, other._optValue :: U) if computed ~= nil then return Some(computed :: R) end end return None() end function Option.unzip(self: Option): (Option, Option) if self:isSome() then if self:isSome() and typeof(self._optValue) == "table" and #self._optValue == 2 then return Some(self._optValue[1] :: A), Some(self._optValue[2] :: B) end end return None(), None() end function Option.getInner(self: Option): T? return self._optValue end return { Option = Option, Some = Some, None = None, }