Nunca escribas un loop de nuevo

Mateo Calle M.

@Mathius17

Un poquito de mi antes de seguir

Disclaimer!

Basado en el libro Refactoring to Collections de Adam Wathan

Programación

imperativa vs declarativa

Imperativa

function getUserEmails(users) {
  let emails = [];

  for (let i = 0; i < users.length; i++) {
    const user = users[i];

    if (user.email) {
        emails.push(user.email);
    }
  }

  return emails;
}

Cómo se hacen las cosas

Declarativa

SELECT email
FROM users
WHERE email IS NOT NULL

Qué queremos hacer

Funciones de orden superior

Es una función que toma una función como parámetro, retorna una función, o ambas.

function twice(f, param) {
  return f(f(param));
}

function add5(value) {
  return value + 5;
}

twice(add5, 10); // 20

Abstrayendo patrones a funciones de orden superior

Tenemos una lista de clientes y queremos saber los correos de cada uno

Tenemos un inventario y queremos saber el valor de inventario para cada producto

let customerEmails = [];

for (let i = 0; i < customers.length; i++) {
  const customer = customers[i];

  customerEmails.push(
    customer.email
  );
}

return customerEmails;
let stockTotals = [];

for (let i = 0; i < inventory.length; i++) {
  const item = inventory[i];

  stockTotals.push({
    product: item.productName,
    totalValue: item.quantity * item.price,
  });
}

return stockTotals;
// let customerEmails = [];

// for (let i = 0; i < customers.length; i++) {
//  const customer = customers[i];

//   customerEmails.push(
       customer.email
//   );
// }

// return customerEmails;
// let stockTotals = [];

// for (let i = 0; i < inventory.length; i++) {
//   const item = inventory[i];

//  stockTotals.push(
      {
        product: item.productName,
        totalValue: item.quantity * item.price,
      }
//  );
// }

// return stockTotals;
let customerEmails = [];

for (let i = 0; i < customers.length; i++) {
  const customer = customers[i];

  customerEmails.push(
    // customer.email
  );
}

return customerEmails;
let stockTotals = [];

for (let i = 0; i < inventory.length; i++) {
  const item = inventory[i];

  stockTotals.push(
  // {
  //   product: item.productName,
  //   totalValue: item.quantity * item.price,
  // }
  );
}

return stockTotals;
function map(items, func) {
  let results = [];

  for (let i = 0; i < items.length; i++) {
    const item = items[i];

    results.push(func(item));
  }

  return results;
}
let customerEmails = [];

for (let i = 0; i < customers.length; i++) {
  const customer = customers[i];

  customerEmails.push(
    customer.email
  );
}

return customerEmails;
let stockTotals = [];

for (let i = 0; i < inventory.length; i++) {
  const item = inventory[i];

  stockTotals.push({
    product: item.productName,
    totalValue: item.quantity * item.price,
  });
}

return stockTotals;
let customerEmails = map(
  customers,
  customer => customer.email
);
let stockTotals = map(
  inventory,
  item => {
    product: item.productName,
    totalValue: item.quantity * item.price,
  }
);

Funciones básicas

Each / ForEach

function each(items, callback) {
  for (let i = 0; i < items.length; i++) {
    const item = items[i];

    callback(item);
  }
}

Imprimir los nombres y apellidos de una lista de personas

for (let i = 0; i < persons.length; i++) {
  const person = persons[i];

  console.log(`${person.name} ${person.lastname}`);
}
each(persons, person => {
  console.log(`${person.name} ${person.lastname}`);
});

Map

function map(items, callback) {
  let results = [];

  for (let i = 0; i < items.length; i++) {
    const item = items[i];

    results.push(callback(item));
  }

  return results;
}

Obtener los correos de una lista de clientes

let emails = [];

for (let i = 0; i < customers.length; i++) {
  const customer = customers[i];

  emails.push(customer.email);
}

return emails;
const emails = map(customers, customer => customer.email);

Filter

function filter(items, callback) {
  let results = [];

  for (let i = 0; i < items.length; i++) {
    const item = items[i];

    if (callback(item)) {
      results.push(item);
    }
  }

  return results;
}

Obtener sólo los productos que valgan menos de $100

let filteredProducts = [];

for (let i = 0; i < products.length; i++) {
  const product = products[i];

  if (product.price < 100) {
    filteredProducts.push(product);
  }
}

return filteredProducts;
const filteredProducts = filter(
  products,
  product => product.price < 100
);

Reduce

function reduce(items, callback, initial) {
  let accumulator = initial;

  for (let i = 0; i < items.length; i++) {
    const item = items[i];

    accumulator = callback(accumulator, item);
  }

  return accumulator;
}

El precio total de un carrito de compras

let total = 0;

for (let i = 0; i < cart.items.length; i++) {
  const item = cart.items[i];

  total += item.price;
}

return total;
const totalPrice = filter(
  cart.items,
  (total, item) => total + item.price
);

Ya está implementado

  • Array.prototype (Nativo ES5+)

  • Lodash

  • Underscore

  • Ramda

  • Etc

Concatenación y código más legible

