2017-01-14 5 views
0

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

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

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

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

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

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

+0

http://typelevel.org/cats/datatypes/state.html – Reactormonk

+1

«FP-совместимый» - нет такой вещи – naomik

ответ

1

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

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

Это в сочетании с 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) 
    } 
    } 
} 
+0

Это также плохо отражается в intellij из-за https: //youtrack.jetbrains.com/issue/SCL-3170 – pfn

+0

Очень интересный ответ. Большое спасибо! – Augier

1

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

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

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

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

Смежные вопросы