Чи можливе функціональне програмування GUI? [зачинено]


404

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

Однак однією з областей, на якій поточний FP, здається, є програмування графічного інтерфейсу. Підхід Haskell, здається, полягає в тому, щоб просто обгортати необхідні набори інструментів GUI (наприклад, GTK + або wxWidgets) та використовувати блоки "do" для імітації імперативного стилю. Я не використовував F #, але я розумію, що він робить щось подібне, використовуючи OOP з .NET-класами. Очевидно, що для цього є вагома причина - поточне програмування графічного інтерфейсу стосується IO та побічних ефектів, тому чисто функціональне програмування неможливо в більшості сучасних рамок.

Моє запитання: чи можливий функціональний підхід до програмування графічного інтерфейсу? У мене виникають труднощі уявити, як це виглядатиме на практиці. Хтось знає про будь-які рамки, експериментальні чи інші, які намагаються подібні речі (або навіть будь-які рамки, розроблені з нуля для функціональної мови)? Або рішення просто використовувати гібридний підхід, з OOP для частин GUI та FP для логіки? (Мене просто цікавлять питання - я б хотів подумати, що FP - це "майбутнє", але програмування GUI здається досить великою діркою для заповнення.)


7
Подивившись на GUI у Common Lisp та OCaml, я б сказав, що, швидше за все, його лінощі Haskell викликають проблему.
new123456

5
@ new123456 Хоча звичайний Lisp не є функціональною мовою, він працює зі змінними даними та охоплює побічні ефекти
Electric Coffee

3
@ElectricCoffee Lisp - надзвичайно гнучка мова, яка може використовуватися в багатьох різних стилях, і багато людей вирішують використовувати Lisp у функціональному стилі.
chrismamo1

7
З мого досвіду (хоча я все ще намагаюся повірити в це і дізнатися більше) FRP дійсно досягає своєї межі з програмуванням GUI; це приємно і елегантно для 80% випадків використання, але багаті віджети вимагають дуже точного контролю над своїм внутрішнім станом (наприклад, пошукові комбо-скринки тощо), і FRP просто заважає. Імператив - це не завжди зло; намагатися мінімізувати кількість імперативного коду - це добре, але видаляти його на 100%? Ще не бачимо, як це працює для розвитку нетривіального інтерфейсу користувача.
AlexG

8
@ElectricCoffee "Хоча звичайний Lisp не є функціональною мовою". Лісп - мати всіх функціональних мов. Ти маєш на увазі, що Лісп не чистий.
Джон Харроп

Відповіді:


183

Підхід Haskell, здається, полягає в тому, щоб просто обернути необхідні набори інструментів GUI (наприклад, GTK + або wxWidgets) та використовувати блоки "do" для імітації імперативного стилю

Це насправді не "підхід Haskell" - саме так ви підключаєтесь до імперативних наборів інструментів GUI безпосередньо - через імперативний інтерфейс. У Haskell, як правило, є досить помітні прив’язки.

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

Деякі приклади:

Для тих, хто вам не знайомий з Haskell, Flapjax, http://www.flapjax-lang.org/ - це реалізація функціонального реактивного програмування поверх JavaScript.


32
Дивіться статтю Conal Elliott про фрукти, щоб отримати чудовий, глибокий опис техніки та рішень: conal.net/papers/genuinely-functional-guis.pdf Я вже декілька місяців займаюся чисто функціональним програмуванням графічного інтерфейсу. . Я люблю це, це таке приємне полегшення від пекла спагеті імперативного програмування інтерфейсу, який, здається, є гіршим у цьому відношенні, ніж більшість імперативних програмувань.
luqui

44
Я на це 100% згоден. Щоб зробити його чітко зрозумілим: причина, по якій часто використовуються існуючі набори інструментів GUI, полягає в тому, що вони існують. Причина, через яку інтерфейси для них, як правило, є імперативними та нечистими, полягає в тому, що набори інструментів, як правило, є імперативними та нечистими. Причиною того, що набори інструментів, як правило, є імперативними та нечистими, полягає в тому, що операційні системи, від яких вони залежать, мають тенденцію бути імперативними та нечистими. Однак, нічого принципово не вимагає, щоб будь-яке з них було нечистим: є функціональні зв'язки для цих наборів інструментів, є функціональні набори інструментів, є навіть функціональні операційні системи.
Йорг W Міттаг

16
Це все лише питання ліні. (Поганий каламбур.)
Jörg W Mittag

10
Колись весь дизайн GUI буде реалізований через WYSIWYG, з логікою, функціонально реалізованою. Це мій прогноз.
BlueRaja - Danny Pflughoeft

23
Згаданий папір luqui, здається, мертвий. На сайті Conal Elliott є робоче посилання, хоча: conal.net/papers/genuinely-functional-guis.pdf
aganders3

74

Моє запитання: чи можливий функціональний підхід до програмування графічного інтерфейсу?

Ключові слова, які ви шукаєте, - це "функціональне реактивне програмування" (FRP).

Conal Elliott та деякі інші зробили трохи котеджного господарства, намагаючись знайти правильну абстракцію для FRP. У Haskell є кілька реалізацій концепцій FRP.

Ви можете подумати, починаючи з останнього документу "Функціональне реактивне програмування" від Conal , але є кілька інших (старших) реалізацій, деякі з яких пов'язані з сайту haskell.org . Conal має спритність для покриття всього домену, і його документ можна читати без посилання на те, що було раніше.

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


Я хотів би додати збільшення використання "Реактивних розширень" (бібліотеки FRP; проте, не FP), які спочатку писалися для C #, а потім переносилися на Java (RxJava) та JavaScript (RxJS) та різні мови. Ознайомтеся з reactivex.ioДо того, Angular 2 широко використовує RxJS.
srph

63

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

