什么是Scala中的"提升"?

use*_*215 245 functional-programming scala function lifting

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

通过Lift框架存在额外的混乱,其名称已经提升,但它无助于回答这个问题.

什么是Scala中的"提升"?

oxb*_*kes 284

有一些用法:

PartialFunction

记住a PartialFunction[A, B]是为域的某个子集定义的函数A(由isDefinedAt方法指定).你可以"升" PartialFunction[A, B]成一个Function[A, Option[B]].也就是说,过定义的功能A,但其值类型Option[B]

这是通过显式调用方法lift来完成的PartialFunction.

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)
Run Code Online (Sandbox Code Playgroud)

方法

您可以将方法调用"提升"到函数中.这称为eta-expansion(感谢Ben James).例如:

scala> def times2(i: Int) = i * 2
times2: (i: Int)Int
Run Code Online (Sandbox Code Playgroud)

我们通过应用下划线将方法提升为函数

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

scala> f(4)
res0: Int = 8
Run Code Online (Sandbox Code Playgroud)

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

函子

一个仿函数(如定义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]
}
Run Code Online (Sandbox Code Playgroud)

这与能够将函数"提升" A => B到仿函数的域中是同构的.那是:

def lift[F[_]: Functor, A, B](f: A => B): F[A] => F[B]
Run Code Online (Sandbox Code Playgroud)

也就是说,如果F是一个仿函数,并且我们有一个函数A => B,那么我们就有了一个函数F[A] => F[B].您可以尝试实现该lift方法 - 这非常简单.

Monad变形金刚

正如hcoopz在下面所说(我刚刚意识到这会让我免于编写大量不必要的代码),"升力"一词在Monad变形金刚中也有意义.回想一下,monad变换器是一种将monad"堆叠"在一起的方式(monad不构成).

例如,假设您有一个返回的函数IO[Stream[A]].这可以转换为monad变压器StreamT[IO, A].现在你可能希望"提升"一些其他价值,IO[B]也许它也是一个StreamT.你可以这样写:

StreamT.fromStream(iob map (b => Stream(b)))
Run Code Online (Sandbox Code Playgroud)

或这个:

iob.liftM[StreamT]
Run Code Online (Sandbox Code Playgroud)

这引出了一个问题:为什么我要将其转换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]
Run Code Online (Sandbox Code Playgroud)

  • 值得一提的是,"将方法提升到函数"通常被称为*eta-expansion*. (11认同)
  • 进一步研究**scalaz**,_ ___也与_monad变形金刚有关.如果我有`M`的`MonadTrans`实例`T`和`N`的`Monad`实例,那么`T.liftM`可以用来将_N [A]类型的值_lift赋值给一个值类型为"M [N,A]`. (7认同)
  • 在**方法**部分_... res0是(函数)类型的实例(即它是一个值)(Int => Int)... _不应该`f`是一个实例,而不是` res0`? (2认同)

Mal*_*off 21

另一种用法提升,我已经在报纸遇到(不一定斯卡拉相关的)从重载函数f: A -> Bf: List[A] -> List[B](或设置,多集,...).这通常用于简化形式化,因为无论f是应用于单个元素还是应用于多个元素都无关紧要.

这种重载通常是以声明方式完成的,例如,

f: List[A] -> List[B]
f(xs) = f(xs(1)), f(xs(2)), ..., f(xs(n))
Run Code Online (Sandbox Code Playgroud)

要么

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

或者必要的,例如,

f: List[A] -> List[B]
f(xs) = xs map f
Run Code Online (Sandbox Code Playgroud)

  • @BenJames确实如此.为了我的辩护:当我开始写我的时,oxbow_lakes的答案还没有. (6认同)
  • 这是oxbow_lakes描述的"升级为仿函数". (5认同)

elm*_*elm 20

注意任何延伸的集合PartialFunction[Int, A](如oxbow_lakes所指出的)可能被解除; 因此,例如

Seq(1,2,3).lift
Int => Option[Int] = <function1>
Run Code Online (Sandbox Code Playgroud)

将部分函数转换为总函数,其中集合中未定义的值映射到None,

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

Seq(1,2,3).lift(22)
Option[Int] = None
Run Code Online (Sandbox Code Playgroud)

此外,

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

Seq(1,2,3).lift(22).getOrElse(-1)
Int = -1
Run Code Online (Sandbox Code Playgroud)

这显示了一种避免索引超出范围异常的简洁方法.


Mar*_*lic 6

还有提升,这是提升的逆过程.

如果提升定义为

将部分功能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]
Run Code Online (Sandbox Code Playgroud)

例如,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))
Run Code Online (Sandbox Code Playgroud)