在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.xunfold()
方法维护的状态与您的示例类似
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,下面是您评论的生成正确输出的示例: