Better solution than returning a list of 3 values?

10

4

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.)

jtbandes

Posted 2012-10-26T22:57:24.277

Reputation: 1 332

You're already returning a single object. It's a List[] – Dr. belisarius – 2012-10-26T23:00:43.443

@belisarius Yes, technically, but that's not helpful because using [[n]] makes it hard to see what's actually going on. – jtbandes – 2012-10-26T23:06:33.470

1yourF[x_]:={a[x],b[x],c[x]}; {myA, myB, myC} = yourF[x] – Dr. belisarius – 2012-10-26T23:12:25.090

1Can you give a small example? I have a hard time imagining what it is that you want to do. belisarius' answer is the obvious one, unless you intended something else. – rm -rf – 2012-10-26T23:12:36.427

Relevant (dupe?) questions: here, here and even maybe here

– István Zachar – 2012-10-27T09:09:52.017

Answers

17

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!" *)

WReach

Posted 2012-10-26T22:57:24.277

Reputation: 62 787

13

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.

Sjoerd C. de Vries

Posted 2012-10-26T22:57:24.277

Reputation: 63 549

2

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]

Rojo

Posted 2012-10-26T22:57:24.277

Reputation: 40 993

Why is this prone to leaking memory? What could cause a leak there? – jtbandes – 2012-10-26T23:45:08.727

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. – Rojo – 2012-10-26T23:46:39.983

I'm giving alternative approaches to Sjoerd's, which is probably the best. – Rojo – 2012-10-26T23:47:36.350