# 모범 사례

```{contributors}
:authors: domenkozar
:editors: fricklerhandwerk, infinisil
```

## URL

Nix 언어 문법은 따옴표 없는 URL을 지원하므로 `"https://example.com"` 대신 `https://example.com`처럼 쓸 수도 있습니다.

따옴표 없는 URL을 폐기하자는 [RFC 45](https://github.com/NixOS/rfcs/pull/45)가 채택되었으며, 이 RFC는 이 기능이 득보다 실이 많은 이유를 여러 가지 제시합니다.

:::{tip}
URL은 항상 따옴표로 감싸십시오.
:::

(rec-expression)=
## 재귀 속성 집합 `rec { ... }`

`rec`을 사용하면 같은 속성 집합 안의 이름을 참조할 수 있습니다.

예시:

```{code-block} nix
:class: expression
rec {
  a = 1;
  b = a + 2;
}
```

```{code-block}
:class: value
{ a = 1; b = 3; }
```

흔히 빠지는 함정은 이름을 가릴(shadowing) 때 디버깅하기 어려운 `infinite recursion` 오류를 일으키는 것입니다. 가장 간단한 예시는 다음과 같습니다.

```{code-block} nix
let a = 1; in rec { a = a; }
```

:::{tip}
`rec`을 피하십시오. `let ... in`을 사용하십시오.

예시:

```{code-block} nix
:class: expression
let
  a = 1;
in {
  a = a;
  b = a + 2;
}
```
:::

:::{tip}
속성 집합에 명시적으로 이름을 붙이면 자기 참조를 할 수 있습니다.

```{code-block} nix
:class: expression
let
  argset = {
    a = 1;
    b = argset.a + 2;
  };
in
  argset
```
:::

## `with` 스코프

실제 코드에서는 여전히 다음과 같은 표현식을 흔히 볼 수 있습니다.

```{code-block} nix
:class: expression
with (import <nixpkgs> {});

# ... lots of code
```

이렇게 하면 가져온 표현식의 모든 속성이 현재 표현식의 스코프로 들어옵니다.

이 방식에는 다음과 같은 문제가 있습니다.

- 어떤 이름이 스코프에 있는지 알려면 이 파일을 실제로 평가해야 하므로, 정적 분석으로는 코드를 추론할 수 없습니다.
- `with`를 두 개 이상 사용하면 이름이 어디에서 오는지 더 이상 분명하지 않습니다.
- `with`의 스코프 규칙은 직관적이지 않습니다. 자세한 내용은 이 [Nix 이슈](https://github.com/NixOS/nix/issues/490)를 참고하십시오.

:::{tip}
Nix 파일의 최상단에서 `with`를 사용하지 마십시오. `let` 표현식에서 이름을 명시적으로 할당하십시오.

예시:

```{code-block} nix
:class: expression
let
  pkgs = import <nixpkgs> {};
  inherit (pkgs) curl jq;
in

# ...
```
:::

스코프가 작으면 대개 문제가 덜하지만, 스코프 규칙 때문에 여전히 예상치 못한 결과가 생길 수 있습니다.

:::{tip}
`with`를 아예 피하고 싶다면 다음 형태의 표현식을

```{code-block} nix
:class: expression
buildInputs = with pkgs; [ curl jq ];
```

다음과 같이 바꿔 보십시오.

```{code-block} nix
:class: expression
buildInputs = builtins.attrValues {
  inherit (pkgs) curl jq;
};
```
:::

(search-path)=
## `<...>` 조회 경로

`<nixpkgs>`를 참조하는 Nix 언어 코드 예시를 자주 보게 될 것입니다.

`<...>`는 환경 변수 [`$NIX_PATH`]의 값에 편리하게 접근하려고 [2011년에 도입된][introduced in 2011] 특수 문법입니다.

[introduced in 2011]: https://github.com/NixOS/nix/commit/1ecc97b6bdb27e56d832ca48cdafd3dbb5185a04
[`$NIX_PATH`]: https://nix.dev/manual/nix/stable/command-ref/env-common.html#env-NIX_PATH

즉, 조회 경로의 값은 외부 시스템 상태에 따라 달라집니다. 조회 경로를 사용하면 같은 Nix 표현식이 다른 결과를 낼 수 있습니다.

대부분의 경우 `$NIX_PATH`는 Nix를 설치할 때 최신 채널로 설정되므로 머신마다 다를 가능성이 높습니다.

:::{note}
[채널](https://nix.dev/manual/nix/stable/command-ref/nix-channel.html)은 원격 Nix 표현식을 참조하고 그 최신 버전을 가져오는 메커니즘입니다.
:::

구독한 채널의 상태는 그 채널에 의존하는 Nix 표현식의 외부에 있습니다. 이 상태는 머신 간에 쉽게 옮길 수 없습니다. 이는 재현성을 제한할 수 있습니다.

예를 들어, 서로 다른 머신을 쓰는 두 개발자의 `<nixpkgs>`는 {term}`Nixpkgs` 저장소의 서로 다른 리비전을 가리킬 가능성이 높습니다. 빌드가 한쪽에서는 성공하고 다른 쪽에서는 실패하여 혼란을 일으킬 수 있습니다.

:::{tip}
[](pinning-nixpkgs)에 나온 기법을 사용하여 의존성을 명시적으로 선언하십시오.

최소한의 예제가 아니라면 조회 경로를 사용하지 마십시오.
:::

일부 도구는 조회 경로가 설정되어 있을 것을 전제로 합니다. 이 경우에는 다음과 같이 하십시오.

::::{tip}
버전 관리되는 중앙 위치에서 `$NIX_PATH`를 알려진 값으로 설정하십시오.

:::{admonition} NixOS
NixOS에서는 [`nix.nixPath`](https://search.nixos.org/options?show=nix.nixPath) 옵션으로 `$NIX_PATH`를 영구적으로 설정할 수 있습니다.
:::
::::

(nixpkgs-config)=
## 재현 가능한 Nixpkgs 설정

시연용 패키지를 빠르게 얻기 위해 다음과 같은 간결한 패턴을 사용합니다.

```nix
import <nixpkgs> {}
```

그러나 [](pinning-nixpkgs)에 나온 대로 `<nixpkgs>`를 대체하더라도 결과가 완전히 재현 가능하지 않을 수 있습니다. 역사적인 이유로 [Nixpkgs 최상위 표현식][Nixpkgs top-level expression]이 설정 매개변수를 얻기 위해 기본적으로 파일 시스템을 비순수하게 읽기 때문입니다. 해당 파일이 채워져 있는 시스템에서는 다른 결과가 나올 수 있습니다.

[Nixpkgs top-level expression]: https://github.com/NixOS/nixpkgs/blob/master/default.nix

이는 기존 설정을 깨뜨리지 않고는 해결할 수 없는 잘 알려진 문제입니다.

:::{tip}
Nixpkgs를 가져올 때 [`config`](https://nixos.org/manual/nixpkgs/stable/#chap-packageconfig)와 [`overlays`](https://nixos.org/manual/nixpkgs/stable/#chap-overlays)를 명시적으로 설정하십시오.


```nix
import <nixpkgs> { config = {}; overlays = []; }
```
:::

튜토리얼에서는 예제가 정확히 기대한 대로 동작하도록 이렇게 합니다. 최소한의 예제에서는 주의를 흩뜨리지 않도록 이를 생략합니다.

## 중첩된 속성 집합 업데이트하기

[속성 집합 업데이트 연산자](https://nix.dev/manual/nix/stable/language/operators.html#update)는 두 속성 집합을 병합합니다.

예시:

```{code-block} nix
:class: expression
{ a = 1; b = 2; } // { b = 3; c = 4; }
```

```{code-block} nix
:class: value
{ a = 1; b = 3; c = 4; }
```

그러나 오른쪽의 이름이 우선하며, 업데이트는 얕게 이루어집니다.

예시:

```{code-block} nix
:class: expression
{ a = { b = 1; }; } // { a = { c = 3; }; }
```

```{code-block} nix
:class: value
{ a = { c = 3; }; }
```

여기서는 `a` 값 전체가 대체되었기 때문에 키 `b`가 완전히 제거되었습니다.

:::{tip}
Nixpkgs 함수 [`pkgs.lib.recursiveUpdate`](https://nixos.org/manual/nixpkgs/stable/#function-library-lib.attrsets.recursiveUpdate)를 사용하십시오.

```{code-block} nix
:class: expression
let pkgs = import <nixpkgs> {}; in
pkgs.lib.recursiveUpdate { a = { b = 1; }; } { a = { c = 3;}; }
```

```{code-block} nix
:class: value
{ a = { b = 1; c = 3; }; }
```
:::

## 재현 가능한 소스 경로

```{code-block} nix
:class: expression
let pkgs = import <nixpkgs> {}; in

pkgs.stdenv.mkDerivation {
  name = "foo";
  src = ./.;
}
```

이 표현식을 담은 Nix 파일이 `/home/myuser/myproject`에 있다면 `src`의 스토어 경로는 `/nix/store/<hash>-myproject`가 됩니다.

문제는 이제 빌드가 상위 디렉터리 이름에 의존하므로 더 이상 재현 가능하지 않다는 점입니다. 이는 소스 코드에 선언할 수 없으며, 결과적으로 비순수성을 초래합니다.

누군가 다른 이름의 디렉터리에서 프로젝트를 빌드하면 `src`와 이에 의존하는 모든 것에 대해 다른 스토어 경로를 얻게 됩니다. 이는 불필요한 재빌드의 원인이 될 수 있습니다.

:::{tip}
`name` 속성을 고정된 값으로 설정한 속성 집합과 함께 [`builtins.path`](https://nix.dev/manual/nix/stable/language/builtins.html#builtins-path)를 사용하십시오.

이렇게 하면 스토어 경로의 심볼릭 이름이 작업 디렉터리 대신 `name`에서 도출됩니다:

```{code-block} nix
:class: expression
let pkgs = import <nixpkgs> {}; in

pkgs.stdenv.mkDerivation {
  name = "foo";
  src = builtins.path { path = ./.; name = "myproject"; };
}
```
:::
