Nâng hạ thế nào trong Scala là gì?


252

Thỉnh thoảng khi tôi đọc các bài báo trong hệ sinh thái Scala, tôi đọc thuật ngữ "nâng" / "nâng". Thật không may, nó không được giải thích chính xác điều đó có nghĩa là gì. Tôi đã thực hiện một số nghiên cứu và dường như việc nâng có liên quan đến các giá trị chức năng hoặc một cái gì đó tương tự, nhưng tôi không thể tìm thấy một văn bản giải thích việc nâng thực sự là gì theo cách thân thiện với người mới bắt đầu.

Có thêm sự nhầm lẫn thông qua khung Nâng có tên nâng, nhưng nó không giúp trả lời câu hỏi.

"Nâng" trong Scala là gì?

Câu trả lời:


290

Có một vài cách sử dụng:

Chức năng một phần

Hãy nhớ a PartialFunction[A, B]là một hàm được định nghĩa cho một số tập hợp con của miền A(như được chỉ định bởi isDefinedAtphương thức). Bạn có thể "nâng" a PartialFunction[A, B]thành a Function[A, Option[B]]. Đó là, một chức năng được xác định trong toàn bộ của Anhưng có giá trị là kiểuOption[B]

Điều này được thực hiện bằng cách gọi rõ ràng của phương thức lifttrên 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)

Phương pháp

Bạn có thể "nâng" một lời gọi phương thức thành một hàm. Điều này được gọi là mở rộng eta (cảm ơn Ben James vì ​​điều này). Ví dụ:

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

Chúng tôi nâng một phương thức thành một hàm bằng cách áp dụng dấu gạch dưới

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

scala> f(4)
res0: Int = 8

Lưu ý sự khác biệt cơ bản giữa các phương thức và chức năng. res0là một thể hiện (tức là nó là một giá trị ) của kiểu (hàm)(Int => Int)

Chức năng

Một functor (như được định nghĩa bởi scalaz ) là một số "container" (tôi sử dụng thuật ngữ cực kỳ lỏng lẻo), Fnhư vậy, nếu chúng ta có F[A]một hàm và một hàm A => B, thì chúng ta có thể chạm tay vào F[B](ví dụ, F = Listmapphương thức )

Chúng tôi có thể mã hóa tài sản này như sau:

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

Đây là đẳng cấu để có thể "nâng" hàm A => Bvào miền của functor. Đó là:

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

Đó là, nếu Flà một functor, và chúng ta có một chức năng A => B, chúng ta có một chức năng F[A] => F[B]. Bạn có thể thử và thực hiện liftphương pháp - nó khá tầm thường.

Máy biến áp đơn nguyên

Như hcoopz nói dưới đây (và tôi vừa nhận ra rằng điều này sẽ giúp tôi tiết kiệm được việc viết một tấn mã không cần thiết), thuật ngữ "thang máy" cũng có ý nghĩa trong Monad Transformers . Hãy nhớ lại rằng một máy biến áp đơn nguyên là một cách "xếp chồng" các đơn nguyên lên nhau (các đơn nguyên không sáng tác).

Vì vậy, ví dụ, giả sử bạn có một hàm trả về một IO[Stream[A]]. Điều này có thể được chuyển đổi để biến áp đơn nguyên StreamT[IO, A]. Bây giờ bạn có thể muốn "nâng" một số giá trị khác IO[B]có lẽ nó cũng là một StreamT. Bạn có thể viết cái này:

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

Hoặc này:

iob.liftM[StreamT]

Điều này đặt ra câu hỏi: tại sao tôi muốn chuyển đổi IO[B]thành một StreamT[IO, B]? . Câu trả lời sẽ là "tận dụng các khả năng sáng tác". Giả sử bạn có một chức năngf: (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]

12
Điều đáng nói là "nâng một phương thức lên một hàm" thường được gọi là mở rộng eta .
Ben James

7
Đi sâu hơn vào scalaz , nâng cũng xuất hiện liên quan đến máy biến áp đơn nguyên . Nếu tôi có một MonadTransthể hiện Tcho Mvà một Monadthể hiện cho N, thì T.liftMcó thể được sử dụng để nâng một giá trị của loại N[A]thành một giá trị của loại M[N, A].
846846846

Cảm ơn Ben, hcoopz. Tôi đã sửa đổi câu trả lời
oxbow_lakes

Hoàn hảo! Chỉ một lý do nữa để nói: Scala - tốt nhất. Mà có thể được nâng lên Martin Oderky & Co - tốt nhất. Tôi thậm chí sẽ sử dụng liftMcho điều đó, nhưng không quản lý hiểu làm thế nào để làm điều đó đúng. Các bạn, bạn là đá!
Dmitry Bespalov

3
Trong phần Phương thức ... res0 là một thể hiện (tức là nó là một giá trị) của loại (hàm) (Int => Int) ... Không nên flà một thể hiện, phải không res0?
srzhio

21

Một cách sử dụng các nâng mà tôi đã đi qua trong các giấy tờ (những người không nhất thiết phải liên quan đến Scala) đang quá tải hàm từ f: A -> Bvới f: List[A] -> List[B](hoặc bộ, multisets, ...). Điều này thường được sử dụng để đơn giản hóa việc chính thức hóa vì nó không thành vấn đềf được áp dụng cho một yếu tố riêng lẻ hay cho nhiều yếu tố.

Loại quá tải này thường được thực hiện khai báo, ví dụ,

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

hoặc là

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

hoặc bắt buộc, ví dụ,

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

5
Đây là "nâng thành một functor" mà oxbow_lakes mô tả.
Ben James

6
@BenJames Đúng thật. Để bảo vệ tôi: câu trả lời của oxbow_lakes chưa có khi tôi bắt đầu viết của tôi.
Malte Schwerhoff

20

Lưu ý mọi bộ sưu tập kéo dài PartialFunction[Int, A](như được chỉ ra bởi oxbow_lakes) có thể được gỡ bỏ; ví dụ như vậy

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

mà biến một phần hàm thành một hàm tổng trong đó các giá trị không được xác định trong bộ sưu tập được ánh xạ lên None,

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

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

Hơn thế nữa,

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

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

Điều này cho thấy một cách tiếp cận gọn gàng để tránh chỉ số ra khỏi giới hạn ngoại lệ.


6

Ngoài ra còn có unlifting , đó là quá trình nghịch đảo để nâng.

Nếu nâng được định nghĩa là

biến một phần hàm PartialFunction[A, B]thành một hàm tổngA => Option[B]

sau đó mở ra là

biến một hàm tổng A => Option[B]thành hàm một phần PartialFunction[A, B]

Thư viện tiêu chuẩn Scala định nghĩa Function.unlift

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

Ví dụ, thư viện play-json cung cấp khả năng mở rộng để giúp xây dựng các bộ nối tiếp 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))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.