Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Beginner JavaScript OOP vs Functional

I'm just starting to research different programming styles (OOP, functional, procedural).

I'm learning JavaScript and starting into underscore.js and came along this small section in the docs. The docs say that underscore.js can be used in a Object-Oriented or Functional style and that both of these result in the same thing.

_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });

I don't understand which one is functional and which one is OOP, and I don't understand why, even after some research into these programming paradigms.

like image 932
inthenameofmusik Avatar asked May 14 '16 21:05

inthenameofmusik


People also ask

Is functional programming better than OOP JavaScript?

Functional programming is better if you have a fixed set of things and you need to add operations to them. Adding functions that perform calculations on existing data types is one example of this. OOP works well when you have a fixed set of operations on things, and you need to add more things.

Should I learn OOP in JavaScript?

If you're coding in JavaScript, getting familiar with OOP principles can make your life easier for a few reasons: It's easier to debug your code when you use objects and classes. You're able to use techniques like encapsulation and inheritance.

Is node JS functional programming or OOP?

Each programming paradigm is made of specific features, however your favorite language does not have to provide all of the features to fall into one paradigm. In fact, OOP can live without inheritance or encapsulation, thus we can say that JavaScript (JS) is an OOP language with inheritance and without encapsulation.

What is the difference between OOP and functional programming?

As mentioned, functional programming relies on functions, whereas object-oriented programming is based on classes and respective objects. A function is a process that retrieves a data input, processes it, and then returns an output. Therefore, functions are modules of code written to achieve a certain task.


1 Answers

FP

In FP, a function takes inputs and produces output with the guarantee that the same inputs will yield the same outputs. In order to do this, a function must always have parameters for the values it operates on and cannot rely on state. Ie, if a function relies on state, and that state changes, the output of the function could be different. FP avoids this at all costs.

We'll show a minimum implementation of map in FP and OOP. In this FP example below, notice how map operates only on local variables and does not rely on state -

const _ = {
                 // 👇🏽has two parameters
  map: function (arr, fn) {
      // 👇🏽local
    if (arr.length === 0)
      return []
    else
            // 👇🏽local               
                // 👇🏽local           // 👇🏽local    // 👇🏽local
      return [ fn(arr[0]) ].concat(_.map(arr.slice(1), fn))
  }
}

const result =
  // 👇🏽call _.map with two arguments
  _.map([1, 2, 3], function(n){ return n * 2; })


console.log(result)
// [ 2, 4, 6 ]

In this style, it doesn't matter that map was stored in the _ object - that doesn't make it "OOP" because an object was used. We could have just as easily written -

function map (arr, fn) {
  if (arr.length === 0)
    return []
  else
    return [ fn(arr[0]) ].concat(map(arr.slice(1), fn))
}

const result =
  map([1, 2, 3], function(n){ return n * 2; })

console.log(result)
// [ 2, 4, 6 ]

This is the basic recipe for a call in FP -

// 👇🏽function to call
             // 👇🏽argument(s)
someFunction(arg1, arg2)

The notable thing for FP here is that map has two (2) parameters, arr and fn, and the output of map depends solely on these inputs. You'll see how this changes dramatically in the OOP example below.


OOP

In OOP, objects are used to store state. When an object's method is called, the context of the method (function) is dynamically bound to the receiving object as this. Because this is a changing value, OOP cannot guarantee any method will have the same output, even if the same input is given.

NB how map only takes only one (1) argument below, fn. How can we map using just a fn? What will we map? How do I specify the target to map? FP considers this a nightmare because the output of the function no longer depends solely on its inputs - Now the output of map is harder to determine because it depends on the dynamic value of this -

            // 👇🏽constructor
function _ (value) {
         // 👇🏽returns new object
  return new OOP(value)
}

function OOP (arr) {
  // 👇🏽dynamic
  this.arr = arr
}
                           // 👇🏽only one parameter
OOP.prototype.map = function (fn) {
     // 👇🏽dynamic
  if (this.arr.length === 0)
    return []
  else         // 👇🏽dynamic           // 👇🏽dynamic
    return [ fn(this.arr[0]) ].concat(_(this.arr.slice(1)).map(fn))
}

const result =
  // 👇🏽create object
             // 👇🏽call method on created object
                    // 👇🏽with one argument
  _([1, 2, 3]).map(function(n){ return n * 2; })


console.log(result)
// [ 2, 4, 6 ]

This is the basic recipe for a dynamic call in OOP -

// 👇🏽state
       // 👇🏽bind state to `this` in someAction
                  // 👇🏽argument(s) to action
someObj.someAction(someArg)

FP revisited

In the first FP example, we see .concat and .slice - aren't these OOP dynamic calls? They are, but these ones in particular do not modify the input array, and so they are safe for use with FP.

That said, the mixture of calling styles can be a bit of an eyesore. OOP favours "infix" notation where the methods (functions) are displayed between the function's arguments -

// 👇🏽arg1
     // 👇🏽function
                       // 👇🏽arg2
user .isAuthenticated (password)

This is how JavaScript's operators work, too -

// 👇🏽arg1
   // 👇🏽function
      // 👇🏽arg2
   1  +  2

FP favours "prefix" notation where the function always comes before its arguments. In an ideal world, we would be able to call OOP methods and operators in any position, but unfortunately JS does not work this way -

// 👇🏽invalid use of method
.isAuthenticated(user, password)

// 👇🏽invalid use of operator
+(1,2)

By converting methods like .conat and .slice to functions, we can write FP programs in a more natural way. Notice how consistent use of prefix notation makes it easier to imagine the how the computation carries out -

function map (arr, fn) {
  if (isEmpty(arr))
    return []
  else
    return concat(
      [ fn(first(arr)) ]
      , map(rest(arr), fn)
    )
}

map([1, 2, 3], function(n){ return n * 2; })
// => [ 2, 4, 6 ]

The methods are converted as follows -

function concat (a, b) {
  return a.concat(b)
}

function first (arr) {
  return arr[0]
}

function rest (arr) {
  return arr.slice(1)
}

function isEmpty (arr) {
  return arr.length === 0
}

This begins to show other strengths of FP where functions are kept small and focus on one task. And because these functions only operate on their inputs, we can easily reuse them in other areas of our program.

Your question was originally asked in 2016. Since then, modern JS features allow you to write FP in more elegant ways -

const None = Symbol()

function map ([ value = None, ...more ], fn) {
  if (value === None)
    return []
  else
    return [ fn(value), ...map(more, fn) ]
}

const result =
  map([1, 2, 3], function(n){ return n * 2; })

console.log(result)
// [ 2, 4, 6 ]

A further refinement using expressions instead of statements -

const None = Symbol()

const map = ([ value = None, ...more ], fn) =>
  value === None
    ? []
    : [ fn(value), ...map(more, fn) ]
    
const result =
  map([1, 2, 3], n => n * 2)

console.log(result)
// [ 2, 4, 6 ]

Statements rely on side effects whereas expressions evaluate directly to a value. Expressions leave less potential "holes" in your code where statements can do anything at anytime, such as throwing an error or exiting a function without returning a value.


FP with objects

FP does not mean "don't use objects" - it's about preserving the ability to easily reason about your programs. We can write the same map program that gives the illusion that we're using OOP, but in reality it behaves more like FP. It looks like a method call, but the implementation relies only on local variables and not on dynamic state (this).

JavaScript is a rich, expressive, multi-paradigm language that allows you to write programs to suit your needs and preferences -

function _ (arr) {
  function map (fn) {
      // 👇🏽local
    if (arr.length === 0)
      return []
    else
            // 👇🏽local
                // 👇🏽local         // 👇🏽local      // 👇🏽local
      return [ fn(arr[0]) ].concat(_(arr.slice(1)).map(fn))
  }
         // 👇🏽an object!
  return { map: map }
}

const result =
            // 👇🏽OOP? not quite!
  _([1, 2, 3]).map(function(n){ return n * 2; })

console.log(result)
// [ 2, 4, 6 ]
like image 101
Mulan Avatar answered Oct 30 '22 06:10

Mulan