[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) );
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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