Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can this be refactored to use generic functional principles?

A comparator function ascending accepts two arguments - a and b. It must return an integer comparing the two.

I have a list that I want to sort by name, so I wrote the following functions.

Is there a functional idiom I can use to combine these two functions, rather than having byName take responsibility for composing the resulting function?

const ascending = (a, b) => a.localeCompare(b);
const byName = (i) => i.get('name');
const useTogether = (...fns) => ...; // is there an idiomatic function like this?

// usage
items.sort(useTogether(byName(ascending))); 
like image 315
Ben Aston Avatar asked Dec 04 '17 16:12

Ben Aston


People also ask

Can you have a functional component with generic props?

e.: Ok now with a little effort you actually can have a functional component with generic props. You are stuck using 'modern' syntax though as it employs an assignment and arrow function which is of no use for your generic case: Let's rewrite the variable assignment as a good old function:

Is it possible to apply generic principles in the operative context?

In the meantime, we welcome your thoughts on the conceptual framework outlined below. It is impossible for an organization to apply its generic principles if not in the operative context of specific applications; while, conversely, the latter are not effective unless embedded into the former.

How to implement generics in our framework?

We will follow the steps below to implement Generics in our framework: 1 Create a Generic Interface 2 Create a class to implement generic interface methods 3 M odification of the Endpoints class 4 Modification of Steps class 5 R un the tests

Why is routine code refactoring important?

And this is why there is a need for routine code refactoring. Code refactoring is important if you want to avoid the dreaded code rot. Code rot results from duplicate code, myriad patches, bad classifications, and other programming discrepancies.


2 Answers

You're looking for contravariant functors

To appreciate them properly, let's start by examining the most basic sorting program

const compare = (a, b) =>
  a.localeCompare (b)

const data =
  [ 'Cindy'
  , 'Alice'
  , 'Darius'
  , 'Bertrand'
  ]
  
data.sort (compare)

console.log (data)
// Alice, Bertrand, Cindy, Darius

Nothing special there. Let's make our first contravariant functor, Comparison. The sort causes mutation, but it's just for demonstration anyway. Focus on contramap

const compare = (a, b) =>
  a.localeCompare (b)

const Comparison = (f = compare) =>
  ({ contramap : g =>
       Comparison ((a, b) => f (g (a), g (b)))
   , sort : xs =>
       xs.sort (f)
   })
   
const data =
  [ { name: 'Cindy' }
  , { name: 'Alice' }
  , { name: 'Darius' }
  , { name: 'Bertrand' }
  ]
  
Comparison ()
  .contramap (x => x.name)
  .sort (data)
  
console.log (data)
// Alice, Bertrand, Cindy, Darius

Composition law holds

m.contramap(f).contramap(g) == m.contramap(compose(f,g))

const compare = (a, b) =>
  a.localeCompare (b)

const Comparison = (f = compare) =>
  ({ contramap : g =>
       Comparison ((a, b) => f (g (a), g (b)))
   , sort : xs =>
       xs.sort (f)
   })

const data =
  [ { name: 'Cindy' }
  , { name: 'Alice' }
  , { name: 'Darius' }
  , { name: 'Bertrand' }
  ]

const compose = (f, g) =>
  x => f (g (x))

Comparison ()
  .contramap (compose (x => x.substr (1), x => x.name))
  // equivalent to
  // .contramap (x => x.substr (1)) // sort starting with second letter
  // .contramap (x => x.name)       // get name property
  .sort (data)
  
console.log (data)
// sorted by second letter this time (A, E, I, L)
// Darius, Bertrand, Cindy, Alice
//  ^       ^         ^      ^

Implementing the monoid interface gives you cool things like "multi-sort"

const Eq =
  0

const Lt =
  -1

const Gt =
  1

const Ord =
  { empty: Eq
  , concat: (a,b) =>
      a === Eq ? b : a
  }

const compare = (a, b) =>
  a < b ? Lt
    : a > b ? Gt
      : Eq

const Comparison = (f = compare) =>
  ({ compare: f
   , contramap : g =>
       Comparison ((a, b) => f (g (a), g (b)))
   , concat : m =>
       Comparison ((a, b) =>
         Ord.concat (f (a, b), m.compare (a, b)))   
   , sort : xs =>
       xs.sort (f)
   })
     
const data =
  [ { name: 'Alicia', age: 10 }
  , { name: 'Alice', age: 15 }
  , { name: 'Alice', age: 10 }
  , { name: 'Alice', age: 16 }
  ]

const sortByName =
  Comparison ()
    .contramap (x => x.name)
    
const sortByAge =
  Comparison ()
    .contramap (x => x.age)

sortByName
  .concat (sortByAge)
  .sort (data)
  
console.log ('sorted by (name,age)', data)
// Alice 10
// Alice 15
// Alice 16
// Alicia 10

sortByAge
  .concat (sortByName)
  .sort (data)

console.log ('sorted by (age,name)', data)
// Alice 10
// Alicia 10
// Alice 15
// Alice 16

Read the linked article for more useful information and an introduction to transducers

like image 81
Mulan Avatar answered Oct 21 '22 12:10

Mulan


I'm not sure if it satisfies what you're looking for, but here is one possible formulation of your useTogether (with a somewhat different signature) that does WORK. I'm not aware of a standard function with precisely that effect.

const ascending = (a, b) => a.localeCompare(b);
const byName = (i) => i['name'];
const useTogether = (selector, consumer) => (...fnArgs) => consumer(...fnArgs.map(selector));

var items = [{ name: "C" }, { name: "A" }, { name: "B" }];

console.log(
  items.sort(useTogether(byName, ascending))
)
like image 31
S McCrohan Avatar answered Oct 21 '22 12:10

S McCrohan