"การยก" ใน Scala คืออะไร


252

บางครั้งเมื่อฉันอ่านบทความในระบบนิเวศสกาล่าฉันอ่านคำว่า "การยก" / "ยกขึ้น" น่าเสียดายที่มันไม่ได้อธิบายว่ามันหมายถึงอะไรกันแน่ ฉันได้ทำการวิจัยและดูเหมือนว่าการยกมีบางอย่างเกี่ยวกับค่านิยมในการใช้งานหรืออะไรทำนองนั้น แต่ฉันไม่สามารถหาข้อความที่อธิบายว่าการยกกำลังนั้นเกี่ยวข้องกับการเริ่มต้นที่เป็นมิตร

มีความสับสนเพิ่มเติมผ่านเฟรมเวิร์กLiftซึ่งยกชื่อขึ้นมา แต่มันก็ไม่ได้ช่วยตอบคำถาม

"การยก" ใน Scala คืออะไร

คำตอบ:


290

มีประเพณีไม่กี่:

PartialFunction

โปรดจำไว้ว่า a PartialFunction[A, B]คือฟังก์ชันที่กำหนดไว้สำหรับโดเมนย่อยบางส่วนA(ตามที่ระบุโดยisDefinedAtวิธีการ) คุณสามารถ "ลิฟท์" ซึ่งเป็นเป็นPartialFunction[A, B] Function[A, Option[B]]นั่นคือฟังก์ชั่นที่กำหนดไว้ในช่วงทั้งของAแต่มีค่าเป็นประเภทOption[B]

นี้จะกระทำโดยการภาวนาอย่างชัดเจนของวิธีการในliftPartialFunction

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)

วิธีการ

คุณสามารถ "ยก" การเรียกใช้เมธอดเป็นฟังก์ชัน สิ่งนี้เรียกว่าการขยายตัว (ขอบคุณ 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)

functors

functor (ตามที่กำหนดโดยscalaz ) เป็นบางส่วน "ภาชนะ" (ผมใช้คำว่ามากคับ) Fเช่นว่าถ้าเรามีF[A]และฟังก์ชั่นA => Bแล้วเราสามารถได้รับในมือของเราบนF[B](คิดว่าสำหรับตัวอย่างF = Listและmapวิธีการ )

เราสามารถเข้ารหัสคุณสมบัตินี้ดังนี้

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

นี่คือ isomorphic ที่จะสามารถ "ยก" ฟังก์ชั่นA => Bเข้าไปในโดเมนของ functor นั่นคือ:

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

นั่นคือถ้าFเป็น functor และเรามีฟังก์ชั่นนี้เรามีฟังก์ชั่นA => B F[A] => F[B]คุณอาจลองใช้liftวิธีนี้ - มันค่อนข้างเล็กน้อย

Monad Transformers

ในฐานะที่เป็นhcoopzกล่าวดังต่อไปนี้ (และฉันได้ตระหนักถึงเพียงว่าเรื่องนี้จะมีการบันทึกฉันจากการเขียนตันของรหัสที่ไม่จำเป็น) คำว่า "ลิฟท์" นอกจากนี้ยังมีความหมายภายในMonad หม้อแปลง จำได้ว่าหม้อแปลง monad เป็นวิธีหนึ่งของ "stacking" monads ที่อยู่ด้านบนของกันและกัน (monads ไม่ได้เขียน)

IO[Stream[A]]ดังนั้นสำหรับตัวอย่างเช่นสมมติว่าคุณมีฟังก์ชั่นซึ่งจะส่งกลับอีกด้วย สามารถแปลงเป็นหม้อแปลงไฟฟ้า monad StreamT[IO, A]ได้ ตอนนี้คุณอาจต้องการที่จะ "ยก" ค่าอื่นบางบางทีอาจจะว่ามันยังมีIO[B] StreamTคุณสามารถเขียนสิ่งนี้:

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

หรือสิ่งนี้:

iob.liftM[StreamT]

นี้ begs คำถาม: ทำไมฉันต้องการที่จะแปลง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]

12
มันอาจคุ้มค่าที่จะกล่าวถึงว่า "การยกวิธีการไปยังฟังก์ชั่น" มักจะถูกเรียกว่าการขยายตัวทางกทพ .
เบ็นเจมส์

7
ล้วงลึกเข้าไปในscalaz , ยกยังเกิดขึ้นในความสัมพันธ์กับหม้อแปลง monad ถ้าฉันมีMonadTransอินสแตนซ์TสำหรับMและMonadอินสแตนซ์สำหรับNแล้วT.liftMสามารถนำมาใช้เพื่อยกค่าของประเภทค่าเป็นประเภทN[A] M[N, A]
846846846

ขอบคุณเบ็น hcoopz ฉันแก้ไขคำตอบแล้ว
oxbow_lakes

ที่สมบูรณ์แบบ! อีกเหตุผลเดียวที่จะพูด: Scala - ดีที่สุด ซึ่งสามารถยกให้กับ Martin Odersky & Co ได้ดีที่สุด ฉันยังต้องการใช้liftMสิ่งนั้น แต่ไม่สามารถจัดการเข้าใจวิธีการทำอย่างถูกต้อง พวกคุณเป็นร็อค!
Dmitry Bespalov

3
ในส่วนวิธีการ... res0 เป็นตัวอย่าง (นั่นคือค่า) ของประเภท (ฟังก์ชั่น) (Int => Int) ...ไม่ควรfเป็นตัวอย่างres0ใช่ไหม?
srzhio

21

การใช้งานอีกอย่างหนึ่งของการยกที่ผมเคยเจอในหนังสือพิมพ์ (คนไม่จำเป็นต้องเกี่ยวข้องกับสกาล่า) เป็นฟังก์ชั่นมากไปจากf: A -> Bที่มีf: List[A] -> List[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

5
นี่คือ "การยกตัวเป็น functor" ซึ่ง oxbow_lakes อธิบาย
เบ็นเจมส์

6
@BenJames จริงแน่นอน เพื่อการป้องกันของฉัน: คำตอบ oxbow_lakes 'ยังไม่ได้มีเมื่อฉันเริ่มที่จะเขียนของฉัน
Malte Schwerhoff

20

โปรดทราบว่า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

สิ่งนี้แสดงวิธีการที่เป็นระเบียบเพื่อหลีกเลี่ยงข้อยกเว้นของดัชนี


6

นอกจากนี้ยังมีการยกซึ่งเป็นกระบวนการผกผันในการยก

หากการยกนั้นหมายถึง

เปลี่ยนฟังก์ชันบางส่วนPartialFunction[A, B]เป็นฟังก์ชันทั้งหมดA => Option[B]

จากนั้นก็ยก

เปลี่ยนฟังก์ชั่นทั้งหมดA => Option[B]เป็นฟังก์ชั่นบางส่วน PartialFunction[A, B]

ห้องสมุดมาตรฐานสกาล่ากำหนด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))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.