Düz Harita / Harita dönüşümü için anlama ile karıştırıldı


87

Map ve FlatMap'i gerçekten anlayamıyorum. Anlayamadığım şey, kavramak için bir eşleme ve flatMap'e iç içe geçmiş aramalar dizisi. Aşağıdaki örnek, Scala'daki Functional Programming'den alınmıştır.

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

Çevirir

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

MkMatcher yöntemi şu şekilde tanımlanır:

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

Ve desen yöntemi aşağıdaki gibidir:

import java.util.regex._

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

Birisi burada map ve flatMap'i kullanmanın ardındaki mantığa biraz ışık tutabilirse harika olur.

Yanıtlar:


201

TL; DR doğrudan son örneğe gider

Deneyeceğim ve özetleyeceğim.

Tanımlar

forAnlama birleştirmek için bir sözdizimi kısayoldur flatMapve mapokumak ve yaklaşık sebebi kolay bir şekilde.

İşleri biraz basitleştirelim ve classyukarıda bahsedilen her iki yöntemi sağlayan her birinin a olarak adlandırılabileceğini monadve iç tipi ile M[A]a anlamına gelmek için sembolü kullanacağımızı varsayalım .monadA

Örnekler

Yaygın olarak görülen bazı monadlar şunları içerir:

  • List[String] nerede
    • M[X] = List[X]
    • A = String
  • Option[Int] nerede
    • M[X] = Option[X]
    • A = Int
  • Future[String => Boolean] nerede
    • M[X] = Future[X]
    • A = (String => Boolean)

harita ve düz harita

Genel bir monadda tanımlanmıştır 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]

Örneğin

  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)

ifade için

  1. <-Sembolü kullanan ifadedeki her satır, flatMapbir sonuç mapçağrısına çevrilen son satır haricinde , sol taraftaki "bağlı sembol" argüman işlevine parametre olarak aktarılır (ne daha önce aradık 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. Yalnızca bir ifadeye sahip <-bir mapifade, bağımsız değişken olarak iletilen ifade ile bir çağrıya dönüştürülür :

    // 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
    

Şimdi konuya

Görebileceğiniz gibi, mapişlem orijinalin "şeklini" korur monad, bu nedenle yieldifade için de aynı şey olur : a , içindeki işlem tarafından dönüştürülen içerik ile a olarak Listkalır .Listyield

Diğer yandan, içindeki her cilt çizgisi, tek bir "dış şekli" korumak için "düzleştirilmesi" gereken forardışık bir bileşimdir monads.

Bir an için her iç bağlantının bir mapçağrıya çevrildiğini , ancak sağ elin aynı A => M[B]işlev olduğunu, M[M[B]]anlamadaki her satır için bir ile sonuçlanacağını varsayalım .
Tüm forsözdiziminin amacı, ardışık monadik işlemlerin (yani, "monadik bir şekilde" bir değeri "yükselten" işlemler) birleştirilmesini, muhtemelen bir nihai dönüşümü gerçekleştiren bir A => M[B]son mapişlemin eklenmesiyle kolayca "düzleştirmek" tir .

Umarım bu, mekanik bir şekilde uygulanan çeviri seçiminin arkasındaki mantığı açıklar, yani n flatMaptek bir mapçağrı ile sonuçlanan iç içe aramalar .

Yapmacık bir açıklayıcı örnek Sözdiziminin
anlamlılığını göstermek anlamına gelirfor

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 (_ + _)
}

Türünü tahmin edebilir misin valuesList?

Daha önce de belirtildiği gibi, monadkavrama yoluyla formun şekli korunur, bu yüzden bir Listin ile başlayıp a company.branchesile bitmeliyiz List.
Bunun yerine iç tip değişir ve yieldifade tarafından belirlenir :customer.value: Int

valueList olmalı List[Int]


1
"İle aynıdır" kelimeleri meta dile aittir ve kod bloğunun dışına taşınmalıdır.
gün

3
Her FP acemi bunu okumalıdır. Bu nasıl başarılabilir?
mert inan

1
@melston ile bir örnek verelim Lists. mapBir işlevi A => List[B](temel monadik işlemlerden biri) bir değerin üzerine iki katına çıkarırsanız , bir Liste [Liste [B]] elde edersiniz (türlerin eşleştiğini kabul ediyoruz). Anlamak için iç döngü, bu işlevleri karşılık gelen flatMapişlemle oluşturur, Liste [Liste [B]] şeklini basit bir Liste [B] halinde "düzleştirir" ... Umarım bu açıktır
pagoda_5b

1
cevabını okumak saf bir harikaydı. Keşke scala hakkında bir kitap yazsaydın, blog falan var mı?
Tomer Ben David

1
@coolbreeze Açıkça ifade etmemiş olabilirim. Demek istediğim, yieldcümlenin customer.valuetürü kimin olduğu Int, dolayısıyla bütünün for comprehensionbir List[Int].
pagoda_5b

7

