10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

これは何?

上記の10日目の記事です。

最近流行りのNix Flakeを使ってHaskellAtCoder環境を作ってみました。

開発者目線でも嬉しいですが、Nix Flakeを使うことでサプライチェーンのセキュリティ問題も解決できそうなので個人的に注目しています。

(アドカレタイトルとの関連も薄いですが、枠余っているし、一応AtCoder用の環境のリプレースなのでご容赦ください)

環境

  • Ubuntu22.04 LTS
  • Ubuntu24.04 LTS
  • Ubuntu 22.04(WSL): ホストOSはWindows11

Nixセットアップ

Nixインストール

公式ドキュメントどおりにインストールする。
特にSingle Userにする理由もないのでMulti Userでインストールした。Multi Userでインストールするとsudo時にも使えるなどのメリットがあるよう。

sh <(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon

nix-commandとflakesを有効化する

毎回、

nix --experimental-features 'nix-command flakes' develop

のように長いオプションを書くのは面倒なので、公式手順に従い、~/.config/nix/nix.confに以下を追記することでnix-commandflakesを有効にする。

~/.config/nix/nix.conf
experimental-features = nix-command flakes

これで上の長いコマンドが

nix develop

で良くなる。


flake.nixの作成

リポジトリトップにflake.nixを作成して記述していく。

{
  description = "Haskell dev environment AtCoder";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs =
    {
      self,
      nixpkgs,
      flake-utils,
    }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = import nixpkgs { inherit system; };
      in
      {
        formatter = nixpkgs.legacyPackages.${system}.nixfmt-tree;

        devShells.default = pkgs.mkShell {
          packages = [
            pkgs.zsh
            (pkgs.haskell.packages.ghc96.ghcWithPackages (ps: [
              ps.vector
              ps.containers
              ps.bytestring
            ]))
            pkgs.haskell.packages.ghc96.cabal-install
            pkgs.haskell.packages.ghc96.haskell-language-server
          ];
        };
      }
    );
}

以下に試したことを書いておく。

GHCのバージョン

AtCoder用にGHCのバージョンを9.4.5似合わせてみようかと思ったが、language serverの互換性がサポート外になっていたので9.6を選択。

外部ライブラリのインストール

自分はまだあまり外部ライブラリを使っていないのと、競技プログラミングだと真面目にバージョン管理するモチベーションが低いので、Data.Vector等のライブラリをflake.nix管理にしてみた。


nix developで環境作成

flake.nix作成後に以下を実行すると環境が作られて、bashが起動する。

nix develop

環境変数としてhttp_proxyhttps_proxyを設定していただけで、proxy経由しないとインターネットに出られない環境でもうまくいった。
Dockerよりもproxy設定も楽で素晴らしい

補足: zshを使いたい場合

shellHookに書いても良さそうだが、以下のようにしたらzshが起動した。

nix develop -c zsh 

追記: 後述のdirenvの設定をやるなら不要。


動作検証

Nixで入れたもののPATHが通っているか

nixで入れたrunghcが使われることを確認した。

nix develop
which runghc
/nix/store/k7bpzkib01ippnpwh7ljpdvzvpf40jfl-ghc-9.6.7-with-packages/bin/runghc

外部ライブラリは動くか

外部ライブラリである、Data.Vectorを入れたコードが動いたので一旦ヨシとする。

import Control.Arrow ((>>>))
import Control.Monad.ST (runST)
import Data.Vector qualified as V
import Data.Vector.Mutable qualified as MV
import Debug.Trace

-- Data.Vectorを使うバージョン
-- 2次元配列を1次元に平坦化: (r, c) -> r * n + c NOTE: Vector自体は1次元しかサポートしていない
solve :: Int -> [[Int]]
solve n = runST $ do
  arr <- MV.replicate (n * n) 0 -- n*n の要素を0で初期化
  -- debug用
  debugArr <- V.freeze arr
  traceM $ show (V.toList debugArr) -- [0,0,0,0,0,0,0,0,0]
  let startC = (n - 1) `div` 2 -- cの初期値
  let idx r c = r * n + c -- 2次元 -> 1次元のインデックス変換
  let go r c k
        | k > n * n = return ()
        | otherwise = do
            MV.write arr (idx r c) k
            let r' = (r - 1) `mod` n
                c' = (c + 1) `mod` n
            v <- MV.read arr (idx r' c')
            if v == 0
              then go r' c' (k + 1)
              else go ((r + 1) `mod` n) c (k + 1)

  -- goを呼び出し NOTE: doブロックの中ではletにinを使わないのでin goと書かなくて良い
  go 0 startC 1
  frozen <- V.freeze arr
  traceM $ show (V.toList frozen) -- debug [8,1,6,3,5,7,4,9,2]

  -- 1次元Vectorを2次元リストに変換
  return
    [ [ frozen V.! (idx i j)
        | j <- [0 .. n - 1]
      ]
      | i <- [0 .. n - 1]
    ]

main :: IO ()
main =
  interact $
    (read :: String -> Int) >>> solve >>> map (unwords . map show) >>> unlines

VS CodeでLSPやフォーマッタ、Linterは動くか

私の環境では、過去にghcupでインストールしたものが動いていたのでNixでインストールしたものに置き換えた。
workspaceの設定を以下のように修正。

.vscode/settings.json
{
  "[haskell]": {
    "editor.defaultFormatter": "haskell.haskell",
    "editor.formatOnSave": true
  },
  "haskell.manageHLS": "PATH", // GHCupから変更
  "haskell.formattingProvider": "ormolu",
  "haskell.ghcupExecutablePath": "", // PATHが通っていたので消した。
  "haskell.upgradeGHCup": false, // GHCupによる更新をしない
  "haskell.serverExecutablePath": "haskell-language-server", // 現在PATHが通っているHLSを使用する

nix developしていない状態でVS Codeを立ち上げるとLSPやフォーマッタ、Linterが使えないので注意。

nix develop
code .

追記: 後述のdirenvを使った設定をするとcode .だけですむようになる。


毎回nix developしなくていいようにdirenvを使う

こちらの記事を参考にセットアップしたので、元記事をご参照ください。

一応メモ程度のことは書いておく。

direnvインストール

sudo apt install -y direnv

nix-direnvをインストール

nix profile install nixpkgs#nix-direnv
~/.config/direnv/direnvrc
# 上記ブログの内容を丸コピしました
if [ -f "$HOME/.nix-profile/share/nix-direnv/direnvrc" ]; then
  source "$HOME/.nix-profile/share/nix-direnv/direnvrc"
elif [ -f "/nix/var/nix/profiles/default/share/nix-direnv/direnvrc" ]; then
  source "/nix/var/nix/profiles/default/share/nix-direnv/direnvrc"
elif [ -f "/run/current-system/sw/share/nix-direnv/direnvrc" ]; then
  source "/run/current-system/sw/share/nix-direnv/direnvrc"
fi

shellにフックを追加

こちらも上記のブログ参照。
自分はzshを使っているため以下のように設定

.zshrc
eval "$(direnv hook zsh)"

.envrcの作成

こちらも上記のブログと同様に.envrcを作成して以下を記載

.envrc
use flake

対象のリポジトリに移動した際に自動でnix developされるか確かめる

初回はエラーがでるのでdirenvを有効化すること

cd myproject
direnv: error /home/sigma/myproject/.envrc is blocked. Run `direnv allow` to approve its content
direnv allow
# GHCupでいれたものが有効
which runghc
/home/sigma/.ghcup/bin/runghc

cd myproject

# direnvによりPATHが切り替わる
which runghc
/nix/store/k7bpzkib01ippnpwh7ljpdvzvpf40jfl-ghc-9.6.7-with-packages/bin/runghc


おまけ: nix fmt

flake.nixのためのフォーマッタも一応導入した。

nix fmt

nix fmtを実行しても謎にハングする現象があったが、以下を参考にして修正したら動いた。

nix fmt
# ずっと終了されない

# これは動く
nix run nixpkgs#nixfmt-rfc-style -- flake.nix\n
# AIに作ってもらって動かなかったやつ
formatter = flake-utils.lib.eachSystem systems (system:
        let pkgs = import nixpkgs { inherit system; };
        in pkgs.nixfmt-rfc-style
      );

# 動いたやつ
formatter = nixpkgs.legacyPackages.${system}.nixfmt-tree;

nix fmtが動かない時に調べて参考になったURL

10
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?