This question already has answers here:
Closed 2 years ago.
I wonder how to create polymorphism in functional programming.
Lets take as example form class with different type of inputs
Sample polymorh in JS es6:
class Form {
constructor(fields){
this.fields = fields;
}
validate() {
let err = null;
for(let f of this.fields) {
if (! field.validate()){
return field.getErr();
}
}
}
}
class TextField {
isNotEmpty() {
return this.value != ''
}
validate() {
return this.isNotEmpty()
}
}
class SelectField {
validate() {
if (! inArray(this.value, this.options) {
return 'value is not in the available options'
}
}
}
The code is for demonstration purposes only, dont look the syntax or the correct behavior.
I wonder how to create similar polymorhism in FP.
In Haskell you can use pattern matching, but how to do it in javascript?
Some say that i can implement different functions with if statements:
if (field.type == 'text')...
else if (field.type == 'select')...
But this is bad because i should write all logic in one function.
If i want to add new type of field (MultipleSelect for example) i should change the code inside the common funciton.
Is there better alternative in javascript?
The only approach I can think of, from what I got from the OP's provided example code, was a mapping of the currently processed form-field to field
-specific validation and error-handling functionality ... a working example code then looks like that ...
/**
* form and field type matching configuration tables/maps
*/
const validationTable = {
isValidFieldType: {
text: isValidTextField,
select: isValidSelectField,
//multiselect: ...
__invalid__: returnInvalidFieldValue
},
validateForm: validateForm
};
const createErrorTable = {
//form: createFormError,
field: {
text: createTextFieldError,
select: createSelectFieldError,
//multiselect: ...
__invalid__: handleCreateErrorException
}
};
/**
* helping getter functionality
* - that either returns a valid field type that does
* match a key in `validationTable.isValidFieldType`
* - or that does return the '__invalid__' key as it's
* fallback value.
*/
function getValidationFieldType(field) {
const fieldType = (field && field.type);
return (
(typeof fieldType === 'string')
&& (fieldType in validationTable.isValidFieldType)
&& fieldType
) || '__invalid__';
}
/**
* main validation functionality
* - that either returns `true` for an entirely valid form validation process
* - or that does return a type specific/related validation error.
*/
function validateForm(type) {
const tableOfIsValidFieldType = validationTable.isValidFieldType;
let recentField;
return Array.from(type.fields).every(field => {
recentField = field;
return tableOfIsValidFieldType[getValidationFieldType(field)](field);
}) || createErrorTable.field[getValidationFieldType(recentField)](/*recentField*/);
}
/**
* validation and error handling specific helper functionality
*/
function isValidTextField(type) {
return (type.value !== '');
}
function isValidSelectField(type) {
return Array.from(type.options).includes(type.value);
}
function returnInvalidFieldValue() {
return false;
}
function createTextFieldError(/*recentField*/) {
return (new Error('an empty text field value is not allowed.'));
}
function createSelectFieldError(/*recentField*/) {
return (new Error('the field value does not match the options'));
}
function handleCreateErrorException(/*recentField*/) {
return (new Error('there is no matching validation and error handling for this field.'));
}
/**
* test
*/
const validForm = { fields: [{
type: 'text',
value: 'valid'
}, {
type: 'select',
value: 'valid',
options: ['valid', 'select', 'field']
}] };
const invalidFormWithNullType = { fields: [{
type: 'text',
value: 'valid'
}, {
type: null,
value: 'valid'
}, {
type: 'select',
value: 'valid',
options: ['valid', 'select', 'field']
}] };
const invalidFormWithMissingType = { fields: [{
value: 'valid'
}, {
type: 'select',
value: 'valid',
options: ['valid', 'select', 'field']
}] };
const invalidFormWithEmptyTextValue = { fields: [{
type: 'text',
value: ''
}, {
type: 'select',
value: 'valid',
options: ['valid', 'select', 'field']
}] };
const invalidFormWithNonMatchingSelectValue = { fields: [{
type: 'text',
value: 'valid'
}, {
type: 'select',
value: 'invalid',
options: ['valid', 'select', 'field']
}] };
console.log('validateForm(validForm) : ', validateForm(validForm));
console.log('validateForm(invalidFormWithNullType).message : ', validateForm(invalidFormWithNullType).message);
console.log('validateForm(invalidFormWithMissingType).message : ', validateForm(invalidFormWithMissingType).message);
console.log('validateForm(invalidFormWithEmptyTextValue).message : ', validateForm(invalidFormWithEmptyTextValue).message);
console.log('validateForm(invalidFormWithNonMatchingSelectValue).message : ', validateForm(invalidFormWithNonMatchingSelectValue).message);
.as-console-wrapper { max-height: 100%!important; top: 0; }
Note
This pretty much also fits to what @Bergi yesterday already did mention in one of his comments.
Notice that OOP and FP are orthogonal, you can easily combine them.
Here a function based solution meets two objects that have these functions assigned as their's data. A field from now will be treated as pure data by these functions (as shown with the example) but always has to feature an additional type
property.