se-funcional



se-funcional

0 0


se-funcional

Sé funcional (charla)

On Github sirreal / se-funcional

Sé funcional

El paradigma funcional

?

Paradigma imperativo

?

C, FORTRAN…

  • Verbos (push, increment, …)
  • Manipular datos compartidos
  • Utiliza efectos secundarios
var l = [1, 2, 3, 4, 5]
var gt2 = []
var el
while (el = l.shift()) {
    if (el > 2) {
        gt2.push(el)
    }
}
// gt2 == [3, 4, 5]

Paradigma Funcional

  • Funciones "matemáticas" o "puras"
  • Programar sin efectos secundarios
  • Marcado por higher-order funciones y recursión

Lenguajes:

  • SQL
  • Haskell, F#, Elm, Erlang, Lisps...
  • JavaScript, Python, Ruby...
  • Funciones matemáticas (puras)
  • f :: i -> o
  • Mapeo desde conjunto input al conjunto output
  • Cada input corresponde a exactamente 1 output
InputOutput

Algunas funciones matemáticas

a = (r) => π * r * r
c = (r) => 2 * π * r
  • ¿Dependen del mundo exterior?
  • ¿Modifican el mundo?
  • ¿a(3) siempre iguala a(3)?
  • x = a(3). ¿Son iguales c(a(3)) y c(x)?

Funciones puras

  • No hay efectos secundarios
  • No hay mutaciones (variables no varían)
  • Transparencia referencial

f :: i -> o

const add1 = (x) => x + 1
Igual que (teóricamente):
const add1_ = (x) => {
    switch (x) {
        // ...
        case -1: return 0
        case  0: return 1
        case  1: return 2
        case  2: return 3
        // ...
    }
}

¿Ves la similaridad?

const add1_ = (x) => {
    switch (x) {
        // ...
        case -1: return 0
        case  0: return 1
        case  1: return 2
        case  2: return 3
        // ...
    }
}
InputOutput

¿Qué me importa?

Es fácil razonar y testear

  • Funciones puras
  • Reducción de "moving parts"
  • Reducción del estado global
  • Explícito no implícito (funciones operan sobre sus input)
  • Funciones pequeñas
  • Se tiende a reutilizar mucho

Programas extremadamente correctos

  • Los principios funcionales reducen complejidad

  • Lenguajes funcionales ofrecen tipado muy potente

Ser mejor programador

Aprender un lenguaje funcional te obliga a pensar de una manera radicalmente distinta. Es un gran ejercicio mental. Ganas otra perspectiva desde la cual aproximarte a una problemática.

Learn […] one language that emphasizes class abstractions […], one that emphasizes functional abstraction (like Lisp or ML or Haskell), one that supports syntactic abstraction (like Lisp), one that supports declarative specifications […], and one that emphasizes parallelism (like Clojure or Go).

"Teach Yourself Programming in Ten Years", Peter Norvig, Director of Research Google

Una tarea…

A partir de una lista de usuarios, crear una tabla html.

const users = [
  {
    id: '87e2fbc2',
    name: 'Frank Underwood',
    occupation: 'Whip',
    transactions: [
      100, -10, 20, -99
    ],
    friends: [ 'bc15b67f',
               '6ba21fbd',
               '9aa690e0' ]
  },
  // ...
]
<!-- ... -->
<tr>
  <td>87e2fbc25</td>
  <td>Frank Underwood</td>
  <td>Whip</td>
  <td>11 €</td>
  <td><ul>
    <li>Zoe Barnes</li>
    <li>Claire Underwood</li>
    <li>Doug Stamper</li>
  </ul></td>
</tr>
<!-- ... -->
const users2html = (us) => {
  let rowHtml = '';
  for (let i=0; i<us.length; i++) { const u = us[i]
    let colHtml = '<td>'+u.id+'</td>'
                + '<td>'+u.name+'</td>' + '<td>'+u.occupation+'</td>'

    let balance = 0
    for (let j=0; j<u.transactions.length; j++) {
      balance += u.transactions[j]
    }
    colHtml += '<td>' + balance + ' €</td>'
    let friendsHtml = ''
    for (let j=0; j<us.length; j++) { const f = us[j]
        if (u.friends.indexOf(f.id) > -1) {
          friendsHtml += '<li>' + f.name + '</li>'
        }
    }
    colHtml += '<td><ul>' + friendsHtml + '</ul></td>'
    rowHtml += '<tr>' + colHtml + '</tr>'
  }
  return '<tbody>' + rowHtml + '</tbody>'
}

