स्काला में टाइप लैम्बडास क्या हैं और उनके फायदे क्या हैं?


152

कभी-कभी मैं अर्द्ध रहस्यमय धारणा में ठोकर खाता हूं

def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..} 

स्काला ब्लॉग पोस्ट्स में, जो इसे "हम उस प्रकार-लैंबडा ट्रिक का उपयोग करते हैं" हैंडवेव देते हैं।

जबकि मुझे इस बारे में कुछ जानकारी है (हम Aइसके साथ परिभाषा को प्रदूषित किए बिना एक अनाम प्रकार का पैरामीटर प्राप्त करते हैं?), मुझे कोई स्पष्ट स्रोत नहीं मिला जिसमें बताया गया है कि प्रकार लैंबडा ट्रिक क्या है, और इसके फायदे क्या हैं। क्या यह सिंटैक्टिक शुगर है, या यह कुछ नए आयाम खोलती है?


जवाबों:


148

जब आप उच्च श्रेणी के प्रकारों के साथ काम कर रहे होते हैं तो टाइप लंबोदर काफी महत्वपूर्ण होते हैं।

या तो [ए, बी] के सही प्रक्षेपण के लिए एक सनक को परिभाषित करने के एक सरल उदाहरण पर विचार करें। मोनाड टाइपकास्ट ऐसा दिखता है:

trait Monad[M[_]] {
  def point[A](a: A): M[A]
  def bind[A, B](m: M[A])(f: A => M[B]): M[B]
}

अब, या तो दो तर्कों का एक प्रकार का निर्माता है, लेकिन मोनाड को लागू करने के लिए, आपको इसे एक तर्क का एक प्रकार का निर्माता देना होगा। इसका समाधान एक प्रकार का मेमने का उपयोग करना है:

class EitherMonad[A] extends Monad[({type λ[α] = Either[A, α]})#λ] {
  def point[B](b: B): Either[A, B]
  def bind[B, C](m: Either[A, B])(f: B => Either[A, C]): Either[A, C]
}

यह प्रकार प्रणाली में करीने का एक उदाहरण है - आपने या तो EER के प्रकार पर अंकुश लगाया है, जैसे कि जब आप EitherMonad का एक उदाहरण बनाना चाहते हैं, तो आपको किसी एक प्रकार को निर्दिष्ट करना होगा; निश्चित रूप से उस समय आपूर्ति की जाती है जब आप बिंदु या बाइंड कहते हैं।

प्रकार लैंबडा ट्रिक इस तथ्य का फायदा उठाती है कि एक प्रकार की स्थिति में एक खाली ब्लॉक एक अनाम संरचनात्मक प्रकार बनाता है। फिर हम एक प्रकार का सदस्य पाने के लिए # सिंटैक्स का उपयोग करते हैं।

कुछ मामलों में, आपको अधिक परिष्कृत प्रकार के लैम्ब्डा की आवश्यकता हो सकती है जो इनलाइन को लिखने के लिए एक दर्द है। आज से मेरे कोड का एक उदाहरण है:

// types X and E are defined in an enclosing scope
private[iteratee] class FG[F[_[_], _], G[_]] {
  type FGA[A] = F[G, A]
  type IterateeM[A] = IterateeT[X, E, FGA, A] 
}

यह वर्ग विशेष रूप से मौजूद है ताकि मैं FG [F, G] #IterateeM जैसे नाम का उपयोग कर सकूं IterateeT monad के प्रकार को संदर्भित करने के लिए जो कि एक दूसरे मोनाड के कुछ ट्रांसफॉर्मर संस्करण के लिए विशिष्ट है जो कुछ तीसरे मोनाड के लिए विशिष्ट है। जब आप स्टैक करना शुरू करते हैं, तो इस प्रकार के निर्माण बहुत आवश्यक हो जाते हैं। मैं निश्चित रूप से एक एफजी को तुरंत नहीं लिखता हूं; यह सिर्फ एक हैक के रूप में है मुझे मुझे व्यक्त करने के लिए कि मैं टाइप सिस्टम में क्या चाहता हूं।


3
यह ध्यान रखना दिलचस्प है कि हास्केल सीधे-प्रकार के लैम्ब्डा का समर्थन नहीं करता है , हालांकि कुछ न्यूटाइप हैकरी (उदाहरण के लिए टाइपकॉम्ब लाइब्रेरी) के आस-पास पाने के लिए तरह तरह के तरीके हैं।
दान बर्टन

1
मुझे bindआपकी EitherMonadकक्षा के लिए विधि को परिभाषित करने के लिए उत्सुक होना चाहिए । :-) इसके अलावा, अगर मैं एड्रियन को एक सेकंड के लिए यहां रख सकता हूं, तो आप उस उदाहरण में उच्च-प्रकार के प्रकारों का उपयोग नहीं कर रहे हैं। आप में हैं FG, लेकिन में नहीं EitherMonad। बल्कि, आप टाइप कंस्ट्रक्टर्स का उपयोग कर रहे हैं , जो दयालु हैं * => *। यह किस्म ऑर्डर -1 की है, जो "उच्चतर" नहीं है।
डैनियल स्पाइवेक

