嗚呼、帰りたい

プログラミングのことから日常のことまで。いわゆるごった煮というものだな。

レイトレーシングを実装した話

この記事はあくあたん工房 Advent Calendar 2023の9日目の記事です。8日目はトカゲ教授こと水野先生によるヒョウモントカゲモドキの孵卵器を自作するでした。

はじめに

こんにちは、茶葉亭です。アドカレとしてちょうどよいネタを探していたところ、しばらく積んでたRay Tracing in One Weekendを思い出したので、実際に手を動かして実装してみることにしました。参考元では実装にC++を用いていますが、個人的にRustで本格的にコードを書いてみたかったこともあり、今回はRustで実装します。

コードはこちらのリポジトリに置いていますのでよろしければご覧ください。

github.com

続きを読む

コンパイル時brainfuckインタプリタを作った話

この記事はあくあたん工房 Advent Calendar 2021の12日目の記事です

なおこの記事が執筆されたのは12月31日です

大学の必修の課題が重かったんだ...許しておくれ...
なお年内に間に合わせるために色々妥協したのでクオリティについてはお察しください

C++コンパイル時計算

C++は広く知られた言語ですが,その機能の一つにコンパイル時計算というものがあります.読んで字のごとく,コンパイルの最中に計算する機能です.この記事はコンパイル時計算を説明する記事ではないので詳細な説明は省きますが,例えば次のような関数があったとして

// C++14
constexpr int fib(int n) {
    if (n == 0) return 0;
    if (n == 1) return 1;
    return fib(n - 1) + fib(n - 2);
}

次のように変数に代入したりすると

constexpr int a = fib(10);

コンパイラによって生成されたバイナリでは fib(10) の呼び出しが無くなり,その計算結果である 55 が直接埋め込まれます.

さて,このコンパイル時計算を使うと面白そうなことができそうな気がしますね.ということでコンパイル時にbrainfuckを実行するプログラムを作ってみました.

コンパイルbrainfuckインタプリタ

今回はC++17を使ってプログラムを作りました.まずはプログラム全体をお見せしましょう.

ちょっと長いので折り畳み

brainfuck::interpreter<InputPtr, ProgramPtr> がその名の通りインタプリタとなっていて,第一引数の InputPtr に入力文字列へのポインタを,第二引数の ProgramPtr にプログラムの文字列へのポインタを受け取ります.私の理解が正しければ,template引数に課せられた制約の都合上,これらの引数はグローバル変数として宣言されている必要があります.

brainfuck::interpreter<InputPtr, ProgramPtr>result に実行結果を,output に出力用の uint8_t 配列を格納します.それぞれについて詳しく見ていきましょう.

result

C++17ではconstexprの文脈でラムダ式を使うことができるのでその機能を活用しています.また,C++17から導入された std::variant (型安全なunionみたいなもの) を使って,result に出力結果かエラーコードを格納しています.出力についてコンパイル時には標準出力を利用することができないので,代わりに特定の長さで配列を確保しておいて順次そこに書き込むようにしており,エラーについてもコンパイル時に標準出力を利用できない都合上エラーコードを返すような設計としています.他に特筆するべき部分はなく,brainfuckプログラムが適格か確認したり,一個一個コマンドを解釈して実行していたりするぐらいです.

output

output は生成された result を基に,出力の余分な部分を切り詰めたり,エラーコード別にコンパイルエラーを出す処理を行ったりします.ある意味コンパイル時計算の威力が最も発揮される部分かもしれません.

static constexpr inline auto output = [] {
    if constexpr (result.index() == 0) {
        constexpr auto buf = std::get<0>(result);
        std::array<uint8_t, buf.second> ret = { };
        for (size_t i = 0; i < buf.second; i++) {
            ret[i] = buf.first[i];
        }
        return ret;
    } else {
        constexpr auto error = std::get<1>(result);
        if constexpr (error == error_reason::MISMATCHING_SQUARE_PARENTHESIS) {
            static_assert(false_v<>, "mismatching square parenthesis");
        } else if constexpr (error == error_reason::TRIED_TO_ACCESS_OUT_OF_INPUT_RANGE) {
            static_assert(false_v<>, "tried to access out of input range");
        } else if constexpr (error == error_reason::TRIED_TO_ACCESS_OUT_OF_CELLS_RANGE) {
            static_assert(false_v<>, "tried to access out of memory range");
        } else if constexpr (error == error_reason::OUTPUT_CAPACITY_IS_TOO_SMALL) {
            static_assert(false_v<>, "output capacity is too small");
        } else {
            static_assert(false_v<>, "unhandled error!");
        }
    }
}();

