फ्लैट मैप / मैप ट्रांसफ़ॉर्मेशन के लिए समझ के साथ उलझन में


87

मैं वास्तव में मैप और फ़्लैटमैप को समझ नहीं पा रहा हूँ। मैं यह समझने में विफल रहा हूं कि मानचित्र और सपाट मानचित्र के लिए नेस्टेड कॉल का एक अनुक्रम कैसे है। निम्न उदाहरण स्काला में कार्यात्मक प्रोग्रामिंग से है

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
            f <- mkMatcher(pat)
            g <- mkMatcher(pat2)
 } yield f(s) && g(s)

में अनुवाद करता है

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = 
         mkMatcher(pat) flatMap (f => 
         mkMatcher(pat2) map (g => f(s) && g(s)))

MMMatcher विधि निम्नानुसार परिभाषित की गई है:

  def mkMatcher(pat:String):Option[String => Boolean] = 
             pattern(pat) map (p => (s:String) => p.matcher(s).matches)

और पैटर्न विधि इस प्रकार है:

import java.util.regex._

def pattern(s:String):Option[Pattern] = 
  try {
        Some(Pattern.compile(s))
   }catch{
       case e: PatternSyntaxException => None
   }

यह बहुत अच्छा होगा अगर कोई यहाँ मानचित्र और फ्लैट मैप का उपयोग करने के पीछे तर्क पर कुछ प्रकाश डाल सकता है।

जवाबों:


199

टीएल; डीआर सीधे अंतिम उदाहरण पर जाते हैं

मैं कोशिश करूँगा और फिर से तैयार करूँगा।

परिभाषाएं

forसमझ गठबंधन करने के लिए एक वाक्य रचना शॉर्टकट है flatMapऔर mapएक तरह से पढ़ सकते हैं और के बारे में कारण आसान है कि में।

चलिए चीजों को थोड़ा सरल करते हैं और मान लेते हैं कि प्रत्येक classजो दोनों पूर्वोक्त विधियों को प्रदान करता है उसे एक कहा जा सकता है monadऔर हम एक आंतरिक प्रकार के साथ प्रतीक M[A]का उपयोग करेंगे ।monadA

उदाहरण

कुछ आम तौर पर देखे जाने वाले मठों में शामिल हैं:

  • List[String] कहाँ पे
    • M[X] = List[X]
    • A = String
  • Option[Int] कहाँ पे
    • M[X] = Option[X]
    • A = Int
  • Future[String => Boolean] कहाँ पे
    • M[X] = Future[X]
    • A = (String => Boolean)

नक्शा और फ्लैटपाइप

एक सामान्य मठ में परिभाषित किया गया M[A]

 /* applies a transformation of the monad "content" mantaining the 
  * monad "external shape"  
  * i.e. a List remains a List and an Option remains an Option 
  * but the inner type changes
  */
  def map(f: A => B): M[B] 

 /* applies a transformation of the monad "content" by composing
  * this monad with an operation resulting in another monad instance 
  * of the same type
  */
  def flatMap(f: A => M[B]): M[B]

जैसे

  val list = List("neo", "smith", "trinity")

  //converts each character of the string to its corresponding code
  val f: String => List[Int] = s => s.map(_.toInt).toList 

  list map f
  >> List(List(110, 101, 111), List(115, 109, 105, 116, 104), List(116, 114, 105, 110, 105, 116, 121))

  list flatMap f
  >> List(110, 101, 111, 115, 109, 105, 116, 104, 116, 114, 105, 110, 105, 116, 121)

अभिव्यक्ति के लिए

  1. <-प्रतीक का उपयोग करने वाले अभिव्यक्ति में प्रत्येक पंक्ति को एक flatMapकॉल में अनुवादित किया जाता है , अंतिम पंक्ति को छोड़कर जो एक समापन mapकॉल के लिए अनुवादित होता है , जहां बाईं ओर के "बाध्य प्रतीक" को तर्क फ़ंक्शन के पैरामीटर के रूप में पारित किया जाता है (क्या हमने पहले कहा जाता है f: A => M[B]:

    // The following ...
    for {
      bound <- list
      out <- f(bound)
    } yield out
    
    // ... is translated by the Scala compiler as ...
    list.flatMap { bound =>
      f(bound).map { out =>
        out
      }
    }
    
    // ... which can be simplified as ...
    list.flatMap { bound =>
      f(bound)
    }
    
    // ... which is just another way of writing:
    list flatMap f
    
  2. केवल एक के साथ एक अभिव्यक्ति को तर्क के रूप में पारित अभिव्यक्ति के साथ कॉल <-में बदल mapदिया जाता है:

    // The following ...
    for {
      bound <- list
    } yield f(bound)
    
    // ... is translated by the Scala compiler as ...
    list.map { bound =>
      f(bound)
    }
    
    // ... which is just another way of writing:
    list map f
    

अब बात है

जैसा कि आप देख सकते हैं, mapऑपरेशन मूल के "आकार" को संरक्षित करता है monad, इसलिए yieldअभिव्यक्ति के लिए भी ऐसा ही होता है : एक Listअवशेष Listजो ऑपरेशन में परिवर्तित सामग्री के साथ रहता है yield

दूसरी ओर प्रत्येक बंधन रेखा forक्रमिक की एक रचना है monads, जिसे "एकल आकार" बनाए रखने के लिए "चपटा" होना चाहिए।

एक पल के लिए मान लें कि प्रत्येक आंतरिक बंधन को एक mapकॉल में अनुवादित किया गया था , लेकिन दाहिने हाथ एक ही A => M[B]कार्य था, आप M[M[B]]समझ में प्रत्येक पंक्ति के लिए समाप्त हो जाएंगे ।
पूरे forसिंटैक्स का आशय आसानी से क्रमिक "मोनडिक शेप" में एक मूल्य को "लिफ्ट" करना है, जो कि "मोनैडिक शेप" में एक मूल्य को "उठाता है" A => M[B]), एक अंतिम mapऑपरेशन के अतिरिक्त जो संभवतः एक समापन परिवर्तन होता है।

मुझे आशा है कि यह अनुवाद की पसंद के पीछे के तर्क की व्याख्या करता है, जो एक यांत्रिक तरीके से लागू होता है, वह है: n flatMapनेस्टेड कॉल एक एकल mapकॉल द्वारा संपन्न ।

वाक्यविन्यास
की स्पष्टता दिखाने के लिए एक आकस्मिक उदाहरण उदाहरण हैfor

case class Customer(value: Int)
case class Consultant(portfolio: List[Customer])
case class Branch(consultants: List[Consultant])
case class Company(branches: List[Branch])

def getCompanyValue(company: Company): Int = {

  val valuesList = for {
    branch     <- company.branches
    consultant <- branch.consultants
    customer   <- consultant.portfolio
  } yield (customer.value)

  valuesList reduce (_ + _)
}

क्या आप इसके प्रकार का अनुमान लगा सकते हैं valuesList?

जैसा कि पहले ही कहा गया है, monadसमझ का आकार बनाए रखा जाता है, इसलिए हम एक Listमें शुरू करते हैं company.branches, और एक के साथ समाप्त होना चाहिए List
इसके बजाय आंतरिक प्रकार बदलता है और yieldअभिव्यक्ति द्वारा निर्धारित किया जाता है: जो हैcustomer.value: Int

valueList होना चाहिए एक List[Int]


1
शब्द "जैसा है" मेटा-भाषा से संबंधित है और इसे कोड ब्लॉक से बाहर ले जाना चाहिए।
दिन

3
हर एफपी शुरुआती को इसे पढ़ना चाहिए। यह कैसे हासिल किया जा सकता है?
मर्टन इन

1
@melston चलो के साथ एक उदाहरण बनाते हैं Lists। यदि आप mapदो बार एक फ़ंक्शन A => List[B](जो कि आवश्यक मौद्रिक ऑपरेशन में से एक है) कुछ मूल्य से अधिक है, तो आप एक सूची [सूची [बी]] के साथ समाप्त होते हैं (हम इस प्रकार मिलान कर रहे हैं)। समझ के लिए आंतरिक लूप उन कार्यों को संबंधित flatMapऑपरेशन के साथ संयोजित करता है , सूची [सूची [बी]] को एक सरल सूची [बी] में "समतल" करता है ... मुझे आशा है कि यह स्पष्ट है
शिवालय_5

1
यह आपके जवाब को पढ़ने के लिए शुद्ध अजीब है। काश, आप स्काला के बारे में एक किताब लिखते, क्या आपके पास एक ब्लॉग या कुछ और होता?
तोमर बेन डेविड

1
@ कूलब्रीस यह हो सकता है कि मैंने इसे स्पष्ट रूप से व्यक्त नहीं किया। मेरा मतलब यह है कि yieldक्लॉज़ है customer.value, जिसका प्रकार है Int, इसलिए पूरा for comprehensionमूल्यांकन करता है aList[Int]
पगोडा_५ बी

7

मैं एक स्काला मेगा माइंड नहीं हूँ इसलिए मुझे सही करने के लिए स्वतंत्र महसूस करें, लेकिन यह है कि मैं flatMap/map/for-comprehensionखुद को कैसे समझाना चाहता हूँ!

