2012-01-05 19 views
139

कुछ समय मैं स्काला ब्लॉग पोस्ट मेंस्कैला में प्रकार के लैम्बडास क्या हैं और उनके लाभ क्या हैं?

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

के अर्द्ध रहस्यमय अंकन है, जो इसे "उस प्रकार-लैम्ब्डा चाल हम इस्तेमाल किया" handwave एक देने में ठोकर।

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

+0

देखें [भी] (https://underscore.io/blog/posts/2016/12/05/type-lambdas.html)। –

उत्तर

137

टाइप लैम्बडास उस समय के बहुत महत्वपूर्ण हैं जब आप उच्च प्रकार के प्रकार के साथ काम कर रहे हों।

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

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] 
} 

इस प्रकार प्रणाली में currying का एक उदाहरण है - आप या तो के प्रकार curried है, जैसे जब आप 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 [एफ, जी] #IterateeM एक नाम का उपयोग कर सकते कुछ ट्रांसफार्मर संस्करण के लिए IterateeT के प्रकार इकाई विशेष उल्लेख करने के लिए मौजूद है: यहाँ आज से मेरे कोड से एक उदाहरण है एक दूसरे मोनैड का जो कि तीसरे मोनड के लिए विशिष्ट है। जब आप ढेर करना शुरू करते हैं, तो इस तरह की संरचनाएं बहुत जरूरी हो जाती हैं। मैं निश्चित रूप से एफजी को तुरंत चालू नहीं करता; यह सिर्फ एक हैक के रूप में है जो मुझे टाइप सिस्टम में जो चाहता है उसे व्यक्त करने के लिए है।

+3

यह ध्यान रखना दिलचस्प है कि [हास्केल * प्रकार * सीधे प्रकार-स्तर लैम्बडास का समर्थन नहीं करता है] (http://stackoverflow.com/questions/4069840/lambda-for-type-expressions-in-haskell), हालांकि कुछ नए प्रकार की हैकर (उदाहरण के लिए) TypeCompose लाइब्रेरी) के आसपास पाने के तरीके हैं। –

+1

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

+2

मैंने सोचा कि दयालु '*' ऑर्डर -1 था, लेकिन किसी भी मामले में मोनाड के पास दयालु '(* => *) => *' है। साथ ही, आप ध्यान दें कि मैंने "या तो [ए, बी]' "का सही प्रक्षेपण निर्दिष्ट किया है - कार्यान्वयन छोटा है (लेकिन यदि आपने इसे पहले नहीं किया है तो एक अच्छा अभ्यास!) –

50

लाभ अज्ञात कार्यों द्वारा प्रदान किए गए समान हैं।

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

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

एक उदाहरण के उपयोग, Scalaz 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) 

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

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

आप इंटेलीजे का उपयोग करते हैं, तो आप सेटिंग सक्षम कर सकते हैं, कोड शैली, स्काला, फोल्डिंग, प्रकार lambdas।यह तो hides the crufty parts of the syntax, और अधिक स्वादिष्ट प्रस्तुत करता है:

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

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

+0

वह अंतिम स्निपेट वाकई अच्छा दिखता है। IntelliJ स्कैला प्लगइन निश्चित रूप से कमाल है! – AndreasScheinert

+1

धन्यवाद! आखिरी उदाहरण में लैम्ब्डा गायब हो सकता है। इसके अलावा, ट्यूपल मज़ेदारों ने आखिरी मूल्य को बदलने का फैसला क्यों किया? क्या यह सम्मेलन/व्यावहारिक डिफ़ॉल्ट है? – ron

+1

मैं निका के लिए नाइटली चला रहा हूं और मेरे पास वर्णित आईडीईए विकल्प नहीं है। दिलचस्प बात यह है कि, "एप्लाइड टाइप लैम्ब्डा को सरल बनाया जा सकता है" के लिए एक निरीक्षण। –

39

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

How to resolve this type definition: Pure[({type ?[a]=(R, a)})#?] ?

What are the reasons of using such construction?

Snipped comes from scalaz library:

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

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

तो, List, Option को आर्टिटी के प्रकार के स्तर के रूप में माना जा सकता है। औपचारिक रूप से हम कहते हैं कि उनके पास * -> * है। तारांकन एक प्रकार को दर्शाता है।

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

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

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

निम्न उदाहरण मदद कर सकता है:

// 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] का कारण बनता है कि जो कुछ भी हम X[A] में A में डाल implicitly[X[A] =:= Foo[String,Int]] हमेशा सच मुझे लगता है कि है।

संबंधित मुद्दे