Вопрос состоит из двух частей. Первый - концептуальный. Следующий вопрос будет рассмотрен более конкретно в Scala.
- Использует ли только неизменяемые структуры данных на языке программирования, чтобы реализовать определенные алгоритмы/логику изначально более дорогостоящим на практике? Это связано с тем, что неизменность является основным принципом чисто функциональных языков. Существуют ли другие факторы, влияющие на это?
- Давайте рассмотрим более конкретный пример. Quicksort обычно изучается и реализуется с использованием изменяемых операций над структурой данных в памяти. Как реализовать такую вещь в функциональном режиме PURE с сопоставимыми издержками на вычисление и хранение в изменяемой версии. В частности, в Scala. Я включил некоторые сырые тесты ниже.
Подробнее:
Я исхожу из императивного программирования (С++, Java). Я изучал функциональное программирование, в частности Scala.
Некоторые из основных принципов чистого функционального программирования:
- Функции являются гражданами первого класса.
- Функции не имеют побочных эффектов и, следовательно, объекты/структуры данных immutable.
Несмотря на то, что современные JVM чрезвычайно эффективны при создании объекта и сбор мусора очень недорого для короткоживущих объектов, возможно, еще лучше минимизировать создание объектов? По крайней мере, в однопоточном приложении, где concurrency и блокировка не являются проблемой. Поскольку Scala представляет собой гибридную парадигму, можно при необходимости выбрать запись императивного кода с изменяемыми объектами. Но, как человек, который потратил много лет, пытается повторно использовать объекты и минимизировать распределение. Я хотел бы хорошо понять школу мысли, которая даже не допустила бы этого.
В качестве конкретного случая я был немного удивлен этим фрагментом кода в этот учебник 6. В нем есть версия QuickSort на Java, за которой следует аккуратная реализация Scala того же самого.
Вот моя попытка проверить реализации. Я не сделал подробного профилирования. Но я полагаю, что версия Scala медленнее, потому что количество выделенных объектов является линейным (по одному на вызов рекурсии). Есть ли шанс, что оптимизация вызовов хвоста может вступить в игру? Если я прав, Scala поддерживает оптимизацию хвостовых вызовов для саморекурсивных вызовов. Таким образом, это должно только помочь. Я использую Scala 2.8.
версия Java
public class QuickSortJ {
public static void sort(int[] xs) {
sort(xs, 0, xs.length -1 );
}
static void sort(int[] xs, int l, int r) {
if (r >= l) return;
int pivot = xs[l];
int a = l; int b = r;
while (a <= b){
while (xs[a] <= pivot) a++;
while (xs[b] > pivot) b--;
if (a < b) swap(xs, a, b);
}
sort(xs, l, b);
sort(xs, a, r);
}
static void swap(int[] arr, int i, int j) {
int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
}
}
Scala версия
object QuickSortS {
def sort(xs: Array[Int]): Array[Int] =
if (xs.length <= 1) xs
else {
val pivot = xs(xs.length / 2)
Array.concat(
sort(xs filter (pivot >)),
xs filter (pivot ==),
sort(xs filter (pivot <)))
}
}
Scala Код для сравнения реализаций
import java.util.Date
import scala.testing.Benchmark
class BenchSort(sortfn: (Array[Int]) => Unit, name:String) extends Benchmark {
val ints = new Array[Int](100000);
override def prefix = name
override def setUp = {
val ran = new java.util.Random(5);
for (i <- 0 to ints.length - 1)
ints(i) = ran.nextInt();
}
override def run = sortfn(ints)
}
val benchImmut = new BenchSort( QuickSortS.sort , "Immutable/Functional/Scala" )
val benchMut = new BenchSort( QuickSortJ.sort , "Mutable/Imperative/Java " )
benchImmut.main( Array("5"))
benchMut.main( Array("5"))
Результаты
Время в миллисекундах для пяти последовательных прогонов
Immutable/Functional/Scala 467 178 184 187 183
Mutable/Imperative/Java 51 14 12 12 12