\$\begingroup\$
\$\endgroup\$
6
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");
200_success
146k22 gold badges190 silver badges479 bronze badges
asked Apr 5, 2018 at 19:55
1 Answer 1
\$\begingroup\$
\$\endgroup\$
1
I'm probably off my rocker here, but what if rather than nested chains of bind, you used a fork/join type of approach?
static combine<T, S, R>(
monads: { [P in keyof T]: StateMonad<S, T[P]> },
selector: (values: T) => R
)
: StateMonad<S, R> {
return new StateMonad<S, R>((state) => {
var ret: any = {};
for (const key in monads) {
if (monads.hasOwnProperty(key)) {
ret[key] = monads[key].runState(state).a;
}
}
return { a: selector(ret), s: state }
}
);
}
and
var { a } = StateMonad
.combine(
{
countLowerCase,
countDigits
},
combined =>
combined.countLowerCase +
combined.countDigits
)
.runState("abc123ABC");
-
1\$\begingroup\$ Welcome to Code Review! You have presented an alternative solution, but haven't reviewed the code. Please explain your reasoning (how your solution works and why it is better than the original) so that the author and other readers can learn from your thought process. \$\endgroup\$Dan Oberlam– Dan Oberlam2018年06月21日 02:26:57 +00:00Commented Jun 21, 2018 at 2:26
lang-js
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\$