Message Boards Message Boards

10
|
7413 Views
|
23 Replies
|
25 Total Likes
View groups...
Share
Share this post:

[WSG23] Daily Study Group: Writing a Wolfram Language Function

Posted 1 year ago

A Wolfram U Daily Study Group on "Writing a Wolfram Language Function" begins on August 21, 2023.

Join me and a group of fellow learners in an exploration of what it takes to make a "good" function in the Wolfram Language. We'll begin with standard function structure and then cover arguments, patterns, options and how to extend function capability with overloading, recursion and iteration, memorization and up-values. Later sessions will discuss error handling and extra tips.

Sessions include short lessons, poll questions to review key concepts, practice problems and live Q&A. A certificate of program completion will be awarded to participants who attend online sessions and pass a quiz.

Some prior Wolfram Language experience or knowledge is recommended for this Study Group.

REGISTER HERE

Wolfram U Banner

POSTED BY: Daniel Robinson
23 Replies

Wolfram U is looking forward to starting this Daily Study Group on Monday, along with @Daniel Robinson. Seats are still available! Sign up here.

POSTED BY: Jamie Peterson
Posted 1 year ago

Excellent first session!

POSTED BY: Gregory Lypny

Thanks for first lecture. I am interested in learning more about sequences. Your introduction is difficult for me to apply to a sequence of mathematical operations. I want to write a function to accept pullback metrics and compute the sequence as a parameter varies. Any guidance/suggestion would be appreciated. Larry

POSTED BY: laurence bloxham

I came across the following expression in a form I had not seen before:

g[h_, b_][x_] := 
 f[b] + (DifferenceQuotient[f[x], {x, h}] /. (x -> b)) (x - b)

What is going on with the [x_] in s separate bracket outside the g[h,b] on the left side of the := ? It looks a little like the Function[parameters,body][x] format but it's not that...

Carl

POSTED BY: Carl Hahn

Hi Carl, this is covered in the third notebook in this series (Extending Capability) in the optional section, Operator Forms and Subvalues. Although the usual syntax for a function might be f[x_, y_] := g[x, y], it's perfectly acceptible to write f[x_][y_] := g[x, y], or even f[x_][y_][z_] := g[x, y, z] (and so on). These are known as operator forms, and are valid because the code on the left-hand side of the SetDelayed symbol (:=) is just a pattern—it can be more or less whatever you like.

For a concrete example, consider the Select function. It is normally used like this: Select[Range[10], EvenQ]. However, it can also be used as an operator like this: Select[EvenQ][Range[10]].

POSTED BY: Daniel Robinson

Any reference of how to order/place the functions in a mathematica packages , context etc, will be useful.

I want to construct a function that inputs an element of the symmetric group of n objects and returns the conjugacy class of this element. The inputs are thus the positive integer "n" and something like "Cycles[{{1,3,2}}}]" so my starting point is

getClass[n\_, cycle\_ ]

I know that in practice I tend:

  1. to forget to to include the argument n,
  2. I input an incorrect value of n (a cycle like Cycles[{{1,3,2}}}] permutes objects 1,2,3 so n>= 3 at least
  3. I tend to mess up the ordering of the argument.

So I want to build getClass[n_,cycle_] so

  • a) getClass[somecycle] returns a message like "order missing",
  • b) getClass[q,somecycle] returns "order is too low" if q is incorrect
  • c) evaluates getClass[somecycle,n] equally well as getClass[n,somecycle]

I can handle c) by using PermutationCyclesQ, which will tell me if a particular argument is actually a cycle, and thus test if the first argument is a cycle or not.

I'm not sure how to proceed to handle the rest or make it work smoothly. The current working version of getClass is:

getClass[p_, cycle_] := Module[{listofcycles, shortlistofcycles, padding},
  listofcycles = Flatten[List @@ cycle, 1];
  shortlistofcycles = Table[Length[listofcycles[[k]]], {k, 1, Length[listofcycles]}];
  padding = p - Sum[shortlistofcycles[[k]], {k, 1, Length[shortlistofcycles]}];
  If[padding != 0, AppendTo[shortlistofcycles, Table[1, {q, 1, padding}]]];
  Reverse[Sort[Flatten[shortlistofcycles]]]]

