Function :: ((->) r) Function a = ((->) r) a = r -> a
;; Functor、Applicative、Monad 的类定义
classFunctor f where fmap :: (a -> b) -> f a -> f b
class (Functorf) => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b
classMonad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b x >> y = x >>= \_ -> y fail :: String -> m a fail msg = error msg
;; 作为Functor、Applicative、Monad 的实例的 Function 的定义,可以对比上面的类定义查看 instanceFunctor ((->) r) where fmap = (.)
fmap :: (a -> b) -> (c -> a) -> (c -> b)
f :: a -> b
g :: c -> a h :: c -> b
const h = fmap(f, g) = map(f, g) = compose(f, g)
instanceApplicative ((->) r) where pure x = (\_ -> x) f <*> g = \x -> f x (g x)
f <*> g = (r->(a->b)) -> (r->a) -> (r->b) = (r->a->b) -> (r->a) -> r -> b
instanceMonad ((->) r) where return x = \_ -> x h >>= f = \w -> f (h w) w
// uncurried version var formatName1 = function(first, middle, last) { return first + ' ' + middle + ' ' + last; }; formatName1('John', 'Paul', 'Jones'); //=> 'John Paul Jones' // (Ah, but the musician or the admiral?) formatName1('John', 'Paul'); //=> 'John Paul undefined');
但柯里化后的函数更有用:
1 2 3 4 5 6 7 8 9 10 11 12
// curried version var formatNames2 = R.curry(function(first, middle, last) { return first + ' ' + middle + ' ' + last; }); formatNames2('John', 'Paul', 'Jones'); //=> 'John Paul Jones' // (definitely the musician!) var jp = formatNames2('John', 'Paul'); //=> returns a function jp('Jones'); //=> 'John Paul Jones' (maybe this one's the admiral) jp('Stevens'); //=> 'John Paul Stevens' (the Supreme Court Justice) jp('Pontiff'); //=> 'John Paul Pontiff' (ok, so I cheated.) jp('Ziller'); //=> 'John Paul Ziller' (magician, a wee bit fictional) jp('Georgeandringo'); //=> 'John Paul Georgeandringo' (rockers)
或这样:
1 2
['Jones', 'Stevens', 'Ziller'].map(jp); //=> ['John Paul Jones', 'John Paul Stevens', 'John Paul Ziller']
你也可以分多次传入参数,像这样:
1 2 3 4 5
var james = formatNames2('James'); //=> returns a function james('Byron', 'Dean'); //=> 'James Byron Dean' (rebel) var je = james('Earl'); also returns a function je('Carter'); //=> 'JamesEarlCarter' (president) je('Jones'); //=> 'JamesEarlJones' (actor, Vader)
var importantFields = R.project(['title', 'dueDate']); var topDataAllUsers = R.compose(R.mapObj(importantFields), topFiveUserTasks);
我们一路创建的一些函数,看起来可以在 TODO 应用的其他地方复用。其他的一些函数或许只是创建出来放在那里,以供将来组合使用。所以,如果现在回顾一下,我们可能会组合出下面的代码:
1 2 3 4 5 6 7 8
var incomplete = R.filter(R.where({complete: false})); var sortByDate = R.sortBy(R.prop('dueDate')); var sortByDateDescend = R.compose(R.reverse, sortByDate); var importantFields = R.project(['title', 'dueDate']); var groupByUser = R.partition(R.prop('username')); var activeByUser = R.compose(groupByUser, incomplete); var topDataAllUsers = R.compose(R.mapObj(R.compose(importantFields, R.take(5), sortByDateDescend)), activeByUser);
好吧,够了!我可以看一些数据吗?
好的,马上就可以了。
现在是时候将数据传给我们的函数了。但关键是,这些函数都接受相同类型的数据,一个包含 TODO 元素的数组。我们还没有具体描述这些元素的数据结构,但我们知道它至少必须包含下列属性:
var gloss = R.compose(importantFields, R.take(5), sortByDateDescend); var topData = R.compose(gloss, incomplete); var topDataAllUsers = R.compose(R.mapObj(gloss), activeByUser); var byUser = R.use(R.filter).over(R.propEq("username"));
// `prop` takes two arguments. If I just give it one, I get a function back var moo = R.prop('moo'); // when I call that function with one argument, I get the result. var value = moo({moo: 'cow'}); // => 'cow'
这种自动柯里化使得 “通过组合函数来创建新函数” 变得非常容易。因为 API 都是函数优先、数据最后(先传函数,最后传数据参数),你可以不断地组合函数,直到创建出需要的新函数,然后将数据传入其中。(Hugh Jackson 发表了一遍描述这种风格优点的 非常优秀的文章。
// take an object with an `amount` property // add one to it // find its remainder when divided by 7 var amtAdd1Mod7 = R.compose(R.moduloBy(7), R.add(1), R.prop('amount'));
// we can use that as is: amtAdd1Mod7({amount: 17}); // => 4 amtAdd1Mod7({amount: 987}); // => 1 amtAdd1Mod7({amount: 68}); // => 6 // etc.
// But we can also use our composed function on a list of objects, e.g. to `map`: var amountObjects = [ {amount: 903}, {amount: 2875654}, {amount: 6} ] R.map(amtAdd1Mod7, amountObjects); // => [1, 6, 0]
// of course, `map` is also curried, so you can generate a new function // using `amtAdd1Mod7` that will wait for a list of "amountObjects" to // get passed in: var amountsToValue = map(amtAdd1Mod7); amountsToValue(amountObjects); // => [1, 6, 0]