Возможно ли функциональное программирование GUI? [закрыто]

405

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

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

У меня вопрос, возможно ли иметь функциональный подход к программированию GUI? Мне трудно представить, как это будет выглядеть на практике. Кто-нибудь знает какие-либо фреймворки, экспериментальные или иные, которые пробуют подобные вещи (или даже какие-либо фреймворки, разработанные с нуля для функционального языка)? Или это решение просто использовать гибридный подход, с ООП для частей GUI и FP для логики? (Я просто спрашиваю из любопытства - мне бы хотелось думать, что FP - это «будущее», но программирование в GUI кажется довольно большой дырой, которую нужно заполнить.)

shosti
источник
7
Посмотрев на GUI в Common Lisp и OCaml, я бы сказал, что, скорее всего, лень Haskell вызывает эту проблему.
new123456
5
@ new123456 Common Lisp, однако, не является функциональным языком, он работает с изменяемыми данными и включает в себя побочные эффекты
Electric Coffee
3
@ElectricCoffee Lisp - чрезвычайно гибкий язык, который можно использовать во многих различных стилях, и многие люди предпочитают использовать Lisp в функциональном стиле.
chrismamo1
8
Исходя из моего опыта (хотя я все еще пытаюсь верить в это и узнавать больше), FRP действительно достигает своего предела с программированием GUI; это красиво и элегантно в 80% случаев, но богатые виджеты требуют очень точного контроля их внутреннего состояния (например, поля со списком поиска и т. д.), а FRP только мешает. Императив не всегда злой; пытаться свести к минимуму количество императивного кода - это хорошо, но удалить его на 100%? Еще предстоит увидеть, как это работает для нетривиальной разработки пользовательского интерфейса.
AlexG
8
@ElectricCoffee "Common Lisp - это не функциональный язык". Лисп - мать всех функциональных языков. Вы имеете в виду, что Лисп не чист.
Джон Харроп

Ответы:

185

Похоже, что подход на Haskell состоит в том, чтобы просто обернуть обязательные наборы инструментов GUI (такие как GTK + или wxWidgets) и использовать блоки «do» для имитации императивного стиля.

На самом деле это не «подход Haskell» - это просто то, как вы привязываетесь к императивным GUI-инструментам наиболее напрямую - через императивный интерфейс. У Haskell просто довольно заметные привязки.

Существует несколько умеренно зрелых, или более экспериментальных, чисто функциональных / декларативных подходов к GUI, в основном на Haskell, и в основном с использованием функционально-реактивного программирования.

Вот некоторые примеры:

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

Дон Стюарт
источник
32
См. Статью Конала Эллиотта о фруктах для более подробного и подробного описания техники и решений: conal.net/papers/genuinely-functional-guis.pdf Я занимаюсь программированием GUI в этом стиле в течение нескольких месяцев. , Я ЛЮБЛЮ это, это - такое приятное облегчение от ага спагетти императивного программирования пользовательского интерфейса, которое, кажется, хуже в этом отношении чем большинство императивного программирования.
Луки
44
Я на 100% согласен с этим. Чтобы сделать это кристально ясным: причина, почему часто используются существующие наборы инструментов GUI, состоит в том, что они существуют. Причина, по которой интерфейсы к ним имеют тенденцию быть императивными и нечистыми, заключается в том, что наборы инструментов имеют тенденцию быть императивными и нечистыми. Причина, по которой наборы инструментов имеют тенденцию быть императивными и нечистыми, заключается в том, что операционные системы, от которых они зависят, имеют тенденцию быть императивными и нечистыми. Тем не менее, нет ничего принципиально требующего нечистоты: есть функциональные привязки для этих наборов инструментов, есть функциональные наборы инструментов, есть даже функциональные операционные системы.
Йорг Миттаг
16
Это всего лишь вопрос лени. (Плохой каламбур.)
Йорг Миттаг
10
Когда-нибудь весь дизайн GUI будет реализован через WYSIWYG с функциональной логикой. Это мой прогноз.
BlueRaja - Дэнни Пфлюгофт
24
Бумага, которую упоминает Луки, кажется, мертва. На сайте Конала Эллиота есть рабочая ссылка: conal.net/papers/genuinely-functional-guis.pdf
aganders3
74

У меня вопрос, возможно ли иметь функциональный подход к программированию GUI?

Ключевые слова, которые вы ищете, это «функционально-реактивное программирование» (FRP).

Конал Эллиотт и некоторые другие создали небольшую индустрию, пытаясь найти подходящую абстракцию для FRP. Существует несколько реализаций концепций FRP в Haskell.

