Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OOP versus Functional decomposition (draft) #9

Open
fuafa opened this issue May 15, 2019 · 0 comments
Open

OOP versus Functional decomposition (draft) #9

fuafa opened this issue May 15, 2019 · 0 comments

Comments

@fuafa
Copy link
Owner

fuafa commented May 15, 2019

Specification

  1. Implement three data variances: Int, Add, Negate.
  2. Implement three operations: evalExp, toString, hasZero on each data variance.
/** 
 * THE GRID
 *              evalExp      toString        hasZero      noNegConstants (to be extended)
 *
 * Int
 *
 * Add
 *
 * Negate
 *
 * Multiply (to be extended)
 * 
 */

Functional approach

  1. Break programs down into functions that perform some operation.
  2. Easy for adding new operations (column). For example adding noNegConstants, we just need to add a new function and keep the remaining unchanged.
  3. Difficult for adding new variance (row). For example adding Multiply, we would need to alter 4 blocks of code.
// 写成这样是想要有 pattern matching 的支持,
// 然而只能做到 exhaustive check, 而没有解构.
type Exp = 
  { kind: 'Int', value: Int } 
  | { kind: 'Negate', value: Negate}
  | { kind: 'Add', value: Add }
  // hard
  // | { kind: 'Multiply', value: Multiply} 

// ts 没有 type constructor, 所以需要另外给每一个 variance 新建一个 class.
// 在支持 type constructor 的语言中, 例如 ml 系, 可以写成:
// datatype Exp = Int of int | Negate of Exp | Add of Exp * Exp
class Int {
  v: number;
  constructor(v: number) {
    this.v = v;
  }
}

class Negate {
  v: Exp;
  constructor(v: Exp) {
    this.v = v;
  }
}

class Add {
  v1: Exp;
  v2: Exp;
  constructor(v1: Exp, v2: Exp) {
    this.v1 = v1;
    this.v2 = v2;
  }
}

// hard
// class Multiply {
//   e1: Exp;
//   e2: Exp;
//   constructor(e1: Exp, e2: Exp) {
//     this.e1 = e1;
//     this.e2 = e2;
//  }

function addValue(v1: Exp, v2: Exp): Exp {
  if (v1.kind !== 'Int' || v2.kind !== 'Int') {
    throw new Error('Non-numbers in addition')
  }
  return {
    kind: 'Int',
    value: new Int(v1.value.v + v2.value.v)
  }
}

function evalExp(e: Exp): Exp {
  switch(e.kind) {
    case 'Int':
      return e;
    case 'Negate':
      const e1 = evalExp(e.value.e);
      if (e1.kind === 'Int') {
        return {
          kind: 'Int',
          value: new Int(-e1.value.v)
        };
      }
      throw new Error('Non-numbers in negation');
    case 'Add':
      return addValue(
        evalExp(e.value.e1),
        evalExp(e.value.e2)
      );
    // hard
    // case 'Multiply':
    //   ...
  }
}

function toString(e: Exp): string {
  switch(e.kind) {
    case 'Int':
      return String(e.value.v);
    case 'Negate':
      return `-${toString(e.value.e)}`;
    case 'Add':
      return `${toString(e.value.e1)} + ${toString(e.value.e2)}`;
    // hard
    // case 'Multiply':
    //   ...
  }
}

function hasZero(e: Exp): boolean {
  switch(e.kind) {
    case 'Int':
      return e.value.v === 0;
    case 'Negate':
      return hasZero(e.value.e);
    case 'Add':
      return hasZero(e.value.e1) || hasZero(e.value.e2);
    // hard
    // case 'Multiply':
    //   ...
  }
}

// easy
// function noNegConstants(e: Exp): Exp {
//   switch(e.kind) {
//     case 'Int':
//     case 'Negate':
//     case 'Add':
//     // case 'Multiply'
//   }
// }

OOP

  1. Break programs down into classes that give behavior to some kind of data.
  2. Easy for adding new variance (row), for example adding Multiply.
  3. Difficult for adding new operations (column), for example adding noNegConstants.
interface Exp {
  evalExp(): Int;
  toString(): string;
  hasZero(): boolean;
}

class Int implements Exp {
  v: number;
  constructor(v: number) {
    this.v = v;
  }
  evalExp() {
    return this; 
  }
  toString() {
    return String(this.v);
  }
  hasZero() {
    return this.v === 0;
  }
}

class Negate implements Exp {
  e: Exp
  constructor(e: Exp) {
    this.e = e;
  }
  evalExp() {
    return new Int(this.e.evalExp().v);
  }
  toString() {
    return `-${this.e.toString()}`;
  }
  hasZero() {
    return this.e.hasZero();
  }
}

class Add implements Exp {
  e1: Exp;
  e2: Exp;
  constructor(e1: Exp, e2: Exp) {
    this.e1 = e1;
    this.e2 = e2;
  }

  evalExp() {
    return new Int(this.e1.evalExp().v + this.e2.evalExp().v);
  }

  toString() {
    return this.e1.toString() + this.e2.toString();
  }
  hasZero() {
    return this.e1.hasZero() || this.e2.hasZero();
  }
}

Specification 2

Suppose there are two more variances to be extended MyString and Rational, and the function add_values now work for any pairs of the variances but not just the Int. If one of the candidate is of type MyString, then we do the concatenation.

Binary methods with functional decomposition

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant