diff --git a/docs/Types.html b/docs/Types.html
index 6486b06..f4e4f6f 100644
--- a/docs/Types.html
+++ b/docs/Types.html
@@ -11775,7 +11775,7 @@ div#notebook {
+
+
+
+
+
For combinators the list contains Python functions.
+
+
+
+
+
+
+
+
For simple combinators that have only one effect (like dip) you only need one function and it can be the combinator itself.
+
+
+
+
+
+
+
+
+
For combinators that can have more than one effect (like branch) you have to write functions that each implement the action of one of the effects.
+
+
+
+
+
+
+
+
You can also provide an optional stack effect, input-side only, that will then be used as an identity function (that accepts and returns stacks that match the "guard" stack effect) which will be used to guard against type mismatches going into the evaluation of the combinator.
+
+
+
+
+
+
+
+
+
infer()¶
With those in place, we can define a function that accepts a sequence of Joy type variables, including ones representing functions (not just values), and attempts to grind out all the possible stack effects of that expression.
+
One tricky thing is that type variables in the expression have to be updated along with the stack effects after doing unification or we risk losing useful information. This was a straightforward, if awkward, modification to the call structure of meta_compose() et. al.
+
+
+
-
-
-
-
-
-
-
-
-
-
-
Out[114]:
-
-
-
-
-
-
(n1, (n2, ((mul, s2), ((stack, s3), (dip, (infra, (first, ())))))))
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Out[116]:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Out[117]:
-
-
-
-
-
-
[(s1, (f1, s1)), (s1, (i1, s1))]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Out[118]:
-
-
-
-
-
-
[(s1, (f1, s1)), (s1, (i1, s1))]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Out[120]:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Out[121]:
-
-
-
-
-
-
[(s1, (f1, s1)), (s1, (i1, s1))]
-
-
-
-
-
-
-
-
And that brings us to current Work-In-Progress. I'm pretty hopeful that the mixed-mode inferencer/interpreter infer() function along with the ability to specify multiple implementations for the combinators will permit modelling of the stack effects of e.g. ifte. If I can keep up the pace I should be able to verify that conjecture by the end of June.
+
Work in Progress¶
And that brings us to current Work-In-Progress. The mixed-mode inferencer/interpreter infer() function seems to work well. There are details I should document, and the rest of the code in the "polytypes" module (FIXME link to its docs here!) should be explained... There is cruft to convert the definitions in DEFS to the new SymbolJoyType objects, and some combinators. Here is an example of output from the current code :
+
+
+
+
+
+
+
+
+
+
The numbers at the start of the lines are the current depth of the Python call stack. They're followed by the current computed stack effect (initialized to ID) then the pending expression (the inference of the stack effect of which is the whole object of the current example.)
+
In this example we are implementing (and inferring) ifte as [nullary bool] dipd branch which shows off a lot of the current implementation in action.
+
+
7 (--) ∘ [pred] [mul] [div] [nullary bool] dipd branch
+ 8 (-- [pred ...2]) ∘ [mul] [div] [nullary bool] dipd branch
+ 9 (-- [pred ...2] [mul ...3]) ∘ [div] [nullary bool] dipd branch
+ 10 (-- [pred ...2] [mul ...3] [div ...4]) ∘ [nullary bool] dipd branch
+ 11 (-- [pred ...2] [mul ...3] [div ...4] [nullary bool ...5]) ∘ dipd branch
+ 15 (-- [pred ...5]) ∘ nullary bool [mul] [div] branch
+ 19 (-- [pred ...2]) ∘ [stack] dinfrirst bool [mul] [div] branch
+ 20 (-- [pred ...2] [stack ]) ∘ dinfrirst bool [mul] [div] branch
+ 22 (-- [pred ...2] [stack ]) ∘ dip infra first bool [mul] [div] branch
+ 26 (--) ∘ stack [pred] infra first bool [mul] [div] branch
+ 29 (... -- ... [...]) ∘ [pred] infra first bool [mul] [div] branch
+ 30 (... -- ... [...] [pred ...1]) ∘ infra first bool [mul] [div] branch
+ 34 (--) ∘ pred s1 swaack first bool [mul] [div] branch
+ 37 (n1 -- n2) ∘ [n1] swaack first bool [mul] [div] branch
+ 38 (... n1 -- ... n2 [n1 ...]) ∘ swaack first bool [mul] [div] branch
+ 41 (... n1 -- ... n1 [n2 ...]) ∘ first bool [mul] [div] branch
+ 44 (n1 -- n1 n2) ∘ bool [mul] [div] branch
+ 47 (n1 -- n1 b1) ∘ [mul] [div] branch
+ 48 (n1 -- n1 b1 [mul ...1]) ∘ [div] branch
+ 49 (n1 -- n1 b1 [mul ...1] [div ...2]) ∘ branch
+ 53 (n1 -- n1) ∘ div
+ 56 (f2 f1 -- f3) ∘
+ 56 (i1 f1 -- f2) ∘
+ 56 (f1 i1 -- f2) ∘
+ 56 (i2 i1 -- f1) ∘
+ 53 (n1 -- n1) ∘ mul
+ 56 (f2 f1 -- f3) ∘
+ 56 (i1 f1 -- f2) ∘
+ 56 (f1 i1 -- f2) ∘
+ 56 (i2 i1 -- i3) ∘
+----------------------------------------
+(f2 f1 -- f3)
+(i1 f1 -- f2)
+(f1 i1 -- f2)
+(i2 i1 -- f1)
+(i2 i1 -- i3)
@@ -16812,7 +16707,7 @@ uncons = ([a1 .1.] -- a1 [.1.])
-
Conclusion¶
(for now...)
+
Conclusion¶
We built a simple type inferencer, and a kind of crude "compiler" for a subset of Joy functions. Then we built a more powerful inferencer that actually does some evaluation and explores branching code paths
@@ -16849,32 +16744,15 @@ uncons = ([a1 .1.] -- a1 [.1.])
-
I'm starting to realize that, with the inferencer/checker/compiler coming along, and with the UI ready to be rewritten in Joy, I'm close to a time when my ephasis is going to have to shift from crunchy code stuff to squishy human stuff. I'm going to have to put normal people in front of this and see if, in fact, they can learn the basics of programming with it.
-
-
-
-
-
-
-
-
-
The rest of this stuff is junk and/or unfinished material.
-
-
-
-
-
-
-
-
-
Appendix: Joy in the Logical Paradigm¶
For this to work the type label classes have to be modified to let T >= t succeed, where e.g. T is IntJoyType and t is int
+
Appendix: Joy in the Logical Paradigm¶
For type checking to work the type label classes have to be modified to let T >= t succeed, where e.g. T is IntJoyType and t is int. If you do that you can take advantage of the logical relational nature of the stack effect comments to "compute in reverse" as it were. There's a working demo of this at the end of the polytypes module. But if you're interested in all that you should just use Prolog!
+
Anyhow, type checking is a few easy steps away.
diff --git a/docs/Types.ipynb b/docs/Types.ipynb
index a220b3f..25d7a5f 100644
--- a/docs/Types.ipynb
+++ b/docs/Types.ipynb
@@ -4,7 +4,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Type Inference\n",
+ "# The Blissful Elegance of Typing Joy\n",
"\n",
"This notebook presents a simple type inferencer for Joy code. It can infer the stack effect of most Joy expressions. It's built largely by means of existing ideas and research. (A great overview of the existing knowledge is a talk [\"Type Inference in Stack-Based Programming Languages\"](http://prl.ccs.neu.edu/blog/2017/03/10/type-inference-in-stack-based-programming-languages/) given by Rob Kleffner on or about 2017-03-10 as part of a course on the history of programming languages.)\n",
"\n",
@@ -841,7 +841,15 @@
"cell_type": "code",
"execution_count": 20,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Cannot unify (1, 2) and (1001, 1002).\n"
+ ]
+ }
+ ],
"source": [
"try:\n",
" C(cons, uncons)\n",
@@ -2139,7 +2147,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "(Eventually I should come back around to this becuase it's not tooo difficult to exend this code to be able to compile e.g. `n3 = mul(n1, n2)` for `mul` and insert it in the right place with the right variable names. It requires a little more support from the library functions, in that we need to know to call `mul()` the Python function for `mul` the Joy function, but since *most* of the math functions (at least) are already wrappers it should be straightforward.)"
+ "(Eventually I should come back around to this becuase it's not tooo difficult to exend this code to be able to compile e.g. `n2 = mul(n1, n1)` for `mul` with the right variable names and insert it in the right place. It requires a little more support from the library functions, in that we need to know to call `mul()` the Python function for `mul` the Joy function, but since *most* of the math functions (at least) are already wrappers it should be straightforward.)"
]
},
{
@@ -3476,7 +3484,15 @@
"metadata": {},
"source": [
"### Hybrid Inferencer/Interpreter\n",
- "I think there's a way forward. If we convert our list of terms we are composing into a stack structure we can use it as a *Joy expression*, then we can treat the *output half* of a function's stack effect comment as a Joy interpreter stack, and just execute combinators directly. We can hybridize the compostition function with an interpreter to evaluate combinators, compose non-combinator functions, and put type variables on the stack. For combinators like `branch` that can have more than one stack effect we have to \"split universes\" again and return both."
+ "I think there's a way forward. If we convert our list (of terms we are composing) into a stack structure we can use it as a *Joy expression*, then we can treat the *output half* of a function's stack effect comment as a Joy interpreter stack, and just execute combinators directly. We can hybridize the compostition function with an interpreter to evaluate combinators, compose non-combinator functions, and put type variables on the stack. For combinators like `branch` that can have more than one stack effect we have to \"split universes\" again and return both."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Joy Types for Functions\n",
+ "We need a type variable for Joy functions that can go in our expressions and be used by the hybrid inferencer/interpreter. They have to store a name and a list of stack effects."
]
},
{
@@ -3497,43 +3513,15 @@
" __radd__ = __add__\n",
"\n",
" def __repr__(self):\n",
- " return self.name\n",
- "\n",
- "\n",
- "class SymbolJoyType(FunctionJoyType): prefix = 'F'\n",
- "class CombinatorJoyType(FunctionJoyType): prefix = 'C'\n",
- "\n",
- "\n",
- "def flatten(g):\n",
- " return list(chain.from_iterable(g))\n",
- "\n",
- "\n",
- "ID = S[0], S[0] # Identity function.\n",
- "\n",
- "\n",
- "def infer(e, F=ID):\n",
- " if not e:\n",
- " return [F]\n",
- "\n",
- " n, e = e\n",
- "\n",
- " if isinstance(n, SymbolJoyType):\n",
- " res = flatten(infer(e, Fn) for Fn in MC([F], n.stack_effects))\n",
- "\n",
- " elif isinstance(n, CombinatorJoyType):\n",
- " res = []\n",
- " for combinator in n.stack_effects:\n",
- " fi, fo = F\n",
- " new_fo, ee, _ = combinator(fo, e, {})\n",
- " ee = update(FUNCTIONS, ee) # Fix Symbols.\n",
- " new_F = fi, new_fo\n",
- " res.extend(infer(ee, new_F))\n",
- " else:\n",
- " lit = s9, (n, s9)\n",
- " res = flatten(infer(e, Fn) for Fn in MC([F], [lit]))\n",
- "\n",
- " return res\n",
- "\n"
+ " return self.name"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Specialized for Simple Functions and Combinators\n",
+ "For non-combinator functions the stack effects list contains stack effect comments (represented by pairs of cons-lists as described above.)"
]
},
{
@@ -3542,10 +3530,15 @@
"metadata": {},
"outputs": [],
"source": [
- "f0, f1, f2, f3, f4, f5, f6, f7, f8, f9 = F = map(FloatJoyType, _R)\n",
- "i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 = I = map(IntJoyType, _R)\n",
- "n0, n1, n2, n3, n4, n5, n6, n7, n8, n9 = N\n",
- "s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 = S"
+ "class SymbolJoyType(FunctionJoyType):\n",
+ " prefix = 'F'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "For combinators the list contains Python functions. "
]
},
{
@@ -3554,48 +3547,28 @@
"metadata": {},
"outputs": [],
"source": [
- "import joy.library\n",
+ "class CombinatorJoyType(FunctionJoyType):\n",
"\n",
- "FNs = '''ccons cons divmod_ dup dupd first\n",
- " over pm pop popd popdd popop pred\n",
- " rest rolldown rollup rrest second\n",
- " sqrt stack succ swaack swap swons\n",
- " third tuck uncons'''\n",
+ " prefix = 'C'\n",
"\n",
- "FUNCTIONS = {\n",
- " name: SymbolJoyType(name, [NEW_DEFS[name]], i)\n",
- " for i, name in enumerate(FNs.strip().split())\n",
- " }\n",
- "FUNCTIONS['sum'] = SymbolJoyType('sum', [(((Ns[1], s1), s0), (n0, s0))], 100)\n",
- "FUNCTIONS['mul'] = SymbolJoyType('mul', [\n",
- " ((i2, (i1, s0)), (i3, s0)),\n",
- " ((f2, (i1, s0)), (f3, s0)),\n",
- " ((i2, (f1, s0)), (f3, s0)),\n",
- " ((f2, (f1, s0)), (f3, s0)),\n",
- "], 101)\n",
- "FUNCTIONS.update({\n",
- " combo.__name__: CombinatorJoyType(combo.__name__, [combo], i)\n",
- " for i, combo in enumerate((\n",
- " joy.library.i,\n",
- " joy.library.dip,\n",
- " joy.library.dipd,\n",
- " joy.library.dipdd,\n",
- " joy.library.dupdip,\n",
- " joy.library.b,\n",
- " joy.library.x,\n",
- " joy.library.infra,\n",
- " ))\n",
- " })\n",
+ " def __init__(self, name, sec, number, expect=None):\n",
+ " super(CombinatorJoyType, self).__init__(name, sec, number)\n",
+ " self.expect = expect\n",
"\n",
- "def branch_true(stack, expression, dictionary):\n",
- " (then, (else_, (flag, stack))) = stack\n",
- " return stack, CONCAT(then, expression), dictionary\n",
- "\n",
- "def branch_false(stack, expression, dictionary):\n",
- " (then, (else_, (flag, stack))) = stack\n",
- " return stack, CONCAT(else_, expression), dictionary\n",
- "\n",
- "FUNCTIONS['branch'] = CombinatorJoyType('branch', [branch_true, branch_false], 100)"
+ " def enter_guard(self, f):\n",
+ " if self.expect is None:\n",
+ " return f\n",
+ " g = self.expect, self.expect\n",
+ " new_f = list(compose(f, g, ()))\n",
+ " assert len(new_f) == 1, repr(new_f)\n",
+ " return new_f[0][1]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "For simple combinators that have only one effect (like ``dip``) you only need one function and it can be the combinator itself."
]
},
{
@@ -3604,7 +3577,16 @@
"metadata": {},
"outputs": [],
"source": [
- "globals().update(FUNCTIONS)"
+ "import joy.library\n",
+ "\n",
+ "dip = CombinatorJoyType('dip', [joy.library.dip], 23)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "For combinators that can have more than one effect (like ``branch``) you have to write functions that each implement the action of one of the effects."
]
},
{
@@ -3613,8 +3595,32 @@
"metadata": {},
"outputs": [],
"source": [
- "from itertools import chain\n",
- "from joy.utils.stack import list_to_stack as l2s"
+ "def branch_true(stack, expression, dictionary):\n",
+ " (then, (else_, (flag, stack))) = stack\n",
+ " return stack, concat(then, expression), dictionary\n",
+ "\n",
+ "def branch_false(stack, expression, dictionary):\n",
+ " (then, (else_, (flag, stack))) = stack\n",
+ " return stack, concat(else_, expression), dictionary\n",
+ "\n",
+ "branch = CombinatorJoyType('branch', [branch_true, branch_false], 100)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "You can also provide an optional stack effect, input-side only, that will then be used as an identity function (that accepts and returns stacks that match the \"guard\" stack effect) which will be used to guard against type mismatches going into the evaluation of the combinator."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### `infer()`\n",
+ "With those in place, we can define a function that accepts a sequence of Joy type variables, including ones representing functions (not just values), and attempts to grind out all the possible stack effects of that expression.\n",
+ "\n",
+ "One tricky thing is that type variables *in the expression* have to be updated along with the stack effects after doing unification or we risk losing useful information. This was a straightforward, if awkward, modification to the call structure of `meta_compose()` et. al."
]
},
{
@@ -3623,139 +3629,130 @@
"metadata": {},
"outputs": [],
"source": [
- "expression = l2s([n1, n2, mul])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 113,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(n1, (n2, (mul, ())))"
- ]
- },
- "execution_count": 113,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "expression"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 114,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[(s1, (f1, s1)), (s1, (i1, s1))]"
- ]
- },
- "execution_count": 114,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "infer(expression)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 115,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "(-- f1)\n",
- "(-- i1)\n"
- ]
- }
- ],
- "source": [
- "for stack_effect_comment in infer(expression):\n",
- " print doc_from_stack_effect(*stack_effect_comment)"
+ "ID = S[0], S[0] # Identity function.\n",
+ "\n",
+ "\n",
+ "def infer(*expression):\n",
+ " return sorted(set(_infer(list_to_stack(expression))))\n",
+ "\n",
+ "\n",
+ "def _infer(e, F=ID):\n",
+ " _log_it(e, F)\n",
+ " if not e:\n",
+ " return [F]\n",
+ "\n",
+ " n, e = e\n",
+ "\n",
+ " if isinstance(n, SymbolJoyType):\n",
+ " eFG = meta_compose([F], n.stack_effects, e)\n",
+ " res = flatten(_infer(e, Fn) for e, Fn in eFG)\n",
+ "\n",
+ " elif isinstance(n, CombinatorJoyType):\n",
+ " fi, fo = n.enter_guard(F)\n",
+ " res = flatten(_interpret(f, fi, fo, e) for f in n.stack_effects)\n",
+ "\n",
+ " elif isinstance(n, Symbol):\n",
+ " assert n not in FUNCTIONS, repr(n)\n",
+ " func = joy.library._dictionary[n]\n",
+ " res = _interpret(func, F[0], F[1], e)\n",
+ "\n",
+ " else:\n",
+ " fi, fo = F\n",
+ " res = _infer(e, (fi, (n, fo)))\n",
+ "\n",
+ " return res\n",
+ "\n",
+ "\n",
+ "def _interpret(f, fi, fo, e):\n",
+ " new_fo, ee, _ = f(fo, e, {})\n",
+ " ee = update(FUNCTIONS, ee) # Fix Symbols.\n",
+ " new_F = fi, new_fo\n",
+ " return _infer(ee, new_F)\n",
+ "\n",
+ "\n",
+ "def _log_it(e, F):\n",
+ " _log.info(\n",
+ " u'%3i %s ∘ %s',\n",
+ " len(inspect_stack()),\n",
+ " doc_from_stack_effect(*F),\n",
+ " expression_to_string(e),\n",
+ " )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "`nullary`, broken here but the current code handles it. I have to \"backport\" the work I'm doing to this notebook. I've run ahead of myself."
+ "#### Work in Progress\n",
+ "And that brings us to current Work-In-Progress. The mixed-mode inferencer/interpreter `infer()` function seems to work well. There are details I should document, and the rest of the code in the \"polytypes\" module (FIXME link to its docs here!) should be explained... There is cruft to convert the definitions in `DEFS` to the new `SymbolJoyType` objects, and some combinators. Here is an example of output from the current code :"
]
},
{
"cell_type": "code",
- "execution_count": 116,
- "metadata": {},
+ "execution_count": null,
+ "metadata": {
+ "scrolled": true
+ },
"outputs": [],
"source": [
- "expression = l2s([n1, n2, (mul, s2), (stack, s3), dip, infra, first])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 117,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(n1, (n2, ((mul, s2), ((stack, s3), (dip, (infra, (first, ())))))))"
- ]
- },
- "execution_count": 117,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "expression"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 132,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[]"
- ]
- },
- "execution_count": 132,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "infer(expression) # should be three numbers."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 119,
- "metadata": {},
- "outputs": [],
- "source": [
- "for stack_effect_comment in infer(expression):\n",
- " print doc_from_stack_effect(*stack_effect_comment)"
+ "1/0 # (Don't try to run this cell! It's not going to work. This is \"read only\" code heh..)\n",
+ "\n",
+ "logging.basicConfig(format='%(message)s', stream=sys.stdout, level=logging.INFO)\n",
+ "\n",
+ "globals().update(FUNCTIONS)\n",
+ "\n",
+ "h = infer((pred, s2), (mul, s3), (div, s4), (nullary, (bool, s5)), dipd, branch)\n",
+ "\n",
+ "print '-' * 40\n",
+ "\n",
+ "for fi, fo in h:\n",
+ " print doc_from_stack_effect(fi, fo)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "And that brings us to current Work-In-Progress. I'm pretty hopeful that the mixed-mode inferencer/interpreter `infer()` function along with the ability to specify multiple implementations for the combinators will permit modelling of the stack effects of e.g. `ifte`."
+ "The numbers at the start of the lines are the current depth of the Python call stack. They're followed by the current computed stack effect (initialized to `ID`) then the pending expression (the inference of the stack effect of which is the whole object of the current example.)\n",
+ "\n",
+ "In this example we are implementing (and inferring) `ifte` as `[nullary bool] dipd branch` which shows off a lot of the current implementation in action.\n",
+ "\n",
+ " 7 (--) ∘ [pred] [mul] [div] [nullary bool] dipd branch\n",
+ " 8 (-- [pred ...2]) ∘ [mul] [div] [nullary bool] dipd branch\n",
+ " 9 (-- [pred ...2] [mul ...3]) ∘ [div] [nullary bool] dipd branch\n",
+ " 10 (-- [pred ...2] [mul ...3] [div ...4]) ∘ [nullary bool] dipd branch\n",
+ " 11 (-- [pred ...2] [mul ...3] [div ...4] [nullary bool ...5]) ∘ dipd branch\n",
+ " 15 (-- [pred ...5]) ∘ nullary bool [mul] [div] branch\n",
+ " 19 (-- [pred ...2]) ∘ [stack] dinfrirst bool [mul] [div] branch\n",
+ " 20 (-- [pred ...2] [stack ]) ∘ dinfrirst bool [mul] [div] branch\n",
+ " 22 (-- [pred ...2] [stack ]) ∘ dip infra first bool [mul] [div] branch\n",
+ " 26 (--) ∘ stack [pred] infra first bool [mul] [div] branch\n",
+ " 29 (... -- ... [...]) ∘ [pred] infra first bool [mul] [div] branch\n",
+ " 30 (... -- ... [...] [pred ...1]) ∘ infra first bool [mul] [div] branch\n",
+ " 34 (--) ∘ pred s1 swaack first bool [mul] [div] branch\n",
+ " 37 (n1 -- n2) ∘ [n1] swaack first bool [mul] [div] branch\n",
+ " 38 (... n1 -- ... n2 [n1 ...]) ∘ swaack first bool [mul] [div] branch\n",
+ " 41 (... n1 -- ... n1 [n2 ...]) ∘ first bool [mul] [div] branch\n",
+ " 44 (n1 -- n1 n2) ∘ bool [mul] [div] branch\n",
+ " 47 (n1 -- n1 b1) ∘ [mul] [div] branch\n",
+ " 48 (n1 -- n1 b1 [mul ...1]) ∘ [div] branch\n",
+ " 49 (n1 -- n1 b1 [mul ...1] [div ...2]) ∘ branch\n",
+ " 53 (n1 -- n1) ∘ div\n",
+ " 56 (f2 f1 -- f3) ∘ \n",
+ " 56 (i1 f1 -- f2) ∘ \n",
+ " 56 (f1 i1 -- f2) ∘ \n",
+ " 56 (i2 i1 -- f1) ∘ \n",
+ " 53 (n1 -- n1) ∘ mul\n",
+ " 56 (f2 f1 -- f3) ∘ \n",
+ " 56 (i1 f1 -- f2) ∘ \n",
+ " 56 (f1 i1 -- f2) ∘ \n",
+ " 56 (i2 i1 -- i3) ∘ \n",
+ " ----------------------------------------\n",
+ " (f2 f1 -- f3)\n",
+ " (i1 f1 -- f2)\n",
+ " (f1 i1 -- f2)\n",
+ " (i2 i1 -- f1)\n",
+ " (i2 i1 -- i3)"
]
},
{
@@ -3763,7 +3760,7 @@
"metadata": {},
"source": [
"## Conclusion\n",
- "(for now...)"
+ "We built a simple type inferencer, and a kind of crude \"compiler\" for a subset of Joy functions. Then we built a more powerful inferencer that actually does some evaluation and explores branching code paths"
]
},
{
@@ -3787,150 +3784,20 @@
" - improve this notebook (it kinda falls apart at the end narratively. I went off and just started writing code to see if it would work. It does, but now I have to come back and describe here what I did."
]
},
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "I'm starting to realize that, with the inferencer/checker/compiler coming along, and with the UI ready to be rewritten in Joy, I'm close to a time when my ephasis is going to have to shift from crunchy code stuff to squishy human stuff. I'm going to have to put normal people in front of this and see if, in fact, they *can* learn the basics of programming with it."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The rest of this stuff is junk and/or unfinished material."
- ]
- },
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Appendix: Joy in the Logical Paradigm\n",
- "For this to work the type label classes have to be modified to let `T >= t` succeed, where e.g. `T` is `IntJoyType` and `t` is `int`"
+ "\n",
+ "For *type checking* to work the type label classes have to be modified to let `T >= t` succeed, where e.g. `T` is `IntJoyType` and `t` is `int`. If you do that you can take advantage of the *logical relational* nature of the stack effect comments to \"compute in reverse\" as it were. There's a working demo of this at the end of the `polytypes` module. But if you're interested in all that you should just use Prolog!\n",
+ "\n",
+ "Anyhow, type *checking* is a few easy steps away."
]
},
{
"cell_type": "code",
- "execution_count": 120,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
@@ -3943,696 +3810,6 @@
"AnyJoyType.accept = tuple, int, float, long, str, unicode, bool, Symbol\n",
"StackJoyType.accept = tuple"
]
- },
- {
- "cell_type": "code",
- "execution_count": 121,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "([a4 a5 .1.] a3 a2 a1 -- [a2 a3 .1.])\n"
- ]
- }
- ],
- "source": [
- "F = infer(l2s((pop, swap, rolldown, rest, rest, cons, cons)))\n",
- "\n",
- "for f in F:\n",
- " print doc_from_stack_effect(*f)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 122,
- "metadata": {},
- "outputs": [],
- "source": [
- "from joy.parser import text_to_expression"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": 123,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "(a3 a2 a1 --)\n"
- ]
- }
- ],
- "source": [
- "F = infer(l2s((pop, pop, pop)))\n",
- "\n",
- "for f in F:\n",
- " print doc_from_stack_effect(*f)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 124,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(0, (1, (2, ())))"
- ]
- },
- "execution_count": 124,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "s = text_to_expression('0 1 2')\n",
- "s"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 125,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(a1, (a2, (a3, s1)))"
- ]
- },
- "execution_count": 125,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "F[0][0]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 126,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "()"
- ]
- },
- "execution_count": 126,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "L = unify(s, F[0][0])\n",
- "L"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": 127,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(0, (1, (2, ((3, (4, ())), ()))))"
- ]
- },
- "execution_count": 127,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "s = text_to_expression('0 1 2 [3 4]')\n",
- "s"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 128,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(a1, (a2, (a3, s1)))"
- ]
- },
- "execution_count": 128,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "F[0][0]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 129,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "()"
- ]
- },
- "execution_count": 129,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "L = unify(s, F[0][0])\n",
- "L"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 130,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "()"
- ]
- },
- "execution_count": 130,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "L = unify(F[0][0], s)\n",
- "L"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 131,
- "metadata": {},
- "outputs": [
- {
- "ename": "IndexError",
- "evalue": "list index out of range",
- "output_type": "error",
- "traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
- "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m
\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mF\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
- "\u001b[0;31mIndexError\u001b[0m: list index out of range"
- ]
- }
- ],
- "source": [
- "F[1][0]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "s[0]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "A[1] >= 23"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## [Abstract Interpretation](https://en.wikipedia.org/wiki/Abstract_interpretation)\n",
- "I *think* this might be sorta what I'm doing above with the `kav()` function...\n",
- " In any event \"mixed-mode\" interpreters that include values and type variables and can track constraints, etc. will be, uh, super-useful. And Abstract Interpretation should be a rich source of ideas.\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Junk"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "class SymbolJoyType(AnyJoyType): prefix = 'F'\n",
- "\n",
- "W = map(SymbolJoyType, _R)\n",
- "\n",
- "k = S[0], ((W[1], S[2]), S[0])\n",
- "Symbol('cons')\n",
- "print doc_from_stack_effect(*k)\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "dip_a = ((W[1], S[2]), (A[1], S[0]))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "d = relabel(S[0], dip_a)\n",
- "print doc_from_stack_effect(*d)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "s = list(unify(d[1], k[1]))[0]\n",
- "s"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "scrolled": true
- },
- "outputs": [],
- "source": [
- "j = update(s, k)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print doc_from_stack_effect(*j)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "j"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "cons"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "scrolled": true
- },
- "outputs": [],
- "source": [
- "for f in MC([k], [dup]):\n",
- " print doc_from_stack_effect(*f)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "l = S[0], ((cons, S[2]), (A[1], S[0]))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print doc_from_stack_effect(*l)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "\n",
- "def dip_t(F):\n",
- " (quote, (a1, sec)) = F[1]\n",
- " G = F[0], sec\n",
- " P = S[3], (a1, S[3])\n",
- " a = [P]\n",
- " while isinstance(quote, tuple):\n",
- " term, quote = quote\n",
- " a.append(term)\n",
- " a.append(G)\n",
- " return a[::-1]\n",
- "\n",
- "\n",
- "\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "from joy.utils.stack import iter_stack"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "a, b, c = dip_t(l)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "a"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "b"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "c"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "MC([a], [b])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "kjs = MC(MC([a], [b]), [c])\n",
- "kjs"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "print doc_from_stack_effect(*kjs[0])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- " (a0 [.0.] -- [a0 .0.] a1)\n",
- " \n",
- " a0 [.0.] a1 [cons] dip\n",
- " ----------------------------\n",
- " [a0 .0.] a1"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### `concat`\n",
- "\n",
- "How to deal with `concat`?\n",
- "\n",
- " concat ([.0.] [.1.] -- [.0. .1.])\n",
- " \n",
- "We would like to represent this in Python somehow..."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "concat = (S[0], S[1]), ((S[0], S[1]),)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "But this is actually `cons` with the first argument restricted to be a stack:\n",
- "\n",
- " ([.0.] [.1.] -- [[.0.] .1.])\n",
- "\n",
- "What we have implemented so far would actually only permit:\n",
- "\n",
- " ([.0.] [.1.] -- [.2.])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "concat = (S[0], S[1]), (S[2],)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- " \n",
- "Which works but can lose information. Consider `cons concat`, this is how much information we *could* retain:\n",
- "\n",
- " (1 [.0.] [.1.] -- [1 .0. .1.])\n",
- "\n",
- "As opposed to just:\n",
- "\n",
- " (1 [.0.] [.1.] -- [.2.])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### represent `concat`\n",
- "\n",
- " ([.0.] [.1.] -- [A*(.0.) .1.])\n",
- "\n",
- "Meaning that `A*` on the right-hand side should all the crap from `.0.`.\n",
- "\n",
- " ([ .0.] [.1.] -- [ A* .1.])\n",
- " ([a .0.] [.1.] -- [a A* .1.])\n",
- " ([a b .0.] [.1.] -- [a b A* .1.])\n",
- " ([a b c .0.] [.1.] -- [a b c A* .1.])\n",
- "\n",
- " "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "or...\n",
- "\n",
- " ([ .0.] [.1.] -- [ .1.])\n",
- " ([a .0.] [.1.] -- [a .1.])\n",
- " ([a b .0.] [.1.] -- [a b .1.])\n",
- " ([a b c .0.] [.1.] -- [a b c .1.])\n",
- " ([a A* c .0.] [.1.] -- [a A* c .1.])\n",
- "\n",
- " "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- " (a, (b, S0)) . S1 = (a, (b, (A*, S1)))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "class Astar(object):\n",
- " def __repr__(self):\n",
- " return 'A*'\n",
- "\n",
- "\n",
- "def concat(s0, s1):\n",
- " a = []\n",
- " while isinstance(s0, tuple):\n",
- " term, s0 = s0\n",
- " a.append(term)\n",
- " assert isinstance(s0, StackJoyType), repr(s0)\n",
- " s1 = Astar(), s1\n",
- " for term in reversed(a):\n",
- " s1 = term, s1\n",
- " return s1"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "a, b = (A[1], S[0]), (A[2], S[1])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": [
- "concat(a, b)"
- ]
}
],
"metadata": {
diff --git a/docs/Types.md b/docs/Types.md
index 9a723c0..599973e 100644
--- a/docs/Types.md
+++ b/docs/Types.md
@@ -1,5 +1,5 @@
-# Type Inference
+# The Blissful Elegance of Typing Joy
This notebook presents a simple type inferencer for Joy code. It can infer the stack effect of most Joy expressions. It's built largely by means of existing ideas and research. (A great overview of the existing knowledge is a talk ["Type Inference in Stack-Based Programming Languages"](http://prl.ccs.neu.edu/blog/2017/03/10/type-inference-in-stack-based-programming-languages/) given by Rob Kleffner on or about 2017-03-10 as part of a course on the history of programming languages.)
@@ -345,6 +345,8 @@ def unify(u, v, s=None):
s[u] = v
elif isinstance(v, int):
s[v] = u
+ else:
+ s = False
return s
```
@@ -556,6 +558,9 @@ except Exception, e:
print e
```
+ Cannot unify (1, 2) and (1001, 1002).
+
+
#### `unify()` version 2
The problem is that the `unify()` function as written doesn't handle the case when both terms are tuples. We just have to add a clause to deal with this recursively:
@@ -584,6 +589,8 @@ def unify(u, v, s=None):
s = unify(a, c, s)
if s != False:
s = unify(b, d, s)
+ else:
+ s = False
return s
```
@@ -1413,7 +1420,7 @@ print compile_('sqr', C(dup, mul))
return (n2, stack)
-(Eventually I should come back around to this becuase it's not tooo difficult to exend this code to be able to compile e.g. `n3 = mul(n1, n2)` for `mul` and insert it in the right place with the right variable names. It requires a little more support from the library functions, in that we need to know to call `mul()` the Python function for `mul` the Joy function, but since *most* of the math functions (at least) are already wrappers it should be straightforward.)
+(Eventually I should come back around to this becuase it's not tooo difficult to exend this code to be able to compile e.g. `n2 = mul(n1, n1)` for `mul` with the right variable names and insert it in the right place. It requires a little more support from the library functions, in that we need to know to call `mul()` the Python function for `mul` the Joy function, but since *most* of the math functions (at least) are already wrappers it should be straightforward.)
#### `compilable()`
The functions that *can* be compiled are the ones that have only `AnyJoyType` and `StackJoyType` labels in their stack effect comments. We can write a function to check that:
@@ -2227,7 +2234,7 @@ for result in unify(a, b):
## Part VII: Typing Combinators
-In order to compute the stack effect of combinators you kinda have to have the quoted programs they expect available. In the most general case, the `i` combinator, you can't say anything about it's stack effect other than it expects one quote:
+In order to compute the stack effect of combinators you kinda have to have the quoted programs they expect available. In the most general case, the `i` combinator, you can't say anything about its stack effect other than it expects one quote:
i (... [.1.] -- ... .1.)
@@ -2250,7 +2257,11 @@ Obviously it would be:
Without any information about the contents of the quote we can't say much about the result.
-I think there's a way forward. If we convert our list of terms we are composing into a stack structure we can use it as a *Joy expression*, then we can treat the *output half* of a function's stack effect comment as a Joy interpreter stack, and just execute combinators directly. We can hybridize the compostition function with an interpreter to evaluate combinators, compose non-combinator functions, and put type variables on the stack. For combinators like `branch` that can have more than one stack effect we have to "split universes" again and return both.
+### Hybrid Inferencer/Interpreter
+I think there's a way forward. If we convert our list (of terms we are composing) into a stack structure we can use it as a *Joy expression*, then we can treat the *output half* of a function's stack effect comment as a Joy interpreter stack, and just execute combinators directly. We can hybridize the compostition function with an interpreter to evaluate combinators, compose non-combinator functions, and put type variables on the stack. For combinators like `branch` that can have more than one stack effect we have to "split universes" again and return both.
+
+#### Joy Types for Functions
+We need a type variable for Joy functions that can go in our expressions and be used by the hybrid inferencer/interpreter. They have to store a name and a list of stack effects.
```python
@@ -2267,211 +2278,183 @@ class FunctionJoyType(AnyJoyType):
def __repr__(self):
return self.name
-
-
-class SymbolJoyType(FunctionJoyType): prefix = 'F'
-class CombinatorJoyType(FunctionJoyType): prefix = 'C'
-
-
-
```
+#### Specialized for Simple Functions and Combinators
+For non-combinator functions the stack effects list contains stack effect comments (represented by pairs of cons-lists as described above.)
+
```python
-def flatten(g):
- return list(chain.from_iterable(g))
+class SymbolJoyType(FunctionJoyType):
+ prefix = 'F'
+```
+
+For combinators the list contains Python functions.
+```python
+class CombinatorJoyType(FunctionJoyType):
+
+ prefix = 'C'
+
+ def __init__(self, name, sec, number, expect=None):
+ super(CombinatorJoyType, self).__init__(name, sec, number)
+ self.expect = expect
+
+ def enter_guard(self, f):
+ if self.expect is None:
+ return f
+ g = self.expect, self.expect
+ new_f = list(compose(f, g, ()))
+ assert len(new_f) == 1, repr(new_f)
+ return new_f[0][1]
+```
+
+For simple combinators that have only one effect (like ``dip``) you only need one function and it can be the combinator itself.
+
+
+```python
+import joy.library
+
+dip = CombinatorJoyType('dip', [joy.library.dip], 23)
+```
+
+For combinators that can have more than one effect (like ``branch``) you have to write functions that each implement the action of one of the effects.
+
+
+```python
+def branch_true(stack, expression, dictionary):
+ (then, (else_, (flag, stack))) = stack
+ return stack, concat(then, expression), dictionary
+
+def branch_false(stack, expression, dictionary):
+ (then, (else_, (flag, stack))) = stack
+ return stack, concat(else_, expression), dictionary
+
+branch = CombinatorJoyType('branch', [branch_true, branch_false], 100)
+```
+
+You can also provide an optional stack effect, input-side only, that will then be used as an identity function (that accepts and returns stacks that match the "guard" stack effect) which will be used to guard against type mismatches going into the evaluation of the combinator.
+
+#### `infer()`
+With those in place, we can define a function that accepts a sequence of Joy type variables, including ones representing functions (not just values), and attempts to grind out all the possible stack effects of that expression.
+
+One tricky thing is that type variables *in the expression* have to be updated along with the stack effects after doing unification or we risk losing useful information. This was a straightforward, if awkward, modification to the call structure of `meta_compose()` et. al.
+
+
+```python
ID = S[0], S[0] # Identity function.
-def infer(e, F=ID):
+def infer(*expression):
+ return sorted(set(_infer(list_to_stack(expression))))
+
+
+def _infer(e, F=ID):
+ _log_it(e, F)
if not e:
return [F]
n, e = e
if isinstance(n, SymbolJoyType):
- res = flatten(infer(e, Fn) for Fn in MC([F], n.stack_effects))
+ eFG = meta_compose([F], n.stack_effects, e)
+ res = flatten(_infer(e, Fn) for e, Fn in eFG)
elif isinstance(n, CombinatorJoyType):
- res = []
- for combinator in n.stack_effects:
- fi, fo = F
- new_fo, ee, _ = combinator(fo, e, {})
- ee = update(FUNCTIONS, ee) # Fix Symbols.
- new_F = fi, new_fo
- res.extend(infer(ee, new_F))
+ fi, fo = n.enter_guard(F)
+ res = flatten(_interpret(f, fi, fo, e) for f in n.stack_effects)
+
+ elif isinstance(n, Symbol):
+ assert n not in FUNCTIONS, repr(n)
+ func = joy.library._dictionary[n]
+ res = _interpret(func, F[0], F[1], e)
+
else:
- lit = s9, (n, s9)
- res = flatten(infer(e, Fn) for Fn in MC([F], [lit]))
+ fi, fo = F
+ res = _infer(e, (fi, (n, fo)))
return res
+def _interpret(f, fi, fo, e):
+ new_fo, ee, _ = f(fo, e, {})
+ ee = update(FUNCTIONS, ee) # Fix Symbols.
+ new_F = fi, new_fo
+ return _infer(ee, new_F)
+
+
+def _log_it(e, F):
+ _log.info(
+ u'%3i %s ∘ %s',
+ len(inspect_stack()),
+ doc_from_stack_effect(*F),
+ expression_to_string(e),
+ )
```
+#### Work in Progress
+And that brings us to current Work-In-Progress. The mixed-mode inferencer/interpreter `infer()` function seems to work well. There are details I should document, and the rest of the code in the "polytypes" module (FIXME link to its docs here!) should be explained... There is cruft to convert the definitions in `DEFS` to the new `SymbolJoyType` objects, and some combinators. Here is an example of output from the current code :
+
```python
-f0, f1, f2, f3, f4, f5, f6, f7, f8, f9 = F = map(FloatJoyType, _R)
-i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 = I = map(IntJoyType, _R)
-n0, n1, n2, n3, n4, n5, n6, n7, n8, n9 = N
-s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 = S
-```
+1/0 # (Don't try to run this cell! It's not going to work. This is "read only" code heh..)
+logging.basicConfig(format='%(message)s', stream=sys.stdout, level=logging.INFO)
-```python
-import joy.library
-
-FNs = '''ccons cons divmod_ dup dupd first
- over pm pop popd popdd popop pred
- rest rolldown rollup rrest second
- sqrt stack succ swaack swap swons
- third tuck uncons'''
-
-FUNCTIONS = {
- name: SymbolJoyType(name, [NEW_DEFS[name]], i)
- for i, name in enumerate(FNs.strip().split())
- }
-FUNCTIONS['sum'] = SymbolJoyType('sum', [(((Ns[1], s1), s0), (n0, s0))], 100)
-FUNCTIONS['mul'] = SymbolJoyType('mul', [
- ((i2, (i1, s0)), (i3, s0)),
- ((f2, (i1, s0)), (f3, s0)),
- ((i2, (f1, s0)), (f3, s0)),
- ((f2, (f1, s0)), (f3, s0)),
-], 101)
-FUNCTIONS.update({
- combo.__name__: CombinatorJoyType(combo.__name__, [combo], i)
- for i, combo in enumerate((
- joy.library.i,
- joy.library.dip,
- joy.library.dipd,
- joy.library.dipdd,
- joy.library.dupdip,
- joy.library.b,
- joy.library.x,
- joy.library.infra,
- ))
- })
-
-def branch_true(stack, expression, dictionary):
- (then, (else_, (flag, stack))) = stack
- return stack, CONCAT(then, expression), dictionary
-
-def branch_false(stack, expression, dictionary):
- (then, (else_, (flag, stack))) = stack
- return stack, CONCAT(else_, expression), dictionary
-
-FUNCTIONS['branch'] = CombinatorJoyType('branch', [branch_true, branch_false], 100)
-```
-
-
-```python
globals().update(FUNCTIONS)
+
+h = infer((pred, s2), (mul, s3), (div, s4), (nullary, (bool, s5)), dipd, branch)
+
+print '-' * 40
+
+for fi, fo in h:
+ print doc_from_stack_effect(fi, fo)
```
+The numbers at the start of the lines are the current depth of the Python call stack. They're followed by the current computed stack effect (initialized to `ID`) then the pending expression (the inference of the stack effect of which is the whole object of the current example.)
-```python
-from itertools import chain
-from joy.utils.stack import list_to_stack as l2s
-```
+In this example we are implementing (and inferring) `ifte` as `[nullary bool] dipd branch` which shows off a lot of the current implementation in action.
-
-```python
-expression = l2s([n1, n2, (mul, s2), (stack, s3), dip, infra, first])
-```
-
-
-```python
-expression
-```
-
-
-
-
- (n1, (n2, ((mul, s2), ((stack, s3), (dip, (infra, (first, ())))))))
-
-
-
-
-```python
-expression = l2s([n1, n2, mul])
-```
-
-
-```python
-expression
-```
-
-
-
-
- (n1, (n2, (mul, ())))
-
-
-
-
-```python
-infer(expression)
-```
-
-
-
-
- [(s1, (f1, s1)), (s1, (i1, s1))]
-
-
-
-
-```python
-infer(expression)
-```
-
-
-
-
- [(s1, (f1, s1)), (s1, (i1, s1))]
-
-
-
-
-```python
-for stack_effect_comment in infer(expression):
- print doc_from_stack_effect(*stack_effect_comment)
-```
-
- (-- f1)
- (-- i1)
-
-
-
-```python
-expression
-```
-
-
-
-
- (n1, (n2, (mul, ())))
-
-
-
-
-```python
-infer(expression)
-```
-
-
-
-
- [(s1, (f1, s1)), (s1, (i1, s1))]
-
-
-
-And that brings us to current Work-In-Progress. I'm pretty hopeful that the mixed-mode inferencer/interpreter `infer()` function along with the ability to specify multiple implementations for the combinators will permit modelling of the stack effects of e.g. `ifte`. If I can keep up the pace I should be able to verify that conjecture by the end of June.
+ 7 (--) ∘ [pred] [mul] [div] [nullary bool] dipd branch
+ 8 (-- [pred ...2]) ∘ [mul] [div] [nullary bool] dipd branch
+ 9 (-- [pred ...2] [mul ...3]) ∘ [div] [nullary bool] dipd branch
+ 10 (-- [pred ...2] [mul ...3] [div ...4]) ∘ [nullary bool] dipd branch
+ 11 (-- [pred ...2] [mul ...3] [div ...4] [nullary bool ...5]) ∘ dipd branch
+ 15 (-- [pred ...5]) ∘ nullary bool [mul] [div] branch
+ 19 (-- [pred ...2]) ∘ [stack] dinfrirst bool [mul] [div] branch
+ 20 (-- [pred ...2] [stack ]) ∘ dinfrirst bool [mul] [div] branch
+ 22 (-- [pred ...2] [stack ]) ∘ dip infra first bool [mul] [div] branch
+ 26 (--) ∘ stack [pred] infra first bool [mul] [div] branch
+ 29 (... -- ... [...]) ∘ [pred] infra first bool [mul] [div] branch
+ 30 (... -- ... [...] [pred ...1]) ∘ infra first bool [mul] [div] branch
+ 34 (--) ∘ pred s1 swaack first bool [mul] [div] branch
+ 37 (n1 -- n2) ∘ [n1] swaack first bool [mul] [div] branch
+ 38 (... n1 -- ... n2 [n1 ...]) ∘ swaack first bool [mul] [div] branch
+ 41 (... n1 -- ... n1 [n2 ...]) ∘ first bool [mul] [div] branch
+ 44 (n1 -- n1 n2) ∘ bool [mul] [div] branch
+ 47 (n1 -- n1 b1) ∘ [mul] [div] branch
+ 48 (n1 -- n1 b1 [mul ...1]) ∘ [div] branch
+ 49 (n1 -- n1 b1 [mul ...1] [div ...2]) ∘ branch
+ 53 (n1 -- n1) ∘ div
+ 56 (f2 f1 -- f3) ∘
+ 56 (i1 f1 -- f2) ∘
+ 56 (f1 i1 -- f2) ∘
+ 56 (i2 i1 -- f1) ∘
+ 53 (n1 -- n1) ∘ mul
+ 56 (f2 f1 -- f3) ∘
+ 56 (i1 f1 -- f2) ∘
+ 56 (f1 i1 -- f2) ∘
+ 56 (i2 i1 -- i3) ∘
+ ----------------------------------------
+ (f2 f1 -- f3)
+ (i1 f1 -- f2)
+ (f1 i1 -- f2)
+ (i2 i1 -- f1)
+ (i2 i1 -- i3)
## Conclusion
-(for now...)
+We built a simple type inferencer, and a kind of crude "compiler" for a subset of Joy functions. Then we built a more powerful inferencer that actually does some evaluation and explores branching code paths
Work remains to be done:
@@ -2489,12 +2472,11 @@ Work remains to be done:
- docstrings all around
- improve this notebook (it kinda falls apart at the end narratively. I went off and just started writing code to see if it would work. It does, but now I have to come back and describe here what I did.
-I'm starting to realize that, with the inferencer/checker/compiler coming along, and with the UI ready to be rewritten in Joy, I'm close to a time when my ephasis is going to have to shift from crunchy code stuff to squishy human stuff. I'm going to have to put normal people in front of this and see if, in fact, they *can* learn the basics of programming with it.
-
-The rest of this stuff is junk and/or unfinished material.
-
## Appendix: Joy in the Logical Paradigm
-For this to work the type label classes have to be modified to let `T >= t` succeed, where e.g. `T` is `IntJoyType` and `t` is `int`
+
+For *type checking* to work the type label classes have to be modified to let `T >= t` succeed, where e.g. `T` is `IntJoyType` and `t` is `int`. If you do that you can take advantage of the *logical relational* nature of the stack effect comments to "compute in reverse" as it were. There's a working demo of this at the end of the `polytypes` module. But if you're interested in all that you should just use Prolog!
+
+Anyhow, type *checking* is a few easy steps away.
```python
@@ -2507,373 +2489,3 @@ AnyJoyType.__ge__ = _ge
AnyJoyType.accept = tuple, int, float, long, str, unicode, bool, Symbol
StackJoyType.accept = tuple
```
-
-
-```python
-F = infer(l2s((pop, swap, rolldown, rest, rest, cons, cons)))
-
-for f in F:
- print doc_from_stack_effect(*f)
-```
-
- ([a4 a5 .1.] a3 a2 a1 -- [a2 a3 .1.])
-
-
-
-```python
-from joy.parser import text_to_expression
-```
-
-
-```python
-F = infer(l2s((pop, pop, pop)))
-
-for f in F:
- print doc_from_stack_effect(*f)
-```
-
- (a3 a2 a1 --)
-
-
-
-```python
-s = text_to_expression('0 1 2')
-s
-```
-
-
-
-
- (0, (1, (2, ())))
-
-
-
-
-```python
-F[0][0]
-```
-
-
-
-
- (a1, (a2, (a3, s1)))
-
-
-
-
-```python
-L = unify(s, F[0][0])
-L
-```
-
-
-
-
- ()
-
-
-
-
-```python
-s = text_to_expression('0 1 2 [3 4]')
-s
-```
-
-
-
-
- (0, (1, (2, ((3, (4, ())), ()))))
-
-
-
-
-```python
-F[0][0]
-```
-
-
-
-
- (a1, (a2, (a3, s1)))
-
-
-
-
-```python
-L = unify(s, F[0][0])
-L
-```
-
-
-
-
- ()
-
-
-
-
-```python
-L = unify(F[0][0], s)
-L
-```
-
-
-
-
- ()
-
-
-
-
-```python
-F[1][0]
-```
-
-
- ---------------------------------------------------------------------------
-
- IndexError Traceback (most recent call last)
-
- in ()
- ----> 1 F[1][0]
-
-
- IndexError: list index out of range
-
-
-
-```python
-s[0]
-```
-
-
-```python
-A[1] >= 23
-```
-
-## [Abstract Interpretation](https://en.wikipedia.org/wiki/Abstract_interpretation)
-I *think* this might be sorta what I'm doing above with the `kav()` function...
- In any event "mixed-mode" interpreters that include values and type variables and can track constraints, etc. will be, uh, super-useful. And Abstract Interpretation should be a rich source of ideas.
-
-
-## Junk
-
-
-```python
-class SymbolJoyType(AnyJoyType): prefix = 'F'
-
-W = map(SymbolJoyType, _R)
-
-k = S[0], ((W[1], S[2]), S[0])
-Symbol('cons')
-print doc_from_stack_effect(*k)
-
-```
-
-
-```python
-dip_a = ((W[1], S[2]), (A[1], S[0]))
-```
-
-
-```python
-d = relabel(S[0], dip_a)
-print doc_from_stack_effect(*d)
-```
-
-
-```python
-s = list(unify(d[1], k[1]))[0]
-s
-```
-
-
-```python
-j = update(s, k)
-```
-
-
-```python
-print doc_from_stack_effect(*j)
-```
-
-
-```python
-j
-```
-
-
-```python
-cons
-```
-
-
-```python
-for f in MC([k], [dup]):
- print doc_from_stack_effect(*f)
-```
-
-
-```python
-l = S[0], ((cons, S[2]), (A[1], S[0]))
-```
-
-
-```python
-print doc_from_stack_effect(*l)
-```
-
-
-```python
-
-def dip_t(F):
- (quote, (a1, sec)) = F[1]
- G = F[0], sec
- P = S[3], (a1, S[3])
- a = [P]
- while isinstance(quote, tuple):
- term, quote = quote
- a.append(term)
- a.append(G)
- return a[::-1]
-
-
-
-
-```
-
-
-```python
-from joy.utils.stack import iter_stack
-```
-
-
-```python
-a, b, c = dip_t(l)
-```
-
-
-```python
-a
-```
-
-
-```python
-b
-```
-
-
-```python
-c
-```
-
-
-```python
-MC([a], [b])
-```
-
-
-```python
-kjs = MC(MC([a], [b]), [c])
-kjs
-```
-
-
-```python
-print doc_from_stack_effect(*kjs[0])
-```
-
- (a0 [.0.] -- [a0 .0.] a1)
-
- a0 [.0.] a1 [cons] dip
- ----------------------------
- [a0 .0.] a1
-
-### `concat`
-
-How to deal with `concat`?
-
- concat ([.0.] [.1.] -- [.0. .1.])
-
-We would like to represent this in Python somehow...
-
-
-```python
-concat = (S[0], S[1]), ((S[0], S[1]),)
-```
-
-But this is actually `cons` with the first argument restricted to be a stack:
-
- ([.0.] [.1.] -- [[.0.] .1.])
-
-What we have implemented so far would actually only permit:
-
- ([.0.] [.1.] -- [.2.])
-
-
-```python
-concat = (S[0], S[1]), (S[2],)
-```
-
-
-Which works but can lose information. Consider `cons concat`, this is how much information we *could* retain:
-
- (1 [.0.] [.1.] -- [1 .0. .1.])
-
-As opposed to just:
-
- (1 [.0.] [.1.] -- [.2.])
-
-### represent `concat`
-
- ([.0.] [.1.] -- [A*(.0.) .1.])
-
-Meaning that `A*` on the right-hand side should all the crap from `.0.`.
-
- ([ .0.] [.1.] -- [ A* .1.])
- ([a .0.] [.1.] -- [a A* .1.])
- ([a b .0.] [.1.] -- [a b A* .1.])
- ([a b c .0.] [.1.] -- [a b c A* .1.])
-
-
-
-or...
-
- ([ .0.] [.1.] -- [ .1.])
- ([a .0.] [.1.] -- [a .1.])
- ([a b .0.] [.1.] -- [a b .1.])
- ([a b c .0.] [.1.] -- [a b c .1.])
- ([a A* c .0.] [.1.] -- [a A* c .1.])
-
-
-
- (a, (b, S0)) . S1 = (a, (b, (A*, S1)))
-
-
-```python
-class Astar(object):
- def __repr__(self):
- return 'A*'
-
-
-def concat(s0, s1):
- a = []
- while isinstance(s0, tuple):
- term, s0 = s0
- a.append(term)
- assert isinstance(s0, StackJoyType), repr(s0)
- s1 = Astar(), s1
- for term in reversed(a):
- s1 = term, s1
- return s1
-```
-
-
-```python
-a, b = (A[1], S[0]), (A[2], S[1])
-```
-
-
-```python
-concat(a, b)
-```
diff --git a/docs/Types.rst b/docs/Types.rst
index a8084de..dc209b5 100644
--- a/docs/Types.rst
+++ b/docs/Types.rst
@@ -1,6 +1,6 @@
-Type Inference
-==============
+The Blissful Elegance of Typing Joy
+===================================
This notebook presents a simple type inferencer for Joy code. It can
infer the stack effect of most Joy expressions. It's built largely by
@@ -476,6 +476,8 @@ integers or tuples of type descriptors:
s[u] = v
elif isinstance(v, int):
s[v] = u
+ else:
+ s = False
return s
@@ -709,6 +711,12 @@ work:
except Exception, e:
print e
+
+.. parsed-literal::
+
+ Cannot unify (1, 2) and (1001, 1002).
+
+
``unify()`` version 2
^^^^^^^^^^^^^^^^^^^^^
@@ -741,6 +749,8 @@ deal with this recursively:
s = unify(a, c, s)
if s != False:
s = unify(b, d, s)
+ else:
+ s = False
return s
@@ -1674,8 +1684,8 @@ such. Note that this is *not* a ``sqr`` function implementation:
(Eventually I should come back around to this becuase it's not tooo
difficult to exend this code to be able to compile e.g.
-``n3 = mul(n1, n2)`` for ``mul`` and insert it in the right place with
-the right variable names. It requires a little more support from the
+``n2 = mul(n1, n1)`` for ``mul`` with the right variable names and
+insert it in the right place. It requires a little more support from the
library functions, in that we need to know to call ``mul()`` the Python
function for ``mul`` the Joy function, but since *most* of the math
functions (at least) are already wrappers it should be straightforward.)
@@ -2612,7 +2622,7 @@ Part VII: Typing Combinators
In order to compute the stack effect of combinators you kinda have to
have the quoted programs they expect available. In the most general
-case, the ``i`` combinator, you can't say anything about it's stack
+case, the ``i`` combinator, you can't say anything about its stack
effect other than it expects one quote:
::
@@ -2646,8 +2656,11 @@ Obviously it would be:
Without any information about the contents of the quote we can't say
much about the result.
-I think there's a way forward. If we convert our list of terms we are
-composing into a stack structure we can use it as a *Joy expression*,
+Hybrid Inferencer/Interpreter
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+I think there's a way forward. If we convert our list (of terms we are
+composing) into a stack structure we can use it as a *Joy expression*,
then we can treat the *output half* of a function's stack effect comment
as a Joy interpreter stack, and just execute combinators directly. We
can hybridize the compostition function with an interpreter to evaluate
@@ -2655,6 +2668,13 @@ combinators, compose non-combinator functions, and put type variables on
the stack. For combinators like ``branch`` that can have more than one
stack effect we have to "split universes" again and return both.
+Joy Types for Functions
+^^^^^^^^^^^^^^^^^^^^^^^
+
+We need a type variable for Joy functions that can go in our expressions
+and be used by the hybrid inferencer/interpreter. They have to store a
+name and a list of stack effects.
+
.. code:: ipython2
class FunctionJoyType(AnyJoyType):
@@ -2670,217 +2690,212 @@ stack effect we have to "split universes" again and return both.
def __repr__(self):
return self.name
-
-
- class SymbolJoyType(FunctionJoyType): prefix = 'F'
- class CombinatorJoyType(FunctionJoyType): prefix = 'C'
-
-
+Specialized for Simple Functions and Combinators
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For non-combinator functions the stack effects list contains stack
+effect comments (represented by pairs of cons-lists as described above.)
.. code:: ipython2
- def flatten(g):
- return list(chain.from_iterable(g))
+ class SymbolJoyType(FunctionJoyType):
+ prefix = 'F'
+
+For combinators the list contains Python functions.
+
+.. code:: ipython2
+
+ class CombinatorJoyType(FunctionJoyType):
+ prefix = 'C'
+ def __init__(self, name, sec, number, expect=None):
+ super(CombinatorJoyType, self).__init__(name, sec, number)
+ self.expect = expect
+
+ def enter_guard(self, f):
+ if self.expect is None:
+ return f
+ g = self.expect, self.expect
+ new_f = list(compose(f, g, ()))
+ assert len(new_f) == 1, repr(new_f)
+ return new_f[0][1]
+
+For simple combinators that have only one effect (like ``dip``) you only
+need one function and it can be the combinator itself.
+
+.. code:: ipython2
+
+ import joy.library
+
+ dip = CombinatorJoyType('dip', [joy.library.dip], 23)
+
+For combinators that can have more than one effect (like ``branch``) you
+have to write functions that each implement the action of one of the
+effects.
+
+.. code:: ipython2
+
+ def branch_true(stack, expression, dictionary):
+ (then, (else_, (flag, stack))) = stack
+ return stack, concat(then, expression), dictionary
+
+ def branch_false(stack, expression, dictionary):
+ (then, (else_, (flag, stack))) = stack
+ return stack, concat(else_, expression), dictionary
+
+ branch = CombinatorJoyType('branch', [branch_true, branch_false], 100)
+
+You can also provide an optional stack effect, input-side only, that
+will then be used as an identity function (that accepts and returns
+stacks that match the "guard" stack effect) which will be used to guard
+against type mismatches going into the evaluation of the combinator.
+
+``infer()``
+^^^^^^^^^^^
+
+With those in place, we can define a function that accepts a sequence of
+Joy type variables, including ones representing functions (not just
+values), and attempts to grind out all the possible stack effects of
+that expression.
+
+One tricky thing is that type variables *in the expression* have to be
+updated along with the stack effects after doing unification or we risk
+losing useful information. This was a straightforward, if awkward,
+modification to the call structure of ``meta_compose()`` et. al.
+
+.. code:: ipython2
+
ID = S[0], S[0] # Identity function.
- def infer(e, F=ID):
+ def infer(*expression):
+ return sorted(set(_infer(list_to_stack(expression))))
+
+
+ def _infer(e, F=ID):
+ _log_it(e, F)
if not e:
return [F]
n, e = e
if isinstance(n, SymbolJoyType):
- res = flatten(infer(e, Fn) for Fn in MC([F], n.stack_effects))
+ eFG = meta_compose([F], n.stack_effects, e)
+ res = flatten(_infer(e, Fn) for e, Fn in eFG)
elif isinstance(n, CombinatorJoyType):
- res = []
- for combinator in n.stack_effects:
- fi, fo = F
- new_fo, ee, _ = combinator(fo, e, {})
- ee = update(FUNCTIONS, ee) # Fix Symbols.
- new_F = fi, new_fo
- res.extend(infer(ee, new_F))
+ fi, fo = n.enter_guard(F)
+ res = flatten(_interpret(f, fi, fo, e) for f in n.stack_effects)
+
+ elif isinstance(n, Symbol):
+ assert n not in FUNCTIONS, repr(n)
+ func = joy.library._dictionary[n]
+ res = _interpret(func, F[0], F[1], e)
+
else:
- lit = s9, (n, s9)
- res = flatten(infer(e, Fn) for Fn in MC([F], [lit]))
+ fi, fo = F
+ res = _infer(e, (fi, (n, fo)))
return res
+
+ def _interpret(f, fi, fo, e):
+ new_fo, ee, _ = f(fo, e, {})
+ ee = update(FUNCTIONS, ee) # Fix Symbols.
+ new_F = fi, new_fo
+ return _infer(ee, new_F)
+
+
+ def _log_it(e, F):
+ _log.info(
+ u'%3i %s ∘ %s',
+ len(inspect_stack()),
+ doc_from_stack_effect(*F),
+ expression_to_string(e),
+ )
+Work in Progress
+^^^^^^^^^^^^^^^^
+
+And that brings us to current Work-In-Progress. The mixed-mode
+inferencer/interpreter ``infer()`` function seems to work well. There
+are details I should document, and the rest of the code in the
+"polytypes" module (FIXME link to its docs here!) should be explained...
+There is cruft to convert the definitions in ``DEFS`` to the new
+``SymbolJoyType`` objects, and some combinators. Here is an example of
+output from the current code :
.. code:: ipython2
- f0, f1, f2, f3, f4, f5, f6, f7, f8, f9 = F = map(FloatJoyType, _R)
- i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 = I = map(IntJoyType, _R)
- n0, n1, n2, n3, n4, n5, n6, n7, n8, n9 = N
- s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 = S
-
-.. code:: ipython2
-
- import joy.library
+ 1/0 # (Don't try to run this cell! It's not going to work. This is "read only" code heh..)
- FNs = '''ccons cons divmod_ dup dupd first
- over pm pop popd popdd popop pred
- rest rolldown rollup rrest second
- sqrt stack succ swaack swap swons
- third tuck uncons'''
+ logging.basicConfig(format='%(message)s', stream=sys.stdout, level=logging.INFO)
- FUNCTIONS = {
- name: SymbolJoyType(name, [NEW_DEFS[name]], i)
- for i, name in enumerate(FNs.strip().split())
- }
- FUNCTIONS['sum'] = SymbolJoyType('sum', [(((Ns[1], s1), s0), (n0, s0))], 100)
- FUNCTIONS['mul'] = SymbolJoyType('mul', [
- ((i2, (i1, s0)), (i3, s0)),
- ((f2, (i1, s0)), (f3, s0)),
- ((i2, (f1, s0)), (f3, s0)),
- ((f2, (f1, s0)), (f3, s0)),
- ], 101)
- FUNCTIONS.update({
- combo.__name__: CombinatorJoyType(combo.__name__, [combo], i)
- for i, combo in enumerate((
- joy.library.i,
- joy.library.dip,
- joy.library.dipd,
- joy.library.dipdd,
- joy.library.dupdip,
- joy.library.b,
- joy.library.x,
- joy.library.infra,
- ))
- })
-
- def branch_true(stack, expression, dictionary):
- (then, (else_, (flag, stack))) = stack
- return stack, CONCAT(then, expression), dictionary
-
- def branch_false(stack, expression, dictionary):
- (then, (else_, (flag, stack))) = stack
- return stack, CONCAT(else_, expression), dictionary
-
- FUNCTIONS['branch'] = CombinatorJoyType('branch', [branch_true, branch_false], 100)
-
-.. code:: ipython2
-
globals().update(FUNCTIONS)
-
-.. code:: ipython2
-
- from itertools import chain
- from joy.utils.stack import list_to_stack as l2s
-
-.. code:: ipython2
-
- expression = l2s([n1, n2, (mul, s2), (stack, s3), dip, infra, first])
-
-.. code:: ipython2
-
- expression
-
-
-
-
-.. parsed-literal::
-
- (n1, (n2, ((mul, s2), ((stack, s3), (dip, (infra, (first, ())))))))
-
-
-
-.. code:: ipython2
-
- expression = l2s([n1, n2, mul])
-
-.. code:: ipython2
-
- expression
-
-
-
-
-.. parsed-literal::
-
- (n1, (n2, (mul, ())))
-
-
-
-.. code:: ipython2
-
- infer(expression)
-
-
-
-
-.. parsed-literal::
-
- [(s1, (f1, s1)), (s1, (i1, s1))]
-
-
-
-.. code:: ipython2
-
- infer(expression)
-
-
-
-
-.. parsed-literal::
-
- [(s1, (f1, s1)), (s1, (i1, s1))]
-
-
-
-.. code:: ipython2
-
- for stack_effect_comment in infer(expression):
- print doc_from_stack_effect(*stack_effect_comment)
-
-
-.. parsed-literal::
-
- (-- f1)
- (-- i1)
-
-
-.. code:: ipython2
-
- expression
-
-
-
-
-.. parsed-literal::
-
- (n1, (n2, (mul, ())))
-
-
-
-.. code:: ipython2
-
- infer(expression)
-
-
-
-
-.. parsed-literal::
-
- [(s1, (f1, s1)), (s1, (i1, s1))]
-
-
-
-And that brings us to current Work-In-Progress. I'm pretty hopeful that
-the mixed-mode inferencer/interpreter ``infer()`` function along with
-the ability to specify multiple implementations for the combinators will
-permit modelling of the stack effects of e.g. ``ifte``. If I can keep up
-the pace I should be able to verify that conjecture by the end of June.
+
+ h = infer((pred, s2), (mul, s3), (div, s4), (nullary, (bool, s5)), dipd, branch)
+
+ print '-' * 40
+
+ for fi, fo in h:
+ print doc_from_stack_effect(fi, fo)
+
+The numbers at the start of the lines are the current depth of the
+Python call stack. They're followed by the current computed stack effect
+(initialized to ``ID``) then the pending expression (the inference of
+the stack effect of which is the whole object of the current example.)
+
+In this example we are implementing (and inferring) ``ifte`` as
+``[nullary bool] dipd branch`` which shows off a lot of the current
+implementation in action.
+
+::
+
+ 7 (--) ∘ [pred] [mul] [div] [nullary bool] dipd branch
+ 8 (-- [pred ...2]) ∘ [mul] [div] [nullary bool] dipd branch
+ 9 (-- [pred ...2] [mul ...3]) ∘ [div] [nullary bool] dipd branch
+ 10 (-- [pred ...2] [mul ...3] [div ...4]) ∘ [nullary bool] dipd branch
+ 11 (-- [pred ...2] [mul ...3] [div ...4] [nullary bool ...5]) ∘ dipd branch
+ 15 (-- [pred ...5]) ∘ nullary bool [mul] [div] branch
+ 19 (-- [pred ...2]) ∘ [stack] dinfrirst bool [mul] [div] branch
+ 20 (-- [pred ...2] [stack ]) ∘ dinfrirst bool [mul] [div] branch
+ 22 (-- [pred ...2] [stack ]) ∘ dip infra first bool [mul] [div] branch
+ 26 (--) ∘ stack [pred] infra first bool [mul] [div] branch
+ 29 (... -- ... [...]) ∘ [pred] infra first bool [mul] [div] branch
+ 30 (... -- ... [...] [pred ...1]) ∘ infra first bool [mul] [div] branch
+ 34 (--) ∘ pred s1 swaack first bool [mul] [div] branch
+ 37 (n1 -- n2) ∘ [n1] swaack first bool [mul] [div] branch
+ 38 (... n1 -- ... n2 [n1 ...]) ∘ swaack first bool [mul] [div] branch
+ 41 (... n1 -- ... n1 [n2 ...]) ∘ first bool [mul] [div] branch
+ 44 (n1 -- n1 n2) ∘ bool [mul] [div] branch
+ 47 (n1 -- n1 b1) ∘ [mul] [div] branch
+ 48 (n1 -- n1 b1 [mul ...1]) ∘ [div] branch
+ 49 (n1 -- n1 b1 [mul ...1] [div ...2]) ∘ branch
+ 53 (n1 -- n1) ∘ div
+ 56 (f2 f1 -- f3) ∘
+ 56 (i1 f1 -- f2) ∘
+ 56 (f1 i1 -- f2) ∘
+ 56 (i2 i1 -- f1) ∘
+ 53 (n1 -- n1) ∘ mul
+ 56 (f2 f1 -- f3) ∘
+ 56 (i1 f1 -- f2) ∘
+ 56 (f1 i1 -- f2) ∘
+ 56 (i2 i1 -- i3) ∘
+ ----------------------------------------
+ (f2 f1 -- f3)
+ (i1 f1 -- f2)
+ (f1 i1 -- f2)
+ (i2 i1 -- f1)
+ (i2 i1 -- i3)
Conclusion
----------
-(for now...)
+We built a simple type inferencer, and a kind of crude "compiler" for a
+subset of Joy functions. Then we built a more powerful inferencer that
+actually does some evaluation and explores branching code paths
Work remains to be done:
@@ -2900,21 +2915,18 @@ Work remains to be done:
went off and just started writing code to see if it would work. It
does, but now I have to come back and describe here what I did.
-I'm starting to realize that, with the inferencer/checker/compiler
-coming along, and with the UI ready to be rewritten in Joy, I'm close to
-a time when my ephasis is going to have to shift from crunchy code stuff
-to squishy human stuff. I'm going to have to put normal people in front
-of this and see if, in fact, they *can* learn the basics of programming
-with it.
-
-The rest of this stuff is junk and/or unfinished material.
-
Appendix: Joy in the Logical Paradigm
-------------------------------------
-For this to work the type label classes have to be modified to let
-``T >= t`` succeed, where e.g. ``T`` is ``IntJoyType`` and ``t`` is
-``int``
+For *type checking* to work the type label classes have to be modified
+to let ``T >= t`` succeed, where e.g. ``T`` is ``IntJoyType`` and ``t``
+is ``int``. If you do that you can take advantage of the *logical
+relational* nature of the stack effect comments to "compute in reverse"
+as it were. There's a working demo of this at the end of the
+``polytypes`` module. But if you're interested in all that you should
+just use Prolog!
+
+Anyhow, type *checking* is a few easy steps away.
.. code:: ipython2
@@ -2926,383 +2938,3 @@ For this to work the type label classes have to be modified to let
AnyJoyType.__ge__ = _ge
AnyJoyType.accept = tuple, int, float, long, str, unicode, bool, Symbol
StackJoyType.accept = tuple
-
-.. code:: ipython2
-
- F = infer(l2s((pop, swap, rolldown, rest, rest, cons, cons)))
-
- for f in F:
- print doc_from_stack_effect(*f)
-
-
-.. parsed-literal::
-
- ([a4 a5 .1.] a3 a2 a1 -- [a2 a3 .1.])
-
-
-.. code:: ipython2
-
- from joy.parser import text_to_expression
-
-.. code:: ipython2
-
- F = infer(l2s((pop, pop, pop)))
-
- for f in F:
- print doc_from_stack_effect(*f)
-
-
-.. parsed-literal::
-
- (a3 a2 a1 --)
-
-
-.. code:: ipython2
-
- s = text_to_expression('0 1 2')
- s
-
-
-
-
-.. parsed-literal::
-
- (0, (1, (2, ())))
-
-
-
-.. code:: ipython2
-
- F[0][0]
-
-
-
-
-.. parsed-literal::
-
- (a1, (a2, (a3, s1)))
-
-
-
-.. code:: ipython2
-
- L = unify(s, F[0][0])
- L
-
-
-
-
-.. parsed-literal::
-
- ()
-
-
-
-.. code:: ipython2
-
- s = text_to_expression('0 1 2 [3 4]')
- s
-
-
-
-
-.. parsed-literal::
-
- (0, (1, (2, ((3, (4, ())), ()))))
-
-
-
-.. code:: ipython2
-
- F[0][0]
-
-
-
-
-.. parsed-literal::
-
- (a1, (a2, (a3, s1)))
-
-
-
-.. code:: ipython2
-
- L = unify(s, F[0][0])
- L
-
-
-
-
-.. parsed-literal::
-
- ()
-
-
-
-.. code:: ipython2
-
- L = unify(F[0][0], s)
- L
-
-
-
-
-.. parsed-literal::
-
- ()
-
-
-
-.. code:: ipython2
-
- F[1][0]
-
-
-::
-
-
- ---------------------------------------------------------------------------
-
- IndexError Traceback (most recent call last)
-
- in ()
- ----> 1 F[1][0]
-
-
- IndexError: list index out of range
-
-
-.. code:: ipython2
-
- s[0]
-
-.. code:: ipython2
-
- A[1] >= 23
-
-`Abstract Interpretation `__
------------------------------------------------------------------------------------
-
-I *think* this might be sorta what I'm doing above with the ``kav()``
-function... In any event "mixed-mode" interpreters that include values
-and type variables and can track constraints, etc. will be, uh,
-super-useful. And Abstract Interpretation should be a rich source of
-ideas.
-
-Junk
-----
-
-.. code:: ipython2
-
- class SymbolJoyType(AnyJoyType): prefix = 'F'
-
- W = map(SymbolJoyType, _R)
-
- k = S[0], ((W[1], S[2]), S[0])
- Symbol('cons')
- print doc_from_stack_effect(*k)
-
-
-.. code:: ipython2
-
- dip_a = ((W[1], S[2]), (A[1], S[0]))
-
-.. code:: ipython2
-
- d = relabel(S[0], dip_a)
- print doc_from_stack_effect(*d)
-
-.. code:: ipython2
-
- s = list(unify(d[1], k[1]))[0]
- s
-
-.. code:: ipython2
-
- j = update(s, k)
-
-.. code:: ipython2
-
- print doc_from_stack_effect(*j)
-
-.. code:: ipython2
-
- j
-
-.. code:: ipython2
-
- cons
-
-.. code:: ipython2
-
- for f in MC([k], [dup]):
- print doc_from_stack_effect(*f)
-
-.. code:: ipython2
-
- l = S[0], ((cons, S[2]), (A[1], S[0]))
-
-.. code:: ipython2
-
- print doc_from_stack_effect(*l)
-
-.. code:: ipython2
-
-
- def dip_t(F):
- (quote, (a1, sec)) = F[1]
- G = F[0], sec
- P = S[3], (a1, S[3])
- a = [P]
- while isinstance(quote, tuple):
- term, quote = quote
- a.append(term)
- a.append(G)
- return a[::-1]
-
-
-
-
-
-.. code:: ipython2
-
- from joy.utils.stack import iter_stack
-
-.. code:: ipython2
-
- a, b, c = dip_t(l)
-
-.. code:: ipython2
-
- a
-
-.. code:: ipython2
-
- b
-
-.. code:: ipython2
-
- c
-
-.. code:: ipython2
-
- MC([a], [b])
-
-.. code:: ipython2
-
- kjs = MC(MC([a], [b]), [c])
- kjs
-
-.. code:: ipython2
-
- print doc_from_stack_effect(*kjs[0])
-
-::
-
- (a0 [.0.] -- [a0 .0.] a1)
-
- a0 [.0.] a1 [cons] dip
- ----------------------------
- [a0 .0.] a1
-
-``concat``
-~~~~~~~~~~
-
-How to deal with ``concat``?
-
-::
-
- concat ([.0.] [.1.] -- [.0. .1.])
-
-We would like to represent this in Python somehow...
-
-.. code:: ipython2
-
- concat = (S[0], S[1]), ((S[0], S[1]),)
-
-But this is actually ``cons`` with the first argument restricted to be a
-stack:
-
-::
-
- ([.0.] [.1.] -- [[.0.] .1.])
-
-What we have implemented so far would actually only permit:
-
-::
-
- ([.0.] [.1.] -- [.2.])
-
-.. code:: ipython2
-
- concat = (S[0], S[1]), (S[2],)
-
-Which works but can lose information. Consider ``cons concat``, this is
-how much information we *could* retain:
-
-::
-
- (1 [.0.] [.1.] -- [1 .0. .1.])
-
-As opposed to just:
-
-::
-
- (1 [.0.] [.1.] -- [.2.])
-
-represent ``concat``
-~~~~~~~~~~~~~~~~~~~~
-
-::
-
- ([.0.] [.1.] -- [A*(.0.) .1.])
-
-Meaning that ``A*`` on the right-hand side should all the crap from
-``.0.``.
-
-::
-
- ([ .0.] [.1.] -- [ A* .1.])
- ([a .0.] [.1.] -- [a A* .1.])
- ([a b .0.] [.1.] -- [a b A* .1.])
- ([a b c .0.] [.1.] -- [a b c A* .1.])
-
-or...
-
-::
-
- ([ .0.] [.1.] -- [ .1.])
- ([a .0.] [.1.] -- [a .1.])
- ([a b .0.] [.1.] -- [a b .1.])
- ([a b c .0.] [.1.] -- [a b c .1.])
- ([a A* c .0.] [.1.] -- [a A* c .1.])
-
-::
-
- (a, (b, S0)) . S1 = (a, (b, (A*, S1)))
-
-.. code:: ipython2
-
- class Astar(object):
- def __repr__(self):
- return 'A*'
-
-
- def concat(s0, s1):
- a = []
- while isinstance(s0, tuple):
- term, s0 = s0
- a.append(term)
- assert isinstance(s0, StackJoyType), repr(s0)
- s1 = Astar(), s1
- for term in reversed(a):
- s1 = term, s1
- return s1
-
-.. code:: ipython2
-
- a, b = (A[1], S[0]), (A[2], S[1])
-
-.. code:: ipython2
-
- concat(a, b)
diff --git a/docs/sphinx_docs/_build/html/_modules/index.html b/docs/sphinx_docs/_build/html/_modules/index.html
index 22e2981..6ec713c 100644
--- a/docs/sphinx_docs/_build/html/_modules/index.html
+++ b/docs/sphinx_docs/_build/html/_modules/index.html
@@ -35,8 +35,10 @@
joy.library
joy.parser
joy.utils.generated_library
+joy.utils.polytypes
joy.utils.pretty_print
joy.utils.stack
+joy.utils.types
diff --git a/docs/sphinx_docs/_build/html/_modules/joy/library.html b/docs/sphinx_docs/_build/html/_modules/joy/library.html
index 23354be..dfa9373 100644
--- a/docs/sphinx_docs/_build/html/_modules/joy/library.html
+++ b/docs/sphinx_docs/_build/html/_modules/joy/library.html
@@ -127,7 +127,6 @@