Thus for instance getClass[3,Cycles[{{1,3,2}}}] returns {3}

getClass[4,Cycles[{{1,2},{3,4}}] returns {2,2}

POSTED BY: Hubert de Guise

Thanks! That's interesting. Just curious: Where in the vast warehouse of Documentation files would I find this little factoid?

Carl

POSTED BY: Carl Hahn

Hi Hubert, your questions can be answered with overloading – something we will be covering in today's session (Extending Capability).

To solve (c), you can use OrderlessPatternSequence, and to solve (b), you can use Condition (/;). Both of these were covered in the first session (The Basics). To solve (a), you can use Message, which will be covered in the fourth session (Error Handling).

Here's some code to get you started:

ClearAll[getClass];

getClass::orderTooLow = "The order you have provided (`1`) is too low. It must be an integer greater than 2.";
getClass::badArgs = "Bad arguments. getClass takes exactly 2 arguments: an 'order' and a 'cycle'. The order must be an integer greater than 2 and the cycle must be a valid permutation cycle.";

(* Main definition: *)
getClass[
    OrderlessPatternSequence[
     order_?IntegerQ,
     cycle_?PermutationCyclesQ
     ]
    ] /; order > 2 := (* Your function *);

(* Order provided is too low: *)
getClass[
   OrderlessPatternSequence[
    order_?IntegerQ,
    cycle_?PermutationCyclesQ
    ]
   ] := (
   Message[getClass::orderTooLow, order];
   $Failed
   );

(* Arguments provided are either invalid, too numerous or too few: *)
getClass[badArgs___] := (
   Message[getClass::badArgs];
   $Failed
   );

You can add more definitions / error messages to handle more cases.

P.S. – you seem to have a lonely p in your function definition.

POSTED BY: Daniel Robinson

I just did a search myself in the documentation and couldn't find anything particularly helpful… (no tutorial that explains how to create your own operator forms, anyway).

However, in case it's of interest to you, I did find some code on StackExchange that claims to list all of the bulit-in functions that have operator forms:

file = FileNameJoin[{$InstallationDirectory, "SystemFiles", "Kernel", 
        "TextResources", $Language, "Usage.m"}];

usage = Import[file, "HeldExpressions"];

usage //
  Cases[
    HoldPattern[
      _[_[sym_Symbol, "usage"] = _String?(StringContainsQ["operator form"])]
    ] :> sym
  ]
POSTED BY: Daniel Robinson

Thanks. I'm looking forward to the details. The lonely p is because the working code uses p rather than n, and I didn't catch the typo before submitting.

POSTED BY: Hubert de Guise

Hi Francisco, these workflows explain the details:

For an example, take a look at the ImageStyler.wl file (which is referenced in the fifth notebook, Useful Tips).

POSTED BY: Daniel Robinson

Hi Laurence, can you give me some more details? It would be best if you could write down a simple example that includes the setup you want to start with and the result you want to end with. I may then be able to help with the steps in between.

POSTED BY: Daniel Robinson

I decided to make a fun example to demonstrate some of the things we have learned in this series.

The function below tests the Collatz conjecture, calculating a sequence of expressions that—it is conjectured—will always end in 1. Together with the interactive tool that follows it, this function uses:

  • An argument (see notebooks 1 and 2)
  • A PatternTest, to make sure the argument is always an integer greater than 1 (see notebook 1)
  • Recursion (see notebook 3)
  • Overloading, to ensure the recursion stops (see notebook 3)
  • Sow and Reap, to collect intermediary values (see notebooks 4 and 5)
  • A Manipulate, to allow one to vary the starting value of the sequence (see notebook 5)
  • A Module, to localise a variable (see notebook 5)

Function:

ClearAll[collatz];
collatz[n_?(IntegerQ[#] && # > 0 &)] := If[EvenQ[Sow@n], collatz[n/2], collatz[3*n + 1]];
collatz[1] := Sow@1;

Interactive tool:

Manipulate[
 Module[
  {data = Table[Reap[collatz[n]][[2, 1]], {n, 2, maxN, 1}]},
  Row[
   {
    ListLinePlot[
     data,
     PlotRange -> {{0, 120}, {0, 10000}},
     Frame -> {{True, False}, {True, False}},
     FrameLabel -> {"Sequence length", "Sequence values"},
     PlotHighlighting -> None,
     PlotStyle -> {
       Sequence @@ 
        ConstantArray[
         Directive[GrayLevel[0.5, 0.5], Thickness[0.001]], maxN - 2],
       Directive[Purple, Thickness[0.003]]
       },
     ImageSize -> 600
    ],
    Framed[
     Pane[
      Row[Last[data], ","],
      ImageSize -> {200, 300},
      Scrollbars -> {False, True}
      ],
     RoundingRadius -> 3
     ]
    },
   BaseStyle -> "Text"
   ]
  ],
 {{maxN, 3, "n"}, 3, 100, 1, Appearance -> "Open"},
 ContinuousAction -> True
]

Enjoy! :)

POSTED BY: Daniel Robinson

In the last session, I mentioned that Alternatives (a pattern object) and Or (a function) are not interchangeable. Here's an example to demonstrate how they differ.

Consider trying to extract all of the cases from a list that are either 2 or True. Using Alternatives (|) gives the correct result:

Cases[{1, 2, 3, True, 4}, 2 | True]
(* {2, True} *)

However, using Or (||) gives an incorrect result:

Cases[{1, 2, 3, True, 4}, 2 || True]
(* True *)

This is because Or immediately evaluates if any of its arguments resolve to True or False. In this case, 2||True evaluates to True, so Cases only picks out the items that match True.

POSTED BY: Daniel Robinson

Wow! Note that it needs 13.3 to evaluate PlotHighlighting -> None, If you delete that statement it works with 13.2

POSTED BY: Carl Hahn

In the lecture, you show the full local names on a few occasions. How is that done? For instance x=1; y=3, Plot{ x, {x,0,y}; x might show as x=1; y=3, Plot{ x$12345, {x$12345,0,y}; x clearly showing the localize, temporary definition of the x inside the Plot... How do you switch between the two views of the cell?

POSTED BY: Paul Erickson

Hi Paul, is that Plot example one that you saw in the lecture? I'm having trouble finding it in the notebook and I'm not sure how to accomplish something like that off the top of my head (or even after some searching). I know that you can do a more basic thing like Module[{x},x] to see the local name of x inside a module, but as far as seeing the names for automatically localized variables, I don't recall seeing that in the presentation and can't find it in the notebook. (Granted, I look at a lot of presentations and notebooks so it's quite possible that I've missed it, in which case a pointed would be helpful!)

POSTED BY: Arben Kalziqi

No, mostly made up, it was in the live presentation, but I don't think shows in the notebook. I assume it a front end maybe cell type or something. The presenter switch back and forth but I didn't hear if he indicated how. I was thinking it was a plot, but not sure - just some expression that had a mixture of localized and global in the expression.

POSTED BY: Paul Erickson

Gotcha! The only cell-view switch I know about is cmd+shift+E, which shows the full cell expression, but—by default, at least—it doesn't show full context information. Dan is currently on holiday, but he should be able to provide you with a more concrete answer once he returns on Monday.

POSTED BY: Arben Kalziqi

Regarding Mapping of operators: How do I map the derivative operator onto a list of expressions?

For example, instead of doing

{D[-Sqrt[4 - x^2], x], D[Sqrt[4 - x^2], x]}

I'd like to have the list of expressions {-Sqrt[4 - x^2], Sqrt[4 - x^2]} and map the Derivative operator onto the list directly. But there is this pesky 'x' in the definition of the operator D[f,x]. How is that done?

POSTED BY: Carl Hahn

Does,

D[#, x] & /@ {-Sqrt[4 - x^2], Sqrt[4 - x^2]}

work for you? It of course relies on the variable of integration being x, so?? So, if you need to change that,

D[#[[1]], #[[2]]] & /@ {{-Sqrt[4 - x^2], x}, {Sqrt[4 - y^2], y}}

seems to work.

POSTED BY: Paul Erickson

Doh! Thanks Paul! I don't know why I did not think of the first option right away, but I didn't; and it does work. Exactly as it should; and exactly what I was looking for. Come think of it, it's a perfect little example of how the syntax of the Wolfram language works. So thanks for the tip.

Carl

POSTED BY: Carl Hahn
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract