【问题标题】:Functional programming polymorhism [duplicate]函数式编程多态性
【发布时间】:2018-09-20 22:03:34
【问题描述】:

我想知道如何在函数式编程中创建多态性。 让我们以具有不同类型输入的表单类为例 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 中有更好的选择吗?

【问题讨论】:

  • 多态有很多种,但没有一种是专门与函数式编程相关的。
  • 你得到了工作代码。你觉得它有什么问题?
  • 我想将 OOP 多态转移到函数式编程中。没有 if 语句。使用 if 语句,您无法添加新类型 - 您必须更改函数中的垃圾代码
  • @user2693928 我看不出你的代码是如何失效的。它不会改变任何东西,它只会创造新的价值。当然,您可能希望删除 for of 循环并将其替换为 find 或其他东西,但您正在以完全好的方式使用对象。请注意,OOP 和 FP 是正交的,您可以轻松地将它们结合起来。
  • @user2693928 您要么必须使用类(并通过instanceof 进行测试),要么给每个对象一个属性,让您的函数根据语义类型进行区分。请注意,这样做几乎可以肯定是一个糟糕的想法,它比仅使用子类型多态性而不是临时多态性更加困难和脆弱。也不清楚这与函数式编程有什么关系:正如 Bergi 所说,我在您的代码中没有看到任何设置器。

标签: javascript ecmascript-6 functional-programming polymorphism


【解决方案1】:

从 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 昨天在他的一个 cmets 中已经提到的内容。

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

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

【讨论】:

  • 这不符合OpenClosed原则。 const validationTable = {type1,type2, ...type_last} 如果这是库并且我使用它,我应该修改坏的库。我想在不修改库的情况下创建我的字段类型。像 symfony 表单一样 - 我使用表单,但我可以创建自己的字段类型(例如图像字段和其他字段),而无需触及库代码。
  • @user2693928 ...此示例代码根本不是库。它只是想证明解决 OP 问题的方法与多态性无关,而是与某种基于属性的匹配有关。如果这个基于表的查找是一个库,它当然会提供一个 api,允许通过例如自定义配置这些表。添加基于匹配字段类型的键的新方法。
  • 这不是多态,这是常规字典查找。如果我想要新类型的字段,我应该在 101 个地方进行更改,这是我提出问题的主要原因。在 OOP 中添加新类型时如何避免更改 ot 101 个位置(只需添加新类)
猜你喜欢
  • 2011-06-25
  • 2012-11-08
  • 1970-01-01
  • 1970-01-01
  • 2010-09-06
  • 1970-01-01
  • 2021-08-23
  • 2021-05-26
  • 1970-01-01
相关资源
最近更新 更多