{"slug": "a-dual-view-on-syntax", "title": "A Dual View on Syntax", "summary": "A new blog post argues that the λ-calculus, typically introduced with variables and bindings, is better understood as a graph-based system where variables merely encode connections, and proposes a dual view of its syntax based on different reduction strategies. The author, writing in a series on LLM use, contends that pure λ-calculus has no true variables and that scoped bindings exist only for human readability, suggesting the underlying graph encoding is more fundamental.", "body_md": "You’re walking through a term. A term that will take you far\naway. You know where you hope this term will take you, but you can’t\nknow for sure. Yet it doesn’t matter. Now tell me why?\n\nThis will be part of a series, based on some of my notes\n(←PoW re. LLM use)\n\nNowadays, the\nλ-calculus\nis almost always introduced in the same way: There is the\nwidely-accepted minimal syntax, potentially using some surprising rule\nfor parentheses, or some very unusual way of declaring substitutions –\nno surprises. Today, I want to show a completely different (dual!) view\non its syntax, that you might find surprising, and I have found\nto be almost more natural. It is what you observe when walking through a\nλ-term\nwith different strategies, and writing down the views as you go.\n\nHowever, let’s first introduce the\nλ-calculus\nin a standard way. Well, here’s the minimal syntax introducing the two\nconstructors:\n\nNote the coinductive construction: I like to use symbol\nf\nand\na\nto mean the function and argument of applications, and\nx\nand\nb\nto mean the variable and body of abstractions.\n\nYou then have the one reduction relation to rule them all,\nβ-reduction:\n(λx.b)a⊳b[a/x],\nthat annihilates the two constructors by replacing all\nx\nin scope with\na,\nbeing especially careful to preserve the bindings of\na\nand\nb.\nUsually, this is then done using some specific strategy that aims to\nfind some kind of normal form of the term.\n\nYes, this notation works very well, especially given the semantics we\nwould expect. However, the lack of surprises is exactly what I find\nboring when trying to explore and play with it.\n\nVariables\n\nOne thing I was confused by since I learned this definition around\nfive years ago, is the concept of a variable.\n\n(terminology rant)\n\nIn the context of imperative programming languages, I always believed\nthe term “variable” to refer to its mutability. In the context\nof maths, I always believed the term “variable” to refer to its\nvariable state, as a function typically ranges over many inputs\n– if it was fixed, it would be a constant. Yet, in the\nλ-calculus\nyou cannot mutate a variable, nor can you call a function multiple times\nwith different arguments – they are anonymous! If you were to duplicate\na function to apply it to different arguments (e.g. by\nλfxy.fx(fy)),\nthere would now be two, different functions, both with a\nconstant input! After all, the most appealing part of the\nλ-calculus\nis its purity and determinism. Thus there is no such\nthing as variables in the pure\nλ-calculus.\n\nAside from the minor syntactic confusion, this “variable” term also\nconfused me semantically: What does it really mean to construct\nor destroy a binding? What exactly is the meaning of scope, and\nwhy do we need it? Do we even need bindings at all? In fact,\nnamed bindings are so annoying that the literature introducing them\neither defines complex renaming machinery\n(α-equivalence),\ntries to ignore all the common fallacies by “Barendregt convention”,\nintroduces sophisticated machinery to make scoping explicit (Hendriks and Oostrom 2003), or\nattempts using even more annoying schemes like de Bruijn\nindices/levels.\n\nJust like in the real world, I believe that fully embracing a\nreductionist point of view may present us with some valuable insights.\nAfter some time of working with many various encodings, I finally\nrealized: The\nλ-calculus\nis merely a useful abstraction modelling its underlying elementary\ncomponents. The\nλ-calculus\nis not as minimal and elegant as I thought after all! This might not\ncome to a surprise to some of you, but, indeed, variables are merely\nused for encoding the connections (wires) between its minimal\nconstructors. We do not need them if we do not care about\ntextual presentation – scoped bindings exist only for easier\ninterpretation by humans. The\nλ-calculus\nis merely a restrictive, small window into some\nunderlying graph encoding. In fact, even just the operational semantics\nof graph reduction has many more capabilities than the pure\nλ-calculus.\n\nUltimately, my goal is to have no traditional variables anywhere at\nall, thus eliminating the complexities of renaming and scoping.\n\nContinuations\n\nBut let’s start at the beginning – namely the secret that the\nholy Church (et al.) is trying to hide from you:\nContinuations are hidden within your precious\nλ-terms\nin the form of implicit, syntactic continuations! This magic\ntrick only works by restricting continuations to be\nused linearly (exactly once), as otherwise the syntax would have to be\nmulti-dimensional, which is both hard to read and write.\n\nLet’s show this using an example:\nK≏λxy.x,\na common term erasing its second argument. If we have, say,\n((KK)K),\nthe continuation of\n(KK)\nis exactly\n(□K).\n\nTo me, it helps imagining myself to be inside of the\nλ-term\nand looking in different directions, observing the views. For this\ngraphical intuition, let us draw both of the constructors as nodes\nhaving three wires. Here, all uplinks from nodes encode the\ncontinuations – the next immediate connected term.\n\nK\nthen looks like the following:\n\nObserve how the second\nλ’s\nbody\nb\nis connected to\nx,\nwhereas the first\nλ’s\nbody is not connected at all, thus matching the definition of\nK≏λxy.x.\n\nLet’s now imagine being a TikZ duck within the\n((KK)K)\nterm. What can we observe from the different positions?\n\nWizard duck\n:\nWe see the interpreter (or prompt), eager to interact\nwith the term from the perspective of the real world.\n\nEvil duck\n:\nWe are at\n(KK),\nthus seeing the other\nK\nas the right side of the outer application,\n(□K).\nWe’re not looking at the term behind us\n(□),\nnor can we see far enough to see the continuation of the constructor\nwe’re looking at.\n\nHero duck\n:\n((KK)□).\n\nUnicorn duck\n:\n(□K).\n\nHarlequin duck\n:\n(K□).\n\nYou might rightly point out here that I have given no reason why the\nducks cannot see far enough to observe the continuations of the terms.\nThis is exactly the idea behind this entire blog post: There is no\nreason! The only problem is that we then observe the terms from\nbelow, which is not the usual perspective for looking\nupon terms. So, let’s figure out, incrementally, what is required for\nthis unusual perspective.\n\nYou can observe how there is seemingly made no difference between the\n“variable” and “body” fields of an abstraction – they are both just\nwires. It therefore makes sense to view continuations as just another\ncommon wire. Indeed, we can describe the constructors as triples\nλ(k,x,b)\nand\nα(f,a,k),\nwhere\nx,b,f,a,k\nare now symbols that connect the constructors with each other\nby having the same name. There is no more syntax-enforced ordering of\nthese constructors; they can just be put into a set, where\nk\nis the prompt:\nK≏{λ(k,x,b),λ(b,y,x)}\n\nHowever, even this does not provide us with the real\nobservable views that the ducks have at each position – from the\nperspective of any wire, we now only see even more wires attached to\nnodes! To continue, we need to recognize another property.\n\nPolarization\n\nTerms have a polarity1 that divides them into\nproducing (positive) and\nconsuming (negative) components. For\nexample, choosing by coin-flip that constructors are producers,\nwe see that\nK,\nλx.x,\nand even\n(KK)\nall have positive polarity. The negative polarity only becomes relevant with the\nintroduction of variables, as they wire the individual components with\ndifferent polarities together. Just as in the real world, wires go from\npositive to negative polarity, and never connect two components of the\nsame polarity. Specifically, you can imagine wires as enabling the\ncommunication between the dual worlds of terms and coterms.\nLet’s look at the previous graph with arrows as wires:\n\nThe reasoning behind the choice of polarization is as follows: Both\nfunction\nf\nand argument\na\nof applications may contain other producing constructors as immediate\nsubterms, therefore they must be negative, as\nthey are wired to a producers’s continuation\nk.\nSimilarly for the abstraction’s body\nb.\nOnly the abstraction’s variable binding is in a confusing position: As\nvariables may be used in consuming position (e.g. in\n(xy)),\nthe bound occurrences must be negative,\nwhereas the binding instance must be positive.2\n\nThe final set of polarized constructors is therefore:\n{λ(k,x,b),α(f,a,k)}.\n\nAs an aside, this duality of opposing polarity between both\nconstructors is precisely the reason why\nβ-reduction\nworks in the first place. During\nβ-reduction,\nthe following abstract components flow into each other: The\napplication’s function\nf\ninteracts with the abstraction’s continuation\nk\n((λx.b)a),\nthe application’s argument\na\nflows into the abstraction’s\nx\nbinding\n([a/x]),\nand the substituted body\nb\ngets returned to the application’s continuation\nk\n(⊳b[a/x]).\n\n{}λ(k,x,b),α(↑↑↓α(f,a,k)\n\nWe have now observed that there is almost nothing special about\nvariables, they are just encoding directed wires. Therefore, in my\nopinion, the syntax\nλx.b\nis downright confusing – it puts\nx\ninto such a special spot, not allowing for immediate subterms, even\nthough technically a notation exactly dual to\nb\ncould be used.\n\nLet me show what I mean, by deriving the syntax by walking through a\ngraph of\nK:\nIn the previous notation\nλxy.x,\nwe traverse the body\nb\n(i.e. λy.x\nand\nx)\nfirst, and only then wire the remaining free symbols back to a binding\ninstance in producing position\nx.\nInstead, we could also traverse the subterms from the other side (the\nbinding instance), thus observing a different view of the traversed\nconstructors.\n\nSyntax\n\nWe are now finally able to slightly abstract over the\nconcept of wires and explicit continuations again. Here’s all the\ndifferent views in a single syntax:\n\nwhere\nv-wire/abstraction/application\nmeans that we view the wire/abstraction/application from the\npoint of\nv,\nthus observing only its fields excluding\nv.\nNote how all colorings are exactly dual – in fact, as all of the\nvariations of abstraction and applications have differently colored\nparentheses, I might as well have used the same symbols (which is why\nthey’re sometimes described as a single\nζ-constructor\n(Mazza 2007)). Note also, how\nthe coloring swapped in comparison to before: Previously,\nb,f,a,x,k,\nwere merely wires – now they can represent subterms directly, without\nrequiring the wire’s indirection. The wires still connect opposing terms\nby having the same name, thus having opposite polarization in syntax as\nwell.\n\nWe can now view both constructors from all three sides. For the\nabstraction:\n\nUnicorn duck\n:\n[xb]\n\nWizard duck\n:\n[bk]\n\nHarlequin duck\n:\n[kx]\n\nYou can see that the colors match exactly whether the observable term\nat the other sides of the wires are consumers/producers.\n\nOf course, compared to before, we can now actually see the\nterms behind consumers without indirection. Let’s take our beloved\nK\nas example. The standard notation now becomes\n[x[yx]]\n(≏λx.λy.x).\nHowever, a different view shows up if we instead start walking from the\nabstraction’s\nx:\n[[by]b],\nwhere\nb\nis indeed a\nk\n(try walking the term in your head!)\n\nJust as with\ny\nin traditional\nλ-calculus,\nit occurring only as binding instance\ny\nmeans that nodes trying to consume it shall effectively get\ngarbage-collected – similar with symbols not being bound at all (free\nvariables). The difference in our syntax is that there is no\nscoping involved – “variables” never get “instantiated”, as\nthey do not exist as such, they merely wire the positive and negative\nterms together. Barendregt convention emerges by construction!\n\nTheoretically, you could traverse nodes multiple times, thus\nresulting in larger notations that would not represent the original term\nanymore. In practice, figuring out the notation for a given\ngraph-encoded\nλ-term\ncan be done using standard BFS-style graph traversal by storing each end\nof a visited wire in a visited set.\n\nHowever, we can still go further without misrepresenting the term.\nCurrently, we start traversal from the real world “prompt” continuation.\nInstead, we can use explicit starting points in our calculus to allow\nspecifying one of the ends of a wire at the top-level:\n\nViewspp::=::=σbσkσ−termσ−term\n\nFor example, setting as invariant that\nk\nalways exists as wire to the outermost continuation (the prompt), we can\ntraverse\nK\nagain, this time from\nk\nexplicitly:\nk[x[yx]],\nand\nk[[by]b].\nYet, we could also start from, say,\ny:\ny[x[kx]],\nor even\ny[[bk]b].\nI’ll leave it as a task to the reader to find the remaining 4 valid\nencodings.\n\nHere’s the route you got to take to observe\ny[x[kx]]\n(red path) and\ny[[bk]b]\n(blue path):\n\nIt might be interesting to observe that this is now a combinatorial\nproblem. For example, to count the number of valid encodings of a Church\nnumeral\nn,\nwe’d calculate\n(n+4)[(n+33)+(n+12)].\nHere are the 78 valid encodings of\nλsz.s(sz):\n\nView\n\na([[k(za)]z]s)\n\na([[ks](as)]s)\n\na([b(a[bk])]s)\n\na([b(as)][bk])\n\na(b1[[(as)b1]k])\n\nb1[[k((sz)b1)]z]\n\nb1[[k(ab1)](as)]\n\nb1[[k(z(b1s))]z]\n\nb1[[ks]((b1s)s)]\n\nb1[b((b1[bk])s)]\n\nb1[b((b1s)[bk])]\n\nb[k((s[b1b])b1)]\n\nb[k((sz)[bz])]\n\nb[k([(sa)b]a)]\n\nb[k([b1b](b1s))]\n\nb[k(a[b(as)])]\n\nb[k(z([bz]s))]\n\ns(([[zb1]k]z)b1)\n\ns(([bk][b1b])b1)\n\ns(([bk]z)[bz])\n\ns((s[b1[ks]])b1)\n\ns((sz)[[ks]z])\n\ns([([bk]a)b]a)\n\ns([(sa)[ks]]a)\n\ns([b1[k(ab1)]]a)\n\ns([b1[ks]](b1s))\n\ns([b1b](b1[bk]))\n\ns(a[[k(za)]z])\n\ns(a[[ks](as)])\n\ns(a[b(a[bk])])\n\ns(z([[ks]z]s))\n\ns(z([bz][bk]))\n\ns(z(b1[[zb1]k]))\n\nz(([[ks]z]s)s)\n\nz(([bz][bk])s)\n\nz(([bz]s)[bk])\n\nz((b1[[zb1]k])s)\n\nz((b1s)[[zb1]k])\n\nz(a[[z(sa)]k])\n\na([[z(sa)]k]z)\n\na([bk][(sa)b])\n\na(s[([bk]a)b])\n\na(s[(sa)[ks]])\n\na(s[b1[k(ab1)]])\n\nb1([[(as)b1]k]a)\n\nb1([[zb1]k](sz))\n\nb1([bk](s[b1b]))\n\nb1(s([[zb1]k]z))\n\nb1(s([bk][b1b]))\n\nb1(s(s[b1[ks]]))\n\nb[((b1[bk])s)b1]\n\nb[((b1s)[bk])b1]\n\nb[(a[bk])(sa)]\n\nb[(as)([bk]a)]\n\nb[z([bk](sz))]\n\nb[z(s([bk]z))]\n\nk[((s[b1b])b1)b]\n\nk[((sz)[bz])b]\n\nk[((sz)b1)[zb1]]\n\nk[([(sa)b]a)b]\n\nk[([b1b](b1s))b]\n\nk[(a[b(as)])b]\n\nk[(ab1)[(as)b1]]\n\nk[(z([bz]s))b]\n\nk[(z(b1s))[zb1]]\n\nk[(za)[z(sa)]]\n\nk[s[((b1s)s)b1]]\n\nk[s[(as)(sa)]]\n\nk[s[z(s(sz))]]\n\ns[[((b1s)s)b1]k]\n\ns[[(as)(sa)]k]\n\ns[[z(s(sz))]k]\n\nz[([bk](sz))b]\n\nz[(s([bk]z))b]\n\nz[(s(sz))[ks]]\n\nz[(sa)[k(za)]]\n\nz[b1[k((sz)b1)]]\n\nz[b1[k(z(b1s))]]\n\nInference\n\nYou may have realized that I have omitted the highlighting of positive/negative\nparentheses. This is in part due to being lazy, but also due to being\nable to always infer it based on the polarity of its subterms (which,\ninductively, can be inferred based on the polarity of the symbols). In\nfact, one can even infer the polarity of all the symbols based on the\npolarity of a single symbol. Making use of the invariant of\nk,\nwe can infer its neighboring term’s polarity, and then inductively the\npolarity of all the terms. This is, with the assumption that we follow\nstandard restrictions of\nλ-terms.\nHere’s a small sketch of how this inference might look like, typing\nterms\n⊢e:τ\neither as positive\n(⊢e:+)\nor negative\n(⊢e:−),\narranged to showcase the duality:\n\nAnd, indeed, there is no other valid coloring, as\nk\ncannot be combined with negative terms – you\nwould also have to swap the positions to\nk[[xy]x].\n\nSemantics\n\nLet’s assume that, for simpler reduction, the term structure is fully\nlifted again – every subterm is a wire to another term, there are no\nimmediate subterm relations. We can easily transform from the graph\nencoding into a map of\nσ→e,\nσ→e,\ninterconnected by wires.\n\nHowever, let’s still keep the notation of immediate\nsubterms,\ne.g. ([xb]a)\ninstead of\n{σ1↦[xb],k↦(σ1a),…}.\nA traditional\nβ-redex\nthen looks like\n([xb]a)⊳b,[a/x],\nwhere\n[a/x]\nis now a global substitution (remember, there is no lexical\nscope). We assume that any\nx/x\nalways has matching\nx/x\nsomewhere, or it is supposed to get (co-)erased (nothing gets\nsubstituted)\n\nDue to the many different introduced views, there now exist multiple\nways to write this\nβ-redex.\nAnd, of course, they are perfectly dual again, encoding the flows of\nβ-reduction\neven inbetween:\n\nThe subfix\nv\nin\nev\nindicates the view from which we look upon the redex.\n\nHere are all combinations of different views on abstractions and\napplications and whether they form valid\nβ-redexes:\n\nTerm\n\n[kx]\n\n[xb]\n\n[bk]\n\n(fa)\n\n𐄂\n\n✔\n\n𐄂\n\n(ak)\n\n✔\n\n𐄂\n\n✔\n\n(kf)\n\n𐄂\n\n✔\n\n𐄂\n\nYou may realize that technically there are many more valid\nterms involving both abstraction and application in a subterm relation\n(e.g. [(fa)k]\nor\neven [(kf)b]).\nHowever, this relation is not between the abstraction’s\nk\nand the application’s\nf.\nYet, it turns out that these other terms have very interesting behavior\nwhen seen as redexes, and I plan to discuss them in one of my next posts\n(RSS) – as a spoiler: call/cc and fixpoint\nrecursion just appear out of nowhere!\n\nNext\n\nThere are many more things I plan to discuss next. For example, I did\nnot yet discuss the programs where unscoped bindings are truly useful\nin practice. What I also did not explain yet, is that these\nwired bindings are in fact hyperedges: Both of its sides may connect to\nmultiplepositive/negative terms. Theoretically our syntax then allows\nmentioning\nk\n(in fact, any continuation symbol) multiple times, or not at\nall. This goes exactly against one of the imposed restrictions I\ndiscussed in the beginning, namely that continuations must be used\nlinearly. The concept of duplicating continuations is typically referred\nto as superposition, coduplication, cocontraction, or even multiple\nresumption. In our case it is\nn-ary,\nas\nk\ncan be mentioned an arbitrary number of times, therefore implementing\ncoerasure/coweakening as well. The idea is that, since constructors are\nproducers, referring to multiple continuations of constructors as a\nsingle continuation essentially merges (superposes) them into a\nsingle producer. We will see that such cases are exactly where we\nactually require the use of dual syntax, non-existent in\ntraditional\nλ-calculus.\n\nRelated Work\n\nYou may be interested in further information about the duality of\nλ-abstraction\nand its graph encodings. Here are some resources related to this\npost:\n\nZeilberger (2016) presents a\ngraph encoding for the linearλ-calculus\nwith support for re-rooting the graph (without syntax)\n\nLafont (1997) and Mazza (2007) present (symmetric)\ninteraction combinators as an encoding of programs as graphs of nodes,\nthough they use explicit, incremental resource management (problematic\n(Asperti 2017)) and do not\npresent a dual syntax\n\nOstermann et al. (2022)\naim to develop a shared symmetric syntax for producer/consumer symmetry\n(not based on graphs)\n\nAs always, thanks for reading! Don’t take anything too serious here,\nit may contain many mistakes, as it is mostly a playground. I’d still\nlove your input on this, I’m very interested in formalization and\nadditional details. There might be a lot more coming in the next few\nmonths though, anyway.\n\nAlso, I’ll be at the “coICFP” FPW26 in Paris next week, presenting\n“Graph Rewriting via Lexical Effect Handlers” at HOPE, introducing an\napproach for effect-directed\nprogramming. Let me know if you want to meet or explore\nParis together! (email, signal,\ndiscord)\n\nAsperti, Andrea. 2017. “About the Efficient Reduction of Lambda\nTerms.”arXiv Preprint arXiv:1701.04240.\n\nCurien, Pierre-Louis, and Hugo Herbelin. 2000. “The Duality of\nComputation.”ACM Sigplan Notices 35 (9): 233–43.\n\nHendriks, RDA, and Vincent van Oostrom. 2003. “Adbmal.”Lecture Notes in Computer Science 2741: 136–50.\n\nLafont, Yves. 1997. “Interaction Combinators.”Information and Computation 137 (1): 69–101.\n\nMazza, Damiano. 2007. “A Denotational Semantics for the Symmetric\nInteraction Combinators.”Mathematical Structures in Computer\nScience 17 (3): 527–62.\n\nOstermann, Klaus, David Binder, Ingo Skupin, Tim Süberkrüb, and Paul\nDownen. 2022. “Introduction and Elimination, Left and\nRight.”Proceedings of the ACM on Programming Languages\n6 (ICFP): 438–65.\n\nSabry, Amr, and Philip Wadler. 1997. “A Reflection on\nCall-by-Value.”ACM Transactions on Programming Languages and\nSystems (TOPLAS) 19 (6): 916–41.\n\nZeilberger, Noam. 2016. “Linear Lambda Terms as Invariants of\nRooted Trivalent Maps.”Journal of Functional\nProgramming 26: e21.\n\n动态网自由门 天安門 天安门 法輪功 李洪志 Free Tibet 六四天安門事件 The\nTiananmen Square protests of 1989 天安門大屠殺 The Tiananmen Square Massacre\n反右派鬥爭 The Anti-Rightist Struggle 大躍進政策 The Great Leap Forward\n文化大革命 The Great Proletarian Cultural Revolution 人權 Human Rights 民運\nDemocratization 自由 Freedom 獨立 Independence 多黨制 Multi-party system 台灣\n臺灣 Taiwan Formosa 中華民國 Republic of China 西藏 土伯特 唐古特 Tibet\n達賴喇嘛 Dalai Lama 法輪功 Falun Dafa 新疆維吾爾自治區 The Xinjiang Uyghur\nAutonomous Region 諾貝爾和平獎 Nobel Peace Prize 劉暁波 Liu Xiaobo 民主 言論\n思想 反共 反革命 抗議 運動 騷亂 暴亂 騷擾 擾亂 抗暴 平反 維權 示威游行 李洪志\n法輪大法 大法弟子 強制斷種 強制堕胎 民族淨化 人體實驗 肅清 胡耀邦 趙紫陽\n魏京生 王丹 還政於民 和平演變 激流中國 北京之春 大紀元時報 九評論共産黨 獨裁\n專制 壓制 統一 監視 鎮壓 迫害 侵略 掠奪 破壞 拷問 屠殺 活摘器官 誘拐 買賣人口\n遊進 走私 毒品 賣淫 春畫 賭博 六合彩 天安門 天安门 法輪功 李洪志 Winnie the\nPooh 劉曉波动态网自由门", "url": "https://wpnews.pro/news/a-dual-view-on-syntax", "canonical_source": "https://text.marvinborner.de/2026-08-11-17.html", "published_at": "2026-08-17 11:17:07+00:00", "updated_at": "2026-08-17 11:41:43.410749+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models"], "entities": ["λ-calculus", "Hendriks", "Oostrom", "Barendregt"], "alternates": {"html": "https://wpnews.pro/news/a-dual-view-on-syntax", "markdown": "https://wpnews.pro/news/a-dual-view-on-syntax.md", "text": "https://wpnews.pro/news/a-dual-view-on-syntax.txt", "jsonld": "https://wpnews.pro/news/a-dual-view-on-syntax.jsonld"}}