preface

The text features 🌰, or case studies of “imperative programming” versus “functional”, or demo practices (mainly React).

// There is an interview question, which is an array of apis,
// Write a tool function similar to chunk to control the number of parameters and make concurrent requests
var testList = Array.from({length: 7}, (v, i) = > () = >
  new Promise(resolve= > {
    setTimeout(() = > {
      resolve(i);
    }, 2000); }));Copy the code

Let’s take a look at the comparison. On the left, I’m writing normal imperative programming code. On the right, I’m forcing ramda as a comparison, just to get a feel for it.

Full Coriolization functions are visually grainy, and in many cases, ramda style lines of code are relatively small. Example online code

The main content less mention of the concept of literary crepe, I hope that the harvest after reading is: there is a feeling of another Angle.

Why do we need a function

advantage

  1. It can be important, it can be unnecessary, it’s subjective, it’s done. Write crossword puzzles like TS. It’s hard to argue with the adage “It’s not impossible to use functional programming.”
  2. Ideologically, I can practice abstract programming and help implement some functions of the React project. Such as redux-related writing or higher-order component design. Keep thinking forward about the structure of your code as much as possible, rather than stacking apis in your business logic.
  3. Code readability. Compressed visual space for a lot of “const, var, ()=>{}” code (Inert to perform), read the keywords to find out what the logic is doing.

“* Immutable data, no negative effects, pure functions, lazy execution, Pointfree style, type signature

* “and other concepts, direct use of the library, and to the use of the same model, can be learned and done, do not add to the words, the core of this article is practice. In addition, in business code, functions are pure impure, and many are not affected. These are ideas that functional programming should embrace, not be exclusive to.

Small risk point

  1. Debugging is difficult. There’s a function exploding in the middle that the console might not see at a glance.
  2. Performance dependent. There’s a lot more packaged code, a lot more function stacks and closures at execution time.
  3. Sometimes T sub s can’t be derived. The pursuit of efficiency is to ignore this error, oras unknown as XX.
  4. Rare, the implementation of the process of the occurrence of inconceivable error, not witnessed.
  5. Getting started is time-consuming and affects the project schedule.

Why Ramda

Is 1.lodash/fp,functional.jsAdvantage:
  • There are more functions that handle traversal processing, such asuncurryN compose
  • Friendlier documentation, also available in Chinese, supports console debugging
  • A collection of common routines

The documentation says 0.27, but Chinese console is 0.26, English web is 0.25, individual functions will indicate the version, the latest version is to try Ramda

Is 2.folktaleIt’s easier to get started.
  • ramdaIs to operate on an existing type. Small functions are grouped together for language description and logical execution, similar to Lodash.
  • folktaleIt is divided into several large attribute modules, which have strong semantic division and need certain familiarity. The document is not easy to debug and query.

Quoting ramda author for comparison description, you can click into the folktale section of the reference feeling, has a strong and different style.

3. There are many users.stackoverflowCan search out a lot of combinations you want, at the same time, after the start can choose similar JS strict modeSanctuary
4. Induced learning of TS.

If you incorporate TS into your project, you might be looking at TS-Toolbelt. Also, if you click on functions, you can learn a little bit about how to write functions.

Many cases do not specify the type, because the ts derivation itself is better. Sometimes when deriving “unknown”, it is necessary to specify the type strongly, which can also help to read and easily know the result of a variable, as shown in the following figure.

When you write ramda, the ts error is probably your function is wrong. Don’t doubt that it hasn’t been derived.

using

The first function

Functions as first-class citizens. Many Korean articles describe them as first-class citizens. Some foreign articles are not mentioned, and the name may be different, but “first-class” is definitely the key word. Put a few more authoritative web sites.

  • MDN- First class function
  • Wikipedia – First class function
  • Wikipedia – The first category object, actually “first-class citizen”, the Chinese and English content is slightly different.

In fact, usually quite a lot of use of the three characteristics:

  1. A function is assigned to a variable. Function expressions, or like ES6 syntax, functions inside objects
  2. Function as argument. Reduce, Map, etc
  3. Returns a function. Higher-order Functions (HOF), Redux middleware cases are many, also pay attention to pure Functions. React is a higher-order component called HOC

In addition, ordinary JS logic in the more common operation object, will use a special existence”Lens series”

The intent is a lens that lets you focus on the properties of an object, wrap setters and getters, and make the original data immutable. Source code will be a bit convoluted, need several functions together, to set an object property value for example.

The following parsed object setting procedure (changing a.x from 1 to 4) feels the above three characteristics. Think of it as a thought experience.