C++17ではconstexpr if文を使ってコンパイル時に定数式の結果に応じて処理内容を変えることができます.上に示したコードはインタプリタのコードから output の部分を抜き出してきたものですが,if constexpr (result.index() == 0) で処理を切り分けているのが分かると思います.この例では resultコンパイル時に計算されてしまっているのでその結果を利用することができ,もし result.index() == 0 が成立したら,すなわち result に出力結果が格納されている場合はバイナリには条件が真のときに実行される処理のみが残され,逆に result.index() == 0 が成立しなかったら,すなわち result にエラーコードが格納されている場合はバイナリには条件が偽のときに実行される処理のみが残されます.一見メリットが分かりにくいかもしれませんが,これを応用することで次のようなコードが実現できたりします.

template<typename T>
constexpr auto func(const T &n) {
    if constexpr (std::is_arithmetic_v<T>) { // Tが算術型かどうか
        return n * 2; // 2倍した値を返す
    } else { // 上と下とで返り値の型が違う!
        return "value is not arithmetic"; // 2倍できない旨を返す
    }
} 

int main()
{
    std::cout << func(42) << std::endl; // 84
    std::cout << func("Hello, world!") << std::endl; // value is not arithmetic
}

output の例では result.index() == 0 でない場合は値を返さずに static_assertコンパイルエラーにします.result に格納されているエラーコード別にメッセージを変えているというのがなんとなくわかると思います.

実行してみた

作ったインタプリタが実際に動くかどうかを確認してみました.実行したbrainfuckのプログラムは Hello world! を出力する次のコードです.

++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.

結果として次のような出力が得られました.とりあえず問題なく動作している様子が確認できました.

Hello world!

本当にコンパイル時に実行しているのか

動作している様子が確認できているとはいえ,実行されているタイミングがコンパイル時でないことを確認できないことには意味がありません.そこで g++ -Sアセンブリを出力させてみます.以下に main関数に対応する(と思われる)アセンブリを載せます.アセンブリには慣れてないので間違ってたらごめんなさい.

長いので折り畳み

main:
.LFB2057:
    .cfi_startproc
    endbr64
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq %rsp, %rbp
    .cfi_def_cfa_register 6
    subq $64, %rsp
    movq %fs:40, %rax
    movq %rax, -8(%rbp)
    xorl %eax, %eax
    movb $72, -20(%rbp)
    movb $101, -19(%rbp)
    movb $108, -18(%rbp)
    movb $108, -17(%rbp)
    movb $111, -16(%rbp)
    movb $32, -15(%rbp)
    movb $87, -14(%rbp)
    movb $111, -13(%rbp)
    movb $114, -12(%rbp)
    movb $108, -11(%rbp)
    movb $100, -10(%rbp)
    movb $33, -9(%rbp)
    leaq -20(%rbp), %rax
    movq %rax, -48(%rbp)
    movq -48(%rbp), %rax
    movq %rax, %rdi
    call _ZNKSt5arrayIhLm12EE5beginEv
    movq %rax, -56(%rbp)
    movq -48(%rbp), %rax
    movq %rax, %rdi
    call _ZNKSt5arrayIhLm12EE3endEv
    movq %rax, -40(%rbp)
.L3:
    movq -56(%rbp), %rax
    cmpq -40(%rbp), %rax
    je   .L2
    movq -56(%rbp), %rax
    movq %rax, -32(%rbp)
    movq -32(%rbp), %rax
    movzbl   (%rax), %eax
    movsbl   %al, %eax
    movl %eax, %esi
    leaq _ZSt4cout(%rip), %rdi
    call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c@PLT
    addq $1, -56(%rbp)
    jmp  .L3
.L2:
    movl $0, %eax
    movq -8(%rbp), %rdx
    xorq %fs:40, %rdx
    je   .L5
    call __stack_chk_fail@PLT
.L5:
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

上のアセンブリのうち,以下に示す部分

    movb $72, -20(%rbp)
    movb $101, -19(%rbp)
    movb $108, -18(%rbp)
    movb $108, -17(%rbp)
    movb $111, -16(%rbp)
    movb $32, -15(%rbp)
    movb $87, -14(%rbp)
    movb $111, -13(%rbp)
    movb $114, -12(%rbp)
    movb $108, -11(%rbp)
    movb $100, -10(%rbp)
    movb $33, -9(%rbp)

Hello world! に対応するバイト列 72 101 108 108 111 32 87 111 114 108 100 33 が即値で埋め込まれているのが確認できます.どうやら本当にコンパイル時に処理ができているようです.

制約

コンパイル時に動作する都合上,このインタプリタコンパイラの制約を受けます.特にGCCの場合は -fconstexpr-loop-limit-fconstexpr-ops-limit でループや演算の回数が縛られます.まあこれらの数値は大きくすることができますが,その分だけコンパイルにかかる時間が伸びますし,brainfuckのプログラムが無限にループしている場合は先ほどの数値を大きくしてもコンパイルできません.まあでもお遊び程度でbrainfuckのプログラムを実行させるならほとんど引っかかることはないんじゃないでしょうか.

まとめ

コンパイル時にbrainfuckを実行するインタプリタを作成しました.C++は規格が新しくなるたびにコンパイル時計算ができる処理が増えていっていることもあり,C++17の時点でもかなり楽にコンパイル時計算が書ける印象があります.実際,brainfuckインタプリタを作る過程でもそこまで悩むことはありませんでした.この記事を読んでコンパイル時計算に興味を持った人(そんな人おるんかな)がいたらまずは副作用のない関数から順次 constexpr をつけていくといいんじゃないですかね.

最後までお読みいただきありがとうございました.

山に登る

蝶ヶ岳山頂からの光景

この記事はあくあたん工房 Advent Calendar 2020の14日目の記事です.遅刻して16日投稿になっていますが一応カレンダー上では2日連続で帰宅志願者がお送りします.何を書くかでとても迷ったのですが,最近大学の実験レポートやら課題やらでヘトヘトになっているところを癒やしを求めて出身高校の山岳部の山行にOBとしてついていったことから僕の趣味の「登山」のことについて書こうと思います.

いきなりですが「登山」というのは奇妙な行為です.交通機関を駆使して目的の山に赴いては,時には辛い思いをしながら延々と山道を何時間も登ったり降りたりして目的地を転々とし,最後に家に帰るわけです.私はどちらかといえば都市部の方に住んでいますから,山に行くだけでも数千円がかかりますし,そこから山に登るわけですから装備を整えたりして更にお金がかかります.誤解を招かないように先に述べておきますが,山に登る行為自体は特段おかしいものではありません.森林資源の獲得だったり都市間移動だったりあるいは宗教的な意味合いがあったりとそういった目的で古くから山に登る人はいます.しかしながらどうでしょう.登山,特に趣味や生き様としての「登山」というのは山に登るという行為自体を目的としています.ますます奇妙に思えてきませんか.どうして「登山」をするのか,人によって理由は多種多様ではありますが,この記事では僕の登山歴を振り返りつつ僕なりの理由付けをしてみようと思います.

僕が本格的に山に登り始めたのは高校一年生の頃です.高校受験を無事終えどの部活に入るかで悩んでいたとき,当時の山岳部公式Twitterアカウントから一枚の写真が流れてきたのをきっかけに山岳部に入部しました.山岳部のアカウントが途中で変わったために残念ながらその写真を探し出すことができなかったのですが,その写真は記憶に間違いがなければ立山で撮影されたもので,吸い込まれそうなほど青く透き通った空を突き刺さんとばかりに山々が堂々と佇んでいる光景が収まっており,思わず見とれてしまったことをよく覚えています.

山岳部は月一回の山行に参加すれば普段の活動に来なくてもOKという体制で運営されており,そんな山岳部のゆるい部分がだらしない僕の性格と適合したのかとても居心地が良い場所でした.登山というのは体力を使うものですから,月一回の山行に参加するだけで今後もついていけるのかという不安もありましたが,これが思いのほか慣れてしまうもので,体力を使うことに変わりはありませんが山に登るときのしんどさというものはどんどん消えていきました.

