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 18, 2008

Quote of the Day: Victor Hugo on nihilism

Filed under: QOTD — metacircular @ 3:34 pm

With nihilism, no discussion is possible; for the nihilist logic doubts the existence of its interlocutor, and is not quite sure that it exists itself.

— Victor Hugo in Les Miserables

September 17, 2008

Quote of the day

Filed under: Life — metacircular @ 11:23 am

The dullest person becomes interesting if you feel that he is really himself, that he is not holding up some absurd shield or other in front of his shivering soul.

—Arthur Christopher Benson in The Upton Letters.

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 6, 2008

HOWTO: Get the scroll wheel working on Linux under VMWare with Logitech MX518 mouse

Filed under: Lunix — metacircular @ 6:03 pm

This is just for my own reference.

I am running Ubuntu Hardy under VMWare Workstation. This should let you scroll webpages in Firefox. The back/forward buttons still don’t work, though.

Instructions to get scroll wheel working

  1. Open up a terminal window and type the following:

    sudo gedit /etc/X11/xorg.conf

  2. Find the section that says Section “InputDevice”.
  3. Replace that section with the following:
    
    Section "InputDevice"
    	Identifier	"Configured Mouse"
    	Driver		"vmmouse"
    	Option		"CorePointer"
    	Option		"Protocol"	"ImPS/2"
    	Option		"Device"	"/dev/input/mice"
    	Option		"ZAxisMapping"	"4 5"
    EndSection
    
  4. Restart X Windows by typing Ctrl+Alt+Backspace. Log in and the scroll wheel should work.

Good luck!

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.

Blog at WordPress.com.