给定一个可能为null的对象,并且可能具有以下属性:
{
templateId: "template1",
templates: {
template1: "hello"
}
}
您如何以故障安全方式获取模板? (可能未定义templateId,或者它所引用的模板可能未定义)
我使用ramda并试图调整我的代码的朴素版本以使用类似于adt的东西来避免显式的null / undefined检查。
我没有提出一个优雅而干净的解决方案。
天真的ramda版本:
const getTemplate = obj => {
const templateId = obj && prop("templateId", obj);
const template = templateId != null && path(["template", templateId], obj);
return template;
}
这确实有效,但我想避免空检查,因为我的代码还有很多东西要做,而且要变得更干净真的很好
修改 我从几个答案中得到的结论是,最好是先确保数据清晰。 但这并非总是可能的。 我也想出了这个,我喜欢。
const Empty=Symbol("Empty");
const p = R.propOr(Empty);
const getTemplate = R.converge(p,[p("templateId"), p("templates")]);
希望获得有关干净程度和可读性的反馈意见(如果有边缘情况会破坏它)
答案 0 :(得分:4)
以下是vanilla Javascript中的ADT方法:
// type constructor
const Type = name => {
const Type = tag => Dcons => {
const t = new Tcons();
t[`run${name}`] = Dcons;
t.tag = tag;
return t;
};
const Tcons = Function(`return function ${name}() {}`) ();
return Type;
};
const Maybe = Type("Maybe");
// data constructor
const Just = x =>
Maybe("Just") (cases => cases.Just(x));
const Nothing =
Maybe("Nothing") (cases => cases.Nothing);
// typeclass functions
Maybe.fromNullable = x =>
x === null
? Nothing
: Just(x);
Maybe.map = f => tx =>
tx.runMaybe({Just: x => Just(f(x)), Nothing});
Maybe.chain = ft => tx =>
tx.runMaybe({Just: x => ft(x), Nothing});
Maybe.compk = ft => gt => x =>
gt(x).runMaybe({Just: y => ft(y), Nothing});
// property access
const prop =
k => o => o[k];
const propSafe = k => o =>
k in o
? Just(o[k])
: Nothing;
// auxiliary function
const id = x => x;
// test data
// case 1
const o = {
templateId: "template1",
templates: {
template1: "hello"
}
};
// case 2
const p = {
templateId: null
};
// case 3
const q = {};
// case 4
const r = null; // ignored
// define the action (a function with a side effect)
const getTemplate = o => {
const tx = Maybe.compk(Maybe.fromNullable)
(propSafe("templateId"))
(o);
return Maybe.map(x => prop(x) (o.templates)) (tx);
};
/* run the effect,
that is what it means to compose functions that may not produce a value */
console.log("case 1:",
getTemplate(o).runMaybe({Just: id, Nothing: "N/A"})
);
console.log("case 2:",
getTemplate(p).runMaybe({Just: id, Nothing: "N/A"})
);
console.log("case 3:",
getTemplate(q).runMaybe({Just: id, Nothing: "N/A"})
);
正如您所看到的,我使用函数来编码ADT,因为Javascript在语言级别上不支持它们。这种编码称为Church / Scott编码。斯科特编码是不可改变的设计,一旦你熟悉它,它的处理是一块蛋糕。
Just
值和Nothing
都属于Maybe
类型,并且包含tag
属性,您可以在其上进行模式匹配。
<强> [编辑] 强>
由于Scott(不是刚才的编码人员)和OP要求更详细的回复,我扩展了我的代码。我仍然忽略对象本身为null
的情况。你必须在前面的步骤中处理这个问题。
你可能认为这是过度设计的 - 确定这个人为的例子。但是当复杂性增加时,这些功能风格可以减轻痛苦。另请注意,我们可以使用此方法处理各种效果,而不仅仅是null
检查。
我目前正在构建一个FRP解决方案,例如,它基本上基于相同的构建块。这种模式的重复是我不想再做的功能范式的特征之一。
答案 1 :(得分:4)
正如其他人告诉你的那样,丑陋的数据会排除漂亮的代码。清理空值或将它们表示为选项类型。
那就是说,ES6确实允许你通过一些重型的解构赋值来处理这个问题
const EmptyTemplate =
Symbol ()
const getTemplate = ({ templateId, templates: { [templateId]: x = EmptyTemplate } }) =>
x
console.log
( getTemplate ({ templateId: "a", templates: { a: "hello" }}) // "hello"
, getTemplate ({ templateId: "b", templates: { a: "hello" }}) // EmptyTemplate
, getTemplate ({ templates: { a: "hello" }}) // EmptyTemplate
)
&#13;
您可以继续让getTemplate
更具防御性。例如,下面我们接受用空对象调用我们的函数,甚至根本没有输入
const EmptyTemplate =
Symbol ()
const getTemplate =
( { templateId
, templates: { [templateId]: x = EmptyTemplate } = {}
}
= {}
) =>
x
console.log
( getTemplate ({ templateId: "a", templates: { a: "hello" }}) // "hello"
, getTemplate ({ templateId: "b", templates: { a: "hello" }}) // EmptyTemplate
, getTemplate ({ templates: { a: "hello" }}) // EmptyTemplate
, getTemplate ({}) // EmptyTemplate
, getTemplate () // EmptyTemplate
)
&#13;
上面,我们开始有点痛苦。这个信号非常重要,不要忽视,因为它警告我们我们做错了什么。如果您必须支持那么多空检查,则表明您需要收紧程序其他区域中的代码。复制/粘贴这些答案中的任何一个都是不明智的,并且错过了每个人都试图教你的课程。
答案 2 :(得分:3)
您可以使用R.pathOr
。只要路径的任何部分不可用,就会返回默认值。例如:
const EmptyTemplate = Symbol();
const getTemplateOrDefault = obj => R.pathOr(
EmptyTemplate,
[ "templates", obj.templateId ],
obj
);
可以在this snippet中找到一组测试。该示例显示pathOr
处理所有(?)&#34;错误&#34;案件相当不错:
const tests = [
{ templateId: "a", templates: { "a": 1 } }, // 1
{ templates: { "a": 1 } }, // "empty"
{ templateId: "b", templates: { "a": 1 } }, // "empty"
{ templateId: null, templates: { "a": 1 } }, // "empty"
{ templateId: "a", templates: { } }, // "empty"
{ templateId: "a" } // "empty"
];
修改:要支持null
或undefined
输入,您可以快速defaultTo
撰写该方法:
const templateGetter = compose(
obj => pathOr("empty", [ "templates", obj.templateId ], obj),
defaultTo({})
);
答案 3 :(得分:0)
试试这个,
const input = {
templateId: "template1",
templates: {
template1: "hello"
}
};
const getTemplate = (obj) => {
const template = obj.templates[obj.templateId] || "any default value / simply remove this or part";
//use below one if you think templates might be undefined too,
//const template = obj.templates && obj.templates[obj.templateId] || "default value"
return template;
}
console.log(getTemplate(input));
您可以结合使用&amp;&amp;和||短路表达。
此外,如果密钥存储在变量中,则使用 [] (而不是。)来获取值。
完整检查
const getTemplate = (obj) => {
const template = obj && obj.templateId && obj.templates && obj.templates[obj.templateId] || "default value"
return template;
}