попытаться преобразовать функцию в более функциональный стиль - PullRequest
0 голосов
/ 31 января 2019

Я изучаю функциональное программирование, и мне хотелось бы знать, каков наилучший подход к рефакторингу сильфонной функции, чтобы увидеть лучшие практики

Я рефакторину созданную мной объектно-ориентированную систему частиц,Итак, вот первый код, который я сделал как класс:

submitToFields(fields) {
    let totalAccelerationX = 0;
    let totalAccelerationY = 0;
    fields.forEach(field => {
      const vectorX = field.position.x - this.position.x;
      const vectorY = field.position.y - this.position.y;
      const force = Particle.calculateForce(field, vectorX, vectorY);
      totalAccelerationX += vectorX * force;
      totalAccelerationY += vectorY * force;
    });
    this.acceleration = new Vector(totalAccelerationX, totalAccelerationY);
  }
  • Этот метод относится к объекту частицы, который имеет положение, скорость, ускорение и т. д.
  • поле являетсямассив объектов поля со структурой, такой как {position: {x: 0, y: 0}, mass: 140, ...}
  • вектор является объектом, подобным {x: 0, y:0}

  • Метод вычисляет ускорение возмущения, которое оказывает влияние на частицу поля (гравитационная масса) в системе частиц

Хорошо, теперь этоэто то, что я получил до сих пор, но я хотел бы немного подробнее рассказать о функционале и постараться быть более свободным от очков и других полезностей, которые функциональное программирование может предложить мне.


const add = a => b => a + b;
const propX = R.prop('x');
const propY = R.prop('y');
const addVectors = (vectorA, vectorB) =>
  vector(
    add(propX(vectorA))(propX(vectorB)),
    add(propY(vectorA))(propY(vectorB)),
  );
const vector = (a, b) => ({ x: a, y: b });
const square = x => x ** 2;
const difference = a => b => a - b;
const position = item => R.prop('position', item) || 0;
const posX = item => R.prop('x', position(item)) || 0;
const posY = item => R.prop('y', position(item)) || 0;
const propMass = item => R.prop('mass', item) || 0;

const calculateForce = (mass, vectr) => {
  return mass / (square(vectr.x) + square(vectr.y) + mass) ** 1.5;
};


const disturbanceAcceleration = (part, fields) => {
  const partX = posX(part);
  const partY = posY(part);
  return fields.reduce((acc, curr) => {
    const fieldX = difference(posX(curr))(partX);
    const fieldY = difference(posY(curr))(partY);
    const force = calculateForce(propMass(curr), vector(fieldX, fieldY));
    const newVector = vector(fieldX * force, fieldY * force);
    return addVectors(acc, newVector);
  }, vector(0, 0));
};

Вот тест:

Дано:

const part = {position:{x:0,y:0}};

const fields = [
 {position:{x:100,y:100}, mass:140},
 {position:{x:200,y:100}, mass:140} , 
 {position:{x:150,y:300}, mass:140}];

выполнить:

disturbanceAcceleration(part,fields)

Выход:

{x: 0.007947635786319896, y: 0.007256173830876778}

1 Ответ

0 голосов
/ 31 января 2019

Vector.js

Начиная с базового модуля Vector, мы используем объекты для хранения свойств x и y, но мы уверены, что векторные операции adddifference и scale не видоизменяют объект;вместо этого всегда возвращается новый вектор -

const Vector = (x = 0, y = 0) =>
  ({ x, y })

Vector.add = (a = Vector (), b = Vector ()) =>
  Vector
    ( a.x + b.x
    , a.y + b.y
    )

Vector.difference = (a = Vector (), b = Vector ()) =>
  Vector
    ( a.x - b.x
    , a.y - b.y
    )

Vector.scale = ({ x, y } = Vector (), k = 1) =>
  Vector
    ( x * k
    , y * k
    )

// ...

export default Vector

Field.js

Затем мы создаем модуль Field, который зависит от Vector.Опять же, мы гарантируем, что мы не используем изменяемые операции -

const Vector =
  require ('./Vector')

const Field = (mass = 0, position = Vector ()) =>
  ({ mass, position })

Field.calculateForce = ({ mass, position:{ x, y } } = Field ()) =>
  mass / (x ** 2 + y ** 2 + mass) ** 1.5

Field.disturbanceAcceleration = (origin, fields = []) =>
  fields.reduce
    ( (acc, { mass, position }) =>
      { const v =
          Vector.difference (position, origin)

        const force =
          Field.calculateForce (Field (mass, v))

        return Vector.add
          ( acc
          , Vector.scale (v, force)
          )
      }
    , Vector ()
    )

// ...

export default Field

Теперь для нашего решения -

import Vector from './Vector'
import Field from './Field'

const fields =
  [ Field (140, Vector (100, 100))
  , Field (140, Vector (200, 100))
  , Field (140, Vector (150, 300))
  ]

Field.disturbanceAcceleration (Vector (), fields)
// { x: 0.007947635786319896, y: 0.007256173830876778 }

Использование аргументов по умолчанию дает нам чрезвычайно удобное использование наших типов данных.Это то, что мы не считаем само собой разумеющимся -

Vector ()         // { x: 0, y: 0 }
Vector () .x      // 0
Vector () .y      // 0
Vector (1, 2) .x  // 1
Vector (1, 2) .y  // 2

Field ()                                 // { mass: 0, position: { x: 0, y: 0 } }
Field () .mass                           // 0
Field () .position                       // { x: 0, y: 0 }
Field (100) .mass                        // 100
Field (100) .position                    // { x: 0, y: 0 }
Field (100, Vector (1, 2)) .mass         // 100
Field (100, Vector (1, 2)) .position .x  // 1
Field (100, Vector (1, 2)) .position .y  // 2

myparticle.js

У вас может быть много субмодулей, и это нормально.Вероятно, вы захотите объединить подмодули в один больший модуль, возможно, тот, который мы назовем myparticle -

import Vector from './Vector'
import Field from './Field'
// ...

export { Vector, Field /*, ... */ }

Затем, когда мы используем его в нашем решении -

import { Vector, Field /*, ... */ } from ('myparticle')

// ...

Демонстрация кода

Разверните фрагмент ниже, чтобы проверить результаты в своем собственном браузере -

// Vector -------------------------------------------
const Vector = (x = 0, y = 0) =>
  ({ x, y })

Vector.add = (a = Vector (), b = Vector ()) =>
  Vector
    ( a.x + b.x
    , a.y + b.y
    )

Vector.difference = (a = Vector (), b = Vector ()) =>
  Vector
    ( a.x - b.x
    , a.y - b.y
    )

Vector.scale = ({ x, y } = Vector (), k = 1) =>
  Vector
    ( x * k
    , y * k
    )

// Field -------------------------------------------
const Field = (mass = 0, position = Vector ()) =>
  ({ mass, position })

Field.calculateForce = ({ mass, position:{ x, y } } = Field ()) =>
  mass / (x ** 2 + y ** 2 + mass) ** 1.5

Field.disturbanceAcceleration = (origin, fields = []) =>
  fields.reduce
    ( (acc, { mass, position }) =>
      { const v =
          Vector.difference (position, origin)

        const force =
          Field.calculateForce (Field (mass, v))
            
        return Vector.add
          ( acc
          , Vector.scale (v, force)
          )
      }
    , Vector ()
    )

// Demo -------------------------------------------
const fields =
  [ Field (140, Vector (100, 100))
  , Field (140, Vector (200, 100))
  , Field (140, Vector (150, 300))
  ]

const result =
  Field.disturbanceAcceleration (Vector (), fields)
  
console .log (result)
// { x: 0.007947635786319896, y: 0.007256173830876778 }

Простые объектные модули

Выше мы сделали функцию Vector и затем присоединяемadd, difference и scale в качестве свойств функции.Это работает в JavaScript, потому что функции являются объектами, и мы можем назначать свойства любым способом, который мы выберем.Этот дизайн позволяет нам конструировать векторы, используя Vector в качестве функции конструктора, а затем вызывать Vector.add, Vector.scale и т. Д. Для наших операций, связанных с вектором. простой объект .Векторные операции add, difference и scale могут быть добавлены в качестве свойств объекта.Затем конструктор можно добавить в качестве свойства make или любого имени по вашему выбору -

// Vector.js
const make = (x = 0, y = 0) =>
  ({ x, y })

const add = ...

const difference = ...

const scale = ...

export { make, add, difference, scale }

Vector больше не является функцией.Вместо этого конструктор экспортируется как make.Теперь мы строим векторы, используя Vector.make -

import Vector from './Vector'

const u =
  Vector.make (1, 1) // must call Vector.make

const v =
  Vector.make (2, 3) // must call Vector.make

Vector.add (u, v)    // { x: 3, y: 4 }

Независимо от того, как вы решите упаковать свои модули, зависит только от вас, просто убедитесь, что оно согласовано.

...