diff --git a/examples/ffp/Functional Programming is Free.ipynb b/examples/ffp/Functional Programming is Free.ipynb
index c7b1e78..7623a3a 100644
--- a/examples/ffp/Functional Programming is Free.ipynb
+++ b/examples/ffp/Functional Programming is Free.ipynb
@@ -15,7 +15,7 @@
"\n",
" Kent Pitman informed me that the esoteric form `#'(lambda ...)` is no longer necessary in ANSI Common Lisp; the text is updated to the simpler form `(lambda ...)`.\n",
"\n",
- "# History of the subject.\n",
+ "# History of the subject\n",
"\n",
"I started the subject of *Effective Homology* in 1984, trying first to implement the Edgar Brown's \"algorithm\" [[4]](#browne1) computing the homotopy groups of the finite simply connected simplicial sets. This idea was quickly understood as ridiculous; thirty years later, there remains difficult to hope for an actual implementation of this algorithm, much too complex.\n",
"\n",
@@ -25,7 +25,7 @@
"\n",
"In the years 1995-8, Xavier Dousson's thesis was devoted to the calculation of homotopy groups; the same tools, *functional programming* combined with the *homological perturbation theorem*, were successfully applied to the so-called Whitehead and Postnikov towers, the most \"elementary\" methods to compute the homotopy groups. Leading to the Kenzo program [[6]](#dsrbss). In particular, Ana Romero extended the scope of the program to the classifying spaces of non-commutative groups, detecting an error [[8]](#romrun) in a published paper, for a homotopy group of a relatively contorted simplicial set.\n",
"\n",
- "These programs, once the basics of algebraic topology are understood, are elementary and certainly not the best possible: it is well known the Adams pectral sequence and all its derivatives are central in the subject and efficient methods *must* use them. Nevertheless, the experience of the Kenzo program, where everything is so elementary, made tempting a\n",
+ "These programs, once the basics of algebraic topology are understood, are elementary and certainly not the best possible: it is well known the Adams spectral sequence and all its derivatives are central in the subject and efficient methods *must* use them. Nevertheless, the experience of the Kenzo program, where everything is so elementary, made tempting a\n",
"non-negligible theoretical result: If a dimension $n$ is fixed, our algorithm computing the homotopy group $\\pi_n X$ of a simply connected simplicial set $X$ has a polynomial complexity *with respect to* $X$; of course not with respect to $n$, as it is well known since David Anick's paper [[1]](#anick), unless $P = NP$.\n",
"\n",
"This polynomiality result is so natural when you know the details of the Kenzo program, and of course the authors of the corresponding 16000 lines of Lisp code know these details, that I often stated this result without any other justification. The authors of [[5]](#ckmvw), after a few years of interesting common work with myself on problems of constructive algebraic topology, suggested to write down a common paper on this polynomiality result. Why not.\n",
@@ -40,7 +40,7 @@
"\n",
"It so happened the authors of [[5]](#ckmvw) in 2012 started preparing pages and pages about our problem of complexity. I soon warned them: sorry, your draft totally forgets the component *functional programming*; without the minimal indications about this matter, the paper is not correct. For example, a Coq proof of this polynomiality result could not avoid studying the cost of this functional programming. It was proposed I cosign the paper [[5]](#ckmvw), I answered it is not possible to cosign a paper you would reject as referee.\n",
"\n",
- "Every mathematican knows how it is difficult in conversations with non-mathematicians to explain what mathematical research is, and also why it is useful~(*!*). Sometimes the same when a computer scientist tries to explain to \"pure\" mathematicians the exact nature of a computer program; and in particular where the difficulties are hidden. Modern programming, in particular functional programming with the so-called *functional programming languages*, seems so natural for a pure mathematician, so close to his habits that he cannot believe some difficulty in complexity is hidden there. Finally, it is not so hard to overcome this difficulty, but a few prerequisites cannot be avoided. It is the role of this text to clarify this point.\n",
+ "Every mathematican knows how it is difficult in conversations with non-mathematicians to explain what mathematical research is, and also why it is useful~(!). Sometimes the same when a computer scientist tries to explain to \"pure\" mathematicians the exact nature of a computer program; and in particular where the difficulties are hidden. Modern programming, in particular functional programming with the so-called *functional programming languages*, seems so natural for a pure mathematician, so close to his habits that he cannot believe some difficulty in complexity is hidden there. Finally, it is not so hard to overcome this difficulty, but a few prerequisites cannot be avoided. It is the role of this text to clarify this point.\n",
"\n",
"# Functional Programming.\n",
"\n",
@@ -92,8 +92,7 @@
"\\stackrel{w_n}{\\longmapsto} X_n \\stackrel{H_n}{\\longmapsto} \\pi_n X\n",
"$$\n",
"\n",
- "The stage $X_i$ of the tower is the inital space $X$ where the first homotopy groups $\\pi_j X$ have been \"killed\" for $j < i$, its homotopy starts at the index $i$. The Hurewicz theorem therefore allows the program to compute $H_i X_i = \\pi_i X_i = \\pi_i X$, and an appropriate fibration using this group then produces the next stage $X_{i+1}$. The last step $H_n =\n",
- "\\pi_n$ is again an application of the Hurewicz theorem. The program starts with $X = X_2$ simply connected, without any homotopy in dimensions 0 and 1.\n",
+ "The stage $X_i$ of the tower is the inital space $X$ where the first homotopy groups $\\pi_j X$ have been \"killed\" for $j < i$, its homotopy starts at the index $i$. The Hurewicz theorem therefore allows the program to compute $H_i X_i = \\pi_i X_i = \\pi_i X$, and an appropriate fibration using this group then produces the next stage $X_{i+1}$. The last step $H_n: X_n \\mapsto H_n X_n = \\pi_n X_n = \\pi_n X$ is again an application of the Hurewicz theorem. The program starts with $X = X_2$ simply connected, without any homotopy in dimensions 0 and 1.\n",
"\n",
"An algorithm is a functional object \"*input* $\\mapsto$ *output*\" and the computer scientists know for a long time the appropriate *types* of the input and the output are to be carefully defined. I invite a reader sufficiently patient to reach this part of this text to analyse the article [[5]](#ckmvw) with the Adobe searcher for the word \"type\": the reader will see this matter of the (computer) *types* of the used objects is never considered. The reader must probably understand the definition of these types is a minor subject: the slave who some day maybe will implement the described algorithm should be able to find out these details.\n",
"\n",
@@ -116,8 +115,7 @@
"\\partial_{K(\\mathbb{Z},1)}((4, (-19, -14, 8, 4)), 2) = (3, (-19, -6, 4))\n",
"$$\n",
"\n",
- "Let $X_{i-1}$ be a possible $(i-1)$-th stage of a Whitehead tower, necessarily implemented as a *simplicial set with effective homology*. This object contains a large collection of functional objects and in particular the face operator $\\partial_{X_{i-1}}$. The step $w_i$ of the program must then construct $X_i := w_i(X_{i-1})$, the output $X_i$ being again a simplicial set with effective homology, this object containing in particular a similar collection of functional objects, in particular the face operator $\\partial_{X_i}$; the construction of $\\partial_{X_i}$ depends of course on the\n",
- "object $\\partial_{X_{i-1}}$, but also of all the other components of $X_i$, in particular on the other functional components, according to a process computationnally not simple: a collection of various Eilenberg-MacLane spaces must in particular be constructed also as simplicial sets with effective homology, each Eilenberg-MacLane space being again the output of a functional subprogram.\n",
+ "Let $X_{i-1}$ be a possible $(i-1)$-th stage of a Whitehead tower, necessarily implemented as a *simplicial set with effective homology*. This object contains a large collection of functional objects and in particular the face operator $\\partial_{X_{i-1}}$. The step $w_i$ of the program must then construct $X_i := w_i(X_{i-1})$, the output $X_i$ being again a simplicial set with effective homology, this object containing in particular a similar collection of functional objects, in particular the face operator $\\partial_{X_i}$; the construction of $\\partial_{X_i}$ depends of course on the object $\\partial_{X_{i-1}}$, but also of all the other components of $X_i$, in particular on the other functional components, according to a process computationnally not simple: a collection of various Eilenberg-MacLane spaces must in particular be constructed also as simplicial sets with effective homology, each Eilenberg-MacLane space being again the output of a functional subprogram.\n",
"\n",
"The paper [[5]](#ckmvw) does not consider at all the cost of the generation of all these functional objects. It is a pity:\n",
"\n",
@@ -165,7 +163,7 @@
{
"data": {
"text/plain": [
- "#"
+ "#"
]
},
"execution_count": 2,
@@ -225,9 +223,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "The first statement defines a function labeled \"`f1`\" mathematically defined as $f_1: n \\mapsto 3n$; read: assign to the symbol `f1` the function defined by the $\\lambda$-expression $\\lambda n\\,(n\\,*\\,3)$; the same for $f_2: n \\mapsto n+8$. The last statement constructs a list, its first element is $f_1(5)$, the second one is $f_2(5)$. Notice the Lisp notation `(funcall f1 5)` (read: call the function located by the symbol `f1` for the input `5`) for the usual mathematical notation $f_1(5)$.\n",
+ "The first statement defines a function labeled \"`f1`\" mathematically defined as $f_1: n \\mapsto 3n$; read: assign to the symbol `f1` the function defined by the $\\lambda$-expression $\\lambda n\\,(n\\,*\\,3)$; the same for $f_2: n \\mapsto n+8$. The last statement constructs a list, its first element is $f_1(5)$, the second one is $f_2(5)$. Notice the Lisp notation `(funcall f1 5)` (read: call the function located by the symbol `f1` for the input `5`) for the usual mathematical notation $f_1(5)$.`; \n",
"\n",
- "The reader may wonder why the returned functions are \"unnamed\". In this organisation the symbol `f1` \"sees\" the functional object, while the functional object does not see at all the symbol `f1`. In particular, different symbols can see the same function. More concretely, the symbol `f1` has a pointer toward the functional object just created, and on the contrary, the functional object has no pointer toward `f1`. A mysterious string such as `100468C65B` is the hexadecimal machine address of the functional object.\n",
+ "The second statement looks for the *value* of the symbol `f1`, just defined; this value is a *function*, the external display being rather poor, mainly the machine hexa-address where the object is stored. The internal object is too technical, a collection of machine statements, too technical to be usefully displayed there.\n",
"\n",
"If we want now to work with $f_3 := f_2 \\circ f_1$, we can use the Lisp statement:"
]
@@ -329,7 +327,7 @@
}
],
"source": [
- "(defvar f4 (funcall compose f2 f1)) "
+ "(defvar f4 (funcall compose f2 f1))"
]
},
{
@@ -414,7 +412,7 @@
"\n",
"A program can also *invoke* a functional object, this functional object is then called a *callee*, while the program at the origin of this call is the *caller*. The callee must then execute some task, and when this task is finished, the *control* is given back to the caller. The scheme below illustrates such an organization; each rectangle contains some machine instruction, and the arrows show the path followed by the *control*. Such a call consists in two jumps, one starting the callee, the other one leaving it.\n",
"\n",
- "\n",
+ "
\n",
"\n",
"In fact the caller, before invoking the callee, pushes onto the *stack* the *return address*; in this way, when the callee has finished its task, it can read this return address to give back the control to the caller, at the next point where the caller must continue its work; the functional object can so be called from anywhere in the program, avoiding to repeat its code in the memory if this functional object is called several times, quite frequent.\n",
"\n",
@@ -546,7 +544,7 @@
{
"data": {
"text/plain": [
- "MUL7"
+ "#"
]
},
"execution_count": 16,
@@ -555,7 +553,7 @@
}
],
"source": [
- "(defvar mul7 (funcall genmul 7))"
+ "mul5"
]
},
{
@@ -568,7 +566,7 @@
{
"data": {
"text/plain": [
- "45"
+ "MUL7"
]
},
"execution_count": 17,
@@ -577,7 +575,7 @@
}
],
"source": [
- "(funcall mul5 9)"
+ "(defvar mul7 (funcall genmul 7))"
]
},
{
@@ -590,7 +588,7 @@
{
"data": {
"text/plain": [
- "63"
+ "#"
]
},
"execution_count": 18,
@@ -598,6 +596,50 @@
"output_type": "execute_result"
}
],
+ "source": [
+ "mul7"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "45"
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(funcall mul5 9)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "63"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"(funcall mul7 9)"
]
@@ -677,7 +719,7 @@
},
{
"cell_type": "code",
- "execution_count": 19,
+ "execution_count": 21,
"metadata": {
"collapsed": false
},
@@ -688,7 +730,7 @@
"MAKE_PACKAGE"
]
},
- "execution_count": 19,
+ "execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
@@ -701,6 +743,28 @@
" (lambda () (setq m (1+ m))))))"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "#"
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "make_package"
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
@@ -710,7 +774,7 @@
},
{
"cell_type": "code",
- "execution_count": 20,
+ "execution_count": 23,
"metadata": {
"collapsed": false
},
@@ -718,10 +782,10 @@
{
"data": {
"text/plain": [
- "#"
+ "#"
]
},
- "execution_count": 20,
+ "execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
@@ -742,7 +806,7 @@
},
{
"cell_type": "code",
- "execution_count": 21,
+ "execution_count": 24,
"metadata": {
"collapsed": false
},
@@ -750,10 +814,10 @@
{
"data": {
"text/plain": [
- "#"
+ "#"
]
},
- "execution_count": 21,
+ "execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
@@ -774,7 +838,7 @@
},
{
"cell_type": "code",
- "execution_count": 22,
+ "execution_count": 25,
"metadata": {
"collapsed": false
},
@@ -785,79 +849,13 @@
"-25"
]
},
- "execution_count": 22,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(funcall mul_1 -5)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 23,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "6"
- ]
- },
- "execution_count": 23,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(funcall inc_1)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 24,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "-30"
- ]
- },
- "execution_count": 24,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(funcall mul_1 -5)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 25,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "-90"
- ]
- },
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
- "(funcall mul_2 -5)"
+ "(funcall mul_1 -5)"
]
},
{
@@ -870,7 +868,7 @@
{
"data": {
"text/plain": [
- "19"
+ "6"
]
},
"execution_count": 26,
@@ -879,7 +877,7 @@
}
],
"source": [
- "(funcall inc_2)"
+ "(funcall inc_1)"
]
},
{
@@ -892,7 +890,7 @@
{
"data": {
"text/plain": [
- "-95"
+ "-30"
]
},
"execution_count": 27,
@@ -901,7 +899,7 @@
}
],
"source": [
- "(funcall mul_2 -5)"
+ "(funcall mul_1 -5)"
]
},
{
@@ -914,7 +912,7 @@
{
"data": {
"text/plain": [
- "7"
+ "-90"
]
},
"execution_count": 28,
@@ -923,7 +921,7 @@
}
],
"source": [
- "(funcall inc_1)"
+ "(funcall mul_2 -5)"
]
},
{
@@ -936,7 +934,7 @@
{
"data": {
"text/plain": [
- "-35"
+ "19"
]
},
"execution_count": 29,
@@ -944,6 +942,72 @@
"output_type": "execute_result"
}
],
+ "source": [
+ "(funcall inc_2)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "-95"
+ ]
+ },
+ "execution_count": 30,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(funcall mul_2 -5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "7"
+ ]
+ },
+ "execution_count": 31,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(funcall inc_1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "-35"
+ ]
+ },
+ "execution_count": 32,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"(funcall mul_1 -5)"
]
@@ -974,93 +1038,9 @@
"\n",
"When a functional object is to be generated, Lisp examines the state of the environment where this generation is to be done. If only the global environment is visible, an ordinary functional object is generated; on the contrary, if the generation happens inside some local environment, then a closure is generated.\n",
"\n",
- "The simplest way in Lisp to generate a local environment uses a `let` instruction.\n",
+ "However, some \"clever\" Lisp systems are able to prove the *local* nature of some variable does not matter, for it can be proved this variable cannot be modified, so being in fact nothing but a constant, which does not need the sophisticated structure of a closure. The Sbcl environment underlying the present CL-Jupyter notebook is of this sort. See the example later.\n",
"\n",
- "> The use of ``DEFVAR`` in the next cell would make it impossible to obtain a closure in the definition of ``ADDA2``. We use ``SETQ`` instead and expect to see a compiler warning."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 30,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "SIMPLE-WARNING: \n",
- "undefined variable: A\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "25"
- ]
- },
- "execution_count": 30,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(setq a 25)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 31,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "510"
- ]
- },
- "execution_count": 31,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(let ((a 255))\n",
- " (+ a a))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 32,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "50"
- ]
- },
- "execution_count": 32,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(+ a a)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "In this short session above, two uses of the variable `a` coexist. The global environment defines a binding \"`a` $\\mapsto$ `25`\", while, when the `(let ...)` instruction is run, a local environment with the binding \"`a` $\\mapsto$ `255`\" is defined. In this local environment, adding `a` and `a` produces 510, but this done, this local environment is definitively dead, so that in the next statement, the same code `(+ a a)` returns this time 50: the global environment was let unchanged and is reused as such.\n",
- "\n",
- "Combining a `(let ...)` statement with generation of functional objects shows when the interpreter or the compiler decides to generate an ordinary functional object or a closure."
+ "The simplest way in Lisp to generate a local environment uses a `let` instruction."
]
},
{
@@ -1073,7 +1053,7 @@
{
"data": {
"text/plain": [
- "ADDA1"
+ "A1"
]
},
"execution_count": 33,
@@ -1082,9 +1062,7 @@
}
],
"source": [
- "(defvar adda1 (lambda (b)\n",
- " (declare (special a))\n",
- " (+ a b)))"
+ "(defvar a1 25)"
]
},
{
@@ -1097,7 +1075,7 @@
{
"data": {
"text/plain": [
- "ADDA2"
+ "25"
]
},
"execution_count": 34,
@@ -1106,10 +1084,7 @@
}
],
"source": [
- "(defvar adda2\n",
- " (let ((a 33))\n",
- " (lambda (b)\n",
- " (+ a b))))"
+ "a1"
]
},
{
@@ -1122,7 +1097,7 @@
{
"data": {
"text/plain": [
- "91"
+ "510"
]
},
"execution_count": 35,
@@ -1131,7 +1106,8 @@
}
],
"source": [
- "(funcall adda1 66)"
+ "(let ((a1 255))\n",
+ " (+ a1 a1))"
]
},
{
@@ -1144,7 +1120,7 @@
{
"data": {
"text/plain": [
- "99"
+ "50"
]
},
"execution_count": 36,
@@ -1153,16 +1129,21 @@
}
],
"source": [
- "(funcall adda2 66)"
+ "(+ a1 a1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "The functional object assigned to `adda1` is generated when only the *global* environment is visible, so that this object has only the status of (ordinary) function. On the contrary, the object to be assigned to `adda2` is generated when the *local* environment with the local binding \"`a` $\\mapsto$ `33`\" is active, so that this requires a closure saving this environment for later uses.\n",
- "\n",
- "This effect is more visible when the global and the local environment are both used in a closure."
+ "In this short session above, two uses of the variable `a1` coexist. The global environment defines a binding \"`a1` $\\mapsto$ `25`\", while, when the `(let ...)` instruction is run, a local environment with the binding \"`a1` $\\mapsto$ `255`\" is defined. In this local environment, adding `a1` and `a1` produces 510, but this done, this local environment is definitively dead, so that in the next statement, the same code `(+ a1 a1)` returns this time 50: the global environment was let unchanged and is reused as such."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In the next statement, the functional object to be created uses only the argument `b`, redefined at each call, and the global variable `a`. No closure is necessary, a simple function is returned."
]
},
{
@@ -1175,7 +1156,7 @@
{
"data": {
"text/plain": [
- "11"
+ "A2"
]
},
"execution_count": 37,
@@ -1184,14 +1165,7 @@
}
],
"source": [
- "(setf (symbol-value 'a) 11)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "> The use of ``DEFVAR`` in the definition of ``B``, in the the next cell, would prevent to obtain a closure in the definition of ``ADD_A_B``. We use ``SETQ`` instead and expect to see a compiler warning."
+ "(defvar a2 222)"
]
},
{
@@ -1201,18 +1175,10 @@
"collapsed": false
},
"outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "SIMPLE-WARNING: \n",
- "undefined variable: B\n"
- ]
- },
{
"data": {
"text/plain": [
- "22"
+ "ADDA2"
]
},
"execution_count": 38,
@@ -1221,7 +1187,8 @@
}
],
"source": [
- "(setq b 22)"
+ "(defvar adda2 (lambda (b)\n",
+ " (+ a2 b)))"
]
},
{
@@ -1234,7 +1201,7 @@
{
"data": {
"text/plain": [
- "ADD_A_B"
+ "#"
]
},
"execution_count": 39,
@@ -1243,10 +1210,7 @@
}
],
"source": [
- "(defvar add_a_b\n",
- " (let ((b 222))\n",
- " (declare (special a))\n",
- " (lambda (n) (+ a b n))))"
+ "adda2"
]
},
{
@@ -1259,7 +1223,7 @@
{
"data": {
"text/plain": [
- "266"
+ "232"
]
},
"execution_count": 40,
@@ -1268,14 +1232,14 @@
}
],
"source": [
- "(funcall add_a_b 33)"
+ "(funcall adda2 10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "> The use of ``SETQ`` in the next two cells would trigger compiler warnings. We use the slighlty more verbose ``(SETF (SYMBOL-VALUE ...))`` instead."
+ "In the next exemple, the *local* variable `a3` is used, but (this) Lisp (and you?) can prove the value of this variable cannot be modified. This pseudo-variable is therefore processed (by this Lisp) as a constant. No closure is necessary."
]
},
{
@@ -1288,7 +1252,7 @@
{
"data": {
"text/plain": [
- "110"
+ "ADDA3"
]
},
"execution_count": 41,
@@ -1297,7 +1261,10 @@
}
],
"source": [
- "(setf (symbol-value 'a) 110)"
+ "(defvar adda3\n",
+ " (let ((a3 333))\n",
+ " (lambda (b)\n",
+ " (+ a3 b))))"
]
},
{
@@ -1310,7 +1277,7 @@
{
"data": {
"text/plain": [
- "2"
+ "#"
]
},
"execution_count": 42,
@@ -1319,7 +1286,7 @@
}
],
"source": [
- "(setf (symbol-value 'b) 2)"
+ "adda3"
]
},
{
@@ -1332,7 +1299,7 @@
{
"data": {
"text/plain": [
- "365"
+ "343"
]
},
"execution_count": 43,
@@ -1341,24 +1308,186 @@
}
],
"source": [
- "(funcall add_a_b 33)"
+ "(funcall adda3 10)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In the next example, a little more sophisticated, we assign to the variable `adda4` a *list* of two functional objects. The first one adds the value of the *local* variable `a4` to the argument, the second one, without any argument, adds 1 to this variable. Then the value of `adda4` is made of two closures, and the next statements are the obvious tests. An example already presented in a slightly different way."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "ADDA4"
+ ]
+ },
+ "execution_count": 44,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(defvar adda4\n",
+ " (let ((a4 444))\n",
+ " (list (lambda (n) (+ a4 n))\n",
+ " (lambda () (setf a4 (+ a4 1))))))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(# #)"
+ ]
+ },
+ "execution_count": 45,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "adda4"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "454"
+ ]
+ },
+ "execution_count": 46,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(funcall (first adda4) 10)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "445"
+ ]
+ },
+ "execution_count": 47,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(funcall (second adda4))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 48,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "455"
+ ]
+ },
+ "execution_count": 48,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(funcall (first adda4) 10)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Notice the (internal) value of `a4` is now unreachable. Except in this very particular case, by the dirty trick:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "445"
+ ]
+ },
+ "execution_count": 49,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(funcall (first adda4) 0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The fact the *value* of the local variable `a4` can be modified by the second functional object definitively requires both functional objects must be implemented as *closures*."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The Kenzo program generates during runtime thousands of closures, implementing components of the sophisticated objects of Algebraic Topology: simplicial sets, chain complexes, algebra and coalgebra structures, cohomology classes, ... So that a lucid study of the corresponding complexity problems cannot avoid a reasonable knowledge of the structure of these closures."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "The closure uses the global binding \"`a` $\\mapsto$ 11\" and the local one \"`b` $\\mapsto$ `222`\" the last binding shadowing the global one \"`b` $\\mapsto$ `22`\". The first call computes $11 + 222 + 33 = 266$. Then the *global* bindings of `a` and `b` are modified. The second call of the closure computes $110 + 222 + 33 = 365$; in other words, the closure uses the unique (global) binding of `a`, which has been modified by `(setq a 110)`, but the local binding of `b` which is not changed by `(setq b 2)`, this last `setq` changing only the global binding of `b`, which binding is shadowed inside the local environment of the closure by its own local binding, unchanged.\n",
- "\n",
- "All the previous examples are artificial, just designed to make obvious how, in a good programming language, the interpreter and the compiler process the terrible problem of *identifier scope* in a relatively sophisticated but\n",
- "powerful way. This organization made of global environments, local environments *and closures* is the result of a long evolution of the art of programming; the interested readers can consult the Wikipedia page \"Closure (computer programming)\" to get some information about this evolution.\n",
- "\n",
"The author of this note does not forget his serious difficulties when he tried in the 70's to understand the very notion of the *funarg problem* in Maclisp, see:\n",
"\n",
"[http://www.maclisp.info/pitmanual/eval.html](http://www.maclisp.info/pitmanual/eval.html)\n",
"\n",
"when the birth of the notion of closure was just under way: the programmer had to manage himself the problem of identifier scope through the *association lists*. The discussion described in this web page is instructive to follow the hesitations of the best implementers of this time.\n",
"\n",
+ "See also:\n",
+ "\n",
+ "[https://en.wikipedia.org/wiki/Funarg_problem](https://en.wikipedia.org/wiki/Funarg_problem)\n",
+ "\n",
"There remains to see why the use of these closures is *free*, we mean in complexity studies, and also why this organization is exactly the right one to implement high level mathematics, we mean when notions coming from the\n",
"*Category Theory*, typically the *functors*, have to be implemented.\n",
"\n",
@@ -1398,7 +1527,7 @@
},
{
"cell_type": "code",
- "execution_count": 44,
+ "execution_count": 50,
"metadata": {
"collapsed": false
},
@@ -1409,7 +1538,7 @@
"N+"
]
},
- "execution_count": 44,
+ "execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
@@ -1427,7 +1556,7 @@
},
{
"cell_type": "code",
- "execution_count": 45,
+ "execution_count": 51,
"metadata": {
"collapsed": false
},
@@ -1438,7 +1567,7 @@
"9"
]
},
- "execution_count": 45,
+ "execution_count": 51,
"metadata": {},
"output_type": "execute_result"
}
@@ -1447,6 +1576,28 @@
"(funcall #'+ 4 5)"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "#"
+ ]
+ },
+ "execution_count": 52,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "#'+"
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
@@ -1458,7 +1609,7 @@
},
{
"cell_type": "code",
- "execution_count": 46,
+ "execution_count": 53,
"metadata": {
"collapsed": false
},
@@ -1469,7 +1620,7 @@
"N*"
]
},
- "execution_count": 46,
+ "execution_count": 53,
"metadata": {},
"output_type": "execute_result"
}
@@ -1480,7 +1631,7 @@
},
{
"cell_type": "code",
- "execution_count": 47,
+ "execution_count": 54,
"metadata": {
"collapsed": false
},
@@ -1491,7 +1642,7 @@
"20"
]
},
- "execution_count": 47,
+ "execution_count": 54,
"metadata": {},
"output_type": "execute_result"
}
@@ -1509,7 +1660,7 @@
},
{
"cell_type": "code",
- "execution_count": 48,
+ "execution_count": 55,
"metadata": {
"collapsed": false
},
@@ -1520,7 +1671,7 @@
"MONOID-IDENTITY"
]
},
- "execution_count": 48,
+ "execution_count": 55,
"metadata": {},
"output_type": "execute_result"
}
@@ -1533,7 +1684,7 @@
},
{
"cell_type": "code",
- "execution_count": 49,
+ "execution_count": 56,
"metadata": {
"collapsed": false
},
@@ -1544,7 +1695,7 @@
"0"
]
},
- "execution_count": 49,
+ "execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
@@ -1562,7 +1713,7 @@
},
{
"cell_type": "code",
- "execution_count": 50,
+ "execution_count": 57,
"metadata": {
"collapsed": false
},
@@ -1573,7 +1724,7 @@
"OPERATION"
]
},
- "execution_count": 50,
+ "execution_count": 57,
"metadata": {},
"output_type": "execute_result"
}
@@ -1584,197 +1735,6 @@
" (funcall (second monoid) item1 item2)))"
]
},
- {
- "cell_type": "code",
- "execution_count": 51,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(9 20)"
- ]
- },
- "execution_count": 51,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(list (funcall operation N+ 4 5)\n",
- " (funcall operation N* 4 5))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now we implement the product *bifunctor*. If $M_1$ and $M_2$ are two monoids, an element of $M_1 \\times M_2$ is represented as a pair. So that the identity of $M_1 \\times M_2$ is the pair of the respective identities. The function which, given the monoids $M_1$ and $M_2$, constructs the identity of $M_1 \\times M_2$ therefore is:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 52,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "MONOID-PRODUCT-IDENTITY"
- ]
- },
- "execution_count": 52,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(defvar monoid-product-identity\n",
- " (lambda (monoid1 monoid2)\n",
- " (list (funcall monoid-identity monoid1) (funcall monoid-identity monoid2))))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 53,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(0 1)"
- ]
- },
- "execution_count": 53,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(funcall monoid-product-identity N+ N*)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We did not yet have seen any closure. In the same way, we must be able to construct, given two *arbitrary* monoids $M_1$ and $M_2$, the operation of the product monoid $M_1 \\times M_2$."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 54,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "MONOID-PRODUCT-OPERATION"
- ]
- },
- "execution_count": 54,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(defvar monoid-product-operation\n",
- " (lambda (monoid1 monoid2)\n",
- " (lambda (item1 item2)\n",
- " (list (funcall operation monoid1\n",
- " (first item1) (first item2))\n",
- " (funcall operation monoid2\n",
- " (second item1) (second item2))))))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 55,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "#"
- ]
- },
- "execution_count": 55,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(funcall monoid-product-operation N+ N*)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The operation to be constructed uses the operation of the first monoid and makes it work on the first components of the \"items\", the same for the second components with respect to the second monoid, and finally makes a pair, a list, with both results.\n",
- "\n",
- "Using this constructor with respect to our monoids `N+` and `N*` this time produces a *closure*. The point is that the internal `(lambda (item1 ...) ...)` uses `monoid1` and `monoid2` constituting the *local* environment where the functional object is generated, so that this environment must be *saved*, it is the role of the closure technology.\n",
- "\n",
- "We have now in our toolbox the necessary ingredients allowing us to implement the product bifunctor."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 56,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "MONOID-PRODUCT"
- ]
- },
- "execution_count": 56,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(defvar monoid-product\n",
- " (lambda (monoid1 monoid2)\n",
- " (list (funcall monoid-product-identity monoid1 monoid2)\n",
- " (funcall monoid-product-operation monoid1 monoid2))))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 57,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "N+_X_N*"
- ]
- },
- "execution_count": 57,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "(defvar N+_x_N* (funcall monoid-product N+ N*))"
- ]
- },
{
"cell_type": "code",
"execution_count": 58,
@@ -1793,6 +1753,198 @@
"output_type": "execute_result"
}
],
+ "source": [
+ "(list (funcall operation N+ 4 5)\n",
+ " (funcall operation N* 4 5))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now we implement the product *bifunctor*. If $M_1$ and $M_2$ are two monoids, an element of $M_1 \\times M_2$ is represented as a pair. So that the identity of $M_1 \\times M_2$ is the pair of the respective identities. The function which, given the monoids $M_1$ and $M_2$, constructs the identity of $M_1 \\times M_2$ therefore is:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 59,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "MONOID-PRODUCT-IDENTITY"
+ ]
+ },
+ "execution_count": 59,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(defvar monoid-product-identity\n",
+ " (lambda (monoid1 monoid2)\n",
+ " (list (funcall monoid-identity monoid1)\n",
+ " (funcall monoid-identity monoid2))))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 60,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(0 1)"
+ ]
+ },
+ "execution_count": 60,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(funcall monoid-product-identity N+ N*)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We did not yet have seen any closure. In the same way, we must be able to construct, given two *arbitrary* monoids $M_1$ and $M_2$, the operation of the product monoid $M_1 \\times M_2$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "MONOID-PRODUCT-OPERATION"
+ ]
+ },
+ "execution_count": 61,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(defvar monoid-product-operation\n",
+ " (lambda (monoid1 monoid2)\n",
+ " (lambda (item1 item2)\n",
+ " (list (funcall operation monoid1\n",
+ " (first item1) (first item2))\n",
+ " (funcall operation monoid2\n",
+ " (second item1) (second item2))))))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 62,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "#"
+ ]
+ },
+ "execution_count": 62,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(funcall monoid-product-operation N+ N*)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The operation to be constructed uses the operation of the first monoid and makes it work on the first components of the \"items\", the same for the second components with respect to the second monoid, and finally makes a pair, a list, with both results.\n",
+ "\n",
+ "Using this constructor with respect to our monoids `N+` and `N*` this time produces a *closure*. The point is that the internal `(lambda (item1 ...) ...)` uses `monoid1` and `monoid2` constituting the *local* environment where the functional object is generated, so that this environment must be *saved*, it is the role of the closure technology.\n",
+ "\n",
+ "We have now in our toolbox the necessary ingredients allowing us to implement the product bifunctor."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 63,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "MONOID-PRODUCT"
+ ]
+ },
+ "execution_count": 63,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(defvar monoid-product\n",
+ " (lambda (monoid1 monoid2)\n",
+ " (list (funcall monoid-product-identity monoid1 monoid2)\n",
+ " (funcall monoid-product-operation monoid1 monoid2))))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 64,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "N+_X_N*"
+ ]
+ },
+ "execution_count": 64,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(defvar N+_x_N* (funcall monoid-product N+ N*))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(9 20)"
+ ]
+ },
+ "execution_count": 65,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"(funcall operation N+_x_N*\n",
" (list 4 4) (list 5 5))"
@@ -1808,7 +1960,7 @@
},
{
"cell_type": "code",
- "execution_count": 59,
+ "execution_count": 66,
"metadata": {
"collapsed": false
},
@@ -1819,7 +1971,7 @@
"STRING-CONC"
]
},
- "execution_count": 59,
+ "execution_count": 66,
"metadata": {},
"output_type": "execute_result"
}
@@ -1832,7 +1984,7 @@
},
{
"cell_type": "code",
- "execution_count": 60,
+ "execution_count": 67,
"metadata": {
"collapsed": false
},
@@ -1843,7 +1995,7 @@
"\"qwertyuiop\""
]
},
- "execution_count": 60,
+ "execution_count": 67,
"metadata": {},
"output_type": "execute_result"
}
@@ -1854,7 +2006,7 @@
},
{
"cell_type": "code",
- "execution_count": 61,
+ "execution_count": 68,
"metadata": {
"collapsed": false
},
@@ -1865,7 +2017,7 @@
"CS"
]
},
- "execution_count": 61,
+ "execution_count": 68,
"metadata": {},
"output_type": "execute_result"
}
@@ -1876,7 +2028,7 @@
},
{
"cell_type": "code",
- "execution_count": 62,
+ "execution_count": 69,
"metadata": {
"collapsed": false
},
@@ -1887,7 +2039,7 @@
"\"qwertyuiop\""
]
},
- "execution_count": 62,
+ "execution_count": 69,
"metadata": {},
"output_type": "execute_result"
}
@@ -1905,7 +2057,7 @@
},
{
"cell_type": "code",
- "execution_count": 63,
+ "execution_count": 70,
"metadata": {
"collapsed": false
},
@@ -1916,7 +2068,7 @@
"CS_X_N+"
]
},
- "execution_count": 63,
+ "execution_count": 70,
"metadata": {},
"output_type": "execute_result"
}
@@ -1927,7 +2079,7 @@
},
{
"cell_type": "code",
- "execution_count": 64,
+ "execution_count": 71,
"metadata": {
"collapsed": false
},
@@ -1938,7 +2090,7 @@
"(\"qwertyuiop\" 9)"
]
},
- "execution_count": 64,
+ "execution_count": 71,
"metadata": {},
"output_type": "execute_result"
}
@@ -1957,7 +2109,7 @@
},
{
"cell_type": "code",
- "execution_count": 65,
+ "execution_count": 72,
"metadata": {
"collapsed": false
},
@@ -1968,7 +2120,7 @@
"CS_X_[N+_X_N*]"
]
},
- "execution_count": 65,
+ "execution_count": 72,
"metadata": {},
"output_type": "execute_result"
}
@@ -1979,7 +2131,7 @@
},
{
"cell_type": "code",
- "execution_count": 66,
+ "execution_count": 73,
"metadata": {
"collapsed": false
},
@@ -1990,7 +2142,7 @@
"(\"qwertyuiop\" (8 25))"
]
},
- "execution_count": 66,
+ "execution_count": 73,
"metadata": {},
"output_type": "execute_result"
}
@@ -2005,7 +2157,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "The product bifunctor is present in our environment and can be used as in category theory. Its main component is a functional object constructing a new functional object from two other functional objects, the closure notion allowing the user to naturally construct this higher functional object, for the constructed closure \"keeps\" the environment where it has been generated. In this way, arbitrary complex uses of the product bifunctor can be done whithout any confusion: every closure will see the right references. *And* the complexity cost in this case is constant, only the addresses of `monoid1` and `monoid2` are used; the code constituting the heart of the function `monoid-product-operation` being once for all installed in the environment, which code can be used by \\emph{any} product of monoids, thanks to the indirect addressing mechanism.\n",
+ "The product bifunctor is present in our environment and can be used as in category theory. Its main component is a functional object constructing a new functional object from two other functional objects, the closure notion allowing the user to naturally construct this higher functional object, for the constructed closure \"keeps\" the environment where it has been generated. In this way, arbitrary complex uses of the product bifunctor can be done whithout any confusion: every closure will see the right references. *And* the complexity cost in this case is constant, only the addresses of `monoid1` and `monoid2` are used; the code constituting the heart of the function `monoid-product-operation` being once for all installed in the environment, which code can be used by *any* product of monoids, thanks to the indirect addressing mechanism.\n",
"\n",
"# When computing homotopy groups.\n",
"\n",