Вы можете начать с самой последней статьи Конала «Push-Pull Functional Reactive Programming» , но есть несколько других (более старых) реализаций, некоторые из которых связаны с сайтом haskell.org . У Конала есть ловкость для охвата всего домена, и его статья может быть прочитана без ссылки на то, что было раньше.

Чтобы понять, как этот подход может быть использован для разработки графического интерфейса, вы можете взглянуть на Fudgets , которые, хотя в наши дни он становится немного длиннее, будучи разработанными в середине 90-х годов, действительно представляют собой надежный подход FRP дизайн GUI.

Эдвард Кметт
источник
Я хотел бы добавить рост использования «Reactive Extensions» (библиотеки FRP, но не FP), который изначально был написан для C #, а затем портирован на Java (RxJava) и JavaScript (RxJS) и различные языки. Посмотрите на реактив. На данный момент Angular 2 широко использует RxJS.
srph
63

Windows Presentation Foundation является доказательством того, что функциональный подход очень хорошо работает для программирования GUI. Он имеет много функциональных аспектов, и «хороший» код WPF (поиск шаблона MVVM) подчеркивает функциональный подход над императивным. Я мог бы смело утверждать, что WPF является самым успешным в мире функциональным инструментарием GUI :-)

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это просто функция обновления, которую вы передаете везде, где вам нужно обрабатывать обновления GUI - это исправление задержки.
Луаан
3
Стивен Пембертон написал 2 великолепных поста на F # и WPF, его « Мысли о разработке WPF с F #» к концу второго поста дополняют эту дискуссию. 2 других примера, которые также заинтриговали меня, - это использование функционального контроллера в 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 # выше.

Tomas Petricek
источник
7
+1 Это отражает наш опыт, написав несколько рабочих интерфейсов на F # с использованием библиотек комбинаторов и IObservable.
Джон Харроп
Изменился ли комментарий к FRP с момента введения реактивных расширений в библиотеку .NET?
Fsharp Пит
1
Вот некоторые исследования Arrowized FRP и того, как эффекты и мутации могут быть встроены в Arrowized FRP без нарушения законов: haskell.cs.yale.edu/wp-content/uploads/2015/10/… (кстати, большинство библиотек FRP используют монады или даже Аппликативные, так что это не правильно, что стрелки требуются).
Эрик Каплун
17

Независимо от того, используете ли вы гибридный функциональный / OO-язык, такой как F # или OCaml, или чисто функциональный язык, такой как Haskell, где побочные эффекты относятся к монаде IO, в большинстве случаев для управления графическим интерфейсом требуется масса работы. это больше похоже на «побочный эффект», чем на чисто функциональный алгоритм.

Тем не менее, было проведено действительно серьезное исследование функциональных графических интерфейсов . Есть даже некоторые (в основном) функциональные наборы инструментов, такие как Fudgets или FranTk .

sblom
источник
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 стало решающим фактором для правильного понимания этой функции. Есть несколько хороших статей о Ямпе. Я рекомендую The Yampa Arcade:

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

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

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

http://www.haskell.org/yampa (к сожалению, на данный момент не работает)

Борис Беркгаут
источник
1
Эта ссылка давно сломана. Попробуйте это Yampa
CoR
7

С тех пор, как этот вопрос был впервые задан, Элм сделал функциональное реактивное программирование немного более распространенным.

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

Это позволяет вам создавать полностью функциональные графические интерфейсы, где код, который вам нужно предоставить, состоит только из чистых функций. Лично мне было намного проще в этом разобраться, чем в различных GUI-средах Haskell.

saolof
источник
6

Доклад Эллиота о FRP можно найти здесь .

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

Но мое смутное понимание состоит в том, что функциональное программирование GUI - это декларативное определение функции, зависящей от времени, которая принимает (реальный) зависящий от времени пользовательский ввод и производит зависящий от времени вывод GUI.

Другими словами, эта функция определяется декларативно, как дифференциальное уравнение, а не алгоритмом, обязательно использующим изменяемое состояние.

Таким образом, в обычном FP ​​используются независимые от времени функции, в то время как в FRP используются зависящие от времени функции в качестве строительных блоков для описания программы.

Давайте подумаем о моделировании мяча на пружине, с которым пользователь может взаимодействовать. Положение шара - это графический вывод (на экране), пользователь, нажимающий на шар, - это нажатие клавиши (ввод).

Описание этой программы моделирования в FRP (согласно моему пониманию) выполняется с помощью одного дифференциального уравнения (декларативно): ускорение * масса = - растяжение пружины * постоянная пружины + сила, прилагаемая пользователем.

