
What is Symbolic Computation? - stevelosh
http://stevelosh.com/blog/2016/06/symbolic-computation/
======
gue5t
The difference between adding symbols to a language with product types (i.e.
consing) and simply having algebraic data types in your language is that
symbols are untyped, and so you're allowed to compare apples and oranges
(they're inequal).

This isn't terribly useful, as it's almost always a mistake to do it. If you
_do_ have a context in which you want to compare apples and oranges, algebraic
datatypes force your hand to declare which types of things you'd like to
compare across (e.g., specify that you're comparing any fruits, and that
apples and oranges are fruits). This clarity helps form a clear mental model
of how your software works and avoid bugs.

Lisp is the "hold my beer, I know what I'm doing" of functional programming
languages. It, like other languages that take this stance, should be laid to
rest. Time has demonstrated that programmers do not know what we're doing, and
need all the machine-provided help we can get.

~~~
taeric
This is... unproductive. Time has demonstrated that language is just one part
of the tooling story when it comes to developing software. It is easily one of
the most talked about, but ultimately it is just another tool.

That LISP structured languages have not died despite many instances of people
insisting they should have died already is almost evidence on its own that
this is a non-starter.

For more relevant disputes, consider [https://docs.racket-lang.org/ts-
guide/](https://docs.racket-lang.org/ts-guide/). Specifically, you can use
tooling to get types into a LISP.

~~~
gue5t
I have no dogma about where you implement your typechecker; all I assert is
that it's better to have one. The LISP philosophy is (in my impression from
the literature) that with powerful enough abstractions, one writes so little
code that there is hardly any room for mistakes to sneak in, so typecheckers
could provide little benefit. In one sense this thesis is plausible: at some
level you either stop encoding the specification of your intended
functionality into your types, or you end up programming in a theorem-proving
language like Coq or Agda, which everyone seems to agree is bad for
productivity. But I think we should be trying to move toward more
specifications, not fewer, and solve the ergonomics problems as we encounter
them.

Unrelatedly, I have worked in Typed Racket and the performance is so poor that
I can't advocate its use. Of course you can profile and optimize anything, and
avoid writing code which typechecks slowly, but such static analyses seem the
sort of thing where incremental computation (caching, and amortization across
executions of the typechecker) should make even very powerful analyses finish
quickly. And Racket doesn't take advantage of the results of its typing
analyses to optimize runtime representations of datatypes, so even after
typechecking your code runs very slowly and uses a lot more memory than it
needs.

~~~
Jtsummers
> I have no dogma about where you implement your typechecker; all I assert is
> that it's better to have one.

You seem to be suggesting that types are absent from lisp, that's not the
case.

Common Lisp, Scheme, and Emacs Lisp all make use of types. They are, to a
first approximation [0], all strongly typed and dynamically typed languages.

Since you're not dogmatic about the where of it, try computing:

    
    
      (+ 2 'atom)
    

In your favorite common lisp. It will result in an error, specifically a
`SIMPLE-TYPE-ERROR`.

You can also specify types in function declarations in Common Lisp:

    
    
      (defun double-integer (x)
        (declare (type integer x))
        (* 2 x))
    

This will only accept integers now. Other types passed in as a parameter to
`double-integer` will result in an error.

You can apply this at any level once you start building up your own types as
well (structs, classes, etc.). The benefit here is that you can leave your
code wide-open to allow for many cases at some stages, while optimizing or
constraining it in others. This malleability is fantastic in lisps.

While I'm certainly a great fan and proponent of static type checking, the
benefits of lisp's concepts (the malleability of its semantics and syntax),
make it a great force-multiplier for component users. A great number of
concepts become practically trivial when given the level of abstraction that
lisps permit.

A good type system can give you something akin to a calculus of types.

A good functional language (here I'll include lisps even though many these
days seem to discard them as such) give you a sort of calculus over
computation.

But the structure of lisp (particularly its homoiconic nature) gives you a
calculus over your programs syntax and semantics.

[0] The places where they may be considered weakly typed are places where it's
somewhat "natural". Such as in Common Lisp where there's the numeric tower.
Technically your number does stay a number, but it moves from an integer to,
say, a rational when you perform a division by a non-divisor integer. It
maintains the highest level of precision/accuracy possible. So `(/ 1 3)`
doesn't become some 32-bit float version like `0.3333334`, but rather `1/3`,
nor does it do the C thing and turn it into 0.

~~~
gue5t
I don't see how "typed/untyped" is a meaningful distinction if you consider
"dynamic types" to be types. If the only observable effect of the typechecker
in a dynamically-typed language is a runtime error, how do we distinguish an
arbitrary source of runtime errors (e.g. "throw" or "error" statements) from a
dynamic typechecker? Obviously it isn't the presence of the word "type" in
error messages, but what _is_ it? In the presence of the metaprogramming you
describe, we can't say it's down to whether checks are inserted automatically
either, since non-type dynamic checks may also be inserted pervasively and
automatically with metaprogramming.

If Lisp is typed, what's an "untyped" language? One in which the user hasn't
done metaprogramming to insert runtime checks? Or is the distinction just
about whether it's the end programmer or the language implementor who does the
metaprogramming? None of these seem like very useful distinctions.

If it's lispy to actually do a separate typechecking pass, please forgive my
ignorance.

~~~
zem
i'd say an untyped (well, weakly typed; every language has _some_ notion of
types) language was one like C where you can construct a value of some type,
and then treat the block of memory in which the physical representation of
that value is stored as some other type.

that said, eric lippert has a good blog post on [defining our terms more
precisely before even asking the
question]([https://ericlippert.com/2012/10/15/is-c-a-strongly-typed-
or-...](https://ericlippert.com/2012/10/15/is-c-a-strongly-typed-or-a-weakly-
typed-language/))

~~~
telotortium
There are in fact typeless languages that are neither statically nor
dynamically typed, although they aren't particularly relevant nowadays --
BCPL, Forth, and assembly, among others. These languages have only words that
hold integer values, with different operators treating the words as an
integer, pointer, etc. Those who claim that only static types are worthy of
the name "type" would probably consider both dynamically typed and typeless
languages to be equally untyped -- dynamically typed languages just support
richer semantics on their values, such as storing a "tag" (i.e., dynamic type)
in each value.

------
agentultra
Has anyone here read _Programming in the 1990s_ or _A Logical Approach to
Discrete Mathematics_?

Symbolic computing is a powerful concept. How does one model something simple
that we take for granted such as assignment? Here's a hint: look to Liebniz
and Tony Hoare. Symbols and syntactic substitution give you powerful
abstractions over expressions. Everything in Lisp is an expression. Symbols
give you the ability to develop concepts built up from complex expressions.
They let you reason about substitution, equivalence, etc. That's the real
power: they're a tool that lets you think above the code.

And as others have pointed out, Common Lisp is typed. Type-declarations,
though optional, are amenable to static analysis. And the interesting thing
about Lisp that I haven't explored much yet is the ability to generate a
specification in a higher-level language like PVS perhaps and generate the
type annotations from that. The implementation would then have to satisfy the
annotations in order to validate the program.

Nice article. _A Gentle Introduction_ is a neat book.

------
lispm
Symbolic Computing means the computation of formulas which not only contain
numbers and its operators, but also names which stand for something (a
variable in some calculus, a function in some calculus, a plan operator, a
note, ...). Thus McCarthy developed a language for computing with symbolic
expressions: Lisp. One of the things he/they found out was that Lisp itself
can be described as symbolic computation, shown by the eval procedure and by
representing Lisp programs as Lisp data. But generally he had all kinds of
applications in mind. One of the first applications were logic formalisms and
algebra. In this case one can not only evaluate algebraic expressions, but
also do computation with them...

This is from an early Computer Algebra program (Macsyma), which was written in
Lisp and whose development has roots back into the 60s. Though Macsyma is
written in Lisp and provides a Read-Eval-Print-Loop, it does not use
s-expression-based syntax. Instead it uses a syntax, which should be familiar
to most people doing algebraic calculations.

expr1 is a variable and we set it to a formula.

    
    
        (%i5) expr1:x^28 + 1;
                                             28
        (%o5)                               x   + 1
    

we can sum two expressions:

    
    
        (%i6) expr1 + expr1;
                                              28
        (%o6)                              2 x   + 2
    

exponent:

    
    
        (%i7) expr1^expr1;
                                                 28
                                         28     x   + 1
        (%o7)                          (x   + 1)
    

Let's factor an expression:

    
    
        (%i8) factor(expr1*3);
                           4        24    20    16    12    8    4
        (%o8)          3 (x  + 1) (x   - x   + x   - x   + x  - x  + 1)
    

Subtract 1 and y from the expression:

    
    
        (%i9) % - 1 - y;
                            4        24    20    16    12    8    4
        (%o9)     - y + 3 (x  + 1) (x   - x   + x   - x   + x  - x  + 1) - 1
    

Factor the expression:

    
    
        (%i10) factor(expr1);
                          4        24    20    16    12    8    4
        (%o10)          (x  + 1) (x   - x   + x   - x   + x  - x  + 1)
    

Subtract 1:

    
    
        (%i11) % - 1;
                        4        24    20    16    12    8    4
        (%o11)        (x  + 1) (x   - x   + x   - x   + x  - x  + 1) - 1
    

Simplify it:

    
    
        (%i12) ratsimp(%);
                                               28
        (%o12)                                x
    

Same idea: interactive computing with symbolic expressions, this time
expressions from the domain of Algebra. The language also allows us to write
programs:

    
    
        (%i14) myfact(n) := if n = 0 then 1 else n * myfact(n - 1)$
    
        (%i15) myfact(10);
        (%o15)                              3628800
    

It allows us to write functions for symbolic expressions, for example to
implement integration, etc.

~~~
leephillips
If you like that, it lives on in the open-source "maxima" (just an apt-get
install away).

------
diogofranco
What an awesome resource to introduce someone to lisp. I would add "On Lisp"
by Paul Graham to the final recommendations. If you don't mind quite a bit of
opinionated writing, "let over lambda" can be a fun read too.

------
shmerl
In the simple sense, I understand symbolic computation as transformation of
text. In more general sense, all information is based on symbols ("letters"),
so transforming one information into another is a symbolic computation.

~~~
wschroed
Amateur perspective here...

My understanding is that it is the transformation of lists of symbols. This is
one step above parsing characters/runes into more meaningful primitive data.
Lisp has facilities for manipulating trees of data. The book Land of Lisp
makes this distinction clear in an interesting way: they implement a text
adventure, and they separate the notion of words or concepts (model) from
their final string representation (view). For example, you can express a
phrase with something like

    
    
      '(you see exits leading (exit east) , (exit west) , and (exit north))
    

or

    
    
      '(you see exits leading (series (exit east) (exit west) (exit north)))
    

Run this through your parser for that list of lists of symbols, and you may
end up with

    
    
      You see exits leading [east], [west], and [north].
    

where east, west, and north are highlighted if expressed on the console or
converted into appropriate URLs if expressed in HTML. You can also imagine a
potential here for language conversions.

Along similar lines -- with due credit to the PAIP book -- you could separate
the notion of some mathematical formula from its inputs. Do manipulations,
simplifications, whatever, and THEN provide inputs. This suggests that with
symbolic computation and some macros, you can assist the Lisp compiler in
coming up with the speediest version of some formula. Or expression. Or
template output.

Not sure how to get an exact link to this comment, but look at sklogic's back-
and-forth with me here for more uses of this:
[https://news.ycombinator.com/item?id=11589614](https://news.ycombinator.com/item?id=11589614)

------
eggy
Peter Kogge's 'The Architecture of Symbolic Computers' is a great book on Lisp
and Lisp machines and much more. It explains a lot of the questions this post
has elicited.

------
tambourine_man
I think there's an extra "the" in the sentence:

 _If you already know the some Lisp…_

------
tropo
Symbolic computation is when you evaluate a binary executable as a math
expression. You might try to solve for inputs that cause a crash. It's
difficult due to well-known issues with the famous halting problem, but in
practice you can get useful things done.

~~~
hcs
Are you thinking of symbolic _execution_?

