What are invariants in programming languages and why is it important? [closed] - programming-languages

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
Can anyone explain what the invariants in programming languages are and why they matter?

What are Invariants
Invariants in any field - are values (usually numbers) that allow you to distinguish "objects" if those invariants are not the same.
For example if you have a mathematical term say
(x+3)²+1
and you want to transform that term one invariant would be to substitute a random number for x, my rng chose x=0 - so the invariant would be
(0+3)²+1 = 9+1 = 10
then if I transform the term incorrectly
x²+6x+3 + 1 = x² + 6x +4
testing again with x = 0 I see 0²+0+4 = 4 which is different from 10 therefore I know there had to be a mistake.
But if on the other hand I had transformed the term to
x²+3x+9 +1 = x²+3x+10
the invariant for x=0 would be 10 again - so we see
Why are they useful?
different invariants => different objects
same invariants => maybe same objects
Example: equational reasoning
why has this become interesting in (functional) programming - one expression you will hear in this context is equational reasoning and this means just the procedure I did above if you can transform an algorithm/function/term into another one without loosing equality. This is often true for languages like haskell, by restriction of immutability, no side effects etc. whereas in oo this is often not true. Equational reasoning allows you to shrink the area where errors turned up quite good so debugging/bug finding is comparatively more easily done.
Example: property based testing
Another field where invariants are common is property based testing: here the standard example for this reverse :: [a] -> [a], i.e. the reverse function on (linked) lists, has the property of reverse . reverse == id, i.e. reversing twice is the same as doing nothing.
If you put this in a Quickcheck test - the test generator generates arbitrary lists and checks this property - if one of these (potentially) thousands of tests fail you know, where to improve your code.
Example: Compiler optimizations
Some properties also can used to make optimizations of your code for example if for all functions fmap f . fmap g == fmap (f . g) and the left hand side traverses a data structure twice, where the right hand side only does one traversal, the compiler can substitute them and make your code twice as fast.

An invariant is a property of your data that you expect to always hold. Invariants are important because they allow you to separate business logic from validation—your functions can safely assume that they’re not receiving invalid data.
For example, in a chess game, you have the invariant that only one piece at a time may occupy a given square on the board. Invariants can be enforced at compile-time, usually by using a static type system to make objects correct by construction, e.g., by representing the board as a matrix of optional pieces. They can also be enforced at runtime, e.g., by raising an exception when trying to make an invalid move.
The approach in functional programming languages is typically to make objects immutable, and prevent the construction of invalid states. In OOP languages, where objects are commonly mutable, methods are expected to prevent invalid state transitions.
Either way, enforcing invariants ensures that your program is always in a predictable state, which makes it easier to reason about your code and safely make changes without introducing regressions.

Related

