Реализация состояний в функциональном программировании

100
11

В настоящее время я пишу приложение для Android-плееров с помощью Scala. Я выбрал Scala для его функциональных возможностей программирования, и я хочу сделать код максимально совместимым с FP.

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

Первый - это случай списка воспроизведения. Музыкальный проигрыватель читает песню в середине списка воспроизведения. Это может быть представлено списком песен и курсором, который указывает текущую воспроизводимую песню. Но когда эта песня заканчивается, игрок должен сыграть следующий, следовательно, изменить значение курсора.

Такая же проблема возникает и с самим списком воспроизведения: пользователь должен иметь возможность изменять (добавлять или удалять песни) список воспроизведения. Если сам плейлист является неизменным, каждый раз, когда пользователь добавляет или подавляет песню, создается новый плейлист. Но этот список воспроизведения должен быть затронут переменной, которая затем должна быть изменчивой.

Везде, где я смотрю в этом приложении, я вижу состояния - игрок остановился или нет? Какова текущая песня, текущий плейлист? Каково текущее состояние настроек? И т.д. - и я не знаю, как решить это по чисто функциональному программированию, т.е. С неизменяемыми переменными.

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

спросил(а) 2017-01-14T14:58:00+03:00 3 года, 9 месяцев назад
1
Решение
58

Вы говорите о пользовательском интерфейсе. Он по своей сути является состоятельным. Вы не можете и не должны работать с ним без состояний. Существует только один правильный способ: разделить код без состояний из кода с состояниями.

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

Будьте осторожны, многие так называемые реактивные технологии программирования в сети не такие уж и только заявляют, что они реактивны. Например, java RX является абсолютным недействительным и не имеет двух очень важных функций. (скрытие слушателей и поддержка одновременности)

Есть очень хорошая книга по этому вопросу. Он также может быть найден в сети в некоторых действиях. Авторы предоставляют базовую библиотеку opensource и быструю библиотеку поддержки FRP, которая может быть использована в качестве шаблона для создания ваших собственных классов FRP для ваших нужд.

ответил(а) 2017-08-10T13:03:00+03:00 3 года, 2 месяца назад
58

Я написал несколько библиотек, которые пытались решить эту проблему, результат был довольно уродливым, ИМО.

В основном, превратили Activity, Fragment и т.д. В чистые функции, которые приняли состояние и возвращенное состояние.


Это в сочетании с модами IO сделало интерфейс несколько чистым. Ниже приведен пример этого примера (источник PureActivity можно найти по адресу https://github.com/pfn/iota-pure), "состояние" в этом случае - "Option [Process]", когда Process присутствует, когда logcat бегом и пустым, когда это не так. Нет vars:

class LogcatActivity extends AppCompatActivity with PureActivity[Option[Process]] {
val LOG_LINE = """^([A-Z])/(.+?)\( *(\d+)\): (.*?)$""".r
val buffersize = 1024
lazy val toolbar = newToolbar
lazy val recycler = {
val r = new RecyclerView(this)
r.setLayoutManager(new LinearLayoutManager(this))
r.setAdapter(Adapter)
r
}
lazy val layout = l[LinearLayout](
toolbar.! >>= lp(MATCH_PARENT, WRAP_CONTENT),
recycler.! >>= lp(MATCH_PARENT, 0, 1)
) >>= vertical

override def initialState(b: Option[Bundle]) = None

override def applyState[T](s: ActivityState[T]) = s match {
case OnPreCreate(_) => s(IO(
setTheme(if (Settings.get(Settings.DAYNIGHT_MODE)) R.style.SetupTheme_Light else R.style.SetupTheme_Dark)
))
case OnCreate(_) => s(IO {
toolbar.setTitle("Logcat")
toolbar.setNavigationIcon(resolveAttr(R.attr.qicrCloseIcon, _.resourceId))
toolbar.navigationOnClick0(finish())
setContentView(layout.perform())
})
case OnStart(_) => s.applyState(IO {
var buffering = true
val logcat = "logcat" :: "-v" :: "brief" :: Nil
val lineLogger = new ProcessLogger {
override def out(s: => String) = addLine(s)
override def buffer[X](f: => X) = f
override def err(s: => String) = addLine(s)

def addLine(line: String) = line match {
case LOG_LINE(level, tag, pid, msg) =>
if (tag != "ResourceType") UiBus.run {
val c = Adapter.getItemCount // store in case at max items already
Adapter.buffer += LogEntry(tag, level, msg)
Adapter.notifyItemInserted(math.min(buffersize, c + 1))
if (!buffering)
recycler.smoothScrollToPosition(Adapter.getItemCount)
}
case _ =>
}
}
Future {
Thread.sleep(500)
buffering = false
} onSuccessMain { case _ =>
recycler.scrollToPosition(Adapter.getItemCount - 1)
}
logcat.run(lineLogger).?
})
case OnStop(proc) => s.applyState(IO {
proc.foreach(_.destroy())
None
})
case x => defaultApplyState(x)
}

case class LogEntry(tag: String, level: String, msg: String)
case class LogcatHolder(view: TextView) extends RecyclerView.ViewHolder(view) {
def bind(e: LogEntry): Unit = view.setText(" %1 %2: %3" formatSpans (
textColor(MessageAdapter.nickColor(e.level), e.level),
textColor(MessageAdapter.nickColor(e.tag), e.tag), e.msg))
}
object Adapter extends RecyclerView.Adapter[LogcatHolder] {
val buffer = RingBuffer[LogEntry](buffersize)
override def getItemCount = buffer.size
override def onBindViewHolder(vh: LogcatHolder, i: Int) = vh.bind(buffer(i))

override def onCreateViewHolder(viewGroup: ViewGroup, i: Int) = {
val tv = new TextView(LogcatActivity.this)
tv.setTypeface(Typeface.MONOSPACE)
LogcatHolder(tv)
}
}
}

ответил(а) 2017-02-02T00:38:00+03:00 3 года, 8 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема