[Functional Programming] Write simple Semigroups type

An introduction to concatting items via the formal Semi-group interface. Semi-groups are simply a type with a concat method that are associative. We define three semigroup instances and see them in action.

 

1. What is Semigroups:

Array type, String type, they are semigroup, because both has ´concat´ method:

"a".concat("b").concat("c"); // abc
[1].concat([2]).concat([3]); //[1,2,3]

 

Sum:

But Number type is not semigroup, because you cannot concat two number... well, for now...

Not let's define a Semigroup for Number as well, it is called 'Sum':

// Sum :: Sum s => a -> s a
const Sum = x => ({
    x,
    concat: ({x: y}) => Sum(x + y),
    inspect: () => `Sum ${x}`
})

Sum takes a variable 'a' and return Sum(a). Here 'a' should be number type. We export 'x' to outside world from Sum is for easy accessing the value from Another Sum.

const res1 = Sum(11).concat(Sum(12)).concat(Sum(2));
console.log(res1); // Sum 25

 

All / Any:

Boolean in JS is not a semigroup type, we can make it so by introduces 'All & Any' semigroup type:

复制代码
// All :: All s => b -> s b
const All = x => ({
    x,
    concat: ({x: y}) => All(y && x),
    inspect: () => `All ${x}`
});
// Any :: Any s => b -> s b
const Any = x => ({
    x,
    concat: ({x: y}) => Any( y || x),
    inspect: () => `Any ${x}`
})
const res2 = All(false).concat(All(true));
const res23 = Any(false).concat(Any(true));
console.log(res2) // All false
console.log(res3) // Any true
复制代码

 

First:

Wcan define a semigroup type for any other Object in JS, which only return the First one, ignore the rest:

复制代码
// First :: First f => a -> f a
const First = x => ({
    x,
    concat: (_) => First(x),
    inspect: () => `First ${x}`
})

const res3 = First('a').concat(First(2)).concat(First(2));
console.log(res3) // 'a'
复制代码

 

 

Map:

Object in JS don't have 'concat' method, of course you can use some libs such as https://github.com/DrBoolean/immutable-ext

But here, we will define a simple version of Map by ourselves. Which loop though each props of the given object, apply concat method for each prop:

// Map :: Map m => a -> m a
const Map = x => ({
    x,
    concat: ({x: y}) => Object.keys(y).map(k => y[k].concat(x[k]))
});

Let take a example to see how those semigroup types can be useful:

For example we have to object, we want to 'concat' them, by concat, I mean, for the name prop, we just want to keep the First one, for the 'isPaid' prop we want to take 'All' operation, for 'points' we want to take 'Sum' operation, for 'friends', you guess so... 'concat' operation.

const _acct1 = {name: 'Nico', isPaid: true, points: 10, friends: ['Franklin']};
const _acct2 = {name: 'Nico', isPaid: false, points: 30, friends: ['Gatsby']};

So the final result should be:

// [ Nico, false, 40, [ 'Gatsby', 'Franklin' ] ]

First, let's apply the Semigroup types we already have to those two objects:

const acct1 = {name: First('Nico'), isPaid: All(true), points: Sum(10), friends: ['Franklin']};
const acct2 = {name: First('Nico'), isPaid: All(false), points: Sum(30), friends: ['Gatsby']};

OK, now we need to concat 'acct1' and 'acct2', but Object doesn't have 'concat' method as we discussed before, therefore we need to wrap our objects into 'Map':

const acct1 = Map({name: First('Nico'), isPaid: All(true), points: Sum(10), friends: ['Franklin']});
const acct2 = Map({name: First('Nico'), isPaid: All(false), points: Sum(30), friends: ['Gatsby']});

Now we can call:

const res4 = acct1.concat(acct2);
console.log(res4); // [ First Nico, All false, Sum 40, [ 'Gatsby', 'Franklin' ] ]

 

OK, that's it. The end of Semigroup... 

Below I append a better version:

复制代码
const R = require('ramda');

// Sum :: Sum s => a -> s a
const Sum = x => ({
    x,
    concat: ({x: y}) => Sum(x + y),
    inspect: () => `Sum ${x}`
})
const res1 = Sum(11).concat(Sum(12)).concat(Sum(2));
console.log(res1); // {x: 25}

// All :: All s => b -> s b
const All = x => ({
    x,
    concat: ({x: y}) => All(y && x),
    inspect: () => `All ${x}`
});
// Any :: Any s => b -> s b
const Any = x => ({
    x,
    concat: ({x: y}) => Any( y || x),
    inspect: () => `Any ${x}`
})
const res2 = All(false).concat(All(true));
console.log(res2) // All false

// First :: First f => a -> f a
const First = x => ({
    x,
    concat: (_) => First(x),
    inspect: () => `First ${x}`
})

const res3 = First('a').concat(First(2)).concat(First(2));
console.log(res3) // 'a'


const _acct1 = {name: 'Nico', isPaid: true, points: 10, friends: ['Franklin']};
const _acct2 = {name: 'Nico', isPaid: false, points: 30, friends: ['Gatsby']};

// Map :: Map m => a -> m a
const Map = x => ({
    x,
    concat: ({x: y}) => Object.keys(y).map(k => y[k].concat(x[k]))
});
const transformations = R.evolve({
    name: First,
    isPaid: All,
    points: Sum
});
const semi_transform = R.compose(
    Map,
    transformations
);
const acct1 = semi_transform(_acct1);
const acct2 = semi_transform(_acct2);
const res4 = acct1.concat(acct2);
console.log(res4); // [ First Nico, All false, Sum 40, [ 'Gatsby', 'Franklin' ] ]
复制代码

 

posted @   Zhentiw  阅读(221)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2018-02-22 [Python Test] Use pytest fixtures to reduce duplicated code across unit tests
2018-02-22 [CSS3] CSS Background Images
2018-02-22 [CSS3] The different of Background-size between 'cover' and 'contain'
2018-02-22 [React] Integration test a React component that consumes a Render Prop
2018-02-22 [React] Unit test a React Render Prop component
2017-02-22 [Redux] Important things in Redux
2016-02-22 [Immutable.js] Exploring Sequences and Range() in Immutable.js
点击右上角即可分享
微信分享提示