트랙
/
Elm
Elm
/
연습 문제
/
안전한 보물 상자
안전한 보물 상자

안전한 보물 상자

학습 연습 문제

소개

불투명 타입

소개

불투명 타입은 Elm에서 쓰는 캡슐화 기법이에요. 정보 은닉이나 구현 세부 사항 감추기라고 부르기도 해요. 검증하지 말고 파싱하기 패턴을 사용해 검증을 강제할 때도 흔히 쓰여요. 예를 들어 유효한 이메일 주소만 담을 수 있는 EmailAddress 불투명 타입을 만들 수 있어요. 무엇이든 담을 수 있는 String과는 달라요.

불투명 타입 만들기

커스텀 타입은 하나 이상의 변형을 가지고, 컴파일러는 변형 이름을 사용해 각 변형을 만드는 함수를 자동으로 만들어 줘요. 커스텀 타입은 이 방법으로만 만들 수 있어요. 컴파일러는 이런 변형에 대한 구조 분해도 허용해요.

type Maybe a
    = Nothing --> Variant implies a 'Nothing' function, with type 'Maybe a', and destructuring of 'Nothing'
    | Just a  --> Variant implies a 'Just' function, with type 'a -> Maybe a', and destructuring of 'Just _'

기본적으로 이 모든 것은 파일이나 모듈 내부에만 있고, exposing 키워드로 무엇을 내보낼지 선택해요. 불투명 타입을 만들려면 변형을 내보내지 않으면 돼요.

이렇게 하면 거의 항상 커스텀 함수를 노출해서 변형을 만들고 가져오는 다른 방법을 마련해야 해요. 이 커스텀 함수들을 사용하면 변형을 만들고 가져올 때 어떤 로직을 적용할 수 있어요. 이렇게 하면 모듈의 내부 구현 세부 사항이 숨겨지고, 노출된 함수가 그대로인 한 구현 세부 사항을 얼마든지 바꿀 수 있어요.

검증하지 말고 파싱하기

아마 불투명 타입의 가장 흔한 쓰임은 검증하지 말고 파싱하기 패턴으로 검증을 강제하는 거예요. 이 패턴에서는 String처럼 원하는 것보다 구조나 타입 안전성이 덜한 입력을 받아서, EmailAddress처럼 더 구조화된 타입으로 파싱하는 함수를 노출해요. 결과로 타입을 반환하거나 오류(보통 Maybe나 Result)를 반환해요.

예를 들어 아래 코드에서는 외부 모듈이 parseEmailAddress 함수로만 EmailAddress 타입을 만들 수 있어요. 그래서 EmailAddress 타입을 다룰 때는 그것이 유효하다고 확신할 수 있어요.

module EmailAddress exposing ( EmailAddress, parseEmailAddress, getEmailAddress )

-- The `EmailAddress` custom type has one variant, also called `EmailAddress`
-- When a custom type has one variant, it is idiomatic to use the same name for both.
type EmailAddress = 
  EmailAddress String

-- create an EmailAddress variant
parseEmailAddress: String -> Maybe EmailAddress
parseEmailAddress candidateEmailAddress =   
  if isValidEmailAddress candidateEmailAddress then
    Just (EmailAddress candidateEmailAddress)
  else
    Nothing

-- get an EmailAddress variant
getEmailAddress: EmailAddress -> String
getEmailAddress ( EmailAddress  emailAdress ) =
  emailAdress

isValidEmailAddress: String -> bool
-- ... 

지침

이번 연습 문제에서는 보물을 담아 두기 위해 제네릭(그리고 마법 같은!) SecureTreasureChest를 작성해요. 눈썰미 좋은 학생이라면 이전 TreasureChest가 그렇게 안전하지 않았다는 걸 눈치챘을 거예요! getTreasure 함수를 사용했다면 비밀번호를 넘겨야 했지만, 비밀번호를 몰라도 TreasureChest 타입을 구조 분해해서 보물을 꺼낼 수 있었어요. 더 심각한 건, 같은 방식으로 비밀번호까지 꺼낼 수 있었다는 거예요! Opaque 타입을 사용해서 이 문제를 해결해 봐요.

1. "Parse, don't validate" 패턴을 사용해 Password Opaque 타입 정의하기

배리언트가 하나뿐인 Password 커스텀 타입을 정의해요.

그 배리언트에는 비밀번호를 위한 String 값을 연결해요.

Password 타입은 노출하지만 배리언트는 노출하지 않아요. 그러면 Password가 Opaque 타입이 되어서, 구조 분해로 비밀번호를 꺼낼 수 없게 돼요. 어떤 타입이 Opaque인지 아닌지는 테스트로 확인할 수 없어요. 실제 프로덕션 코드에서는 elm-review(정적 분석 도구)를 사용해 이를 강제해요.

String을 받아 Maybe Password를 반환하는 createPassword 함수를, 타입 정의와 함께 정의하고 노출해요.

Owasp는 비밀번호가 최소 8자 이상이어야 한다고 권장해요. 그래서 이 불변식을 강제하기 위해 Parse, don't validate 패턴을 사용해 봐요.

비밀번호가 8자보다 짧으면 createPassword는 Nothing을 반환하고, 그렇지 않으면 Just Password를 반환해요.

2. SecureTreasureChest는 노출하되 배리언트는 노출하지 않기

SecureTreasureChest 타입은 노출하지만 배리언트는 노출하지 않아요. 그러면 SecureTreasureChest가 Opaque 타입이 되어서, 구조 분해로 비밀번호나 보물을 꺼낼 수 없게 돼요.

3. createTreasure 함수 정의하기

이 함수는 매개변수 두 개를 받고, 타입 정의를 함께 가져야 해요.

  • 제네릭 타입 (보물을 담기 위한 것)
  • Password (비밀번호를 위한 것)

이 함수는 SecureTreasureChest를 반환해요.

다른 모듈에서 SecureTreasureChest의 인스턴스를 만들 수 있도록, 이 함수를 노출해요.

4. getTreasure 함수 정의하기

이 함수는 매개변수 두 개를 받고, 타입 정의를 함께 가져야 해요.

  • String (비밀번호를 위한 것)
  • SecureTreasureChest 제네릭 커스텀 타입

이 함수는 전달받은 비밀번호 시도가 SecureTreasureChest 안의 Password와 일치하는지 확인해요.

이 함수는 Maybe를 반환해요.

비밀번호가 일치하면 SecureTreasureChest에 들어 있는 제네릭 값(바로 보물이에요!)을 담아 Just를 반환해요.

비밀번호가 일치하지 않으면 Nothing을 반환해요.

다른 모듈에서 올바른 비밀번호만 넘기면 SecureTreasureChest에서 보물을 꺼낼 수 있도록, 이 함수를 노출해요.

GitHub에서 편집 링크가 새 창이나 탭에서 열려요
Elm Exercism

안전한 보물 상자 문제를 시작해 볼 준비가 됐나요?

Exercism에 가입하고 Elm 트랙을 개념 28개연습 문제 110개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.