Group Abstract Group Abstract

Message Boards Message Boards

0
|
761 Views
|
10 Replies
|
2 Total Likes
View groups...
Share
Share this post:

A template for functions that produce 'object' functions

Hello! I find myself wanting to write functions that do a common WL thing: produce as their output an 'object' function which is then queried by sending arguments. The statistical fitting functions are an example: you get a 'fitted model' object from which you extract different outputs, e.g.,:

myFit["ParameterTable"]
myFit["AIC"]

But I can't seem to find any examples of how these 'object functions' are coded within the functions that produce them. I can think of some ways it could be done, but I'm guessing there is a standard way that WL functions do it, and I would prefer to just copy that.

Does anyone know? Is there a template?

POSTED BY: Gareth Russell
10 Replies
Posted 22 days ago

I usually use a pseudo-object-oriented paradigm in which an object has the form classHead[data]. Methods are defined for classHead as subvalues. I usually use an Association[] for data, but pre V10 this was not possible. Here are two variants. If I want to update data in my object (the mutable variant), then I need to be able to modify the association. To do this the argument has to be a symbol that refers to an association. In a package, I often create this symbol in a private subcontext, but below I do it manually in the "Global`" context. Further the argument must remain a symbol, so classHead[] must have an attribute like HoldAll. In the immutable variant, the data does not change, and the argument need not be held. In both variants, the methods can either return parts of the data or compute something from the data.

myObjImmutable // ClearAll;
myObjImmutable[data_Association]["Total"] := (* the "Total" method *)
  Total@myObjImmutable[data]["Data"];
myObjImmutable[data_Association][method_String] := (* generic data lookup *)
  Lookup[data, method, Message[myObjImmutable::notmethod]];

myObjMutable // ClearAll;
myObjMutable // Attributes = {HoldAll};
myObjMutable[data_Symbol]["Total"] /; AssociationQ@data :=  (* the "Total" method *)
  Lookup[data, "Total", data["Total"] = Total[data["Data"]]];
myObjMutable[data_Symbol][method_String] /; AssociationQ@data := (* generic data lookup *)
  Lookup[data, method, Message[myObjMutable::notmethod]];

my1 = myObjImmutable[<|"Data" -> Range[5]|>]
(*  myObjImmutable[<|"Data" -> {1, 2, 3, 4, 5}|>]  *)

my1["Total"]
(*  15  *)

my1 (* unchanged *)
(*  myObjImmutable[<|"Data" -> {1, 2, 3, 4, 5}|>]  *)

myData = <|"Data" -> Range[5]|>;
my2 = myObjMutable[myData]
(*  myObjMutable[myData]  *)

my2["Total"]
(*  15  *)

myData (* has been updated with "Total" *)
(*  <|"Data" -> {1, 2, 3, 4, 5}, "Total" -> 15|>  *)
POSTED BY: Updating Name

An Association with RuleDelayed will be evaluated only when requested:

myFit = <|"ParameterTable" :> Plot[Sin[x], {x, 0, Pi}],
  "AIC" -> somethingElse|>
POSTED BY: Gianluca Gorni

So the method employed by LinearModelFit is a hybrid: It does return a function, FittedModel, but this is an existing function defined externally (and hidden) and is returned with a bunch of info bundled into an Association as its argument:

FittedModel[<|"Type" -> "Linear", 
 "Model" -> <|"FittedParameters" -> {0.186441, 0.694915}, 
   "IndependentVariables" -> {x}, "BasisFunctions" -> {1, x}, 
   "LinearOffset" -> <|"Function" -> 0, "Values" -> 0|>|>, 
 "KeyNames" -> Automatic, "Weights" -> <|"ExampleWeights" -> 1.|>, 
 "InputData" -> {{0, 1}, {1, 0}, {3, 2}, {5, 4}}, 
 "UserDefinedDesignMatrixQ" -> False, 
 "DesignMatrix" -> {{1., 0.}, {1., 1.}, {1., 3.}, {1., 5.}}, 
 "Localizer" -> 
  Function[Null, FittedModels`LocalizedBlock[{x}, #1], {HoldAll}], 
 "Options" -> {}|>]

This bundled info is enough for FittedModel to calculate any of the requested outputs. So FittedModel must be defined using SubValues something like this:

FittedModel[params_]["PValue"]:=...
FittedModel[params_]["ParameterTable"]:=

So I think I am beginning to see how it works! Don't understand the "Localizer" association yet, but little steps...

POSTED BY: Gareth Russell
Posted 24 days ago

An association works about this way:

myFit = Association["ParameterTable" -> whatever, 
   "AIC" -> somethingElse];
myFit["ParameterTable"]
myFit["AIC"]
POSTED BY: Gianluca Gorni

Interesting. If that is how it is done, then that is simple. Does this imply that the different outputs ("whatever", "somethingElse") are always made up front? I get the impression that sometimes code is executed when requesting an output, for example to make a figure (perhaps so that the initial object is not too memory-hungry). I wonder if the right hand side of each association be an expression held unevaluated until requested.

POSTED BY: Gareth Russell
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard
Be respectful. Review our Community Guidelines to understand your role and responsibilities. Community Terms of Use