[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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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)
);
复制代码

 

  

 

posted @   Zhentiw  阅读(186)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2017-03-27 [Grid Layout] Describe a grid layout using named grid lines
2017-03-27 [Grid Layout] Specify a grid gutter size with grid-gap
2017-03-27 [Grid Layout] Place grid items on a grid using grid-column and grid-row
2016-03-27 [Angular 2] implements OnInit, OnDestory for fetching data from server
点击右上角即可分享
微信分享提示