Функціональне програмування - чи незмінність дорога? [зачинено]


98

Питання складається з двох частин. Перший - концептуальний. Наступний розглядає це питання більш конкретно у Scala.

  1. Чи робить використання лише незмінних структур даних у мові програмування реалізацію певних алгоритмів / логіки, як правило, дорожче обчислювальних на практиці? Це призводить до того, що незмінність є основним принципом суто функціональних мов. Чи існують інші фактори, які впливають на це?
  2. Візьмемо більш конкретний приклад. Як правило, Quicksort викладається та реалізується з використанням змінних операцій над структурою даних в пам'яті. Як реалізувати таку річ ЧИСТИМ функціональним способом із порівнянними накладними витратами на обчислення та зберігання до змінної версії. Зокрема в Scala. Нижче я включив кілька сирих орієнтирів.

Детальніше:

Я походжу з необхідного фону програмування (C ++, Java). Я досліджував функціональне програмування, зокрема Scala.

Деякі основні принципи чистого функціонального програмування:

  1. Функції - це громадяни першого класу.
  2. Функції не мають побічних ефектів, а отже, об'єкти / структури даних незмінні .

Незважаючи на те, що сучасні JVM надзвичайно ефективні у створенні об’єктів, а збір сміття дуже недорогий для об’єктів з нетривалим життям, напевно, все ж краще мінімізувати створення об’єктів, чи не так? Принаймні в однопотоковій програмі, де паралельність та блокування не є проблемою. Оскільки Scala є гібридною парадигмою, при необхідності можна написати імперативний код із змінними об'єктами. Але, як той, хто витратив багато років, намагаючись повторно використовувати об'єкти та мінімізувати розподіл. Я хотів би добре розуміти школу думок, яка б навіть не дозволяла цього.

Як конкретний випадок, я був трохи здивований цим фрагментом коду в цьому підручнику 6 . У ньому є версія Java Quicksort, за якою слідує чудова реалізація 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

10
Це дорого, якщо його реалізувати наївно або з використанням методів, вирощених для імперативних мов. Розумний компілятор (наприклад, GHC, компілятор Haskell - а Haskell має лише незмінні значення) може скористатися незмінністю та високою продуктивністю, яка може конкурувати з кодом за допомогою змінності. Зайве говорити, що наївна реалізація швидкого сорту все ще є повільною, оскільки вона, серед іншого, вимагає важкої рекурсії та O(n)списку конкатів. Однак він коротший за версію псевдокоду;)

3
Чудова, пов’язана з цим стаття в блозі знаходиться тут: blogs.sun.com/jrose/entry/larval_objects_in_the_vm заохочуйте його продовжувати , тому що це було б на користь як Java, так і функціональним мовам віртуальних
машин

2
у цьому потоці SO багато детальних дискусій щодо ефективності функціонального програмування. stackoverflow.com/questions/1990464/… . Багато відповідає на те, що я хотів знати.
smartnut007

5
Найнаївніше тут - це ваш орієнтир. Ви не можете нічого порівняти з таким кодом! Вам слід серйозно прочитати деякі статті про тестування на JVM, перш ніж робити будь-які висновки ... чи знаєте ви, що JVM, можливо, ще не JITted ваш код, перш ніж запускати його? Чи правильно ви встановили початковий та максимальний розмір вашої купи (щоб ви не враховували час, коли процеси JVM вимагають більше пам'яті?)? Чи знаєте ви про те, які методи компілюються чи перекомпілюють? Вам відомо про GC? Результати, отримані з цього коду, абсолютно нічого не означають!
Бруно Рейс

2
@userunknown Ні, це декларативно. Імперативне програмування "змінює стан за допомогою команд", тоді як функціональне програмування - це декларативна парадигма програмування ", яка" уникає зміни стану "( Вікіпедія ). Так що , так, функціональний та імперативний дві абсолютно різні речі, і код , який ви написали, НЕ важливо.
Brian McCutchon

Відповіді:


106

Оскільки тут летить кілька помилкових уявлень , я хотів би пояснити деякі моменти.

  • «На місці» швидке сортування насправді не на місці (і швидке сортування НЕ по визначенню на місці). Для рекурсивного кроку потрібне додаткове сховище у формі простору стека, яке в кращому випадку має порядок O (log n ), а в гіршому O ( n ).

  • Реалізація функціонального варіанту quicksort, який працює на масивах, перемагає мету. Масиви ніколи не змінюються.

  • "Правильна" функціональна реалізація швидкого сортування використовує незмінні списки. Це, звичайно, не на місці, але він має такий самий гірший випадок асимптотичного виконання ( O ( n ^ 2)) та складності простору ( O ( n )), як і процедурна версія на місці.

    В середньому час його роботи все ще відповідає часу роботи на місці ( O ( n log n )). Проте його космічна складність все ще залишається O ( n ).


Є два очевидні недоліки реалізації функціональної швидкої сортування. Далі розглянемо цю посилальну реалізацію в Haskell (я не знаю Scala ...) із вступу Haskell :

qsort []     = []
qsort (x:xs) = qsort lesser ++ [x] ++ qsort greater
    where lesser  = (filter (< x) xs)
          greater = (filter (>= x) xs)
  1. Перший недолік - вибір елемента повороту , який є дуже негнучким. Сила сучасних реалізацій швидкого сорту в значній мірі залежить від розумного вибору основи (порівняйте “Розробка функції сортування” Бентлі та ін. ). Зазначений алгоритм поганий у цьому плані, що значно погіршує середню продуктивність.

  2. По-друге, цей алгоритм використовує конкатенацію списку (замість побудови списку), що є операцією O ( n ). Це не впливає на асимптотичну складність, але є вимірюваним фактором.

Третій недолік дещо прихований: на відміну від варіанту “in-place”, ця реалізація постійно запитує пам’ять із купи для комірок списку мінусів і потенційно розкидає пам’ять повсюдно. Як результат, цей алгоритм має дуже погану локалізацію кешу . Я не знаю, чи можуть розумні розподільники в сучасних функціональних мовах програмування це пом'якшити - але на сучасних машинах помилки кешу стали головним вбивцею продуктивності.


Який висновок? На відміну від інших, я б не сказав, що швидке сортування за своєю суттю є імперативним, і тому воно погано працює в середовищі FP. Навпаки, я б стверджував, що швидка сортування є прекрасним прикладом функціонального алгоритму: він безперешкодно перетворюється на незмінне середовище, його асимптотичний час роботи та складність простору відповідають процедурній реалізації, і навіть його процедурна реалізація використовує рекурсію.

Але цей алгоритм все ще працює гірше, якщо обмежений незмінним доменом. Причиною цього є те, що алгоритм має особливу властивість отримувати вигоду від безлічі (іноді низького рівня) тонкої настройки, яка може бути ефективно виконана лише на масивах. Наївний опис швидкого сорту пропускає всі ці тонкощі (як у функціональному, так і в процедурному варіанті).

Прочитавши “Розробка функції сортування”, я більше не можу вважати швидкий сорт елегантним алгоритмом. Запроваджений ефективно, це незграбний безлад, робота інженера, а не робота художника (не знецінювати техніку! Це має свою естетику).


Але я також хотів би зазначити, що цей момент є особливо сприятливим. Не кожен алгоритм підходить до одного і того ж типу налаштування низького рівня. Дуже багато алгоритмів та структур даних справді можна виразити без втрати продуктивності в незмінному середовищі.

А незмінність може навіть зменшити витрати на продуктивність, усуваючи потребу в дорогих копіях або міжпотоковій синхронізації.

Отже, щоб відповісти на вихідне запитання, „ чи незмінність дорога? ”- У конкретному випадку швидкого сортування існує вартість, яка насправді є результатом незмінності. Але загалом ні .


10
+1 - чудова відповідь! Хоча особисто я закінчував би часом, а не як ні . І все-таки це лише особистість - ви дуже добре пояснили проблеми.
Рекс Керр,

6
Вам слід додати, що правильна реалізація з використанням незмінних значень негайно паралелізується на відміну від імперативних версій. У сучасному технологічному контексті це стає все більш важливим.
Рафаель

Скільки коштує qsort lesser ++ (x : qsort greater)допомога?
Соломон Учко

42

З цим, як еталоном функціонального програмування, є купа речей. Основні моменти:

  • Ви використовуєте примітиви, які, можливо, доведеться поставити в коробці / без коробки. Ви не намагаєтеся перевірити накладні витрати на обгортання примітивних об’єктів, ви намагаєтеся перевірити незмінність.
  • Ви вибрали алгоритм, де робота на місці є надзвичайно ефективною (і доводиться). Якщо ви хочете продемонструвати, що існують алгоритми, які швидше реалізуються із мутацією, то це хороший вибір; інакше це може ввести в оману.
  • Ви використовуєте неправильну функцію синхронізації. Використовуйте System.nanoTime.
  • Тест занадто короткий, щоб ви могли бути впевнені, що компіляція JIT не буде значною частиною вимірюваного часу.
  • Масив не розбивається ефективно.
  • Масиви можна змінювати, тому їх використання з FP у будь-якому випадку є дивним порівнянням.

Отже, це порівняння є чудовою ілюстрацією того, що ви повинні детально розуміти свою мову (та алгоритм), щоб написати високопродуктивний код. Але це не дуже вдале порівняння ПП проти НП. Якщо ви цього хочете, перевірте Haskell проти C ++ у «Комп’ютерних мовах» . Повідомлення про те, що забрати додому, полягає в тому, що штраф зазвичай не перевищує коефіцієнта 2 або 3 або близько того, але це насправді залежить. (Немає обіцянок, що люди Haskell написали найшвидший з можливих алгоритмів, але принаймні деякі з них, ймовірно, пробували! Знову ж таки, деякі з Haskell викликає бібліотеки C ....)

Тепер, припустимо, ви хочете отримати більш розумний орієнтир Quicksort, визнаючи, що це, мабуть, один із найгірших випадків для FP проти змінних алгоритмів, і ігноруючи питання структури даних (тобто роблячи вигляд, що ми можемо мати незмінний масив):

object QSortExample {
  // Imperative mutable quicksort
  def swap(xs: Array[String])(a: Int, b: Int) {
    val t = xs(a); xs(a) = xs(b); xs(b) = t
  }
  def muQSort(xs: Array[String])(l: Int = 0, r: Int = xs.length-1) {
    val pivot = xs((l+r)/2)
    var a = l
    var b = r
    while (a <= b) {
      while (xs(a) < pivot) a += 1
      while (xs(b) > pivot) b -= 1
      if (a <= b) {
        swap(xs)(a,b)
        a += 1
        b -= 1
      }
    }
    if (l<b) muQSort(xs)(l, b)
    if (b<r) muQSort(xs)(a, r)
  }

  // Functional quicksort
  def fpSort(xs: Array[String]): Array[String] = {
    if (xs.length <= 1) xs
    else {
      val pivot = xs(xs.length/2)
      val (small,big) = xs.partition(_ < pivot)
      if (small.length == 0) {
        val (bigger,same) = big.partition(_ > pivot)
        same ++ fpSort(bigger)
      }
      else fpSort(small) ++ fpSort(big)
    }
  }

  // Utility function to repeat something n times
  def repeat[A](n: Int, f: => A): A = {
    if (n <= 1) f else { f; repeat(n-1,f) }
  }

  // This runs the benchmark
  def bench(n: Int, xs: Array[String], silent: Boolean = false) {
    // Utility to report how long something took
    def ptime[A](f: => A) = {
      val t0 = System.nanoTime
      val ans = f
      if (!silent) printf("elapsed: %.3f sec\n",(System.nanoTime-t0)*1e-9)
      ans
    }

    if (!silent) print("Scala builtin ")
    ptime { repeat(n, {
      val ys = xs.clone
      ys.sorted
    }) }
    if (!silent) print("Mutable ")
    ptime { repeat(n, {
      val ys = xs.clone
      muQSort(ys)()
      ys
    }) }
    if (!silent) print("Immutable ")
    ptime { repeat(n, {
      fpSort(xs)
    }) }
  }

  def main(args: Array[String]) {
    val letters = (1 to 500000).map(_ => scala.util.Random.nextPrintableChar)
    val unsorted = letters.grouped(5).map(_.mkString).toList.toArray

    repeat(3,bench(1,unsorted,silent=true))  // Warmup
    repeat(3,bench(10,unsorted))     // Actual benchmark
  }
}

Зверніть увагу на модифікацію функціональної Quicksort, щоб вона лише один раз пройшла дані, якщо це можливо, і порівняння із вбудованою сортуванням. Коли ми запускаємо його, ми отримуємо щось на зразок:

Scala builtin elapsed: 0.349 sec
Mutable elapsed: 0.445 sec
Immutable elapsed: 1.373 sec
Scala builtin elapsed: 0.343 sec
Mutable elapsed: 0.441 sec
Immutable elapsed: 1.374 sec
Scala builtin elapsed: 0.343 sec
Mutable elapsed: 0.442 sec
Immutable elapsed: 1.383 sec

Отож, крім того, що ми дізнаємось, що намагатися написати власний сорт - це погана ідея, ми виявляємо, що за незмінну швидку сорту передбачено приблизно 3-кратне покарання, якщо остання буде виконана дещо обережно. (Ви також можете написати метод trisect, який повертає три масиви: ті, що менші за, ті, що рівні, та ті, що більші за опорну точку. Це може трохи пришвидшити процес.)


Що стосується боксу / розпакування. Якщо що-небудь, це повинно бути покаранням з боку Java-права? Isnt Int - кращий числовий тип для Scala (проти Integer). Таким чином, немає боксу, який хапається на стороні "Скали". Бокс є проблемою лише на стороні Java, оскільки автобокс формує масштаб Int до java.lang.Integer / int. ось посилання, яке детально розповідає про цю тему ansorg-it.com/en/scalanews-001.html
smartnut007

Так, я тут граю адвоката дияволів. Змінність є інтегральною частиною дизайну швидких сортів. Ось чому мені було дуже цікаво чисто функціональний підхід до проблеми. Зітхніть, я сказав це твердження вже 10-ий раз у потоці :-). Буду дивитись на решту вашої публікації, коли я прокинусь і повернусь. Дякую.
smartnut007