そうして山岳部として山に登り続けるようになったのですが,僕の中で登山が重要な立ち位置を占めるようになった決定的な出来事といえば一年生のときの山岳部の合宿です.一年生のときの合宿の行き先は上高地で,徳沢キャンプ場を幕営地としていました.合宿二日目となり,待ち望んでいたとにかく標高の高い山1である蝶ヶ岳に向けていよいよ出発したのはいいのですが,片道5kmで標高差1,100m2の山道で最初は似たような景色が延々と続くのでなかなかしんどく感じられました.

蝶ヶ岳に至る登山道.最初は新鮮に感じるがこの景色がずっと続く

絶景を期待していたばかりに似たような木々の風景が続くのに少しウンザリしながら登り続けていると,徐々に植生が低木に置き換わっていきます.

森林限界に至り突然視界が開ける

歩きながら「あの景色がいよいよこの目で見られる」と期待も頂点に達したとき,目の前に待ち望んでいたそれが現れました.

あの景色を僕はどのように言葉で表せばいいのか今でも分かりませんが,鮮明に思い出せるほどに僕の脳裏に焼き付いています.信じられないほど青い空.容赦なく照りつける太陽.透き通った冷涼な大気.四方を埋め尽くす巨大な山々.頭上ではなく眼下を流れる雲海.現地でしか味わうことのできないあの空気感.あの場所を去るのが惜しまれたのでせめて写真を取って記録に残そうと努力しましたが,どうやっても僕が目にしているあの光景を写真に収めることができません.しかしながら時間は残酷にも過ぎていき,興奮も冷めやらぬうちに下山の時間をむかえてしまいました.

合宿はまだ続き,三日目は積雪のために当初予定していたルートを変更して涸沢に向かったのですが,こちらもやはりなんというか...すごかった.どうやって言葉で表すべきなんでしょうか.

涸沢は氷河の侵食作用によって生じたカールという地形をしているのですが,今にも僕を飲み込まんとするような,そういった迫力がありました.二日目の時点で「写真に収めるのは無理だ」という結論に至ったのでこちらは写真を数枚取る程度におさめてあとは場の空気感を楽しむことに専念しました.それでもやはり下山するのは惜しまれましたが...

合宿を終えてからは大会の練習を積んだりとか後輩ができたりとかいくつかの大会で優勝したりとか二年連続でインターハイに出場しているのに僕の代では出場できなかったりとか色々ありましたが,一年生のときの合宿と比較すればまあどれも些末なもの3です.二年生のときの合宿もやはり感動があったのですが,流石にくどいのでここで述べるのはやめておきます.

高校を卒業して大体二年半とちょっとを過ぎますが,まだ僕は日帰りで行ける山を中心に登り続けています.先日はこの記事の冒頭でも述べたように山岳部の通常山行にOBとして参加し,武奈ヶ岳に登りに行きました.山岳部で活動していた中で山に登るときのしんどさというものは消えましたが,やはり未だに山に登るのは疲れますし足が痛くなります.それでも登ることだけを考えて歩き続ければペースが多少遅かろうがいずれは山頂に着きますし家に帰れます.まあ時には引き返す勇気も必要ですが.

合宿のときの記憶は未だに鮮明に残っており,今でもあの日と同じ空気感をもう一度味わいたいと思っています4.僕の家から日帰りで行けるような山では合宿のときのような感動はありませんが,一旦登山のしんどさが消えてしまえば身の回りのことから一旦距離を取って山の中に身をおくだけでもなんだか楽しく感じます.だから僕は山に登り続けるのでしょう.

いつも技術系の記事を書いているのでどうしても気持ち悪く微妙な文章になってしまいました.まあ初めての試みなので大目に見てやってください.友人の言葉を借りてこの記事の締めとしたいと思います.

"なぜ山に登るのか?"は実際に山に登った人だけの特権ですよ


  1. 標高の高い山を意図して「高山」でググったら岐阜県高山市という飛騨山脈付近の市町村が出てきたので地名ではないことを明示するためにわざわざ「標高の高い山」という表現にしました

  2. 徳沢キャンプ場から長堀山を経由して蝶ヶ岳に至るルートでの当時の記録による大体の数値.地形図に糸を乗せながら測った記憶があるが正直なところ今となっては自信がない

  3. 山岳部では伝統的に毎年春と秋の大会に出場することになっていました.別に大会に出たいわけではなかったのですが,大会では1チームを4人で組まなければいけない一方で僕の代の部員は5人しかおらず,しかも1人が兼部していて大会の練習ができる状況ではなかったので残った4人で仕方なく大会に出ることになったわけです.他の3人も大会出場に否定的だったのになんでいくつかの大会で優勝できたのか今でも分かりません.最後の大会が終わったときは喜びさえしました.まあでも優勝して全校集会で表彰されるのは悪いものではありませんでした

  4. 卒業後も僕を含めた同期の5人で毎年夏山の計画を立てるのですが,大学浪人してたり予定日に台風が接近したりCOVID-19が蔓延しだしたりで毎回なかったことになってなかなかうまくいきません