WPF описує користувальницький інтерфейс у XAML (хоча ви можете переписати його на функціональний вигляд C # або F # теж), тому для створення певного користувальницького інтерфейсу ви б написали:

<!-- Declarative user interface in WPF and XAML --> 
<Canvas Background="Black">
   <Ellipse x:Name="greenEllipse" Width="75" Height="75" 
      Canvas.Left="0" Canvas.Top="0" Fill="LightGreen" />
</Canvas>

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

<DoubleAnimation
   Storyboard.TargetName="greenEllipse" 
   Storyboard.TargetProperty="(Canvas.Left)"
   From="0.0" To="100.0" Duration="0:0:5" />

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


12
Хоча XAML має дуже декларативний характер, чи дійсно MVVM заохочує функціональний стиль програмування? Ціле поняття моделі перегляду, завданням якої є відстеження стану перегляду (і реалізує інтерфейс, який називається INotifyPropertyChangedвсіма речами), здається мені протиправним для FP. Я, безумовно, не знаю FP, і, можливо, я занадто багато зосереджуюсь на аспекті незмінності на відміну від декларативного аспекту, але у мене виникають проблеми, коли модель MVVM (як зазвичай використовується) є прикладом FP.
devuxer

1
@devuxer Я б заперечував, що це так. Я не думаю, що реально використовувати FP для суворого незмінного коду. Натомість ви вирішуєте, де є ваші межі змінності, і працюєте непорушними на всіх інших рівнях - у цьому випадку кожен може вважати, що стан є непорушним, за винятком тієї єдиної крихітної частини, яка насправді мутує стан. Це схоже на те, як працює HTML - так, у вас є непорушний DOM, але щоразу, коли ви переходите, вам все одно доведеться будувати новий. INotifyPropertyChangedце лише функція оновлення, яку ви передаєте туди, куди вам потрібно обробляти оновлення графічного інтерфейсу, - це виправлення затримки.
Луаан

3
Стівен Пембертон написав 2 чудових дописи про F # і WPF, його думки про розвиток WPF з F # до кінця другого допису додають до цієї дискусії. Інші приклади, які мене також заінтригували, - це використання функціонального контролера в MVVM, керованому подіями, та використання дискримінованих об'єднань та рекурсії для побудови простого інтерфейсу в демонстраційному контролі WPF від Flying Frog Consultancy.
Funk

29

Я б фактично сказав, що функціональне програмування (F #) є набагато кращим інструментом для програмування користувальницького інтерфейсу, ніж, наприклад, C #. Вам потрібно подумати про проблему трохи інакше.

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

let rec drawingLoop(clr, from) = async { 
   // Wait for the first MouseMove occurrence 
   let! move = Async.AwaitObservable(form.MouseMove) 
   if (move.Button &&& MouseButtons.Left) = MouseButtons.Left then 
      // Refresh the window & continue looping 
      drawRectangle(clr, from, (move.X, move.Y)) 
      return! drawingLoop(clr, from) 
   else
      // Return the end position of rectangle 
      return (move.X, move.Y) } 

let waitingLoop() = async { 
   while true do
      // Wait until the user starts drawing next rectangle
      let! down = Async.AwaitObservable(form.MouseDown) 
      let downPos = (down.X, down.Y) 
      if (down.Button &&& MouseButtons.Left) = MouseButtons.Left then 
         // Wait for the end point of the rectangle
         let! upPos = drawingLoop(Color.IndianRed, downPos) 
         do printfn "Drawn rectangle (%A, %A)" downPos upPos }

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

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


7
+1 Це відображає наш досвід, написавши кілька виробничих графічних інтерфейсів у F # за допомогою бібліотек комбінаторів та IObservable.
Джон Харроп

Чи змінився коментар до FRP після впровадження реактивних розширень у бібліотеку .NET?
Fsharp Pete

1
Ось кілька досліджень щодо Arrowized FRP та того, як ефекти та мутації можна вбудовувати в Arrowized FRP, не порушуючи закони: haskell.cs.yale.edu/wp-content/uploads/2015/10/… (btw більшість бібліотек FRP використовують Monads або навіть Додатки, тому не правильно, що потрібні стрілки).
Ерік Каплун

17

Незалежно від того, що ви використовуєте гібридну функціональну / OO мову, як F # або OCaml, або чисто функціональну мову, як Haskell, де побічні ефекти передаються монаді IO, здебільшого справа в тому, що для управління графічним інтерфейсом потрібна ціла робота. набагато більше схожий на "побічний ефект", ніж як суто функціональний алгоритм.

Однак було проведено декілька справді ґрунтовних досліджень функціональних графічних інтерфейсів . Існують навіть деякі (в основному) функціональні набори інструментів, такі як Fudgets або FranTk .


6
Посилання "функціональних графічних інтерфейсів" розірвано :( кешовано: webcache.googleusercontent.com/search?q=cache:http://…
Ден Бертон

15

Ви можете ознайомитись із серією Дон Сайм на F #, де він демонструє створення гуї. наступне посилання - на третю частину серії (звідти ви можете зв’язати інші дві частини).

Використання F # для розробки WPF було б дуже цікавою парадигмою GUI ...

http://channel9.msdn.com/shows/Going+Deep/C9-Lectures-Dr-Don-Syme-Introduction-to-F-3-of-3/


12

Однією з цікавих ідей, що стоїть за функціональним реактивним програмуванням, є функція обробки подій, що виробляє ВІДРУЧНУ реакцію на події та наступну функцію обробки подій. Таким чином, що розвивається система представлена ​​у вигляді послідовності функцій обробки подій.

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

http://www.cs.nott.ac.uk/~nhn/Talks/HW2003-YampaArcade.pdf (слайди, PDF) http://www.cs.nott.ac.uk/~nhn/Publications/hw2003. pdf (повна стаття, PDF)

На сайті Haskell.org є вікі-сторінка про Yampa

http://www.haskell.org/haskellwiki/Yampa

Оригінальна домашня сторінка Yampa:

http://www.haskell.org/yampa (на жаль, на даний момент зламано)


1
Ця ланка розірвана давно. Спробуйте це Yampa
CoR

7

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

Я пропоную перевірити це на веб-сайті http://elm-lang.org , де також є кілька справді чудових інтерактивних навчальних посібників про те, як зробити повністю функціональний інтерфейс браузера.

Це дозволяє зробити повністю функціональний графічний інтерфейс, де код, який вам потрібно надати, складається лише з чистих функцій. Мені особисто виявилося набагато простіше потрапити в різні рамки Haskell GUI.


Ось оригінальна теза FRP за в'язом . Але також з травня 2016 року в'яз вже не є мовою FRP .
icc97

6

Еліотські розмови про FRP можна знайти тут .

Крім того, насправді не відповідь, а зауваження та кілька думок : якось термін «функціональний графічний інтерфейс» трохи схожий на оксиморон (чистота та IO в тому ж терміні).

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

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

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

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

Опис цієї програми імітації в FRP (на моє розуміння) робиться за допомогою єдиного диференціального рівняння (декларативно): прискорення * маса = - розтягнення пружини * константа пружини + Сила, прикладена користувачем.

Ось відео на ELM, яке ілюструє цю точку зору.


5

Станом на 2016 рік існує ще кілька, відносно зрілих рамок FRP для Haskell, таких як Sodium та Reflex (але також Netwire).

У книзі Меннінга про функціональне реактивне програмування представлена ​​версія Java Sodium для робочих прикладів, і показано, як поводиться і масштабується база коду FRP GUI порівняно з імперативними, а також підходами, заснованими на Actor.

Існує також нещодавній документ про Arrowized FRP та перспективі включення побічних ефектів, IO та мутації в закон, що дотримується, чисте положення FRP: http://haskell.cs.yale.edu/wp-content/uploads/2015/10/ dwc-yale-formatted-dissertation.pdf .

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


Згідно з
читанням

4

Мови розмітки, такі як XUL, дозволяють створювати графічний інтерфейс декларативно.


3

Для вирішення цього питання я опублікував свої думки щодо використання F #,

http://fadsworld.wordpress.com/2011/04/13/f-in-the-enterprise-i/ http://fadsworld.wordpress.com/2011/04/17/fin-the-enterprise-ii- 2 /

Я також планую зробити відеоурок, щоб закінчити серію та показати, як F # може зробити свій внесок у програмування UX.

Я говорю лише в контексті F # тут.

-Фахад


2

Всі ці інші відповіді будуються на функціональному програмуванні, але приймають багато власних дизайнерських рішень. Одна бібліотека, побудована в основному повністю з функцій та простих абстрактних типів даних gloss. Ось тип його playфункції від джерела

-- | Play a game in a window. Like `simulate`, but you manage your own input events.
play    :: Display              -- ^ Display mode.
        -> Color                -- ^ Background color.
        -> Int                  -- ^ Number of simulation steps to take for each second of real time.
        -> world                -- ^ The initial world.
        -> (world -> Picture)   -- ^ A function to convert the world a picture.
        -> (Event -> world -> world)    
                -- ^ A function to handle input events.
        -> (Float -> world -> world)
                -- ^ A function to step the world one iteration.
                --   It is passed the period of time (in seconds) needing to be advanced.
        -> IO ()

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


1

Найбільш очевидне нововведення, яке помічають люди, які не знайомі з Haskell, - це те, що існує розмежування між нечистим світом, який пов'язаний із спілкуванням із зовнішнім світом, та чистим світом обчислень та алгоритмів. Часто початківець питання : «Як я можу позбутися від IO, тобто, зверненого IO aв a?» Шлях до цього - використовувати монади (або інші абстракції) для написання коду, який виконує IO та ланцюгові ефекти. Цей код збирає дані із зовнішнього світу, створює його модель, проводить деякі обчислення, можливо, використовуючи чистий код, і виводить результат.

Що стосується вищезгаданої моделі, я не бачу нічого страшного поганого в маніпулюванні графічними інтерфейсами в IOмонаді. Найбільша проблема, що виникає в цьому стилі, полягає в тому, що модулі вже не є композиційними, тобто я втрачаю більшість своїх знань про глобальний порядок виконання заяв у своїй програмі. Щоб відновити його, я повинен застосувати аналогічні міркування, як і у паралельного, обов'язкового коду GUI. Тим часом, для нечистого, не-GUI-коду, порядок виконання очевидний через визначення оператора IOмонади >==(принаймні, поки існує лише один потік). Для чистого коду це взагалі не має значення, за винятком випадків, коли для підвищення продуктивності чи уникнення оцінок у результаті не виникає .

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

Популярна філософія функціонування асинхронного IO у функціональному відношенні називається функціональним реактивним програмуванням (FRP). Останнім часом він отримав велику тягу в нечистих, нефункціональних мовах завдяки таким бібліотекам, як ReactiveX , та рамкам, як Elm. Коротше кажучи, це як перегляд елементів графічного інтерфейсу та інших речей (наприклад, файлів, годинників, тривог, клавіатури, миші) як джерел подій, званих "спостережними", які випромінюють потоки подій. Ці події об'єднуються з допомогою знайомих операторів , таких , як map, foldl, zip, filter, concat, joinі т.д., для створення нових потоків. Це корисно, оскільки сам стан програми можна розглядати як scanl . map reactToEvents $ zipN <eventStreams>програму, де Nдорівнює кількості спостережуваних, які коли-небудь розглядалися програмою.

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


-22

Функціональне програмування, можливо, перейшло з того часу, коли я був в університеті, але, як я пам'ятаю, головним моментом функціональної системи програмування було зупинити програміста, створюючи будь-який «побічний ефект». Однак користувачі купують програмне забезпечення через створені побічні ефекти, наприклад оновлення інтерфейсу користувача.


25
Я думаю, ви неправильно зрозуміли суть: не те, що функціональне програмування не має зовнішнього впливу на світ - це зробило б усі програми абсолютно марними! Швидше за все, функціональне програмування дозволяє вам зробити карантин IO, щоб ви знали, які біти використовують, а які - не.
Тихон Єлвіс

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