The composition

Function Composition

In computer science, function composition is an act or mechanism to combine simple functions to build more complicated ones. Like the usual composition of functions in mathematics, the result of each function is passed as the argument of the next, and the result of the last one is the result of the whole. -- Wikipedia

Example:

1
2
3
4
5
6
var compose = (f, g) => (x) => f(g(x))
var head = (x) => x[0]
var reverse = reduce((a, x) => [x].concat(a), [])

var last = compose(head, reverse)
last(["first", "second", "last"]) // 'last'

In the definition of compose, g will be executed before f, thus creating a right-to-left data stream. This is much more readable than nesting a bunch of function calls.

Associativity

Like many other functional programming concepts, associativity is derived from math.It is an expression in which the order of evaluation does not affect the end result provided the sequence of the operands does not get changed. -- JOSEPH REX

Example:

1
2
2 + (3 + 4) === 2 + 3 + 4 // true
2 * (3 * 4) === 2 * 3 * 4 // true

Because of the grouping of calls to compose is not important, so the result is the same. This also gives us the ability to write a variadic compose.
Like this:

1
2
3
4
5
6
7
8
9
10
var last = compose(head, reverse)
last(["first", "second", "last"]) // 'last'

var upperLast = compose(head, reverse, toUppercase)
upperLast(["first", "second", "last"]) // 'LAST'

var mediaUrl = _.compose(_.prop("m"), _.prop("media"))
// var images = _.compose(_.map(img), _.map(mediaUrl), _.prop('items'));
// use the associativity
var images = _.compose(_.map(_.compose(img, mediaUrl)), _.prop("items"))

There is no standard answer on how to composition, just make it more reusable.

Tacit programming

Tacit programming, also called point-free style, is a programming paradigm in which function definitions do not identify the arguments (or "points") on which they operate. -- Wikipedia

1
2
3
4
5
6
7
8
9
10
11
var head = (x) => x[0]
var toUppercase = (x) => x.toUpperCase()

// not pointfree
// it used the name
var foo1 = (name) => name.split(" ").map(compose(toUppercase, head)).join(".")
foo1("Edward Wang") // E.W

// pointfree
var foo2 = compose(join("."), map(compose(toUppercase, head)), split(" "))
foo2("Edward Wang") // E.W

some practice

Use compose in rambda.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// test data
var dragons = [
{ name: "thiny dragon", height: 600, price: 700, sold: true },
{ name: "tall dragon", height: 6600, price: 1700, sold: false },
{ name: "dangerous dragon", height: 5200, price: 700, sold: false },
{ name: "small dragon", height: 200, price: 1300, sold: false },
{ name: "fat dragon", height: 400, price: 900, sold: true },
{ name: "gold dragon", height: 500, price: 2000, sold: false },
]

// 1. rewrite the following with the compose. Tip: the prop is curry function.
// var isLastSold = xs => {
// var x = $.last(xs);
// return $.prop('sold', x);
// }
var isLastSold = $.compose($.prop("sold"), $.last)
isLastSold(dragons) // false

// 2. use the compose, prop and head to get the name of first dragon.
var nameOfFirst = $.compose($.prop("name"), $.head)
nameOfFirst(dragons) // 'thiny dragon'

// 3. refactoring the averagePrice with the average to make it composition.
var _average = (xs) => reduce(add, 0, xs) / xs.length
// var averagePrice = xs => {
// var p = map(x => x.price, xs);
// return _average(p);
// }
var averagePrice = $.compose(_average, map($.prop("price")))
averagePrice(dragons) // 1216.6666666666667

// 4. write a function that => 'gold dragon' => 'GOLD_DRAGON'
var replaceSpace = replace(/\W+/g, "_")
var changeName = $.map($.compose(replaceSpace, toUppercase, $.prop("name")))
changeName(dragons) //[ 'THINY_DRAGON', 'TALL_DRAGON', 'DANGEROUS_DRAGON', 'SMALL_DRAGON', 'FAT_DRAGON', 'GOLD_DRAGON' ]

Test Application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
requirejs.config({
paths: {
ramda: "https://cdnjs.cloudflare.com/ajax/libs/ramda/0.13.0/ramda.min",
jquery: "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min",
},
})

require(["ramda", "jquery"], (_, $) => {
var log = _.curry((tag, x) => {
console.log(tag, x)
return x
})
var url = (term) =>
"https://api.flickr.com/services/feeds/photos_public.gne?tags=" +
term +
"&format=json&jsoncallback=?"
var img = (url) => $("<img />", { src: url })

var setHtml = _.curry((tag, html) => $(tag).html(html))
var getJSON = _.curry((callback, url) => $.getJSON(url, callback))

var mediaUrl = _.compose(_.prop("m"), _.prop("media"))
var mediaToImg = _.compose(img, mediaUrl)
var images = _.compose(_.map(mediaToImg), _.prop("items"))

var renderImages = _.compose(setHtml("body"), images)
var app = _.compose(getJSON(renderImages), url)

app("car")
})