Concept
I am implementing an interpreter that allows a user to define arbitrary combinators and apply them to arbitrary terms. For example, a user may define the Church encoding for pairs by inputting the following combinator definitions:
pair a b c → c a b
true a b → a
first a → a true
The user may then input first (pair a b), which is reduced step-by-step according to the previously defined rules:
first (pair a b)
→ pair a b true
→ true a b
→ a
Other combinators may also be defined, such as those used in the SKI combinator calculus:
S x y z → x z (y z)
K x y → x
I x → x
The identity combinator could also be defined in terms of the first two combinators by I → S S K K or I → S K (K K) or I = S K x. The universal iota combinator could then be defined by:
ι x → x S K
These examples hopefully illustrate what I am trying to do.
Implementation
I am attempting to implement this using graph reduction and a graph rewriting system. Let tree be a data type defined recursively by
tree = leaf | (tree tree)
This is a binary tree, where nodes may be either leafs (terminal nodes) or branches (internal nodes) consisting of a pair of subtrees. The branches represent the application of a term to another term, while leaves represent combinators and arguments. Let rule be a data type defined by
rule = (tree tree)
This corresponds to a reduction rule that transforms the left tree into the right tree (a → b). The list of rules may then be defined by
rules = rule | (rule rules)
Effectively, when evaluating an expression such as pair a b c → c a b, the interpreter constructs a tree of the form (((pair a) b) c) corresponding to the left hand side, a tree of the form ((c a) b) corresponding to the right hand side, constructs a pair of both trees corresponding to a rule (where a,b,c are somehow specified to be arbitrary parameters and not necessarily combinators or terminal symbols), and appends this pair to the list rules. When reducing an expression of the form first (pair a b), the interpreter constructs the corresponding tree (first ((pair a) b)) and applies the reduction rules as follows:
(first ((pair a) b))
→ (((pair a) b) true)
→ ((true a) b)
→ a
To do this, the interpreter must perform pattern matching on the tree and its subtrees, "moving around" the combinators and arbitrary parameters to construct a new tree corresponding to the right hand side of the rule. An example implementation of the tree structure in C is given by
struct tree_t {
bool is_leaf;
union {
char* symbol;
struct {
tree_t* left;
tree_t* right;
};
};
};
A pattern matching function could be implemented as
bool matches(tree_t* pattern, tree_t* replacement) {
if (pattern -> is_leaf && replacement -> is_leaf)
//do stuff, return a boolean
else if (pattern -> is_leaf && replacement -> is_branch)
//do stuff, return a boolean
else if (pattern -> is_branch && replacement -> is_leaf)
//do stuff, return a boolean
else if (pattern -> is_branch && replacement -> is_branch)
return matches(pattern -> left, replacement -> left) && matches(pattern -> right, replacement -> right);
//The above tests for equality recursively by testing for equality in each subtree.
}
However, I am unsure of how to implement important details of this process, including:
Matching an input tree with the LHS tree of a reduction rule.
Transforming the input tree into the RHS tree of the reduction rule, preserving parameters (which may be leaves or branches) and "moving them around" around to their appropriate places.
I believe pattern-matching on a node would involve examining the left child and right child of the node and so on, until terminal nodes are reached. Does anyone know of a program or tutorial online that has implemented a similar concept in C and that I could learn from? Am I even on the right track in approaching the problem through this method, or is there a simpler way?
You need to take it in two separate steps. A pattern matcher matches a pattern against a tree, and builds a dictionary mapping variables in the pattern to values in the tree.
Then you pass that dictionary to a separate function that fills in the replacement, by replacing variables with their values from the dictionary.
The pattern matching approach described in SICP will work just fine in C, though you may find it easier to use a mutable data structure for the dictionary. See https://mitpress.mit.edu/sicp/full-text/sicp/book/node99.html
Related
During two weeks I've been doing some simple programs in OCaml. I've noticed that when we are working with a recursive structure T and we want to have the information I on T then depending on the information I we have two types of recursive function.
For simplicity let's assume T is a binary tree. So I'll use the following type :
type 'a tree = Empty | 'a * 'a tree * 'a tree
Now let's say the information I can be calculated from left to right on the binary tree. When I am saying left to right it means that the information I can be calculated from the root to the leaves without getting backward.
To be more clear let's say the information I we want to have is simply "the number of nodes of the binary tree". Then what's nice with this information is that when we get to all leaves then we get I, so we are going left to right in the sense that we begin from the root and expend recursively to the left and right subtree and the end case is when we arrived at the leaves.
So we simply have :
let rec nodes = function
|Empty -> 0 (*it's ok we are done here*)
|Node(_,l,r) -> 1 + nodes l + nodes r
What's very nice is that when the information can be calculated left to right then OCaml's pattern matching is a very strong tool and the information I can be calculated in an easy way.
So more generally we have :
let rec get_information = function
| Empty -> (*here we are done so we return a constant value*)
|Node(_,l,r)-> (*here we apply recusrively the function to the left and right tree*)
Now here comes my problem. Let's say I is an information that can't be calculated from left to right but from right to left. So it means that to get the information I we need to begin from the leaves of the tree and extend recursively to the top and we are done only when we get to the root of the binary tree (so the end case is when we get to the root of the binary tree and not the leaves).
For example, let's say the information I is : "the binary tree has the propriety that for every node the number of nodes in his left subtree is strictly superior to the number of nodes in his right subtree". If we want to solve this in linear time then we need to begin from the leaves and expend recursively to the top (note that I don't necessarily want a solution to the problem).
So to me, it's tricky to write a function that gets the information I when I is a right to left information (it needs to begin from the leaves and extend to the top). On the contrary pattern-matching is perfect when the information is a left to right information.
So my question is how to do when we need to write a function that gets the information I (when I is right to left)? Are there techniques to solve these kind of problems? Is it still possible to use pattern matching in a tricky way in order to get the desired result?
Pattern matching is useful for writing both kinds of function. Higher order functions called folds can also be used.
First, a concrete version. We will want to know whether a tree is left leaning, and if so, how many nodes it has. An int option will represent this nicely, with None indicating any non-left leaning tree.
type 'a tree = Empty | Branch of 'a * 'a tree * 'a tree
let rec tree_info = function
| Empty -> Some 0
| Branch (_, l, r) ->
match tree_info l, tree_info r with
| Some x, Some y when x >= y -> Some (x + y + 1)
| _ -> None
let is_left_leaning tree =
match tree_info tree with
| Some _ -> true
| None -> false
(Note that the condition x >= y is not 'strictly greater than', but this is deliberate; x > y is a poor choice. I'll leave figuring out why as an exercise.)
We can also express this style of function in terms of an operation called a right fold. For this operation one provides a value for each constructor of the datatype being folded over: in each place that constructor occurs, the fold operation will use that value to compute the result of the fold:
let rec foldr empty branch = function
| Empty -> empty
| Branch (x, l, r) ->
branch x (foldr empty branch l) (foldr empty branch r)
Note that the empty value and the Empty constructor have the same arity, and the branch value and the Branch constructor have the same arity, with corresponding argument types. That's characteristic of a right fold.
Given foldr, we can easily define map:
let map f tree =
foldr Empty (fun x l r -> Branch (f x, l, r)) tree
Or of course, 'tree_info':
let tree_info tree =
foldr
(Some 0)
(fun _ l r ->
match l, r with
| Some x, Some y when x >= y -> Some (x + y + 1)
| _ -> None)
tree
This is the alternative to pattern matching on the constructors of tree.
Prefix Notation conversion into a tree is usually done like this:
Create a binary tree from an algebraic expression
However, I need to support so called chainable operations that have more than two operands. If that operation is splittable i.e
(+ a b c) = (+(+ a b) c)
there is no problem.
+
/ \
+ c
/ \
a b
However if an operator is not splittable this does not work. One example is the pairwise distinct operator.
(distinct a b c) != (distinct (distinct a b) c)
the left side implies a != b, a != c and b != c, while the right side implies only a != b and b != c. Trying to build an n-ary tree would probably lead to not being able to traverse the tree very well:
distinct
/ | \
a b c
Does somebody have experience with this kind of problem and has an idea on how to solve the issue?
The c# System.Linq.Expressions namespace solves it by having a big range of node types, and a base class visitor, where you can override the visit method of each node type, by default just traversing the whole tree. For example there is a node type for calling a method, where the method definition, the object, and all arguments are all children of the MethodCallExpression node, and the return value is what the node represents. You can see it is not a binary tree, not even anything regular.
I'm having trouble understanding the letrec definition for HM system that is given on Wikipedia, here: https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system#Recursive_definitions
For me, the rule translates roughly to the following algorithm:
infer types on everything in the letrec definition part
assign temporary type variables to each defined identifier
recursively process all definitions with temporary types
in pairs, unify the results with the original temporary variables
close (with forall) the inferred types, add them to the basis (context) and infer types of the expression part with it
I'm having trouble with a program like this:
letrec
p = (+) --has type Uint -> Uint -> Uint
x = (letrec
test = p 5 5
in test)
in x
The behavior I'm observing is as follows:
definition of p gets temporary type a
definition of x gets some temporary type too, but that's out of our scope now
in x, definition of test gets a temporary type t
p gets the temporary type a from the scope, using the HM rule for a variable
(f 5) gets processed by HM rule for application, resulting type is b (and the unification that (a unifies with Uint -> b)
((p 5) 5) gets processed by the same rule, resulting in more unifications and type c, a now in result unifies with Uint -> Uint -> c
now, test gets closed to type forall c.c
variable test of in test gets the type instance (or forall c.c) with fresh variables, accrodingly to the HM rule for variable, resulting in test :: d (that is unified with test::t right on)
resulting x has effectively type d (or t, depending on the mood of unification)
The problem: x should obviously have type Uint, but I see no way those two could ever unify to produce the type. There is a loss of information when the type of test gets closed and instance'd again that I'm not sure how to overcome or connect with substitutions/unifications.
Any idea how the algorithm should be corrected to produce x::Uint typing correctly? Or is this a property of HM system and it simply will not type such case (which I doubt)?
Note that this would be perfectly OK with standard let, but I didn't want to obfuscate the example with recursive definitions that can't be handled by let.
Thanks in advance
Answering my own question:
The definition on the Wiki is wrong, although it works to certain extent at least for type checking.
Most simple and correct way to add recursion to HM system is to use fix predicate, with definition fix f = f (fix f) and type forall a. (a->a) -> a. Mutual recursion is handled by double fixpoint, etc.
Haskell approach to the problem (described at https://gist.github.com/chrisdone/0075a16b32bfd4f62b7b#binding-groups ) is (roughly) to derive an incomplete type for all functions and then run the derivation again to check them against each other.
I asked a series of question to get to the point I can define the following simple model in Isabelle, But I still stuck in getting what I wanted. I try to very briefly describe the problem with an example:
Example:
Suppose I have two classes Person and Car, Person owns cars and also drives cars. So I need the following types/sets:
Person
Car
owns (* owns relates elements of Person to Car *)
drives (* drives relate elements of Person to car as well *)
Problem:
I would like to formulate above example in Isabelle is a way that provides the following flexibilities:
Enable me to define some constraint; for example: if a Person owns a car, he/she definitely drives the car. I can do this by using a kind answer from here.
Enable me to define a new set/type namely C whose elements are disjoint union of elements of Car and owns. This is the first place I stuck: Car and owns are different types, so how I can union them?
Be able to continue the process numerous time in number (2); for example, define a new type/set namely D which is disjoint union of C and drives.
In number (2) and (3), I would like to keep the properties/characteristics of the elements of newly defined sets/types; for example, if I would have defined a property age for a Person (see here), I would like the elements of C retain this property in the sense that I can access this property for the elements in C whose type are Person. Consequently, if o1 is an element in C whose type is owns, I would like to access the source (i.e., the Person) and the target (the Car) that are related by o1.
I would appreciate any comments and suggestions.
There is the sum-type, written 'a + 'b, in Isabelle/HOL that allows you to combine elements of two different types into a new one. The two constructors of the sum type are
Inl :: 'a => 'a + 'b
for inject left and
Inr :: 'b => 'a + 'b
for inject right. Using the sum-type you could for example combine lists of numbers nat list with plain numbers nat to obtain (nat list) + nat. Since lists provide a function length :: 'a list => nat, you can still use this function on elements of the disjoint sum for which you know that they are lists. In order to obtain this information (i.e., whether the element you look at is a list or a plain number) we typically use pattern-matching.
The following function would compute the length of the list if the current element is a list and just return the number it represents, otherwise.
fun maybe_length :: "(nat list) + nat => nat"
where
"maybe_length (Inl xs) = length xs" |
"maybe_length (Inr n) = n"
The typical academic example is to sum a list.
Are there real world examples of the use of fold that will shed light on its utility ?
fold is perhaps the most fundamental operation on sequences. Asking for its utility is like asking for the utility of a for loop in an imperative language.
Given a list (or array, or tree, or ..), a starting value, and a function, the fold operator reduces the list to a single result. It is also the natural catamorphism (destructor) for lists.
Any operations that take a list as input, and produce an output after inspecting the elements of the list can be encoded as folds. E.g.
sum = fold (+) 0
length = fold (λx n → 1 + n) 0
reverse = fold (λx xs → xs ++ [x]) []
map f = fold (λx ys → f x : ys) []
filter p = fold (λx xs → if p x then x : xs else xs) []
The fold operator is not specific to lists, but can be generalised in a uniform way to ‘regular’ datatypes.
So, as one of the most fundamental operations on a wide variety of data types, it certainly does have some use out there. Being able to recognize when an algorithm can be described as a fold is a useful skill that will lead to cleaner code.
References:
A tutorial on the universality and expressiveness of fold
Writing foldl in terms of foldr
On folds
Lots And Lots Of foldLeft Examples lists the following functions:
sum
product
count
average
last
penultimate
contains
get
to string
reverse
unique
to set
double
insertion sort
pivot (part of quicksort)
encode (count consecutive elements)
decode (generate consecutive elements)
group (into sublists of even sizes)
My lame answer is that:
foldr is for reducing the problem to the primitive case and then assembling back up (behaves as a non tail-recursion)
foldl is for reducing the problem and assembling the solution at every step, where at the primitive case you have the solution ready (bahaves as a tail recursion / iteration)
This question reminded me immediately of a talk by Ralf Lämmel Going Bananas (as the rfold operator notation looks like a banana (| and |)). There are quite illustrative examples of mapping recursion to folds and even one fold to the other.
The classic paper (that is quite difficult at first) is Functional Programming with Bananas, Lenses,. Envelopes and Barbed Wire named after the look of other operators.