Metacircular thoughts

September 23, 2008

Prolog just blew my mind!

Filed under: Prolog — metacircular @ 11:04 pm

A while ago, I came across a fascinating blog entry apparently detailing how to embed an ALGOL-like language in Prolog, with a compelling concrete syntax exposed. However, as the comments indicate, his code did not work as is. Changing the “then” and “else” creators to be infix and adhering to the defined convention of “if {Cond} …” (he used “if(Cond) …”) seems to make his code samples work.

Here’s a more friendly guide to getting this awesome code to work. Skip to “code samples” below for awesome examples of this embedded language in action.

My system setup

I am running Ubuntu Hardy with SWI-Prolog 5.6.47; I installed SWI-Prolog straight from the Debian packages (through Synaptic, the usual way). Just get yourself a copy of Ubuntu and install SWI Prolog the sane way and this code should work.

The interpreter

change_member(_, N, [], [N]).
change_member(O, N, [O|XS], [N|XS]).
change_member(O, N, [X|XS], [X|YS]) :- change_member(O, N, XS, YS).

:- op(900, xfy, :=).
:- op(100, fx, [if]).
:- op(125, fx, [while,  return]).
:- op(125, yfx, [do, then, else]).

run({Block},                          EnvI, O, Ret) :-
  run(Block, EnvI, O, Ret).

run((First; Second),                  EnvI, O, Ret) :-
  run(First, EnvI, M, _), run(Second, M, O, Ret).

run(Place := Expr,                    EnvI, O, void) :-
  eval(Expr, Value, EnvI), change_member(Place = _, Place = Value, EnvI, O), !.

run(if {Cond} then {This} else {That}, EnvI, O, Ret) :-
  eval(Cond, Value, EnvI),
  ( Value = true -> Body = This ; Body = That ),
  run(Body, EnvI, O, Ret).

run(while(Cond) do {Body},            EnvI, O, Ret) :-
  eval(Cond, Value, EnvI),
  ( Value = true -> run(Body, EnvI, M, _),
                    run(while(Cond) do {Body}, M, O, Ret)
  ; O = EnvI, Ret = void
  ).

run(return Value,                     EnvI, O, Ret) :-
  eval(Value, Ret, EnvI), O = EnvI.

eval(Var,   Val,   Env) :- member(Var = Val, Env), !.
eval(Num,   Num,   _  ) :- number(Num), !.
eval(true,  true,  _  ).
eval(false, false, _  ).

eval(X + Y, Z, Env) :- eval(X, XV, Env), eval(Y, YV, Env), Z is XV + YV.
eval(X - Y, Z, Env) :- eval(X, XV, Env), eval(Y, YV, Env), Z is XV - YV.
eval(X * Y, Z, Env) :- eval(X, XV, Env), eval(Y, YV, Env), Z is XV * YV.
eval(X / Y, Z, Env) :- eval(X, XV, Env), eval(Y, YV, Env), Z is truncate(XV / YV).

eval(X = Y, B, Env) :- eval(X, XV, Env), eval(Y, YV, Env),
  ( XV = YV -> B = true ; B = false ).
eval(X < Y, B, Env) :- eval(X, XV, Env), eval(Y, YV, Env),
  ( XV < YV -> B = true ; B = false ).
eval(X > Y, B, Env) :- eval(X, XV, Env), eval(Y, YV, Env),
  ( XV > YV -> B = true ; B = false ).
eval(not(P), B, Env) :- eval(P, PV, Env),
  ( PV = true -> B = false ; B = true ).

Language samples

Here are samples of using this little embedded language we’ve built along with the evaluation results in comments.

factorial(N, {i := 1;
              n := N;
              while(n > 0) do {
                i := i * n;
                n := n - 1
              };
              return i}).

%% ?- factorial(5, P), run(P, [], _, X).

%% X = 120

fibonacci(F, {i := 1; a := 1; b := 1;
              while(not(i = F)) do {
                a := a + b;
                b := a - b;
                i := i + 1
              };
              return b}).

%% ?- fibonacci(7, P), run(P, [], _, X).

%% X = 13

pow2(N, {i := N;
         n := 1;
         while(not(i = 0)) do {
           i := i - 1;
           n := n * 2
         };
         return n}).

%% ?- pow2(8, P), run(P, [], _, X).

%% X = 256

gcd(X, Y,
    {x := X;
     y := Y;
     while(not(x = 0)) do {
       if {x < y} then {
         y := y - x
       }
       else {
         x := x - y
       }
     };
     return y}).

%% ?- A is 2*2*5*7*13, B is 5*13*7*7, gcd(A, B, P), run(P, [], _, X).

%% A = 1820,
%% B = 3185,
%% X = 455

isqrt(N, {q := N; p := (q + N / q) / 2;
          while(q > p) do {
            q := p;
            p := (q + N / q) / 2
          };
          return p}).

%% ?- isqrt(81, P), run(P, [], _, X).

%% X = 9

Discussion

As best I can tell this actually works. For instance, evaluating our Fibonacci function at 150 gives the correct value, spitting back 9969216677189303386214405760200 as should be the case. Notice how, therefore, we get, among many other things, SWI-Prolog’s built-in arbitrary-precision integers for free — a classic example of why embedded domain-specific languages are such a huge win.

The implementation depends heavily on Prolog’s excellent support for custom symbolic operators and pattern matching abilities, even tolerating added bits of syntactic sugar like the brackets used to delimit boolean conditions in our if-then-else blocks.

Obviously you wouldn’t want to use an interpreter this trivial for serious production/mission-critical stuff, but I think it definitely provides a compelling answer to how far a computer language could conceivably be extended to support new syntax. Languages that claim to support language-oriented programming, but don’t support custom operators with custom fixity and precedence — you’re on notice.

September 13, 2008

Experimenting with Prolog

Filed under: In the beginning, Prolog — metacircular @ 12:13 am

Here’s some hypothetical ActiveRecord-like application metadata for a blog-like application along with a utility function for querying such metadata.


:- op(500, fx, many).
:- op(500, fx, one).
:- op(600, fx, always).
:- op(600, xf, notnull).

:- op(500, fx, at_least).
:- op(500, fx, at_most).
:- op(500, fx, exactly).

model(post,
      title(length(at_least 10)) notnull,
      many tags(name),
      always one author(name notnull, email(email_addr), url(url)),
      body(memo),
      many comments,
      chronological).

model(comment,
      name,
      email(validate_regex("\w+ blah blah line noise here")),
      ip(ip_addr),
      body(memo),
      nonenull,
      chronological).

get_predicate_instances(Predicate, Instance) :-
        between(1, 255, Arity),
        functor(Instance, Predicate, Arity),
        predicate_property(Instance, interpreted),
        call(Instance).

get_predicate_instances allows us to query all instances of a predicate without needing to restrict their arity by enclosing things in lists. Query facilities like it would allow you to place fewer restrictions on what kind of input a user can provide when describing an application.

The point is, if you create a domain-specific metadata language, Prolog can slice and dice it quite nicely. No XML required, no search/traversal boilerplate.

Designing a system like this in a vacuum would be a mistake; I was just toying with postfix and prefix operators more than anything else. What we need is an application we can use every day to drive the development of a system like this.

September 4, 2008

Enjoy the power of Linux from the safety of Windows with VMWare

Filed under: Lunix, Prolog, The Dark Side, Virtualization — metacircular @ 11:10 pm

I’ve been a fan of Linux for a very long time. I’ve sworn off Windows at least a dozen times — but each time I came crawling back, because there was always something I couldn’t figure out how to configure or get to work: in the past it was usually printing, video, audio, or multimedia-related.

With the rise of Ubuntu, most of those things just work by default. But still, having to give up Windows gaming, Photoshop, and other proprietary crap I can’t kick the habit of will always force me to keep Windows installed. I think this is the best of both worlds: I have Ubuntu running under VMWare Workstation running on one monitor (at 1680×1050, too, thanks to VMWare Tools) and Windows on the other. I can, for instance, do web development on the Linux screen and test it on Safari, Google Chrome, Firefox and Internet Explorer in Windows.

In the screenshot above you can see me drafting this post in Windows on Chrome while listening to crappy dance music in iTunes and, meanwhile, tinkering with Emacs, git and Prolog under Linux. Good times.

This way, I can make use of both monitors without having to figure out Xinerama or tinkering with stupid drivers in order to make use of my video card in Linux. It takes away a lot of the hassle and if you’ve been wanting to try an alternative operating system but haven’t been able to move out, this is a good compromise.

August 18, 2007

New beginnings: natural language processing

Filed under: Prolog — metacircular @ 7:46 pm

I am planning on undertaking a research project in my spare time involving automatically data mining and natural language generation.

The first thing to do is to learn Prolog thoroughly. Prolog has a number of interesting properties, among them that parsing is declarative, for free, making it even more effortless than combinator parsing in functional programming languages.

More posts will be forthcoming.

I was once overcome with an urge to rant about the relative merits of programming languages. I am done with this and hopefully it will be something that gets covered up in Google searches by more valuable contributions I leave in the Internet crumb trail anyone who actively participates in online discussion creates.

May 14, 2007

What do you get in a commercial Common Lisp implementation?

Filed under: Lisp, Politics, Prolog, The Dark Side, Web development — metacircular @ 8:44 pm

Commercial Common Lisp implementations like LispWorks and Allegro Common Lisp cost thousands of dollars. What do you get in them which isn’t included in SBCL or something, which costs $0?

  • ACL has AllegroCache, a true object database which has ACID transactions and scales to billions of objects. It is used transparently and integrates seamlessly with CLOS, Common Lisp’s ridiculously powerful object system. Depending on the project, this alone could be worth thousands of dollars.
  • LW and ACL have fast embedded Prolog compilers which seamlessly integrates with everything else in the distribution, including AllegroCache. LW actually gives you a full-blown expert system IDE (which is LW’s distinguishing feature as it doesn’t have an object database). In the ’80s, the kind of expert system functionality LW provides costed thousands of 1980s dollars.
  • Good cross-platform support so that you don’t run into the kind of problems the reddits had when working between Mac OS X and FreeBSD.
  • Libraries for web programming: XML/SOAP/WSDL/yadda yadda parsers, HTML parsers, a Lispish web application server and HTTP server, and easy libraries for all major networking protocols bundled in.
  • Cross-platform GUI libraries which are good enough for prototyping “programmer interfaces”.
  • Database libraries: ODBC, MySQL, Postgresql, Oracle.
  • Excellent foreign function interfaces for C libraries.
  • ACL provides OLE/COM linkage and examples of things like creating a simple CLOS layer for working with Excel.
  • Linkage to Java and/or .NET (e.g., jLinker and full support for RDNZL), which, together with the C FFI, means you can easily use code written in all the most popular languages.
  • Native threading (I guess you don’t get this), Unicode, and other oft-neglected but essential stuff.
  • Abundant working examples and comprehensive, helpful documentation for all of this.
  • All these libraries are bundled in, they work, they’re efficient, and they are supported.

All Common Lisp distributions give you efficient implementations of CLOS, a metacircular multiple-dispatch multiple-inheritance object system with an amazing metaobject protocol, as well as mini-languages for pretty-printing and iteration. SBCL, LispWorks, and ACL all have optimizing native-code compilers that give you performance somewhere between C and Java. SLIME, LW, and ACL provide IDEs matched only by Squeak in power.

Clearly if you pay $3,000 or however much, you’re getting a helluva lot more than just an implementation of a spec from 1984.

May 11, 2007

Easter egg in SWI Prolog

Filed under: Life, Prolog — metacircular @ 11:46 pm

?- True.
% ... 1,000,000 ............ 10,000,000 years later
%
%       >> 42 << (last release gives the question)

Blog at WordPress.com.