2
@ smartnut007 - колекції Scala є загальними. Дженерики здебільшого вимагають типів у коробці (хоча зараз намагаються їх спеціалізувати для певних примітивних типів). Отже, ви не можете використовувати всі чудові методи колекцій і припустити, що не буде штрафу, якщо ви передасте через них колекції примітивних типів. Цілком ймовірно, що примітивний тип доведеться вкласти в коробку на вході та розпакувати на виході.
Рекс Керр

Мені не подобається той факт, що
основний

1
@ smartnut007 - це головний недолік, тому що це важко перевірити, і якщо істина справді псує результати. Якщо ви впевнені, що боксу немає, то я погоджуюсь, що недолік не дійсний. Недолік полягає не в тому, що є бокс, а в тому, що ви не знаєте, чи існує бокс (і я також не впевнений - спеціалізація зробила це складно зрозуміти). З боку Java (або змінної реалізації Scala) немає боксу, оскільки ви використовуєте лише примітиви. У будь-якому випадку, незмінна версія працює через n log n простір, тож ви в кінцевому підсумку порівнюєте вартість порівняння / обміну з розподілом пам'яті.
Рекс Керр

10

Я не думаю, що версія Scala насправді хвоста рекурсивна, оскільки ви використовуєте Array.concat.

Крім того, що це ідіоматичний код Scala, це не означає, що це найкращий спосіб зробити це.

