ChatGPT解决这个技术问题 Extra ChatGPT

Scala 中的“提升”是什么?

有时当我阅读 Scala 生态系统中的文章时,我会阅读术语“提升”/“提升”。不幸的是,没有解释这究竟意味着什么。我做了一些研究,似乎提升与功能价值或类似的东西有关,但我无法找到以初学者友好的方式解释提升实际上是什么的文本。

名称中有提升的 Lift 框架存在额外的混淆,但它无助于回答问题。

Scala 中的“提升”是什么?


o
oxbow_lakes

有几种用法:

偏函数

请记住,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] = <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-expansion(感谢 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)instance(即,它是

函子

functor(由 scalaz 定义)是某种“容器”(我使用术语 extremely),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 方法 - 它非常简单。

单子变形金刚

正如 hcoopz 在下面所说的(我刚刚意识到这可以让我免于编写大量不必要的代码),术语“lift”在 Monad Transformers 中也有其含义。回想一下,单子转换器是一种将单子“堆叠”在彼此之上的方式(单子不组合)。

例如,假设您有一个返回 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]

值得一提的是,“将方法提升为函数”通常被称为 eta 扩展。
进一步研究 scalazlifting 也与 monad transformers 相关。如果我有一个用于 MMonadTrans 实例 T 和一个用于 NMonad 实例,那么 T.liftM 可用于提升类型为 N[A] 的值M[N, A] 类型的值。
谢谢本,hcoopz。我修改了答案
完美的!还有一个理由说:Scala - 最好的。这可以举给 Martin Odersky &合作 - 最好的。我什至会为此使用 liftM,但不知道如何正确地做到这一点。伙计们,你是摇滚!
Methods 部分中,...res0 是(函数)类型 (Int => Int)... 的一个实例(即,它是一个值)... f 是一个实例,而不是 res0
e
elm

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

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

它将部分函数转换为总函数,其中未在集合中定义的值映射到 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

这显示了一种避免索引越界异常的巧妙方法。


M
Malte Schwerhoff

我在论文(不一定是与 Scala 相关的)中遇到的 lifting 的另一种用法是使用 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

这就是 oxbow_lakes 描述的“提升为函子”。
@BenJames 确实如此。为我辩护:当我开始写我的答案时,oxbow_lakes 的答案还没有出现。
M
Mario Galic

还有解除,这是解除的逆过程。

如果起重定义为

将部分函数 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 serialisers

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))