Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Des boucles aux transducers - PyConFr 2015

Des boucles aux transducers - PyConFr 2015

En supprimant les boucles que nous utilisons traditionnellement dans les transformations de données. Et, à travers une série de refactoring successifs, nous verrons comment améliorer le traitement d’importante collection de données sur les questions de :
- la performance,
- la consommation mémoire
- la réusabilité
- et le découplage

Nous démarrerons avec les architectures traditionnelles pour passer aux architectures à Pipeline. Enfin nous terminerons par l'utilisation de certaines techniques de la programmation fonctionnelle afin d’optimiser les traitements jusqu’à pouvoir gérer des collections de tailles infinies.

Le code sera écrit en Python, mais les techniques décrites sont applicables avec n’importe quels autres langages, tous les développeurs sont donc bienvenus.

Une vidéo de cette conférence est disponible sur https://medium.com/software-craftsman/des-boucles-aux-transducers-pyconfr-2015-d9cd1849b429

Arnaud LEMAIRE

October 18, 2015
Tweet

More Decks by Arnaud LEMAIRE

Other Decks in Programming

Transcript

  1. IL ÉTAIT UNE COLLECTION DE DONNÉE invoices = (
 dict(


    due_date=datetime(2015, 10, 22),
 amount_excl_vat=36,
 quantity=1,
 vat_rate=20,
 paid=False
 ),
 dict(
 due_date=datetime(2015, 9, 18),
 amount_excl_vat=28,
 quantity=1,
 vat_rate=10,
 paid=True
 ),
 dict(
 due_date=datetime(2015, 10, 23),
 amount_excl_vat=17,
 quantity=2,
 vat_rate=5,
 paid=False
 )
 )
  2. EXEMPLE DE TRAITEMENT IMPÉRATIF def get_amount_for_month(invoices, month):
 total_amount = 0


    for invoice in invoices:
 if (invoice['due_date'].month != month):
 continue
 
 total_amount += invoice['amount_excl_vat']\
 + (invoice['amount_excl_vat']
 / 100.0
 * invoice['vat_rate']) \
 * invoice['quantity']
 
 return total_amount
  3. def get_amount_for_month(invoices, month):
 total_amount = 0
 for invoice in invoices:


    if (invoice['due_date'].month != month):
 continue
 
 total_amount += invoice['amount_excl_vat']\
 + (invoice['amount_excl_vat']
 / 100.0
 * invoice['vat_rate']) \
 * invoice['quantity']
 
 return total_amount IDENTIFICATION DES ÉTAPES DU PIPELINE filter = tri 1 1 reduce = accumulateur 2 2 map = traitements sur chaque élément de la collection 3 3
  4. REFACTORONS POUR UTILISER DES PIPELINES def get_amount_for_month(invoices, month):
 invoices_of_the_month =

    filter(
 lambda invoice: invoice['due_date'].month == month,
 invoices
 )
 
 incl_vat_amount = map(
 lambda invoice: invoice['amount_excl_vat']\
 + (invoice['amount_excl_vat']
 / 100.0
 * invoice['vat_rate']) \
 * invoice['quantity'],
 invoices_of_the_month
 )
 
 return reduce(
 lambda carry, x: carry + x,
 incl_vat_amount
 )
  5. EXTRACTION DES FONCTIONS def invoice_is_due_for(month):
 return lambda invoice: \ invoice['due_date'].month

    == month def including_vat(invoice):
 return invoice[‘amount_excl_vat'] \
 + (invoice['amount_excl_vat']
 / 100.0
 * invoice['vat_rate']) \
 * invoice['quantity']
 
 
 def sum_invoice_amount(carry, x):
 return carry + x Clojure = pré-contextualisation d’une fonction 1 1
  6. VERSION REFACTORÉE def get_amount_for_month(invoices, month):
 invoice_of_the_month = \
 filter(invoice_is_due_for(month), invoices)


    
 including_vat_amount = \
 map(including_vat, invoice_of_the_month)
 
 return reduce(sum_invoice_amount, including_vat_amount)
  7. REDUCE PEUT ACCUMULER DU NON SCALAIRES def letter_count(sentence):
 
 def

    acc_letter(carry, word):
 if(not carry.has_key(word)):
 carry[word] = 1
 else:
 carry[word] += 1
 
 return carry
 
 return reduce(acc_letter, sentence, {})
  8. MAIS… L’ensemble des éléments de la collection est parcouru à

    chaque étape de transformation Chaque étape de transformation crée une collection intermédiaire Chaque étape supplémentaire augmente le temps de calcul et la mémoire consommée
  9. MAP OU FILTER PEUVENT ÊTRE EXPRIMÉS VIA REDUCE def reduced_map(callable,

    collection):
 
 def map_reducer(carry, item):
 carry.append(callable(item))
 return carry
 
 return reduce(map_reducer, collection, [])
 
 def reduced_filter(callable, collection):
 
 def filter_reducer(carry, item):
 if callable(item):
 carry.append(item)
 return carry
 
 return reduce(filter_reducer, collection, [])
  10. SORTONS L’INSTRUCTION REDUCE def mapper(callable):
 
 def map_reducer(carry, item):
 carry.append(callable(item))


    return carry
 
 return map_reducer
 
 def filterer(callable):
 
 def filter_reducer(carry, item):
 if callable(item):
 carry.append(item)
 return carry
 
 return filter_reducer
  11. ET NOS RÉDUCERS DEVIENNENT TRANSDUCERS def mapping(callable):
 def transducer(next_reducer):
 


    def map_reducer(carry, item):
 return next_reducer(carry, callable(item))
 
 return map_reducer
 
 return transducer
 
 
 def filtering(callable):
 def transducer(next_reducer):
 
 def filter_reducer(carry, item):
 if callable(item):
 return next_reducer(carry, item)
 else:
 return carry
 
 return filter_reducer
 
 return transducer
  12. ET VOICI LES TRANSDUCTEURS À L’ACTION def adder(carry, item):
 return

    carry + item
 
 
 
 reduce(
 filtering(invoice_is_due_for(10))(
 mapping(including_vat)(
 adder
 )
 ), 
 invoices, 
 0
 )
  13. TRANSDUCERS L’ensemble des traitements est composé 
 en une fonction

    Chaque élément de la collection parcourt l’ensemble des transformations en une fois Le nombre d’itération ne dépend plus du nombre de transformation
  14. TRANSDUCERS Gains en consommation mémoire et temps de calcul Peut

    traiter des ensembles infinis Plus besoin de créer des représentations intermédiaires de la collection
  15. Les traitements se font successivement sur la collection L’ensemble des

    transformations est composé en une fonction pipelines « classiques » transducers
  16. COMPOSE def compose2(f, g):
 return lambda *a, **kw: f(g(*a, **kw))


    
 
 def compose(*fs):
 return reduce(compose2, fs)
 
 
 def get_amount_for_month(invoices, month):
 return reduce(
 compose(
 filtering(invoice_is_due_for(month)),
 mapping(including_vat),
 )(adder),
 invoices,
 0
 )
  17. AMÉLIORONS L’API DES TRANSDUCERS def mapity(callable, collection = None):
 if

    collection is None:
 return mapping(callable)
 else:
 return map(callable, collection)
 
 
 def filterity(callable, collection = None):
 if collection is None:
 return filtering(callable)
 else:
 return filter(callable, collection)
  18. AMÉLIORONS L’API DES TRANSDUCERS, SUITE… filterity(
 paid_invoice, 
 mapity(
 including_vat,

    
 invoices
 )
 )
 
 compose(
 filterity(paid_invoice),
 mapity(including_vat)
 )(appender) Génération d’un transducer Utilisation avec les pipelines « classiques » 1 1 2 2