Like Iven Marquardt said, your approach is fine and adheres to functional programming principles, but can get repetitive. I would suggest using lenses instead.
What are lenses?
There's a good explanation of lenses in the What are lenses used/useful for? question. Even though the question is focused on Haskell, the answers and explanations are still mostly applicable to JS.
Essentially, a lens is a way to get, set, and modify parts of a data structure (e.g. properties of an object or elements of an array). This abstracts over the {...state, c: map(extractProperty)})}
into something like modify(cLens, map(extractProperty), state)
.
A simple implementation
While I recommend using a library to makes things easier, understanding the basics of how lenses work can be helpful.
const get = lens => s => lens.get(s)
const modify = lens => f => lens.modify(f)
const set = lens => a => lens.modify(() => a)
// You can also compose lenses:
const composeLens = (sa, ab) => ({
get: s => ab.get(sa.get(s)),
modify: f => sa.modify(ab.modify(f))
})
const propLens = prop => ({
get: ({[prop]: a}) => a,
modify: f => ({[prop]: a, ...rest}) => ({...rest, [prop]: f(a)})
})
const idxLens = i => ({
get: arr => arr[i],
modify: f => arr => [...arr.slice(0, i), f(arr[i]), ...arr.slice(i + 1)]
})
const cLens = propLens('c')
const headLens = idxLens(0)
const headOfC = composeLens(cLens, headLens)
const state = {b: 0, c: [1, 2, 3]}
console.log(get(headOfC)(state)) // 1
console.log(set(headOfC)(4)(state)) // {b: 0, c: [4, 2, 3]}
console.log(modify(headOfC)(x => 5 * x)(state)) // {b: 0, c: [5, 2, 3]}
// your example:
modify(cLens)(map(extractProperty))(A)
// or alternatively
cLens.modify(map(extractProperty))(A)
Using libraries
While the above implementation works, there are other implementations of lenses that are more general and facilitate things such as prisms (can return an optional value) and traversals (can return an applicative functor of values).
As Iven Marquardt suggested, Ramda is a good library for functional programming that includes lenses:
const cLens = R.lensProp('c')
const headLens = R.lensIndex(0)
const headOfC = R.compose(cLens, headLens)
const state = {b: 0, c: [1, 2, 3]}
console.log(R.view(headOfC, state)) // 1
console.log(R.set(headOfC, 4, state)) // {b: 0, c: [4, 2, 3]}
console.log(R.over(headOfC, x => 5 * x, state)) // {b: 0, c: [5, 2, 3]}
<script src="https://cdn.jsdelivr.net/npm/ramda@0.27.0/dist/ramda.min.js"></script>
Personally, I'm more of a TypeScript person and prefer monocle-ts (which you can still use in plain JavaScript) as it has better TypeScript support and integrates nicely with fp-ts:
import {Lens} from 'monocle-ts'
import {indexReadonlyArray} from 'monocle-ts/lib/Index/ReadonlyArray'
interface State {
readonly b: number
readonly c: readonly number[]
}
const cLens = Lens.fromProp<State>()('c')
const headLens = indexReadonlyArray<number>().index(0)
const headOfC = cLens.composeOptional(headLens)
const state = {b: 0, c: [1, 2, 3]}
console.log(headOfC.getOption(state)) // {_tag: 'Some', value: 1}
console.log(headOfC.set(4)(state)) // {b: 0, c: [4, 2, 3]}
console.log(headOfC.modify(x => x * 5)(state)) // {b: 0, c: [5, 2, 3]}