Parametricity in Haskell [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 2 years ago.
Improve this question
In my Haskell learning journey, I can't help but notice that parametricity is of the utmost importance in the language. Given how the type system and the inference capability of the compiler works, i think it is safe to say that parametricity or parametric polymorphism is natural, encourage and at the core of the philosophy of the language.
While the question I am going to ask is not specific to Haskell and could be ask to almost any programming language community, I'm quite intrigued at the point of view of the Haskellers, given the nature of the language as suggested above.
Why is parametricity so important to Haskellers? The language does really encourage to code to generic type and somewhat let the compiler figure out the right type when it is the most appropriate time (when it is forced too). Granted one does not have to stick to that, and we can and it is probably a good practice to declare the type.
But somehow I have the feeling, the all thing encourage you to be generic and not focus on the concrete type at first, adding the capability you need to the signature through type class, and focus on the composition and delay the concrete type at last, or leave it to the compiler.
I'm not completely sure of what I am saying but it feels that way.
I'm probably biased because I read a book in Scala, that also encourage that, although it is way more manual activity to than in Haskell.
Any philosophical response to that maybe? I have some idea about it, but from your point of you, how parametricity help programming faster and maybe safer too?
Note: I'm a Scala programmer learning Haskell
Edit
I illustrate my propos as I am studying with "Programming Haskell from first principles". To cite the author:
"There are some caveats to keep in mind here when it comes to using
concrete types. One of the nice things about parametricity and type
classes is that you are being explicit about what you mean to do with
your data, which means you are less likely to make a mistake. Int is a
big datatype with many inhabitants and many type classes and
operations defined for it—it would be easy to make a function that
does something unintended. Whereas if we were to write a function,
even if we have Int values in mind for it, that uses a polymorphic
type constrained by the type class instances we want, we could ensure
we only use the operations we intend. This isn’t a panacea, but
sometimes it can be worth avoiding concrete types for these (and
other) reasons.
(Page 208). "
I'd like to know what are the other reasons .... I mean this parametricity when compare to Scala that has it way more manual, is so baked in the language, I can't help think that is is part of the productivity philosophy of the language.
Parametricity is important because it restricts the implementation space. It's often the case that a properly parametric type restricts the implementation space down to a single implementation that lacks bottoms. Consider fst :: (a, b) -> a, for instance. With that type, there is only one possible return value from the function that doesn't have bottoms in it.
There are a lot of ways to write it that have bottoms - undefined, error, infinite loops, all of which varying in terms of eta expansion of the definition and whether the pair's constructor is matched. Many of these differences can be observed externally by careful means, but the thing they all have in common is that they don't produce a usable (non-bottom) value of type a.
This is a strong tool for implementing a definition. Given the guarantees parametricity makes, it's actually sufficient to test only that fst ((), ()) == (). If that expression evaluates to True, the implementation is correct. (Ok, it's not quite that simple in ghc, given the ability to break all sorts of rules with unsafe functions. You also need to validate that the implementation doesn't use anything unsafe that breaks parametricity.)
But guiding the implementation is only the first benefit. A consequence of the implementation being so limited is that parametricity also turns the type into concise, precise, and machine-checked documentation. You know that no matter what the implementation is, the only non-bottom value it can return is the first element of the pair.
And yes - usually things aren't quite so constrained as in the type of fst. But in every case where parametric polymorphism is present in a type, it restricts the implementation space. And every time the implementation space is restricted, that knowledge serves as machine-checked documentation of implementation of the type.
Parametricity is a clear win for both the implementor and user of code. It reduces the space for incorrect implementations and it improves precision and accuracy of documentation. This should be as close to an objectively good thing as there is in programming.

Side-effect vs non-destructive in lisp [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
For example in Mathematica programs to generate a new list for every operation performed on list is dramatically faster than in place manipulations in list(say append, drop etc).
Haskell also gives a new list by default for every operation performed on list.
I read that lisp provides both facilities, creating new list every time or choosing to do in place manipulations in list.
But I am confused as it also states that in place helps in reusing same list again and hence reduces garbage. So, which way is the effective, because I am seeing two great languages that say something opposite.
Immutable data structures let the compiler reason about them easier.
(Some zealots have even argued that mutable data structures are a crutch for poor compilers. :-)
Mutable structures (arrays, lists, hash tables - all of them are present in Lisp) let one do some things easier, and they sometimes let the programmer micro-optimize the code, which was not all that stupid when the "sufficiently smart compiler" was even more of a pipe dream than it is now.
PS. It is not quite fair to compare Haskell and Mathematica with Lisp as they belong to different generations. Lisp is the second oldest programming language still in use (after Fortran) while Haskell and Mathematica build on 30+ years of CS research.
But I am confused as it also states that in place helps in reusing
same list again and hence reduces garbage. So, which way is the
effective, because I am seeing two great languages that say something
opposite.
One of the main design goals of Haskell is easy reasoning. When all of your data structure is immutable, you get referential transparency. This make your code easy to reason about. Other advantages of this being easy concurrency and parallelism.
In Mathematica, they seem to be recommending use of immutable datastrucutres but also provide mutable data structures through emulation. Although, I don't think this is a great design decision, this according to them offers flexibility.
In Haskell, Lists are not the start and the end of data structures. There are many more: Set, Vector, Map, Array etc. Each data structure has it's own characteristics. For getting a good performance, I would suggest you to know the pros and cons of each one and use the right data structures according to your requirement. For most simple requirement, the usage of List suffices.
And if for some reason, you really want mutable data objects in Haskell, you may want to check out the ST monad.
As others have pointed out, immutable data structures make code easy to reason about. That isn't just for the programmer: the compiler can also reason about the code more easily.
For instance think about the following identity:
map (f . g) xs = (map f . map g) xs
If you don't know Haskell, "map f xs" applies function f to each element of list Xs in turn, giving you a new list, and "." is an operator that composes two functions together; the definition is "(f . g) x = f (g x)".
The identity above isn't part of the definition of "map", but its something you can prove by taking the defintions of map and "." as axioms and doing some substitution. What it says is that you can replace "map f . map g" (that is, two iterations through the list) with "map (f . g)" (that is, a single iteration doing "g" and then "f" to each item in turn). But this only works if you assume that "f" and "g" have no side effects. If their order of execution matters then the identity no longer holds. So you can only do this optimisation in languages where all values are immutable.
GHC lets the programmer assert these identities using rewrite rules. The actual rule set in Data.List is more sophisticated and general, but the one above is a special case that follows from the rules it gives.
So in Haskell you can write a long chain of list operations like
foo = take 5 . sortBy cmp . map f . filter pred . concatMap g
and have it compiled down into the optimal code. A language where values are mutable would have to execute each of those steps on the entire list, store the result in memory, and then move on to the next step. For large lists that would be too slow and/or memory hungry, so the above expression would have to be rewritten manually instead of just letting the compiler do it for you.

Can a pure function have free variables?

For example, a referentially transparent function with no free variables:
g op x y = x `op` y
A now now a function with the free (from the point-of-view of f) variables op and x:
x = 1
op = (+)
f y = x `op` y
f is also referentially transparent. But is it a pure function?
If it's not a pure function, what is the name for a function that is referentially transparent, but makes use of 1 or more variables bound in an enclosing scope?
Motivation for this question:
It's not clear to me from Wikipedia's article:
The result value need not depend on all (or any) of the argument values. However, it must depend on nothing other than the argument values.
(emphasis mine)
nor from Google searches whether a pure function can depend on free (in the sense of being bound in an enclosing scope, and not being bound in the scope of the function) variables.
Also, this book says:
If functions without free variables are pure, are closures impure?
The function function (y) { return x } is interesting. It contains a
free variable, x. A free variable is one that is not bound within
the function. Up to now, we’ve only seen one way to “bind” a variable,
namely by passing in an argument with the same name. Since the
function function (y) { return x } doesn’t have an argument named x,
the variable x isn’t bound in this function, which makes it “free.”
Now that we know that variables used in a function are either bound or
free, we can bifurcate functions into those with free variables and
those without:
Functions containing no free variables are called pure functions.
Functions containing one or more free variables are called closures.
So what is the definition of a "pure function"?
To the best of my understanding "purity" is defined at the level of semantics while "referentially transparent" can take meaning both syntactically and embedded in lambda calculus substitution rules. Defining either one also leads to a bit of a challenge in that we need to have a robust notion of equality of programs which can be challenging. Finally, it's important to note that the idea of a free variable is entirely syntactic—once you've gone to a value domain you can no longer have expressions with free variables—they must be bound else that's a syntax error.
But let's dive in and see if this becomes more clear.
Quinian Referential Transparency
We can define referential transparency very broadly as a property of a syntactic context. Per the original definition, this would be built from a sentence like
New York is an American city.
of which we've poked a hole
_ is an American city.
Such a holey-sentence, a "context", is said to be referentially transparent if, given two sentence fragments which both "refer" to the same thing, filling the context with either of those two does not change its meaning.
To be clear, two fragments with the same reference we can pick would be "New York" and "The Big Apple". Injecting those fragments we write
New York is an American city.
The Big Apple is an American city.
suggesting that
_ is an American city.
is referentially transparent. To demonstrate the quintessential counterexample, we might write
"The Big Apple" is an apple-themed epithet referring to New York.
and consider the context
"_" is an apple-themed epithet referring to New York.
and now when we inject the two referentially identical phrases we get one valid and one invalid sentence
"The Big Apple" is an apple-themed epithet referring to New York.
"New York" is an apple-themed epithet referring to New York.
In other words, quotations break referential transparency. We can see how this occurs by causing the sentence to refer to a syntactic construct instead of purely the meaning of that construct. This notion will return later.
Syntax v Semantics
There's something confusing going on in that this definition of referential transparency above applies directly to English sentences of which we build contexts by literally stripping words out. While we can do that in a programming language and consider whether such a context is referentially transparent, we also might recognize that this idea of "substitution" is critical to the very notion of a computer language.
So, let's be clear: there are two kinds of referential transparency we can consider over lambda calculus—the syntactic one and the semantic one. The syntactic one requires we define "contexts" as holes in the literal words written in a programming language. That lets us consider holes like
let x = 3 in _
and fill it in with things like "x". We'll leave the analysis of that replacement for later. At the semantic level we use lambda terms to denote contexts
\x -> x + 3 -- similar to the context "_ + 3"
and are restricted to filling in the hole not with syntax fragments but instead only valid semantic values, the action of that being performed by application
(\x -> x + 3) 5
==>
5 + 3
==>
8
So, when someone refers to referential transparency in Haskell it's important to figure out what kind of referential transparency they're referring to.
Which kind is being referred to in this question? Since it's about the notion of an expression containing a free variable, I'm going to suggest that it's syntactic. There are two major thrusts for my reasoning here. Firstly, in order to convert a syntax to a semantics requires that the syntax be valid. In the case of Haskell this means both syntactic validity and a successfully type check. However, we'll note that a program fragment like
x + 3
is actually a syntax error since x is simply unknown, unbound leaving us unable to consider the semantics of it as a Haskell program. Secondly, the very notion of a variable such as one that can be let-bound (and consider the difference between "variable" as it refers to a "slot" such as an IORef) is entirely a syntactic construct—there's no way to even talk about them from inside the semantics of a Haskell program.
So let's refine the question to be:
Can an expression containing free variables be (syntactically) referentially transparent?
and the answer is, uninterestingly, no. Referential transparency is a property of "contexts", not expressions. So let's explore the notion of free variables in contexts instead.
Free variable contexts
How can a context meaningfully have a free variable? It could be beside the hole
E1 ... x ... _ ... E2
and so long as we cannot insert something into that syntactic hole which "reaches over" and affects x syntactically then we're fine. So, for instance, if we fill that hole with something like
E1 ... x ... let x = 3 in E ... E2
then we haven't "captured" the x and thus can perhaps consider that syntactic hole to be referentially transparent. However, we're being nice to our syntax. Let's consider a more dangerous example
do x <- foo
let x = 3
_
return x
Now we see that the hole we've provided in some sense has dominion over the later phrase "return x". In fact, if we inject a fragment like "let x = 4" then it indeed changes the meaning of the whole. In that sense, the syntax here is no referentially transparent.
Another interesting interaction between referential transparency and free variables is the notion of an assigning context like
let x = 3 in _
where, from an outside perspective, both phrases "x" and "y" are reference the same thing, some named variable, but
let x = 3 in x ==/== let x = 3 in y
Progression from thorniness around equality and context
Now, hopefully the previous section explained a few ways for referential transparency to break under various kinds of syntactic contexts. It's worth asking harder questions about what kinds of contexts are valid and what kinds of expressions are equivalent. For instance, we might desugar our do notation in a previous example and end up noticing that we weren't working with a genuine context, but instead sort of a higher-order context
foo >>= \x -> (let x = 3 in ____(return x)_____)
Is this a valid notion of context? It depends a lot on what kind of meaning we're giving the program. The notion of desugaring the syntax already implies that the syntax must be well-defined enough to allow for such desugaring.
As a general rule, we must be very careful with defining both contexts and notions of equality. Further, the more meaning we demand the fragments of our language to take on the greater the ways they can be equal and the fewer the valid contexts we can build.
Ultimately, this leads us all the way to what I called "semantic referential transparency" earlier where we can only substitute proper values into a proper, closed lambda expression and we take the resulting equality to be "equality as programs".
What this ends up meaning is that as we impute more and more meaning on our language, as we begin to accept fewer and fewer things as valid, we get stronger and stronger guarantees about referential transparency.
Purity
And so this finally leads to the notion of a pure function. My understanding here is (even) less complete, but it's worth noting that purity, as a concept, does not much exist until we've moved to a very rich semantic space—that of Haskell semantics as a category over lifted Complete Partial Orders.
If that doesn't make much sense, then just imagine purity is a concept that only exists when talking about Haskell values as functions and equality of programs. In particular, we examine the collection of Haskell functions
trivial :: a -> ()
trivial x = x `seq` ()
where we have a trivial function for every choice of a. We'll notate the specific choice using an underscore
trivial_Int :: Int -> ()
trivial_Int x = x `seq` ()
Now we can define a (very strictly) pure function to be a function f :: a -> b such that
trivial_b . f = trivial_a
In other words, if we throw out the result of computing our function, the b, then we may as well have never computed it in the first place.
Again, there's no notion of purity without having Haskell values and no notion of Haskell values when your expressions contain free variables (since it's a syntax error).
So what's the answer?
Ultimately, the answer is that you can't talk about purity around free variables and you can break referential transparency in lots of ways whenever you are talking about syntax. At some point as you convert your syntactic representation to its semantic denotation you must forget the notion and names of free variables in order to have them represent the reduction semantics of lambda terms and by this point we've begun to have referential transparency.
Finally, purity is something even more stringent than referential transparency having to do with even the reduction characteristics of your (referentially transparent) lambda terms.
By the definition of purity given above, most of Haskell isn't pure itself as Haskell may represent non-termination. Many feel that this is a better definition of purity, however, as non-termination can be considered a side effect of computation instead of a meaningful resultant value.
The Wikipedia definition is incomplete, insofar a pure function may use constants to compute its answer.
When we look at
increment n = 1+n
this is obvious. Perhaps it was not mentioned because it is that obvious.
Now the trick in Haskell is that not only top level values and functions are constants, but inside a closure also the variables(!) closed over:
add x = (\y -> x+y)
Here x stands for the value we applied add to - we call it variable not because it could change within the right hand side of add, but because it can be different each time we apply add. And yet, from the point of view of the lambda, x is a constant.
It follows that free variables always name constant values at the point where they are used and hence do not impact purity.
Short answer is YES f is pure
In Haskell map is defined with foldr. Would you agree that map is functional? If so did it matter that it had global function foldr that wasn't supplied to map as an argument?
In map foldr is a free variable. It's not doubt about it. It makes no difference that it's a function or something that evaluates to a value. It's the same.
Free variables, like the functions foldl and +, are essential for functional languages to exist. Without it you wouldn't have abstraction and the languages would be worse off than the Fortran.

A Question About the Expressive Power of Higher-Order Logical Reasoning Formalisms

I do not really know if this is scientifically proven, but I've read in a book (It was a relatively modern AI book by Peter Norvig) that second-order logical programming could be more expressive than existing first-order languages.
The question is: Is it statistically/symbolically proven that higher-order predicate logics exceed first-order predicates in their expressive power? Or they just bring the modularity/convenience/maintainability to your knowledge bases?
Additionally: If there is some kind of firm direction in which I could go seeking more expressive power than I have (I mean exactly the descriptive potential of the symbols I write in given semantics/syntax) - then I would be glad to hear just almost everything :)
Thank you.
Second order logic is more powerful and expressive than first order logic. Second order logic allows one to quantify over relations in addition to variables; thus it is possible, using a single sentence of second order logic, to express something that would require an infinite number of first order logic sentences. The relationship is similar to that between FOL and propositional logic.
As an example, consider the SOL statement:
\forall R \exists x \exists y (x R y)
This states that for any relation R there are x and y such that x R y holds. In order to express this in FOL, one would need a statement for each relation R in the language, which clearly could be infinite.
For a more interesting example, one could look at the proof that the transitive closure of a relation is not expressible in FOL. I can post it if you want to see it; but for the sake of succinctness I will omit it unless someone wants it.
Edit: You may also be interested in Descriptive Complexity -- essentially, it ties together the notions of complexity and expressibility -- if you can fully state a problem in a certain fragment of logic, then you know it is contained within the corresponding complexity class. For example, if a problem can be stated in Existential Second Order Logic, then it's in NP; if it can be stated in First Order Logic + a Least Fixed Point operator, then it's in P. If you can show that every statement of existential second order logic can be translated to FOL(LFP), then you've proven P=NP. (well, you've proven NP\subset P, but since the other containment is already known, you've proven equality...)
You may want to look into dependent type theories.

Explain concatenative languages to me like I'm an 8-year-old

I've read the Wikipedia article on concatenative languages, and I am now more confused than I was when I started. :-)
What is a concatenative language in stupid people terms?
In normal programming languages, you have variables which can be defined freely and you call methods using these variables as arguments. These are simple to understand but somewhat limited. Often, it is hard to reuse an existing method because you simply can't map the existing variables into the parameters the method needs or the method A calls another method B and A would be perfect for you if you could only replace the call to B with a call to C.
Concatenative language use a fixed data structure to save values (usually a stack or a list). There are no variables. This means that many methods and functions have the same "API": They work on something which someone else left on the stack. Plus code itself is thought to be "data", i.e. it is common to write code which can modify itself or which accepts other code as a "parameter" (i.e. as an element on the stack).
These attributes make this languages perfect for chaining existing code to create something new. Reuse is built in. You can write a function which accepts a list and a piece of code and calls the code for each item in the list. This will now work on any kind of data as long it's behaves like a list: results from a database, a row of pixels from an image, characters in a string, etc.
The biggest problem is that you have no hint what's going on. There are only a couple of data types (list, string, number), so everything gets mapped to that. When you get a piece of data, you usually don't care what it is or where it comes from. But that makes it hard to follow data through the code to see what is happening to it.
I believe it takes a certain set of mind to use the languages successfully. They are not for everyone.
[EDIT] Forth has some penetration but not that much. You can find PostScript in any modern laser printer. So they are niche languages.
From a functional level, they are at par with LISP, C-like languages and SQL: All of them are Turing Complete, so you can compute anything. It's just a matter of how much code you have to write. Some things are more simple in LISP, some are more simple in C, some are more simple in query languages. The question which is "better" is futile unless you have a context.
First I'm going to make a rebuttal to Norman Ramsey's assertion that there is no theory.
Theory of Concatenative Languages
A concatenative language is a functional programming language, where the default operation (what happens when two terms are side by side) is function composition instead of function application. It is as simple as that.
So for example in the SKI Combinator Calculus (one of the simplest functional languages) two terms side by side are equivalent to applying the first term to the second term. For example: S K K is equivalent to S(K)(K).
In a concatenative language S K K would be equivalent to S . K . K in Haskell.
So what's the big deal
A pure concatenative language has the interesting property that the order of evaluation of terms does not matter. In a concatenative language (S K) K is the same as S (K K). This does not apply to the SKI Calculus or any other functional programming language based on function application.
One reason this observation is interesting because it reveals opportunities for parallelization in the evaluation of code expressed in terms of function composition instead of application.
Now for the real world
The semantics of stack-based languages which support higher-order functions can be explained using a concatenative calculus. You simply map each term (command/expression/sub-program) to be a function that takes a function as input and returns a function as output. The entire program is effectively a single stack transformation function.
The reality is that things are always distorted in the real world (e.g. FORTH has a global dictionary, PostScript does weird things where the evaluation order matters). Most practical programming languages don't adhere perfectly to a theoretical model.
Final Words
I don't think a typical programmer or 8 year old should ever worry about what a concatenative language is. I also don't find it particularly useful to pigeon-hole programming languages as being type X or type Y.
After reading http://concatenative.org/wiki/view/Concatenative%20language and drawing on what little I remember of fiddling around with Forth as a teenager, I believe that the key thing about concatenative programming has to do with:
viewing data in terms of values on a specific data stack
and functions manipulating stuff in terms of popping/pushing values on the same the data stack
Check out these quotes from the above webpage:
There are two terms that get thrown
around, stack language and
concatenative language. Both define
similar but not equal classes of
languages. For the most part though,
they are identical.
Most languages in widespread use today
are applicative languages: the central
construct in the language is some form
of function call, where a function is
applied to a set of parameters, where
each parameter is itself the result of
a function call, the name of a
variable, or a constant. In stack
languages, a function call is made by
simply writing the name of the
function; the parameters are implicit,
and they have to already be on the
stack when the call is made. The
result of the function call (if any)
is then left on the stack after the
function returns, for the next
function to consume, and so on.
Because functions are invoked simply
by mentioning their name without any
additional syntax, Forth and Factor
refer to functions as "words", because
in the syntax they really are just
words.
This is in contrast to applicative languages that apply their functions directly to specific variables.
Example: adding two numbers.
Applicative language:
int foo(int a, int b)
{
return a + b;
}
var c = 4;
var d = 3;
var g = foo(c,d);
Concatenative language (I made it up, supposed to be similar to Forth... ;) )
push 4
push 3
+
pop
While I don't think concatenative language = stack language, as the authors point out above, it seems similar.
I reckon the main idea is 1. We can create new programs simply by joining other programs together.
Also, 2. Any random chunk of the program is a valid function (or sub-program).
Good old pure RPN Forth has those properties, excluding any random non-RPN syntax.
In the program 1 2 + 3 *, the sub-program + 3 * takes 2 args, and gives 1 result. The sub-program 2 takes 0 args and returns 1 result. Any chunk is a function, and that is nice!
You can create new functions by lumping two or more others together, optionally with a little glue. It will work best if the types match!
These ideas are really good, we value simplicity.
It is not limited to RPN Forth-style serial language, nor imperative or functional programming. The two ideas also work for a graphical language, where program units might be for example functions, procedures, relations, or processes.
In a network of communicating processes, every sub-network can act like a process.
In a graph of mathematical relations, every sub-graph is a valid relation.
These structures are 'concatenative', we can break them apart in any way (draw circles), and join them together in many ways (draw lines).
Well, that's how I see it. I'm sure I've missed many other good ideas from the concatenative camp. While I'm keen on graphical programming, I'm new to this focus on concatenation.
My pragmatic (and subjective) definition for concatenative programming (now, you can avoid read the rest of it):
-> function composition in extreme ways (with Reverse Polish notation (RPN) syntax):
( Forth code )
: fib
dup 2 <= if
drop 1
else
dup 1 - recurse
swap 2 - recurse +
then ;
-> everything is a function, or at least, can be a function:
( Forth code )
: 1 1 ; \ define a function 1 to push the literal number 1 on stack
-> arguments are passed implicitly over functions (ok, it seems to be a definition for tacit-programming), but, this in Forth:
a b c
may be in Lisp:
(c a b)
(c (b a))
(c (b (a)))
so, it's easy to generate ambiguous code...
you can write definitions that push the xt (execution token) on stack and define a small alias for 'execute':
( Forth code )
: <- execute ; \ apply function
so, you'll get:
a b c <- \ Lisp: (c a b)
a b <- c <- \ Lisp: (c (b a))
a <- b <- c <- \ Lisp: (c (b (a)))
To your simple question, here's a subjective and argumentative answer.
I looked at the article and several related web pages. The web pages say themselves that there isn't a real theory, so it's no wonder that people are having a hard time coming up with a precise and understandable definition. I would say that at present, it is not useful to classify languages as "concatenative" or "not concatenative".
To me it looks like a term that gives Manfred von Thun a place to hang his hat but may not be useful for other programmers.
While PostScript and Forth are worth studying, I don't see anything terribly new or interesting in Manfred von Thun's Joy programming language. Indeed, if you read Chris Okasaki's paper on Techniques for Embedding Postfix Languages in Haskell you can try out all this stuff in a setting that, relative to Joy, is totally mainstream.
So my answer is there's no simple explanation because there's no mature theory underlying the idea of a concatenative language. (As Einstein and Feynman said, if you can't explain your idea to a college freshman, you don't really understand it.) I'll go further and say although studying some of these languages, like Forth and PostScript, is an excellent use of time, trying to figure out exactly what people mean when they say "concatenative" is probably a waste of your time.
You can't explain a language, just get one (Factor, preferably) and try some tutorials on it. Tutorials are better than Stack Overflow answers.

Resources