函数式编程多态性

时间:2018-04-11 06:27:21

标签: javascript ecmascript-6 functional-programming polymorphism

我想知道如何在函数式编程中创建多态。 让我们以具有不同类型输入的表格类为例 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'
       }
   }
}

该代码仅用于演示目的,不要查看语法或正确的行为。

我想知道如何在FP中创建类似的多态性。 在Haskell中你可以使用模式匹配,但是如何在javascript中进行操作?

有人说我可以用if语句实现不同的功能:

if (field.type == 'text')...
else if (field.type == 'select')...

但这很糟糕,因为我应该在一个函数中编写所有逻辑。 如果我想添加新类型的字段(例如MultipleSelect),我应该更改公共函数内的代码。

javascript中有更好的选择吗?

1 个答案:

答案 0 :(得分:0)

我从OP提供的示例代码中得到的唯一方法是将当前处理的表单字段映射到field - 特定的验证和错误处理功能......一个工作示例代码然后看起来像......



/**
 *  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; }




<强> 注意

这几乎也符合他昨天在其中一条评论中提及的@Bergi

  

请注意,OOP和FP是正交的,您可以轻松地将它们组合起来。

这里基于功能的解决方案满足将这些功能分配为其数据的两个对象。从现在开始的字段将被这些函数视为纯数据(如示例所示),但始终必须具有额外的type属性。