그린랩스(Greenlabs)

그린랩스는 홈페이지에 따르면 “4차 산업혁명을 선도하는 Agritech 기업” 이라고 합니다. 항상 클로저Elm 등 함수형 언어에 관심이 많았던 남현우 CTO님이 이직한 회사에서 어떻게 팀을 꾸리고 운영해 나갈지 관심이 많았습니다. 몇 달 전에 방문했던 그린랩스는 PHP로 작성된 IoT 스마트팜 시스템인 팜모닝만 있었는데, 남현우 님이 새로 시작한 조직은 이와 별도로 농업과 관련된 플랫폼 서비스를 개발하고 있었습니다.

과제

그린랩스는 새로 합류한 분들에게 함수형 프로그래밍을 익힐 수 있는 기간을 약 3개월 정도 제공하고 있습니다. 이 기간에 문제 풀이와 제시되는 과제를 통해 그린랩스에서 주력으로 사용하고 있는 리스크립트(ReScript) 등에 익숙해져야 합니다. 그린랩스 측은 이 시간과 비용을 들여서라도 장기적인 관점에서 의미 있는 생산성의 향상을 기대할 수 있다고 합니다. 현재 입사한 분들에게 제시되는 문제 중 하나를 받아 해결하고, 남은 시간에는 간단한 이슈를 해결하여 프로덕션 코드에 기여해보는 것을 목표로 했습니다.

리스크립트로 풀어야 할 문제로 Advent Of Code의 4일차 문제인 ‘Passport Processing’을 받았습니다. 표준 입력으로 들어오는 문자열을 파싱하여 단락 단위로 쪼개, 정상적인 여권의 정보를 몇 건이나 가졌는지 확인하는 문제입니다. 익숙한 절차형 언어 — C, Python 등으로 문제를 풀었다면 문자열 스트림을 읽으면서 단락이 나누어졌을 때, 읽어 들인 문자열을 파싱해서 필요한 정보가 존재하는지 확인하고 True / False 의 불린(Boolean) 값으로 바꿔 배열에 넣은 후 더하거나, 아니면 매번 확인할 때마다 카운터 변수의 값을 변경했을 것입니다.

해결 과정

리스크립트의 기본 명령어들은 매우 제한적이라, 일반적인 문제를 해결하기 위해서 기본 API가 필요하며, 자바스크립트 API를 감싸고 있는 Js, 자바스크립트에서 제공되지 않는 추가적인 컬렉션과 헬퍼를 담고 있는 Belt 등이 포함되어 있습니다.

코드를 작성하다 보면 조건에 따라 option<'a> 즉, None | Some('a)로 표현되는 a 이거나 아닐 수도 있는 타입을 받게 되는데, 여기서 a 타입만 남기고 그 외의 경우는 에러를 발생시키기 위해 BeltBelt.Option.getExn 기능이 필요합니다.

리스크립트의 문법도 낯설었지만, 무엇보다 모든 데이터가 불변(immutable)인 상황이 코드를 작성하기 어렵게 만들었습니다. 남현우 님이 지적했듯, 리스크립트를 익히는데 두 가지 큰 산을 한 번에 만나 어려움이 컸습니다.

  • 절차형 언어에만 익숙한 사고를 함수적으로 바꾸는 것.
  • 강타입(strongly-typed) 언어에 익숙해지는 것.

절차적인 코드에만 익숙한 탓에 힘들었지만, Js.String2, Js.Array2 등 파이프(->) 작업에 맞게 최적화된 바인딩 들을 통해 문제를 해결했습니다. 처음에는 올바른 여권이면 true, 그렇지 않으면 false를 리턴한 뒤, 그 수를 세는 과거의 방식으로 풀었습니다.

// 절차 피클러가 최초로 작성한 코드
let result =
    Js.String2.splitByRe(
        input, %re("/\\n\\n/")
    )
    ->Js.Array2.map(
        x => Belt.Option.getExn(x)
    )
    ->Js.Array2.map(
        x =>
            (Js.String2.indexOf(x, "byr:") != -1) &&
            (Js.String2.indexOf(x, "iyr:") != -1) &&
            (Js.String2.indexOf(x, "eyr:") != -1) &&
            (Js.String2.indexOf(x, "hgt:") != -1) &&
            (Js.String2.indexOf(x, "hcl:") != -1) &&
            (Js.String2.indexOf(x, "ecl:") != -1) &&
            (Js.String2.indexOf(x, "pid:") != -1)
    )
    ->Js.Array2.filter(
        x => x == true
    )
    ->Js.Array2.length

result->Js.log

Js나 Belt의 함수를 사용하면서도 명시적인 import가 필요하지 않은데, 이는 리스크립트에서는 별도의 명시적 모듈 선언 없이도 모듈 레졸루션을 수행해주기 때문에 가능합니다. (그린랩스 기술 블로그 ‘프론트엔드 개발 ReasonML이라 좋았던 점’의 모듈 레졸루션 부분)

이후에는 그린랩스 테크리드 양성민 님의 도움을 받아 Passport 타입을 생성하고, 이 타입의 구조에 맞는 값을 추출하는 방식으로 변경하여 1차 과제를 완료하였습니다.

추가 작업

CTO님은 리스크립트를 배우고 실제 코드를 작성하기에 하루는 너무 짧아 진행하기 어렵다고 얘기했지만, 그래도 간단하게 체험이라도 할 수 있도록 준비를 부탁드렸고, 그린랩스 직원분들의 도움을 받아 프로덕션 코드를 작성할 기회를 얻었습니다.

그린랩스에서 제공하는 무료 농사 정보 서비스인 모닝노트의 시세 위젯의 스타일을 갱신했습니다. 이 과정에서 리스크립트 문법으로 React 코드를 작성하였으며, 시세의 네 가지 타입에 대해 특정 타입을 구현하지 않으면 컴파일 시에 문법 에러가 발생하는 것도 경험하여, 언어가 주는 장점을 작게나마 확인할 수 있었습니다.

GreenLabs price widget in Figma

GreenLabs price widget in Chrome

더 알아보기

  • 그린랩스 기술 블로그: https://green-labs.github.io/

  • 힌들리–밀너 타입 추론: https://palindrom615.dev/hindley-milner-type-inference

  • GraphQL-ppx: https://graphql-ppx.com/

  • 리스크립트(ReScript), 리즌ML(ReasonML), 버클스크립트(BuckleScript)?

    • 리브랜딩에 대하여: https://rescript-lang.org/blog/bucklescript-is-rebranding
    • 과거 버클스크립트는 OCaml의 파생 언어로 자바스크립트에서 실행되는 결과물을 만드는 언어였습니다.
    • 리즌은 OCaml 위에 자바스크립트처럼 보이는 레이어를 만든 도구였습니다. 자바스크립트 출력을 위해서는 버클스크립트를 사용하고, 네이티브 결과물을 위해서는 OCaml을 사용했습니다.
    • 리즌을 사용하던 사용자들은 대부분 자바스크립트를 타겟으로 많이 사용해왔고, 이에 버클스크립트를 리스크립트라는 이름으로 리브랜딩하고 리즌의 문법과 도구를 개선해 자바스크립트로의 사용을 더 강화하는 방향을 택했습니다.
  • 그린랩스는 웹에서의 스타일을 위해서도 타입 언어를 사용하고 있습니다.

영상