However, that's telling the language what to do. As sentient beings, we can look at that and infer more information. Given @Z and @X, we could infer @Y. Given just @Z, we could infer all combinations of @X and @Y that can be combined to form @Z.

Perl cannot do that. In logic programming, however, by defining what append() looks like, we get all of that other information.

In Prolog, it looks like this:

append([], X, X).
append([W|X],Y,[W|Z]) :- append(X,Y,Z).

(There's actually often something called a "cut" after the first definition, but we'll keep this simple.)

What the above code says is "appending an empty list to a non-empty list yields the non-empty list." This is a boundary condition. Logic programs frequently require a careful analysis of boundary conditions to avoid infinite loops (similar to how recursive functions in Perl generally should have a terminating condition defined in them.)

The second line is where the bulk of the work gets done. In Prolog, to identify the head (first element) of a list and its tail (all elements except the first), we use the syntax [head|tail]. Since ":-" is read as "if" in Prolog, what this says if we want to concatenate (a,b,c) and (d,e,f):