实现一个通用的半群附加在打字稿中

时间:2018-04-09 17:24:00

标签: typescript functional-programming

假设我想在打字稿中实现semigroup,半群就会附加到自身以获得新的semigroup

所以界面看起来像这样:

export interface Semigroup<A> {
  append: (x: A, y: A) => A;
}

但是说我希望有一个可以在对象和数组上工作的通用追加实现,然后我想不出在类型脚本中这样做的合理方法。

我可以按照fp-ts中的每种类型执行不同的实现:

export const getArraySemigroup = <A>(): Semigroup<Array<A>> => ({
  concat: (x, y) => x.concat(y)
});

assert.deepEqual(getArraySemigroup<number>().concat([1], [2]), [1, 2])

export const getObjectSemigroup = <O extends Object>(): Semigroup<O> => {
  return { append: (x, y) => Object.assign({}, x, y) };
};

expect(
  getObjectSemigroup<T>().append(
    { one: 1, two: 2 },
    { two: "two", three: 3 }
  )
).toEqual({
  one: 1,
  two: "two",
  three: 3
});

有没有任何类型安全的方法,我可以有一个genefic append函数,可以接受一个数组或一个对象,然后调用getArraySemigroup或getObjectSemigroup的正确实现?

1 个答案:

答案 0 :(得分:1)

我真的不知道为什么要将这两个函数合并为一个函数,尤其是对于对象类型,您需要一个额外的参数。您可以使用function overloads以某种类型安全的方式执行此操作,如下所示:

function getArrayOrObjectSemigroup<A>(arr: Array<A>): Semigroup<Array<A>>;
function getArrayOrObjectSemigroup<O extends { [key: string]: any }>(
  obj: O, 
  semigroups: { [K in keyof O]: Semigroup<O[K]> }
): Semigroup<O>;
function getArrayOrObjectSemigroup<T>(
  arrOrObj: Array<T> | T, 
  semigroups?: { [K in keyof T]: Semigroup<T[K]> }
): Semigroup<Array<T>> | Semigroup<T> {
  if (Array.isArray(arrOrObj)) {
    return getArraySemigroup<T>();
  }
  if (semigroups) {
    return getObjectSemigroup(semigroups);
  }
  throw new Error(
    'You cannot call getArrayOrObjectSemigroup(array, semigroups), sorry.');
}

可以以任何一种方式调用:

const arraySemigroup = getArrayOrObjectSemigroup([1, 2, 3]); // Semigroup<number[]>

const objectSemigroup = getArrayOrObjectSemigroup(
  { a: [1, 2], b: "hey" }, 
  { a: getArraySemigroup<number>(), b: { append: (x, y) => x + y } }
); // Semigroup<{ a: number[]; b: string; }>

但这似乎并不值得,因为你仍然需要在编译时知道哪个实现会被调用,所以你不妨只使用两个函数。如果这不是你想要的,你可能需要在你的问题中加入更多的信息来充实你的用例。

希望这是有道理的。祝好运。

更新:您似乎已在问题中编辑了semigroups参数。我不明白使用琐碎的Object.assign({},x,y)(基本上y除非它缺少一些键)作为对物体的附加操作,但如果那是你想要的您可以将我的上述解决方案更改为以下内容:

function getArrayOrObjectSemigroup<T extends Array<any> | object>(
  arrOrObj: T
): T extends any ? Semigroup<T>: never;
function getArrayOrObjectSemigroup<T>(
  arrOrObj: Array<T> | T
): Semigroup<Array<T>> | Semigroup<T> {
  return Array.isArray(arrOrObj) ? getArraySemigroup<T>() : getObjectSemigroup<T>();
}

这至少可以在编译时调用,如果你有一个数组或对象,因为两个实现现在需要相同数量的参数:

const arraySemigroup = getArrayOrObjectSemigroup([1, 2, 3]); // Semigroup<number[]>
const objectSemigroup = getArrayOrObjectSemigroup(
  { a: [1, 2], b: "hey" }
); // Semigroup<{ a: number[]; b: string; }>
const whoKnows = Math.random() < 0.5 ? [1, 2, 3] : { a: 'who', b: 'knows' };
const whoKnowsSemigroup = getArrayOrObjectSemigroup(whoKnows);
// Semigroup<number[]> | Semigroup<{ a: string; b: string; }>

但是,我仍然不了解你的用例。你刚刚和半群人一起玩吗?由上述函数生成的函数似乎并没有多大用处。至少具有semigroups参数的那个具有允许调用者决定将哪个半群用于每个属性而不是仅使用最后定义的值的吸引力。无论如何,这可能是我可以用的。祝你好运!