>  기사  >  php教程  >  멋진 기능 코드를 읽고 작성하는 방법

멋진 기능 코드를 읽고 작성하는 방법

大家讲道理
大家讲道理원래의
2016-11-12 10:17:183411검색

오늘 웨이보에서 다음과 같은 기능 코드를 공유하는 것을 보았습니다. 아래 코드를 게시했는데, 기능 버전의 경우 언뜻 보면 이해하기가 매우 어렵습니다. 자세히 보면 기절할 수도 있어요. 완전 하늘에서 내려온 책인 것 같아요. 하하. 하지만 그 기능적 코드를 파싱하는 것이 더 흥미로운 과정이 될 수 있다고 생각합니다. 게다가 이전에 "함수형 프로그래밍"에 대한 소개 기사를 작성한 적이 있는데, 이 예제를 사용하여 원본 기사를 승화시킬 수 있습니다. 많은 기초 지식을 여러분에게 소개하는 것이 좋을 것 같아서 이 글을 썼습니다.

멋진 기능 코드를 읽고 작성하는 방법

코드를 먼저 보세요

이 코드는 특이하지 않은 배열에서 숫자를 찾는 것입니다. 찾을 수 없으면 null을 반환하세요.

다음은 일반적인 구식 방식입니다. 말할 필요도 없습니다.

//正常的版本
function find (x, y) {
  for ( let i = 0; i < x.length; i++ ) {
    if ( x[i] == y ) return i;
  }
  return null;
}
let arr = [0,1,2,3,4,5]
console.log(find(arr, 2))
console.log(find(arr, 8))

