[Functional Programming] Randomly Pull an Item from an Array with the State ADT (Pair)
Functor
composition is a powerful concept that arises when we have one Functor
nested in another Functor
. It becomes even more powerful when both of those are Chain
s, allowing us to apply each Functor
’s special properties and effects for a given computation.
We witness this power first hand by combining our beloved State
with a Pair
type mixing the ability to randomly pick an index from an Array
using State
and use it to draw an element from the Array
. We will build up to an easy to use interface that allows us to pull as many elements as we need, by chaining on single State transaction.
// getDeck :: () -> State AppState Deck const getDeck = () => generateCards() .map(xs => Pair([], xs)); /** Pair( [ ], [ { id: "orange-square", color: "orange", shape: "square" }, { id: "orange-triangle", color: "orange", shape: "triangle" }, { id: "orange-circle", color: "orange", shape: "circle" }, { id: "green-square", color: "green", shape: "square" }, { id: "green-triangle", color: "green", shape: "triangle" }, { id: "green-circle", color: "green", shape: "circle" }, { id: "blue-square", color: "blue", shape: "square" }, { id: "blue-triangle", color: "blue", shape: "triangle" }, { id: "blue-circle", color: "blue", shape: "circle" }, { id: "yellow-square", color: "yellow", shape: "square" }, { id: "yellow-triangle", color: "yellow", shape: "triangle" }, { id: "yellow-circle", color: "yellow", shape: "circle" } ] ) */ // draw :: Integer -> Deck -> Deck const draw = compose( chain, drawCardAt ); // const draw = index => deck => deck.chain(drawCardAt(index)) // drawRandom :: Deck -> State AppState Deck // From the right side pair, get a random index, then draw the card by index const drawRandom = converge( liftA2(draw), compose( randomIndex, snd ), liftState(identity) ) console.log( getDeck() .chain(drawRandom) .chain(drawRandom) .chain(drawRandom) .evalWith(state).fst() ) /** * [ { id: 'orange-square', color: 'orange', shape: 'square' }, { id: 'blue-triangle', color: 'blue', shape: 'triangle' }, { id: 'blue-square', color: 'blue', shape: 'square' } ] */ console.log( getDeck() .chain(drawRandom) .chain(drawRandom) .chain(drawRandom) .evalWith(state).snd() ) /** [ { id: 'orange-triangle', color: 'orange', shape: 'triangle' }, { id: 'orange-circle', color: 'orange', shape: 'circle' }, { id: 'green-square', color: 'green', shape: 'square' }, { id: 'green-triangle', color: 'green', shape: 'triangle' }, { id: 'green-circle', color: 'green', shape: 'circle' }, { id: 'blue-circle', color: 'blue', shape: 'circle' }, { id: 'yellow-square', color: 'yellow', shape: 'square' }, { id: 'yellow-triangle', color: 'yellow', shape: 'triangle' }, { id: 'yellow-circle', color: 'yellow', shape: 'circle' } ] */
------
const {prop,assoc, Pair, pick, bimap, State, snd, identity, omit, curry, filter, fanout, converge,map, composeK, liftA2, equals, constant,option, chain, mapProps, find, propEq, isNumber, compose, safe} = require('crocks'); const {get, modify, of} = State; const state = { colors: [ 'orange', 'green', 'blue', 'yellow' ], shapes: [ 'square', 'triangle', 'circle' ], seed: Date.now() }; const liftState = (fn) => compose( of, fn ); const getState = key => get(prop(key)) // #region random // nextSeed :: Integer -> Integer const nextSeed = seed => (seed * 1103515245 + 12345) & 0x7fffffff // value :: Integer -> Number const value = seed => (seed >>> 16) / 0x7fff // normalize :: (Integer, Integer) -> Number -> Integer const normalize = (min, max) => x => Math.floor(x * (max - min)) + min // getNextSeed :: () -> State AppState Integer const getNextSeed = () => get(({ seed }) => nextSeed(seed)) // updateSeed :: Integer -> State AppState () const updateSeed = seed => modify(assoc('seed', seed)) // nextValue :: Integer -> State AppState Number const nextValue = converge( liftA2(constant), liftState(value), updateSeed ) // random :: () -> State AppState Number const random = composeK(nextValue, getNextSeed) // between :: (Integer, Integer) -> State AppState Integer const between = (min, max) => random() .map(normalize(min, max)); const randomIndex = xs => between(0, xs.length); // #endregion // #region generate const getColors = () => getState('colors').map(option([])); const getShapes = () => getState('shapes').map(option([])); const buildCard = curry((color, shape) => ({ id: `${color}-${shape}`, color, shape })); const buildCards = liftA2(buildCard); const generateCards = converge( liftA2(buildCards), getColors, getShapes ); // #endregion // #region draw const getAt = index => array => Array.of(array[index]); const unsetAt = index => array => [ ...array.slice(0, index), ...array.slice(index + 1) ]; const drawCardAt = index => fanout(getAt(index), unsetAt(index)); // #endregion // getDeck :: () -> State AppState Deck const getDeck = () => generateCards() .map(xs => Pair([], xs)); // draw :: Integer -> Deck -> Deck const draw = compose( chain, drawCardAt ); // const draw = index => deck => deck.chain(drawCardAt(index)) // drawRandom :: Deck -> State AppState Deck const drawRandom = converge( liftA2(draw), compose( randomIndex, snd ), liftState(identity) ) console.log( getDeck() .chain(drawRandom) .chain(drawRandom) .chain(drawRandom) .chain(drawRandom) .evalWith(state).fst() )
【推荐】国内首个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-01-18 [Angular] Using ngOnChanges lifeCycle hook to break object reference
2017-01-18 [React] Public Class Fields with React Components
2017-01-18 [RxJS] Use groupBy in real RxJS applications
2017-01-18 [Ramda] Curry and Uncurry Functions with Ramda
2015-01-18 [AngularJS] New in Angular 1.3 - $httpProvider.useApplyAsync