C++で型に特定のメンバ関数が宣言されているときだけその関数を呼び出す処理を実装する

お久しぶりです,帰宅志願者です.この記事はあくあたん工房 Advent Calendar 2020の13日目の記事です.なお執筆時点で14日目になっていて大遅刻しております.ごめんなさい.前置きはこれぐらいにして本題へ.

型に特定のメンバ関数が宣言されている場合はその関数を呼び出したい...

と思うことが最近C++を書いていて思うことがありました.これを実現するには

  1. まず型に特定のメンバ関数が存在するかを確認し
  2. 存在しているかどうかで処理を分岐する

ことが必要です.私はC++初心者なので頭をひねってみてもどのようにすれば良いのかがわかりません.というかそもそもこれが実現できるのかさえ怪しく思えます.

ということでGoogle先生に聞いてみた.

型に特定のメンバ関数が存在するかを確認する

SFINAEを使うバージョン

最初に出てきたのがSFINAEを用いたこちらの形.型Tにfunc()というメンバ関数が存在するかどうかを調べています.

template<typename T>
struct has_func {
private:
    template<typename U, typename = std::void_t<decltype(std::declval<U>().func())>>
    static std::true_type check(U);
    static std::false_type check(...);
public:
    static constexpr bool value = decltype(check(std::declval(T)))::value;
};

以下のようにして型に特定の関数が存在するかをコンパイル時にbool値で得ることができます.

has_func<T>::value;

メタ関数を使うバージョン

最初の結果で満足せずにGoogle先生を質問攻めにしたところ std::experimental::is_detected なるものが出てきました.これはまだC++で実験的な扱いを受けているメタ関数ですが,これを使うと次のようにしてメンバ関数の存在を調べることができます.

template<typename T>
using func_t = decltype(std::declval<T>().func());

template<typename T>
using has_func = std::experimental::is_detected<func_t, T>;

こちらも先ほどと同様に以下のようにして特定の関数が存在するかをコンパイル時にbool値で得ることができます.

has_func<T>::value;

SFINAEを用いたバージョンと比較してとてもスッキリとした印象を受け,良さげに思えます.しかしながら, std::experimental::is_detected はまだ実験的なものですから使用が難しいところでしょう.幸いなことに std::experimental::is_detected は数行の実装で実現することができます.そこで以下のように実装してしまうことにしましょう.

namespace detail {
    template<typename AlwaysVoid, template<typename ...> typename Op, typename ...Args>
    struct detector : std::false_type {};

    template<template<typename ...> typename Op, typename ...Args>
    struct detector<std::void_t<Op<Args...>>, Op, Args...> : std::true_type {};
}

template<template<typename ...> typename Op, typename ...Args>
using is_detected = detail::detector<void, Op, Args...>;

あるいは自分で実装しなくてもBoost.TypeTraitsがすでに実装を提供していますから,そちらを使っても良いと思います.

メンバ関数が存在しているかどうかで処理を分岐する

先程まででメンバ関数が存在しているかどうかがbool値で得られることがわかったのでこれを使って実装します.やり方としては愚直にif文を使ったりSFINAEを使う方法がありますが,もう2020年も終わろうとしている時期ですからC++17の恩恵を被ることにしましょう.constexpr if文を使います.

T t;
if constexpr (has_func<T>::value) t.func();

if文と同様の記述量でSFINAEと同様に実体化するかどうかの制御をすることができます.清々しいほどにシンプルですね.

まとめ

C++でここまで込み入ったコードは初めて書きましたが,静的型付け言語であるC++でこんな処理ができるのかと驚くばかりでした.私はC++初心者なので(保険)記事に間違いや気になる点などがありましたら優しく指摘していただけると幸いです.以上「C++で型に特定のメンバ関数が宣言されているときだけその関数を呼び出す処理を実装する」でした.