Ben büyük ölçekli bir beyin değilim, bu yüzden beni düzeltmekten çekinmeyin, ama bu flatMap/map/for-comprehensiondestanı kendime böyle açıklıyorum !

Anlamak for comprehensionve tercüme etmek scala's map / flatMapiçin küçük adımlar atmalı ve beste yapan bölümleri anlamalıyız - mapve flatMap. Ama değil scala's flatMapsadece mapile flattensize Kendini sorun! eğer öyleyse, neden bu kadar çok geliştirici bunu anlamakta zorlanıyor for-comprehension / flatMap / map? Peki, sadece scala'lara mapve flatMapimzaya bakarsanız , aynı dönüş türünü döndürdüklerini M[B]ve aynı girdi argümanı üzerinde çalıştıklarını A(en azından aldıkları fonksiyonun ilk kısmı) görürsünüz, eğer öyleyse, fark yaratan nedir?

Bizim planımız

  1. Scala'ları anlayın map.
  2. Scala'ları anlayın flatMap.
  3. Scala's for comprehensionanlayın. '

Scala'nın haritası

scala harita imzası:

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

Ama bu imzaya baktığımızda büyük bir eksiklik var ve bu - bu Anereden geliyor? bizim kapsayıcımız tipte Aolduğundan, bu işleve kapsayıcı bağlamında bakmak önemlidir - M[A]. Konteynırımız bir Listtür öğe olabilir Ave bizimmap fonksiyon türünün her ürün dönüşümü bir işlevi alır Atürüne B, o zaman tipteki bir kabın döndüren B(veya M[B])

Kabı dikkate alarak haritanın imzasını yazalım:

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]

Bir Not harita hakkında son derece yüksek derece önemli gerçeği - bu demetleri otomatik çıkış kaptaM[B] bunun üzerinde hiçbir kontrole sahip. Tekrar vurgulayalım:

  1. mapbizim için çıktı kabını seçer ve üzerinde çalıştığımız kaynakla aynı kap olacaktır, bu nedenle M[A]kap için aynı Mkabı sadece karşılığında alırız, B M[B]başka hiçbir şey almayız!
  2. mapbu kapsayıcılığı bizim için yapıyor mu, bizden bir eşleme Averiyoruz Bve onu M[B]bizim için kutuya koyacak.

containerizeİç öğeleri nasıl dönüştüreceğinizi belirlediğiniz öğenin nasıl yapılacağını belirlemediğinizi görüyorsunuz . Ve aynı konteynere sahip olduğumuz içinM her ikisi için deM[A] ve M[B]bu M[B]aynı konteynır olduğu anlamına gelir , yani eğer varsa, List[A]o zaman bir tane olacak List[B]ve daha da önemlisi mapbunu sizin için yapacaksınız!

Şimdi hallettiğimize göre devam mapedelim flatMap.

Scala'nın Daire Haritası

İmzasını görelim:

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

Haritadan flatMapflatMap'e kadar olan büyük farkı görüyorsunuz, biz ona sadece onu dönüştürmekle A to Bkalmayıp aynı zamanda onu içine alan bir fonksiyon sağlıyoruz M[B].

Neden konteynerleştirmeyi kimin yaptığını önemsiyoruz?

Öyleyse, konteynırlaştırmayı içine alan M[B]veya haritanın kendisi bizim için kapsayıcılığı yapan eşleme / flatMap giriş işlevini neden bu kadar önemsiyoruz ?

Neler olduğu bağlamında for comprehension, üründe sağlanan öğede çoklu dönüşümler olduğunu görüyorsunuz, bu fornedenle montaj hattımızdaki bir sonraki çalışana ambalajı belirleme yeteneği veriyoruz. bir montaj hattımız olduğunu hayal edin, her işçinin ürüne bir şeyler yaptığı ve yalnızca son çalışan onu bir konteynere paketliyor! HoşgeldinizflatMap amacı budur, mapher işçi öğe üzerinde çalışmayı bitirdiğinde onu paketler, böylece konteynerler üzerinde konteynerler alırsınız.

Anlama gücü

Şimdi, yukarıda söylediklerimizi dikkate alarak anlayışınıza bakalım:

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

