A sample to illustrate the various issues of scoping:
?- Xs = [A,B], maplist(Y+\X^Z^(X+Y #= Z), Xs, Zs).
Xs = [A, B],
Zs = [_G1698, _G1701],
A+Y#=_G1698,
B+Y#=_G1701.
See http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord
Comments welcome!
Loop version for readability comparison:
?- Xs = [A,B], (foreach(X,Xs),foreach(Z,Zs),param(Y) do X+Y #= Z).
-- Joachim
Xs = [A,B], (foreach(X in Xs, [Z], ac1(Zs,[]), (Zs^0=[Z|Zs^1],X+Y#=Z))
I would prefer the recursive version to any iterative version.
Cheers,
Neng-Fa
What about a list of lists?
I am still not sure how to write this with for-loops compactly.
?- Xs = [[A],[B,C]], maplist(maplist(Y+\X^Z^(X+Y #= Z)), Xs, Zs).
Xs = [[A], [B, C]],
Zs = [[_G1350], [_G1356, _G1359]],
A+Y#=_G1350,
B+Y#=_G1356,
C+Y#=_G1359.
But in any case: lambdas and for loops are related but still quite
different.
One other use of lambdas is to get a different and more readable
quantification in setof/3:
http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord#setof
The loop version will be available in SICStus 4.1. --Mats
Lambda comes from functional programming. Why not use an
approach that comes from logic? Namely allowing assumption
programming (not invented by, have seen it already in some
places but cannot recall right now) and using it for
passing relations.
Lets first explain what I understand by assumption programming.
I understand by it allowing implication (:-) in goal literals
with the following semantics (BTW there is an interesting theoretical
unterpinning from Minimal Logic):
P, A |- B
-------------
P |- (A :- B)
So when a goal (A :- B) is reached the interpreter will add A
as a new assumption and then try to proof B. Here is a example
how this could be used for map:
map([],[])
map([X|Xs],[Y|Ys]) :- f(X,Y), map(Xs,Ys).
?- ((f(X,Y) :- Y is X+1) :- map([2,5],Z))
Z = [3,6]
Only implementation problem is maybe here that the assumption A needs
proper quantifier handly. Which means on one hand to allow forall
quantification of those assumption variables which should be local
to the assumption and regenerated for each invokation of the assumption.
And on the other hand those variables which are not quantified
should behave like goal variables, and also continue to communicate
with the goal. Here is an example of the behaviour:
?- (p(z,X) :- p(Y,Y))
X=z
Y=z
?- ((forall X p(z,X)) :- p(Y,Y))
Y=z
etc..
Anybody tried this already? Are there some usability problems with it,
such that passing a lambda expression around is more transparent than
dynamically changing the program by an assumption, although the change
goes away when we exit the goal.
Bye
And here is yet another idea to do it, and also currious what people
think about it and why somebody would prefer it over lambdas or not.
I simply would use object oriented programming:
map([],[],O)
map([X|Xs],[Y|Ys],O) :- O:f(X,Y), map(Xs,Ys,O).
myobj:f(X,Y) :- Y is X+1.
?- map([2,5],X,myobj)
X = [3,6]
We could also introduce anonymous classes/objects to act as closures.
Something like that:
?- map([2,5],Z,{f(X,Y) :- Y is X+1}).
Z = [3,6]
Now the idea would be that the anonymous classes/objects are created
at compile time or when the {} is first encountered. Problems of
assumption programming appear again.
But new problems pop up also. Suppose we generate objects on the fly,
we would trash memory. So how is the anonymous object automatically
reclaimed in the light of cut, tail recursion etc..?
Is the conclusion this that we would need to compile time a class
which has kind of a constructor where we can pass variables that
should reappear in the closure?
So I am just reading:
Conclusion
There is no need for higher order extensions to bloat ISO
Prolog's syntax. The traditional call/N and this library
are sufficient. The real challenge is an effective error
detection and an efficient compilation scheme. But all
this can be done within 13211-1 and 13211-2.
http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord
Ok, I will bookmark the page and dig deeper when I have time. This
makes me really curious.
Bye
> Ulrich Neumerkel wrote:
> > Recently, I developed a small library lambda.pl to provide lambda
> > Y in Prolog. The library runs in SWI and YAP - and with minimal
Both the idea having implicational goals, dealt with by
local assumptions, and lambda abstraction are well dealt with
in Lambda-Prolog:
http://www.lix.polytechnique.fr/~dale/lProlog/
> Bye
>
>
--
Alan Smaill
Note that both in Logtalk and, I believe, in most (if not all) Prolgo
module systems, you can already pass a predicate such as map/3 an
explicitly qualified closure.
Cheers,
Paulo
> Both the idea having implicational goals, dealt with by
> local assumptions, and lambda abstraction are well dealt with
> in Lambda-Prolog:
>
> http://www.lix.polytechnique.fr/~dale/lProlog/
>
>> Bye
Can I use easily ISO prolog to implement lambda prolog?
Are there lambda prologs without types?
Are there lambda prologs that can reach classical logic inference?
(a stupid approach would be to allow double negation elimination
together with minimal logic, but maybe another approach is necessary)
When they can reach classical logic inference, do they just simulate
a prover or is it directly natively running?
Bye
> Alan Smaill schrieb:
>
> > Both the idea having implicational goals, dealt with by
> > local assumptions, and lambda abstraction are well dealt with
> > in Lambda-Prolog:
> > http://www.lix.polytechnique.fr/~dale/lProlog/
> >
> >> Bye
>
> Can I use easily ISO prolog to implement lambda prolog?
I believe The first implementation was in Prolog ...
> Are there lambda prologs without types?
Types are needed for higher-order unification, which is
also a feature of the language.
> Are there lambda prologs that can reach classical logic inference?
> (a stupid approach would be to allow double negation elimination
> together with minimal logic, but maybe another approach is necessary)
That depends what you're after, and what count as reaching it.
The underlying logic is a part of higher-order intuitionistic logic
(as the underlying logic of (pure) Prolog is intuitionistic first-order).
> When they can reach classical logic inference, do they just simulate
> a prover or is it directly natively running?
See above.
There is a fair-sized litereture out there, if you are interested,
including on the different implementations, which I know less about.
> Bye
--
Alan Smaill
The examples directory of QuProlog contains a couple of lambda term
evaluators and one that does type inference of lambda terms.
E.g. the beta rule can be written as the fact
(lambda x A)(B) => [B/x]A.
There is also a small natural deduction prover.
Ergo is a quite sophisticated sequent calculus theorem prover written
in Qu-Prolog.
Find both at http://www.itee.uq.edu.au/~pjr/
Peter
One advantage of library(lambda) is that it fits seamlessly with
traditional higher order predicates based on call/N. So there
is no need to change everything.
The scoping of variables is most probably difficult to handle
Also your scheme does not fit together with the
common higher order predicates. The traditional maplist/2,3 (~ 1983)
has its arguments in a different order - not without reason:
?- Xss = [[[1,2]],[[3]]], maplist(maplist(maplist(\X^Y^(Y is X+1))), Xss, Yss).
...
>Is the conclusion this that we would need to compile ...
A clean sematics is what is needed first. Most compiler based approaches
are not only difficult to implement but also difficult to understand.
I am just reading a Ciao doc (2000 Tutorial),
which says:
?- call(p,1,2,3,4,5,6). --> p(1,2,3,4,5,6)
?- call(p(1),2,3,4,5,6). --> p(2,1,3,4,5,6)
?- call(p(1,2),3,4,5,6). --> p(3,1,2,4,5,6)
?- call(p(1,2,3),4,5,6). --> p(4,1,2,3,5,6)
?- call(p(1,2,3,4),5,6). --> p(5,1,2,3,4,6)
?- call(p(1,2,3,4,5),6). --> p(6,1,2,3,4,5)
?- call(p(1,2,3,4,5,6)). --> p(1,2,3,4,5,6)
This is not to be expected, or am I wrong?
(I guess we have the rule: call(P(X1,...,Xm),
Y1,...,Yn) :- P(X1,...,Xm, Y1,...,Yn))
Bye
This does not correspond to what call/N is in other systems.
It might be useful in certain situations, but it definitely
should get a different name.
I cannot see how this convention would allow arbitrary nesting
in maplist/N. I.e.
?- Xsss = [[[1,2,3],[4]],[[5]]], maplist(maplist(maplist(\X^Y^(Y is X+3))), Xsss, Ysss).
Xsss = [[[1, 2, 3], [4]], [[5]]],
Ysss = [[[4, 5, 6], [7]], [[8]]].
Historically, the first public mentioning of call/N was
Richard O'Keefe's posting 1987-09-26 with Subject: "The call/N family".
Quintus (as a library) and NU were probably the first to adopt it.
It was not part of DECsystem 10 Prolog which had apply/2 only.
>(I guess we have the rule:
> call(P(X1,...,Xm), Y1,...,Yn) :- P(X1,...,Xm, Y1,...,Yn))
Exactly! And this is a good occasion to make this more explicit in
the current ISO drafts.
I have implemented Ulrich's syntax for lambda expressions in the
current development version of Logtalk (r5204 or later). It should
work with all supported back-end Prolog compilers.
Sample queries using GNU Prolog as the back-end compiler:
pmmbp:~ pmoura$ gplgt
...
Logtalk 2.38.0
Copyright (c) 1998-2009 Paulo Moura
...
GNU Prolog 1.3.2
By Daniel Diaz
Copyright (C) 1999-2009 Daniel Diaz
| ?- {metapredicates(loader)}.
...
yes
| ?- meta::map(\X^(X>3),[4,5,9]).
yes
| ?- meta::map(t+\X^Y^(X=A-B,Y=B-A), [1-a,2-b,3-c], Zs).
Zs = [a-1,b-2,c-3]
yes
| ?- meta::map(\X^Y^(X=A-B,Y=B-A), [1-a,2-b,3-c], Zs).
Zs = [a-1,b-2,c-3]
(1 ms) yes
| ?- meta::map(\X^(B-A)^(X=A-B), [1-a,2-b,3-c], Zs).
Zs = [a-1,b-2,c-3]
yes
| ?- meta::map(\ (A-B)^(B-A)^true, [1-a,2-b,3-c], Zs).
Zs = [a-1,b-2,c-3]
yes
| ?- Xss= [[1,2],[3]], meta::map(meta::map(\X^Y^Z^(X+Y#=Z)), Xss, Yss,
Zss).
Xss = [[1,2],[3]]
Yss = [[_#3(0..268435454),_#54(0..268435453)],[_#105(0..268435452)]]
Zss = [[_#22(1..268435455),_#73(2..268435455)],[_#124(3..268435455)]]
(1 ms) yes
| ?- Xss= [[1,2],[3]], meta::map(meta::map(Y+\X^Z^(X+Y#=Z)), Xss,
Zss).
Xss = [[1,2],[3]]
Y = _#3(0..268435452)
Zss = [[_#22(1..268435453),_#66(2..268435454)],[_#110(3..268435455)]]
(1 ms) yes
Feedback welcome, specially when using other back-end Prolog
compilers. Cheers,
Paulo
Could you tell me what
?- \ _^write(metipse).
does? The reason I am asking is that this seems to be a very
frequent error people do in the beginning (Peter Szeredi alerted
me to this very early on).
It is for this reason that the implementation contains the
no_hat_call/1 - which posed some challenge to be written in such
a manner that it is compatible with the minimally different
module systems of SWI and YAP - and at the same time conformant
to ISO 13211-2 Modules.
http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/lambda.pl
In the current Logtalk development version (r5205):
?- logtalk << \ _^write(metipse).
metipse
true.
In the above call, "logtalk" is a built-in, empty object and the <</2
is the Logtalk control construct for context switching calls, which
allows calling a goal within the context of an object.
> It is for this reason that the implementation contains the
> no_hat_call/1 - which posed some challenge to be written in such
> a manner that it is compatible with the minimally different
> module systems of SWI and YAP - and at the same time conformant
> to ISO 13211-2 Modules.
>
> http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/lambda.pl
I will eventually implement the equivalent of the no_hat_call/1. So
far, the current implementation of lambda expressions in Logtalk is
just an experiment in order to gather feedback and gain insights on a
native implementation (i.e. an implementation that doesn't use a
library object). A problem that we share in both implementations is,
of course, performance. But that will only be a concern when the
implementation stabilizes.
Cheers,
Paulo
Error checking is implemented in r5208:
| ?- logtalk << \ _^write(hello).
uncaught exception: error(representation_error(lambda_parameters),
\_278^write(hello),logtalk)
| ?- meta::map(\ X^3,[4,5,9]).
uncaught exception: error(type_error(callable,3),\_279^3,meta)
Cheers,
Paulo
Very fine! Having an error here is the only way to prevent so many
misunderstandings of beginners - the runtime overheads are neglectable
- see my next-to-be-written response.
Concerning the representation error - which might appear as somewhat
unusual - here is my motivation:
If one peeks to functional languages, the most "natural" error in this
situation would be a type error. However, type errors have a very
clear connotation in Prolog: They mean semantically failure. In fact,
some Prolog systems even offer to fail optionally, for a particular
goal for type and domain errors. I am still not convinced that this
situation corresponds to such a failure. Also, another error class
covers semantical failure - see below.
I chose a representation error because it is semantically neutral:
There are cases where representation errors would "hide" a case which
actually should fail and others where it should succeed. For example
functor(F,f,10000) may issue a representation error provided max_arity
is smaller than 10000. But in a system with a sufficiently large or
unbounded max_arity it would succeed.
I am less and less happy with this representation error, as - in a
sense - I read a promise in ISO 13211-1 7.12.2 Error Classification f:
| f) There shall be a Representation Error when an
| implementation defined limit has been breached. It has
| the form representation_error(Flag) where
| Flag \el {
| character,
| character_code,
| in_character_code,
| max_arity,
| max_integer,
| min_integer
| }.
The promise is that since the limit that has been breached is "only"
implementation defined, there might be implementations that have a
higher limit or none at all. But in the situation of not passing
sufficiently many arguments there is nothing that depends on an
implementation at all.
Another alternative might be the existence error. Note that
semantically an existence error of a procedure also means failure. So
maybe existence_error(lambda_parameter, Culprit) might be a good idea
with Culprit showing the remaining parameters.
There are the following options to implement library(lambda) which can
be handled differently on a case-by-case basis.
a) no optimization - library(lambda) as is
b) additionally to a) for problematic cases warnings (only messages
during compilation) or errors (refusing compilation)
c) optimize while maintaining *complete* operational equivalence
But please do not consider the path of optimizing and changing the
meaning of lambdas. Also please never optimize incorrectly with a
warning. Let's leave that box closed!
(Exactly for this reason there is still some hesitation for
integrating library(lambda) into SWI.)
Maybe
http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord
is not clear enough?
Much to my chagrin such cases of ambiguity already exist in related
situations. Consider SICStus 3 and its clpfd imlementation:
| ?- E = 1+1, X #= E.
E = 1+1,
X = 1+1 ?
yes
| ?- E = 1+1, call(X #= E).
E = 1+1,
X = 2 ?
Why is this such a problem? Aren't all resonable uses of constraints
effectively statically resolvable? Unfortunately not.
Higher order programming is very useful exactly for clpfd programs -
and even already without lambdas! You can express relations over
several lists compactly. Take the library(lists) of SICStus 4 which
contains the traditional maplist/2
?- E = 1+1, maplist(#=(E),[X]).
will resolve dynamically, but an optimization would do this
statically. So we would get exactly the same difference as in the
cases with and without call/1.
In SWI and YAP, library(clpfd) behaves identical for all such cases.
Thanks to Markus Triska's meticulous diligence.
Maybe we should make a distinction, using different exception terms,
between missing lambda parameters and too many lambda parameters?
Cheers,
Paulo
Ditto for Logtalk r5209.
> b) additionally to a) for problematic cases warnings (only messages
> during compilation) or errors (refusing compilation)
Hope to include some of that in Logtalk 2.38.9 stable.
> c) optimize while maintaining *complete* operational equivalence
>
> But please do not consider the path of optimizing and changing the
> meaning of lambdas. Also please never optimize incorrectly with a
> warning. Let's leave that box closed!
Optimizations that change the semantics are bugs.
Btw, the Logtalk implementation is based only on your proposed syntax,
not in your library(lambda) code. An interesting feature of the
Logtalk implementation is that the number of lambda parameters is only
limited by max_arity of the back-end Prolog compiler.
Cheers,
Paulo
How can we say that we transmit too many parameters? In ISO-Prolog,
and even more in SWI, we associate frequently one name to many
different arities. Not only is ISO-Prolog a precedent, but also
phrase/2, phrase/3 and even some built-ins in DECsystem 10 Prolog as of
1978.
?- [user].
|: factum(1,2).
|: % user://1 compiled 0.00 sec, 312 bytes
true.
?- \ factum(1,2).
true.
?- \_^factum(1,2).
ERROR: user://1:26:
Cannot represent due to `lambda_parameters'
So here we have a case where a lambda parameter expects
an argument, but does not get one.
?- call( \factum(1,2), 3).
ERROR: user://1:29:
lambda:\/2: Undefined procedure: factum/3
However, there are definitions for:
factum/2
And this is the case you probably want to be handled differently.
Impossible - think of open/2 and 3 etc.
Are you sure? Consider (using Logtalk r5209 + GNU Prolog):
| ?- meta::map(\X^Y^(X=A-B,Y=B-A), [1-a,2-b,3-c], Zs).
Zs = [a-1,b-2,c-3]
(1 ms) yes
| ?- meta::map(\X^Y^Z^(X=A-B,Y=B-A), [1-a,2-b,3-c], Zs).
uncaught exception: error(representation_error(lambda_parameters),
\_278^_281^_284^(_278=_293-_294,_281=_294-_293),meta)
| ?- meta::map(\X^(X=A-B,Y=B-A), [1-a,2-b,3-c], Zs).
uncaught exception: error(representation_error(lambda_parameters),
\_278^(_278=_287-_288,_290=_288-_287),meta)
In the first error case, there is an extraneous lambda parameter (Z).
In the second case, a lambda parameter is missing. Both cases can be
easily detected and differentiated when unifying the lambda parameters
with the maplist/3 closure arguments. At least in the Logtalk
implementation. The question for me is: is it worth to have two
distinct exception terms for these cases? Say:
representation_error(missing_lambda_parameters) representation_error
(extraneous_lambda_parameters)
I think not but your feedback is welcome.
Cheers,
Paulo
It is definitely a misunderstanding. You have implemented
only half of the lambdas. I put the entire source code out
such as to avoid misunderstandings as these. But evidently
providing a complete implementation is not enough.
That's why specifications are so necessary.
Please refer to Richard O'Keefe's examples. They are just
above
http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord#Hiord
common_prefix(Front, Xs, Ys) :-
maplist(Front+\append(Front), Xs, Ys).
?- common_prefix([1],Xs,Ys).
Xs = [],
Ys = [] ;
Xs = [_G2168],
Ys = [[1|_G2168]] ;
Xs = [_G2168, _G2187],
Ys = [[1|_G2168], [1|_G2187]]
What do you get here?
But maybe I implemented the better half? Just kidding :-)
> I put the entire source code out
> such as to avoid misunderstandings as these. But evidently
> providing a complete implementation is not enough.
Note that I'm not using a single bit of your implementation. I'm only
taking from your proposal the lambda expressions syntax and query
examples.
> That's why specifications are so necessary.
>
> Please refer to Richard O'Keefe's examples. They are just
> above
>
> http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord#Hiord
>
> common_prefix(Front, Xs, Ys) :-
> maplist(Front+\append(Front), Xs, Ys).
>
> ?- common_prefix([1],Xs,Ys).
> Xs = [],
> Ys = [] ;
> Xs = [_G2168],
> Ys = [[1|_G2168]] ;
> Xs = [_G2168, _G2187],
> Ys = [[1|_G2168], [1|_G2187]]
>
> What do you get here?
In the current development version (not yet in the Subversion server):
| ?- half::common_prefix([1],Xs,Ys).
Xs = []
Ys = [] ? ;
Xs = [A]
Ys = [[1|A]] ? ;
Xs = [A,B]
Ys = [[1|A],[1|B]] ? ;
Xs = [A,B,C]
Ys = [[1|A],[1|B],[1|C]] ? ;
Xs = [A,B,C,D]
Ys = [[1|A],[1|B],[1|C],[1|D]] ? ;
...
Cheers,
Paulo
Committed in r5210. Is quite possible that bugs remain, however.
Moreover, not a single optimization yet in r5210.
Cheers,
Paulo
What was really interesting here, is that we identified differences
by looking at the "corner cases" only. One case to underline how
important errors are in Prolog.
Logtalk 2.38.0, released today, contains a (no longer experimental)
implementation of lambda expressions (not based in Ulrich's lambda
library implementation). This implementation uses a non-frills syntax
(different from the one in Ulrich's proposal) that reuses existing ISO
Prolog standard operators:
http://logtalk.org/manuals/refman/grammar.html#grammar_lambdas
Currying is supported. Example queries can be found here:
http://svn.logtalk.org/viewvc.cgi/trunk/examples/lambdas/
Needless to say, lambda expressions are supported when using all
compatible back-end Prolog compilers :-) Enjoy!
Cheers,
Paulo
I published a more user friendly presentation in my blog:
http://blog.logtalk.org/2009/12/08/lambda-expressions-in-logtalk/
Cheers,
Paulo
Just to state this clearly: That version is wrong again.
I tried several times, to explain you what the deep
reasons behind are (on the SWI-list) but it seems your
major goal is to use a different syntax. So I give up.
Care to post an example that shows what's wrong with it? E.g.
something that is possible with your "lambda" library implementation
that is not possible using the current Logtalk implementation? Or an
example where the Logtalk implementation returns a wrong answer? Can
you back up your claim with hard evidence? I'm happy to learn from my
mistakes.
> I tried several times, to explain you what the deep
> reasons behind are (on the SWI-list)
Like the following one?
https://mailbox.iai.uni-bonn.de/mailman/public/swi-prolog/2009/002611.html
Sure, is quite enlightening in telling that *something* is wrong
without actually pointing to *what* is actually wrong.
> but it seems your
> major goal is to use a different syntax.
No, it's not. You're simply ignoring the critics to your proposed
syntax made by me and others, specially in the SWI-Prolog mailing
list. Have you actually read my blog post?
> So I give up.
That's perfectly within your rights.
Regards,
Paulo
http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/lambda.pl