在Scala函数式编程中,是否有一种惯用的方法来映射状态?

在Scala函数式编程中,是否有一种惯用的方法来映射状态?,scala,functional-programming,Scala,Functional Programming,传统的映射函数具有签名A=>B,用于将F[A]转换为F[B],例如将List[A]转换为List[B] 但是,如果映射函数应该携带一些计算B所需的状态,那么该怎么办呢 比如说,映射函数如下所示:(A,S)=>(B,S),其中S是状态的类型。对于每个A,先前返回的S被传递到映射函数中,而最初为状态提供一个zero元素。然后,映射函数返回一个新状态(以及结果),然后再将其与下一个值一起传递,以此类推 当然,.map的功能还不够强大,因此解决方案必须基于另一个操作符 为了便于说明,举一个具体的例子,假

传统的映射函数具有签名
A=>B
,用于将
F[A]
转换为
F[B]
,例如将
List[A]
转换为
List[B]

但是,如果映射函数应该携带一些计算
B
所需的状态,那么该怎么办呢

比如说,映射函数如下所示:
(A,S)=>(B,S)
,其中S是
状态的类型。对于每个
A
,先前返回的
S
被传递到映射函数中,而最初为状态提供一个
zero
元素。然后,映射函数返回一个新状态(以及结果),然后再将其与下一个值一起传递,以此类推

当然,
.map
的功能还不够强大,因此解决方案必须基于另一个操作符

为了便于说明,举一个具体的例子,假设我有一个
Int
序列,我想计算每个
Int
与该序列中前一个
Int
的差值。如上所述的映射函数的实现如下所示:

def映射(currentElement:Int,previousElement:Option[Int]):(Option[Int],Option[Int])={
(previousElement.map(currentElement-389;),Some(currentElement))
}
previousElement
的初始
zero
值将是
None
,在第一个元素之后,它将始终是
Some(currentElement)
。每次迭代的结果将是当前值减去最后一个值的
Some
,但第一个元素除外,它是
None

例如,如何使用
映射
功能将
List(1,4,3)
转换为
List(无、部分(3)、部分(-1))


(请注意,整数减法示例仅用于说明目的,问题的焦点是所述操作类型的通用解决方案。)

有一些库可用于执行“mtl样式”状态传递,这正是您所描述的。(检查此代码段后的类型签名)

导入猫_
导入cats.data_
进口猫_
//给定一个元素和状态,计算下一个状态和返回值
def modifyEntry(currentElement:Int):状态[选项[Int],选项[Int]]=for{
先前的元素(s,())
// ...
}
经过一些修改后的功能:

抽象私有[data]类状态函数{
// ...
def get[S]:State[S,S]=???//其他一些代码,如State(S=>(S,S))
def集合[S](S:S):状态[S,单位]=状态(=>(S,())
}
对于CAT,请使用其他一些示例查看优秀文档:

对于scalaz来说,这里有一个很好的谈话,概述了Scala和scalaz中的“mtl风格”,不过要注意

对于其中任何一种,都要注意单变压器的缺点(不是mtl样式/特征,请参见第二部分):

您要寻找的“运算符”是
fold

List(1, 4, 3).foldLeft(None: Option[Int], List[Option[Int]]())
   ((acc, curr) => (Some(curr), acc._1.map(_ - curr) :: acc._2))
    ._2
    .reverse
另一种思考方法是使用
zip

val xs = List(1, 4, 3)

val result = None :: xs.zip(xs.drop(1)).map(currAndNext => Some(currAndNext._2 - currAndNext._1))

Scala 2.13.x
unfold()
方法维护的状态与您的示例类似

List.unfold((Option.empty[Int], List(1, 4, 3))){
  case (prev, hd::tl) => Some((prev.map(hd.-), (Some(hd),tl)))
  case (prev, Nil)    => None
}
//res0: List[Option[Int]] = List(None, Some(3), Some(-1))

这可以在
LazyList
Iterator
上找到,因此它可以用来创建一个伪无限流。

这很好地回答了这个示例,但它只适用于有限的数据结构。如果F[A]是一个无限流(这就是我需要的)@ig dev将取决于您对该流使用什么。我相信fs2会与您合作this@sinanspd是 啊我不这么认为,因为折叠不会返回中间结果。
fs2
有这样的机制吗?@ig dev我已经有一段时间没有研究过fs2的内部结构了,所以要仔细检查一下,但据我所知,fs2折叠被定义为拉动和折叠。因此,它从流中提取新数据,然后折叠。也许
scanLeft
就是您想要的。我看到了类型签名的相似性,我正在研究这一点,但到目前为止,实际应用对我来说还不清楚,如何从
列表(1,4,3)
列表(无,一些(3),一些(-1))
使用
StateFunctions
状态将由normal map(和flatMap)保留,因此如果您想转换列表并保留状态,请使用它。您好@ig dev,我修改了答案来说明您的示例,很抱歉延迟。另外@ig dev,下面是您评论的生成正确输出的示例: