I set myself the following additional goal (devised by me):
I chose to learn Scala for this course because of its expressive type system and functional features. As such, I decided to write a program which uses Scala's type system to consistently type the Y Combinator and then uses the result to implement a function that calculates Pascal's triangle without recursion but using several of Scala's functional features. I have placed the type-level logic in a comment.
// Here begins file main.scala
object Combinator {
/*
The overall type of the Y Combinator below is ((A -> B) -> (A -> B)) -> (A -> B). The challenge is to consistently type its subexpressions.
λf.(λx.f(x x))(λx.f(x x))
By looking at the definition, we can deduce the following about the types of some of the elements:
f: (A -> B) -> (A -> B)
x: X
&&
x: X -> (A -> B)
In some type systems, we can define a type Rec s.t. Rec == Rec -> (A -> B), a recursive type. In Scala, we can approximate this using the following:
case class Rec(r: Rec => (A => B)) {
def apply(x: Rec) = r(x)
}
The constructor converts Rec -> (A -> B) to just Rec but said Rec can still act like Rec -> (A -> B) via the apply method.
The resulting types of the subexpressions are then as follows:
x: Rec
x x: A -> B
f(x x): A -> B
(λx.f(x x)): Rec -> (A -> B)
Rec((λx.f(x x))): Rec
(λx.f(x x))Rec((λx.f(x x))): A -> B
λf.(λx.f(x x))(λx.f(x x)): ((A -> B) -> (A -> B)) -> (A -> B)
This gives the result we wanted.
*/
def Y[A,B](f: (A => B) => (A => B)) = {
case class Rec(r: Rec => (A => B)) {
def apply(x: Rec) = r(x)
}
((x: Rec) => f(x(x))(_))(Rec(x => f(x(x))(_)))
}
}
object Main extends App {
def pascal = Combinator.Y {
f: (Int => Array[Int]) =>
n: Int =>
if(n == 1) Array(1)
else {
val prev = f(n-1)
1 +: (prev zip prev.drop(1)).map{ case(a,b) => a + b } :+ 1
}
}
val depth = 15
val spacing = 6
for(y <- 1 to depth) {
print(" "*(spacing*(depth - y)/2))
for(x <- pascal(y)) print(x + " "*(spacing - x.toString().length))
println()
}
}