Добрый день!
Столь претензионным заголовком я хочу начать статью про одну из многих моделей исчисления (Compitational model) — рекурсивные функции. В первой части этого поста мы разберем (в кратце, ибо подробно все расписано на Википедии) теоретическую составляющую этой модели (примитивная рекурсия), во второй же половине мы попробуем претворить данную модель в жизнь (частично) с помощью языка Scala.
1. Рекурсивные функции — что это?
Всем известно словосочетание «Машина Тьюринга» — модель исчисления, созданная британским математиком, логиком, криптографом и просто хорошим человеком Аланом Тьюрингом. Данная модель является одной из самых известных и популярных моделей исчисления среди многих. Однако, с недавнего времени начали популяризироваться другие модели, такие как лябмда-исчисления и мю-рекурсия.
И всё же, что такое рекурсивная функция?
Представте себе, что перед вами лежит абстрактная вычеслительная машина, которая не знает (пока еще) таких операций как сложение двух чисел, вычитание, дифференцирование и интегрирование. Как работать с такой машиной? Как научить ее считать?
Для начала, определим базовый функционал, которым должна обладать данная машина:
- Набор базовых математических функций:
— Zero(x)=0: для каждого данного аргумента x функция возвращает 0
— Succ(x)=x+1: для каждого данного x функция возвращает x+1
— Семейство функций проекций , где : для любых n аргументов возвращает тот, которые стоит на m позиции.
- Набор операций, которые мы можем применить на данные функции:
— Композиция функций (суперпозиция): (f•g)(x)=f(g(x));
— Примитивная рекурсия: Пусть есть функция f(x1,x2,...,xn) от n переменных, и функция g(X1,X2,...,Xn,Xn+1,Xn+2) от n+2 переменных. Тогда мы можем задать функцию h(X1,X2,....Xn,Xn+1) от n+1 переменных:
h(X1,X2,...,Xn,0) = f(X1,X2,...,Xn);
h(X1,X2,...,Xn,i+1) = g(X1,X2,...,Xn, i, h(X1,X2,...,Xn,i));
Вот собственно и все, что может делать наша абстрактная вычислительная машина. Главное в этом списке, как вы уже догадались, это примитивная рекурсия. Что бы было понятней, поставим себе задачу, например, реализовать функцию сложения двух чисел a и b:
Sum(a,b):
Sum(a,0) = I(a);
Sum(a,i+1) = Succ(F(a,i,Sum(a,i))) где F — проекция третьего аргумента, то есть в данном случае F вернет Sum(a,i);
Все! Теперь мы умеем складывать два числа и мы этому очень рады.
Кстати, возможно у кого-то возник вопрос, зачем так сложно расписывать Sum(a, i+1), и почему не написать просто Sum(a, i+1)=Succ(Sum(a,i)). Отвечаю: по определению, рекурсивная функция должна иметь как аргументы все аргументы (кроме итерационного) желаемой функции (в нашем случае это a), также итерационный аргумент, уменьшенный на единицу (в нашем случае это i), и собственно сам рекурсивный вызов Sum(a,i). Сделано это для возможности работать с непримитивной рекурсией (итерация идет по двум и больше аргументам).
Как вы понимаете, таким же методом мы можем создать функцию уможения, степени, и т.д. Все эти примеры очень хорошо расписаны на Википедии, да и мы их реализуем во второй части статьи. В любом случае, мы можем уже определить бесконечное множество примитивно-рекурсивных функций:
F={f0, f1, f2, f3, ...} — бесконечное исчисляемое множество функций, заданных следующим образом:
f0(0,y) = Succ(y);
f[n+1](x,y): f[n+1](0,y)=y; f[n+1](i+1,y)=fn(y, f[n+1](i,y));
Вот это и есть примитивная рекурсия.
Я не буду погружаться глубже и рассказывать, что такое μ-recursive function, на википедии все очень приятно расписано, тем более что примитивной рекурсии нам достаточно для начала реализации собственной… математики!
Примитивная рекурсия на Scala
Для начала, нам надо определить, что такое число? Создадим абстрактный класс MyNumber:
abstract class MyNumber {
val isNatural: Boolean
val isNeg: Boolean
val isZero: Boolean
val isInf: Boolean
def +(b: MyNumber): MyNumber
def -(b: MyNumber): MyNumber
def *(b: MyNumber): MyNumber
def /(b: MyNumber): MyNumber
def ^(b: MyNumber): MyNumber
def <(b: MyNumber): Boolean
def >(b: MyNumber): Boolean
def ==(b: MyNumber): Boolean
def neg: MyNumber
def abs: MyNumber
def pre: MyInteger
def suc: MyInteger
val num: MyInteger
val denum: MyInteger
def gcd(b: MyNumber): MyNumber
def mod(b: MyInteger): MyNumber
override def toString(): String
def toInteger: MyInteger =
if(isNatural) this.asInstanceOf[MyInteger]
else (num / denum).asInstanceOf[MyInteger]
def toFraction: Fraction =
if(isNatural) new Fraction(this.toInteger)
else this.asInstanceOf[Fraction]
}
Как уже видно, у нас будут два дочерних класса, Fraction и MyInteger:
abstract class MyInteger extends MyNumber {
val isNatural = true
val isNeg: Boolean
val isZero: Boolean
val isInf: Boolean
def +(b: MyNumber): MyNumber =
if (b.isNatural) this plusInt b.toInteger
else this plusFrac b.toFraction
protected def plusInt(b: MyInteger): MyNumber
private def plusFrac(b: Fraction): MyNumber = this.toFraction + b
def -(b: MyNumber): MyNumber =
if (b.isNatural) this minusInt b.toInteger
else this minusFrac b.toFraction
protected def minusInt(b: MyInteger): MyNumber
private def minusFrac(b: Fraction): MyNumber = this.toFraction - b
def *(b: MyNumber): MyNumber =
if (b.isNatural) this multInt b.toInteger
else this multFrac b.toFraction
protected def multInt(b: MyInteger): MyNumber
private def multFrac(b: Fraction): MyNumber = this.toFraction * b
def /(b: MyNumber): MyNumber =
if (b.isNatural) this divInt b.toInteger
else this divFrac b.toFraction
protected def divInt(b: MyInteger): MyNumber
private def divFrac(b: Fraction): MyNumber = this.toFraction / b
def ^(b: MyNumber): MyNumber =
if (b.isNatural) this powInt b.toInteger
else this powFrac b.toFraction
protected def powInt(b: MyInteger): MyNumber
protected def powFrac(b: Fraction): MyNumber //TODO
def <(b: MyNumber): Boolean =
if (b.isNatural) this lessThanInt b.toInteger
else this lessThanFrac b.toFraction
protected def lessThanInt(b: MyInteger): Boolean
private def lessThanFrac(b: Fraction): Boolean = this.toFraction < b
def >(b: MyNumber): Boolean =
if (b.isNatural) this biggerThanInt b.toInteger
else this biggerThanFrac b.toFraction
protected def biggerThanInt(b: MyInteger): Boolean
private def biggerThanFrac(b: Fraction): Boolean = this.toFraction > b
def ==(b: MyNumber): Boolean =
if (b.isNatural) this equalsInt b.toInteger
else this equalsFrac b.toFraction
protected def equalsInt(b: MyInteger): Boolean
private def equalsFrac(b: Fraction): Boolean = this.toFraction == b
def pre: MyInteger
def suc: MyInteger
val num: MyInteger = null
val denum: MyInteger = null
def neg: MyNumber
def abs: MyNumber =
if (!isNeg || isZero) this
else this neg
def gcd(b: MyNumber): MyNumber =
if (b.isNatural) this gcdPrivate b.abs.toInteger
else (new Zero).pre
private def gcdPrivate(b: MyNumber): MyNumber = {
def iter(a: MyNumber, b: MyNumber): MyNumber = {
if (b == (new Zero)) a else iter(b, a.toInteger mod b.toInteger)
}
iter(this, b)
}
def mod(b: MyInteger): MyNumber = {
def iter(a: MyNumber, b: MyNumber): MyNumber = {
if (b.isNeg) this mod b.abs.toInteger
else if (!b.isNatural) (new Zero).pre
else if (a < b) if (a.isNeg) a + b else a
else iter(if (a.isNeg) a + b else a - b, b)
}
iter(this, b)
}
override def toString: String = {
def iter(nb: MyInteger, accu: Int): Int = {
if (nb isZero) accu
else if (nb isNeg) iter(nb.suc.toInteger, accu - 1)
else iter(nb.pre.toInteger, accu + 1)
}
if(this.isInf) "Inf" else iter(this, 0).toString()
}
}
Для определения Integer'а, нам понадобится понятие следующего числа (Suc), предыдущего (Pre), нуля (Zero) и бесконечности (PosInf):
class Zero extends MyInteger {
val isZero = true
val isNeg = false
val isInf = false
protected def plusInt(b: MyInteger): MyNumber = b
protected def minusInt(b: MyInteger): MyNumber = b neg
protected def multInt(b: MyInteger): MyNumber = this
protected def divInt(b: MyInteger): MyNumber =
if (b.isZero) new PosInf
else if(b.isInf) new Zero
else this
protected def powInt(b: MyInteger): MyNumber = this.suc
protected def powFrac(b: Fraction): MyNumber = this.suc
protected def lessThanInt(b: MyInteger): Boolean = !b.isNeg && !b.isZero
protected def biggerThanInt(b: MyInteger): Boolean = b.isNeg && !b.isZero
protected def equalsInt(b: MyInteger): Boolean = b.isZero
def pre: MyInteger = new Pre(this)
def suc: MyInteger = new Suc(this)
def neg = this
}
class PosInf extends MyInteger {
val isZero = false
val isNeg = false
val isInf = true
protected def plusInt(b: MyInteger): MyNumber = this
protected def minusInt(b: MyInteger): MyNumber = this
protected def multInt(b: MyInteger): MyNumber = this
protected def divInt(b: MyInteger): MyNumber = this
protected def powInt(b: MyInteger): MyNumber = this
protected def powFrac(b: Fraction): MyNumber = this
protected def lessThanInt(b: MyInteger): Boolean = false
protected def biggerThanInt(b: MyInteger): Boolean = true
protected def equalsInt(b: MyInteger): Boolean = b.isInf
def pre: MyInteger = this
def suc: MyInteger = this
def neg = this
}
class Pre(nb: MyInteger) extends MyInteger {
val isZero = false
val isNeg = true
val isInf = false
protected def plusInt(b: MyInteger): MyNumber = {
def iter(b: MyInteger, accu: MyNumber): MyNumber = {
if (b isNeg) this - (b neg)
else if (b isZero) accu
else iter(b.pre, accu.suc)
}
iter(b, this)
}
protected def minusInt(b: MyInteger): MyNumber = {
def iter(b: MyInteger, accu: MyNumber): MyNumber = {
if (b isNeg) (this + (b neg))
else if (b isZero) accu
else iter(b.pre, accu.pre)
}
iter(b, this)
}
protected def multInt(b: MyInteger): MyNumber = {
(this.neg * b).neg
}
protected def divInt(b: MyInteger): MyNumber =
if (b.isZero) new PosInf
else if(b.isInf) new Zero
else if (b.isNeg) this.abs / b.abs
else (this.abs / b).neg
protected def powInt(b: MyInteger): MyNumber = if(b!=new Zero)(this.neg ^ b).neg else (new Zero).suc
protected def powFrac(b: Fraction): MyNumber = (new Zero).pre //TODO
protected def lessThanInt(b: MyInteger): Boolean = {
def iter(a: MyInteger, b: MyInteger): Boolean = a.isZero || b.isZero match {
case true => !(a isZero) && (b isZero)
case false => iter(a.suc, b.suc)
}
if (!b.isNeg || b.isZero) true else iter(this, b)
}
protected def biggerThanInt(b: MyInteger): Boolean = {
def iter(a: MyInteger, b: MyInteger): Boolean = a.isZero || b.isZero match {
case true => (a isZero) && !(b isZero)
case false => iter(a.suc, b.suc)
}
if (!b.isNeg || b.isZero) false else iter(this, b)
}
protected def equalsInt(b: MyInteger): Boolean = !(this < b) && !(this > b)
def pre: MyInteger = new Pre(this)
def suc: MyInteger = nb
def neg = {
def iter(nb: MyInteger, accu: MyInteger): MyInteger = {
if (nb isZero) accu
else iter(nb.suc, accu.suc)
}
iter(this, new Zero)
}
}
class Suc(nb: MyInteger) extends MyInteger {
val isZero = false
val isNeg = false
val isInf = false
protected def plusInt(b: MyInteger): MyNumber = {
def iter(b: MyInteger, accu: MyNumber): MyNumber = {
if (b isNeg) this - (b neg)
else if (b isZero) accu
else iter(b.pre, accu.suc)
}
iter(this, b)
}
protected def minusInt(b: MyInteger): MyNumber = {
def iter(b: MyInteger, accu: MyNumber): MyNumber = {
if (b isNeg) this + (b neg)
else if (b isZero) accu
else iter(b.pre, accu.pre)
}
iter(b, this)
}
protected def multInt(b: MyInteger): MyNumber = {
def iter(a: MyInteger, b: MyInteger, accu: MyNumber): MyNumber = {
if (b.isZero) accu
else iter(a, b.pre, accu + a)
}
if (b.isZero) new Zero
else {
if (this < b) {
if (b.isNeg) iter(b.neg.toInteger, this, new Zero).neg
else iter(b, this, new Zero)
} else {
if (b.isNeg) iter(this, b.neg.toInteger, new Zero).neg
else iter(this, b, new Zero)
}
}
}
protected def divInt(b: MyInteger): MyNumber = {
def iter(a: MyNumber, b: MyNumber, count: MyNumber): MyNumber = {
if (a.isZero) count
else if (a.isNeg) count.pre
else iter(a - b, b, count.suc)
}
if (b.isZero) new PosInf
else if(b.isInf) new Zero
else if (b.isNeg) iter(this, b.abs, new Zero).neg
else iter(this, b, new Zero)
}
protected def powInt(b: MyInteger): MyNumber = {
def iter(a: MyInteger, b: MyInteger, accu: MyNumber): MyNumber = {
if (b.isZero) accu
else iter(a, b.pre, accu * a)
}
if (b.isZero) (new Zero).suc
else {
if (b.isNeg) new Fraction((new Zero).suc, iter(this, b.neg.toInteger, (new Zero).suc).toInteger)
else new Fraction(iter(b, this, (new Zero).suc).toInteger)
}
}
protected def powFrac(b: Fraction): MyNumber = (new Zero).pre //TODO
protected def lessThanInt(b: MyInteger): Boolean = {
def iter(a: MyInteger, b: MyInteger): Boolean = a.isZero || b.isZero match {
case true => (a isZero) && !(b isZero)
case false => iter(a.pre, b.pre)
}
if (b.isNeg || b.isZero) false else iter(this, b)
}
protected def biggerThanInt(b: MyInteger): Boolean = {
def iter(a: MyInteger, b: MyInteger): Boolean = a.isZero || b.isZero match {
case true => !(a isZero) && (b isZero)
case false => iter(a.pre, b.pre)
}
if (b.isNeg || b.isZero) true else iter(this, b)
}
protected def equalsInt(b: MyInteger): Boolean = !(this < b) && !(this > b)
def pre: MyInteger = nb
def suc: MyInteger = new Suc(this)
def neg = {
def iter(nb: MyInteger, accu: MyInteger): MyInteger = {
if (nb isZero) accu
else iter(nb.pre, accu.pre)
}
iter(this, new Zero)
}
}
Класс Fraction несколько легче, ибо он полностью базируется на MyInteger:
class Fraction(x: MyInteger, y: MyInteger) extends MyNumber {
def this(x: MyInteger) = this(x, (new Zero).suc)
val isNatural: Boolean = false
val isNeg: Boolean = (x.isNeg && !y.isNeg) || (!x.isNeg && y.isNeg)
val isZero: Boolean = x.isZero
val isInf: Boolean = x.isInf || y.isInf
def +(b: MyNumber): MyNumber = this plus b.toFraction
private def plus(b: Fraction): MyNumber = new Fraction((this.num * b.denum + this.denum * b.num).toInteger, (this.denum * b.denum).toInteger)
def -(b: MyNumber): MyNumber = this minus b.toFraction
private def minus(b: Fraction): MyNumber = new Fraction((this.num * b.denum - this.denum * b.num).toInteger, (this.denum * b.denum).toInteger)
def *(b: MyNumber): MyNumber = this mult b.toFraction
private def mult(b: Fraction): MyNumber = new Fraction((this.num * b.num).toInteger, (this.denum * b.denum).toInteger)
def /(b: MyNumber): MyNumber = this div b.toFraction
private def div(b: Fraction): MyNumber = new Fraction((this.num * b.denum).toInteger, (this.denum * b.num).toInteger)
def ^(b: MyNumber): MyNumber =
if (!b.isNatural) (new Zero).pre
else new Fraction((this.num ^ b.toInteger).toInteger, (this.denum ^ b.toInteger).toInteger)
def <(b: MyNumber): Boolean =
if (this.num.isNeg) {
if (b.num.isNeg) this.neg > b.neg
else true
} else if (this.isZero) {
if (b.isZero || b.num.isNeg) false
else true
} else {
if (b.num.isNeg || b.isZero) false
else this.num * b.denum < this.denum * b.num
}
def >(b: MyNumber): Boolean =
if(this.num.isNeg){
if(b.num.isNeg) this.neg < b.neg
else false
}else if(this.isZero){
if(b.isZero || !b.num.isNeg) false
else true
}else{
if(b.num.isNeg || b.isZero) true
else this.num * b.denum > this.denum * b.num
}
def ==(b: MyNumber): Boolean =
if(!b.isNatural)(this.num == b.num) && (this.denum == b.denum)
else this == b.toFraction
def neg: MyNumber = new Fraction(num.neg.toInteger, denum)
def abs: MyNumber = new Fraction(num.abs.toInteger, denum)
def pre: MyInteger = sys.error("Predicat of fraction")
def suc: MyInteger = sys.error("Successor of fraction")
val num = ((if(this.isNeg)x.abs.neg else x.abs) / (x.abs gcd y.abs)).toInteger
val denum = (y.abs / (x.abs gcd y.abs)).toInteger
def gcd(b: MyNumber): MyNumber = this.denum gcd (new Zero).suc
def mod(b: MyInteger): MyNumber = new Zero
override def toString(): String =
if(num.isZero) 0.toString()
else if(this.isInf) "вИЮ"
else if(denum == (new Zero).suc) num.toString()
else num + "/" + denum
}
Вот и все! У нас теперь есть натуральные и дробные числа, и набор простейших операций (заметьте, ни единого использования нативных классов!)
По такому же принципу можно создавать структуры данных для новоявленных чисел:
abstract class MySet {
def add(elem: MyNumber): MySet
def delete(elem: MyNumber): MySet
def union(set: MySet): MySet
def intersec(set: MySet): MySet
def contains(elem: MyNumber): Boolean
def linearSearch(elem: MyNumber): MySet
def mergeSort: MySet
def merge: MySet
def quickSort: MySet
def timSort: MySet
def binaryTreeSort: MySet
def length: MyNumber
def get(pos: MyNumber): MyNumber
def take(n: MyNumber): MySet
def drop(n: MyNumber): MySet
def map(p: MyNumber => MyNumber): MySet
def filter(p: MyNumber => Boolean): MySet
def flat(p: (MyNumber, MyNumber) => MyNumber): MyNumber
Да и много чего можно сделать с рекурсивными функциями, даже создать свою вселенную. «С блекджеком и шлюхами»©. Спасибо за внимание.
Автор: Mgrin