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.)
3 Answers
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!" *)
Just picking up three named return values:
{city, temperature, pressure} = {"London", 18, 1005};
Or using an inert object with functions defined on itself:
res = obj["London", 18, 1005]
obj["London", 18, 1005]
obj[a___]["city"] := obj[a][[1]]
obj[a___]["temperature"] := obj[a][[2]]
obj[a___]["pressure"] := obj[a][[3]]
res["city"]
"London"
res["temperature"]
18
res["pressure"]
1005
UPDATE
The above was the answer for 2012. Nowadays one would use an Association.
I'm not saying I recommend this. It's prone to leaking memory
yourFunc[] := Module[{obj},
obj["this"] = "lala";
obj["that"] = 98;
obj];
So
bla = yourFunc[];
bla["this"]
bla["that"]
"lala"
98
Perhaps a better approach is passing the output variable to the function
SetAttributes[yourFunc2, HoldAll];
yourFunc2[out1_][arg_] := (out1["bla"] = 98; out1["blo"] = 98 - arg;
out1)
So
yourFunc2[x][23]
-
$\begingroup$ Why is this prone to leaking memory? What could cause a leak there? $\endgroup$– jtbandesCommented Oct 26, 2012 at 23:45
-
$\begingroup$ Because the variable that actually "stores" the data is the temporary "obj" variable. So when you later clear bla, the data remains. Unless you get smart about it. $\endgroup$– RojoCommented Oct 26, 2012 at 23:46
-
$\begingroup$ I'm giving alternative approaches to Sjoerd's, which is probably the best. $\endgroup$– RojoCommented Oct 26, 2012 at 23:47
List[]
$\endgroup$yourF[x_]:={a[x],b[x],c[x]}; {myA, myB, myC} = yourF[x]
$\endgroup$