Ferreteria/v0.6/clade/Data/Mem/QVar: Difference between revisions

From Woozle Writes Code
< Ferreteria‎ | v0.6‎ | clade‎ | Data‎ | Mem
Jump to navigation Jump to search
(Created page with "{{page/clade/v2 |fam= {{!}} align=right {{!}} <code>{{l/ver/clade|Aux|StandardBase}}</code> {{!}} align=center {{!}} ⇒ <code>{{l/ver/clade|Data\Mem|QVar}}</code> ⇒ {{!}} align=left {{!}} <poem> <code>/Arr/</code> <code>/Int/</code> <code>/Mix/</code> <code>/Obj/</code> <code>/Res/</code> <code>/Str/</code> <code>/Wild/</code> </poem> |alia= {{!-!}} '''Base'''* [c,i] {{!!}} <code>{{l/ver/clade/full|Aux|StandardBase}}</code> {{!-...")
 
mNo edit summary
 
(4 intermediate revisions by the same user not shown)
Line 22: Line 22:
}}
}}
==About==
==About==
* '''Purpose''': queryable var-wrapper objects
* '''Purpose''': queryable value-wrapper objects
* '''Naming''': Ideally, there would be <code>QVal</code> for read-only values and <code>QVar</code> for R/W, but PHP already makes it difficult enough to implement what I've already got...
 