Вот видео на ELM, которое иллюстрирует эту точку зрения.

jhegedus
источник
5

По состоянию на 2016 год существует еще несколько относительно зрелых платформ FRP для Haskell, таких как Sodium и Reflex (но также и Netwire).

Книга Мэннинга по функциональному реактивному программированию демонстрирует Java-версию Sodium для рабочих примеров и иллюстрирует, как ведет себя кодовая база FRP GUI и масштабируется по сравнению с императивными и актерскими подходами.

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

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

Эрик Каплун
источник
Натрий был объявлен устаревшим в пользу реактивного банана в соответствии с readme от Sodium github
mac10688
4

Языки разметки, такие как XUL, позволяют создавать GUI декларативным способом.

StackedCrooked
источник
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 # здесь.

-Fahad

Fahad
источник
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 ()

Как вы можете видеть, он работает полностью, обеспечивая чистые функции простыми абстрактными типами, с которыми вам могут помочь другие библиотеки.

PyRulez
источник
1

Наиболее очевидным нововведением, замеченным людьми, плохо знакомыми с Хаскеллом, является то, что существует некое разделение между нечистым миром, связанным с общением с внешним миром, и чистым миром вычислений и алгоритмов. Частый вопрос для начинающих: «Как я могу избавиться IO, т.е. преобразовать IO aв a?» Путь к этому - использовать монады (или другие абстракции) для написания кода, который выполняет эффекты ввода-вывода и цепочки. Этот код собирает данные из внешнего мира, создает его модель, выполняет некоторые вычисления, возможно, используя чистый код, и выводит результат.

Что касается вышеприведенной модели, я не вижу ничего страшного в манипулировании GUI в IOмонаде. Самая большая проблема, которая возникает из-за этого стиля, состоит в том, что модули больше не могут быть компонованы, то есть я теряю большую часть моих знаний о глобальном порядке выполнения операторов в моей программе. Чтобы восстановить его, я должен применить те же рассуждения, что и в параллельном императивном коде GUI. Между тем, для нечистого, не GUI-кода порядок выполнения очевиден из-за определения оператора IOмонады >==(по крайней мере, пока существует только один поток). Для чистого кода это вообще не имеет значения, кроме как в угловых случаях для повышения производительности или во избежание оценок, приводящих к .

Самое большое философское различие между консольным и графическим вводом-выводом заключается в том, что программы, реализующие первый, обычно написаны в синхронном стиле. Это возможно, потому что есть (оставляя в стороне сигналы и другие дескрипторы открытого файла) только один источник событий: поток байтов, обычно называемый stdin. Хотя GUI по своей сути асинхронны и должны реагировать на события клавиатуры и щелчки мыши.

Популярная философия функционального асинхронного ввода-вывода называется функционально-реактивным программированием (FRP). В последнее время он получил широкое распространение в нечистых, нефункциональных языках благодаря библиотекам, таким как ReactiveX , и фреймворкам, таким как Elm. В двух словах, это похоже на просмотр элементов графического интерфейса пользователя и других вещей (таких как файлы, часы, сигналы тревоги, клавиатура, мышь) в качестве источников событий, называемых «наблюдаемыми», которые излучают потоки событий. Эти события объединяются с помощью знакомых операторов , таких , как map, foldl, zip, filter, concat, joinи т.д., для создания новых потоков. Это полезно, потому что само состояние программы можно рассматривать как scanl . map reactToEvents $ zipN <eventStreams>программу, где Nравно количеству наблюдаемых, когда-либо рассмотренных программой.

Работа с наблюдаемыми FRP позволяет восстановить возможность компоновки, поскольку события в потоке упорядочены во времени. Причина в том, что абстракция потока событий позволяет рассматривать все наблюдаемые как черные ящики. В конечном счете, объединение потоков событий с использованием операторов возвращает некоторое локальное упорядочение при выполнении. Это заставляет меня быть более честным в отношении того, на какие инварианты на самом деле опирается моя программа, подобно тому, как все функции в Haskell должны быть прозрачными по ссылкам: если я хочу получить данные из другой части моей программы, я должен быть явным объявление объявляет соответствующий тип для моих функций. (Монада ввода-вывода, являющаяся специфичным для домена языком для написания нечистого кода, эффективно обходит это)

MauganRa
источник
-22

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

Ян Рингроз
источник
25
Я думаю, вы не поняли суть: функциональное программирование не оказывает никакого внешнего воздействия на мир - это делает все программы совершенно бесполезными! Скорее, функциональное программирование позволяет изолировать IO, чтобы вы знали, какие биты его используют, а какие нет.
Тихон Джелвис
Whoooooooosh x20