// Set x of variable A to 4
const xLens = R.lensProp('x'), a = {x: 1.y: 2}; // code A logic
R.set(xLens, 4, a); // => {x: 4, y: 2}; // code B logic

// A logic A1). LensProp actually returns a function, point 3. Returned to the lens
function lensProp(k) {
  // prop functions like lodash.get,
  // Assoc functions like lodash.set, but inside assoc is a simple clone that returns a new object
  return lens(prop(k), assoc(k));
})

// A2) lens takes and sets two functions as arguments, point 2.
function lens(getter, setter) {
  return function(toFunctorFn) { // the function in xLens corresponds to point 1.
    return function(target) {
      return R.map(// ramda traversal
        function(focus) {
          return setter(focus, target);
        },
        toFunctorFn(getter(target))
      );
    };
  };
}

// ⭐️ xLens is the result of running lens, which is a function like this. Code C logic. Properties that need to be operated on are recorded (partial functions)
 function(toFunctorFn) {
    return function(target) { // code D logic
      return R.map(// ramda traversal
        (focus) = > assoc('x')(focus, target),
        toFunctorFn(prop('x')(target))
      );
    };
  };

// B logic B1) call r.ver in r.set. Set accepts values, and over accepts functions.
// r.set (xLens, 4, a) ≈ R.ver (xLens, () => 4, a)
function over(lens, f, x) {
  return lens(y= > Identity(f(y)))(x).value;
})

// The map function of over, which takes the conversion function f, can be converted. Provided to the r.mapfunction so that the values remain identical
function Identity(x) {
  return {value: x, map: (f) = > Identity(f(x))};
};

// B2) r.ver (xLens, () => 4, a) run xLens is about equal to the following
var tmp1 = () = > 4 // f(y) is run, and the second argument to the over function is run, with no input. That is the "prop(' X ')(Target)" part above
var tmp2 = () = > Indetity(tmp1()) {value: 4, map: (f) => Identity(f(4))}

// Then run C logic, xLens(tmp2), which also returns a function.
var tmp3 = function(target) {
   // The following figure is used as an aid to understanding
  return R.map(// Ramda map traversal, this is a magic map, will maintain the Identity format
    (focus) = > assoc('x')(focus, target),
    tmp2()
  );
};

// The focus argument above is the result of tmp2
var tmp4 = tmp3(a).value // Pass in the object and run D. {x: 4, y: 2};

Copy the code

The currization description is omitted above, and is the general content of the presentation. Nineteen does not leave ten. The Lodash library has a lot of internal references, so one API might be loaded with another, but that’s not a problem.

Temp1 on the console below is the result of TMP2 above. Source code specific ‘fantasy-land/map’ and functor function ‘Identity’ are the content of this article

A small case

Ramda useWith and converge are commonly used in services

// Handle multiple input arguments
R.useWith(R.add, [R.dec, R.inc])(2.3); // Result: 1 + 4 = 5Left left down into the refs2       3

// Process 1 input parameter
R.converge(R.add, [R.dec, R.inc])(2.3); // Result: 1 + 3 = 4Left left down into the refs2       2
Copy the code

Utilization and optimization

React component transfer and HOC are already common uses. It can be applied to some hooks accordingly.

Project background: There is the same page with A and B components, both of which use the user list interface, but the front-end rendering structure is different

My direction of thinking:

  1. getUsersNot twice, unless yourhttp.jsIt has its own interface cache. –swrorreact-queryOr other means of storing interface content
  2. Changes are made by passing parameters through a formatting function
// service/usersList.ts
export type TUser = {id: number,name: string}
export const getUsers = async (params, filter) => {
    cosnt res = await http.post<TUser[]>('/api/user', params)
    if(filter) {
        return res.map(filter)
    }
    return res;
}

// Step 1, add this hook
export const useGetUsersBySwr = (mapObj? : {[keyin keyof TUser]: string}) = > any[]) => {
    const res = useSWR('/api/user'.() = >{...return getUsers(params, mapObj ? renameKeys(mapObj) : null)})return{... res} }// ramdaCookBook.ts -> https://github.com/ramda/ramda/wiki/Cookbook#rename-keys-of-an-object
export const renameKeys = R.curry((keysMap, obj) = >
  R.reduce((acc, key) = > R.assoc(keysMap[key] || key, obj[key], acc), {}, R.keys(obj))
);


// A.tsx
const A = () = > {
    const data = useGetUsersBySwr({id: 'value'.name: 'text'})
    return <></>
}

