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

The Art of Functional Programming

The Art of Functional Programming

Functional Programming (FP), a paradigm in which programs are made up of pure, stateless functions, is adored by many programmers for how easy it makes it to predict, test, and debug the behavior of the code we write. Although FP has an unfortunate reputation as an ivory-tower domain full of obscure jargon comprehensible only to those with PhDs in category theory, the core concepts are straightforward ideas all of us can understand, and even have fun learning!

In this talk, we’ll create generative SVG art as a way to explore fundamental FP concepts such as recursion and function composition, and tackle the practical challenges of FP in the real world, such as dealing with the tricky parts like side effects and randomness which don’t fit into the safe, sterile world of pure functions. By the end of the talk we’ll not only have the key concepts & techniques we need to start writing our own functional programs, we’ll have some pretty functional art to look at too!

Anjana Sofia Vakil

June 17, 2022
Tweet

More Decks by Anjana Sofia Vakil

Other Decks in Programming

Transcript

  1. <svg id="art" width=500 height=500 viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"></svg> function

    impureSetSVGContents(contents) { const art = document.getElementById("art"); art.innerHTML = contents; return art; } impureSetSVGContents(pureGetArt(params));
  2. function getPattern(color1, color2, width) { const thisTile = `<rect x=0

    y=0 width=${width} height=${width} fill="${color1}" />`; if (width <= 20) { return `${thisTile}`; } return `${thisTile} <g transform="translate(${width / 2}) rotate(45) "> ${getPattern(color2, color1, width * hyp)} </g>`; } getPattern("limegreen", "rebeccapurple", 100) base case recursive case
  3. function getTiles(width, height, cols, rows, makeTile, data) { const [colWidth,

    rowHeight] = [width / cols, height / rows]; const [thisTile, newData] = makeTile(colWidth, rowHeight, data); return `${thisTile} ${cols > 1 ? // rest of this row to the right of this tile `<g transform="translate(${colWidth}, 0)"> ${getTiles(width-colWidth, rowHeight, cols-1, 1, makeTile, newData)} </g>` : ``} ${rows > 1 ? // rows below this one `<g transform="translate(0, ${rowHeight})"> ${getTiles(width, height-rowHeight, cols, rows-1, makeTile, newData)} </g>` : ``}`;} function makeBasicTile(width, height, { color }) { return [ `<rect width=${width} height=${height} stroke="black" fill="${color}" />`, { color } ];} getTiles(100, 100, 5, 5, basicTile, { color: "orange" }) base recursive recursive
  4. function makeCheckerTile(width, height, { colors, colorIndex }) { return [

    `<rect x=0 y=0 width=${width} height=${height} fill="${colors[colorIndex]}" />`, { colors, colorIndex: (colorIndex + 1) % colors.length } ]; } getTiles(100, 100, 5, 5, makeCheckerTile, { colors: ["orange", "hotPink"], colorIndex: 0 })
  5. function makeTiledTile(width, height, data) { const split = 0.7; const

    [sWidth, sHeight] = [width*split, height*split]; const newData = {...data, colorIndex: data.n % 2 === 0 ? data.colorIndex : (data.colorIndex + 1) % data.colors.length }; const big = getTiles(sWidth, sHeight, …, makeCheckerTile, data); const narrow = getTiles(width-sWidth, sHeight, …, makeCheckerTile, newData); const wide = getTiles(sWidth, height-sHeight, …, makeCheckerTile, newData); const small = getTiles(width-sWidth, height-sHeight, …, makeCheckerTile, data); return [ `${big} <g transform="translate(${sWidth}, 0)">${narrow}</g> <g transform="translate(0, ${sHeight})">${wide}</g> <g transform="translate(${sWidth}, ${sHeight})">${small}</g>`, data ];} rotate(getTiles(200, 200, 8, 8, tiledTile, { colors: ["orange", "hotpink"], colorIndex: 0, n: 5 }))
  6. const SimplexNoise = require("[email protected]") function impureGetNoiseField(seed) { return new SimplexNoise(seed);

    } const myNoise = impureGetNoiseField("hello noise!"); myNoise.noise2D(50, 50); // 0.38978764467539995 const otherNoise = impureGetNoiseField("other noise!"); otherNoise.noise2D(50,50); // -0.10783404678637644 const sameNoise = impureGetNoiseField("hello noise!"); sameNoise.noise2D(50,50); // 0.38978764467539995
  7. function makeRandomTile(width, height, { colors, noiseField, tileIndex }) { const

    noiseHere = noiseField.noise2D(tileIndex * width, tileIndex * height); const colorIndex = Math.floor(Math.abs(noiseHere * 50)) % colors.length; return [ `<rect x=0 y=0 width=${width} height=${height} fill="${colors[colorIndex]}" />`, { noiseField, colors, tileIndex: tileIndex + 1 } ]; } getTiles(100, 100, 30, 30, makeRandomTile, { colors: ["orange", "hotpink", "purple"], noiseField: myNoise, tileIndex: 0 })
  8. function makeClockTile(width, height, data) { const { colors, tileIndex, noiseField

    } = data; const noiseHere = noiseField.noise2D(tileIndex * width, tileIndex * height); const degree = 45 * (noiseHere * 4); const colorIndex = tileIndex % colors.length; return [ `<circle cx=${width/2} cy=${height/2} r=${width/2-1} stroke="black" fill="${colors[colorIndex]}" /> <line x1=${width/2} x2=${width/2} y1=1 y2=${height/2-1} stroke="black" transform="rotate(${degree}, ${width/2}, ${height/2})"/>`, { ...data, tileIndex: tileIndex + 1} ]; } getTiles(100, 100, 5, 5, clockTile, { noiseField: myNoise, colors: ["yellow", "limegreen", "hotpink", "orange"], tileIndex: 0 })
  9. look at all we made! • Side effects • Repetition

    • “State” • “Randomness”