Skip to main content

function construction - Better solution than returning a list of 3 values?


I have a function (using SetDelayed) that currently returns 3 values in a list. Later on I use the result of this list along with [[1]], [[2]], and [[3]] to use the values. Is there a way I can give each value a "name" of some sort, and return only one value in such a way that all these values can be accessed by name? (Coming from an object-oriented programming perspective, I just want to return a single object with a few fields/accessors.)



Answer



Here are some options:


Lists of Rules


A simple option would be to return a list of rules:



$someone = {"name" -> "Fred", "gender" -> "Male", "age" -> 25};

Fields can then be extracted thus:


"name" /. $someone
(* "Fred" *)

"age" /. $someone
(* 25 *)

Wrapper Patterns



A variation on this theme would be to define a pattern that represents a new value type:


$person = person[name_, gender_, age_];

$someoneElse = person["Fred", "Male", 25];

Extracting fields is more verbose:


$someoneElse /. $person :> name
(* "Fred" *)

... but it opens the possibility of extracting values computed from multiple fields:



$someoneElse /. $person :> name ~~ " (" ~~ gender ~~ ")"
(* "Fred (Male)" *)

Manually Defined Wrapper Accessors


We could extend the previous example by writing "accessor functions" that access components of a wrapper:


personName[$person] := name

personGender[$person] := gender

personAge[$person] := age



personName @ $someoneElse
(* Fred *)

personAge @ $someoneElse
(* 25 *)

Automatically Defined Wrapper Accessors


If we were going to define many such wrapper types, it would be convenient to automate the generation of the wrapper functions:



SetAttributes[assembleName, HoldAll]
assembleName[p_Symbol, s_Symbol] :=
Context[p]~~SymbolName[p]~~StringReplace[SymbolName[s], f_~~r___ :> ToUpperCase[f]~~r] //
Symbol

defineAccessors[f:w_[Verbatim[Pattern][_, Blank[]]..]] :=
Cases[f, Verbatim[Pattern][s_, Blank[]] :> (Hold[#[f], s] &@ assembleName[w, s])] /.
Hold[l:s_[___], r_] :> (l := r; s)

For example:



defineAccessors[movie[name_, year_, quote_]]
(* {movieName, movieYear, movieQuote} *)

randomMovie[] :=
RandomChoice @ {
movie["2001: A Space Odyssey",1968,"Watch out! He's got a bone!"]
, movie["Prometheus",2012,"Here, cobra, cobra... Gimme a hug!"]
, movie["Star Wars: The Phantom Menace",1999,"I say we nuke the JJB from orbit..."]
, movie["Firefly",2002,"...Sniff..."]
}


$someMovie = randomMovie[];

$someMovie // movieName
(* "2001: A Space Odyssey" *)

$someMovie // movieYear
(* 1968 *)

$someMovie // movieQuote

(* "Watch out! He's got a bone!" *)

Comments