const contacts = [
  { name: 'Mateo', email: 'mateo@company.com', ... },
  { name: 'Laura', email: 'laura@company2.com', ... },
  { name: 'Paula', email: 'paula@company.com', ... },
];

// Usando funciones
const count = reduce(
  filter(
    map(
      contacts,
      contact => contact.email
    ),
    email => email.includes('@company.com')
  ),
  (carry, email) => carry += `${email}`,
  0
);

// Usando los métodos nativos de Array
const count = contacts
  .map(contact => contact.email)
  .filter(contact => contact.email)
  .reduce((carry, email) => carry + 1, 0);

// Usando lodash
const count = _(contacts)
  .map(contact => contact.email)
  .filter(contact => contact.email)
  .reduce((carry, email) => carry + 1, 0);
import { flow, map, flatten, sortBy } from 'lodash/fp';

// Usando `flow` y lodash/fp
flow(
  map(x => [x, x*2]), 
  flatten, 
  sortBy(x => x)
)([1, 2, 3]);

// En algunos algunos lenguajes funcionales
[1, 2, 3]
  |> map(x => [x, x*2])
  |> flatten
  |> sortBy(x => x);

Ejercicio:

¿Cuál es mi puntaje en GitHub?

https://api.github.com/users/{username}/events

GET request a la siguiente URL:

  1. PushEvent: 5 puntos
  2. CreateEvent: 4 puntos
  3. IssuesEvent: 3 puntos
  4. CommitCommentEvent: 2 puntos
  5. Todo lo demás 1 punto

Valor por cada tipo de evento

async function githubScore(username) {
  let events = await fetch(`https://api.github.com/users/${username}/events`).then(
    response => response.json()
  );

  // Get all of the event types
  let eventTypes = [];

  for (let i = 0; i < events.length; i++) {
    eventTypes.push(event.type);
  }

  // Loop over the event types and add up the corresponding scores
  let score = 0;
  for (let i = 0; i < eventTypes.length; i++) {
    switch (eventType[i]) {
      case 'PushEvent':
        score += 5;
        break;
      case 'CreateEvent':
        score += 4;
        break;
      case 'IssuesEvent':
        score += 3;
        break;
      case 'CommitCommentEvent':
        score += 2;
        break;
      default:
        score += 1;
        break;
    }
  }

  return score;
}
async function githubScore(username) {
  let events = await fetch(`https://api.github.com/users/${username}/events`).then(
    response => response.json()
  );

  // Get all of the event types
  const eventTypes = events.map(event => event.type);

  // Loop over the event types and add up the corresponding scores
  let score = 0;
  for (let i = 0; i < eventTypes.length; i++) {
    switch (eventTypes[i]) {
      case 'PushEvent':
        score += 5;
        break;
      case 'CreateEvent':
        score += 4;
        break;
      case 'IssuesEvent':
        score += 3;
        break;
      case 'CommitCommentEvent':
        score += 2;
        break;
      default:
        score += 1;
        break;
    }
  }

  return score;
}
async function githubScore(username) {
  let events = await fetch(`https://api.github.com/users/${username}/events`).then(
    response => response.json()
  );

  // Get all of the event types
  const eventTypes = events.map(event => event.type);

  // Loop over the event types and add up the corresponding scores
  const scores = eventTypes.map(eventType => {
    switch (eventType) {
      case 'PushEvent':
        return 5;
      case 'CreateEvent':
        return 4;
      case 'IssuesEvent':
        return 3;
      case 'CommitCommentEvent':
        return 2;
      default:
        return 1;
    }
  });

  return scores.reduce((carry, value) => carry + value, 0);
}
async function githubScore(username) {
  let events = await fetch(`https://api.github.com/users/${username}/events`).then(
    response => response.json()
  );

  return events
    .map(event => event.type);
    .map(eventType => {
      switch (eventType) {
        case 'PushEvent':
          return 5;
        case 'CreateEvent':
          return 4;
        case 'IssuesEvent':
          return 3;
        case 'CommitCommentEvent':
          return 2;
        default:
          return 1;
      }
    })
    .reduce((carry, value) => carry + value, 0);
}
async function githubScore(username) {
  let events = await fetch(`https://api.github.com/users/${username}/events`).then(
    response => response.json()
  );

  return _(events)
    .map('type');
    .map(eventType => {
      switch (eventType) {
        case 'PushEvent':
          return 5;
        case 'CreateEvent':
          return 4;
        case 'IssuesEvent':
          return 3;
        case 'CommitCommentEvent':
          return 2;
        default:
          return 1;
      }
    })
    .sum();
}
async function githubScore(username) {
  let events = await fetch(`https://api.github.com/users/${username}/events`).then(
    response => response.json()
  );

  const eventScores = {
    'PushEvent': 5,
    'CreateEvent': 4,
    'IssuesEvent': 3,
    'CommitCommentEvent': 2,
  };

  return _(events)
    .map('type')
    .map(eventType => eventScores[eventType] || 1)
    .sum();
}

¿Preguntas?

Nunca escribas un loop de nuevo

By mathius17

Nunca escribas un loop de nuevo

  • 267