PreviousUpNext

5.3.6  Mutually Recursive Functions and Datastructures

A C enum declaration allows definition of a data type consisting of a finite list of alternatives:

    enum Color { RED, GREEN, BLUE };

The enum declaration is not particularly near and dear to the C programmer’s heart. In fact, the enum declaration was not even mentioned in The C Programming Language.

Mythryl has a similar construct:

    Color = RED | GREEN | BLUE;

This construct is however very near and dear indeed to the heart of the Mythryl programmer; it is the rock upon which mutually recursive datastructures are built, which are in turn the backbone of sophisticated programs such as the Mythryl compiler itself.

Simple declarations such as the above one are frequently used to define a data type which can then be used in records:

    #!/usr/bin/mythryl

    Color  = BLUE | GREEN | RED;

    fun print_color( color ) = {
        case color
            RED   => print "Red\n";
            GREEN => print "Green\n";
            BLUE  => print "Blue\n";
        esac;
    };

    a = { x => 1.0, y => 1.0, diameter => 0.5, color => RED  };
    b = { x => 1.0, y => 2.0, diameter => 0.7, color => BLUE };

    print_color(a.color);
    print_color(b.color);

The a and b records above might represent circles to be drawn upon the screen in a graphics application, say.

One nice aspect of code such as the above is that if Color is redefined to include one more (or less) color, the compiler will automatically flag all case statements on Color which have not been modified appropriately to reflect the new definition. This can be an enormous help when maintaining large complex programs. (Relative to doing the same thing in C, say.)

However, the real power of such declarations only begins to become apparent when the named constants are decorated with data values:

    #!/usr/bin/mythryl

    Tree = LEAF(Int)
         | NODE { key: Int, left_kid: Tree, right_kid: Tree };

    fun print_tree( t ) = {

        case t

            LEAF i => printf "leaf: %d\n" i;

            NODE { key, left_kid, right_kid }
                =>
                {   print_tree(left_kid);
                    printf "key: %d\n" key;
                    print_tree(right_kid);
                };

        esac;
    };

    my_tree = NODE { key => 2, left_kid => LEAF 1, right_kid => LEAF 3 };

    print_tree(my_tree);

Running the above will produce:

    linux$ ./my-script
    leaf: 1
    key: 2
    leaf: 3

Now, there’s a lot going on there! If you translated the above into C or Java, you would probably have several pages of code. Let’s break it down:

    Tree = LEAF(Int)
         | NODE { key: Int, left_kid: Tree, right_kid: Tree };

This two-liner defines a complete binary tree data type.

The first line says that leaf nodes carry a single integer value.

The second line says that internal nodes carry a record containing an integer key and pointers to two subtrees. (Mythryl does not distinguish between record values and pointers to records the way C does. Think of it as always implementing the pointer case.)

Since the definition of the Tree type refers to itself (the record fields left_kid and right_kid are both of type Tree), it defines a recursive datastructure, instances of which may be arbitrarily large.

Now consider the function definition:

    fun print_tree( t ) = {

        case t

            LEAF i => printf "leaf: %d\n" i;

            NODE { key, left_kid, right_kid }
                =>
                {   print_tree(left_kid);
                    printf "key: %d\n" key;
                    print_tree(right_kid);
                };
        esac;
    };

The case expressions LEAF i and NODE { key, left_kid, right_kid } are again examples of pattern-matching assignments.

This recursive routine takes a single argument.

If that argument is a LEAF, it simply prints out the integer value associated with the leaf, pattern-matched out of the left hand side of the rule.

If that argument is an internal binary tree NODE, it does an in-order traversal, recursively printing out its left subtree, then printing out its key, then again recursively printing out its right subtree.

Finally, consider the statement

    my_tree = NODE { key => 2, left_kid => LEAF 1, right_kid => LEAF 3 };

This statement constructs a complete (little) binary tree consisting of two leaf nodes and one internal node. (Don’t try doing this in one line in C!)

In this context the constants NODE and LEAF are called constructors, for the simple and sufficient reason that when they are applied as functions they construct values of the indicated type.

Having these constructors implicitly generated by the Mythryl compiler in response to the Tree type declaration is one of the things that makes Mythryl code so economical.

As the final major twist to this story, Mythryl allows us to define mutually recursive datastructures. Suppose, for example, we are building a mud — an online interactive text game in which players wander through rooms connected by doors. Each room may have multiple doors, and each door connects the current room to another room:

    #!/usr/bin/mythryl

    Room = ROOM { name: String, description: String, doors: List(Door) }
    also
    Door = DOOR { name: String, description: String, to: Room };

    fun print_room( ROOM { name, description, doors } ) = {
        printf "%s room: You see %s\n" name description;
        apply print_door doors; 
    }
    also
    fun print_door( DOOR { name, description, to } ) = {
        printf "%s door: You see %s\n" name description;
        print_room to;
    };

    level = ROOM { name => "main",
                   description => "a big entryroom.", 
                   doors => [  DOOR { name => "kitchen.",
                                      description => "a white door.",
                                      to => ROOM { name => "kitchen", 
                                                   description => "a tidy kitchen.",
                                                   doors => []
                                                 }
                                    }
                            ] 
                   };

    print_room level;

The Room and Door types are mutually recursive. Naturally, this means that to process the resulting data structure we need mutually recursive functions, in this case print_room and print_door.

In both cases we use the also reserved word to notify the compiler of the mutual recursion.

Notice in each case the absence of a semicolon preceding the also. Mythryl ends complete statements with a semicolon, and only complete statements.

Here we also see for the first time that function call parameter lists do pattern-matching. We use this facility in both the print_door and print_room functions to efficiently unpack the values we need from the relevant record structures.

When run, the above script produces

    linux$ ./my-script
    main room: You see a big entryroom.
    kitchen door: You see a white door.
    kitchen room: You see a tidy kitchen.

That’s not much as muds go, but that’s a lot accomplished in half a page of code! If you coded up the same thing in C you might have five or ten pages of code before you were done.


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext