Skip to content

GitLab

  • Menu
Projects Groups Snippets
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in
  • CS-210 Functional programming 2021 CS-210 Functional programming 2021
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 9
    • Issues 9
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 1
    • Merge requests 1
  • Deployments
    • Deployments
    • Releases
  • Monitor
    • Monitor
    • Metrics
    • Incidents
  • Packages & Registries
    • Packages & Registries
    • Package Registry
    • Infrastructure Registry
  • Analytics
    • Analytics
    • Value stream
    • Repository
  • Activity
  • Graph
  • Create a new issue
  • Commits
  • Issue Boards
Collapse sidebar
  • lamp
  • CS-210 Functional programming 2021CS-210 Functional programming 2021
  • Issues
  • #104

Closed
Open
Created Dec 21, 2021 by Matt Bovel@bovelOwner

Church encodings in Scala

Follow-up of https://discord.com/channels/885109005469511700/885109005469511704/922863225689759804.

Representing Church encodings in Scala is not trivial, and we didn't want to show something like that because we did not want to confuse you.

But as some of you tried, here are some examples of how this could be done following what was posted on Discord:

type ChurchNum[T] = (T => T) => T => T            // By Fran.

def zero [T]: ChurchNum[T] = f => z => z          // By Fran.
def one  [T]: ChurchNum[T] = f => z => f(z)       // By Fran.
def two  [T]: ChurchNum[T] = f => z => f(f(z))    // By Fran.
def three[T]: ChurchNum[T] = f => z => f(f(f(z))) // By Fran.

def toInt[T](i: ChurchNum[Int]): Int = i(_ + 1)(0)

assert(toInt(three) == 3)

def succ[T]: ChurchNum[T] => ChurchNum[T] = (n: ChurchNum[T]) => f => z => f(n(f)(z))

// Trick to make this version work: set type of `n` to `ChurchNum[ChurchNum[T]]`
def add[T]: ChurchNum[ChurchNum[T]] => ChurchNum[T] => ChurchNum[T] = n => m => n(succ)(m)

def add2[T]: ChurchNum[T] => ChurchNum[T] => ChurchNum[T] = a => b => f => z => a(f)(b(f)(z)) // By David Kalajdzic

assert(toInt(add(zero)(one)) == 1)
assert(toInt(add(two)(three)) == 5)

assert(toInt(add2(zero)(one)) == 1)
assert(toInt(add2(two)(three)) == 5)

However, the trick used for add is limited. With these typings we can only refer to a specific instance of the type ChurchNum at a time; typing n as ChurchNum[ChurchNum[T]] excludes using it as ChurchNum[T] for example.

To encode Church numerals more faithfully, we could use Scala 3's polymorphic function types: https://docs.scala-lang.org/scala3/reference/new-types/polymorphic-function-types.html. Note that this is advanced material, outside of the scope of this course.

Polymorphic function types allow us to encode the fact that a Church numeral is a function that can be instantiated with any type (a polymorphic function in due form):

type ChurchNum = [T] => (T => T) => T => T

val zero: ChurchNum = [T] => (f: T => T) => (z: T) => z
val one: ChurchNum = [T] => (f: T => T) => (z: T) => f(z)
val two: ChurchNum = [T] => (f: T => T) => (z: T) => f(f(z))
val three: ChurchNum = [T] => (f: T => T) => (z: T) => f(f(f(z)))

def toInt(i: ChurchNum): Int = i[Int](_ + 1)(0)

assert(toInt(three) == 3)

val succ: ChurchNum => ChurchNum = (n: ChurchNum) => [T] => (f: T => T) => (z: T) => f(n(f)(z))
val add: ChurchNum => ChurchNum => ChurchNum = n => m => n(succ)(m)
val add2: ChurchNum => ChurchNum => ChurchNum = a => b => [T] => (f: T => T) => (z: T) => a(f)(b(f)(z))

assert(toInt(add(zero)(one)) == 1)
assert(toInt(add(two)(three)) == 5)

assert(toInt(add2(zero)(one)) == 1)
assert(toInt(add2(two)(three)) == 5)

We could represent Church Booleans similarly:

type ChurchBool = [T] => T => T => T

val tru: ChurchBool = [T] => (x: T) => (y: T) => x
val fls: ChurchBool = [T] => (x: T) => (y: T) => y
val not2 /*: ChurchBool => ChurchBool*/ = (a: ChurchBool) => [T] => (x: T) => (y: T) => a(y)(x)
val and: ChurchBool => ChurchBool => ChurchBool = (a: ChurchBool) => (b: ChurchBool) => a(b)(fls)
val nand: ChurchBool => ChurchBool => ChurchBool = (a: ChurchBool) => (b: ChurchBool) => a(not2(b))(not2(a))
val nand2: ChurchBool => ChurchBool => ChurchBool = (a: ChurchBool) => (b: ChurchBool) => a(not2(b))(tru)

def toBool(b: ChurchBool): Boolean = b(true)(false)

assert(toBool(not2(nand2(tru)(tru))) == true)

This is not prefect, as inference does not always work as expected (or at least I could not make it work, see the not2 case for example).

Also, we can not represent church lists as we'd need a recursive type definition which is not supported yet:

type ChurchList = [A, L] => (n: L) => (c: A => ChurchList => L) => L
// illegal cyclic type reference

Anyway, if these thoughts can help some of you to better understand Church encodings, good. Otherwise, if this yields to more confusion, just forget it 😉

Edited Dec 21, 2021 by Matt Bovel
Assignee
Assign to
Time tracking