// B.tsx
const A = () = > {
    const data = useGetUsersBySwr({id: 'name'.name: 'label'})
    return <></>
}
Copy the code

Function composition

The core is pipe and compose, and the code is essentially the same, with the latter calling the former and the former using reduce.

The purpose is to return a new function that, at runtime, runs the function of the PIPE parameter, passing the callback value layer by layer.

// Create a function that increments the square of a number, such as 1 -> 2 -> 4
var inc = x= > x + 1;
var square = x= > x * x;

var combo = n= > square(inc(n))
combo(1) / / 4

R.pipe(inc, square)(1)
R.pipe(square, inc)(1)
Copy the code
  • Pipe: Will be more like normal reading order, I will use it in structured paragraph code.
  • Compose: Will have a more primitive combo function feel, with a touch of the Onion model or middleware.

Redux compose, KoA compose. Ramda’s composite processing is a bit deeper, with more context binding

A small case

// there is an index array that is the parent node
var dataList = [{id: 1}, {id: 2.pId: 1}, {id: 3.pId: 2}, {id: 4}]

var pidCollection = R.compose(
        R.map(R.prop('id')),
        // Debug code 👇🏻
        (v) = > { console.log(v); return v },
        R.filter(R.compose(R.not, R.propIs(Number.'pId')))
      )(dataList)
      
Filter (propIs,not) -> map(prop)
Copy the code

Utilization and optimization

As shown in the following figure, the entry file of a React project is already wrapped with four layers of providers, or some providers with sequential requirements may be added. This kind of concatenation is very inelegant and inconvenient.

  1. The obvious first step is to pull out the APP part of your main logic, called the “main” function
  2. Using composite functions, the providers that are irrelevant to the business logic are loaded

The React website also mentions it

  const main = () = > <EEApp />

  /** * Provider's container function *@param {JSX.Element} React business entry component *@returns Package the app */ after the provider
  const hocWrap = (app: JSX.Element) = > {
    // 0. Used to bind some static parameters
    const providerWrap = (Comp: React.FC<any>, extraParams = {}) = > props= >
      R.compose(Comp, R.merge(extraParams))(props)

    type TProvider = (child: any) = > JSX.Element
    // 1. Store static providers, and then plug new providers in here
    const providerList = [
      SulaWrapper,
      StoreProvider,
      providerWrap(ConfigProvider, {locale: zhCN}),
      providerWrap(QueryClientProvider, {client: queryClient}),
    ] as TProvider[]

    Comp => (children) => 
      
       {children}
      
    const mapWrap = (list: TProvider[]) = > {
      return R.map((comp: TProvider) = > children= > comp({children}))(list)
    }

    // Ramda ts does not support expansion operators
    returnR.pipe(... (mapWrap(providerList)as [TProvider]))(app)
  }
  
  return <>{hocWrap(main())}</>
Copy the code

Loading react. forwardRef, Redux Connect, or other HOC can be used to organize code structure in this way, helping to break down the code hierarchy and focus on the main business code.

Currie and partial functions

  1. 14. To “lazy-execute” a multi-parameter function so that it does not execute internal logic until all arguments are available:
    1. The original function F1 executes F1(a,b,c), which returns “OK”. The new function of currified F1 is F2,
    2. Run F2(a,b) [or F2(a)(b)], F2 is not obtainedF1.lengthReturns the new function F3,
    3. F3 is missing one parameter at present, and F1 logic will be executed after (a,b,c) is set. Execute F3(c) to return “OK”.
  2. Partial function: Conceptually storing a variable, F3 is equivalent to a partial function because it has stored a and B parameters. likeFunction.prototype.bind, but does not bind this. The ramdapartialNature is_curry2+Function.prototype.apply,partialThe second argument and result of the new functionargumentsPieced together

Most of the library’s functions are native to the cremation process, such as ternary Cremation R.propis and binary cremation R.addd

There are four coriolization processing functions inside the source code.

  • Such as_curry1,_curry2,_curry3That’s ordinary judgmentarguments.lengthNumber of returns.

Most of ramda’s API functions are less than 3 parameters, guess performance reasons, different number of parameters of the API, internal call corresponding to the internal curry function. For example, r.addd -> _curry2.

  • Fourth:_curryNBut neither does the conventional Coriolization handle function, which is called internally_arity.The number of parameters cannot be greater than 10. A lot of them are called internally, likeR.pipe, so “the first function can be any meta-function (with unlimited parameters)” is not very accurate.
var test1 = (a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12) = > {}
R.pipe(test1)() // First argument to _arity must be a non-negative integer no greater than ten