Найкращий спосіб зробити це було б використовувати одну із вбудованих функцій сортування Scala. Таким чином ви отримуєте гарантію незмінності та знаєте, що у вас є швидкий алгоритм.

Див. Запитання щодо переповнення стека Як сортувати масив у Scala? для прикладу.


4
Крім того, я не думаю, що можливий швидкий рекурсивний хвіст, оскільки вам потрібно здійснити два рекурсивні дзвінки
Alex Lo

1
Цілком можливо, вам просто потрібно буде продовжити закриття для підняття ваших потенційних кадру на купу.
Брайан

вбудований scala.util.Sorting.quickSort (масив) мутує масив. Це робиться так швидко, як Java, не дивно. Мене цікавить ефективне чисто функціональне рішення. Якщо ні, то чому. Це обмеження Scala чи функціональна парадигма взагалі. ця сорта.
smartnut007

@ smartnut007: Яку версію Scala ви використовуєте? У програмі Scala 2.8 ви можете робити, array.sortedякий повертає новий відсортований масив, не мутує початковий.
missingfaktor

@AlexLo - можливий швидкий рекурсивний хвіст. Щось на зразок:TAIL-RECURSIVE-QUICKSORT(Array A, int lo, int hi): while p < r: q = PARTITION(A, lo, hi); TAIL-RECURSIVE-QUICKSORT(A, lo, q - 1); p = q + 1;
Джейквей

8

Незмінність не дорога. Зрозуміло, це може бути дорого, якщо виміряти невеликий набір завдань, які повинна виконати програма, і вибрати рішення, засноване на змінності завантаження - наприклад, вимірювання швидкості.

Простіше кажучи, ви не швидко сортуєте, використовуючи чисто функціональні мови.

Давайте розглянемо це з іншого боку. Розглянемо ці дві функції:

// Version using mutable data structures
def tailFrom[T : ClassManifest](arr: Array[T], p: T => Boolean): Array[T] = {
  def posIndex(i: Int): Int = {
    if (i < arr.length) {
      if (p(arr(i)))
        i
      else
        posIndex(i + 1)
    } else {
      -1
    }
  }

  var index = posIndex(0)

  if (index < 0) Array.empty
  else {
    var result = new Array[T](arr.length - index)
    Array.copy(arr, index, result, 0, arr.length - index)
    result
  }
}

// Immutable data structure:

def tailFrom[T](list: List[T], p: T => Boolean): List[T] = {
  def recurse(sublist: List[T]): List[T] = {
    if (sublist.isEmpty) sublist
    else if (p(sublist.head)) sublist
    else recurse(sublist.tail)
  }
  recurse(list)
}

Порівняйте ЦЕ, і ви виявите, що код, що використовує змінні структури даних, має набагато гіршу продуктивність, оскільки йому потрібно скопіювати масив, тоді як незмінний код не повинен цим займатися.

Програмуючи незмінні структури даних, ви структуруєте свій код, щоб скористатися його сильними сторонами. Це не просто тип даних або навіть окремі алгоритми. Програма буде розроблена по-іншому.

Ось чому бенчмаркінг зазвичай безглуздий. Або ви вибираєте алгоритми, які є природними для того чи іншого стилю, і цей стиль перемагає, або ви тестуєте всю програму, що часто недоцільно.


7

Сортування масиву - це, наче, найголовніше завдання у Всесвіті. Не дивно, що багато елегантних "незмінних" стратегій / реалізацій погано не спрацьовують на "сортуванні масиву" мікро-орієнтиру. Однак це не означає, що незмінність є "загалом" дорогою. Є багато завдань, де незмінні реалізації виконуватимуться порівняно із змінними, але сортування масивів часто не є одним із них.


7

Якщо ви просто переписуєте свої імперативні алгоритми та структури даних на функціональну мову, це справді буде дорого і марно. Щоб речі сяяли, слід використовувати функції, доступні лише у функціональному програмуванні: стійкість до стуктур даних, ледачі оцінки тощо.


Ви могли б бути люб’язними, щоб надати реалізацію в Scala.
smartnut007

3
powells.com/biblio/17-0521631246-0 (чисто функціональні структури даних Кріса Окасакі) - просто перегляньте цю книгу. У ньому є сильна історія щодо використання переваг функціонального програмування при реалізації ефективних алгоритмів та структур даних.
Василь Ременюк

1
code.google.com/p/pfds деякі структури даних, реалізовані в Scala Дебашишем Гошем
Василь Ременюк

Чи можете ви пояснити, чому ви вважаєте, що Scala не є обов’язковою? list.filter (foo).sort (bar).take (10)- що може бути більш імперативним?
невідомий користувач

7

Вартість незмінності в Scala

Ось версія, яка майже така ж швидша, ніж Java. ;)

