ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 패턴매칭
    Elixir 프로그래밍 2019. 7. 30. 22:29


    제 생각에는 패턴 매칭이야 말로, Elixir의 백미라고 생각됩니다. 
    패턴 매칭에 의해서 간결하고 이해하기 쉬운 코드의 작성이 가능해집니다. 그리고, 어찌 보면 애텀을 사용하는 이유가 패턴 매칭을 이용하기 위해서 이기도 합니다. 
    패턴 매칭의 편리성 때문인지, 비단 Elixir 이외에도 많은 프로그래밍 언어에서 패턴 매칭을 도입하고 있습니다. 

    이번 글은 패턴 매칭에 대해 설명하도록 하겠습니다. 

    사실 우리가 어떤 프로그램을 작성한다는 것은, 
    - 어떤 입력을 받아서, 그 입력을 처리할 어떤 함수를 호출하고 ,
    - 함수의 호출결과가 이러할 때는 이러하게 하고, 저러할 때는 저러하게 한다.
    라는 것을 컴퓨터에게 시키는 것이라고 볼수 있습니다. 

    이는,  
      - 어떤 입력(데이터)가 있을 때, 그 데이터를 처리할 함수를 지정할지를 정해주고, 
      - 함수의 결과에 따른 분리(분기) 처리를 해야 함을
    의미합니다. 

    그러므로, 프로그래밍 언어에는 비교, 판단 문이 많이 쓰일 수밖에 없습니다. 

    그리고, 어떤 함수의 결과가 기본형이 아닌 집합형인 경우, 내가 원하는 값만을 취할 수 있어야 합니다. 
    패턴 매칭은 이에 대한 처리를 아주 편리하고 명쾌한 방법으로 할 수 있도록 해줍니다. 

    패턴 매칭은 Elixir 함수의 파라미터와 결괏값이 Dynamic Biding이라는 것과 매우 깊은 연관관계가 있습니다.

    함수의 입력 파라이터의 타입이 정의되지 않는다는 것은 어떤 함수의 호출 시 파라미터를 어떤 값으로도 호출할 수 있다는 
    것을 의미합니다. 그러므로, 함수호출시 파라미터의 종류(또는 값)에 의해서 다른 처리를 할 수 있어야 하며, 이때 패턴 매칭이 사용됩니다.

    그리고, 함수의 출력도 어떤 종류의 데이터도 가질수 있다는 것은, 한 함수의 호출 결과 값의 형태에 따라서 다른
    처리를 할수 있어야 하다는 것을 의미하며, 여기에도 패턴 매칭이 사용됩니다. 

    패턴 매칭은 단지 호출될 함수의 분기나 함수의 결괏값의 분기뿐 아니라, 집합체에서 원하는 특정값만을 추출하는 데에도 사용됩니다. 
    함수의 결과값중에서 원하는 값만 추출하는 등의 용도로 사용됩니다. 

    나중에 적겠지만, 패턴 매칭은 오류에 대한 처리에도 사용됩니다. 
    보통 프로그램 개발시, 오류처리에 대한 코드가 원래 해야 하는 일의 양보다 많을 수 있습니다. 
    Elixir는 오류에 대한 처리를 하지 않고, 오류가 발생할 경우 해당 프로세스를 그냥 죽여버립니다. 
    정상적인 처리의 코드만 작성하고, 정상적이지 않은 경우는 그냥 프로세스를 죽이고, 다시 살립니다.
    이를 fail fast & retry 정책이라고 합니다. 이의 처리에도 패턴 매팅이 사용됩니다. 
    이에 대한 자세한 내용은 Elixir 프로세스 설명글에 적도록 하겠습니다. 


    함수의 입력(파라미터)에 따른 패턴매칭 
    위에 적은 것처럼, 프로그램의 개발에는 입력된 데이터의 종류에 따라서 다른 함수가 호출되어야 하는 경우가 많습니다. 

    아래는 두수의 나눗셈을 구현한 코드입니다.


    Calc 모듈내에 동일한 이름의 함수 두 개가 있습니다. 

        def div(_,0) do
            "0으로 나눌수 없습니다."
        end


    는 첫번째 두 개의 파라미터를 입력으로 받으며 , 첫 번째 파라미터는 어떤 값인지 신경 쓰지 않고, ( "_"로 시작하는 단어는 don't carre를 의미합니다.) 두 번째 파라미터가 0인 경우에 호출되는 함수입니다.


        def div(a,b) do
            a /b
        end

     

    는 두 개의 파라미터를 입력받으며, 입력된 값이 각각 div 함수 내의 변수 a, b에 할당됩니다.  Elixir는 dynamic binding 이므로  a,b 에는 정수형 숫자, 실수형 숫자 모두 입력받을 수 있습니다. 

    Calc.div(1,0)을 호출하는 경우, 패턴 매팅에 의해서 div(_,0) 이 호출됩니다.   Calc.div(1,2) 호출하는 경우에는 div(a, b)가 호출됩니다. 

    함수의 호출 시 파라미터의 데이터에 맞는(패턴이 매칭이 되는) 함수가 호출되는 것이지요. 


    추가로, div(a,b) 처럼 파라미터에 어떤 값(애텀을 포함한)이 아닌 변수가 오면, "어떤 값이라도" 라는 의미 입니다. 
    그러므로 div(1,2)도,  div(1.1, 1.2) 도 모두 div(a,b) 함수가 호출됩니다. 

    위의 코드와 같은 매턴매칭이 이 루져서, 입력되는 데이터에 따라서 자동으로 호출될 함수가 정해지게 됩니다. 
    코드에서와 같이 패턴 매칭에 따라서 자동으로 호출될 함수가 정해 지므로 if 문을 사용하지도 않고도 0으로 나누는 것을 해결할 수 있습니다. 

    아래의 코드는 1부터 주어진 수까지의 합계를 구하는 코드입니다.
    위 코드는 패턴 매칭과 함께, 재귀 호출(recursive)을 이용한 경우입니다.  
    이경우도 if 이 없이, 구현되었습니다. 

    함수의 구현 내부에서 if 문을 사용하는 것보다, 이런 패턴 매칭을 사용하는 것이 Elixir 방식이며, 
    이는 각각의 기능을 하는 순수 함수를 만드는 방식으로 함수 내부에서 if 문을 사용하지 않으므로 코드의 시인성이 높아지고, 유지보수가 쉬워집니다. 



    아래의 코드는 사람에 대한 정보(이름, 키, 몸무게)를 맵으로 받고, 이중 사람의 이름은 사용하지 않고 키와 몸무게만으로 체질량지수
    구하는 코드의 예입니다. 


    위의 BMI.calc 는 BMI.calc(%{"name" => "홍길동", "weight" => 80, "height" => 1.8 }) 과 같이 호출할 수 있습니다.
    코드의 
        def calc(%{"height" => height, "weight" => weight}) do
    는 입력으로 주어지는 맵의 "height"와 "weight" 키 값의 값을 각각 변수 height, weight에 할당한다는 의미입니다.

    이는 비교와 동시에 집합체에서 값을 추출하는 기능을 합니다. 

    만일 BMI.calc(%{"name" => "홍길동", "weight" => 80 })와 같이 height 항목을 빼고 함수를 호출하는 경우에는 오류가 발생합니다.
    또는 BMI.calc(["홍길동,80,1.8])와 같이 맵이 아닌 다를 값으로 호출해도 오류가 발생합니다. 


    함수의 출력에 따른 패턴 매칭
    아래의 코드는 특정 폴더에 있는 파일의 이름을 출력하는 함수로, Elixir에서 기본 제공하는 File 모듈의 ls 함수를 이용하였습니다. 



    File.ls 함수는 성공인 경우는 {:ok, ["file1","file2"]} 와 같이  :ok 애텀 값과,  주어진 폴더 내의 파일 이름인 문자열의 리스트를 묶음으로 가지는 튜블을 반환입니다.
    만일 존재하지 않는 폴더를 입력으로 주는 경우에는 {:error,:enoent} 와 같이 :error 애텀과, 오류의 종류를 명시하는 애텀을 묶음으로 가지는 튜플을 반환합니다.

    File.ls 는 성공 실패의 여부와 함께 , 성공인 경우 파일의 리스트를 줍니다. 
    위의 코드에서는 이의 분기 처리를 Elixir의 case 문을 이용하였습니다. (https://elixirschool.com/ko/lessons/basics/control-structures/#case)

    {:ok, files} -> files |> Enum.join(",") |> IO.puts 의 코드는 File.ls의 함수 반환 값이  :ok과 다른 하나의 값으로 이루어진 튜플인 경우에 대한 처리를 합니다.
    {:ok, files}에서 :ok는 애텀 값이므로, File.ls의 결과와 직접 비교가 되며, files는 변수이므로 튜플의 두 번째 항목이 할당됩니다.
    즉, 함수의 출력 값에 대한 판단과 함께 값의 추출이 동시에 이루어지게 됩니다. 

    case 문에서는 -> 문법을 사용하지만, 일반 패턴 매칭은 "="를 사용합니다.
    위의 코드를 iex 해보려면, 

    {:ok, files} = File.ls("폴더 이름")와 같이 하면 됩니다.
    만일 File.ls 가 성공이면 files 변수의 값은 폴더명의 리스트가 됩니다.

    알일 File.ls 가 실패했을 경우는 오류가 발생됩니다. 
    오류 메시지의 내용은 좌변 와 우변의 패턴 매칭이 실패했다고 나옵니다. 

    iex에서는 오류가 발생했음을 표시해주지만,
    프로그램이 실행될 때는 해당 프로세스가 죽게 됩니다. 이에 대한 처리에 대한 설명은 프로세스 설명글에 적도록 하겠습니다. 

    위의 코드 예에서  _ -> IO.puts "Unknown Error"의 "_"는 "어떤 값도"에 해당합니다.
    이는 unkonwn_error -> IO.puts "Unknown Error"와 같이 변수를 지정해서 할 수도 있지만, (하나의 변수는 어떤 값도 받을 수 있으므로)
    unkonwn_error를 코드 내에서 사용하지 않으면  Warning이 나오므로,  "_"로 사용한 것입니다. 
    즉, 패턴 매칭에서 "_"는 어떤 값에도 해당하지만, 그 값을 사용하지 않을 경우 사용합니다. "_" 는 "_notuse"와 같이 "_"바로 시작하는 단어로 
    사용할 수도 있습니다. 


    튜플과 리스트를 패턴매칭에 사용하는 경우는 항목의 갯수가 일치해야 하지만, 맵의 경우는 위의 예와 같이 항목의 수가 일치하지 않아도 됩니다.

    예로.  

         {a, b} = {1,2,3} 은 좌우변의 항목의 개수가 일치하지 않기 때문에 패턴매칭 실패 입니다. 

        [a] = [1,2] 도, 마찬가지로 패턴 매칭이 실패합니다. 

     

    https://elixirschool.com/ko/lessons/basics/pattern-matching/

    'Elixir 프로그래밍' 카테고리의 다른 글

    Project, Application  (0) 2019.07.31
    Recursion, Tail Recursion  (2) 2019.07.30
    Function과 Module  (0) 2019.07.29
    변수와 애텀(Atom)  (0) 2019.07.28
    함수와 함수의 입출력  (0) 2019.07.27
Designed by Tistory.