使用 Ramda 以声明方式优先编写布尔值 -> 字符串映射器
Writing a Boolean -> string mapper with precedence in a declarative way using Ramda
如何以更 functional/declarative 的方式编写此代码?
type FieldType = "dropdown" | "text" | "file" | null;
const getFieldType = (field: {isDropdown?: boolean, isTextInput?: boolean, isFileModal?: boolean}) => {
// a field can have all 3 of the above boolean properties at once as "true"
//but some of them are more important than others - eg. isDropdown
//hence the if/else if below
if(field.isDropdown) {
return 'dropdown';
} else if(field.isTextInput) {
return 'text';
}
else if (field.isFileModal) {
return 'file';
}
}
我的解决方案:
const getFieldType = (field) => {
const mapConditionToType = [
[field.isDropdown, 'dropdown'],
[field.isTextInput, 'text'],
[field.isFileModal, 'file']
]
return mapConditionToType.find([condition, type] => condition)?.type ?? null;
}
如您所见,它根本不包含...任何 Ramda。我想知道是否可以使用函数式编程以更具声明性的方式编写它?
我建议利用一些声明性库,例如 Ramda
/**
@typescript ```
R.cond<Record<string, boolean>, FieldType>([ ... ]);
```
**/
const getFieldType = R.cond([
[R.prop('isDropdown'), R.always('dropdown')],
[R.prop('isTextInput'), R.always('text')],
[R.prop('isFileModal'), R.always('file')],
[R.T, R.always(null)],
]);
console.log(
'it should return "dropdown" =>',
getFieldType({ isDropdown: true }),
)
console.log(
'it should return "file" =>',
getFieldType({ isFileModal: true }),
)
console.log(
'it should return "text" =>',
getFieldType({ isTextInput: true }),
)
console.log(
'it should return "null" =>',
getFieldType({ isMultiSelect: true }),
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
如@walnut_salami 的评论所述,cond 是在这种情况下使用的正确运算符。您可以使用更高级的运算符来测试第一个参数的真实性,但由于要检查的属性 return 为真值,因此 prop 运算符应该足够了。
interface Field {
isDropdown?: boolean;
isTextInput?: boolean;
isFileModal?: boolean;
}
const getFieldType = cond<Field, FieldType>([
[prop('isDropdown'), always('dropdown') ],
[prop('isTextInput'), always('text') ],
[prop('isFileModal'), always('file') ],
[T, always(null)]
]);
console.log(getFieldType({ isDropdown: true })); // dropdown
console.log(getFieldType({ isTextInput: true })); // text
console.log(getFieldType({ isFileModal: true })); // file
console.log(getFieldType({ })); // null
console.log(getFieldType({ isTextInput: true, isDropdown: true })); // dropdown
由于您使用的是 TypesSript,因此您可以利用接受主题类型和 return 类型参数的 cond 的重载。通过这样做,你和重构 Field 和 FieldType 将抛出编译器错误,如果它们以某种方式破坏了运算符中使用的内容。
R.cond
适合您希望提供的对数组的结构。您可以使用 R.pipe
生成一个接受数组对的函数,然后根据 R.cond
生成一个新函数(来自 的测试用例):
const { pipe, map, evolve, prop, always, append, T, cond } = R
const createGetFieldType = pipe(
map(evolve([prop, always])), // wrap each element in the pairs with the relevant function
append([T, always(null)]), // add the default value
cond // partially apply to cond
)
const getFieldType = createGetFieldType([
['isDropdown', 'dropdown'],
['isTextInput', 'text'],
['isFileModal', 'file']
])
console.log(getFieldType({ isDropdown: true })); // dropdown
console.log(getFieldType({ isTextInput: true })); // text
console.log(getFieldType({ isFileModal: true })); // file
console.log(getFieldType({ })); // null
console.log(getFieldType({ isTextInput: true, isDropdown: true })); // dropdown
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
如何以更 functional/declarative 的方式编写此代码?
type FieldType = "dropdown" | "text" | "file" | null;
const getFieldType = (field: {isDropdown?: boolean, isTextInput?: boolean, isFileModal?: boolean}) => {
// a field can have all 3 of the above boolean properties at once as "true"
//but some of them are more important than others - eg. isDropdown
//hence the if/else if below
if(field.isDropdown) {
return 'dropdown';
} else if(field.isTextInput) {
return 'text';
}
else if (field.isFileModal) {
return 'file';
}
}
我的解决方案:
const getFieldType = (field) => {
const mapConditionToType = [
[field.isDropdown, 'dropdown'],
[field.isTextInput, 'text'],
[field.isFileModal, 'file']
]
return mapConditionToType.find([condition, type] => condition)?.type ?? null;
}
如您所见,它根本不包含...任何 Ramda。我想知道是否可以使用函数式编程以更具声明性的方式编写它?
我建议利用一些声明性库,例如 Ramda
/**
@typescript ```
R.cond<Record<string, boolean>, FieldType>([ ... ]);
```
**/
const getFieldType = R.cond([
[R.prop('isDropdown'), R.always('dropdown')],
[R.prop('isTextInput'), R.always('text')],
[R.prop('isFileModal'), R.always('file')],
[R.T, R.always(null)],
]);
console.log(
'it should return "dropdown" =>',
getFieldType({ isDropdown: true }),
)
console.log(
'it should return "file" =>',
getFieldType({ isFileModal: true }),
)
console.log(
'it should return "text" =>',
getFieldType({ isTextInput: true }),
)
console.log(
'it should return "null" =>',
getFieldType({ isMultiSelect: true }),
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
如@walnut_salami 的评论所述,cond 是在这种情况下使用的正确运算符。您可以使用更高级的运算符来测试第一个参数的真实性,但由于要检查的属性 return 为真值,因此 prop 运算符应该足够了。
interface Field {
isDropdown?: boolean;
isTextInput?: boolean;
isFileModal?: boolean;
}
const getFieldType = cond<Field, FieldType>([
[prop('isDropdown'), always('dropdown') ],
[prop('isTextInput'), always('text') ],
[prop('isFileModal'), always('file') ],
[T, always(null)]
]);
console.log(getFieldType({ isDropdown: true })); // dropdown
console.log(getFieldType({ isTextInput: true })); // text
console.log(getFieldType({ isFileModal: true })); // file
console.log(getFieldType({ })); // null
console.log(getFieldType({ isTextInput: true, isDropdown: true })); // dropdown
由于您使用的是 TypesSript,因此您可以利用接受主题类型和 return 类型参数的 cond 的重载。通过这样做,你和重构 Field 和 FieldType 将抛出编译器错误,如果它们以某种方式破坏了运算符中使用的内容。
R.cond
适合您希望提供的对数组的结构。您可以使用 R.pipe
生成一个接受数组对的函数,然后根据 R.cond
生成一个新函数(来自
const { pipe, map, evolve, prop, always, append, T, cond } = R
const createGetFieldType = pipe(
map(evolve([prop, always])), // wrap each element in the pairs with the relevant function
append([T, always(null)]), // add the default value
cond // partially apply to cond
)
const getFieldType = createGetFieldType([
['isDropdown', 'dropdown'],
['isTextInput', 'text'],
['isFileModal', 'file']
])
console.log(getFieldType({ isDropdown: true })); // dropdown
console.log(getFieldType({ isTextInput: true })); // text
console.log(getFieldType({ isFileModal: true })); // file
console.log(getFieldType({ })); // null
console.log(getFieldType({ isTextInput: true, isDropdown: true })); // dropdown
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>