Hangman in stateless JavaScript
Such a great question,


hasLost and hasWon should be functions
You should write functions with the keyword function, otherwise you get a mess of anonymous functions in stack-traces. The fat arrow syntax was only meant for inline functions.
head is not a great variable name, I don't know what it is supposed to be from the name
On the whole, the challenge in pure fp is to never assign, which you do a few times, read this for good measure: https://stackoverflow.com/a/33114654/7602
I am not sure I like that the guess function creates a new Hangman object every single time, there has to be a cleaner approach.


I found an interesting guide: https://drboolean.gitbooks.io/mostly-adequate-guide/ which states that this should be avoided.. Meanwhile, this is my this littered approach. It follows all my recommendations, and shows where I feel fat arrows have a place. I will try to rewrite again once I finish the guide ;)

function Hangman(word, copy, mistakes){

  //Initialize again, no assignments here ;)
  if(!copy){
    return Hangman(word, '_'.repeat(word.length), 0 );
  }

  //Non Exposed functions requiring context
  const won = (() =&gt; {
    return function won(){
      return word == copy;                             
    };                          
  })();

  const lost = (() =&gt; {
    return function lost(){
      return mistakes &gt; 5;
    }
  })();

  const updateMistakes = (() =&gt; {
    return function updateMistakes(oldCopy , newCopy){
      return mistakes += oldCopy == newCopy ? 1 : 0;
    }
  })();

  const applyGuess = (() =&gt; {
    return function applyGuess(letter){
      return copy  = word.split('').map( (c,i) =&gt; letter == c ?  c : copy[i] ).join('');
    }
  })();

  //Exposed Functions
  function guess(){
    return function guess(letter){ 
      if(!done()){
        updateMistakes( copy , applyGuess(letter) );      
      }
      this.display();
      return this;
    };
  }

  function display(){
    return function display(){
      console.log( won()? 'You won' : lost() ? 'You lost' : copy + ' ' + mistakes );
    };
  }

  //Return object
  return {
    guess : guess(),
    display: display(),    
  }

  //Pure function?
  function done(){
    return won() || lost();
  }  

}

const game = Hangman('Agile Development');
game.guess('e').guess('a').guess('t').guess('z');


Okay, 2nd version with far less this, many more clean functions:

function Hangman(word, copy, mistakes){

  //Initialize again, no assignments here ;)
  if(!copy){
    return Hangman(word, '_'.repeat(word.length), 0 );
  }

  //Exposed Functions
  function guess(){
    return function guess(letter){ 
      if( !done( won(word,copy), lost(mistakes) ) ){
        //An assignment, it was unavoidable I think
        mistakes = updateMistakes( mistakes, copy , copy = applyGuess(word,copy,letter) );      
      }
      display(word, copy, mistakes, letter );
      return this;
    };
  }

  //Return object, kind pointless now that there is only 1 function :/
  return {
    guess : guess()
  }

  //Not entirely pure function, not cacheable
  function display(word, copy, mistakes, letter){
    console.log( won(word,copy) ? 'You won' :
                 lost(mistakes) ? 'You lost' :
                 copy + ' ' + mistakes + ' You tried (' + letter + ')' );
  }

  //Pure functions!!
  function applyGuess(word, copy, letter){
    return word.split('').map( (c,i) =&gt; letter == c ?  c : copy[i] ).join('');
  }

  function updateMistakes(mistakes, oldCopy , newCopy){
    return mistakes + (oldCopy == newCopy ? 1 : 0 );
  }

  function done(won, lost){
    return won || lost;
  }

  function won(word, copy){
    return word == copy;                             
  }   

  function lost(mistakes){
      return mistakes &gt; 5;
  }

}

const game = Hangman('Agile Development');
game.guess('e').guess('a').guess('t').guess('z');