## Functional Programming for the Long Haul Michael Snoyman, VP of Engineering <img src="/static/fpcomplete-logo.png" style="border:0;margin:0"> December 14, 2018 <img src="https://functionalconf.com/static/images/social/fuconf_twitter_card.png" style="height:150px;border:0;margin:0"> --- ## My name is Michael Snoyman... * and I'm a functional programmer * How do we choose languages, libraries, and frameworks? * I'll focus on languages * How can we do it better? * Let's start with my history * Programming for about 25 years * Haskell for about the last 10 * How did I get here? --- ## My first two "languages" * Volunteer taught my grade school class Logowriter * He also gave me a QuickBasic book * Changed my life, yay! * Choosing was easy: no choice * Found a (Visual) C++ book, learned that * Java became popular, learned that, used for a while --- ## My language selection process * There wasn't one * Not lots of learning material available * Limited availability of tooling * Followed "obvious" choices (Java will rule the web!) * Decent criteria, if not great, given the constraints I had * Then something happened... --- ## The internet ![The internet](/static/longhaul/aol.jpg) --- ## Information overload * Learning material abundantly available * Download compilers/IDEs/etc * Enter: paradox of choice <img src="/static/longhaul/languages.jpg" height="300px"> --- ## Some defaults still remain * Choose the right tool for the platform, e.g. * Objective C or Swift a good default choice for iOS * Kotlin for Android * Match the existing codebase * Corporate coding standards What do you do if none of that helps? --- ## Non-starter: full evaluation * Try out each language fully on a project * Takes too much time, costs too much * Deadlines need to be met * Never ending task <img src="/static/longhaul/sisyphus.jpg" height="200"> We need some help --- ## Evaluation tools * We need to make a choice * We've eliminated the easy ways to choose mentioned already * No choices available * Some obvious, default choice * Need a smarter pruning system * Want to come up with a short list, priority sorted --- ## Tool 1: Benchmarks * We _love_ benchmarks * Compare traffic on a review of library features post vs benchmarks * But mostly misguided: things we benchmark usually aren't the bottleneck * Example: speeding up HTTP protocol handling 50% likely won't make much difference in your application (We still need to make faster code as an industry) --- <div style="margin-top:250px"> <h4>Benchmarks are one of the few objective criteria we use when comparing languages, libraries, and frameworks</h4> </div> --- ## Why benchmarks (or not) * Benchmarks can be deeply flawed * But engineers love pointing to numbers and claiming victory * Benchmarks are a valid evaluation criterion * Often weighted more heavily than they should be * Considered objective truth, but: * They can be misleading * May not be as relevant as we expect --- ## Tool 2: Hype * If it's popular it must be good, right? * Deeply flawed, but still provides _some_ signal * Also goes by another name: reputation * May be accurate, but may not be * Need to know whom to trust * Ask trusted friends/colleagues first * Observe market trends --- ## Trust the Reddit hivemind <img src="/static/longhaul/resf.png" style="margin:0;border:0"><br> <img src="/static/longhaul/pcj1.png" style="margin:0;border:0"><br> <img src="/static/longhaul/pcj2.png" style="margin:0;border:0"><br> --- ## Marketing * We have a visceral negative reaction to hype * Problem: it can be gamed through good marketing * Solution: hate the game, not the player * Game theory: everyone needs to level the playing field * We shouldn't be afraid of doing _accurate_, _respectful_ FP marketing --- ## Where are we? * No default or obvious choices to be made * Benchmarks have eliminated a few non-starters * Reputation has eliminated others * We have a short list, priority sorted * Could reasonably solve our problem * Decent performance numbers * Well liked by some subset of the world you trust --- ## Tool 3: Onramping * Gotta actually do the work * Time to learn the language * Get the tools, follow the tutorial, play with Hello World * Compare and contrast syntax and semantics with what you know * Write an actual demo/prototype/whatever * Try one, get bored, try another * Eventually, language X is the victor! --- ## Problems with approach * Easily take 1-2 months to make a decision * Still pretty costly * That's unavoidable * But two more fundamental problems --- ## Problem 1: Sunk cost fallacy * Invested a lot of time and energy * Made a decision * Human psychology doesn't want to admit that's a waste * We're now biased to defend our choices * Will likely unintentionally ignore new information --- ## Problem 2: Only half the story * Benchmarks: flawed, but tell us about performance * Initial eval: tells us initial productivity * What about long term maintainability? * Bug count * Ability to rearchitect * Respond to changing requirements * Scalability to large teams * Technical debt * Onboarding new team members --- ## What about dat hype? Hype indicates long term usage of a language, but... <img src="/static/longhaul/sunkcost.png", height="300"> Those advocating the language have already fallen for the same trap --- ## Isn't this a FP conference? * Most programmers today start imperative or OO * Taught in schools * Dominant in industry * Large budgets for tooling * Lots of educational material * Money for marketing == more hype Which leads to: __Easy onramping__ --- ## We're fighting a losing battle * Claim: highly trained Python and Haskell devs can complete task in the same time * Someone with no FP background will _definitely_ take longer to ramp up * Fighting the battle on initial eval hamstrings us * Recent FP uptake is leveling the playing field --- ## Why this matters * Initial development phase a small fraction of time * Both commercial and open source work: maintenance phase is _far_ longer * Requirements change * Bugs need to be fixed * Technical debt must be paid * Architecture must change * Standard evaluation will not touch any of this --- ## The industry needs this ![Bug count](/static/longhaul/bugcount.jpg) --- ## Changing the conversation * Let's talk about maintainability * Blog about it * Discuss with management * Don't pass off maintenance to "the junior devs" --- ## My belief * Strongly typed FP great for maintainability * Not a panacea: still subject to * Poorly designed APIs * Subtle brekages in backwards compat * Poorly captured invariants in types * However, when done right, it's awesome * Focus on quality and maintainability * Still gotta test your code! --- ## My experience (anecdotal) * Onboarded coworkers to massive Haskell projects quickly * Lack of mutable state * Data driven nature of code * Compiler-driven development * Ability to refactor/rearchitect in massive ways * Faster feedback than unit tests * Attempt changes I would never dream of without types --- ## Loops Which of these functions is simpler? ```rust fn total1() -> u32 { let mut total = 0; for i in 0..10 { total += i; } total } fn total2() -> u32 { (0..10).fold(0, |x, y| x + y) } ``` --- ## Typical conversations * Functional programmers: obviously the fold! * Imperative programmers: * Too complex * Have to deal with closures and other complexity * Loops always work, why learn something else * Isn't the fold slow? Focuses entirely on short-term productivity and performance --- ## Changing requirements Just add the evens ```rust fn total1() -> u32 { let mut total = 0; for i in 0..10 { if i % 2 == 0 { total += i; } } total } fn total2() -> u32 { (0..10) .filter(|x| x % 2 == 0) .fold(0, |x, y| x + y) } ``` OK, I can follow that... --- ## And more Add `3` to the number after you know it's even ```rust fn total1() -> u32 { let mut total = 0; for mut i in 0..10 { if i % 2 == 0 { i += 3; total += i; } } total } ``` ```rust fn total2() -> u32 { (0..10) .filter(|x| x % 2 == 0) .map(|x| x + 3) .fold(0, |x, y| x + y) } ``` <aside class="notes">Ask the audience where it's easier to find the bug</aside> --- ## Types ```python class Person: def __init__(self, name): self.name = name def greet(self): print "Hello", self.name Person("Alice").greet() ``` ```haskell data Person = Person { name :: String } greet :: Person -> IO () greet (Person name) = putStrLn $ "Hello " ++ name main :: IO () main = greet $ Person {name = "Alice"} ``` So many annoying things in the Haskell code! --- ## And requirements change ```python class Person: def __init__(self, first, last): self.first = first self.last = last def greet(self): print "Hello", self.first, self.last Person("Alice").greet() ``` * Bug will only be caught at runtime * Better make sure your test suite covers this! --- ## Types to the rescue ```haskell data Person = Person { first :: String, last :: String } greet :: Person -> IO () greet (Person first last) = putStrLn $ "Hello " ++ first ++ " " ++ last main :: IO () main = greet $ Person {name = "Alice"} ``` * Caught at compile time * No tests required * Minimal example like this isn't particularly compelling * *Really* compelling on large code bases --- ## Dysfunctional Haskell Our favorite language can fail too! ```haskell import Control.Concurrent main :: IO () main = do account1 <- newMVar 50 account2 <- newMVar 60 forkIO $ transfer account1 account2 20 transfer account2 account1 10 readMVar account1 >>= print readMVar account2 >>= print ``` ```haskell transfer :: MVar Int -> MVar Int -> Int -> IO () transfer fromVar toVar amt = modifyMVar_ fromVar $ \from -> do threadDelay 100000 -- simulate some expensive stuff modifyMVar_ toVar $ \to -> pure $ to + amt pure $ from - amt ``` --- ## Needs moar FP ```haskell import Control.Concurrent import Control.Concurrent.Async import Control.Concurrent.STM main :: IO () main = do account1 <- newTVarIO 50 account2 <- newTVarIO 60 concurrently_ (transfer account1 account2 20) (transfer account2 account1 10) atomically (readTVar account1) >>= print atomically (readTVar account2) >>= print ``` ```haskell transfer :: TVar Int -> TVar Int -> Int -> IO () transfer fromVar toVar amt = atomically $ do modifyTVar fromVar (\from -> from - amt) modifyTVar toVar (\to -> to + amt) ``` --- ## What can we do? * Keep getting FP ideas out into the mainstream * People aren't afraid of folds anymore * FP is mostly seen as an advantage today * Learning curve is reduced * Make it fun, easy, and exciting to learn an FP language * Change the conversation: get people talking about long term maintainability * Talk about practical benefits * Would love to have some data to back up my claims --- ## Three pillars How we analyze things at FP Complete * Performance * Computing speed/hardware requirements * Productivity * Time to market * Maintainability * Bug rate, evolving a codebase All three are vitally important, all three need to be in the conversation --- ## The rest of my story * Moved into Java web dev * So painful that I tried PHP and _liked it_ * Eventually admitted that the long term pain was too high * Jumped to Perl, D, tried others * Perl 6 looks fun, what's this Pugs thing * Pugs is written in Haskell... what's that? --- ## Learning Haskell * I like math, let's do this! * Docs suck, tools suck, this isn't a real language * Played with it anyway, fell in love * Through a lot of luck (or self delusion), found a really great language * And years later, I'm convinced it meets the goal of maintaining large software projects --- ## Don't rely on luck! * Others may not be stubborn like we have been * Far too easy to make an initial guess and get locked into a bad choice * We have a responsibility to help our industry * Talk about the benefits of FP * Improve the conversation * Enjoy the conference, learn a lot! --- ## Thank you!