Haskellに入門するべくUbuntu18.04でNixを使って環境構築をした

大学が春休みに入ったので,以前から興味があったHaskellに入門することにしました.となると最初に待ち構えるのは環境構築となるわけで,ネット上の様々な記事を読んで作業を進めていたわけですが,結構詰まった部分があって苦労しました.ですから,備忘録としてブログにその過程を残しておきます.誰かの役に立てればいいな.

なお私が環境構築を行ったOSは Ubuntu 18.04.4 LTS です.

今回用いるソフトウェアたち

ネット上の記事によると,ひとえに環境構築といえども異なるツールの組み合わせが存在するようです.そこで今回は

の組み合わせでやっていきます.VSCodeはインストールされていることを前提とします.

NixとHIEをインストールする

Nixでシュッと入れるのがバージョン管理も含めて一番手軽に思えたので以下のページに従ってNixとHIEをインストールします.

ryota-ka.hatenablog.com

stackをインストールする

せっかくNixというパッケージマネージャをインストールしたのですから,有効活用します.ということで早速stackもNixでインストールします.

nix-env -iA nixpkgs.stack nixpkgs.cabal-install 

インストールしたら以下のコマンドを実行します.

stack update

次に ~/.local/bin/ へのパスを通します.私はfishを使っているので以下のコマンドを ~/.config/fish/config.fish に追加しました.

set PATH ~/.local/bin $PATH

ここで,GHC8.6.5を利用するために~/.stack/global-project/stack.yaml

resolver: lts-15.0

resolver: lts-14.27

に変えておきます.GHC8.6.5以外のコンパイラを用いる場合は https://www.stackage.org の Latest LTS per GHC version のところから適宜選択して変更してください.

GHC をインストールする

GHCgmp に依存しているようなので apt でインストールしておきます.

sudo apt install libgmp-dev

以下のコマンドでGHCをインストールします.

stack setup

なお私の環境では以下のエラー

<stderr>: commitAndReleaseBuffer: invalid argument (invalid character)

が出たので,ググったらなんか出てきたこのイシューを参考に,

env LC_ALL="C.UTF-8" stack setup

とすることでインストールできました.

hoogleをインストールする

HIEはAPIドキュメントの表示にhoogleを用いるので以下のコマンドでインストールします.

stack install hoogle

GHCのインストールで述べたものと同様のエラーが出る場合は

env LC_ALL="C.UTF-8" stack install hoogle

を実行するとうまくいきます.

hoogleをインストールしたら

hoogle generate

を実行してセットアップも済ませておきましょう.

プロジェクトの作成

プロジェクトのディレクトリを置きたいディレクトリで以下を実行します.<プロジェクト名> の部分には自由に名前を入れて下さい.

stack new <プロジェクト名> --resolver lts-14.27

すると <プロジェクト名> の名前がついた以下のような構成のディレクトリが生成されます.なお私が用いたstackのバージョンは 2.1.3.1 x86_64 hpack-0.33.0 ですが,他のバージョンではディレクトリの構成が異なるかもしれません.

<プロジェクト名>
├── app
│   └── Main.hs
├── ChangeLog.md
├── <プロジェクト名>.cabal
├── LICENSE
├── package.yaml
├── README.md
├── Setup.hs
├── src
│   └── Lib.hs
├── stack.yaml
├── stack.yaml.lock
└── test
    └── Spec.hs

作成したプロジェクトのディレクトリに移動して以下のコマンドを実行します.もしかするとこのコマンドを実行する必要は無いかもしれませんが,一応,念のため.

stack setup

するとGHC8.6.5がインストールされます.

VSCodeのセットアップ

VSCodeHaskell Language Server という拡張機能をインストールします.これにより,VSCodeでHIEの恩恵にあずかることができます.

marketplace.visualstudio.com

場合によってはエラーが出ることがあるので,その際は

qiita.com

を参考にするといいかもしれません.私の場合はトラブルシューティングの一番最初に載っているエラーが発生しました.

実行する

作成したプロジェクトのディレクトリ内で

stack run

あるいは

stack runhaskell <main関数があるファイルへのパス>

を実行しましょう.以上で環境構築は終わりです.

まとめ

正直なところ環境構築をしてて詰まるところが私は多かったので一旦は入門を諦めようかとも思いましたが,なんとかなってよかったです.