Funciones base

Combinar 2 funciones para crear otra

compose :: (b -> c) -> (a -> b) -> a -> c
h = compose(g, f)
h(x) === g(f(x))

Filtrar elementos

filter :: (a -> Bool) -> [a] -> [a]
filter(() => true, [1, 2, 3]) === [1, 2, 3]
filter(() => false, [1, 2, 3]) === []

Crear un nuevo array aplicando aplicando una funcion

map :: (a -> b) -> [a] -> [b]
map((x) => x + 1, [1, 2, 3]) === [2, 3, 4]

Combinar todos los elements del array para llegar a 1 valor

reduce :: (a -> b -> a) -> a -> [b] -> a
reduce((x, y) => x + y, 0, [1, 2, 3]) === 6
let balance = 0
for (let j=0; j<u.transactions.length; j++) {
  const t = u.transactions[j]
  balance += t
}
colHtml += '<td>' + balance + ' €</td>'

!! reduce !!

const balance = reduce((a, b) => a + b, 0, u.transactions)
let fusers = []
for (let j=0; j<us.length; j++) { const fuser = us[j]
  if (u.friends.indexOf(fuser.id) > -1)
    fusers.push(fuser)
}

!! filter !!

const fusers = filter(fuser => u.friends.indexOf(fuser.id) > -1, us)
let rowHtml = ''
for (let i=0; i<us.length; i++) { const u = us[i]
  // ...
  rowHtml = rowHtml + '<tr>' + colHtml + '</tr>'
}
let balance = 0
for (let j=0; j<u.transactions.length; j++) {
  balance = balance + u.transactions[j]
}
let friendsHtml = ''
for (let j=0; j<fusers.length; j++) { const f = fusers[j]
  friendsHtml = friendsHtml + '<li>' + f.name + '</li>'
}
const add = (a, b) => a + b
const elem = (x, xs) => -1 < xs.indexOf(x)
const wrap = (el) => (t) => `<${el}>${t}</${el}>`
const td = wrap('td')

const u2html = (us) => (u) => {
  const balance = reduce(add, 0, u.transactions) + ' €'
  const friendsHtml = wrap('ul')(
    reduce((html,f) =>
        elem(f.id, u.friends) 
          ? html + wrap('li')(f.name)
          : html
    , '', us)
  )

  const colHtml = map(td, [u.id, u.name, u.occupation, balance, friendsHtml]).join('')
  return wrap('tr')(colHtml)
}

const users3html = (us) => wrap('tbody')(map(u2html(us), us).join(''))
const users2html = (us) => {
  let rowHtml = '';
  for (let i=0; i<us.length; i++) { const u = us[i]

    let colHtml = '<td>'+u.id+'</td>'
                + '<td>'+u.name+'</td>'
                + '<td>'+u.occupation+'</td>'

    let balance = 0
    for (let j=0; j<u.transactions.length; j++) {
      const t = u.transactions[j]
      balance += t
    }
    colHtml += '<td>' + balance + ' €</td>'

    let friendsHtml = ''
    for (let j=0; j<us.length; j++) { const f = us[j]
        if (u.friends.indexOf(f.id) > -1) {
          friendsHtml += '<li>' + f.name + '</li>'
        }
    }
    colHtml += '<td><ul>' + friendsHtml + '</ul></td>'
    rowHtml += '<tr>' + colHtml + '</tr>'
  }

  return '<tbody>' + rowHtml + '</tbody>'
}

Definiciones

  • map
  • filter
  • reduce

map

const map = (f, xs) =>
  0 === xs.length
    ? []
    : [f(xs[0])].concat(map(f, xs.slice(1)))

filter

const filter = (pred, xs) =>
  0 === xs.length
    ? []
    : pred(xs[0]) ? [xs[0]].concat(filter(pred, xs.slice(1)))
                  : filter(pred, xs.slice(1))

reduce

