[Functional Programming] Using Last monoid with Maybe

Imaging we have a deck of cards, eveytimes we need to pick one card from deck, the result we want to have is:

// Selected: "A♠", 
// Remaining: [ "2♠", "3♠", "4♠", "5♠", "6♠", "7♠", "8♠", "9♠", "10♠", "J♠", "Q♠", "K♠", "A♣", "2♣", "3♣", "4♣", "5♣", "6♣", "7♣", "8♣", "9♣", "10♣", "J♣", "Q♣", "K♣", "A♥", "2♥", "3♥", "4♥", "5♥", "6♥", "7♥", "8♥", "9♥", "10♥", "J♥", "Q♥", "K♥", "A♦", "2♦", "3♦", "4♦", "5♦", "6♦", "7♦", "8♦", "9♦", "10♦", "J♦", "Q♦", "K♦" ] '

For selected, each time we want only one! remaining should be all the rest of cards which have been choosen. If we draw more than one time, remining cards should be reduced.

For *Selected + remaining* pair, we can consider to use 'Pair' monad from ADT. Then our results looks like:

// 'Pair( "A♠", [ "2♠", "3♠", "4♠", "5♠", "6♠", "7♠", "8♠", "9♠", "10♠", "J♠", "Q♠", "K♠", "A♣", "2♣", "3♣", "4♣", "5♣", "6♣", "7♣", "8♣", "9♣", "10♣", "J♣", "Q♣", "K♣", "A♥", "2♥", "3♥", "4♥", "5♥", "6♥", "7♥", "8♥", "9♥", "10♥", "J♥", "Q♥", "K♥", "A♦", "2♦", "3♦", "4♦", "5♦", "6♦", "7♦", "8♦", "9♦", "10♦", "J♦", "Q♦", "K♦" ] )'

If chain drawCard a scond time, the results should be:

// 'Pair( "2♠", [ "3♠", "4♠", "5♠", "6♠", "7♠", "8♠", "9♠", "10♠", "J♠", "Q♠", "K♠", "A♣", "2♣", "3♣", "4♣", "5♣", "6♣", "7♣", "8♣", "9♣", "10♣", "J♣", "Q♣", "K♣", "A♥", "2♥", "3♥", "4♥", "5♥", "6♥", "7♥", "8♥", "9♥", "10♥", "J♥", "Q♥", "K♥", "A♦", "2♦", "3♦", "4♦", "5♦", "6♦", "7♦", "8♦", "9♦", "10♦", "J♦", "Q♦", "K♦" ] )'

Now we can pretty much know the Pair should be:

// Deck :: Pair (Last Card) [Card]

We use Last and Array tow semigourps, so the value know how to concat themselves, 'Last' will just keep the last value, Array will concat each other.

We have deck.js file which provides data:

const Last = require('crocks/Last');
const Pair = require('crocks/Pair');

const assign = require('crocks/helpers/assign');
const chain = require('crocks/pointfree/chain');
const liftA2 = require('crocks/helpers/liftA2');
const map = require('crocks/pointfree/map');
const reduce = require('crocks/pointfree/reduce')

const suits = [
    { suit: '♠', color: 'dark' },
    { suit: '♣', color: 'dark' },
    { suit: '♥', color: 'light' },
    { suit: '♦', color: 'light' },
  ]

const values = [
    { value: 1, face: 'A' },
    { value: 2, face: '2' },
    { value: 3, face: '3' },
    { value: 4, face: '4' },
    { value: 5, face: '5' },
    { value: 6, face: '6' },
    { value: 7, face: '7' },
    { value: 8, face: '8' },
    { value: 9, face: '9' },
    { value: 10, face: '10' },
    { value: 11, face: 'J' },
    { value: 12, face: 'Q' },
    { value: 13, face: 'K' },
  ];

// Deck :: Pair (Last Card) [Card]  
// deck :: Deck
const deck = Pair(Last.empty(), liftA2(assign, suits, values));
// displayCard :: Card -> String
const displayCard = ({face, suit}) =>
  `${face}${suit}`;
// displayCards :: [Card] -> [String]
const displayCards = map(displayCard);

// pickCard : [ Card ] -> Pair [Card][Card]
const pickCard = cs => {
  const idx = Math.floor(Math.random() * cs.length);

  return Pair(
      [].concat(cs[idx]),
      cs.slice(0, idx).concat(cs.slice(idx + 1))
  )
}
// shuffleCards : [ Cards ] -> [ Cards ]
const shuffleCards = cards => reduce(
  chain(pickCard), Pair([], cards), cards
).fst();

module.exports = {
    deck,
    displayCard,
    displayCards,
    pickCard,
    shuffleCards
}

