Изучаем Scala, на основе решения задачи символьного дифференцирования

в 12:43, , рубрики: Песочница

Часто для того чтобы начать использовать новый язык/технологию нужно найти/выбрать какую-то весьма простую, но хоть сколько-нибудь интересную задачку. Предлагаю, в качестве такой задачи, взять задачу символьного дифференцирования, которая является одной из задач символьных вычислений. Задача символьного дифференцирования состоит в том, чтобы преобразовать одно арифметическое выражение (исходное выражение) в другое арифметическое выражение (результат символьного дифференцирования), которое называется производной этого выражения.

Так как цель этой статьи помочь читателю в изучении языка Scala, то будем рассматривать эту задачу в существенном упрощении, и сосредоточимся на самой, так называемой, «инженерной» задаче.

Прежде чем приступить к решению задачи, введем те самые упрощения.
Во-первых, будем считать, что исходное выражение является или константой или переменной.
Во-вторых, добавим в понятие выражение сумму двух выражений в привычном ее понимании, — как сложение двух величин (в данном случае, как сложение двух выражений).

Определим формальные требования к нашей задаче:

  • Символьный дифференциал от константы, есть константа равная 0
  • Символьный дифференциал от переменной, есть константа равная 1, если имя переменной совпадает с «х», в противном случае результатом символьного дифференцирования будет константа равная 0
  • Символьный дифференциал от суммы, есть сумма символьных дифференциалов

Всюду далее я буду предполагать, что читатель имеет представление об основных концепциях OOP и некое первоначальное представление о самом языке Scala.

Начнем решение с выполнения простой задачи, а затем, итеративно, будем расширять имеющийся функционал. В качестве «базисной» задачи возьмем реализацию первого формального требования:

Символьный дифференциал от константы, есть константа равная 0

Итак, теперь все подготовительные работы закончены и можно начинать решать поставленные задачи.
Я всегда веду разработку любого функционала с того, как я хотел бы пользоваться полученным результатом, т.е. сначала определяю то, как я буду пользоваться функционалом, а уже потом, поняв архитектуру решения поставленной передо мной задачи, приступаю к работе.

Итак, наконец-то немного кода:

package expression

object SymbolicDifferentiation {
    def main(args: Array[String]) {
        // в качестве исходного выражения возьмем произвольную константу, например единицу. 
        val expression = new Constant(1);

        // печатаем исходное выражение
        println("Origin: " + expression); 
        // ожидаем "(1)"
        
        // печатаем результат символьного дифференцирования исходного выражения
        println("Result: " + expression.diff());
        // ожидаем "(0)"
    }
}

Начнем с главного — с исходного выражения, — фундаментального элемента поставленной задачи. Нам необходимо лишь описать поведение, поэтому используем ключевое слово trait:

package expression.core

/* Исходное выражение*/
trait Expression {
    /* возвращает символьный дифференциал исходного выражения */
    def diff(): Expression
}

Определим теперь класс Constant (константа), как конкретную реализацию исходного выражения Expression:

package expression.core

/* Константа */
class Constant(value: Number) extends Expression {
    // требуем чтобы было указанно значение константы, иначе выбрасываем исключение
    require(value != null, throw new IllegalArgumentException)

    // возвращает значение константы
    def getValue(): Number = value

    // возвращает результат символьного дифференцирования константы
    override def diff(): Expression = new Constant(0)

    override def toString(): String = "(" + getValue() + ")"
}

Отлично, мы выполнили первое формальное требование. Приступим к выполнению второго.

Символьный дифференциал от переменной, есть константа равная 1, если имя переменной совпадает с «х», в противном случае результатом символьного дифференцирования будет константа равная 0

И, снова, начнем с того, что определим то, как мы хотим пользоваться будущим функционалом:

package expression

object SymbolicDifferentiation {
    def main(args: Array[String]) {
        // в качестве исходного выражения возьмем произвольную переменную, например "x" 
        val expression = new Variable("x");

        // печатаем исходное выражение
        println("Origin: " + expression); 
        // ожидаем "(x)"
        
        // печатаем результат символьного дифференцирования исходного выражения
        println("Result: " + expression.diff());
        // ожидаем "(1)"
    }
}

При разработке «функционала», удовлетворяющего второму функциональному требованию, создадим еще одну конкретизацию исходного выражения, — Variable(переменная):

package expression.core

/* Переменная */
class Variable(name: String) extends Expression {
    // требуем, чтобы у переменной было указанно имя
    require((name != null) && !"^\s*$".matches(name), throw new IllegalArgumentException)

    // возвращает имя переменной
    def getName(): String = name

    // возвращает результат символьного дифференцирования
    def diff(): Expression = if ("x" == name) { new Constant(1) } else { new Constant(0) }

    override def toString(): String = "(" + getName() + ")"
}

и, наконец, переходим к заключительному третьему функциональному требованию:

Символьный дифференциал от суммы, есть сумма символьных дифференциалов

Как обычно, начнем с описания того, как хотим пользоваться будущим функционалом

package expression

object SymbolicDifferentiation {
    def main(args: Array[String]) {
        // в качестве исходного выражения возьмем сумму ( x + 1 )
        val e = new Add(new Variable("x"), new Constant(1));

        // печатаем исходное выражение
        println("Origin: " + expression); 
        // ожидаем "((x) + (1))"
        
        // печатаем результат символьного дифференцирования исходного выражения
        println("Result: " + expression.diff());
        // ожидаем "((1) + (0))"
    }
}

Перейдем к определению последнего объекта поставленной задачи, — Add (сумма):

/* Cумма двух выражений */
class Add(firstSummand: Expression, secondSummand: Expression) extends Expression {
    // требуем, чтобы были указанны оба слагаемых, иначе выбрасываем исключение
    require((firstSummand != null) && (secondSummand != null), throw new IllegalArgumentException)

    // возвращает первое слагаемое
    def getFirstSummand(): Expression = firstSummand

    // возвращает второе слагаемое
    def getSecondSummand(): Expression = secondSummand

    // возвращает результат символьного дифференцирования, - сумму символьных дифференциалов 
    override def diff(): Expression = new Add(getFirstSummand().diff(), getSecondSummand().diff())

    override def toString(): String = "(" + getFirstSummand() + " + " + getSecondSummand() + ")"
}

Вот и всё. На основе решения задачи символьного дифференцирования и, принятых упрощений, реализовали вот такую небольшую программку, а заодно немного разобрались в том, как «использовать» OOP в Scala, по-крайней мере на ранних этапах знакомства с этим замечательным языком. Всем спасибо за внимание! Всем добра!

Используемые ресурсы:
— Официальный сайт, посвященный языку Scala.

Автор: jxcoder

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js