2
मुझे लगा कि दयालु *आदेश -1 था, लेकिन किसी भी स्थिति में मोनाद दयालु है (* => *) => *। इसके अलावा, आप ध्यान दें कि मैंने "सही प्रक्षेपण" निर्दिष्ट Either[A, B]किया है - कार्यान्वयन तुच्छ है (लेकिन एक अच्छा व्यायाम यदि आपने इसे पहले किया है!)
Kris Nuttycombe

मैं निर्देशित करता हूं कि डैनियल की बात को *=>*उच्चतर नहीं कहना उस सादृश्य द्वारा न्यायोचित है जिसे हम एक साधारण कार्य नहीं कहते हैं (जो गैर-कार्यों के लिए गैर-कार्यों को मैप करता है, दूसरे शब्दों में, सादे मूल्यों को सादे मूल्यों) उच्च क्रम फ़ंक्शन।
jhegedus

1
पियर्स की TAPL पुस्तक, पृष्ठ ४४२:Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
jhegedus

52

लाभ बिल्कुल वैसा ही है जैसे कि अनाम कार्यों द्वारा प्रदान किया जाता है।

def inc(a: Int) = a + 1; List(1, 2, 3).map(inc)

List(1, 2, 3).map(a => a + 1)

एक उदाहरण उपयोग, स्कैलाज़ के साथ 7. हम एक का उपयोग करना चाहते हैं Functorजो दूसरे तत्व में एक फ़ंक्शन को मैप कर सकता है Tuple2

type IntTuple[+A]=(Int, A)
Functor[IntTuple].map((1, 2))(a => a + 1)) // (1, 3)

Functor[({type l[a] = (Int, a)})#l].map((1, 2))(a => a + 1)) // (1, 3)

स्कलाज़ कुछ निहित रूपांतरण प्रदान करता है जो टाइप तर्क का अनुमान लगा सकता है Functor, इसलिए हम अक्सर इनको पूरी तरह से लिखने से बचते हैं। पिछली पंक्ति को फिर से लिखा जा सकता है:

(1, 2).map(a => a + 1) // (1, 3)

यदि आप IntelliJ का उपयोग करते हैं, तो आप सेटिंग्स, कोड स्टाइल, स्काला, फोल्डिंग, लैंबडास को सक्षम कर सकते हैं। यह तब सिंटैक्स के क्रुटी भागों को छिपाता है , और अधिक तालमेल प्रस्तुत करता है:

Functor[[a]=(Int, a)].map((1, 2))(a => a + 1)) // (1, 3)

स्काला का भावी संस्करण इस तरह के सिंटैक्स का सीधे समर्थन कर सकता है।


यह अंतिम स्निपेट वास्तव में अच्छा लग रहा है। IntelliJ scala प्लगइन निश्चित रूप से भयानक है!
एंड्रियासचिनर्ट

1
धन्यवाद! अंतिम उदाहरण में मेमना गायब हो सकता है। इसके अलावा, अंतिम मान को बदलने के लिए ट्यूपल फ़ंक्शनलर्स ने क्यों चुना? क्या यह सम्मेलन / एक व्यावहारिक डिफ़ॉल्ट है?
रॉन

1
मैं Nika के लिए नाइटलाइफ़ चला रहा हूं और मेरे पास वर्णित IDEA विकल्प नहीं है। दिलचस्प बात यह है कि वहाँ है के लिए एक निरीक्षण "एप्लाइड प्रकार लैम्ब्डा सरल किया जा सकता।"
रान्डेल शुल्ज

6
इसे सेटिंग्स -> एडिटर -> कोड फोल्डिंग में ले जाया जाता है।
शाम

@retronym, (1, 2).map(a => a + 1)REPL में कोशिश करते समय मुझे एक त्रुटि मिली : `<कंसोल>: 11: त्रुटि: मान का नक्शा (Int, Int) (1, 2) का सदस्य नहीं है ।map (a => a + 1) ^`
केविन मेरेडिथ 3

41

चीजों को संदर्भ में रखने के लिए: यह उत्तर मूल रूप से किसी अन्य थ्रेड में पोस्ट किया गया था। आप इसे यहां देख रहे हैं क्योंकि दो धागे विलय हो गए हैं। उक्त सूत्र में प्रश्न कथन इस प्रकार था:

इस प्रकार की परिभाषा को कैसे हल करें: शुद्ध [({प्रकार? [A] = (R, a)})?]?

ऐसे निर्माण का उपयोग करने के कारण क्या हैं?

स्नैप्ड स्केलज़ लाइब्रेरी से आता है:

trait Pure[P[_]] {
  def pure[A](a: => A): P[A]
}

object Pure {
  import Scalaz._
//...
  implicit def Tuple2Pure[R: Zero]: Pure[({type ?[a]=(R, a)})#?] = new Pure[({type ?[a]=(R, a)})#?] {
  def pure[A](a: => A) = (Ø, a)
  }

//...
}

उत्तर:

trait Pure[P[_]] {
  def pure[A](a: => A): P[A]
}

बक्से में एक अंडरस्कोर का Pतात्पर्य है कि यह एक प्रकार का कंस्ट्रक्टर है जो एक प्रकार लेता है और एक अन्य प्रकार देता है। इस तरह के प्रकार के निर्माणकर्ताओं के उदाहरण: List, Option

दे दो Listएक Int, एक ठोस प्रकार, और यह आपको देता है List[Int], एक और ठोस प्रकार। दे दो Listएक Stringऔर यह आपको देता है List[String]। आदि।

तो, List, Option1. औपचारिक रूप से हम कह arity के प्रकार के स्तर के कार्यों माना जा सकता है, वे एक तरह * -> *। तारांकन एक प्रकार को दर्शाता है।

अब Tuple2[_, _]प्रकार के साथ एक प्रकार का कंस्ट्रक्टर है। (*, *) -> *आपको एक नया प्रकार प्राप्त करने के लिए इसे दो प्रकार देने की आवश्यकता है।

के बाद से अपने हस्ताक्षर से मेल नहीं खाते, तो आप स्थानापन्न नहीं कर सकता Tuple2के लिए P। आपको इसके एक तर्क पर आंशिक रूप से लागू Tuple2 करने की आवश्यकता है, जो हमें प्रकार के साथ एक प्रकार का निर्माण देगा * -> *, और हम इसके लिए प्रतिस्थापन कर सकते हैं P

दुर्भाग्य से स्काला में टाइप कंस्ट्रक्टर्स के आंशिक अनुप्रयोग के लिए कोई विशेष वाक्यविन्यास नहीं है, और इसलिए हमें प्रकार lambdas नामक राक्षसी का सहारा लेना होगा। (आपके उदाहरण में आपके पास क्या है।) उन्हें कहा जाता है क्योंकि वे लैम्ब्डा अभिव्यक्तियों के अनुरूप हैं जो मूल्य स्तर पर मौजूद हैं।

निम्नलिखित उदाहरण से मदद मिल सकती है:

// VALUE LEVEL

// foo has signature: (String, String) => String
scala> def foo(x: String, y: String): String = x + " " + y
foo: (x: String, y: String)String

// world wants a parameter of type String => String    
scala> def world(f: String => String): String = f("world")
world: (f: String => String)String

// So we use a lambda expression that partially applies foo on one parameter
// to yield a value of type String => String
scala> world(x => foo("hello", x))
res0: String = hello world


// TYPE LEVEL

// Foo has a kind (*, *) -> *
scala> type Foo[A, B] = Map[A, B]
defined type alias Foo

// World wants a parameter of kind * -> *
scala> type World[M[_]] = M[Int]
defined type alias World

// So we use a lambda lambda that partially applies Foo on one parameter
// to yield a type of kind * -> *
scala> type X[A] = World[({ type M[A] = Foo[String, A] })#M]
defined type alias X

// Test the equality of two types. (If this compiles, it means they're equal.)
scala> implicitly[X[Int] =:= Foo[String, Int]]
res2: =:=[X[Int],Foo[String,Int]] = <function1>

संपादित करें:

अधिक मूल्य स्तर और प्रकार स्तर समानताएं।

// VALUE LEVEL

// Instead of a lambda, you can define a named function beforehand...
scala> val g: String => String = x => foo("hello", x)
g: String => String = <function1>

// ...and use it.
scala> world(g)
res3: String = hello world

// TYPE LEVEL

// Same applies at type level too.
scala> type G[A] = Foo[String, A]
defined type alias G

scala> implicitly[X =:= Foo[String, Int]]
res5: =:=[X,Foo[String,Int]] = <function1>

scala> type T = World[G]
defined type alias T

scala> implicitly[T =:= Foo[String, Int]]
res6: =:=[T,Foo[String,Int]] = <function1>

आपके द्वारा प्रस्तुत किए गए मामले में, प्रकार पैरामीटर Rकार्य करने के लिए स्थानीय है Tuple2Pureऔर इसलिए आप बस परिभाषित नहीं कर सकते हैं type PartialTuple2[A] = Tuple2[R, A], क्योंकि बस कोई जगह नहीं है जहां आप उस पर्यायवाची को रख सकते हैं।

इस तरह के एक मामले से निपटने के लिए, मैं निम्नलिखित चाल का उपयोग करता हूं जो टाइप सदस्यों का उपयोग करता है। (उम्मीद है कि उदाहरण आत्म-व्याख्यात्मक है।)

scala> type Partial2[F[_, _], A] = {
     |   type Get[B] = F[A, B]
     | }
defined type alias Partial2

scala> implicit def Tuple2Pure[R]: Pure[Partial2[Tuple2, R]#Get] = sys.error("")
Tuple2Pure: [R]=> Pure[[B](R, B)]

0

type World[M[_]] = M[Int]कि जो कुछ भी हम में डाल का कारण बनता है Aमें हमेशा सच मुझे लगता है कि है।X[A]implicitly[X[A] =:= Foo[String,Int]]

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.