There doesn’t seem to be a good tutorial out there for concatenative programming, so I figured I’d write one, inspired by the classic “Why Functional Programming Matters” by John Hughes. With any luck it will get more people interested in the topic, and give me a URL to hand people when they ask what the heck I’m so excited about.
Foremost, there seems to be some disagreement over what the term “concatenative” actually means. This Stack Overflow answer by Norman Ramsey even goes so far as to say:
“…it is not useful to use the word ‘concatenative’ to describe programming languages. This area appears to be a private playground for Manfred von Thun. There is no real definition of what constitutes a concatenative language, and there is no mature theory underlying the idea of a concatenative language.”This is rather harsh, and, well, wrong. Not entirely wrong, mind, just rather wrong. But it’s not surprising that this kind of misinformation gets around, because concatenative programming isn’t all that well known. (I aim to change that.)
Concatenative programming is so called because it uses function composition instead of function application—a non-concatenative language is thus called applicative. That’s the defining difference, and it’s as “real” a definition as I need, because, well, it’s the one that people are using. Bang. Descriptivism.
One of the problems with functional programming is that the oft-touted advantages—immutability, referential transparency, mathematical purity, &c.—don’t immediately seem to apply in the real world. The reason “Why Functional Programming Matters” was necessary in the first place was that functional programming had been mischaracterised as a paradigm of negatives—no mutation, no side-effects—so everybody knew what you couldn’t do, but few people grasped what you could.
There is a similar problem with concatenative programming. Just look at the Wikipedia introduction:
A concatenative programming language is a point-free programming language in which all expressions denote functions and the juxtaposition of expressions denotes function composition. The combination of a compositional semantics with a syntax that mirrors such a semantics makes concatenative languages highly amenable to algebraic manipulation.This is all true—and all irrelevant to your immediate problem of why you should care. So in the next sections, I will show you:
- How concatenative programming works
- How typing in a concatenative language works
- How concatenative languages are efficient
- What concatenative languages are not good for
- Where to go for more information
Now let’s get started!
♦ ♦ ♦
The Basics
In an applicative language, functions are applied to values to get other values. λ-calculus, the basis of functional languages, formalises application as “β-reduction”, which just says that if you have a function (f x = x + 1) then you can substitute a call to that function (f y) with its result (y + 1). In λ-calculus, simple juxtaposition (f x) denotes application, but function composition must be handled with an explicit composition function:
compose := λf. λg. λx. f (g x)This definition says “the composition (compose) of two functions (f and g) is the result of applying one (f) to the result of the other (g x)”, which is pretty much a literal definition. Note that this function can only be used to compose functions of a single argument—more on this later.
In concatenative languages, composition is implicit: “f g” is the composition of f and g. However, this does not mean that function application becomes explicit—it actually becomes unnecessary. And as it turns out, this peculiar fact makes these languages a whole lot simpler to build, use, and reason about.
The untyped λ-calculus is a relatively simple system. However, it and the many systems derived from it still need three kinds of term—variables, lambdas, and applications—as well as a number of rules about how to correctly replace variable names when applying functions. You have to deal with name binding, closures, and scope. For a supposedly low-level language, it has quite a bit of inherent complexity.
Concatenative languages have a much simpler basis—there are only functions and compositions, and evaluation is just the simplification of functions. It is never necessary to deal with named state—there are no variables. In a sense, concatenative languages are “more functional” than traditional functional languages! And yet, as we will see, they are also easy to implement efficiently.
♦ ♦ ♦
Let’s say we want to multiply a couple of numbers. In a typical concatenative language, this looks like:
2 3 ×There are two weird things about this.
First, we use postfix notation (2 3 ×) rather than the prefix notation (× 2 3) that is common in the functional world, or the infix notation (2 × 3) that most languages provide for convenience. There is nothing stopping a concatenative language from having infix operators, but for the sake of consistency, most stick to postfix notation: “f g” means (g ∘ f), i.e., the reverse of function composition. This is actually rather nice notation, because it means that data flows in the order the functions are written in.
Second, we said all terms denote functions—so what the heck are 2 and 3 doing in there? They sure look like values to me! But if you tilt your head a little, you can see them as functions too: values take no arguments and return themselves. If we were to write down the inputs and outputs of these functions, it would look like this:
2 :: () → (int)As you may guess, “x :: T” means “x is of type T”, and “T1 → T2” means “a function from type T1 to type T2”. So these functions take no input, and return one integer. We also know the type of the multiplication function, which takes two integers and returns just one:
3 :: () → (int)
× :: (int, int) → (int)Now how do you compose all of these functions together? Remember we said “f g” means the (reverse) composition of f and g, but how can we compose 2 with 3 when their inputs and outputs don’t match up? You can’t pass an integer to a function that takes no arguments.
The solution lies in something called stack polymorphism. Basically, we can give a generic, polymorphic type to these functions that says they’ll take any input, followed by what they actually need. They return the arguments they don’t use, followed by an actual return value:
2 :: ∀A. (A) → (A, int)“∀A.” means “For all A”—in these examples, even if A has commas in it. So now the meaning of the expression “2 3” is clear: it is a function that takes no input and returns both 2 and 3. This works because when we compose two functions, we match up the output of one with the input of the other, so we start with the following definitions:
3 :: ∀A. (A) → (A, int)
× :: ∀A. (A, int, int) → (A, int)
2 :: ∀A. (A) → (A, int)We match up the types:
3 :: ∀B. (B) → (B, int)
(A, int) = (B)By substituting, we get a new polymorphic type for 3 within the expression:
3 :: ∀C. (C, int) → (C, int, int)This matches the non-polymorphic type:
3 :: ∀A. (A, int) → (A, int, int) = ∀B. B → (B, int)So the final type of the expression becomes:
2 3 :: ∀A. (A) → (A, int, int)Going through the same process for multiplication, we get:
2 3 :: ∀A. (A) → (A, int, int)This is correct: the expression “2 3 ×” takes no input and produces one integer. Whew! As a sanity check, note also that the equivalent function “6” has the same type as “2 3 ×”:
× :: ∀B. (B, int, int) → (B, int)
A = B
2 3 × :: ∀A. (A) → (A, int)
6 :: ∀A. (A) → (A, int)So already, concatenative languages give us something applicative functional languages generally can’t: we can actually return multiple values from a function, not just tuples. And thanks to stack polymorphism, we have a uniform way to compose functions of different types, so the flow of data in our programs doesn’t get obscured, as it were, by the plumbing.
♦ ♦ ♦
Cool Stuff
In the above example, we worked from left to right, but because composition is associative, you can actually do it in any order. In math terms, (f ∘ g) ∘ h = f ∘ (g ∘ h). Just as “2 3 ×” contains “2 3”, a function returning two integers, it also contains “3 ×”, a function that returns thrice its argument:
3 × :: (int) → (int)(From now on I’ll omit the ∀ bit from type signatures to make them easier to read.)
So we can already trivially represent partial function application. But this is actually a huge win in another way. Applicative languages need to have a defined associativity for function application (almost always from left to right), but here we’re free from this restriction. A compiler for a statically typed concatenative language could literally:
- Divide the program into arbitrary segments
- Compile every segment in parallel
- Compose all the segments at the end
Because all we have are functions and composition, a concatenative program is a single function—typically an impure one with side effects, but that’s by no means a requirement. (You can conceive of a pure, lazy concatenative language with side-effect management along the lines of Haskell.) Because a program is just a function, you can think about composing programs in the same way.
This is the basic reason Unix pipes are so powerful: they form a rudimentary string-based concatenative programming language. You can send the output of one program to another (
|
); send, receive, and redirect multiple I/O streams (n<
, 2&>1
);
and more. At the end of the day, a concatenative program is all about
the flow of data from start to finish. And that again is why
concatenative composition is written in “reverse” order—because it’s actually forward:+---+
| 2 |
+---+
|
| +---+
| | 3 |
| +---+
| |
V V
+---------+
| * |
+---------+
|
V
♦ ♦ ♦
So far, I have deliberately stuck to high-level terms that pertain to all concatenative languages, without any details about how they’re actually implemented. One of the very cool things about concatenative languages is that while they are inherently quite functional, they also have a very straightforward and efficient imperative implementation. In fact, concatenative languages are the basis of many things you use every day:
- The Java Virtual Machine on your PC and mobile phone
- The CPython bytecode interpreter that powers BitTorrent, Dropbox, and YouTube
- The PostScript page description language that runs many of the world’s printers
- The Forth language that started it all, which still enjoys popularity on embedded systems
It’s a stack. Yep. Just a stack.
Consider the information flowing between functions in the expression “2 3 × 4 5 × +”, which, if you’re not up on your postfix, is equal to (2 × 3 + 4 × 5):
Function | Output |
---|---|
() | |
2 | (2) |
3 | (2, 3) |
× | (6) |
4 | (6, 4) |
5 | (6, 4, 5) |
× | (6, 20) |
+ | (26) |
Moving from left to right in the expression, whenever we encounter a “value” (remember: a nullary self-returning function), we push its result to the stack. Whenever we encounter an “operator” (a non-nullary function), we pop its arguments, perform the computation, and push its result. Another name for postfix is reverse Polish notation, which achieved great success in the calculator market on every HP calculator sold between 1968 and 1977—and many thereafter.
So a concatenative language is a functional language that is not only easy, but trivial to run efficiently, so much so that most language VMs are essentially concatenative. x86 relies heavily on a stack for local state, so even C programs have a little bit of concatenativity in ’em, even though x86 machines are register-based.
Furthermore, it’s straightforward to make some very clever optimisations, which are ultimately based on simple pattern matching and replacement. The Factor compiler uses these principles to produce very efficient code. The JVM and CPython VMs, being stack-based, are also in the business of executing and optimising concatenative languages, so the paradigm is far from unresearched. In fact, a sizable portion of all the compiler optimisation research that has ever been done has involved virtual stack machines.
♦ ♦ ♦
Point-free Expressions
It is considered good functional programming style to write functions in point-free form, omitting the unnecessary mention of variables (points) on which the function operates. For example, “f x y = x + y” can be written as “f = (+)”. It is clearer and less repetitious to say “f is the addition function” than “f returns the sum of its two arguments”.
More importantly, it’s more meaningful to write what a function is versus what it does, and point-free functions are more succinct than so-called “pointful” ones. For all these reasons, point-free style is generally considered a Good Thing™.
However, if functional programmers really believe that point-free style is ideal, they shouldn’t be using applicative languages! Let’s say you want to write a function that tells you the number of elements in a list that satisfy a predicate. In Haskell, for instance:
countWhere :: (a -> Bool) -> [a] -> Int
countWhere predicate list = length (filter predicate list)
It’s pretty simple, even if you’re not so familiar with Haskell.
countWhere
returns the length
of the list you get when you filter
out elements of a list
that don’t satisfy a predicate
. Now we can use it like so:countWhere (>2) [1, 2, 3, 4, 5] == 3
We can write this a couple of ways in point-free style, omitting
predicate
and list
:countWhere = (length .) . filter
countWhere = (.) (.) (.) length filter
But the meaning of the weird repeated self-application of the composition operator (
.
) isn’t necessarily obvious. The expression (.) (.) (.)
—equivalently (.) . (.)
using infix syntax—represents a function that composes a unary function (length
) with a binary function (filter
). This type of composition is occasionally written .:
, with the type you might expect:(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(.:) = (.) . (.)
countWhere = length .: filter
But what are we really doing here? In an applicative language, we have to jump through some hoops in order to get the basic concatenative operators we want, and get them to typecheck. When implementing composition in terms of application, we must explicitly implement every type of composition.
In particular, there is no uniform way to compose functions of different numbers of arguments or results. To even get close to that in Haskell, you have to use the
curry
and uncurry
functions to explicitly wrap things up into tuples. No matter what, you
need different combinators to compose functions of different types,
because Haskell doesn’t have stack polymorphism for function arguments,
and it inherently can’t.Writing point-free expressions demands concatenative semantics, which just aren’t natural in an applicative language. Now contrast a concatenative example:
define countWhere [filter length]
(1, 2, 3, 4, 5) [2 >] countWhere
It’s almost painfully literal: “to count the number of elements in a list that match a predicate is to filter it and take the length”. In a concatenative language, there is no need at all for variables, because all you’re doing is building a machine for data to flow through:
+-----------------+
| (1, 2, 3, 4, 5) |
+-----------------+
|
| +-------+
| | [2 >] |
| +-------+
| |
+---|-----------------|-----+
| | countWhere | |
| V V |
| +-----------------------+ |
| | filter | |
| +-----------------------+ |
| | |
| V |
| +--------+ |
| | length | |
| +--------+ |
| | |
+---|-----------------------+
|
V
When you’re building a diagram like this, just follow a few simple rules:
- Each block is one function
- A block takes up as many columns as needed by its inputs or outputs, whichever are more
- When adding a block, put it in the rightmost column possible:
- If it takes no inputs, add a column
- If there aren’t enough arrows to match the block, the program is ill-typed
♦ ♦ ♦
Notice the use of brackets for the predicate [2 >] in the preceding example? In addition to composition, the feature that completes a concatenative language is quotation, which allows deferred composition of functions. For example, “2 >” is a function that returns whether its argument is greater than 2, and [2 >] is a function that returns “2 >”.
It’s at this point that we go meta. While just composition lets us build descriptions of dataflow machines, quotation lets us build machines that operate on descriptions of other machines. Quotation eliminates the distinction between code and data, in a simple, type-safe manner.
The “filter” machine mentioned earlier takes in the blueprints for a machine that accepts list values and returns Booleans, and filters a list according to the instructions in those blueprints. Here’s the type signature for it:
filter :: (list T, T → bool) → (list T)There are all kinds of things you can do with this. You can write a function that applies a quotation to some arguments, without knowing what those arguments are:
apply :: ∀A B. (A, A → B) → (B)You can write a function to compose two quotations into a new one:
compose :: ∀A B C. (A → B, B → C) → (A → C)And you can write one to convert a function to a quotation that returns it:
quote :: (T) → (() → T)
♦ ♦ ♦
The Dark Side
Say you want to convert a math expression into concatenative form:
f x y z = y2 + x2 − |y|This has a bit of everything: it mentions one of its inputs multiple times, the order of the variables in the expression doesn’t match the order of the inputs, and one of the inputs is ignored. So we need a function that gives us an extra copy of a value:
dup :: (T) → (T, T)A function that lets us reorder our arguments:
swap :: (T1, T2) → (T2, T1)And a function that lets us ignore an argument:
drop :: (T) → ()From the basic functions we’ve defined so far, we can make some other useful functions, such as one that joins two values into a quotation:
join2 :: (T1, T2) → (() → (T1, T2))Or a function that rotates its three arguments leftward:
join2 = quote swap quote swap compose
rot3 :: (T1, T2, T3) → (T2, T3, T1)And hey, thanks to quotations, it’s also easy to declare your own control structures:
rot3 = join2 swap quote compose apply
define true [[drop apply]] # Apply the first of two arguments.
define false [[swap drop apply]] # Apply the second of two arguments.
define if [apply] # Apply a Boolean to two quotations.
And using them is like—actually, just is—using a regular function:
["2 is still less than 3."] # "Then" branch.
["Oops, we must be in space."] # "Else" branch.
2 3 < # Condition.
if print # Print the resulting string.
Those particular definitions for
true
and false
will be familiar to anyone who’s used Booleans in the λ-calculus. A Boolean is a quotation, so it behaves like an ordinary value, but it contains a binary function that chooses one branch and discards another. “If-then-else” is merely the application of that quotation to the particular branches.Anyway, getting back to the math, we already know the type of our function ((int, int, int) → (int)), we just need to deduce how to get there. If we build a diagram of how the data flows through the expression, we might get this:
+---+ +---+ +---+
| x | | y | | z |
+---+ +---+ +---+
x y z
| | |
| | V
| | +------+
| | | drop |
| | +------+
| V
| +----------+
| | dup |
| +----------+
| y y
| | |
| | V
| | +----------+
| | | dup |
| | +----------+
| | y y
| | | |
| | V V
| | +----------+
| | | * |
| | +----------+
| | y^2
| | |
| V V
| +----------+
| | swap |
| +----------+
| y^2 y
| | |
| | V
| | +-----+
| | | abs |
| | +-----+
| | |y|
| | |
V V V
+-----------------+
| rot3 |
+-----------------+
y^2 |y| x
| | |
| | V
| | +----------+
| | | dup |
| | +----------+
| | x x
| | | |
| | V V
| | +----------+
| | | * |
| | +----------+
| | x^2
| | |
| V V
| +----------+
| | swap |
| +----------+
| x^2 |y|
| | |
| V V
| +----------+
| | - |
| +----------+
| x^2-|y|
| |
V V
+----------+
| + |
+----------+
y^2+x^2-|y|
|
V
So our final function can be written:f = drop dup dup × swap abs rot3 dup × swap − +Well…that sucked.
♦ ♦ ♦
A Lighter Note
You’ve just seen one of the major problems with concatenative programming—hey, every kind of language has its strengths and weaknesses, but most language designers will lie to you about the latter. Writing seemingly simple math expressions can be difficult and unintuitive, especially using just the functions we’ve seen so far. To do so exposes all of the underlying complexity of the expression that we’re accustomed to deferring to a compiler.
Factor gets around this by introducing a facility for lexically scoped local variables. Some things are simply more natural to write with named state rather than a bunch of stack-shuffling. However, the vast majority of programs are not predominated by mathematical expressions, so in practice this feature is not used very much:
“Out of 38,088 word and method definitions in the source code of Factor and its development environment at the time of this writing, 310 were defined with named parameters.”—Factor: A Dynamic Stack-based Programming LanguageOne of the great strengths of concatenative languages, however, is their ability to refactor complex expressions. Because every sequence of terms is just a function, you can directly pull out commonly used code into its own function, without even needing to rewrite anything. There are generally no variables to rename, nor state to manage.
So in practice, a lot of expressions can be refactored using a wide array of handy functions, of the sort that commonly end up in a standard library. With some refactoring, that math expression might look like this:
square = dup ×Which doesn’t look so bad, and actually reads pretty well: the difference between the square and absolute value of the second argument, plus the square of the first. But even that description shows that our mathematical language has evolved as inherently applicative. It’s better sometimes just to stick to tradition.
f = drop [square] [abs] bi − [square] dip +
♦ ♦ ♦
Whither Hence
So you’ve got the gist, and it only took a few dozen mentions of the word “concatenative” to get there. I hope you’re not suffering from semantic satiation.
You’ve seen that concatenative programming is a paradigm like any other, with a real definition and its own pros and cons:
- Concatenative languages are simple and consistent (“everything is a”)
- They are amenable to dataflow programming
- Stack languages are well studied and have good performance
- You can easily roll your own control structures
- Everything is written in point-free style (for better or worse)
If you’re interested in trying out a mature, practical concatenative language, check out Factor and the official blog of the creator, Slava Pestov. Also see Cat for more information on static typing in concatenative languages.
I’ve been idly working on a little concatenative language called Kitten off and on for a while now. It’s dynamically typed and compiles to C, so you can run it just about anywhere. I wanted a language I could use for a site on a shared host where installing compilers was irritating. That shows you the extent of my language geekery—I’d rather spend hours writing a language than twenty minutes figuring out how to install GHC on Bluehost.
Anyway, the implementation is just barely complete enough to play around with. Feel free to browse the source, try it out, and offer feedback. You’ll need GHC and GCC to build it, and I imagine it works best on Linux or Unix, but there shouldn’t be any particularly horrible incompatibilities.
This would also be a good time to mention that I’m working on a more serious language called Magnet, which I mentioned in my last article about how programming is borked. It’s principally concatenative, but also has applicative syntax for convenience, and relies heavily on the twin magics of pattern matching and generalised algebraic data types. Hell, half the reason I wrote this article was to provide background for Magnet. So expect more articles about that.
Edit (20 April 2013) The above information is no longer accurate. Kitten is currently being rewritten; the work that was done on Magnet has been incorporated. Kitten is now statically typed and, until the compiler is complete, interpreted.
And that about does it. As always, feel free to email me at evincarofautumn@gmail.com to talk at length about anything. Happy coding!
What is the difference between concatenative language and FP-systems ?
ReplyDeletehttp://c2.com/cgi/wiki?CanProgrammingBeLiberatedFromTheVonNeumannStyle
Concatenative languages are an elaboration on and simplification of FP systems; some of the distinctions that FP systems maintain are arbitrary, such as between objects and functions. Also, row polymorphism makes the definition of a complete concatenative system much cleaner and smaller; you can get away with only two stack combinators (see http://tunes.org/~iepos/joy.html), and it’s possible to give a type to the Y combinator (∀A B. (A, (A, (A → B)) → B) → B).
DeleteNice article! Maybe mention RPN and the Forth language for sake of completeness?
ReplyDeleteThanks. Have added a couple of notes.
DeleteGreat Post! Thanks for writing it.
ReplyDeleteCool stuff.
ReplyDeleteJust curious: is there a fundamental reason why you can't use lambda's and variables in a concatenative language?
ReplyDeleteFor example, the 'function' \x could pop the top value of the stack, and bind it to the name x for the remainder of the expression. Then
f = \z \y \x y square x square + y abs -
The reverse order of popping the arguments could be a bit confusing, but I would say that this is a clear improvement.
Indeed, there is no particular reason—Factor allows something similar based on stack annotations, and Prog will do it with pattern matching. The “remainder of the expression” bit is a little unclear; you would obviously need to delimit the definition somehow (even if just with a newline) lest scopes run amok. I would prefer a syntax like “\ z y x { y square x square + y abs ‒ }”, but the principle is the same. Manfred von Thun shows in “The Theory of Concatenative Combinators” (http://tunes.org/~iepos/joy.html) that you can use stack combinators to rewrite any expression with lambdas into an expression without them. But my attitude is like yours: “can” doesn’t necessarily mean “should”.
Delete"The Theory of Concatenative Combinators" is written by Brent Kerby not Manfred von Thun
DeleteMany thanks for writing this! I'm finally starting to feel like being excited about concatenative languages is something I'm allowed to be proud of as a functional programmer. I'll have to take a long look at Prog's design; I've always wondered how pattern matching could be combined with concatenative syntax in a sensible and convenient way.
ReplyDeleteOne major advantage which you didn't mention and which doesn't seem to have been explored a lot yet: a (statically typed) concatenative language can afford incredible IDE support. The type of the stack at the cursor's position is everything you need to know about your current context; just display it in a corner and the programmer has all the info they need. Better yet, autocomplete for words that can take the current stack as input and you get IntelliSense on steroids.
ceii, I agree. I'd also like to see dynamic dataflow, with the IDE drawing a dataflow graph centered around the word nearest the cursor, showing you where its data came from and is going to.
DeleteThat sounds cool. I'd love to see a prototype. Maybe you could select an interval of code and it would graph the flow associated with that fragment. Sounds like something a capable person could draft in html5 quickly (or perhaps factor's IDE?). (I disagree that types are "all you need". (int,int,int) isn't informative enough about what is what, for example, if you've been rolling and duping and dipping and dropping lately). Actually on that count, the data flow graph could *be* the language with the right UI.
DeleteReally great article. Thanks.
ReplyDeleteI am writing a compiler that targets CIL right now. Thinking of all this in terms of a stack really made it so much easier to follow. .NET is not completely stack based though as it uses local variables as well.
If you'd just called it a 'stack based language' instead of concatenate it'd all have been massively clearer from the start..
ReplyDeleteAlso, you list the Android phone as having a stack-based interpreter. In fact its famously register based, which is why it is so much faster. Its not a JVM, its Dalvik. http://en.wikipedia.org/wiki/Dalvik_%28software%29#Architecture
I admit that the article started out a bit unclear in its purpose, but you have to admit he cleared up any confusion quite well. (And considering that he's starting with Wikipedia's definition, he's doing GREAT in comparison.)
DeleteYou're right about Dalvik being register based, but I can't find anything suggesting that it was done for speed, unless it was actually done to give reasonable speed without a JIT (and whether that's true or not, I can find no hint that any research was done). Lua's switch to a register machine is always described in terms of a speed-centered choice, but it's very clear that they were comparing a modern optimized register machine versus an antique naive stack machine; not a fair comparison at all.
A JITted virtual stack machine should be faster on arbitrary CPUs than a register machine -- the only exception being when the virtual register machine is a very close match to the physical register machine (after the overhead of virtualization).
Thanks for the information. I removed the Android reference.
DeleteThe reason I structured this how I did is that the paradigm is not bound to stacks. There are a lot of high-level concepts that apply to all concatenative languages whether they use a stack or not. I wanted to demonstrate those concepts first, before showing that a stack is just an efficient way of implementing them.
It’s like Smalltalk and JavaScript versus Simula and C++: even though the former pair has prototypes and messages while the latter has classes and methods, they’re both object-oriented. A concatenative language based on term rewriting or directed graphs would still be concatenative, so long as it were still based on composition and quotation.
Did you mean Self instead of Smalltalk? Self has prototypes and messages (and methods but not classes.) Smalltalk has classes and methods (and messages but not prototypes.)
Delete"The PostScript page description language that runs most of the world’s printers"
ReplyDelete"Most of the world's printers" are either cheap-ass inkjets or industrial thermal printers, neither of which use PostScript. Even the vast majority of mid-range business printers are non-PostScript.
In fact, you'd be hard-pressed to find a PostScript printer that was made in the last 10 years and isn't a high-end network laser printer.
Thanks for the information. Edited.
DeleteOne quick note on the HP calculator front: they were sold WAY later than 1977, and some of us still love our HP48s. :)
ReplyDeleteHowever, I do wonder about the usefulness of this style for longer programs - I wrote quite a few medium-sized things on the HP48, and the code was always *intensely* write-only since you had to have a mental model of exactly what was on the stack where to even hope to follow it. Not sure how much of that was due to the system discouraging the definition of many small functions, though (you could only edit one function at a time).
Thanks, I didn’t know that about the calculators. I’ll correct that.
DeleteWhether it’s useful for larger programs I think just depends on the programmer and the program. Concatenative programming demands obsessive factoring. If you don’t have “many small functions”, you’re doing it wrong, and your work will be harder than it has to be.
It’s like writing assembly. You can think about everything at the instruction level, with a mental model (or excessive comments) about what’s where. But you’ll never get anything done that way, because you’re working at the wrong level of abstraction. If you’re shuffling the stack a lot, then you’re treating the symptom of a problem you’re not seeing because you need to step back.
Concatenative languages are metalanguages. (Indeed, something I neglected to talk about is how good they are for metaprogramming.) You use the language to create structures that are suited to your problem, so you can solve the problem naturally. That’s what you ought to do in any language, but concatenative programming makes it almost painfully direct. Some like it, others don’t.
You use the language to create structures that are suited to your problem, so you can solve the problem naturally. That’s what you ought to do in any language, but concatenative programming makes it almost painfully direct. Some like it, others don’t.
DeletePut "functional programming" in there and the statement still works.
One problem with concatenative languages and point free style is, i think, that named variables are some kind of documentation.
DeleteWith a concatenative lanuguage, you need to know (or document in an informal comment) what inputs and outputs a function has. And in which order it's on the stack.
“…named variables are some kind of documentation.”
DeleteTrue, but not always. The name of a parameter is often immaterial or generic; most of the identifiers in programs I’ve read have been largely meaningless syntactic junk. Where names serve a real purpose is in math expressions, where you expect to do symbolic manipulation.
“With a concatenative lanuguage, you need to know (or document in an informal comment) what inputs and outputs a function has. And in which order…”
That’s equally true of any dynamically typed language.
I've read the original John Hughes paper and this was an interesting read as well.
ReplyDeleteThat said:
"Concatenative programming is so called because it uses function composition instead of function application—a non-concatenative language is thus called applicative."
I must observe: at some point, you need to apply your composed functions to your arguments to get any results at all.
No, that "5 is a function!" talk is just a silly fallacy to make it look overly-sophisticated what is really just values in a stack consumed by subsequent function calls. I can say the same for any scheme expression: (+ 1 2 3 4 5) is not then the application of the arguments to the function +, but the composition of a new function out of + and constant "functions" denoted by numbers... I can then compose another function from this new function (15) and get yet another "function": (* 2 (+ 1 2 3 4 5))
"the expression “2 3 ×” takes no input and produces one integer" please...
"concatenative languages give us something applicative functional languages generally can’t: we can actually return multiple values from a function, not just tuples"
No, you can't. You can just fill a stack and let subsequent calls alter it.
In scheme you may return multiple values, but the context must be waiting for them. (values 1 2 3) actually returns 3 values instead of a tuple (list). In the top-level REPL, it returns the separate 3 values, but if you were to use them, you'd better make a context to consume them:
(call-with-values
(lambda () (values 1 2 3)) ; return multiple values
(lambda (x y z) (+ x y z))) ; bind the values to the arguments x y z
In other words: (+ 1 (sqrt 4)) won't return 2 values, even if sqrt returned 2 values (default sqrt in scheme returns just the positive value) because its continuation (+ 1 []) expects a single value.
say:
> (define (sqrt2 n)
(let ((pos (sqrt n))) ; original sqrt
(values pos (* -1 pos))))
> (sqrt2 4)
2
-2
> (+ 1 (sqrt2 4))
context expected 1 value, received 2 values: 2 -2
OTOH, this would work, but is too much of a hassle:
(call-with-values
(lambda () (sqrt2 4)) ; assuming it returns 2 values
(lambda (pos neg) (values (+ 1 pos) (+ 1 neg)))) ; returns 2 values too
it's much simpler to just return lists, which are Lisp's forte anyway, just like stacks are for concatenative languages. So you just return a list and use the usual functional higher-order functions to process the result:
> (define (sqrt2 n)
(let ((r (sqrt n)))
(list r (* -1 n))))
> (sqrt2 25)
'(5 -25)
> (map (lambda (x) (+ 1 x)) (sqrt2 25))
'(6 -24)
All that said, concatenative languages always sound to me like the exact opposite of Lisp as far as syntax is concerned. :)
"I must observe: at some point, you need to apply your composed functions to your arguments to get any results at all."
DeleteWhich textual functions compose with which textual arguments? The answer isn't as cut and dried as you imply -- remember, concatenation (and composition) is associative. In an applicative language the answer is VERY clear in the text.
"No, that "5 is a function!" talk is just a silly fallacy"
Ouch.
"to make it look overly-sophisticated what is really just values in a stack consumed by subsequent function calls."
You're assuming stack semantics. The author explained that textual evaluation is another possible semantics; there's no "really just values" there. I believe www.enchiladacode.nl is a purely rewriting semantics (although that may just be my bad memory). Apter built a few concatenative languages using things like a stack-dequeue pair. Most languages entirely WITHOUT a stack are mere toys, but I'm not sure it will always be so. Oh, and of course, all professional Forths aside from stuff for hardware stack machines perform register optimizations, which uses a stack approximately like a C program would.
You're also missing the point. In the _language_, that is in the mapping of text to semantics, there is no difference between a literal and a function. The semantics of a literal may be simple; but then again they may not. The point is that a literal like "3" is the same sort of language (text) object as a function; it's parsed the same way (even though obviously it's lexed differently).
For higher-order languages, and for real lower-level languages, not all symbols are so tidy. There has to be a symbol that means "do not execute the following symbols", which is NOT semantically similar to what a function does.
"I can say the same for any scheme expression: (+ 1 2 3 4 5) is not then the application of the arguments to the function +, but the composition of a new function out of + and constant "functions" denoted by numbers... I can then compose another function from this new function (15) and get yet another "function": (* 2 (+ 1 2 3 4 5))"
You're not performing composition; you're building a tree, not a string. It's not associative except between siblings on the tree (and that's not a concept that's directly apparent in the text; you have to convert it to a parsed data structure to see which things are siblings).
"please..."
Well... It's true. And what's more, because of the associative property, _every lexically correct substring_ has a valid type, and can be pulled out and made a function. (Of course, this is only true inasfar as the language is purely concatenative. All currently used languages offer things like definitions and nested blocks, which are not associative. I made a language which doesn't have any of those and is therefore purely concatenative, but you wouldn't want to code in it; its name is "zeroone", and its two functions are named "zero" and "one". The bits, not the English numerals.)
"All that said, concatenative languages always sound to me like the exact opposite of Lisp as far as syntax is concerned. :)"
Sounds fair to me :-). And no parens (unless you want literal lists or quotations).
-Wm
"No, that "5 is a function!" talk is just a silly fallacy to make it look overly-sophisticated"
DeleteUnderstand the difference between ($ 5) and (5) in Haskell. 5 in Concat languages is equivalent to ($ 5) in Haskell. It's not overly-sophisticated talk, it's actually useful and I've used it several times in Haskell.
Sorry, Blogger messed it up. I meant `dollar` 5
Delete@Codingtales
DeleteThat was MathJax actually, which I’ve since disabled.
Your Haskell examples are a bit unfair, I think: you omitted the style it would most likely be written in:
ReplyDelete> countWhere predicate = length . filter predicate
Oh, absolutely. They’re deliberately unfair—I was showing that point-free style is not as natural in applicative style as in concatenative.
DeleteI always thought that concatenative languages were a bit inelegant due to the apparently explicit stack. You know, it's point-free but there's a stack there to help you out, so whatever. :)
ReplyDeleteBut having the semantics formalised via row polymorphism actually makes the stack an implementation detail and the elegance has returned!
Thanks for a great article.
Thanks. Yeah, I’d say stacks are relevant, but not fundamental. Indeed, Prog will use a stack for storage, but all word simplification will be done by pattern matching and rewriting, so it’s not inherently a stack language.
DeleteVery clear and informative article. Thanks! I experimented with both Forth and Factor a couple of months ago, and found it quite interesting how the languages made me start to think about algorithms.
ReplyDeleteForth also prompted me to try Rebol, which I totally enjoyed. I'm starting to see how ideas evolve and change from language to language, and I think knowing this is really valuable for any serious developer.
I believe the principle reason virtual machines have recently moved away from stack machine representations was the cost in maintaining invariants over the stack representation. The overhead exceeded the benefits
ReplyDeleteHowever, for a peephole optimization, a stack-based representation might be most optimal.
Cheers,
Z-Bo
It is indeed easy to peephole optimize stack-based representation. In particular, naive register allocation is very easy; if your datum is near the top of the stack it's FAR more likely to be a good candidate for register allocation than a datum deeper down. In essence, the time the programmer spends trying to write code without too many "SWAP"s and other "stack noise" leads not only to clear code, but also to prioritized dataflow. (In practice it's always harder.)
DeleteNow, I don't understand what you mean by "maintaining invariants". What invariants are you talking about? Is there some context here that I'm missing?
-Wm
I thought this post was going to be an executable m4 macro, i.e., string concatenation. ;-)
ReplyDeleteThis is the coolest thing ever. It brings new insight in how to implement USL/DBTF (http://en.wikipedia.org/wiki/Universal_Systems_Language).
ReplyDeleteI can't see any substance in this: you're just replacing dealing with explicit elements to dealing with morphisms () -> T, which is how elements are *defined* in CCCs.
ReplyDeleteMath Nerd Stuff!
ReplyDelete"In math terms, (f ∘ g) ∘ h = f ∘ (g ∘ h). Just as “2 3 ×” contains “2 3”, a function returning two integers, it also contains “3 ×”, a function that returns thrice its argument" - this is sloppy, the two obviously differ by currying, which is something we cannot forget "in math terms".
ReplyDeleteIt would be, except I established early on how row-polymorphic functions are uniformly composable. Currying is not at play.
DeleteI have a question about the use of the compose function in Join2. I was just working through examples by hand to make sure I understood the dynamics of the language, and, given "0 1 join2", I ended up at a step:
ReplyDelete(() -> 0) (() -> 1) ((A->B, B->C) -> (A->C))
I don't understand how this evaluation goes to (() -> 0 1)
Did I evaluate up to the right step until then? And if so, how does the compose function pair up numbers?
It’s easiest to do by substitution, without considering the types.
DeleteStart: 1 2 join2.
Expand join2: 1 2 quote swap quote swap compose.
Substitute quote: 1 [2] swap quote swap compose.
Substitute swap: [2] 1 quote swap compose.
Substitute quote: [2] [1] swap compose.
Substitute swap: [1] [2] compose.
Substitute compose: [1 2].
There is a bit of a notational problem here. The type T—one value—is equivalent to the type () → T—function from 0 values to 1 value—which too is equivalent to ∀A. A → (A, T)—function from n to n+1 values. So while the type I gave for “quote” is accurate, it is also somewhat misleading, which probably led you astray when you were simplifying “compose”.
From these signatures:
() → 0 :: ∀A. A → (A, int)
() → 1 :: ∀B. B → (B, int)
compose :: ∀X Y Z. (X → Y, Y → Z) → (X → Z)
You should get these constraints:
1. X = (A)
2. Y = (A, int)
3. Y = (B)
4. Z = (B, int)
From which you can derive:
5. B = (A, int) [by #3 and #2]
6. Z = (A, int, int) [by #4 and #5]
So the monomorphic type of “compose” becomes:
compose :: (() → int, int → (int, int)) → (() → (int, int))
And thus the type of “(() → 1) (() → 2) compose” is “() → (int, int)”, as expected.
Great, thanks for the help.
DeleteVery nice post, Jon. It made me that curious that I've implemented my own concatenative language, even before really grasping everything I probably had better known. But hey, it's good enough to calculate the factorial! ;-)
ReplyDeleteIt's here: https://github.com/tsdh/clj-miniconcat
This is a great post! I was vaguely familiar with the formal notion of concatenative languages, and this is an extremely clear explanation. Thank you.
ReplyDeleteAlso, tangentially, this: http://home.iae.nl/users/mhx/sf.html
Leo Brodie's "Starting Forth" is IMO one of the best programming books ever - accessible to smart kids (I read it the first time in 6th grade, I think), but with enough detail that you can actually implement a Forth compiler after reading it.
I _think_ Dijkstra (the famous Edsger W.) used the term 'pointless' for what is here called 'concatenative', and 'pointed' for 'applicative'.
ReplyDeleteI'm familiar with the term "point-free", which refers to the original style of functional programming developed by Backus. I'm not familiar with Dijkstra using a term "pointless", and it wasn't a concatenative language Backus developed; it was applicative.
DeleteI found EWD 1116 (http://www.cs.utexas.edu/~EWD/ewd11xx/EWD1116.PDF), which uses the term, in a similar setting.
ReplyDeleteThanks. That does help -- it looks like "pointless" here doesn't mean the same as "point-free" does. Here's an abstract that mentions and briefly defines the term:
Deletehttp://www.mendeley.com/research/relation-algebras-and-their-application-in-temporal-and-spatial-reasoning/
It looks like in this sense "pointless" means "without referring to the objects being stored". There's a similarity, but it's a different area of discussion; and note that the "pointless" proofs use plenty of names.
-Wm
That's really a fantastic post ! added to my favorite blogs list.. I have been reading your blog last couple of weeks and enjoy every bit. Thanks.
ReplyDeleteWhile one of the anonymous commenters above is right that most printers are not PostScript printers, PostScript still builds the basis of PDF, so every PDF-Viewer is implicitly an interpreter of a concatenative programming language:
ReplyDeletehttp://en.wikipedia.org/wiki/PDF#Technical_foundations
I find the article interesting and provocative. However, there are a number of things I disagree with.
ReplyDeleteI don't like ‘concatenative programming […] uses function composition instead of function application’ as a definition. Dropping one operation while promoting another does not mean the latter is used ‘instead’ of the former; they mean very different things after all.
The statement ‘that function application becomes unnecessary […] makes these languages a whole lot simpler to build, use, and reason about’ is not true in general – the author himself gives an example of the opposite, later on in the article.
One interesting question is the relation between concatenativity, stacks, and postfix notation. The author seems to maintain that stack is ‘not fundamental’ to concatenativity, but why then all real, usable, concatenative languages are stack-based?
Also, if being postfix is also not fundamental and ‘there is nothing stopping a concatenative language from having infix [or prefix] operators’, then why all concatenative languages are postfix? Can concatenativity be preserved in the presence of non-postfix notation?
A commenter said that ‘semantics […] via row polymorphism […] makes the stack an implementation detail’, but I see it exactly the opposite: the way it is used, row polymorphism itself already seems to assume a stack organization (a row is a stack); by employing it to define composition, the inherent relation of concatenative computation to stacks is only accented.
In a comment following the article, the author says that ‘some of the distinctions that FP systems maintain are arbitrary, such as between objects and functions’. In fact, that distinction in FP is no more consequential than it is in concatenative languages. An FP programme consists entirely of functions. Data objects only enter the picture when the programme is run.
Still further, he says: ‘row polymorphism makes the definition of a complete concatenative system much cleaner and smaller; you can get away with only two stack combinators’. But of course two combinators (e.g. S and K) suffice to express any computation in applicative style, too – this sort of minimalism has nothing to do with row polymorphism or concatenativity.
The statement ‘the Forth language […] started it all’ is inaccurate. Forth is perhaps the most influential in popularizing stack-based programming, but it was preceded by Pop (currently known as Pop-11), which was stack-based and, unlike Forth, mostly functional.
The statement ‘because all we have are functions and composition, a concatenative program is a single function’ is somewhat misleading, too – the said property holds of any (purely) functional programme, not just concatenative ones.
The statement ‘most language VMs are essentially concatenative’ needs to be substantiated. First, there is a well respectable set of VMs that are register-based, i.e., LLVM, Lua VM, Parrot, Dalvik, and HASTE (Falcon's VM). Second, a VM may be stack-based but not concatenative.
The phrase ‘quotation […] allows deferred composition of functions’ is, I believe, incorrect. Should it not rather be ‘deferred evaluation’?
Finally, the phrase ‘our mathematical language has evolved as inherently applicative’ needs to be made more precise. That language is not only ‘applicative’. It is clearly variable-based, as opposed to function-based (point-free). Functions very rarely are treated as values in themselves. And, syntactically, infix notation is preferred wherever it applies.
I find the article interesting and provocative. However, there are a number of things I disagree with.
ReplyDeleteI don't like ‘concatenative programming […] uses function composition instead of function application’ as a definition. Dropping one operation while promoting another does not mean the latter is used ‘instead’ of the former; they do very different job after all.
The statement ‘that function application becomes unnecessary […] makes these languages a whole lot simpler to build, use, and reason about’ is not true in general – the author himself gives an example of the opposite, later on in the article.
One interesting question is the relation between concatenativity, stacks, and postfix notation. The author seems to maintain that stack is ‘not fundamental’ to concatenativity, but why then all real, usable, concatenative languages are stack-based?
Also, if being postfix is also not fundamental and ‘there is nothing stopping a concatenative language from having infix [or prefix] operators’, then why all concatenative languages are postfix? Can concatenativity be preserved in the presence of non-postfix notation?
A commenter said that ‘semantics […] via row polymorphism […] makes the stack an implementation detail’, but I see it exactly the opposite: the way it is used, row polymorphism itself already seems to assume a stack organization (a row is a stack); by employing it to define composition, the inherent relation of concatenative computation to stacks is only accented.
In a comment following the article, the author says that ‘some of the distinctions that FP systems maintain are arbitrary, such as between objects and functions’. In fact, that distinction in FP is no more consequential than it is in concatenative languages. An FP programme consists entirely of functions; data objects only enter the picture when the programme is run.
Still further, he says: ‘row polymorphism makes the definition of a complete concatenative system much cleaner and smaller; you can get away with only two stack combinators’. But of course two combinators (e.g. S and K) suffice to express any computation in applicative style, too – this sort of minimalism has nothing to do with row polymorphism or concatenativity.
The statement ‘the Forth language […] started it all’ is inaccurate. Forth is perhaps the most influential in popularizing stack-based programming, but it was preceded by Pop (currently known as Pop-11), which was stack-based and, unlike Forth, mostly functional. Pop-11 is still finding use for AI education and research in UK.
The statement ‘because all we have are functions and composition, a concatenative program is a single function’ is somewhat misleading, too – the said property holds of any (purely) functional programme, not just concatenative ones.
The statement ‘most language VMs are essentially concatenative’ needs to be substantiated. First, there is a respectable set of VMs that are register-based, e.g. LLVM, Lua VM, Parrot, Dalvik, and HASTE (Falcon's VM). Second, a VM may be stack-based and not concatenative.
The phrase ‘quotation […] allows deferred composition of functions’ is, I believe, incorrect. Should it not be ‘evaluation’ rather than ‘composition’?
Finally, the phrase ‘our mathematical language has evolved as inherently applicative’ needs to be made more precise. That language is not only ‘applicative’. It is conspicuously variable-based, as opposed to function-based (point-free). Functions very rarely are treated as values in themselves. And, syntactically, infix notation is preferred wherever it applies.
I've recently been learning Stratego/XT which is a program transformation language. Typically terms go in on the left and are passed automatically from "strategy" (== function) to "strategy" working left the right. Each strategy can transform the given term in some way to yield a new one, or fail. Terms are composed by separating with a semicolon and you can take any sebsequence of strategies and give it a name. So, it's another example of a concatenative style of programming.
ReplyDeleteSome OO libraries that make use of this concatenative style, to good effect:
x.doThis().thenThat().andThisOtherThing().butWaitTheresMore()
It reads nicely compared to the functional reverse order:
andThisOtherThing(thenThat(doThis(x)))
You can factor out the middle parts into a new method, to some degree, like:
x.part2 = function() { return thenThat().andThisOtherThing(); }
x.doThis.part2().butWaitTheresMore();
I think probably the greatest hinderance to do concatenative programming style in static languages like Java or C++ is that you cannot extend existing classes and defining all the classes needed to represent intermediate states of the computation would become pretty painful.
Thanks for the article! It gave me a better understanding of how Factor works and which I am attempting to master.
ReplyDeleteI find that you did fantastic resolution when you selected this topic of the article here. Do you usually create your articles alone or maybe you have a business partner or even an assistant?
ReplyDeleteThis may be a really dumb question, but would it be reasonable to interpret a Combinatory Categorial Grammar (http://en.wikipedia.org/wiki/Combinatory_categorial_grammar) as a special purpose concatenative programming language?
ReplyDeleteI don’t know enough about the topic. At first glance, CCGs look more like APL in that they’re tacit, infix, and based on application of terms to other terms. The concepts are akin but not the same.
DeleteI wrote my own JIT/AOT compiler. In AOT mode you can do #exe{} and run JIT code during compile time. You can insert code into the AOT stream. I did not count on lots of code this way.
ReplyDeletenice code...
ReplyDeleteSSAS online training
SSIS online training
SSRS online training
Thanks again for the article post.Really thank you! Fantastic.
ReplyDeletetableau training
Power bi online
Abinitio training
ReplyDeleteI just loved your article on the beginners guide to starting a blog.If somebody take this blog article seriously
in their life, he/she can earn his living by doing blogging.Thank you for this article.
tibco sportfire online training
After getting the mantras, you need to recite them daily for a few minutes. Within a few days, you are going to get the desired results just by chanting mantras. Get Him Back After A Fight
ReplyDeletewhere to buy gastrox oxide online at cheaper price
ReplyDeleteBuy Gastrox Oxide Online from painkillerpharmaceuticals.net
EMAIL; painkillerpharmaceuticalslink@gmail.com
Non-alcoholic Steatohepatitis Treatment Market
ReplyDeletevehicle ownership transfer online delhi nice blog post
ReplyDeletevehicle ownership transfer online delhi nice blog post
ReplyDeleteMont Blanc Colognes | Mont blanc perfume |Mont blanc legend nice blog post
ReplyDeleteTom Ford Perfumes | Tom Ford Perfumes women | Tom Ford Fragrances nice blog post
ReplyDeleteBest Jasmine Perfumes |White Jasmine Perfume | Jasmine Oil nice blog post
ReplyDeleteb2b research
ReplyDeletegreat content
Best Perfumes for Men| Best perfumes for men in the world|Best Fragrance nice blog post
ReplyDeleteRussian to English
ReplyDeleteawesome content
Nonic toothbrush nice blog post
ReplyDeleteNonic toothbrush https://nonicecoproduct.com/
Best Montblanc perfumes for Women in 2021 – ( Reviews ) nice blog post
ReplyDeleteperfumes with patchouli nice and informative post thanks for the update
ReplyDeletehttps://shop.fragrancereviews.in/ perfumes with patchouli
ReplyDeleteWow. That is so elegant and logical and clearly explained. Brilliantly goes through what could be a complex process and makes it obvious.I want to refer about the best corporate Training institute In Bangalore . They Provide 100% Placement Program .
SEO services in IndiaIn the Website Building segment, our strategy involves blending in design and development with sustainable practice. In the end, your Business will be able to achieve digital marketing goals with long-term growth. Top SEO companies in India
ReplyDeleteDigital Marketing Agency in IndiaWe are a Website Design Company and Digital marketing agency worldly known for our super creative blend in the projects that we do deliver. Internet Marketing Agency
tutku iç giyim Türkiye'nin önde gelen iç giyim markalarından birisi olmasının yanı sıra en çok satan markalardan birisidir. Ürünleri hem çok kalitelidir hem de pamuk kullanımı daha fazladır.
ReplyDeleteDigitürk sporun yıldızı Faturalıya ilk 4 ay 49,00 TL, daha sonra ayda yalnızca 154,00 TL. Kredi Kartı ile 12 ay boyunca ayda 119 TL, toplamda 1428 TL.
nbb sütyen hem kaliteli hem de uygun fiyatlı sütyenler üretmektedir. Sütyene ek olarak sütyen takımı ve jartiyer gibi ürünleri de mevcuttur. Özellikle Avrupa ve Orta Doğu'da çokça tercih edilmektedir.
yeni inci sütyen kaliteyi ucuz olarak sizlere ulaştırmaktadır. Çok çeşitli sütyen varyantları mevcuttur. iç giyime damga vuran markalardan biridir ve genellikle Avrupa'da ismi sıklıkla duyulur.
iç giyim ürünlerine her zaman dikkat etmemiz gerekmektedir. Üretimde kullanılan malzemelerin kullanım oranları, kumaşın esnekliği, çekmezlik testi gibi birçok unsuru aynı anda değerlendirerek seçim yapmalıyız.
iç giyim bayanların erkeklere göre daha dikkatli oldukları bir alandır. Erkeklere göre daha özenli ve daha seçici davranırlar. Biliyorlar ki iç giyimde kullandıkları şeyler kafalarındaki ve ruhlarındaki özellikleri dışa vururlar.
ReplyDeleteinformative blog post. Thanks for sharing
Best Web designing company in Hyderabad
Best Web development company in Hyderabad
Best App development company in Hyderabad
Best Digital Marketing company in Hyderabad
Profesyonel Bostancı evden eve nakliyat hizmetleri
ReplyDeleteThis is very interesting, You’re a very skilled blogger. I have joined your feed and look forward to seeking more of your excellent post. Also, I have shared your website in my social networks! Unique Dofollow Backlinks
ReplyDeleteRegardless or how enormous an issue you face, there isn't anything that we can't help you with. Our online Dissertation Writing Service, dissertation writing help and dissertation proposition help administrations and need assignment help administrations are uniquely designed to suit your inclinations, so you get precisely what you requested inside the deadline. assignment help
ReplyDeletecar noc bangalore nice and informative post thanks for the update
ReplyDeletecar noc bangalore
https://www.itzeazy.com/noc-for-car-bike-bangalore.html
best free online coding classes nice and informative post thanks for the update
ReplyDeleteThank you, this article is very inspiring and increases knowledge I hope the writer and the team will be more successful. Sir…
ReplyDeletePinoy Teleserye
Your blog website provided us with useful information to execute with. Each & every recommendations of your website are awesome. Thanks a lot for talking about .matka 420
ReplyDeleteazure networking
ReplyDeletearm templates
azure notification hub
azure bastion host
app power bi
kubernetes dashboard
terraform cheat sheet
Quick Boxes Packaging nice and informative post thanks for the update
ReplyDeleteQuick Boxes Packaging https://www.quickboxespackaging.com/
Product Reviews nice and informative post thanks for the update
ReplyDeleteProduct Reviews https://marketerhunt.com/
Cordelia Cruises nice and informative post thanks for the update
ReplyDeleteCordelia Cruises https://triplou.com/cordelia-cruise/
aws inspector
ReplyDeleteopenshift vs kubernetes
azure data lake
arm templates
azure traffic manager
azure bastion
azure synapse analytics
Cruise Vacations from India nice and informative post thanks for the update
ReplyDeleteCruise Vacations from India https://triplou.com/cordelia-cruise/
Thanks a lot for sharing this post
ReplyDeleteNorton Customer Service
Bank PO coaching in jaipur nice and informative post thanks for the update
ReplyDeleteBank PO coaching in jaipur
Best Nina Ricci Perfumes for Women nice and informative post thanks for the update
ReplyDeletesms onay
ReplyDelete70% off of retail prices nice and informative post thanks for the update
ReplyDeleteLakme Academy Janakpuri nice and informative post thanks for the update
ReplyDeleteChanging the Address of your Company nice and informative post thanks for the update
ReplyDeletetour packages near me nice and informative post thanks for the update
ReplyDeletebrochure design in gurgaon. nice and informative post thanks for the update
ReplyDeleteThanks for sharing the article I always appreciate your topic.Python Training In Jodhpur
ReplyDeleteCyber Insurance in india Such a very useful blog. Very Interesting to read this blog. Thanks for proving such a wonderful content.
ReplyDeleteopenshift certification
ReplyDeleteazure data engineer certification
aws solution architect training
azure solution architect certification
Slotnation88 adalah agen casino resmi terbaik, Situs Judi Online Terpercaya di Indonesia yang menjadi rekanan beberapa provider ternama seperti sbobet casino, ion casino, asia gaming, evolution gaming, pretty gaming, dreamgaming dan allbet, juga merupakan penyedia game live casino terbaik Situs Judi Khusus Slot Online Gampang Menang seperti judi roulette online, judi baccarat online, judi blackjack, judi dragon tiger online, judi dadu sicbo, dan masih banyak lainnya lagi.
ReplyDeleteSepak bola merupakan cabang olahraga paling populer di dunia, banyak pemain bertaruh di team atau negara favoritnya. Apalagi akan diadakan piala euro 2021 Situs Judi Khusus Slot Online Gampang Menang di tahun ini, sudah pasti banyak yang pasang taruhan judi bola. Untuk mendukung aktivitas taruhan anda kami sebagai agen bola online terpercaya menyediakan provider judi bola ternama seperti Sbobet, virtual sports, dan I-sports dengan pasaran lengkap.
Slotnation88.com merupakan situs judi poker terpercaya anti BOT dan sudah menjadi agen resmi dari beberapa provider game poker terbaik antara lain, 9Gaming, Balak Play, dan IDN Poker atau IDN Play. Situs ini juga situs judi slot online deposit via pulsa 10 ribu sudah menjadi surganya penggemar judi kartu online dikarenakan kelengkapan jenis permainanya, seperti capsa susun, poker online, poker 6, texas poker, blackjack poker, super 10, ceme online, ceme keliling, omaha, gaple online, domino qq, dan domino online semuanya ada di sini.
Pusatnya game arcade terlengkap hanya disini, arcade atau biasa disebut game Situs Judi Slot Online Gampang Menang dingdong sangat digemari oleh berbagai kalangan serta usia karena untuk memainkannya mudah dan tidak ribet. Slotnation88 sebagai bandar dan agen resmi juga sudah menjalin kerjasama dengan beberapa provider game casino terbaik seperti Fishing World, situs khusus judi slot online Joker Fishing sebagai tempat untuk bermain judi tembak ikan, juga ada MM Tangkas atau micky mouse. Kemudian Live Arcade yang menawarkan game Capit Duit, coin pusher, dan claw machine. Daftar Situs Judi Slot Online No.1 Terpercaya Dan game keluaran terbaru yaitu Giocoplus yang gameplay nya seperti tembak pesawat.
Native Somali Transcription nice and informative post thanks for the update
ReplyDeletedriving licence renewal nice and informative post thanks for the update
ReplyDeleteMovie reviews nice and informative post thanks for the update
ReplyDeleteTop Advertising Agency in Gurgaon nice and informative post thanks for the update
ReplyDeleteValueassignmenthelp.com is the finest website for assignment help online and homework help services. We have 10years of experience PhD level experts for all subjects. Value-assignment-help highly searched website for paper work and content writers. Check our services and our coverage range.
ReplyDeleteAssignment Help
Canada Assignment help
Australia assignment help
Best Assignment Online
Best Assignment Help
Blackgoat Creative nice and informative post thanks for the update
ReplyDeleteGood to be going to your blog once more, it has been months for me. Nicely this article that ive been waited for so long.
ReplyDelete경마사이트
경마
birth certificate agent Pune nice and informative post thanks for the update
ReplyDeleteteen chat for teenager
ReplyDeletehttps://www.zgamespc.com/wondershare-pdfelement-pro-crack-2021/
ReplyDeleteWondershare PDFelement Crack The developing purpose of this software is to deal with PDF files. So, if you would like to edit anything in your document then you will roll in this application. On the other hand, you can save your documents with a password.
https://techsoftkey.com/sony-vegas-pro-crack-2021/
ReplyDeleteSony Vegas Crack is a perfect editing tool that brings fast working. In other words, this app contains superb fast speed and you can edit your videos within minutes.
ReplyDeleteCamtasia Studio Crack
Camtasia Studio Crack is the best, famous, and all-in-one video editor and screen recorder software. This is software invented to make it simple to make a high-quality video on Mac and Windows device platforms. It is easy to learn to create a video with it. With its clear tutorials, you will come to know that how you can simply create a video. This will provide you with everything for creating a video like video effects, sounds, etc.
Wondershare Filmora crack
ReplyDeleteWondershare Filmora is the best and simple tool that use to build up the best videos you want to make. Therefore, you can use it to make the text and animation style that add to the videos
ReplyDeleteDisk Drill Crack
Disk Drill Crack is a tool that use to create a backup of any file. While it uses to save your file and get save your file that is there. While you can use to make storage device that is there and help to damage to get data entry cost. Also, it uses to get and make a solution to use the disk drill system in the system.
EaseUS MobiMover Pro Crack
ReplyDeleteEaseUS MobiMover Pro Cracked It is one of the best software that use to shift one device from another. It let you make and get a search from the data that are there. And it let you get out source files from any type of work. Hence, it cannot use to get convert from there. And it has a mobile or tablet version that works there.
iMyFone Fixppo Crack
ReplyDeleteiMyFone Fixppo Crack is an efficient program developed for IOS device recovery. On the other hand, it is the latest development tool with the power to fix issues related to PDAs. In other words, it can help in recovering the problems that are occurring in the devices.
OPC Company Registration nice and informative post thanks for the update
ReplyDelete"ebs on oci free guide
ReplyDeleteEBS R12 to Cloud For Beginners
oracle integration cloud services
az-303 questions
AWS Certified Solutions Architect For Beginners
AWS Solutions Architect Interview Questions
aws certified solutions architect - associate q&a
AWS Certified Solutions Architect Free training
da 100 examination questions
Power BI Data Analyst Certification For Beginners
Docker & CKA free training
Guide for Kubernetes Admin"
Balloon Decorators near me
ReplyDeleteBring joy on the faces of your loved ones by presenting them with special Balloon Decoration
Daily news nice and informative post thanks for the update
ReplyDeleteI would like to thank you for the efforts you had made for writing this awesome article, really explains everything in detail, the article is very interesting and effective. Thank you and good luck for the upcoming articles.
ReplyDeletebuy best esports jerseys
This blog is what I was looking for. This piece of content will really help me. Thanks for sharing it.
ReplyDeletecheap used pc parts
creative agency in gurgaon. nice and informative post thanks for the update
ReplyDeleteHP termination from vehicle (Car/Bike) in Ghaziabad nice and informative post thanks for the update
ReplyDeletegenç yaşlı ücretsiz chat ortamı
ReplyDeleteGood site you have here.. It’s hard to find quality writing like yours these days. I honestly appreciate individuals like you! Take care!! Feel free to visit my website; 카지노사이트링크
ReplyDeleteYoure so cool! I dont suppose Ive read something such as this before. So nice to find somebody with authentic applying for grants this subject. Feel free to visit my website; 배트맨토토프로
ReplyDeleteNow, you don’t have to worry about your internet problems. The solution to all of your problems can be found right over the phone. Dial TPG Customer Service Number, and make use of their satisfaction-guaranteed services.
ReplyDeletekochi to mumbai cruise nice and informative post thanks for the update
ReplyDeletemumbai to goa cruise ticket booking nice and informative post thanks for the update
ReplyDeletePassport lost nice and informative post thanks for the update
ReplyDeleteTop 10 Sex Positions that you must Try with your partner
ReplyDeleteI really like your content.
Thank you for sharing such amazing information with us. If are you searching for Best Web designing Agencies in Bangalore. So visit CyberWorx Technologies is best for you.
ReplyDelete
ReplyDeleteNice explanation and article. Continue to write articles like these, and visit my website at https://usacrack.info/ for more information.
Rockstar 1.1 Crack
DC Unlocker 1.00 Crack
DLL Files Fixer Crack
Quillbot Premium Crack
Teorex Inpaint 9.1 Crack
XYplorer Pro 22.20 Crack
Enscape3D Sketchup Crack
Thanks you for your views on the write-up. I am glad you liked it.
ReplyDeleteThis article is very helpful, Thank you for sharing the technical information. If you are searching for a Website Designing Company in Bangalore
ReplyDeletePress Release Box
ReplyDeletewebdesign dresden Webdesign Dresden gesucht? Hier Website erstellen lassen vom Webdesigner. Professionell & zuverlässig! Mit Iconic Marketing ® auf Seite 1 bei Google & Co.
ReplyDeleteAnnual ROC form filing for Private Limited Companies I read this post your post so quite an exceptionally instructive post much obliged for sharing this post, a Great article. Couldn't be composed much better! Keep it up
ReplyDeleteReally I enjoy your site with effective and useful information. It is included very nice post with a lot of our resources.thanks for share. 카지노사이트
ReplyDeleteI wan’t going to comment as this posts a bit old now, but just wanted to say thanks. 릴게임
ReplyDeletetop creative agency in delhi. a Great article. Couldn't be composed much better! Keep it up
ReplyDeleteRoot canal is a term which is used for describing your natural cavity that lies within the center of your tooth. The nerves of the tooth lie in the root canal. When the nerve tissue or the pulp of your tooth gets damaged, it will break down, resulting in multiplication of bacteria in the pulp chamber.The presence of bacteria and the decayed debris may cause a tooth infection. It can also cause a swelling that sometimes spreads to the other parts of the neck, face or head. If you are experiencing pain, tooth discoloration, swelling or a feeling of tenderness in your lymph nodes, there may be a chance that you require a root canal treatment.
ReplyDelete야한소설
대딸방
마사지
출장마사지
카지노사이트
You possess lifted an essential offspring. Bless for using. I would want to study better latest transactions from this blog. Preserve posting.
ReplyDelete무료야설
휴게텔
타이마사지
타이마사지
온라인카지노
Proceeded with instructive choices will efficiently, such as white stable light co. Great on sign in should get into the worker entrance will give of installments. Multi month following the left portion of the key phrase. As soon as you can permit you need to share your company related to love during the accreditation. Entryway establishment direct you will need to land their standing chances to your
ReplyDelete야설
대딸방
출장안마
스포츠마사지
카지노사이트
malatya eskort
ReplyDeleteağrı eskort
adana eskort
edirne eskort
zonguldak eskort
rize eskort
balıkesir eskort
karabük eskort
kırşehir eskort
konak eskort
Very interesting information and i really glad to getting this information.
ReplyDelete스포츠토토
I’m not sure where you’re getting your information, but great topic. I needs to spend some time learning much more or understanding more. Thanks for great information I was looking for this info for my mission.
ReplyDelete야한소설
대딸방
타이마사지
출장마사지
온라인카지노
To solve the CenturyLink account email problems, the first thing to ensure is your internet functioning. Moreover, check whether you are entering your proper credentials while logging into your account. Further, check whether the CenturyLink email configuration settings for the incoming and outgoing mail servers are correct. Delete the unnecessary and junk mails from your CenturyLink email account. The reason behind your CenturyLink Email Problems today
ReplyDeleteNice Blog, really liked it. I would also like to grab your attention on Jodhpur To Jaisalmer Tour Package
ReplyDeleteAOC-4 and MGT-7 form filing for Companies a Great article. Couldn't be composed much better! Keep it up
ReplyDeleteurfa eskort
ReplyDeleteamasya eskort
trabzon eskort
bursa eskort
uşak eskort
bakırköy eskort
düzce masöz
manisa masöz
You do not have to be concerned about internet connection issues anymore. You can contact TPG Customer Service Number if you have any problems or need assistance troubleshooting TPG internet. The phone number is (800-431-401) to get the needed support.
ReplyDeleteLooking for effective and reliable customer care support to troubleshoot your technical glitches? Dial Belong Contact Number (1-800-431-401), and get effective assistance.
ReplyDeleteIt’s nearly impossible to find well-informed people on this topic, however, you sound like you know what you’re talking about! Thanks
ReplyDelete야한소설
휴게텔
마사지블루
건마탑
카지노
Hello there, simply turned into aware of your weblog thru Google, and found that it is really informative. I am going to be careful for Brussels. I will appreciate for those who continue this in future. Many people will likely be benefited from your writing. Cheers!
ReplyDelete무료야설
오피
스포츠마사지
출장마사지
카지노사이트
thia ia Best University for Diploma Engineering in Roorkee
ReplyDeleteThe Power of Social Media a Great article. Couldn't be composed much better! Keep it up
ReplyDeleteCHANGING THE ADDRESS OF YOUR COMPANY/LLP (OTHER STATE/ ROC) a Great article. Couldn't be composed much better! Keep it up
ReplyDeleteITR OF INDIVIDUALS HAVING BUSINESS INCOME a Great article. Couldn't be composed much better! Keep it up
ReplyDeleteYour blog is perfect, thank you.
ReplyDeleteFollow also the most beautiful porn topics I was looking for sex and found these wonderful sites in the search engine.
سكسي محارم
سكس نت
سكس
BTMS 50 a Great article. Couldn't be composed much better! Keep it up
ReplyDeleteNiacinamide a Great article. Couldn't be composed much better! Keep it up
ReplyDeleteWe offer you Do My Classes For Me by Do My Classes Now for me services to the customers at pocket friendly rates. We have designed our pricing plans in such a way that every student could afford. So stop worrying about the budget. Give us a call and we will try to accommodate you in the best possible
ReplyDeleteland for sale in Florida is best rates. If you are wondering Your land in best locations of south Florida The reel n realtors may help you.
ReplyDeleteWhere Marketing Ends, Branding Begins a Great article. Couldn't be composed much better! Keep it up
ReplyDeleteIt’s a magical week for Everygame Poker as the online gaming brand is prepping for its upcoming extra spins week! Starting on February 14, players can enjoy extra spins on Nucleus Gaming’s Pixie Magic and Lucky Clovers with special deposit bonus codes. 바카라사이트
ReplyDeleteGreat article, your blog is very nice.
ReplyDeleteFollow the best porn stories through the following titles. I really enjoyed the following porn sites.
It is characterized by high speed.
قصص سكس محارم
قصص نيك محارم
قصص محارم
قصص سكس امهات
قصص سكس
قصص سكس محارم
قصص سكس مصورة
قصص سكس اخوات
قصص محارم
قصص نيك محارم
Fix your Verizon email not working problem
ReplyDeleteVerizon Webmail login issuesResolve Verizon Email Not Working Issue
WHY IS MY VERIZON EMAIL NOT WORKING IN OUTLOOK?
Fix Verizon AOL email not working Issue
Verizon email settings 2022
What are the Verizon AOL Email Settings?
Verizon aol email settings 2021
Verizon smtp settings
facebook account locked due to suspicious activity
ReplyDeletegoogle not sending verification code to phone
Z-library
Bresnan email
webpage not available
outlook settings for SBCGlobal net
chrome webpage not being available
Does chrome use a lot of ram
how to recover suspended Twitter account
ReplyDeleteYour blog is amazing, one of the most beautiful blogs I have seen. Thank you for this wonderful article.
Also, follow the best porn topics through the following titles:
تحميل نيك
صور سكس
سكس محارم
I saw your blog it's great, also watched great porn topics.
ReplyDeleteمحارم روسي
تحميل افلام نيك
تحميل سكس منقبات
سكس سما المصري
اخ ينيك اختة الكبيرة
سكسي محارم عنيف
Your blog is amazing, one of the most beautiful blogs I have seen. Thank you for this wonderful article.
ReplyDeleteAlso, follow the best porn topics through the following titles:
تحميل افلام نيك
تحميل افلام سكس
سكس كلاب
CLASS III DIGITAL SIGNATURE CERTIFICATE a Great article
ReplyDeleteVery cool, thank you for your beautiful blog.
ReplyDeleteI found the most beautiful sex video through the following titles:
bd sex videoنيك حماتي السمينة
bd sex videoبورنو محارم
bd sex videoمحارم سعودي
bd sex videoنيك امهات xmxx
bd sex videoتحميل نيك امهات
bd sex videoسكسي امهات جماعي
bd sex videoءىءء
https://johnakecsouthsudan.blogspot.com/2011/02/founding-conference-of-academics_24.html
ReplyDeleteIboysoft Data Recovery 2022 crack
ReplyDeleteCCleaner Pro 2022 crack
novaPDF Pro 2022 crack
CyberGhost VPN Premium 2022 crack
https://retailmarketing.co.in/ready-to-eat-instant-food-mixes-marketing/
ReplyDeleteThank you for sharing such excellent information with us. Visual Merchandising Course Online.
ReplyDeletenice post and article, best collection, will be thankful if you can share collection of websites which offers peshawari chappal. thank you very much...
ReplyDeleteCompare Rev Pricing vs. GoTranscript Pricing vs. myTranscriptionPlace Pricing a Great article.Well said, your every point is true. Thanks for sharing its an informative post
ReplyDelete
ReplyDeleteMobile App Development Company
Digital marketing Services
seo services
ppc management services
social media management company
website development company
Thanks for provide great informatic and looking beautiful blog, really nice required information & the things i never imagined and i would request, wright more blog and blog post like that for us. Thanks you once agian. tutoring Sydney
ReplyDeleteHi, I am John Smith I am Web Developer, It is an amazing blog thanks for the sharing the blog. Frantic infotech provide the php web development company such as an information about software development for costumer service. Frantic infotech also provide the Hardware Mobile App Development. The development of advanced web applications is Orient Software’s specialty and we will successfully fulfill all your web application development requirements, from small-sized to wider-ranged projects.
ReplyDeletemixpoint biography a Great article.Well said, your every point is true. Thanks for sharing its an informative post
ReplyDeleteImperial Money
ReplyDeleteImperial Money
Imperial Money
Imperial Money
Imperial Money
Great informative site. I'm really impressed after reading this blog post. I really appreciate the time and effort you spend to share this with us!Ready to know your Weekly Career Horoscope Today! Swansea kitchen fitters
ReplyDeletei am also seeking advanced programming tutorials because now a days I am working on global b2b marketplace online project
ReplyDeleteThank you for sharing such interesting information with us. Interior Design Institute in Delhi | Interior Designing Course Fees Delhi.
ReplyDeleteLifetime fitness membership This gym is known for its large facilities and wide range of member options. Minnesota was the first location of this famous chain.
ReplyDeleteHi, thank you so much for sharing this, it is extremely useful. Thanks again canaan avalon 1246
ReplyDeleteHi there
ReplyDeleteVery nice content and blog, I found it very informative and useful, hope to read more nice articles like this one around here,
Keep sharing the best content,
Best regards!
Your follower
Salvatore from:
Compra de Ingresso on-line – Os ingressos para visitar o Patrimônio Mundial Natural e uma das 7 Maravilhas da Natureza, que abriga as Cataratas do Iguaçu, são limitados e vendidos exclusivamente on-line, com agendamento de dia e horário para o passeio no site Tour Cataratas Cataratas do Iguaçu Preço É importante lembrar que não existe a opção de compra de bilhete físico no parque. É necessário adquirir o ingresso de entrada no Parque Nacional do Iguaçu com antecedência em sites autorizados.
Thanks and take care