什么是Scala中的“提升”?

8yoxcaq7  于 2023-05-07  发布在  Scala
关注(0)|答案(4)|浏览(114)

有时当我阅读Scala生态系统的文章时,我会读到“提升”/“提升”这个术语。不幸的是,它没有解释这到底是什么意思。我做了一些研究,似乎举重与函数值或类似的东西有关,但我没能找到一个文本,解释什么举重实际上是关于一个初学者友好的方式。
Lift框架的名称中包含了lifting,这也带来了额外的困惑,但这无助于回答这个问题。

什么是Scala中的“提升”?

slsn1g29

slsn1g291#

有几种用法:

PartialFunction

记住,PartialFunction[A, B]是为域A的某个子集定义的函数(由isDefinedAt方法指定)。您可以将PartialFunction[A, B]“提升”为Function[A, Option[B]]。也就是说,一个定义在Awhole 上的函数,但其值的类型为Option[B]
这是通过显式调用PartialFunction上的方法lift来完成的。

scala> val pf: PartialFunction[Int, Boolean] = { case i if i > 0 => i % 2 == 0}
pf: PartialFunction[Int,Boolean] = <function1>

scala> pf.lift
res1: Int => Option[Boolean] = <function1>

scala> res1(-1)
res2: Option[Boolean] = None

scala> res1(1)
res3: Option[Boolean] = Some(false)

方法

您可以将方法调用“提升”到函数中。这被称为 * eta-扩展 *(感谢@Ben James)。例如:

scala> def times2(i: Int) = i * 2
times2: (i: Int)Int

我们通过使用 * 下划线 * 将一个方法提升为一个函数

scala> val f = times2 _
f: Int => Int = <function1>

scala> f(4)
res0: Int = 8

注意方法和函数之间的根本区别。res0是一个示例(即它是(函数)类型(Int => Int)的 * 值 *)

函数

一个 functor(由scalaz定义)是一个“容器”(我非常松散地使用术语 *),F,如果我们有一个F[A]和一个函数A => B,那么我们可以得到一个F[B](例如,想想F = Listmap方法)
我们可以将此属性编码如下:

trait Functor[F[_]] { 
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

这同构于能够将函数A => B“提升”到函子的域中。即:

def lift[F[_]: Functor, A, B](f: A => B): F[A] => F[B]

也就是说,如果F是一个函子,我们有一个函数A => B,我们有一个函数F[A] => F[B]。您可以尝试实现lift方法-它非常简单。

Monad Transformers

正如 hcoopz 在下面所说的(我刚刚意识到这将使我免于编写大量不必要的代码),术语“lift”在Monad Transformers中也有含义。回想一下,monad转换器是一种将monad“堆叠”在彼此之上的方法(monad不组合)。
例如,假设你有一个返回IO[Stream[A]]的函数。这可以转换为monad转换器StreamT[IO, A]。现在,您可能希望“提升”一些其他值IO[B],也许它也是StreamT。你可以这样写:

StreamT.fromStream(iob map (b => Stream(b)))

或者这个:

iob.liftM[StreamT]

这就引出了一个问题:* 为什么要将IO[B]转换为StreamT[IO, B]?*.答案是“利用组合的可能性”。假设你有一个函数f: (A, B) => C

lazy val f: (A, B) => C = ???
val cs = 
  for {
    a <- as                //as is a StreamT[IO, A]
    b <- bs.liftM[StreamT] //bs was just an IO[B]
  }
  yield f(a, b)

cs.toStream //is a Stream[IO[C]], cs was a StreamT[IO, C]
bakd9h0s

bakd9h0s2#

注意任何扩展到PartialFunction[Int, A]的集合(如oxbow_lakes所指出的)都可以被提升;例如

Seq(1,2,3).lift
Int => Option[Int] = <function1>

其将部分函数转变为其中集合中未定义的值被Map到None上的总函数,

Seq(1,2,3).lift(2)
Option[Int] = Some(3)

Seq(1,2,3).lift(22)
Option[Int] = None

此外,委员会认为,

Seq(1,2,3).lift(2).getOrElse(-1)
Int = 3

Seq(1,2,3).lift(22).getOrElse(-1)
Int = -1

这显示了一种避免 index out of bounds 异常的简洁方法。

xfyts7mz

xfyts7mz3#

我在论文中遇到的另一个 lifting 的用法(不一定是与Scala相关的)是用f: List[A] -> List[B](或集合,多集合,...)从f: A -> B重载函数。这通常用于简化形式化,因为f应用于单个元素还是多个元素并不重要。
这种重载通常是声明性的,例如,

f: List[A] -> List[B]
f(xs) = f(xs(1)), f(xs(2)), ..., f(xs(n))

f: Set[A] -> Set[B]
f(xs) = \bigcup_{i = 1}^n f(xs(i))

或者强制地,例如,

f: List[A] -> List[B]
f(xs) = xs map f
dffbzjpn

dffbzjpn4#

还有一种是 unlifting,这是提升的逆过程。
如果提升定义为
将部分函数PartialFunction[A, B]转化为总函数A => Option[B]
则解除是
将全函数A => Option[B]转换为部分函数PartialFunction[A, B]
Scala标准库将Function.unlift定义为

def unlift[T, R](f: (T) ⇒ Option[R]): PartialFunction[T, R]

例如,play-json库提供了unlift来帮助构建JSON序列化器:

import play.api.libs.json._
import play.api.libs.functional.syntax._

case class Location(lat: Double, long: Double)

implicit val locationWrites: Writes[Location] = (
  (JsPath \ "lat").write[Double] and
  (JsPath \ "long").write[Double]
)(unlift(Location.unapply))

相关问题