Чому побічні ефекти вважаються злом у функціональному програмуванні?


69

Я відчуваю, що побічні ефекти - це природне явище. Але це щось на зразок табу у функціональних мовах. Які причини?

Моє питання стосується функціонального стилю програмування. Не всі мови програмування / парадигми.


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

@JacquesB Було б гарною відповіддю пояснити, чому їх легше зрозуміти, простіше проаналізувати, простіше тестувати та легше оптимізувати.
припинення

Відповіді:


72

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

Це також дозволяє легко складати ці функції для створення нової поведінки.

Це також робить можливими певні оптимізації, коли компілятор може, наприклад, запам'ятати результати функцій або використовувати загальне усунення субекспресії.

Змінити: за запитом Benjol в: Тому що багато ваш стан зберігається в стеку (потік даних, не контролює потік, як Іона назвав його тут ), ви можете parallelise або іншим чином змінювати порядок виконання цих частин вашого обчислення , що не залежать від один одного. Ви можете легко знайти ці незалежні частини, оскільки одна частина не забезпечує введення іншої.

У середовищах з відладчиками, які дозволяють повернути стек і відновити обчислення (наприклад, Smalltalk), наявність чистих функцій означає, що ви можете легко побачити, як змінюється значення, оскільки попередні стани доступні для перевірки. У важкому для мутації обчисленні, якщо ви явно не додаєте дії do / unndo до вашої структури чи алгоритму, ви не можете бачити історію обчислень. (Це пов'язано з першим пунктом: написання чистих функцій полегшує перевірку правильності вашої програми.)


4
Можливо, подумайте, чи додати у відповідь щось про одночасність?
Benjol

5
Функції без побічних ефектів простіше перевірити та повторно використовувати.
LennyProgrammers

@ Lenny222: повторне використання - це те, на що я натякав, розповідаючи про склад функції.
Френк Ширар

@Frank: Ах, гаразд, занадто дрібне перегляд. :)
LennyProgrammers

@ Lenny222: це нормально; можливо, це добре прописати.
Френк Ширар

23

Зі статті про функціональному програмуванні :

На практиці програми повинні мати деякі побічні ефекти. Саймон Пейтон-Джонс, головний учасник мови функціонального програмування Haskell, сказав наступне: "Зрештою, будь-яка програма повинна маніпулювати станом. Програма, яка не має жодних побічних ефектів, є різновидом чорного поля. Все, що ви можете сказати, це що коробка стає гарячішою ". ( Http://oscon.blip.tv/file/324976 ) Ключ обмежити побічні ефекти, чітко визначити їх і уникати їх розсіювання в коді.



23

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

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


Ось чому вони "щось на зразок табу" - FPLs рекомендують обмежити побічні ефекти.
Френк Ширар

+1 за підхід. побічні ефекти все ще існують. Дійсно, вони обмежені
Белун

Для уточнення я не сказав "чому побічний ефект не дозволений у функціональному програмуванні" або "чому побічний ефект не потрібен". Я знаю, що це дозволено на функціональних мовах, і іноді це обов'язково. Але це дуже відлякує функціональне програмування. Чому? Це було моє питання.
Гульшан

@Gulshan - Оскільки побічні ефекти ускладнюють програми для розуміння та оптимізації.
ChaosPandion

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

13

Кілька приміток:

  • Функції без побічних ефектів можуть тривіально виконуватись паралельно, тоді як функції із побічними ефектами, як правило, потребують певної синхронізації.

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


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

1
@NoelWidmer Щось подібне вже існує. PL / SQL Oracle пропонує deterministicположення про функції без побічних ефектів, тому вони не виконуються частіше, ніж потрібно.
користувач281377

Оце Так! Однак я вважаю, що мови повинні бути семантично виразними, щоб компілятор міг розібратися в цьому сам, не вказуючи явного прапора (я не впевнений, що таке пункт). Рішенням може бути визначення параметрів, що підлягають змінному / незмінному режиму. Загалом кажучи, для цього потрібна система сильного типу, в якій компілятор може робити припущення про побічні ефекти. І функцію потрібно було б мати можливість вимкнути за бажанням. Відмовитися від заміни. Це лише моя думка, виходячи з обмежених знань, які я маю з тих пір, коли я прочитав вашу відповідь :)
Ноель Відмер

deterministicПоложення тільки ключове слово , яке повідомляє компілятору , що це є детермінованою функцією, яку можна порівняти з тим, як finalключове слово в Java повідомляє компілятору , що змінна не може змінитися.
користувач281377

11

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

Розглянемо цей простий приклад:

val foo = 42
// Several lines of code you don't really care about, but that contain a
// lot of function calls that use foo and may or may not change its value
// by side effect.

// Code you are troubleshooting
// What's the expected value of foo here?

У функціональній мові я знаю, що fooце все ще 42. Мені навіть не доводиться дивитися на код між ними, тим більше розуміти його чи дивитися на реалізацію функцій, які він викликає.

Все, що стосується паралельності, паралелізації та оптимізації, є приємним, але це те, що комп'ютерні працівники розміщують у брошурі. Не потрібно дивуватися, хто мутує вашу змінну і коли це те, що мені дуже подобається в щоденній практиці.


6

Мало на яких мовах неможливо викликати побічні ефекти. Мови, повністю вільні від побічних ефектів, було б надзвичайно важким (майже неможливим) для використання, за винятком обмеженої кількості.

Чому побічні ефекти вважаються злими?

Тому що їм набагато складніше міркувати про те, що саме робить програма, і доводити, що вона робить те, що ти очікуєш.

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

Переваги

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

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

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


1
Ада дуже важко викликати побічні ефекти. Хоча це не неможливо, але ви чітко знаєте, чим тоді займаєтесь.
mouviciel

@mouviciel: Я думаю, що є принаймні кілька корисних мов, які роблять побічні ефекти дуже важкими, і намагаються віднести їх до Monads.
Мерлін Морган-Грехем

4

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

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

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


+1 - "або якийсь нічого не підозрюючий колега"
Мерлін Морган-Грем

1
-1 для побічних ефектів, які є "протіканнями, з якими потрібно боротися". Створення "побічних ефектів" (нечисто-функціональний код) - це вся мета написання будь-якої нетривіальної комп'ютерної програми.
Мейсон Уілер

Цей коментар з'являється через шість років, але є побічні ефекти, а потім є побічні ефекти. Бажаний вид побічних ефектів, введення вводу / виводу тощо, дійсно необхідний будь-якій програмі, тому що потрібно якось надати свої результати користувачеві - але інший вид побічних ефектів, коли ваш код змінює стан без гарного Причини, як, наприклад, введення / виведення, насправді є "витоком", який потрібно буде вирішити пізніше. Основна ідея - розділення команд-запитів : функція, яка повертає значення, не повинна мати побічного ефекту.
rmunn

4

Ну, ІМХО, це досить лицемірно. Ніхто не любить побічні ефекти, але всі їм потрібні.

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

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

Функціональне програмування має набагато більш радикальний підхід, коли стан програми просто незмінний з точки зору програміста. Це гарна ідея, але робить мову самостійно марною. Чому? Оскільки будь-яка операція вводу / виводу має побічні ефекти. Щойно ви читаєте з будь-якого потоку введення, стан програми, швидше за все, зміниться, оскільки наступного разу, коли ви будете викликати ту саму функцію, результат, ймовірно, буде іншим. Можливо, ви читаєте різні дані, або - також можливість - операція може не вдатися. Те саме стосується виводу. Рівномірний вихід - це операція з побічними ефектами. Це не те, що ви часто розумієте в наші дні, але уявіть, що у вас є лише 20 Кб для вашого виходу, і якщо ви виходите більше, ваш додаток виходить з ладу, оскільки у вас немає місця на диску або що завгодно.

Так, так, побічні ефекти є неприємними та небезпечними з точки зору програміста. Більшість помилок походять від того, як певні частини стану програми перемикаються майже незрозуміло, через непродумані та часто непотрібні побічні ефекти. З точки зору користувача, побічні ефекти - це сенс використання комп'ютера. Їм не байдуже, що відбувається всередині або як це організовано. Вони щось роблять і очікують, що комп'ютер змінить відповідно.


що цікаво логічне програмування не тільки не має функціональних побічних ефектів; але ви навіть не можете змінити значення змінної, щойно призначена.
Ілан

@Ilan: Це також стосується деяких функціональних мов, і це стиль, який легко прийняти.
back2dos

"Функціональне програмування має набагато більш радикальний підхід, коли стан програми просто незмінний з точки зору програміста. Це хороша ідея, але робить мову самостійно марною. Чому? Тому що будь-яка операція вводу / виводу має сторону ефекти ": FP не забороняє побічні ефекти, а скоріше обмежує їх, коли це не потрібно. Наприклад (1) введення / виведення -> побічні ефекти необхідні; (2) обчислення сукупної функції з послідовності значень -> побічний ефект (наприклад, для циклу зі змінною акумулятора) не потрібно.
Джорджіо

2

Будь-який побічний ефект вводить додаткові параметри вводу / виводу, які необхідно враховувати при тестуванні.

Це робить валідацію коду набагато складнішою, оскільки середовище не може бути обмежене лише валідизованим кодом, але повинно вносити деяке або все навколишнє середовище (глобальний, який оновлюється, живе в цьому коді там, що, в свою чергу, залежить від цього код, який, у свою чергу, залежить від того, щоб жити на повному сервері Java EE ....)

Намагаючись уникнути побічних ефектів, ви обмежуєте кількість екстерналізму, необхідного для запуску коду.


1

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

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

Далеко від зла, правильне розташування цих побічних ефектів, що змінюють купу та екрани, лежить в основі дизайну ОО (в даному випадку - модель MVC).

Звичайно, це не означає, що ваші методи повинні мати довільні побічні ефекти. І функції, що не мають побічних ефектів, мають місце в поліпшенні читаемості, а іноді і продуктивності вашого коду.


1
Спостерігачі (включаючи інтерфейс користувача) повинні дізнатися про зміни, підписавшись на повідомлення / події, які надсилає ваш об’єкт. Це не є побічним ефектом, якщо об'єкт безпосередньо не модифікує спостерігача - це було б поганим дизайном.
ChrisF

1
@ChrisF Певно, це побічний ефект. Повідомлення, передане спостерігачеві (в мові OO, швидше за все, виклик методу на інтерфейсі) призведе до зміни компонента інтерфейсу на купі, що змінюється (і ці об’єкти купи видно в інших частинах програми). Компонент UI не є ні параметром методу, ні значенням повернення. У формальному сенсі, щоб функція була вільною від побічних ефектів, вона повинна бути ідентичною. Повідомлення у шаблоні MVC не є, наприклад, інтерфейс може відображати список отриманих повідомлень - консолі - викликаючи його двічі, призводить до іншого програмного стану.
flamingpenguin

0

Зло трохи на вершині .. все залежить від контексту використання мови.

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


0

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

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

  • У коді зі змінним станом ми можемо керувати сферою дії таким чином, щоб статично переконатися, що він не може витікати за межі заданої функції, що дозволяє нам збирати сміття без будь-якого посилання на підрахунок чи схеми стилю розмітки , але все ж будьте впевнені, що жодна згадка не збереглася Ці ж гарантії також корисні для збереження конфіденційної інформації тощо. (Це можна досягти, використовуючи монаду ST в haskell)
  • Змінюючи загальний стан у кількох потоках, ми можемо уникнути необхідності блокування, відстежуючи зміни та виконуючи атомне оновлення в кінці транзакції, або відкочуючи транзакцію назад та повторюючи її, якщо інший потік вніс суперечливі зміни. Це досяжно лише тому, що ми можемо гарантувати, що код не має ніяких наслідків, крім модифікацій стану (від яких ми можемо з радістю відмовитися). Це виконується монадою STM (Software Transactional Memory) в Haskell.
  • ми можемо відслідковувати ефекти коду та тривіально пісочного поля, фільтруючи будь-які ефекти, які він може знадобитися виконувати, щоб переконатися, що він безпечний, тим самим дозволяючи (наприклад) введений користувачем код надійно виконувати на веб-сайті

0

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

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

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

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

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

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

for each pixel in an image:
    make it red

Думати про правильність такого коду досить просто, але головним чином через те, що побічні ефекти настільки рівномірні, а контрольний потік настільки мертвий. Але скажімо, у нас був такий код:

for each vertex to remove in a mesh:
     start removing vertex from connected edges():
         start removing connected edges from connected faces():
             rebuild connected faces excluding edges to remove():
                  if face has less than 3 edges:
                       remove face
             remove edge
         remove vertex

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

for each vertex to remove:
     mark connected edges
for each marked edge:
     mark connected faces
for each marked face:
     remove marked edges from face
     if num_edges < 3:
          remove face

for each marked edge:
     remove edge
for each vertex to remove:
     remove vertex

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

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


-1

Це не зло. На мою думку, потрібно розрізняти два типи функцій - з побічними ефектами і без. Функція без побічних ефектів: - повертає завжди те саме з однаковими аргументами, тому, наприклад, така функція без жодних аргументів не має сенсу. - Це також означає, що порядок, в якому деякі такі функції називаються, не грає ніякої ролі - повинен бути в змозі запускатися і може бути налагоджений на самоті (!), Без іншого коду. А тепер, хай, подивися, що робить JUnit. Функція з побічними ефектами: - має свого роду "протікання", що можна виділити автоматично - це дуже важливо шляхом налагодження та пошуку помилок, що, як правило, викликано побічними ефектами. - Будь-яка функція з побічними ефектами також має "частину" без побічних ефектів, яку також можна відокремити автоматично. Тож злі ті побічні ефекти,


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