Here's my implementation of State Monad in TypeScript, based on a canonical Haskell implementation. I would like it to get code reviewed.
class StateMonad<S, A> {
constructor(public runState: (s: S) => ({ s: S, a: A })) {
}
static return_<S, A>(a: A): StateMonad<S, A> {
return new StateMonad(s => ({ s, a }));
}
bind<B>(func: (a: A) => StateMonad<S, B>): StateMonad<S, B> {
return new StateMonad<S, B>((s: S) => {
const { s: s_, a } = this.runState(s);
return func(a).runState(s_);
});
}
}
// aux monad factory
const createCounter = (regex: RegExp) => new StateMonad((s: string) =>
s.split('')
.reduce((acc, c) =>
(regex.test(c)) ? { s: acc.s.replace(c, ''), a: acc.a + 1 } : acc,
{ s, a: 0 })
);
const countLowerCase = createCounter(/[a-z]/);
const countDigits = createCounter(/[0-9]/);
// usage example
const { a } = countLowerCase /* -- haskell equivalent */
.bind(n1 => countDigits /* do n1 <- countLowerCase */
.bind(n2 => StateMonad /* n2 <- countDigits */
.return_(n1 + n2))) /* return n1 + n2 */
.runState("abc123ABC");
StateMonad
? If so why would you need it? \$\endgroup\$const { s: s_, a } = this.runState(s);
. Object decomposition seems to be broken in parts: _s
. Where does the_s
come from? Is it a bug or I am missing something? \$\endgroup\$s
is already in scope. In fact you could have copied this code typescript online compiler and seen what it compiles to. \$\endgroup\$return_
being ugly for a public interface (just usereturn
) andcreateCounter
being unnecessarily complex (just make the regular expression global anda
will be the difference in lengths of the resulting string and original string) \$\endgroup\$