समझने के लिए for comprehensionऔर इसका अनुवाद करना हैscala's map / flatMap हमें छोटे कदम उठाने होंगे और रचना के हिस्सों को समझना होगा - mapऔर flatMap। लेकिन scala's flatMapसिर्फ तुम्हारे mapसाथ flattenअपने आप से पूछ नहीं है! यदि ऐसा है तो क्यों कई डेवलपर्स इसे या की समझ पाने के लिए बहुत मुश्किल है for-comprehension / flatMap / map। ठीक है, यदि आप केवल स्कैला mapऔर flatMapहस्ताक्षर को देखते हैं, तो आप देखते हैं कि वे एक ही रिटर्न प्रकार लौटाते हैं M[B]और वे एक ही इनपुट तर्क पर काम करते हैं A(कम से कम उस फ़ंक्शन का पहला भाग जो वे लेते हैं) यदि ऐसा है तो क्या फर्क पड़ता है?

हमारी योजना

  1. समझो स्काला की map
  2. समझो स्काला की flatMap
  3. स्केला को समझें! for comprehension`

स्काला का नक्शा

scala नक्शा हस्ताक्षर:

map[B](f: (A) => B): M[B]

लेकिन एक बड़ा हिस्सा गायब है जब हम इस हस्ताक्षर को देखते हैं, और यह है - यह Aकहां से आता है? हमारे कंटेनर प्रकार के हैं Aइसलिए इस फ़ंक्शन को कंटेनर के संदर्भ में देखना महत्वपूर्ण है - M[A]। हमारे कंटेनर Listप्रकार Aऔर हमारे आइटम हो सकते हैंmap फ़ंक्शन एक फ़ंक्शन लेता है जो टाइप के प्रत्येक आइटम को टाइप Aकरने के लिए बदल Bदेता है, फिर यह टाइप का एक कंटेनर लौटाता है B(या M[B])

आइए कंटेनर में खाते में नक्शे के हस्ताक्षर लिखें:

M[A]: // We are in M[A] context.
    map[B](f: (A) => B): M[B] // map takes a function which knows to transform A to B and then it bundles them in M[B]

मानचित्र के बारे में एक अत्यंत उच्च महत्वपूर्ण तथ्य पर ध्यान दें - यह स्वचालित रूप से आउटपुट कंटेनर में बंडल करता है जिसका M[B]आपके पास इस पर कोई नियंत्रण नहीं है। आइए हम इसे फिर से तनाव दें:

  1. mapहमारे लिए आउटपुट कंटेनर को चुनता है और इसके कंटेनर उसी स्रोत के रूप में होते हैं जिस स्रोत पर हम M[A]कंटेनर के लिए काम करते हैं हमें Mकेवल उसी कंटेनर के लिए मिलता है B M[B]और कुछ नहीं!
  2. mapहमारे लिए यह कंटेनरीकरण हम सिर्फ एक मैपिंग से Aदेते हैं Bऔर यह इसे M[B]हमारे लिए बॉक्स में रख देगा!

आप देखें कि आपने यह निर्दिष्ट नहीं किया है कि containerizeआपने जिस आइटम को निर्दिष्ट किया है वह आंतरिक आइटम को कैसे बदलना है। और जैसा कि हम Mदोनों के लिए एक ही कंटेनर है M[A]और M[B]इसका मतलब M[B]एक ही कंटेनर है, मतलब अगर आपके पास है List[A]तो आपके पास एक List[B]और महत्वपूर्ण बात mapयह है कि यह आपके लिए कर रहा है!

अब जब हमने निपटा लिया है कि mapचलो आगे बढ़ते हैं flatMap

स्काला का सपाट नक्शा

आइए देखते हैं इसके हस्ताक्षर:

flatMap[B](f: (A) => M[B]): M[B] // we need to show it how to containerize the A into M[B]

आप मानचित्र से बड़े अंतर को देखते हैं, flatMapहम इसे उस फ़ंक्शन के साथ प्रदान कर रहे हैं जो न केवल से परिवर्तित करता है, A to Bबल्कि इसे कंटेनर में भी रखता है M[B]

हम इस बात की परवाह क्यों करते हैं कि कंटेनरीकरण कौन करता है?

तो हम मैप / फ्लैटपाइप के लिए इनपुट फंक्शन का इतना ध्यान M[B]क्यों रखते हैं?

आप देख रहे हैं कि for comprehensionजो कुछ हो रहा है उसके संदर्भ में आइटम में कई बदलाव हैं, forइसलिए हम अपनी विधानसभा लाइन में अगले कार्यकर्ता को पैकेजिंग निर्धारित करने की क्षमता दे रहे हैं। कल्पना करें कि हमारे पास एक असेंबली लाइन है प्रत्येक कार्यकर्ता उत्पाद के लिए कुछ करता है और केवल अंतिम कर्मचारी इसे कंटेनर में पैकेजिंग करता है! आपका स्वागत flatMapहै यह उद्देश्य है, mapप्रत्येक कार्यकर्ता में जब आइटम पर काम करना समाप्त हो जाता है तो यह पैकेज भी देता है ताकि आपको कंटेनरों पर कंटेनर मिलें।

समझ के लिए ताकतवर

अब हम आपके द्वारा ऊपर बताई गई बातों को ध्यान में रखते हुए देखते हैं:

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
    f <- mkMatcher(pat)   
    g <- mkMatcher(pat2)
} yield f(s) && g(s)

हमें वहां क्या मिला है:

  1. mkMatcherएक रिटर्न containerकंटेनर एक समारोह में शामिल है:String => Boolean
  2. नियम हैं यदि हमारे पास एकाधिक हैं तो <-वे flatMapपिछले एक को छोड़कर अनुवाद करते हैं ।
  3. जैसा f <- mkMatcher(pat)कि पहले है sequence(सोचें assembly line) हम चाहते हैं कि सभी इसे fविधानसभा लाइन में अगले कार्यकर्ता के पास ले जाएं और इसे पास करें, हम अगले कार्यकर्ता को हमारी विधानसभा लाइन (अगला कार्य) यह निर्धारित करने की क्षमता देते हैं कि क्या होगा हमारे आइटम के पीछे पैकेजिंग यही कारण है कि अंतिम कार्य है map
  4. अंतिम g <- mkMatcher(pat2)इसका उपयोग करेगा mapक्योंकि इसकी विधानसभा लाइन में अंतिम है! तो यह सिर्फ अंतिम ऑपरेशन कर सकता है map( g =>जिसके साथ हाँ! बाहर खींचता है gऔर उपयोग करता है fजो पहले से ही कंटेनर से बाहर खींच लिया गया है flatMapइसलिए हम पहले के साथ समाप्त होते हैं:

    mMMatcher (pat) flatMap (f // पुल आउट फंक्शन फंक्शन आइटम को अगले असेंबली लाइन वर्कर को दे देता है (आप देख सकते हैं कि इसकी एक्सेस है f, और इसे वापस पैकेज न करें। मेरा मतलब है कि मैप को पैकेजिंग निर्धारित करने दें) कंटेनर। mkMatcher (pat2) नक्शा (g => f (s) ...)) // असेंबली लाइन में यह अंतिम कार्य है, हम मानचित्र का उपयोग करने जा रहे हैं और g को कंटेनर से बाहर खींच कर वापस पैकेजिंग के लिए ले जा रहे हैं , इसकी mapऔर यह पैकेजिंग सभी तरह से समाप्त हो जाएगी और हमारा पैकेज या हमारा कंटेनर होगा, याह!


4

औचित्य श्रृंखला के उन्मादी संचालन का है जो एक लाभ के रूप में प्रदान करता है, उचित "तेजी से विफल" त्रुटि से निपटने।

यह वास्तव में बहुत सरल है। mkMatcherविधि एक रिटर्न Option(जो एक इकाई है)। mkMatcherमोनडिक ऑपरेशन का परिणाम Noneया तो एक या एक हैSome(x)

mapया flatMapफ़ंक्शन को Noneहमेशा रिटर्न पर लागू करना None- फ़ंक्शन को पैरामीटर के रूप में पारित किया गया mapऔरflatMap मूल्यांकन नहीं किया जाता है।

इसलिए आपके उदाहरण में, यदि mkMatcher(pat)कोई भी नहीं लौटाता है, तो उस पर लागू किया गया फ्लैटपाइप एक वापस आ जाएगा None(दूसरा विवादास्पद ऑपरेशन mkMatcher(pat2)निष्पादित नहीं किया जाएगा) और अंतिम mapफिर से वापस आ जाएगाNone । दूसरे शब्दों में, यदि कोई भी कार्रवाई के लिए समझ में नहीं आता है, तो कोई नहीं देता है, आपके पास एक विफल तेज़ व्यवहार है और बाकी कार्रवाई निष्पादित नहीं की जाती हैं।

यह त्रुटि से निपटने की विवादास्पद शैली है। अनिवार्य शैली अपवादों का उपयोग करती है, जो मूल रूप से कूदते हैं (एक पकड़ने वाले खंड के लिए)

एक अंतिम नोट: patternsफ़ंक्शन एक अनिवार्य शैली त्रुटि हैंडलिंग ( try... catch) का उपयोग करने के लिए एक monadicic त्रुटि त्रुटि का "अनुवाद" करने का एक विशिष्ट तरीका हैOption


क्या आप जानते हैं कि क्यों flatMap(और नहीं map) का उपयोग पहले और दूसरे आह्वान के "समाप्‍त" करने के लिए किया जाता है mkMatcher, लेकिन दूसरे और ब्लॉक का उपयोग " और " क्यों mapनहीं flatMapकिया जाता है ? mkMatcheryields
माल्टे श्वार्फ

1
flatMapआपसे अपेक्षा है कि आप मोनाड में लिपटे हुए "लिपटे" / रिजल्ट को वापस करते हुए एक फंक्शन पास mapकरेंगे , जबकि रैपिंग / लिफ्टिंग खुद करेंगे। कॉल ऑफ़ ऑपरेशंस के दौरान for comprehensionआपको संचालन की आवश्यकता होती है flatmapताकि पैरामीटर पारित किए गए फ़ंक्शन वापस लौटने में सक्षम हों None(आप मूल्य को एक में नहीं उठा सकते)। अंतिम ऑपरेशन कॉल, एक में मूल्य yieldको चलाने और वापस करने की उम्मीद है ; एक mapश्रृंखला के लिए है कि पिछले आपरेशन के लिए पर्याप्त है और टाल इकाई में समारोह का परिणाम लिफ्ट करने के लिए हो रही है।
ब्रूनो ग्रिडर

1

इसे निम्न के रूप में वर्गीकृत किया जा सकता है:

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
    f <- mkMatcher(pat)  // for every element from this [list, array,tuple]
    g <- mkMatcher(pat2) // iterate through every iteration of pat
} yield f(s) && g(s)

इसे इसके बेहतर विस्तार के लिए चलाएं

def match items(pat:List[Int] ,pat2:List[Char]):Unit = for {
        f <- pat
        g <- pat2
} println(f +"->"+g)

bothMatch( (1 to 9).toList, ('a' to 'i').toList)

परिणाम हैं:

1 -> a
1 -> b
1 -> c
...
2 -> a
2 -> b
...

यह flatMapप्रत्येक तत्व के माध्यम से लूप के समान है - patऔर प्रत्येक तत्व में mapइसे तत्व के लिए अग्रेषित करता हैpat2


0

सबसे पहले, mkMatcherएक फ़ंक्शन देता है जिसका हस्ताक्षर है String => Boolean, यह एक नियमित जावा प्रक्रिया है जो बस चलती है Pattern.compile(string), जैसा कि patternफ़ंक्शन में दिखाया गया है। फिर, इस लाइन को देखें

pattern(pat) map (p => (s:String) => p.matcher(s).matches)

mapफ़ंक्शन के परिणाम पर लागू किया जाता है pattern, जो है Option[Pattern], इसलिए pमें p => xxxसिर्फ पैटर्न आप संकलित है। इसलिए, एक पैटर्न दिया जाता है p, एक नया फ़ंक्शन का निर्माण किया जाता है, जो एक स्ट्रिंग लेता है s, और sजांचता है कि क्या पैटर्न से मेल खाता है।

(s: String) => p.matcher(s).matches

ध्यान दें, pचर संकलित पैटर्न से बंधा हुआ है। अब, यह स्पष्ट है कि हस्ताक्षर के साथ एक समारोह String => Booleanका निर्माण कैसे किया जाता है mkMatcher

अगला, चलो bothMatchफ़ंक्शन को चेकआउट करते हैं, जो पर आधारित है mkMatcher। यह दिखाने के लिए कि कैसे bothMathchकाम करता है, हम पहले इस भाग को देखते हैं:

mkMatcher(pat2) map (g => f(s) && g(s))

जब से हम हस्ताक्षर के साथ एक समारोह मिल String => Booleanसे mkMatcherहै, जो gइस संदर्भ में, g(s)के बराबर है Pattern.compile(pat2).macher(s).matches, जो रिटर्न अगर स्ट्रिंग रों मैचों पैटर्न pat2। तो कैसे के बारे में f(s), यह एक ही है g(s), केवल अंतर यह है कि, का mkMatcherउपयोग करता है flatMapके बजाय पहला कॉल mapक्यों? क्योंकि mkMatcher(pat2) map (g => ....)रिटर्न Option[Boolean], आपको एक नेस्टेड परिणाम मिलेगा Option[Option[Boolean]]यदि आप mapदोनों कॉल के लिए उपयोग करते हैं, तो यही वह नहीं है जो आप चाहते हैं।

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