it-swarm.com.ru

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

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

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

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

389
shosti

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

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

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

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

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

178
Don Stewart

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

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

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

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

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

70
Edward KMETT

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 иногда чувствует себя немного странно и неясно, используете ли вы функционал точка зрения).

61
Tomas Petricek

Я бы на самом деле сказал, что функциональное программирование (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 # выше.

27
Tomas Petricek

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

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

17
sblom

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

Использование F # для разработки WPF было бы очень интересной парадигмой GUI ...

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

15
Kevin Won

Одна из идей, открывающих разум в основе функционально-реактивного программирования, состоит в том, чтобы иметь функцию обработки событий, производящую ОБА реакцию на события И следующую функцию обработки событий. Таким образом, развивающаяся система представляется в виде последовательности функций обработки событий.

Для меня изучение 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 (к сожалению, в данный момент не работает)

12
Boris Berkgaut

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

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

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

6
saolof

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

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

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

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

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

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

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

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

6
jhegedus

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

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

Также есть недавняя статья о Arrowized FRP и перспективе включения побочных эффектов, IO и ​​мутации в законопослушных, чистых настройках FRP: http://haskell.cs.yale.edu/ WP-содержание/добавление/2015/10/DWC-Yale-форматированный-dissertation.pdf .

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

5
Erik Allik

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

4
StackedCrooked

Чтобы решить эту проблему, я написал несколько своих соображений по поводу использования F #,

http://fadsworld.wordpress.com/2011/04/13/f-in-the-enterprise-i/ http://fadsworld.wordpress.com/2011/04/17/fin -The-предприятие-II-2 /

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

Я говорю только в контексте F # здесь.

-Fahad

4
Fahad

Все эти другие ответы основаны на функциональном программировании, но принимают множество собственных дизайнерских решений. Одна библиотека, которая построена в основном полностью из функций и простых абстрактных типов данных: 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 ()

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

2
PyRulez

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

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

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

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

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

1
MauganRa