Burada ne var:

  1. mkMatcher döndürür container kap bir işlev içerir:String => Boolean
  2. Kurallar , sonuncusu dışında <-çevirdikleri birden fazla sayıya sahipsek flatMap.
  3. As f <- mkMatcher(pat)ilkidir sequence(düşünmek assembly linebiz bunun dışında isteyen tüm almaktır)f montaj hattındaki bir sonraki işçiye , montaj hattımızdaki bir sonraki işçiye (bir sonraki işlev) ne olacağını belirleme becerisi sağlamaktır. Ürünümüzün geri paketlenmesi bu yüzden son işlevi map.
  4. Sonuncusu g <- mkMatcher(pat2)bunu kullanacak mapçünkü montaj hattındaki sonuncusu! yani map( g =>evet olan son işlemi yapabilir ! çeker gve fdaha önce konteynerden çıkarılmış olanı kullanır .flatMap nedenle ilk olarak son buluruz:

    mkMatcher (pat) flatMap (f // pull out f fonksiyonu öğeyi sonraki montaj hattı çalışanına verir (erişiminin olduğunu görürsünüz fve geri paketlemeyin, yani haritanın ambalajı belirlemesine izin verin, bir sonraki montaj hattı çalışanı container. mkMatcher (pat2) map (g => f (s) ...)) // bu montaj hattındaki son fonksiyon olduğu için haritayı kullanacağız ve g'yi konteynerden çekip pakete geri çekeceğiz , onun mapve bu ambalaj tüm yolu yukarı çekecek ve bizim paketimiz veya konteynerimiz olacak, yah!


4

Mantık, bir fayda olarak uygun "başarısızlık hızlı" hata işleme sağlayan monadik işlemleri zincirlemektir.

Aslında oldukça basit. mkMatcherYöntem, bir döner Option(bir Monad olan). mkMatcherMonadik işlemin sonucu ya a Noneya daSome(x) .

mapVeya flatMapişlevinin a öğesine uygulanması Noneher zaman a None- parametresine mapve öğesine parametre olarak iletilen işlevflatMap değerlendirilmez.

Bu nedenle, örneğinizde, eğer mkMatcher(pat)bir None döndürürse, ona uygulanan flatMap bir döndürür None(ikinci monadik işlem mkMatcher(pat2)çalıştırılmayacaktır) ve final maptekrar birNone . Başka bir deyişle, anlama için'deki işlemlerden herhangi biri bir Yok döndürürse, hızlı bir davranışınız olur ve diğer işlemler yürütülmez.

Bu, hata işlemenin monadik tarzıdır. Zorunlu stil, temelde atlama olan istisnaları kullanır (bir catch cümlesine)

Son bir not: patternsişlev, bir emir niteliğindeki hata işlemeyi ( try... catch) tek tip bir hata işlemeye "çevirmenin" tipik bir yoludur.Option


Neden birinci ve ikinci çağrıyı "birleştirmek" için kullanıldığını flatMap(ve mapkullanılmadığını) biliyor musunuz mkMatcher, ancak neden ikinci ve bloğu "birleştirmek" map(ve değil flatMap) kullanıldı ? mkMatcheryields
Malte Schwerhoff

1
flatMapmapsarma / kaldırma işlemini kendisi yaparken Monad'de "sarılmış" / kaldırılmış sonucu döndüren bir işlevi geçmenizi bekler . İşlemlerin çağrı zinciri sırasında , parametre olarak iletilen işlevlerin geri dönebilmesi için for comprehensionyapmanız gerekir (değeri Hiçbiri'ne kaldıramazsınız). Son işlem çağrısı, içindekinin çalışması ve bir değer döndürmesi beklenir ; Son işlemin yeterli olduğu ve işlevin sonucunu monad içine kaldırma zorunluluğunu ortadan kaldıran bir zincirleme. flatmapNoneyieldmap
Bruno Grieder

1

Bu şu şekilde tercüme edilebilir:

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)

Nasıl genişlediğini daha iyi görmek için bunu çalıştırın

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)

sonuçlar:

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

Bu benzer flatMapher eleman döngü - patve foreach eleman mapher bir elemanı için depat2


0

İlk olarak, mkMatcherimzası olan bir fonksiyon döndürür String => Boolean, bu fonksiyonda Pattern.compile(string)gösterildiği gibi sadece çalışan normal bir java prosedürüdür pattern. O zaman bu satıra bak

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

mapFonksiyon sonucu uygulanan patternolduğunu Option[Pattern], bu yüzden pde p => xxxsize derlenmiş sadece kalıptır. Böylece, bir desen verildiğinde p, bir String alan yeni bir işlev oluşturulur ve desenle eşleşip eşleşmediğini skontrol eder s.

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

Unutmayın, pdeğişkenin derlenmiş desene bağlı olduğunu unutmayın . Şimdi, imzalı bir fonksiyonun nasıl String => Booleaninşa edildiği açıktır mkMatcher.

Ardından, bothMatchtemel alan işlevi kontrol edelim mkMatcher. Nasıl bothMathchçalıştığını göstermek için önce bu kısma bakıyoruz:

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

Biz imzası ile bir fonksiyon olduğundan beri String => Booleangelen mkMatcherolduğunu gbu bağlamda, g(s)eşdeğerdir Pattern.compile(pat2).macher(s).matches, hangi döner String s maçlar desen eğer pat2. Yani nasıl f(s)bu aynı şey g(s), tek fark, ilk çağrı olmasıdır mkMatcherkullanımların flatMapyerine, mapNeden? Çünkü mkMatcher(pat2) map (g => ....)iadeler Option[Boolean], bir iç içe bir sonuç elde edecek Option[Option[Boolean]]kullanırsanız maphem çağrı için en istemediğiniz şey bu.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.