결과적으로 함수식은 이런 모습이 됩니다. (위의 코드들이 아래에 희미하게 보이는 것 같지만 조금 다릅니다. if 언어를 없애기 위해, it be visible ? 표현식을 사용하면 표현식에 더 가깝습니다.

//函数式的版本
const find = ( f => f(f) ) ( f =>
  (next => (x, y, i = 0) =>
    ( i >= x.length) ?  null :
      ( x[i] == y ) ? i :
        next(x, y, i+1))((...args) =>
          (f(f))(...args)))
let arr = [0,1,2,3,4,5]
console.log(find(arr, 2))
console.log(find(arr, 8))

이 코드를 명확하게 설명하려면 먼저 몇 가지 지식을 추가해야 합니다.
Javascript의 화살표 함수



먼저 ECMAScript2015에서 도입한 화살표 표현식에 대해 간단히 설명하겠습니다. 화살표 함수는 실제로 익명 함수이며 기본 구문은 다음과 같습니다.

(param1, param2, …, paramN) => { statements } 
(param1, param2, …, paramN) => expression
     // 等于 :  => { return expression; } 
// 只有一个参数时,括号才可以不加:
(singleParam) => { statements }
singleParam => { statements }
//如果没有参数,就一定要加括号:
() => { statements }

다음은 몇 가지 예입니다.

var simple = a => a > 15 ? 15 : a; 
simple(16); // 15
simple(10); // 10
let max = (a, b) => a > b ? a : b;
// Easy array filtering, mapping, ...
var arr = [5, 6, 13, 0, 1, 18, 23];
var sum = arr.reduce((a, b) => a + b);  // 66
var even = arr.filter(v => v % 2 == 0); // [6, 0, 18]
var double = arr.map(v => v * 2);       // [10, 12, 26, 0, 2, 36, 46]

복잡해 보이지 않습니다. . 그러나 위의 처음 두 개의 단순 예제와 최대 예제는 모두 화살표 함수를 변수에 할당하므로 이름이 있습니다. 때로는 특정 함수가 선언될 때, 특히 함수형 프로그래밍에서 함수가 외부 함수도 반환할 때 호출됩니다. 예를 들어, 다음 예에서는

function MakePowerFn(power) {
  return function PowerFn(base) {
    return Math.pow(base, power);
  } 
}
power3 = MakePowerFn(3); //制造一个X的3次方的函数
power2 = MakePowerFn(2); //制造一个X的2次方的函数
console.log(power3(10)); //10的3次方 = 1000
console.log(power2(10)); //10的2次方 = 100

실제로 MakePowerFn 함수의 PowerFn에는 이름을 지정할 필요가 전혀 없습니다.

function MakePowerFn(power) {
  return function(base) {
    return Math.pow(base, power);
  } 
}
화살표 함수를 사용하면 다음과 같이 쓸 수 있습니다.


MakePowerFn = power  => {
  return base => {
    return Math.pow(base, power);
  } 
}
더 간결하게 쓸 수도 있습니다(표현식을 사용하면 {, }, 반환 문은 필요하지 않습니다.):


MakePowerFn => base => Math.pow(base, power)

더 명확하게 하기 위해 괄호와 줄바꿈을 추가하겠습니다.


MakePowerFn = (power) => (
  (base) => (Math.pow(base, power))
)
자, 위의 지식을 바탕으로 좀 더 고급 주제인 익명 함수의 재귀에 들어갈 수 있습니다.


익명 함수의 재귀


함수 프로그래밍은 상태 저장 함수와 함수 표현식이 있는 for/while 루프를 제거하는 것을 목표로 하므로 함수에서는 형식 프로그래밍의 세계에서 , for/while 루프는 사용하지 말고 대신 재귀를 사용해야 함(재귀 성능이 매우 좋지 않아 일반적으로 최적화를 위해 꼬리 재귀를 사용함, 즉 함수의 계산된 상태를 매개변수로 사용함) 합격 언어 컴파일러나 해석기가 함수의 내부 변수 상태를 저장하는 데 도움을 주기 위해 함수 스택을 사용할 필요가 없도록 계층별로 계층화합니다.


그럼 익명함수 재귀는 어떻게 할까요?

일반적으로 재귀 코드는 함수가 자신을 호출하는 경우입니다. 예를 들어 계승을 찾는 코드는 다음과 같습니다.

function fact(n){
  return n==0 ? 1 :  n * fact(n-1);
};
result = fact(5);
익명 함수에서 이 재귀를 작성하는 방법은 무엇입니까? 익명 함수의 경우 익명 함수를 다른 함수에 매개 변수로 전달할 수 있습니다. 함수의 매개 변수에는 이름이 있으므로 자신을 호출할 수 있습니다. 아래와 같이



function combinator(func) {
  func(func);
}
조금 부정행위가 의심되는 걸까요? 어쨌든, 더 나아가 위 함수를 화살표 함수 스타일의 익명 함수로 변환해 보겠습니다.



(func) => (func(func))

이제 바람피울 것 같지 않네요. 위의 계승 함수를 삽입하는 방법은 다음과 같습니다.

먼저 사실을 재구성하고 사실에서 자신이라고 부르는 이름을 제거합니다.

function fact(func, n) {
  return n==0 ? 1 :  n * func(func, n-1);
}
fact(fact, 5); //输出120
그런 다음 위 버전을 화살표 함수의 익명 함수 버전으로 변환합니다. varfact = (func, n) => ( n==0 ? 1 : n * func(func, n-1) )


fact(fact, 5)
여기서 이 익명 함수를 저장하려면 여전히 팩트를 사용해야 합니다. 계속해서 익명 함수가 선언될 때 자체적으로 호출되도록 하겠습니다.


즉,


(func, n) => ( n==0 ? 1 :  n * func(func, n-1) )
함수를 호출 매개변수로 처리하고 이를 다음 함수에 전달해야 합니다.



(func, x) => func(func, x)

마침내 다음 코드를 얻습니다.

( (func, x) => func(func, x) ) (  //函数体
  (func, n) => ( n==0 ? 1 :  n * func(func, n-1) ), //第一个调用参数
  5 //第二调用参数
);
어쨌든 좀 복잡해 보이는데, 이해하셨나요? 괜찮아, 계속하자.


고차 함수를 이용한 재귀


그런데 위 재귀 익명함수는 자기 자신을 호출하기 때문에 코드에 실제 하드코드의 매개변수가 들어있습니다. 실제 매개변수를 제거하고 싶은데 어떻게 제거하나요? 앞서 언급한 MakePowerFn 예제를 참조할 수 있지만 이번에는 고차 함수의 재귀 버전입니다.



HighOrderFact = function(func){
  return function(n){
    return n==0 ? 1 : n * func(func)(n-1);
  };
};
위 코드에서는 단순히 함수를 매개변수로 요구하고 이 함수의 재귀 버전을 반환하는 것을 볼 수 있습니다. 그럼 어떻게 부르나요?



fact = HighOrderFact(HighOrderFact);
fact(5);

连起来写就是:

HighOrderFact ( HighOrderFact ) ( 5 )

但是,这样让用户来调用很不爽,所以,以我们一个函数把 HighOrderFact ( HighOrderFact ) 给代理一下:

fact = function ( hifunc ) {
  return hifunc ( hifunc );
} (
  //调用参数是一个函数
  function (func) { 
    return function(n){
      return n==0 ? 1 : n * func(func)(n-1);
    };
  }
);
fact(5); //于是我们就可以直接使用了

用箭头函数重构一下,是不是简洁了一些?

fact = (highfunc => highfunc ( highfunc ) ) (
  func => n =>  n==0 ? 1 : n * func(func)(n-1)
);

上面就是我们最终版的阶乘的函数式代码。
回顾之前的程序

我们再来看那个查找数组的正常程序:

//正常的版本
function find (x, y) {
  for ( let i = 0; i < x.length; i++ ) {
    if ( x[i] == y ) return i;
  }
  return null;
}

先把for干掉,搞成递归版本:

function find (x, y, i=0) {
  if ( i >= x.length ) return null;
  if ( x[i] == y ) return i;
  return find(x, y, i+1);
}

然后,写出带实参的匿名函数的版本(注:其中的if代码被重构成了 ?号表达式):

( (func, x, y, i) => func(func, x, y, i) ) (  //函数体
  (func, x, y, i=0) => (
      i >= x.length ?  null :
         x[i] == y  ?  i : func (func, x, y, i+1)
  ), //第一个调用参数
  arr, //第二调用参数
  2 //第三调用参数
)

最后,引入高阶函数,去除实参:

const find = ( highfunc => highfunc( highfunc ) ) (
   func => (x, y, i = 0) => (
     i >= x.length ?  null :
           x[i] == y  ?  i : func (func) (x, y, i+1)
   )
);

注:函数式编程装逼时一定要用const字符,这表示我写的函数里的状态是 immutable 的,天生骄傲!

再注:我写的这个比原来版的那个简单了很多,原来版本的那个又在函数中套了一套 next, 而且还动用了不定参数,当然,如果你想装逼装到天上的,理论上来说,你可以套N层,呵呵。

现在,你可以体会到,如此逼装的是怎么来的了吧?。
其它

你还别说这就是装逼,简单来说,我们可以使用数学的方式来完成对复杂问题的描述,那怕是递归。其实,这并不是新鲜的东西,这是Alonzo Church 和 Haskell Curry 上世纪30年代提出来的东西,这个就是 Y Combinator 的玩法,关于这个东西,你可以看看下面两篇文章:《The Y Combinator (Slight Return)》,《Wikipedia: Fixed-point combinator》

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.