Succinct JavaScript?
Published: 2020-09-28 16:18:10

It goes with no saying, I am really liking [Clojure](https://clojure.org) lately.

Although I do not use in my day to day job (yet?), just in the same way I did with Haskell with regards of [Prism](https://dev.to/vncz/forewords-and-domain-model-1p13), I like to see how other languages approach problems and try to apply their ideas back to TypeScript.

Last week I've been playing with the [Game of pure strategy problem](https://en.wikipedia.org/wiki/Goofspiel), and try to implement a possible solution in Clojure.

After some back and forth and some good feedback from the Clojurians Slack channel, I ended up with a next state function that looks like this:

```clojure
(def initial-state "Initial game state"
  {:first-player {:deck #{1 2 3 4 5 6 7 8 9 10 11 12 13} :score 0}
   :second-player {:deck #{1 2 3 4 5 6 7 8 9 10 11 12 13} :score 0}
   :bounty-deck #{1 2 3 4 5 6 7 8 9 10 11 12 13}})

(defn random-card-strategy [deck] (rand-nth (seq deck)))
(defn highest-card-strategy [deck] (apply max (seq deck)))
(def draw-card random-card-strategy)

(defn game-step [current-state]
  (let [{:keys [bounty-deck first-player second-player]} current-state
        drawn-card (draw-card bounty-deck)
        first-player-card (random-card-strategy (:deck first-player))
        second-player-card (highest-card-strategy (:deck second-player))
        match-winner (if (> first-player-card second-player-card) :first-player :second-player)]
    (-> current-state
        (update-in [match-winner :score] inc)
        (update-in [:bounty-deck] disj drawn-card)
        (update-in [:first-player :deck] disj first-player-card)
        (update-in [:second-player :deck] disj second-player-card))))
```

It's kind of known that LISP is generally a very dense language; if we also take in consideration the rich set of Clojure's seq functions and macros, turns out that small chunks of code can do a lot of things in Clojure.

I am (still) primarily a JavaScript/TypeScript developer, and I have been asking "What is the most succinct code that I can write to achieve the same end result?

I cobbled something some code as fast as possible and this is the base line I started from:

```ts
const initialState: State = {
  bountyDeck: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
  firstPlayer: {
    score: 0,
    deck: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
  },
  secondPlayer: {
    score: 0,
    deck: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
  }
}

const randomCardStrategy = (deck: number[]) => deck[Math.floor(Math.random() * deck.length)];
const highestCardStrategy = (deck: number[]) => Math.max.apply(null, deck);
const drawCard = randomCardStrategy;

const gameStep = (currentState: State): State => {
  const drawnCard = drawCard(currentState.bountyDeck);
  const firstPlayerCard = randomCardStrategy(currentState.firstPlayer.deck);
  const secondPlayerCard = highestCardStrategy(currentState.firstPlayer.deck);
  const matchWinner = firstPlayerCard > secondPlayerCard ? 'firstPlayer' : 'secondPlayer'

  return {
    bountyDeck: currentState.bountyDeck.splice(currentState.bountyDeck.indexOf(drawnCard), 1),
    firstPlayer: {
      deck: currentState.firstPlayer.deck.filter(c => c >== firstPlayerCard),
      score: matchWinner === 'firstPlayer' ? currentState.firstPlayer.score++ : currentState.firstPlayer.score
    },
    secondPlayer: {
      deck: currentState.secondPlayer.deck.filter(c => c >== firstPlayerCard),
      score: matchWinner === 'secondPlayer' ? currentState.firstPlayer.score++ : currentState.firstPlayer.score
    },
  }
}
```

Note that I have omitted the type definitions in this snippet, since they're not part of the actual code.

It's clear enough that the problem here is that JavaScript does not have a great story to mutate stuff in place, but we can probably do something better by using a Lens library: [monocle-ts](https://github.com/gcanti/monocle-ts)

{% gist https://gist.github.com/XVincentX/395a6a4631d9dd161666655d2321f8ae %}

Without the boilerplate, the final code that we should take in account is:

```ts
const gameStep = (currentState: State): State => {
  const drawnCard = drawCard(currentState.bountyDeck);
  const firstPlayerCard = randomCardStrategy(currentState.firstPlayer.deck);
  const secondPlayerCard = highestCardStrategy(currentState.firstPlayer.deck);
  const matchWinner = firstPlayerCard > secondPlayerCard ? "firstPlayer" : "secondPlayer";

  return pipe(
    currentState,
    matchWinner === "firstPlayer" ? incrementFirstPlayerScore : incrementSecondPlayerScore,
    removeBountyDeckCard(drawnCard),
    removeFirstPlayerDrawnCard(firstPlayerCard),
    removeSecondPlayerDrawnCard(secondPlayerCard)
  );
};
```

…which is not exactly that far from the Clojure counterpart.

1. JavaScript is missing a good story for data manipulation out of the box. You need lenses and/or collection libraries to effectively manipulate stuff. Still, it's not an excuse to write bad code.
2. Without the dot notation (`a.b.c`), destructuring and threading macro become very important in Clojure. Without them this:

`(-> structure :first-player :deck :card :somethingelse)`
would be:
`(:somethingelse (:card (:deck (:first-player structure))))`

which I guess ain't that cool.

P.S: I am aware of [ImmutableJS](https://github.com/immutable-js/immutable-js). However their story around typings is not that polished as the one that monocle-ts is offering, so I removed it early from the selection.