-
Notifications
You must be signed in to change notification settings - Fork 97
/
Copy pathMonadicTracing.m
388 lines (264 loc) · 14 KB
/
MonadicTracing.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
(*
Monadic tracing Mathematica package
Copyright (C) 2017 Anton Antonov
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Written by Anton Antonov,
ʇǝu˙oǝʇsod@ǝqnɔuouoʇuɐ,
Windermere, Florida, USA.
*)
(*
Mathematica is (C) Copyright 1988-2017 Wolfram Research, Inc.
Protected by copyright law and international treaties.
Unauthorized reproduction or distribution subject to severe civil
and criminal penalties.
Mathematica is a registered trademark of Wolfram Research, Inc.
*)
(* :Title: MonadicTracing *)
(* :Context: MonadicTracing` *)
(* :Author: Anton Antonov *)
(* :Date: 2017-06-13 *)
(* :Package Version: 0.1 *)
(* :Mathematica Version: *)
(* :Copyright: (c) 2017 Anton Antonov *)
(* :Keywords: *)
(* :Discussion:
## Introduction
The monadic implementations in this package allow tracking of the pipeline execution
of functions with other monads.
The primary reason for developing package was the desire to have the ability to print a tabulated trace
of code and comments using the usual monad pipeline notation. (I.e. without conversion to strings etc.)
From that perspective the main function is `TraceMonadEchoGrid`.
## Example
In this example we can see that pipeline functions of the Maybe monad are interleaved with comment strings.
Producing the grid of functions and comments happens "naturally" with the monad function `TraceMonadEchoGrid`.
data = RandomInteger[10, 15];
TraceMonadUnit[MaybeUnit[data]] ⟹ "lift to monad" ⟹
TraceMonadEchoContext ⟹
MaybeFilter[# > 3 &] ⟹ "filter current value" ⟹
MaybeEcho ⟹ "display current value" ⟹
MaybeWhen[#[[3]] > 3 &,
MaybeEchoFunction[Style[#, Red] &]] ⟹
(Maybe[#/4] &) ⟹
MaybeEcho ⟹ "display current value again" ⟹
TraceMonadEchoGrid[Grid[#, Alignment -> Left] &];
Note that :
1) the tracing is initiated by just using `TraceMonadUnit`;
2) pipeline functions (actual code) and comments are interleaved;
3) putting a comment string after a pipeline function is optional.
## Longer motivational discussion
While writing an article on monadic programming in Mathematica / WL I made a function
that tabulates code and comments. (See the definition below.)
Here is an example:
code = "
FoldList[(* reduction function *)
Plus,(* function to apply repeatedly *)
0,(* initial value *)
{1,2,3,3,100}(* arguments in repeated computations *)]";
GridOfCodeAndComments[ code, "GridFunction" -> (Panel@Grid[#, Alignment -> Left] &)]
I have several problems with the implementation of `GridOfCodeAndComments`,
the main one being that I have to give a string to the function instead of (commented) code.
For example, I would like to be able to write the tabulate code directly to `GridOfCodeAndComments`:
GridOfCodeAndComments[
FoldList[(* reduction function *)
Plus,(* function to apply repeatedly *)
0,(* initial value *)
{1, 2, 3, 3, 100}(* arguments in repeated computations *)],
"GridFunction" -> (Panel@Grid[#, Alignment -> Left] &)]
See [2] for a larger discussion on that problem.
### Definition of GridOfCodeAndComments
ClearAll[GridOfCodeAndComments]
Options[GridOfCodeAndComments] = {"GridFunction" -> (Grid[#, Alignment -> Left] &)};
GridOfCodeAndComments[code_String, opts : OptionsPattern[]] :=
Block[{grData, codeLines, commentLines, comPat, gridFunc},
gridFunc = OptionValue["GridFunction"];
If[TrueQ[gridFunc === Automatic], gridFunc = (Grid[#, Alignment -> Left] &)];
(* Split the code into lines *)
codeLines = StringSplit[code, "\n"];
(* Split each line into a {code, comment} pair *)
comPat = ("(*" ~~ (Except["*"] ..) ~~ "*)");
grData =
Map[
If[StringFreeQ[#, "("~~"*"], {#, ""},
StringCases[#, (x__ ~~ y : (comPat) ~~ z___) :> {x <> z, y}][[1]]
] &, codeLines];
(* Style the code and comments *)
grData[[All, 1]] = Map[Style[#, "Input"] &, grData[[All, 1]]];
grData[[All, 2]] =
Map[Style[#, "CommentStyle" /. Options[$FrontEnd, AutoStyleOptions][[1, 2]]] &, grData[[All, 2]]];
(* Show result *)
gridFunc[grData]
];
The problem gets simplified if we want to build code-comment grids for monad pipelines only.
The resulting `TraceMonad` code is fairly simple, and demonstrates well the "programming semicolon"
view of the binding operator in monadic programming.
## Monad code design and steps outline
Generate a State monad code for the name `TraceMonad`.
The `TraceMonad` has a context with keys "binder", "data", "commands", "comments".
The values of "commands" and "comments" are incremented with new commands and comments
given in the pipeline. Each command has a corresponding comment. (Empty comments are appended if needed.)
Here are the steps for tracing a pipeline of a monad, e.g.
Maybe[data] ⟹ MaybeEchoValue ⟹ (Maybe[#/2]&)
1) `TraceMonadUnit`, monad's unit operator takes another monad object. E.g.
TraceMonad[Maybe[data]] ⟹ MaybeEchoValue ⟹ (Maybe[#/2]&)
2) The content of `TraceMonad[__]` is stored in the context for the key "data".
3) If a call to `TraceMonadBind` is with a pipeline "function" that is a string,
then append that string to the "comments" value in the context.
4) If the pipeline function -- converted to a string -- matches `"TraceMonad"~~__`
then handle as a `TraceMonad` pipeline function.
5) Otherwise
5.1. Call the wrapped monad binding operation with the function.
5.2. Check is the result a failure.
5.2.1. If "yes", dump the current values of the context.
5.2.2. If "no", append the function to a "commands" value in the context.
6) A grid of pipeline functions and comments can be displayed with `TraceMonadEchoGrid`.
7) If the result of the pipeline is assigned to a variable, say, `res`
then the pipeline can be can be re-executed with:
Fold[MaybeBind, ReleaseHold[res[[2]]["data"]], res[[2]]["commands"]]
## References
[1] Anton Antonov, State monad code generator Mathematica package, MathematicaForPrediction at GitHub, (2017),
URL: https://github.com/antononcube/MathematicaForPrediction/blob/master/MonadicProgramming/StateMonadCodeGenerator.m
[2] Mathematica Stack Exchange discussion, "How to improve the creation of tables of code and comments", 2017,
URL: https://mathematica.stackexchange.com/q/148160/34008
## End matters
This file was created by Mathematica Plugin for IntelliJ IDEA.
Anton Antonov
Windermere, FL, USA
2017-06-13
*)
If[Length[DownValues[StateMonadCodeGenerator`GenerateStateMonadCode]] == 0,
Import["https://raw.githubusercontent.com/antononcube/MathematicaForPrediction/master/MonadicProgramming/StateMonadCodeGenerator.m"]
];
(**************************************************************)
(* Generation *)
(**************************************************************)
BeginPackage["MonadicTracing`"];
$TraceMonadFailure::usage = "Failure symbol for TraceMonad.";
TraceMonadUnit::usage = "Lifting a monad object into TraceMonad.";
TraceMonadBind::usage = "The binding function of TraceMonad.";
TraceMonadEchoGrid::usage = "Echoes a tabulation of the traced monad functions using Grid.";
TraceMonadTakeGrid::usage = "Gives a tabulation of the traced monad functions using Grid.";
Grid87::usage = "A modified version of Grid.";
Begin["`Private`"];
ClearAll[$TraceMonadFailure];
(*$TraceMonadFailure = None;*)
(**************************************************************)
(* Generation *)
(**************************************************************)
Needs["StateMonadCodeGenerator`"];
GenerateStateMonadCode["MonadicTracing`TraceMonad", "StringContextNames" -> False, "FailureSymbol" -> $TraceMonadFailure ];
(**************************************************************)
(* Infix operators *)
(**************************************************************)
(* Not needed here -- should have been already done by GenerateStateMonadCode. *)
(*DoubleLongRightArrow[x_?TraceMonadUnitQ, f_] := TraceMonadBind[x, f];*)
(**************************************************************)
(* Monad specific functions *)
(**************************************************************)
ClearAll[TraceMonadUnit];
SetAttributes[TraceMonadUnit, HoldAll];
TraceMonadUnit[x_] := TraceMonadUnit[x, DoubleLongRightArrow ];
TraceMonadUnit[x_, binder_Symbol] :=
TraceMonad[x, <|"data" -> HoldForm[x], "binder" -> binder, "commands" -> {}, "comments" -> {""}, "contextKeys" -> {{}} |>];
ClearAll[TraceMonadBind];
TraceMonadBind[___] := $TraceMonadFailure;
TraceMonadBind[TraceMonad[x_, context_], f_] :=
Block[{res = f[x, context]}, If[FreeQ[res, $TraceMonadFailure], res, $TraceMonadFailure]] /;
TrueQ[StringMatchQ[ToString[f], "TraceMonad" ~~ __]];
TraceMonadBind[TraceMonad[x_, context_], com_String] :=
Block[{res},
TraceMonad[x, Join[context, <|"comments" -> ReplacePart[context["comments"], -1 -> com]|>]]
];
TraceMonadBind[TraceMonad[x_, context_], f_] :=
Block[{res},
res = context["binder"][x, f];
Which[
(* Applying a heuristic for the failure symbol of the wrapped monad. *)
res === None || Developer`SymbolQ[res] && StringMatchQ[SymbolName[res], "$"~~___~~"Failure"~~___],
TraceMonadEchoGrid[][x, context];
res, (* This should not be $TraceMonadFailure .*)
Length[res] >= 2 && AssociationQ[ res[[2]] ],
(* Assuming State monad. *)
TraceMonad[res,
Join[context,
<|"commands" -> Append[context["commands"], f],
"comments" -> Append[context["comments"], ""],
"contextKeys" -> Append[context["contextKeys"], Keys[res[[2]]] ] |>]
],
True,
TraceMonad[res,
Join[context,
<|"commands" -> Append[context["commands"], f],
"comments" -> Append[context["comments"], ""],
"contextKeys" -> Append[context["contextKeys"], {}]|>]
]
]
];
ClearAll[Grid87];
Grid87 = Framed@
Grid[#, Alignment -> Left, Dividers -> All, FrameStyle -> Directive[Dashing[2], GrayLevel[0.87]]] &;
ClearAll[TraceMonadEchoGrid];
Options[TraceMonadEchoGrid] = { "ComplexStyling" -> True, "ContextKeys" -> False };
TraceMonadEchoGrid[x_, context_Association] := TraceMonadEchoGrid[][x, context];
TraceMonadEchoGrid[][x_, context_] := TraceMonadEchoGrid[Grid87][x, context];
TraceMonadEchoGrid[opts:OptionsPattern[] ][x_, context_] := TraceMonadEchoGrid[Grid87, opts ][x, context];
TraceMonadEchoGrid[gridFunc_, opts:OptionsPattern[] ][x_, context_] :=
Block[{res},
res = TraceMonadBind[ TraceMonad[x, context], TraceMonadTakeGrid[gridFunc, opts] ];
(* Show result *)
Echo[res];
TraceMonad[x, context]
];
ClearAll[TraceMonadTakeGrid];
Options[TraceMonadTakeGrid] = Options[TraceMonadEchoGrid];
TraceMonadTakeGrid[x_, context_Association] := TraceMonadTakeGrid[][x, context];
TraceMonadTakeGrid[][x_, context_] := TraceMonadTakeGrid[Grid87][x, context];
TraceMonadTakeGrid[opts:OptionsPattern[] ][x_, context_] := TraceMonadTakeGrid[Grid87, opts ][x, context];
TraceMonadTakeGrid[gridFunc_, opts:OptionsPattern[] ][x_, context_] :=
Block[{grData, delim, cStyleQ, cKeysQ},
cStyleQ = TrueQ[OptionValue[TraceMonadTakeGrid, "ComplexStyling"]];
cKeysQ = TrueQ[OptionValue[TraceMonadTakeGrid, "ContextKeys"]];
If[ !cKeysQ,
grData =
Transpose[{Prepend[HoldForm /@ context["commands"], context["data"]], context["comments"]}],
(* ELSE *)
grData =
Transpose[{Prepend[HoldForm /@ context["commands"], context["data"]], context["comments"], context["contextKeys"]}];
];
If[ context["binder"] === NonCommutativeMultiply, delim = "**", delim = "\[DoubleLongRightArrow]"];
delim = "\[ThinSpace]" <> delim;
(* Style the code and comments *)
If[ cStyleQ,
(* Using RuleCondition because the pipeline functions are kept in HoldForm. *)
(* Note that RuleCondition is undocumented. *)
(* The alternative is to use /. (z_String :> With[{eval = "\"" <> z <> "\""}, eval /; True]) *)
grData[[All, 1]] =
Map[
Row[{" ",
Style[ # /. (z_String :> RuleCondition[("\"" <> z <> "\"")]), "Input"],
Style[delim, "Input"]}] &,
grData[[All, 1]]],
(* ELSE *)
grData[[All, 1]] = Map[Row[{" ", Style[#, "Input"], Style[delim, "Input"]}] &, grData[[All, 1]]];
];
grData[[1, 1]] = Row[Rest@grData[[1, 1, 1]]];
grData[[-1, 1]] = Row[Most@grData[[-1, 1, 1]]];
grData[[All, 2]] =
Map[Style[#, "CommentStyle" /. Options[$FrontEnd, AutoStyleOptions][[1, 2]]] &, grData[[All, 2]]];
If[Dimensions[grData][[2]] == 3, (*i.e. cKeysQ *)
grData[[All, 3]] =
Map[Style[#, "CommentStyle" /. Options[$FrontEnd, AutoStyleOptions][[1, 2]]] &, grData[[All, 3]]];
];
(* Show result *)
gridFunc[grData]
];
End[]; (* `Private` *)
EndPackage[]