[HN Gopher] Exotic Programming Ideas: Part 2 (Term Rewriting)
       ___________________________________________________________________
        
       Exotic Programming Ideas: Part 2 (Term Rewriting)
        
       Author : azhenley
       Score  : 96 points
       Date   : 2020-11-16 15:32 UTC (5 hours ago)
        
 (HTM) web link (www.stephendiehl.com)
 (TXT) w3m dump (www.stephendiehl.com)
        
       | georgewsinger wrote:
       | > It is true that this system can be used to program arbitrary
       | logic, it can however become a bit clunky when trying to code
       | imperative logic that works with inplace references and mutable
       | data structures. Nevertheless it is a very powerful domain tool
       | for working in scientific computing and the number of functions
       | related to technical domains that are surfaced in the language
       | represents decades of meticulous expert domain expertise.
       | 
       | Can you explain this more? Why is (i.e.) the Wolfram Programming
       | Language best thought of as a "domain" tool, and not a general
       | purpose programming language? Why couldn't someone build an
       | extraordinarily complex piece of software (say with millions of
       | lines of code) under term-rewriting paradigm (like Wolfram) in a
       | domain that has nothing to do with traditional
       | mathematics/scientific computing?
       | 
       | BTW, my favorite Wolfram fact is that it is the most concise
       | programming language on the Rosetta:
       | https://blog.revolutionanalytics.com/2012/11/which-programmi...
        
         | 6gvONxR4sf7o wrote:
         | You could. But as one of the Mathematica devout, it works be a
         | massive pain in the as compared to other languages better
         | suited for it. Assuming languages are equivalently expressive
         | (in the Turing complete sense), the difference is in what they
         | make easy.
         | 
         | It's easier to make a small web API in python than assembly,
         | even though they can do the same things. Mathematica is
         | designed to make (symbolic) mathematics easiest. It excelled
         | there at the expense of weirdness elsewhere.
         | 
         | The answer unfortunately reduces to "I dunno, try it and see."
        
           | chubot wrote:
           | Yeah I'm finding myself explaining the difference between
           | shell and Python a lot:
           | 
           | https://news.ycombinator.com/item?id=24873284
           | 
           |  _Shell is good for creating Unix systems because of the
           | tools it provides. ... It 's hard to explain, but if you work
           | in that area, you'll very quickly see it._
           | 
           | I think many people have learned one language, or 3 languages
           | that are similar, and they think all languages do roughly the
           | same thing.
           | 
           | That's incorrect on all counts: in terms of the core data
           | model, the operators, and the libraries.
           | 
           | If you do a lot of work in a specific domain, you'll start to
           | see all the differences. Shell is the best tool for bringing
           | up machines, which happens a lot in both the cloud/web and
           | embedded spaces.
           | 
           | ----
           | 
           | Ditto for R. R makes a lot of tasks really easy, and a lot of
           | other ones really hard (like being 10x slower than Python, or
           | 1000x slower than C, for many tasks). That sounds horrifying,
           | but it really helps you get a lot of work done.
           | 
           | Another thing people have trouble appreciating is the
           | difference between R and Matlab, and Matlab and Mathematica.
           | Those 3 languages are drastically different because of both
           | their core data model and who uses them.
        
           | kmill wrote:
           | It's "fine" for imperative programming if you never make a
           | mistake: the problem is that it is extremely permissive and
           | won't stop execution on errors. I usually add my own error
           | checking and default cases to cause programs to halt, and
           | these help a lot, but there are still a number of footguns.
           | 
           | One example is what happens if you map a function f over a
           | list lst: f /@ list. Oh, the variable was called lst and not
           | list? Mathematica will happily evaluate this and result in
           | list, assuming list has not been defined. This is because
           | what /@ does is apply f to each part of the expression, and a
           | Symbol has no parts. If Mathematica had a way to declare
           | which global symbols can be used in local expressions, it
           | might help prevent this class of bugs.
        
       | tgb wrote:
       | What's the magic going on in substitutions like:
       | Sin[a+b] /. Sin[x_] -> Cos[x]
       | 
       | which yields 'Cos[a+b]'. Is the underscore after the x what
       | indicates that "x_" can take any value? And Mathematica then
       | strips the undescore and replaces 'x' with that value? Why not
       | just use x_ on both sides? And earlier the author was using '_x'
       | instead of 'x_'. It seems rather mysterious to me.
        
         | 6gvONxR4sf7o wrote:
         | It matches the expression with x_ as a named wildcard (it has
         | lots of stuff you can do to constrain the matching, like "only
         | match if x is prime"). Then it replaces the whole expression
         | having x bound to whatever x_ matched.
        
         | colehasson wrote:
         | the underscore makes it a symbolic variable, not one with value
         | 
         | unbound in the lambda sense
        
           | tgb wrote:
           | So _any_ underscore at all makes it a symbolic variable? (Or
           | trailing /leading underscores only?) And the name of the
           | variable on the right-hand side of the substitution is that
           | name with the (or all?) underscore removed? It seems very
           | clunky, so I think I'm missing something.
        
             | Someone wrote:
             | Mathematica doesn't allow underscores in names. I also
             | think the use of __x_ is an error in this article
             | (disclaimer: I haven't used mathematics in decades)
             | 
             | = underscores must come at the end.
        
               | tgb wrote:
               | So is the right interpretation to think of "_" not as
               | part of the variable name at all but rather as a unary
               | operator (that acts on its left) which denotes a variable
               | as unbound?
        
               | kmill wrote:
               | A pattern like _List matches lists, but the match isn't
               | associated to a variable.
        
             | 6gvONxR4sf7o wrote:
             | At this point you seem too understand the mechanism well
             | enough that the docs will help you with the details best.
        
         | kmill wrote:
         | The left-hand side of the arrow is a pattern expression [1]
         | (though usually you'd use the :> arrow to delay evaluation of
         | the right-hand side). The "full" notation is x_H for a pattern
         | variable x that needs to have head H. Dropping the H, it
         | matches anything. Dropping the x, it just doesn't set a
         | variable when matching. Dropping both, it matches anything and
         | doesn't set a variable.
         | 
         | [1] https://reference.wolfram.com/language/guide/Patterns.html
        
           | tgb wrote:
           | Okay thanks, that all makes more sense now.
        
       | hyperpallium2 wrote:
       | I want to write a term rewriter for algebraic equations (just
       | commute, assoc, distr, etc), but I wonder about the UI: the best
       | way for a user to _specify_ which parts to transform?
       | 
       | Here's the obvious ways I've thought of - have I missed some?
       | 
       | - Derivations are usually written as before and after, and
       | sometimes the name of the transform, and the reader must work out
       | the arguments. Could have the program check the transform... but
       | I'd like to save the user all that rewriting...
       | 
       | - You could specify the arguments with a function at that point
       | (using location to address them). Seems just as bad.
       | 
       | - An awkward _path_ through the parse tree to specify each
       | argument is also possible.
       | 
       | - In a GUI, arguments could be selected by clicking.
       | 
       | - The user could name the transform, and the program
       | automatically find where it is applicable, and apply it - or give
       | a list of applications, and the user selects which one. But this
       | is a step towards an automatic theorem prover, and is more
       | automation than I want the user to have.
       | 
       | Are there other ways?
        
         | jswrenn wrote:
         | The `traces` procedure from Redex displays a navigable graph of
         | _all_ applicable transformations and their result.
         | 
         | https://docs.racket-lang.org/redex/The_Redex_Reference.html#...
        
       | siraben wrote:
       | In a similar vein, see[0] which talks about rewrite systems from
       | a more formal viewpoint. On the practice side, GHC performs
       | rewrite rules[1], an example of which is
       | "map/map"    forall f g xs.  map f (map g xs) = map (f.g) xs
       | 
       | Combining two calls to map into one. While this seems silly,
       | terms like this can arise as a result of other compiler
       | optimizations. GHC can even remove intermediate data
       | structures[2] entirely, so writing factorial as
       | factorial n = product [1..n]
       | 
       | would result in an efficient first-order program without the list
       | at all. Another example of where rewrite rules come in handy is
       | linting[3]. It does seem that a lot of these rewrites are
       | possible only in a pure language, otherwise the compiler would
       | have to resort to analyzing side-effects.
       | 
       | [0] www.cs.tau.ac.il/~nachum/papers/taste-fixed.pdf
       | 
       | [1]
       | https://downloads.haskell.org/~ghc/7.0.3/docs/html/users_gui...
       | 
       | [2]
       | https://users.cs.northwestern.edu/~robby/courses/395-495-201...
       | 
       | [3] https://github.com/ndmitchell/hlint
        
       | codekilla wrote:
       | Maude is a really cool Language based on rewriting logic.
       | 
       | http://maude.lcc.uma.es/maude31-manual-html/maude-manual.htm...
        
       | sritchie wrote:
       | If anyone wants to play with these ideas in Clojure(script), take
       | a look at the SICMUtils project:
       | https://github.com/littleredcomputer/sicmutils
       | 
       | I picked up co-maintenance on this project a few months ago; it's
       | a Clojure port of the computer algebra system behind Sussmans'
       | "Structure and Interpretation of Classical Mechanics" [0] and
       | "Functional Differential Geometry" [1], and can run all the code
       | from those books. (I'm currently working on integrating it into
       | https://Maria.cloud so all of this will be usable from the
       | browser.)
       | 
       | `sicmutils.generic.simplify` plugs in to a rule-based term
       | rewriting system like Mathematica's, along with polynomial and
       | rational function simplification.
       | 
       | The core idea of the library is that if you build everything on
       | top of extensible generic arithmetic operators (multimethods in
       | Clojure), then a lot of good stuff shows up for free:
       | 
       | - If I implement all the operations for symbols, I get a symbolic
       | arithmetic system for free. - Extend the generics to Dual
       | numbers[2]? Boom, forward-mode AD pops out!
       | 
       | I'm getting carried away. Check it out, get your term-rewriting
       | fix.
       | 
       | [0] https://mitpress.mit.edu/books/structure-and-
       | interpretation-... [1] https://mitpress.mit.edu/books/functional-
       | differential-geome... [2]
       | https://en.wikipedia.org/wiki/Dual_number
        
         | agambrahma wrote:
         | This is really cool.
         | 
         | Remember coming across SICM a long time ago, feel it gets
         | under-appreciated because it's perhaps too niche (not enough of
         | the "SICP crowd" cares about the physics, people who care about
         | the physics don't come across it).
         | 
         | A Clojure version will ensure it lives on longer (the one
         | downside of these old books is how closely they're tied to MIT-
         | Scheme)
        
       | muizelaar wrote:
       | If you want to follow along you can use without using Mathematica
       | you can use the Mathematica clone
       | https://github.com/mathics/Mathics
        
       | kmill wrote:
       | Sometimes the way I explain Mathematica to people is that, where
       | Perl is the Perl of string rewriting, Mathematica is the Perl of
       | tree rewriting.
       | 
       | Some comments about the article:
       | 
       | Mathematica has two types of rules, Rule and RuleDelayed, given
       | by a -> b and a :> b. You usually use RuleDelayed unless you want
       | Mathematica to pre-evaluate b. For example, if you create a rule
       | like x_ -> (Print[x]; 1 + x) it will immediately print the symbol
       | x and then result in the rule x_ -> 1 + x, but x_ :> (Print[x]; x
       | + 1) instead evaluates to itself without that side effect.
       | 
       | It might be worth knowing that $Assumptions has a dynamic scope
       | construct (like fluid-let in scheme), where Assuming[a > 0, ...]
       | will temporarily append a > 0 to $Assumptions.
       | 
       | The code for Diff is not correct, since it has patterns on the
       | right-hand side of the rules (and also it's Power, not Pow). For
       | idiomatic Mathematica, you would attach these rules to the Diff
       | symbol itself as "DownValues" so that Mathematica will
       | automatically apply them:                 Diff[f_ g_, x_] := f
       | Diff[g, x] + g Diff[f, x];        Diff[f_ + g_, x_] := Diff[f, x]
       | + Diff[g, x];        Diff[Power[x_, n_Integer], x_] := n*Power[x,
       | n - 1];        Diff[n_?NumericQ, x_] := 0;
       | 
       | These := operators append a rule to DownValues[Diff], where the
       | rule is exactly from replacing := with :>.
       | In[14]:= DownValues[Diff]            Out[14]=
       | {HoldPattern[Diff[f_ g_, x_]] :> f Diff[g, x] + g Diff[f, x],
       | HoldPattern[Diff[f_ + g_, x_]] :> Diff[f, x] + Diff[g, x],
       | HoldPattern[Diff[x_^n_Integer, x_]] :> n x^(n - 1),
       | HoldPattern[Diff[n_?NumericQ, x_]] :> 0}
       | 
       | The HoldPatterns in here are to prevent Mathematica from
       | evaluating the patterns, since Diff has all these rules that
       | really want to activate for those left-hand sides. (Yes,
       | patterns, too, are subject to evaluation. It can be helpful in
       | very limited circumstances, like metaprogramming the rules.)
       | 
       | It looks like it's still missing some rules, though:
       | In[42]:= Diff[x^2 + 2 x + 1, x]            Out[42]= 2 x + 2
       | Diff[x, x]
       | 
       | One fix is to change the power rule to
       | Diff[Power[x_, n_Integer: 1], x_] := n*Power[x, n - 1]
       | 
       | which gives n the default value of 1, plugging into a system
       | where Mathematica knows x^1 and x are the same.
       | 
       | ---
       | 
       | Something I use Mathematica for is calculations in things like
       | the Temperley-Lieb algebra (for calculating the Jones polynomial
       | of knots). The Temperley-Lieb algebra is pretty much a calculus
       | of path composition, but you also take formal linear combinations
       | of these, and closed loops evaluate to some polynomial. In the
       | following, P[1, 2] would denote a path between points 1 and 2.
       | SetAttributes[P, Orderless];       P /: P[a_, b_] P[b_, c_] :=
       | P[a, c];       P /: P[a_, b_]^2 := P[a, a];       P /: P[a_, a_]
       | := -A^2 - A^-2;
       | 
       | For example, P[1, 2] P[2, 3] P[3, 1] evaluates to -A^2 - A^-2. (I
       | have a library for these things where the "boxes" it renders to
       | in the Mathematica notebook is a graphical representation of the
       | paths. The graphical paths _are_ the expressions, which has been
       | very helpful to me.)
        
         | 7thaccount wrote:
         | Sometimes I think people don't fully understand how cool the
         | term rewriting is in Mathematica where it continuously expands
         | on anything from text to functions to images and audio...it's
         | pretty neat.
        
       | jswrenn wrote:
       | If you're interested in playing around with term rewriting, I
       | highly recommend the Redex framework! https://docs.racket-
       | lang.org/redex/index.html
       | 
       | It's a really neat way to model programming languages and
       | transition systems. Unfortunately, I think the documentation is
       | perhaps more intimidating than the tool (its target audience is
       | programming language theorists; not people who just like noodling
       | around with programming language design).
       | 
       | My favorite Redex hack is that you surface your Redex model as a
       | hashlang (Racket's way of providing replacing/extending the
       | language), and get a fully-featured algebraic stepper in
       | DrRacket! See this repo and PR for details:
       | https://github.com/justinpombrio/RacketSchool/pull/5
        
         | boygobbo wrote:
         | That's a really useful comment - thanks!
        
       | sriram_malhar wrote:
       | I like Maude a lot. I recommend Peter Olveczky's book
       | 
       | Designing Reliable Distributed Systems: A Formal Methods Approach
       | Based on Executable Modeling in Maude
        
       | Syzygies wrote:
       | Two term rewriting algorithms of central importance in their
       | domains: Grobner bases for systems of polynomials in many
       | variables, and the Schreier-Sims algorithm for computing with
       | permutations.
        
       ___________________________________________________________________
       (page generated 2020-11-16 21:02 UTC)