I am implementing an impure untyped lambda-calculus interpreter in Haskell.
I'm presently stuck on implementing "alpha-congruence" (also called "alpha-equivalence" or "alpha-equality" in some textbooks). I want to be able to check whether two lambda-expressions are equal or not equal to each other. For example, if I enter the following expression into the interpreter it should yield True (\ is used to indicate the lambda symbol):
>\x.x == \y.y
True
The problem is understanding whether the following lambda-expressions are considered alpha-equivalent or not:
>\x.xy == \y.yx
???
>\x.yxy == \z.wzw
???
In the case of \x.xy == \y.yx I would guess that the answer is True. This is because \x.xy => \z.zy and \y.yx => \z.zy and the right-hand sides of both are equal (where the symbol => is used to denote alpha-reduction).
In the cae of \x.yxy == \z.wzw I would likewise guess that the answer is True. This is because \x.yxy => \a.yay and \z.wzw => \a.waw which (I think) are equal.
The trouble is that all of my textbooks' definitions state that only the names of the bound variables need to be changed for two lambda-expressions to be considered equal. It says nothing about the free variables in an expression needing to be renamed uniformly also. So even though y and w are both in their correct places in the lambda-expressions, how would the program "know" that the first y represents the first w and the second y represents the second w. I would need to be consistent about this in an implementation.
In short, how would I go about implementing an error-free version of a function isAlphaCongruent? What are the exact rules that I need to follow in order for this to work?
I would prefer to do this without using de Bruijn indices.
You are misunderstanding: different free variables are not alpha equivalent. So y /= x, and \w.wy /= \w.wx, and \x.xy /= \y.yx. Similarly, \x.yxy /= \z.wzw because y /= w.
Your book says nothing about free variables being allowed to be uniformly renamed because they are not allowed to be uniformly renamed.
(Think of it this way: if I haven't yet told you the definition of not and id, would you expect \x. not x and \x. id x to be equivalent? I sure hope not!)
Related
I'm going through the book "Haskell programming from first principles" and although the authors try to explain in detail how to reduce expressions, I still have trouble understanding how to read some expressions in the examples provided.
Let's assume the below expression:
(λx.x) Then if I understand things correctly this can be read as: "A lambda function, that accepts an argument x and returns (basically translating . as "returns") the same value x.
Now supposing that that we have the below expression:
(λxy.xy), how do we read that expression in plain English? To make it even more complex the authors then expand (λxy.xy) to (λxy.xy)(λz.a) 1. How are these z and a appearing to the above expression? I was hoping that if I can properly transform the expression to plain English I'd be able to understand where z and a come from.
In lambda calculus, values that are next to each other imply that the first value is being called as a function with the second value as an argument. (λxy.xy) is shorthand for (λx.λy.xy), which means a function that takes an argument x then an argument y, and calls x with argument y. (if you're wondering why there's a function returning a function, it's due to currying). To figure out what (λxy.xy)(λz.a) 1 means, we have to take it step by step:
Undo the shorthand: (λx. λy. xy) (λz. a) 1
Apply first function (λx): (λy. (λz. a) y) 1
Apply second function (λy): (λz. a) 1
Apply third function (λz): a
So, we end up with whatever a is (probably an externally defined constant).
I'm studying project euler solutions and this is the solution of problem 4, which asks to
Find the largest palindrome made from the product of two 3-digit
numbers
problem_4 =
maximum [x | y<-[100..999], z<-[y..999], let x=y*z, let s=show x, s==reverse s]
I understand that this code creates a list such that x is a product of all possible z and y.
However I'm having a problem understanding what does s do here. Looks like everything after | is going to be executed everytime a new element from this list is needed, right?
I don't think I understand what's happening here. Shouldn't everything to the right of | be constraints?
A list comprehension is a rather thin wrapper around a do expression:
problem_4 = maximum $ do
y <- [100..999]
z <- [y..999]
let x = y*z
let s = show x
guard $ s == reverse s
return x
Most pieces translate directly; pieces that aren't iterators (<-) or let expressions are treated as arguments to the guard function found in Control.Monad. The effect of guard is to short-circuit the evaluation; for the list monad, this means not executing return x for the particular value of x that led to the false argument.
I don't think I understand what's happening here. Shouldn't everything to the right of | be constraints?
No, at the right part you see an expression that is a comma-separated (,) list of "parts", and every part is one of the following tree:
an "generator" of the form somevar <- somelist;
a let statement which is an expression that can be used to for instance introduce a variable that stores a subresult; and
expressions of the type boolean that act like a filter.
So it is not some sort of "constraint programming" where one simply can list some constraints and hope that Haskell figures it out (in fact personally that is the difference between a "programming language" and a "specification language": in a programming language you have "control" how the data flows, in a specification language, that is handled by a system that reads your specifications)
Basically an iterator can be compared to a "foreach" loop in many imperative programming languages. A "let" statement can be seen as introducing a temprary variable (but note that in Haskell you do not assign variable, you declare them, so you can not reassign values). The filter can be seen as an if statement.
So the list comprehension would be equivalent to something in Python like:
for y in range(100, 1000):
for z in range(y, 1000):
x = y * z
s = str(x)
if x == x[::-1]:
yield x
We thus first iterate over two ranges in a nested way, then we declare x to be the multiplication of y and z, with let s = show x, we basically convert a number (for example 15129) to its string counterpart (for example "15129"). Finally we use s == reverse s to reverse the string and check if it is equal to the original string.
Note that there are more efficient ways to test Palindromes, especially for multiplications of two numbers.
From wiki.haskell.org:
First of all, common subexpression elimination (CSE) means that if an expression appears in several places, the code is rearranged so that the value of that expression is computed only once. For example:
foo x = (bar x) * (bar x)
might be transformed into
foo x = let x' = bar x in x' * x'
thus, the bar function is only called once. (And if bar is a particularly expensive function, this might save quite a lot of work.)
GHC doesn't actually perform CSE as often as you might expect. The trouble is, performing CSE can affect the strictness/laziness of the program. So GHC does do CSE, but only in specific circumstances --- see the GHC manual. (Section??)
Long story short: "If you care about CSE, do it by hand."
I'm wondering under what circumstances CSE "affects" the strictness/laziness of the program and what kind of effect that could be.
The naive CSE rule would be
e'[e, e] ~> let x = e in e'[x, x].
That is, whenever a subexpression e occurs twice in the expression e', we use a let-binding to compute e once. This however leads itself to some trivial space leaks. For example
sum [1..n] + prod [1..n]
is typically O(1) space usage in a lazy functional programming language like Haskell (as sum and prod would tail-recurse and blah blah blah), but would become O(n) when the naive CSE rule is enacted. This can be terrible for programs when n is high!
The approach is then to make this rule more specific, restricting it to a small set of cases that we know won't have the problem. We can begin by more specifically enumerating the problems with the naive rule, which will form a set of priorities for us to develop a better CSE:
The two occurrences of e might be far apart in e', leading to a long lifetime for the let x = e binding.
The let-binding must always allocate a closure where previously there might not have been one.
This can create an unbound number of closures.
There are cases where the closure might never deallocate.
Something better
let x = e in e'[e] ~> let x = e in e'[x]
This is a more conservative rule but is much safer. Here we recognize that e appears twice but the first occurrence syntactically dominates the second expression, meaning here that the programmer has already introduced a let-binding. We can safely just reuse that let-binding and replace the second occurrence of e with x. No new closures are allocated.
Another example of syntactic domination:
case e of { x -> e'[e] } ~> case e of { x -> e'[x] }
And yet another:
case e of {
Constructor x0 x1 ... xn ->
e'[e]
}
~>
case e of {
Constructor x0 x1 ... xn ->
e'[Constructor x0 x1 ... xn]
}
These rules all take advantage of existing structure in the program to ensure that the kinetics of space usage remain the same before and after the transformation. They are much more conservative than the original CSE but they are also much safer.
See also
For a full discussion of CSE in a lazy FPL, read Chitil's (very accessible) 1997 paper. For a full treatment of how CSE works in a production compiler, see GHC's CSE.hs module, which is documented very thoroughly thanks to GHC's tradition of writing long footnotes. The comment-to-code ratio in that module is off the charts. Also note how old that file is (1993)!
I noticed today that such a definition
safeDivide x 0 = x
safeDivide = (/)
is not possible. I am just curious what the (good) reason behind this is. There must be a very good one (it's Haskell after all :)).
Note: I am not looking suggestions for alternative implementations to the code above, it's a simple example to demonstrate my point.
I think it's mainly for consistency so that all clauses can be read in the same manner, so to speak; i.e. every RHS is at the same position in the type of the function. I think would mask quite a few silly errors if you allowed this, too.
There's also a slight semantic quirk: say the compiler padded out such clauses to have the same number of patterns as the other clauses; i.e. your example would become
safeDivide x 0 = x
safeDivide x y = (/) x y
Now consider if the second line had instead been safeDivide = undefined; in the absence of the previous clause, safeDivide would be ⊥, but thanks to the eta-expansion performed here, it's \x y -> if y == 0 then x else ⊥ — so safeDivide = undefined does not actually define safeDivide to be ⊥! This seems confusing enough to justify banning such clauses, IMO.
The meaning of a function with multiple clauses is defined by the Haskell standard (section 4.4.3.1) via translation to a lambda and case statement:
fn pat1a pat1b = r1
fn pat2a pat2b = r2
becomes
fn = \a b -> case (a,b) of
(pat1a, pat1b) -> r1
(pat2a, pat2b) -> r2
This is so that the function definition/case statement way of doing things is nice and consistent, and the meaning of each isn't specified redundantly and confusingly.
This translation only really makes sense when each clause has the same number of arguments. Of course, there could be extra rules to fix that, but they'd complicate the translation for little gain, since you probably wouldn't want to define things like that anyway, for your readers' sake.
Haskell does it this way because it's predecessors (like LML and Miranda) did. There is no technical reason it has to be like this; equations with fewer arguments could be eta expanded. But having a different number of arguments for different equations is probably a typo rather than intentional, so in this case we ban something sensible&rare to get better error reporting in the common case.
I noticed today that such a definition
safeDivide x 0 = x
safeDivide = (/)
is not possible. I am just curious what the (good) reason behind this is. There must be a very good one (it's Haskell after all :)).
Note: I am not looking suggestions for alternative implementations to the code above, it's a simple example to demonstrate my point.
I think it's mainly for consistency so that all clauses can be read in the same manner, so to speak; i.e. every RHS is at the same position in the type of the function. I think would mask quite a few silly errors if you allowed this, too.
There's also a slight semantic quirk: say the compiler padded out such clauses to have the same number of patterns as the other clauses; i.e. your example would become
safeDivide x 0 = x
safeDivide x y = (/) x y
Now consider if the second line had instead been safeDivide = undefined; in the absence of the previous clause, safeDivide would be ⊥, but thanks to the eta-expansion performed here, it's \x y -> if y == 0 then x else ⊥ — so safeDivide = undefined does not actually define safeDivide to be ⊥! This seems confusing enough to justify banning such clauses, IMO.
The meaning of a function with multiple clauses is defined by the Haskell standard (section 4.4.3.1) via translation to a lambda and case statement:
fn pat1a pat1b = r1
fn pat2a pat2b = r2
becomes
fn = \a b -> case (a,b) of
(pat1a, pat1b) -> r1
(pat2a, pat2b) -> r2
This is so that the function definition/case statement way of doing things is nice and consistent, and the meaning of each isn't specified redundantly and confusingly.
This translation only really makes sense when each clause has the same number of arguments. Of course, there could be extra rules to fix that, but they'd complicate the translation for little gain, since you probably wouldn't want to define things like that anyway, for your readers' sake.
Haskell does it this way because it's predecessors (like LML and Miranda) did. There is no technical reason it has to be like this; equations with fewer arguments could be eta expanded. But having a different number of arguments for different equations is probably a typo rather than intentional, so in this case we ban something sensible&rare to get better error reporting in the common case.