R.curry(test1) // First argument to _arity must be a non-negative integer no greater than ten
Copy the code

The purpose of the guess limit is that R. Curryn needs to run the exact number limit, see Inssue. Ten is usually enough.

I have seen lodash.filter and ramda.filter comparing performance, not much significance, positioning is different. Ramda is all about functional programming, and LoDash is a solid library of tools that clone, Curry, and others will be much more rigorous about.

If there are more than 10 passed parameters, you can try using ES6 syntax to bypass them.

// After Babel, test1 is ten arguments, and rest variables are generated inside the function.
var test1 = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,... rest) {return R.sum(arguments)}
console.log('9 parameters', R.curry(test1)(... Array(9).fill(1))); ƒ (t){return s.ply (this,arguments)}
console.log('11 parameters', R.curry(test1)(... Array(11).fill(1))); // 11 parameters 11
Copy the code

* arguments (R.__) : placeholder (R.__) : arguments (R.__); internal _isPlaceholder function (placeholder)

f2(a, b){} returns a new function if a is a placeholderfunction(_a) { return fn(_a, b); }
Copy the code

A small case

R.__ // {@@functional/placeholder: true}

R.propIs(Number.'x', {x: 1}) // true
R.propIs()(Number.'x', {x: 1}); // true
R.propIs(R.__, 'x', {x: 1}) (Number); // true
R.propIs()(Number.'x'); / / ƒ f1 (a) {... }
R.propIs(Number); ƒ F2 (a, b) { }
Copy the code
R.feather (r.feather (Number, 'pId'))
R.filter(R.partial(R.propIs, [Number.'pId']))
Copy the code

Utilization and optimization

Project Background:

  1. There is A secondary encapsulation of ANTD dynamic Form component, code A; haveInput box boyandSelect box girlThe two are dynamic, mutually exclusive, if/else.
  2. There is a new requirement that the same page has a drawer module with a new form component called B.
  3. Some change in BboyorgirlThe value of becomes specific content

My direction of thinking:

  1. You have to use an API to change valuesForm instance of AsetFieldsValue
  2. My purpose: A changes the leastDo not useReact.forwardRefThrow the function up
  3. My purpose: B does not care whether A is male or female at the moment, only responsible for setting the valueA needs to pass indirectly to B A function of 1, 2, 3setFieldsValue('boy', xx)setFieldsValue('girl', xx)

// Page file: page.tsx ↓
const page = () = > {
    // If you do not throw something, you must set A function.
    const [
        handlerForSetFieldsValue,
        setHandlerForSetFieldsValue,
      ] = useState<(v: any) = > void> (() = > v= > {
          throw 'Partial function with no set value'
      })
    // ps: use useRef,context,redux, useState.

    return <>
        <A {{. setHandlerForSetFieldsValue}} / >
        <B {{. handlerForSetFieldsValue}} / >
    </>
}

// Component file: a.tsx ↓
const A = ({setHandlerForSetFieldsValue}) = > {
    const [form] = useForm()

    const changeForShowField = (type: 'boy' | 'grid') = > {
        // The current gender is stored and passed to the external component directly set the value
        const fn = R.partial(form.setFieldsValue, [type])
        setHandlerForSetFieldsValue(fn)
   }
   
   useEffect(() = >{...// Conditional trigger sets male and femalechangeForShowField(sex) ... }, [...]. )... }// Component file: b.tsx ↓
const B = ({handlerForSetFieldsValue}) = > {
    const onChange = (v) = >{...// Set the value of A field in form A
        handlerForSetFieldsValue(newVal)
    }
    ...
}
Copy the code

I don’t know what the design pattern is, the visitor pattern? State mode? . But there is a definite convenience to this approach. A recent use of the phrase “Sula, Changing drop-down data sources” in a form also circumnavigates the issue of rendering data in real time after changes to the form.

There are other experiments as well: in the global REdux of previous projects, the partial function component was passed. In a business component, there is a small function component that changes in real time. Partial functions fix some parameters and pass them to the popover factory component of the ancestor layer, which further renders the Modal footer.

conclusion

It takes time to find out how to write the actual business code, and it is hard to say that there is any great advantage in ordinary or not complicated business. I am looking for something fresh in boring business.

However, the purpose of this article is to share the idea of a functional direction, mainly to show a feeling of function transfer, as well as a few pieces of real use of Ramda code, can give people who want to learn to find a feeling. In addition, function programming ideas extend libraries such as immutable. Js, RxJs, Immer.