object QuickSortS {
  def sort(xs: Array[Int]): Array[Int] = {
    val res = new Array[Int](xs.size)
    xs.copyToArray(res)
    (new QuickSortJ).sort(res)
    res
  }
}

Ця версія робить копію масиву, сортує його на місці за допомогою версії Java та повертає копію. Scala не змушує вас використовувати непорушну структуру всередині.

Таким чином, користь Scala полягає в тому, що ви можете використовувати зміни та незмінність, як вважаєте за потрібне. Недоліком є ​​те, що якщо ви зробите це неправильно, ви не отримаєте користі від непорушності.


Хоча це не точна відповідь на запитання, я думаю, що це частина будь-якої вагомої відповіді: Quicksort швидший при використанні змінної структури. Але головними перевагами незмінності є інтерфейс, і в Scala принаймні ви можете мати обидва. Змінність швидша для швидкого сортування, але це не заважає вам писати продуктивний, переважно незмінний код.
Пол Дрейпер

7

Відомо, що QuickSort швидше працює на місці, тому це навряд чи справедливе порівняння!

Сказавши це ... Array.concat? Якщо нічого іншого, ви показуєте, як тип колекції, оптимізований для імперативного програмування, особливо повільний, коли ви намагаєтесь використовувати його у функціональному алгоритмі; майже будь-який інший вибір був би швидшим!


Ще один дуже важливий момент, який слід врахувати, можливо , найважливіше питання при порівнянні двох підходів: «як добре робить цю шкалу до декільком вузлам / ядер»

Швидше за все, якщо ви шукаєте незмінний швидкий сорт, то ви робите це, тому що ви насправді хочете паралельний швидкий сорт. У Вікіпедії є кілька цитат на цю тему: http://en.wikipedia.org/wiki/Quicksort#Parallelizations

Версія Scala може просто розгалужитися до того, як функція повториться, що дозволить їй дуже швидко сортувати список, що містить мільярди записів, якщо у вас є достатньо ядер.

На даний момент графічний процесор у моїй системі має 128 ядер, доступних мені, якби я міг просто запустити на ньому код Scala, і це на простій настільній системі, що на два роки відстає від поточного покоління.

Як би це склалося проти однопоточного імперативного підходу, цікаво ...

Можливо, найважливіше питання:

"Беручи до уваги, що окремі ядра не збираються ставати швидшими, а синхронізація / блокування представляє справжню проблему для паралелізації, чи є змінність дорогою?"


Аргументів там немає. Quicksort - це за визначенням сортування в пам'яті. Я впевнений, що більшість людей пам’ятають це з коледжу. Але, як ви швидко сортуєте чисто функціональним способом. тобто без побічних ефектів.
smartnut007

Його важливою причиною є твердження, що функціональна парадигма може бути настільки ж швидкою, як і функції з побічними ефектами.
smartnut007

Версія списку скорочує час наполовину. І все-таки не будь-де, де близька швидкість версії java.
smartnut007

Чи можете ви пояснити, чому ви вважаєте, що Scala не є обов’язковою? list.filter (foo).sort (bar).take (10)- що може бути більш імперативним? Дякую.
невідомий користувач

@user unknown: Можливо, ви могли б пояснити, що, на вашу думку, означає "імператив", оскільки ваш викладений приклад для мене виглядає чудово функціональним. Сама Scala не є ні імперативною, ні декларативною, мова підтримує обидва стилі, і ці терміни найкраще використовувати для опису конкретних прикладів.
Кевін Райт,

2

Вже було сказано, що програмування ОО використовує абстракцію, щоб приховати складність, а функціональне програмування використовує незмінність для усунення складності. У гібридному світі Scala ми можемо використовувати OO, щоб приховати імперативний код, не залишаючи код програми розумнішим. Дійсно, бібліотеки колекцій використовують безліч імперативних кодів, але це не означає, що ми не повинні їх використовувати. Як говорили інші, використовуючи обережно, ви дійсно отримуєте найкраще з обох світів тут.


Чи можете ви пояснити, чому ви вважаєте, що Scala не є обов’язковою? list.filter (foo).sort (bar).take (10)- що може бути більш імперативним? Дякую.
невідомий користувач

Я не розумію, де він сказав, що Скала не є імперативом.
Янкс,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.