"lifting"在Scala中是什么意思?

14 浏览
0 Comments

"lifting"在Scala中是什么意思?

有时在Scala生态系统中阅读文章时,会看到术语“lifting” / “lifted”。不幸的是,它没有解释那究竟是什么意思。我做了一些研究,似乎lifting与函数值或类似于此的东西有关,但我没有找到一个以初学者友好的方式解释lifting是关于什么的文本。

由于Lift框架中有lifting,这使得情况更加混乱,但它并不能回答这个问题。

在Scala中,“lifting”是什么?

admin 更改状态以发布 2023年5月22日
0
0 Comments

注意到任何扩展PartialFunction[Int, A]的集合都可以被提升;因此例如

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

可以将一个局部函数转换为总函数,其中在集合中没有定义的值映射到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

这表明了一种简洁的方法来避免索引超出边界异常。

0
0 Comments

有几种用法:

PartialFunction

记住,PartialFunction[A, B] 是一种针对域 A 的子集定义的函数(由 isDefinedAt 方法指定)。您可以将一个 PartialFunction[A,B] 提升为一个 Function[A,Option[B]]。也就是说,它是在整个域 A 上定义的函数,但其值的类型为 Option[B]

这是通过对 PartialFunction 进行显式调用 lift 方法实现的。

scala> val pf: PartialFunction[Int, Boolean] = { case i if i > 0 => i % 2 == 0}
pf: PartialFunction[Int,Boolean] = 
scala> pf.lift
res1: Int => Option[Boolean] = 
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 = 
scala> f(4)
res0: Int = 8

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

Functors

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 提升到 Functor 的域中。也就是说:

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

也就是说,在 functor F 存在,并且我们有函数 A => B,我们有函数 F[A] => F[B]。您可以尝试实现 lift 方法 - 它非常简单。

Monad Transformers

正如下面的hcoopz所说(我刚意识到这将节省我写大量不必要的代码),“lift”这个术语在Monad Transformers中也有意义。回想一下,monad transformers是一种在彼此之上“堆叠”monad的方法(monad不能组合)。

例如,假设你有一个返回IO[Stream[A]]的函数。这可以转换为monad transformerStreamT[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]

0