Я реализовал кодированный Скоттом тип List
в Javascript вместе с перегруженной функцией append
, которая имитирует класс типов Semigroup
.
append
работает просто отлично, но для больших списков это унесет стек. Вот решающая часть моей реализации:
appendAdd("List/List", tx => ty => tx.runList({
Nil: ty,
Cons: x => tx_ => Cons(x) (append(tx_) (ty))
}));
Обычно я использую батут, чтобы избежать растущего стека, но это предполагает рекурсию хвоста и поэтому не будет работать в этом случае.
Поскольку эта реализация основана на Haskell, я полагаю, что ленивая оценка и защищенная рекурсия / хвостовая рекурсия по модулю имеют значение:
(++) [] ys = ys
(++) (x:xs) ys = x : xs ++ ys
При условии, что я правильно понимаю, из-за ленивой оценки (почти) ничего не происходит в Haskell, когда я добавляю список в другой, пока я фактически не сделаю что-то с этим новым списком. В приведенном ниже примере я сложу его.
Что я не понимаю, так это то, как защищенная рекурсия может препятствовать росту рекурсии в стеке вызовов и может ли это поведение быть реализовано явно в строго оцененном языке, таком как Javascript.
Надеюсь, этот вопрос не слишком широк.
Для лучшего понимания, вот полная реализация / пример:
// type constructor
const Type = name => {
const Type = tag => Dcons => {
const t = new Tcons();
Object.defineProperty(
t,
`run${name}`,
{value: Dcons});
t[TAG] = tag;
return t;
};
const Tcons =
Function(`return function ${name}() {}`) ();
Tcons.prototype[Symbol.toStringTag] = name;
return Type;
};
const TAG = Symbol("TAG");
const List = Type("List");
// data constructors
const Cons = x => tx => List("Cons") (cases => cases.Cons(x) (tx));
const Nil = List("Nil") (cases => cases.Nil);
// overload binary functions
const overload2 = (name, dispatch) => {
const pairs = new Map();
return {
[`${name}Add`]: (k, v) => pairs.set(k, v),
[`${name}Lookup`]: k => pairs.get(k),
[name]: x => y => {
if (typeof x === "function" && (VALUE in x))
x = x(y);
else if (typeof y === "function" && (VALUE in y))
y = y(x);
const r = pairs.get(dispatch(x, y));
if (r === undefined)
throw new TypeError("...");
else if (typeof r === "function")
return r(x) (y);
else return r;
}
}
};
const dispatcher = (...args) => args.map(arg => {
const tag = Object.prototype.toString.call(arg);
return tag.slice(tag.lastIndexOf(" ") + 1, -1);
}).join("/");
// Semigroup "typeclass"
const {appendAdd, appendLookup, append} =
overload2("append", dispatcher);
// List instance for Semigroup
appendAdd("List/List", tx => ty => tx.runList({
Nil: ty,
Cons: x => tx_ => Cons(x) (append(tx_) (ty))
}));
// fold
const foldr = f => acc => {
const aux = tx =>
tx.runList({
Nil: acc,
Cons: x => tx_ => f(x) (aux(tx_))})
return aux;
};
// data
const tx = Cons(1) (Cons(2) (Nil));
const ty = Cons(3) (Cons(4) (Nil));
const tz = append(tx) (ty);
// run
console.log(
foldr(x => acc => `${x}${acc}`) (0) (tz) // "12340"
);