const reduce = (f, acc, xs) =>
  0 === xs.length
    ? acc
    : reduce(f, f(acc, xs[0]), xs.slice(1))
const map = (f, xs) =>
  0 === xs.length
    ? []
    : [f(xs[0])].concat(map(f, xs.slice(1)))
const filter = (pred, xs) =>
  0 === xs.length
    ? []
    : pred(xs[0]) ? [xs[0]].concat(filter(pred, xs.slice(1)))
                  : filter(pred, xs.slice(1))
const map_ = (f, xs) =>
  reduce((acc, x) => acc.concat(f(x)), [], xs)
const filter_ = (pred, xs) =>
  reduce((acc, x) => pred(x) ? acc.concat(x) : acc, [], xs)

Conceptos relevantes

  • Higher-order functions
  • Recursión
  • Closure
  • Lambda
  • Composición
  • Partial application
  • Curry

Higher order functions

(funciones de orden superior)

Funciones que reciben o devuelven funciones.

Higher order functions

const logInOut = (f) =>
    (...args) => {
        const result = f(...args)
        console.log("%s(%s) = %O", f.name, args.join(', '), result)
        return result
    }

const add = (x, y) => x + y

const logAdd = logInOut(add)

var x = logAdd(5, 10)
// loguea "add(5, 10) = 15"
// x === 15
logInOut(add)(5, 10)
// loguea "add(5, 10) = 15"
// returns 15

Recursión

La recursión es una estratégia que aproxima a la solucion a través de soluciónes reducidas sucesivas, hasta encontrar un caso base (que no puede reducirse).

Recursión

const fibo = (n) => n < 1 ? 0
                          : n < 3 ? 1
                                  : fibo(n-1) + fibo(n-2)

Closure (clausura)

La "captura" de variables de un scope superior.

const initCounter = (init) => {
    // ^^^ scope de init vvv
    return () => init++
    // ^^^ scope de init vvv
}

x = initCounter(-10)
y = initCounter(5)

x() // -10
x() // -9

y() // 5
y() // 6

Higher-order + closure

const memo = (f) => {
    _memo = {}
    return (...args) => {
        const input = JSON.stringify(args)
        return _memo[input] ? _memo[input]
                            : _memo[input] = f(...args)
    }
}

const fibo_ = memo(
    (n) => n < 1 ? 0 : n < 3 ? 1 : fibo_(n-1) + fibo_(n-2)
)

λ

No es más que una función anónima.

Tiene gran utilidad cuando se combina con funciones higher-order.

//         | ------------------- λ ------------------- |
setTimeout(() => console.log('El tiempo se ha acabado!'), 1000)
const fibo_ = memo(
//  | ---------------------- λ ------------------------- |
    (n) => n < 1 ? 0 : n < 3 ? 1 : fibo_(n-1) + fibo_(n-2)
)

//           | ----------------- λ ------------------- |
$('a').click(function(e) { $(this).addClass('clicked') })

    // Patrón de módulo
    // IIFE / IIλ
    // λ()
;(function() {
    const exports = {}
    // exports.someFunction = ...
}())

Composición

Funciones pueden ser combinados para crear otras funciones.

const compose = (f, g) => (...args) => f(g(...args))

const product = (a, b) => a * b
const square = (x) => product(x, x)
const double = (x) => product(2, x)

const area = compose((x) => product(Math.PI, x), square)

Aplicación parcial

"Guardamos" algunos parametros de una función

const apply = (f, x) => (y) => f(x, y)
// ver Function.prototype.bind

const double_ = (x) => product(2, x)
// se convierte en
const double = apply(product, 2)

const area_ = compose((x) => product(Math.PI, x), square)
// se convierte en
const area = compose(apply(product, Math.PI), square)

Curry

Se dice que una función es "curried" cuando puede recibir sus argumentos de uno en uno.

Piensa en aplicación parcial automática.

// compara
const product = (a, b) => a * b
const productC = (a) => (b) => a * b
const double = (x) = product(2, x)
const double_ = productC(2)

¿Qué puedo hacer?

  • Utilizar funciones puras
  • Eliminar efectos secundarios
  • Evitar mutación (valores inmutables)
  • Componer funciones a partir de otras
  • Abstraer funcionalidad común

Aprender

Sé funcional