Important here, is to know how we define our 'deck' to Pair monad with Last and Array.

// Deck :: Pair (Last Card) [Card]  
// deck :: Deck
const deck = Pair(Last.empty(), liftA2(assign, suits, values));

For our consumer part, we can do:

const log = require('./lib/log');
const Last = require('crocks/Last');
const Pair = require('crocks/Pair');

const bimap = require('crocks/pointfree/bimap');

const {deck, displayCard, displayCards} = require('./model/deck');

const look = bimap(
    x => displayCard(x.option('')), 
    displayCards
);

// Deck :: Pair (Last Card) [Card]
// drawCard : Int -> [Card] -> Deck
const drawCard = indx => deck => {
    return Pair(
        Last(deck[indx]),
        deck.slice(0, indx).concat(deck.slice(indx + 1))
    )
}

const m = deck
    .chain(drawCard(0))
    .chain(drawCard(0))
    .chain(drawCard(0))
    .chain(drawCard(0));

log(
    look(m)
);
// 'Pair( "4♠", [ "5♠", "6♠", "7♠", "8♠", "9♠", "10♠", "J♠", "Q♠", "K♠", "A♣", "2♣", "3♣", "4♣", "5♣", "6♣", "7♣", "8♣", "9♣", "10♣", "J♣", "Q♣", "K♣", "A♥", "2♥", "3♥", "4♥", "5♥", "6♥", "7♥", "8♥", "9♥", "10♥", "J♥", "Q♥", "K♥", "A♦", "2♦", "3♦", "4♦", "5♦", "6♦", "7♦", "8♦", "9♦", "10♦", "J♦", "Q♦", "K♦" ] )'

Thre is one problem: 

if we change last chain call to:

const m = deck
    .chain(drawCard(0))
    .chain(drawCard(0))
    .chain(drawCard(0))
    .chain(drawCard(99));

Our code throw error:

TypeError: Cannot match against 'undefined' or 'null'.

This is because, index 99 is out or range;

Last(deck[indx]),

Because Last can take a single value or a Maybe type, and return when value is present and wrap with Just, or return Nothing, which means we can prevent this error happens by add Maybe:

const safe = require('crocks/Maybe/safe');
const isDefined = require('crocks/predicates/isDefined');

// isValid :: a -> Maybe a
const isValid = safe(isDefined);

// Deck :: Pair (Last Card) [Card]
// drawCard : Int -> [Card] -> Deck
const drawCard = indx => deck => {
    return Pair(
        // Last can take Maybe or value in arguement, 
        // make it possible to control the code safety
        Last(isValid(deck[indx])),
        deck.slice(0, indx).concat(deck.slice(indx + 1))
    )
}

Now if we still try to get 99 index, it will just ignore it:

const m = deck
    .chain(drawCard(0))
    .chain(drawCard(0))
    .chain(drawCard(0))
    .chain(drawCard(99));

log(
    look(m)
);
//'Pair( "3♠", [ "4♠", "5♠", "6♠", "7♠", "8♠", "9♠", "10♠", "J♠", "Q♠", "K♠", "A♣", "2♣", "3♣", "4♣", "5♣", "6♣", "7♣", "8♣", "9♣", "10♣", "J♣", "Q♣", "K♣", "A♥", "2♥", "3♥", "4♥", "5♥", "6♥", "7♥", "8♥", "9♥", "10♥", "J♥", "Q♥", "K♥", "A♦", "2♦", "3♦", "4♦", "5♦", "6♦", "7♦", "8♦", "9♦", "10♦", "J♦", "Q♦", "K♦" ] )'

---

Full code index.jks:

const log = require('./lib/log');
const Last = require('crocks/Last');
const Pair = require('crocks/Pair');

const bimap = require('crocks/pointfree/bimap');
const safe = require('crocks/Maybe/safe');
const isDefined = require('crocks/predicates/isDefined');

const {deck, displayCard, displayCards} = require('./model/deck');

const look = bimap(
    x => displayCard(x.option('')), 
    displayCards
);

// isValid :: a -> Maybe a
const isValid = safe(isDefined);

// Deck :: Pair (Last Card) [Card]
// drawCard : Int -> [Card] -> Deck
const drawCard = indx => deck => {
    return Pair(
        // Last can take Maybe or value in arguement, 
        // make it possible to control the code safety
        Last(isValid(deck[indx])),
        deck.slice(0, indx).concat(deck.slice(indx + 1))
    )
}

const m = deck
    .chain(drawCard(0))
    .chain(drawCard(0))
    .chain(drawCard(0))
    .chain(drawCard(99));

log(
    look(m)
);

  

原文地址:https://www.cnblogs.com/Answer1215/p/10609818.html