The idea here is that I wanted to be able to indicate whether a value {exists, has been set, etc.} without relying on a coding convention (the most obvious/common being the use of NULL to indicate that a value is unavailable). While "NULL=unavailable" is not a ''bad'' convention (and I still use it occasionally), it feels klugey to rely on it too much -- while providing an object to wrap the results is both semantic: <code>$qx->{{l/ver/fx|HasIt}}()</code> is much less ambiguous in intent than <code>![https://www.php.net/manual/en/function.is-null.php is_null]($x)</code>.
===Complications===
The only ''problem'' with doing it this way involves a combination of circumstances:
* In order to continue properly enforcing return-types, we need to have a <code>QVar</code>-clade for each type we want to return.
* The simplest and most maintainable way to do this is to have
** a parent-clade (<code>QVar</code>) which defines the interface and implements some general rules, and
** a set of podling-clades which each restrict the value being handled to a specific type.
* However, [https://www.php.net/manual/en/language.oop5.variance.php PHP's inheritance rules for method definitions] only allow return-values to be {the same as or more strict than} the parental definition ("covariance"), while argument-types must be {the same or ''less'' strict} ("contravariance") (see Wikipedia: {{l/wp|type variance}}).
 
This means that we cannot have a common (public) interface for all types, except for methods that only ''return'' values (e.g. <code>{{l/ver/fx|HasIt}}()</code>, <code>{{l/ver/fx|GetIt}}()</code>); each podling-clade must re-define those methods with the appropriate type for its parameters.
===Advantages===
This methodology does have some advantages, despite the awkwardness:
* It allows type-checking for different types of scalars, which is not currently supported by PHP. (It is more commonly supported by classic compiled languages like Pascal and C/C++.)
** e.g. if you want an <code>int</code> which is specifically an error-code and not a quantity &ndash; or even an error-code from a specific library (where two or more libraries might return the same code but mean completely different things by it)
* It allows additional ways of handling values and operations between them that simplify syntax.
** e.g. <code>{{l/ver/fx|FromAValue}}()</code> allows initializing a QVar from an array plus a key in that array, while handling element-nonexistence in a sensible way.
==History==
==History==
* '''{{fmt/date|2024-07-18** starting this from scratch to replace the awkward double-wrapper Wrap and Core class-family.
* '''{{fmt/date|2024|07|18}}''' starting this from scratch to replace the awkward double-wrapper Wrap and Core class-family.
* '''{{fmt/date|2025-01-14** Decided `SetIt_from[O]Array()` should specifically *not* save a reference, because that can cause problems if the value is set elsewhere, or if we're trying to let go of the array to save memory.
* '''{{fmt/date|2025|01|14}}''' Decided <code>SetIt_from[O]Array()</code> should specifically ''not'' save a reference, because that can cause problems if the value is set elsewhere, or if we're trying to let go of the array to save memory.
* '''{{fmt/date|2025-04-20** Renamed `RefFromOArrElem()` -> `WithOAElement()`, for consistency with `FromOAElement()`
* '''{{fmt/date|2025|04|20}}''' Renamed <code>RefFromOArrElem()</code> &rArr; <code>{{l/ver/fx|WithOAElement}}()</code>, for consistency with <code>{{l/ver/fx|FromOAElement}}()</code> (see <code>{{l/ver/fx|From*}}()</code>)
* '''{{fmt/date|2025-04-24** Restored `FromValue()` from code removed on 04/14.
* '''{{fmt/date|2025|04|24}}''' Restored <code>{{l/ver/fx|FromValue}}()</code> from code removed on 04/14.
* '''{{fmt/date|2025-05-11** Changed some terminology:
* '''{{fmt/date|2025|05|11}}''' Changed some terminology:
** <code>[From/With]AElement()</code> -> <code>[From/With]AValue()</code>
** <code>[From/With]AElement()</code> &rArr; <code>[From/With]AValue()</code>
** <code>[From/With]OAElement()</code> -> <code>[From/With]OAValue()</code>
** <code>[From/With]OAElement()</code> &rArr; <code>[From/With]OAValue()</code>
*:  ...and added <code>[From/With]AIndex()</code>
*:  ...and added <code>[From/With]AIndex()</code>
==Code==
==Code==
Line 155: Line 173:
         }
         }
     }
     }
     public function WithOAValue(ArrayIface $oa, int|string $snKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : void {
     public function WithOAValue(ArrayIface $oa, int{{!}}string $snKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : void {
         $isFnd = FALSE;
         $isFnd = FALSE;
         if ($oa->HasIt($snKey)) {
         if ($oa->HasIt($snKey)) {
Line 168: Line 186:
         }
         }
     }
     }
     protected function HandleAbsent(int|string $snKey, ArrayOptsEnum $eOpt) : bool {
     protected function HandleAbsent(int{{!}}string $snKey, ArrayOptsEnum $eOpt) : bool {
         $isFnd = FALSE;
         $isFnd = FALSE;
         switch($eOpt) {
         switch($eOpt) {

Latest revision as of 18:00, 26 November 2025

clade: Data\Mem\QVar
Clade Family
StandardBase QVar
Clade Aliases
Alias Clade
Base* [c,i] Aux\StandardBase
StaticTypes Data\Mem\Val\Types
ArrayOptsEnum Data\Mem\QVar\opts\Array
ArrayIface Sys\Data\Things\Array
Subpages

About

  • Purpose: queryable value-wrapper objects
  • Naming: Ideally, there would be QVal for read-only values and QVar for R/W, but PHP already makes it difficult enough to implement what I've already got...

The idea here is that I wanted to be able to indicate whether a value {exists, has been set, etc.} without relying on a coding convention (the most obvious/common being the use of NULL to indicate that a value is unavailable). While "NULL=unavailable" is not a bad convention (and I still use it occasionally), it feels klugey to rely on it too much -- while providing an object to wrap the results is both semantic: $qx->HasIt() is much less ambiguous in intent than !is_null($x).

Complications

The only problem with doing it this way involves a combination of circumstances:

  • In order to continue properly enforcing return-types, we need to have a QVar-clade for each type we want to return.
  • The simplest and most maintainable way to do this is to have
    • a parent-clade (QVar) which defines the interface and implements some general rules, and
    • a set of podling-clades which each restrict the value being handled to a specific type.
  • However, PHP's inheritance rules for method definitions only allow return-values to be {the same as or more strict than} the parental definition ("covariance"), while argument-types must be {the same or less strict} ("contravariance") (see Wikipedia: type variance).

This means that we cannot have a common (public) interface for all types, except for methods that only return values (e.g. HasIt(), GetIt()); each podling-clade must re-define those methods with the appropriate type for its parameters.

Advantages

This methodology does have some advantages, despite the awkwardness:

  • It allows type-checking for different types of scalars, which is not currently supported by PHP. (It is more commonly supported by classic compiled languages like Pascal and C/C++.)
    • e.g. if you want an int which is specifically an error-code and not a quantity – or even an error-code from a specific library (where two or more libraries might return the same code but mean completely different things by it)
  • It allows additional ways of handling values and operations between them that simplify syntax.
    • e.g. FromAValue() allows initializing a QVar from an array plus a key in that array, while handling element-nonexistence in a sensible way.

History

  • 2024-07-18 starting this from scratch to replace the awkward double-wrapper Wrap and Core class-family.
  • 2025-01-14 Decided SetIt_from[O]Array() should specifically not save a reference, because that can cause problems if the value is set elsewhere, or if we're trying to let go of the array to save memory.
  • 2025-04-20 Renamed RefFromOArrElem()WithOAElement(), for consistency with FromOAElement() (see From*())
  • 2025-04-24 Restored FromValue() from code removed on 04/14.
  • 2025-05-11 Changed some terminology:
    • [From/With]AElement()[From/With]AValue()
    • [From/With]OAElement()[From/With]OAValue()
    ...and added [From/With]AIndex()

Code

interface iQVar extends BaseIface {

    // SETUP
    static function AsNew() : self;
    static function FromVarRef(&$v) : self;
    static function FromAValue(array $ar, int|string $vKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : self;
    static function FromOAValue(ArrayIface $oa, int|string $vKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : self;

    // ACCESS: typeless
    function HasIt() : bool;
    function ZapIt();
    function TypeOk(mixed $v) : bool;

    function WithOAValue(ArrayIface $oa, int|string $key, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : void;

    // ++ ACCESS: basic types ++ //

    //// - int

    function HasInt() : bool;
    function SetInt(int $n);
    function SetIntNz(?int $n);
    function &GetInt() : int;
    function GetIntNz(int $nDefault=0) : int;
    function RefInt(int &$n) : void;

    //// - string

    function HasStr() : bool;
    function SetStr(string $s);
    function SetStrNz(?string $s);
    function &GetStr() : string;
    function GetStrNz(string $sDefault='', string $sPfx='', string $sSfx='') : string;
    function RefStr(string &$s) : void;

    //// - object

    function HasObj() : bool;
    function SetObj(object $o);
    function SetObjNz(?object $o);
    function GetObj() : object;
    function GetObjNz(object $oDefault=NULL) : ?object;

    /*
    // ACCESS: undeclarable
    // These can't be declared because their types change in each podling. "mixed" is replaced by the corresponding type.

    function SetIt(mixed $n);
    function SetItNz(?mixed $v);
    function &GetIt() : mixed;
    function GetItNz(mixed $vDefault=0) : mixed;
    function RefIt(mixed &$v) : void;

    function SetDefault(mixed $n);
    */
}

/**
 * NOTE:
 *  2024-07-19 There is an implied SetIt(variant) method, but we can't declare it here
 *    because that would prevent $v from having a required type.
 */
trait tQVar {

    // ++ SETUP ++ //

    public static function AsNew() : iQVar { return new static; }
    public static function FromVarRef(&$v) : iQVar {
        $oThis = new static;
        $oThis->RefIt($v);
        return $oThis;
    }
    public static function FromValue(mixed $v) : iQVar {
        $oThis = new static;
        $oThis->SetIt($v);
        return $oThis;
    }
    public static function FromAIndex(array $ar, int|string $vKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : iQVar {
        $oThis = new static;
        $oThis->WithAIndex($ar,$key,$eOpt);
        return $oThis;
    }
    public static function FromAValue(array $ar, int|string $vKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : iQVar {
        $oThis = new static;
        $oThis->WithAValue($ar,$key,$eOpt);
        return $oThis;
    }
    public static function FromOAValue(ArrayIface $oa, int|string $key, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : iQVar {
        $oThis = new static;
        $oThis->WithOAValue($oa,$key,$eOpt);
        return $oThis;
    }

    // -- SETUP -- //
    // ++ ACCESS: typeless ++ //

    protected $v = NULL;
    public function ZapIt()          { $this->v = NULL; }

    protected function WithAIndex(array $ar, int|string $snKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : void {
        $isFnd = FALSE;
        if (array_key_exists($snKey,$ar)) {
            $this->SetIt($snKey);
        }
    }

    public function WithAValue(array $ar, int|string $snKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : void {
        $isFnd = FALSE;
        if (array_key_exists($snKey,$ar)) {
            $vDup =& $ar[$snKey];
            $isFnd = $this->TypeOk($vDup);
        }
        if (!$isFnd) {
            $isFnd = $this->HandleAbsent($snKey,$eOpt);
        }
        if ($isFnd) {
            $this->RefIt($vDup);
        }
    }
    public function WithOAValue(ArrayIface $oa, int|string $snKey, ArrayOptsEnum $eOpt=ArrayOptsEnum::Nothing) : void {
        $isFnd = FALSE;
        if ($oa->HasIt($snKey)) {
            $vDup =& $oa->GetRef($snKey);
            $isFnd = $this->TypeOk($vDup);
        }
        if (!$isFnd) {
            $isFnd = $this->HandleAbsent($snKey,$eOpt);
        }
        if ($isFnd) {
            $this->RefIt($vDup);
        }
    }
    protected function HandleAbsent(int|string $snKey, ArrayOptsEnum $eOpt) : bool {
        $isFnd = FALSE;
        switch($eOpt) {
          case ArrayOptsEnum::MakeElem:
            $vDup = $oa->MakeIt($snKey);  // create an entry for the key so we can RefIt
            $isFnd = TRUE;
            echo " - used MakeIt($snKey)".CRLF;
            break;
          case ArrayOptsEnum::ZapLocal:
            $this->ZapIt();
            echo " - used ZapIt()".CRLF;
            break;
        }
        return $isFnd;
    }

    // ++ ACCESS: basic types ++ //

    //// - int

    public function HasInt() : bool           { return is_int($this->v); }
    public function SetInt(int $n)            { $this->v = $n; }
    public function SetIntNz(?int $n)         { if (is_int($n)) { $this->SetInt($n); } else { $this->ZapIt(); } }
    public function &GetInt() : int           { return $this->v; }
    public function GetIntNz(int $nDefault=0) : int { return $this->HasInt() ? $this->v : $nDefault; }
    public function RefInt(int &$n) : void { $this->v =& $n; }

    //// - string

    public function HasStr() : bool      { return is_string($this->v); }
    public function SetStr(string $s)    { $this->v = $s; }
    public function SetStrNz(?string $s) { if (is_string($s)) { $this->SetStr($s); } else { $this->ZapIt(); } }
    public function &GetStr() : string   { return $this->v; }
    public function GetStrNz(
      string $sDefault='',
      string $sPfx='',
      string $sSfx=''
      ) : string { return ($this->HasStr() ? ($sPfx.$this->v.$sSfx) : $sDefault); }
    public function GetStrNzN(
      string $sDefault=NULL,
      string $sPfx='',
      string $sSfx=''
      ) : ?string { return ($this->HasStr() ? ($sPfx.$this->v.$sSfx) : $sDefault); }
    public function RefStr(string &$s) : void { $this->v =& $s; }

    //// - object

    public function HasObj() : bool                             { return is_object($this->v); }
    public function SetObj(object $o)                           { $this->v = $o; }
    public function SetObjNz(?object $o)                        { if (is_object($o)) { $this->SetObj($o); } else { $this->ZapIt(); } }
    public function GetObj() : object                           { return $this->v; }
    public function GetObjNz(object $oDefault=NULL) : ?object   { return $this->HasObj() ? $this->v : $oDefault; }

    // -- ACCESS -- //
}
abstract class caQVar extends BaseClass implements iQVar {
    use tQVar;
}