Functional programming 函数式编程中的实现状态
我目前正在使用Scala编写一个Android音乐播放器应用程序。我选择Scala是因为它的函数式编程功能,我希望使代码尽可能符合FP 由于FP意味着不变性,代码不应该携带任何状态,变量应该是不可变的。但我面临着一些复杂的用例,我不知道如何用纯函数编程的方式来解决 第一个是播放列表案例。音乐播放器正在播放列表中的一首歌曲。这可以用歌曲列表和指示当前播放歌曲的光标来表示。但当这首歌结束时,播放器必须播放下一首歌,因此,更改光标的值 播放列表本身也存在同样的问题:用户必须能够更改(添加或抑制歌曲)播放列表。如果播放列表本身是不可变的,则每当用户添加或抑制歌曲时,都会生成一个新的播放列表。但该播放列表必须受一个变量的影响,该变量必须是可变的 在这个应用程序中,我看到的每一处状态——播放器是否暂停?什么是当前歌曲,当前播放列表?设置的当前状态是什么?等等——我不知道如何用纯函数编程的方式来解决这个问题,也就是说,使用不可变变量Functional programming 函数式编程中的实现状态,functional-programming,Functional Programming,我目前正在使用Scala编写一个Android音乐播放器应用程序。我选择Scala是因为它的函数式编程功能,我希望使代码尽可能符合FP 由于FP意味着不变性,代码不应该携带任何状态,变量应该是不可变的。但我面临着一些复杂的用例,我不知道如何用纯函数编程的方式来解决 第一个是播放列表案例。音乐播放器正在播放列表中的一首歌曲。这可以用歌曲列表和指示当前播放歌曲的光标来表示。但当这首歌结束时,播放器必须播放下一首歌,因此,更改光标的值 播放列表本身也存在同样的问题:用户必须能够更改(添加或抑制歌曲)播
由于这些用例看起来相当标准,我想有一些设计模式可以解决它们(比如monad),但我不知道该去哪里寻找。我写了一些库试图解决这个问题,结果相当糟糕 基本上,将活动、片段等转换为接受状态和返回状态的纯函数 这与IO monads结合在一起,使得界面有点纯净。下面是一个例子(PureActivity的源可以在中找到),在这种情况下的“状态”是“Option[Process]”,当logcat运行时进程存在,当logcat不运行时进程为空。无变量:
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)
}
}
}
我写了一些库试图解决这个问题,结果相当糟糕 基本上,将活动、片段等转换为接受状态和返回状态的纯函数 这与IO monads结合在一起,使得界面有点纯净。下面是一个例子(PureActivity的源可以在中找到),在这种情况下的“状态”是“Option[Process]”,当logcat运行时进程存在,当logcat不运行时进程为空。无变量:
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)
}
}
}
你说的是用户界面。它本质上是有状态的。如果没有国家,你不能也不能使用它。只有一种正确的方法:将没有状态的代码与有状态的代码分开 最好的概念是FRP-。它将功能部件和具有可变状态内容的不可变框分开,并通过事件将它们连接起来 请注意,网络上许多所谓的反应式编程技术实际上并不是这样的,只是宣称是反应式的。例如,java RX是绝对无效的,并且缺少两个非常重要的特性。(隐藏侦听器和同时性支持)
关于这个问题有一个很好的讨论。它也可以在网上的一些动作中找到。作者给出了开源基础库和swift FRP支持库,它们可以作为一种模式,用于根据您的需要创建自己的FRP类 你说的是用户界面。它本质上是有状态的。如果没有国家,你不能也不能使用它。只有一种正确的方法:将没有状态的代码与有状态的代码分开 最好的概念是FRP-。它将功能部件和具有可变状态内容的不可变框分开,并通过事件将它们连接起来 请注意,网络上许多所谓的反应式编程技术实际上并不是这样的,只是宣称是反应式的。例如,java RX是绝对无效的,并且缺少两个非常重要的特性。(隐藏侦听器和同时性支持)
关于这个问题有一个很好的讨论。它也可以在网上的一些动作中找到。作者给出了开源基础库和swift FRP支持库,它们可以作为一种模式,用于根据您的需要创建自己的FRP类 “FP兼容”-没有“FP兼容”这样的东西-没有这样的东西这在intellij中也很糟糕,因为答案非常复杂。谢谢!这在intellij中也很突出,因为答案非常复杂。谢谢!