Add blog (4)
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
import { page } from "$app/state";
|
||||
import { limitWidth } from "$lib/constants";
|
||||
import { cn } from "$lib/utils";
|
||||
import LuCopyleft from "lucide-svelte/icons/copyleft";
|
||||
import LuChevronsRight from "lucide-svelte/icons/chevrons-right";
|
||||
import LuCopyleft from "lucide-svelte/icons/copyleft";
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
---
|
||||
title: F# で型クラス
|
||||
description: F# の言語機能の隠された真の力をお伝えするために、とりあえずは Haskell の do-notation のようなものを実現してみせよう
|
||||
date: !!timestamp 2018-02-14T03:54:24+09:00
|
||||
category: Programming
|
||||
tags: ["F#", "SRTP"]
|
||||
---
|
||||
|
||||
F# は OCaml を .NET に乗っけて色々足した言語だが、その過程で失ってしまったものもたくさんあり、 その中でも特に痛いのは functor がないことだ。
|
||||
|
||||
そして F# には高階型も型クラスもないので、われわれは例の interface でなんとか生き延びざるを得ない……
|
||||
|
||||
……わけでもない。
|
||||
|
||||
F# の言語機能の隠された真の力をお伝えするために、とりあえずは Haskell の do-notation のようなものを実現してみせよう。
|
||||
|
||||
まず、名前を言ってはいけない例のあの概念を表す "型クラス" を作る。
|
||||
|
||||
```fsharp
|
||||
[<Struct>]
|
||||
type MonadClass<'a, 'Ma, 'Mb> = {
|
||||
Bind: ('a -> 'Mb) -> 'Ma -> 'Mb
|
||||
Return: 'a -> 'Ma
|
||||
}
|
||||
```
|
||||
|
||||
コンテナ型 `M` に対する bind/return の実装を、型 `MonadClass<'a, M<'a>, M<'b>>` の値で持つことになる。
|
||||
|
||||
次に、既存の型を "インスタンス化" しておく。今回は `'a option` と `Result<'a, 'b>` を使う。
|
||||
|
||||
```fsharp
|
||||
type MonadBuiltin = MonadBuiltin with
|
||||
static member MonadImpl (_: option<_>) =
|
||||
{ Bind = Option.bind; Return = Some }
|
||||
|
||||
static member MonadImpl (_: Result<_,_>) =
|
||||
{ Bind = Result.bind; Return = Ok }
|
||||
```
|
||||
|
||||
ダミーの引数でコンテナ型を明示的に指定させるのは、 F# コンパイラがオーバーロードを自動で解決できるようにするため。
|
||||
|
||||
たとえば引数を `unit` などにしてしまうと、どのオーバーロードを呼べば目的のコンテナ型に対する実装が手に入るのかが判断できなくなってしまう。
|
||||
|
||||
このビルトイン実装は後ほど使う。
|
||||
|
||||
そして、`^Builtin` 型もしくはコンテナ型 `^Ma` から bind/return の実装を取り出すインライン関数 `getImpl` を定義する。
|
||||
|
||||
インライン関数では **Statically Resolved Type Parameters (SRTP)** を型パラメータに取ることができて、通常の型パラメータが `'T` と 表記されるのに対して SRTP は `^T` と表記される。
|
||||
|
||||
```fsharp
|
||||
let inline getImpl (builtin: ^Builtin)
|
||||
(dummy: MonadClass< ^a, ^Ma, ^Mb >)
|
||||
: MonadClass< ^a, ^Ma, ^Mb > =
|
||||
((^Builtin or ^Ma):
|
||||
(static member MonadImpl: ^Ma -> MonadClass< ^a, ^Ma, ^Mb >) (Unchecked.defaultof< ^Ma >)
|
||||
)
|
||||
```
|
||||
|
||||
SRTP は型が持っているメンバに対して制約をかけることができる。ここでは、メンバ `MonadImpl` を型 `^Builtin` もしくは `^Ma` が持っていることを要求している。
|
||||
|
||||
また SRTP はコンパイル時に消えてしまうので、`^Ma` と `^Mb` はここでは高階型ではないのだが、インライン展開後にはコンテナ型が具体化されて、結果的に高階型*だったことになる*。
|
||||
|
||||
ここでも `MonadBuiltin` と同様のテクニックで、ダミーの引数を使って入手する実装の型を指定している。
|
||||
|
||||
先ほど定義しておいたビルトイン実装と `getImpl` を組み合わせて、任意のコンテナ型に対する bind/return を定義する。
|
||||
|
||||
```fsharp
|
||||
let inline bind_ (f: ^a -> ^Mb) (x: ^Ma) : ^Mb =
|
||||
(getImpl MonadBuiltin
|
||||
(Unchecked.defaultof<MonadClass< ^a, ^Ma, ^Mb >>)
|
||||
).Bind f x
|
||||
|
||||
let inline return_ (x: ^a) : ^Ma =
|
||||
(getImpl MonadBuiltin
|
||||
(Unchecked.defaultof<MonadClass< ^a, ^Ma, _ >>)
|
||||
).Return x
|
||||
```
|
||||
|
||||
ここでもインライン関数を使って SRTP で制約をかけており、コンテナ型 `^Ma` は `MonadBuiltin` で bind/return をすでに実装してあるか、自分でメンバに実装を持っていなければならない。
|
||||
|
||||
最後に、モナ……コンピューテーション式を定義。
|
||||
|
||||
`do` は残念ながら予約語なので恐怖の the M-word で代用する。
|
||||
|
||||
```fsharp
|
||||
type MonadBuilder () =
|
||||
member inline __.Bind (x, f) = bind_ f x
|
||||
member inline __.Return x = return_ x
|
||||
member inline __.ReturnFrom mx = mx
|
||||
|
||||
let monad = MonadBuilder ()
|
||||
```
|
||||
|
||||
できた!
|
||||
|
||||
では、動かしてみよう。
|
||||
|
||||
```fsharp
|
||||
monad {
|
||||
let! a = Some 21
|
||||
let! b = Some 2
|
||||
return a * b
|
||||
} |> printfn "%A"
|
||||
|
||||
// Some 42
|
||||
|
||||
monad {
|
||||
let! a = Ok 42
|
||||
let! b = Error "err"
|
||||
return sprintf "%i, %i" a b
|
||||
} |> printfn "%A"
|
||||
|
||||
// Error "err"
|
||||
```
|
||||
|
||||
自作型を定義して、型クラス `MonadClass` のインスタンスにする。
|
||||
|
||||
```fsharp
|
||||
type YesNo<'a> = Yes of 'a | No with
|
||||
static member MonadImpl(_: YesNo<'a>) =
|
||||
{
|
||||
Bind = fun f -> function Yes x -> f x | No -> No
|
||||
Return = Yes
|
||||
}
|
||||
```
|
||||
|
||||
同じように使える。
|
||||
|
||||
```fsharp
|
||||
monad {
|
||||
let! a = Yes 21
|
||||
let! b = Yes 2
|
||||
return a = b
|
||||
} |> printfn "%A"
|
||||
|
||||
// Yes false
|
||||
```
|
||||
|
||||
なお、 orphan instances は type extension で外部モジュールの型に追加したメンバでは SRTP のメンバ制約を満たすことができないことによって(偶然)防がれている。
|
||||
|
||||
外部モジュールの型を型クラスのインスタンスにするには、型クラスの定義と同時にビルトイン実装するか、それ自身で実装を持っていなければならない。
|
||||
|
||||
どちらもできないときは Haskell の場合と同様に、ラッパ型を作って包むしかない。
|
||||
@@ -0,0 +1,256 @@
|
||||
---
|
||||
title: F# で型レベルプログラミング
|
||||
description: 今F# で依存型をシミュレートしようと頑張ってるのだけど、その途中でちょっと面白いことができたのでそこだけ切り出してまとめておく
|
||||
date: !!timestamp 2018-03-08T02:08:49+09:00
|
||||
category: Programming
|
||||
tags: ["F#", "SRTP"]
|
||||
---
|
||||
|
||||
今 [F# で依存型をシミュレートしようと頑張ってる](https://github.com/cannorin/FSharp.Dependent) のだけど、その途中でちょっと面白いことができたのでそこだけ切り出してまとめておく。
|
||||
|
||||
F# で型レベルプログラミングができたのだ。
|
||||
|
||||
(依存型シミュレートはそれなりに納得できる形になったら記事にするよ!)
|
||||
|
||||
いつものように inline functions と SRTP を悪用していくわけだけど、F# の型引数にはある望まざる性質があるので、型を値として受け渡しするラッパを用意しておく。
|
||||
|
||||
```fsharp
|
||||
[<Struct>]
|
||||
type Ty<'Type> =
|
||||
override __.ToString() = // 型の表示用
|
||||
typeof<'Type>.ToString()
|
||||
.Replace("FSI_0001+", "")
|
||||
.Replace("[", "<")
|
||||
.Replace("]", ">")
|
||||
.Replace("`1", "")
|
||||
.Replace("`2", "")
|
||||
|
||||
let inline ty< ^Type > : Ty< ^Type > = Ty()
|
||||
```
|
||||
|
||||
こいつは空っぽの struct なので、値レベルでは何もしない。ただ型を受け渡すだけの存在だ。
|
||||
|
||||
で、 F# で型の評価を進める、つまりある型から別の型を作る方法はあまり多くなくて、そのうち SRTP で制約として書けるものは「メンバを生やす」方法のみだ。
|
||||
|
||||
そこで、型 `A` に `Ty<A> -> Ty<B>` なるメンバ `eval` を生やすことにする。自明な例から行ってみよう。
|
||||
|
||||
```fsharp
|
||||
type True = True with
|
||||
static member inline eval (_: Ty<True>) = ty<True>
|
||||
|
||||
type False = False with
|
||||
static member inline eval (_: Ty<False>) = ty<False>
|
||||
```
|
||||
|
||||
また、 F# は型の評価を暗黙的に進めてくれないので、項の評価ですよと言いくるめて押し通す。
|
||||
|
||||
```fsharp
|
||||
let inline eval (x: Ty< ^A >) : Ty< ^B > =
|
||||
(^A: (static member inline eval: Ty< ^A > -> Ty< ^B >) x)
|
||||
```
|
||||
|
||||
inline functions はコンパイル時に展開されるし、 `eval` は型の変形しかしないので、実行時には何の処理も行われない。
|
||||
|
||||
で、`True` も `False` も正規形なので、 `eval`` しようがそのままだ。
|
||||
|
||||
```fsharp
|
||||
let true_ = eval ty<True>
|
||||
true_ |> printfn "%A" // True
|
||||
|
||||
let false_ = eval ty<False>
|
||||
false_ |> printfn "%A" // False
|
||||
```
|
||||
|
||||
また、[church encoding](https://en.wikipedia.org/wiki/Church_encoding) を知ってると話が早いのだけど、真偽値自体に car/cdr の機能を持たせておくととても便利である。どう便利かはすぐにわかる。
|
||||
|
||||
```fsharp
|
||||
type True with
|
||||
static member inline ifThenElse (_: Ty<True>, x, y) = x
|
||||
|
||||
type False with
|
||||
static member inline ifThenElse (_: Ty<False>, x, y) = y
|
||||
```
|
||||
|
||||
`True` が car、`False` が cdr の機能を持つ。
|
||||
|
||||
これを使うと、`Not` が書ける。
|
||||
|
||||
```fsharp
|
||||
type Not<'a> = Not of 'a with
|
||||
static member inline eval (_: Ty<Not< ^A >>) : Ty< ^B >
|
||||
when ^A: (static member eval: Ty< ^A > -> Ty< ^Bool >) = // (1)
|
||||
(^Bool: (static member ifThenElse: Ty< ^Bool > * _ * _ -> Ty< ^B >) ty< ^Bool >, ty<False>, ty<True>) // (2)
|
||||
```
|
||||
|
||||
**ここが今回の記事の要だ!!** `Not< ^A >` の `eval` において、
|
||||
|
||||
1. まず `Ty< ^A >` を `eval` してその結果 `Ty< ^Bool >` を取り出す。
|
||||
2. `^Bool` がメンバ `ifThenElse` を持っているものとして、それを `(ty<False>, ty<True>)` に適用する。
|
||||
|
||||
ここで、もし `^Bool` が `True` だったら `ifThenElse` は car であり、`ty<False>` が取り出される。`False` だったらその逆になって、どちらでもなかったらコンパイルエラーになる。
|
||||
|
||||
```fsharp
|
||||
let notTrue = eval ty<Not<True>>
|
||||
notTrue |> printfn "%A" // False
|
||||
|
||||
let notNotTrue = eval ty<Not<Not<True>>> // 先に中身を eval するので入れ子も OK
|
||||
notNotTrue |> printfn "%A" // True
|
||||
|
||||
// let error1 = eval ty<Not<bool>>
|
||||
// ^ error FS0001: The type 'bool' does not support the operator 'eval'
|
||||
|
||||
type BadType = BadType with
|
||||
static member eval (_: Ty<BadType>) = ty<BadType>
|
||||
|
||||
// let error2 = eval ty<Not<BadType>>
|
||||
// ^ error FS0001: The type 'BadType' does not support the operator 'ifThenElse'
|
||||
```
|
||||
|
||||
つまり `eval` 結果の型のメンバ `ifThenElse` の有無によって型の型(カインド)を判別している。(Haskell では同じことを`-XDataKinds` でやる)
|
||||
|
||||
これが理解できれば、`And` も `Or` も同じことをするだけだ。
|
||||
|
||||
```fsharp
|
||||
type And<'a, 'b> = And of 'a * 'b with
|
||||
static member inline eval (_: Ty<And< ^A, ^B >>) : Ty< ^C >
|
||||
when ^A: (static member eval: Ty< ^A > -> Ty< ^A' >)
|
||||
and ^B: (static member eval: Ty< ^B > -> Ty< ^B' >) =
|
||||
(^A': (static member ifThenElse: _*_*_ -> Ty< ^C >) ty< ^A' >,ty< ^B' >,ty<False>)
|
||||
|
||||
type Or<'a, 'b> = Or of 'a * 'b with
|
||||
static member inline eval (_: Ty<Or< ^A, ^B >>) : Ty< ^C >
|
||||
when ^A: (static member eval: Ty< ^A > -> Ty< ^A' >)
|
||||
and ^B: (static member eval: Ty< ^B > -> Ty< ^B' >) =
|
||||
(^A': (static member ifThenElse: _*_*_ -> Ty< ^C >) ty< ^A' >,ty<True>,ty< ^B' >)
|
||||
|
||||
let x = eval ty<And<Not<False>, Or<Not<True>, True>>>
|
||||
// not false && (not true || true)
|
||||
x |> printfn "%A" // True
|
||||
```
|
||||
|
||||
また、 if-then-else を型レベルに持ち上げることすらできる。
|
||||
|
||||
```fsharp
|
||||
ype IfThenElse<'_bool, 'a, 'b> = IfThenElse of '_bool * 'a * 'b with
|
||||
static member inline eval (_: Ty<IfThenElse< ^X, ^A, ^B>>) : Ty< ^EvalAorB >
|
||||
when ^X: (static member eval: Ty< ^X > -> Ty< ^Bool >) =
|
||||
(^Bool: (static member ifThenElse: _*_*_ -> Ty< ^EvalAorB >) ty< ^Bool >,eval ty< ^A >,eval ty< ^B >)
|
||||
// 制約に絡まないなら eval は値レベルでやってもよい
|
||||
|
||||
// 適当な型定数
|
||||
type A = A with static member eval _ = ty<A>
|
||||
type B = B with static member eval _ = ty<B>
|
||||
|
||||
type Code =
|
||||
IfThenElse<
|
||||
Or<
|
||||
Not<True>,
|
||||
IfThenElse<
|
||||
Or<
|
||||
Not<True>, False>,
|
||||
BadType, Not<False>>>, // それぞれの型の一致は見ない。どうせ静的に決まる
|
||||
A, B>
|
||||
|
||||
let result = eval ty<Code>
|
||||
result |> printfn "%A" // A
|
||||
```
|
||||
|
||||
いよいよプログラミングらしくなってきた。
|
||||
|
||||
自然数も同様で、こちらも church encoding 的に、型自体に加算の機能をもたせる。
|
||||
|
||||
```fsharp
|
||||
type Z = Z with
|
||||
static member eval (x: Ty<Z>) = x
|
||||
static member inline add (_: Ty<Z>, y) = eval y
|
||||
|
||||
type S<'n> = S of 'n with
|
||||
static member inline eval (_: Ty<S< ^N >>) : _
|
||||
when ^N: (static member eval: Ty< ^N > -> Ty< ^N' >) = ty<S< ^N' >>
|
||||
static member inline add (_: Ty<S< ^X >>, _: Ty< ^Y >) : _
|
||||
when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >)
|
||||
and ^X': (static member add: Ty< ^X' > * Ty< ^Y > -> Ty< ^Z >) =
|
||||
ty<S< ^Z >>
|
||||
|
||||
type Add<'x, 'y> = Add of 'x * 'y with
|
||||
static member inline eval (_: Ty<Add< ^X, ^Y>>) : _
|
||||
when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >) =
|
||||
(^X': (static member add: _*_ -> _) ty< ^X' >,ty< ^Y >)
|
||||
|
||||
type One = S<Z>
|
||||
type Three = Add<One, Add<One, One>>
|
||||
type Five = S<Add<Three, One>>
|
||||
|
||||
let five = eval ty<Five>
|
||||
five |> printfn "%A" // S<S<S<S<S<Z>>>>>
|
||||
```
|
||||
|
||||
減算はちょっと面倒だが、 `S< ^X > - S< ^Y >` と `^X - Z` だけを定義しておけば結果が負になるような減算は型エラー(メンバが見つからない)にすることができる。
|
||||
|
||||
```fsharp
|
||||
type Z with
|
||||
static member inline sub (x, _: Ty<Z>) = eval x
|
||||
|
||||
type S<'n> with
|
||||
static member inline sub (_: Ty<S< ^X> >, _: Ty<S< ^Y >>) : _
|
||||
when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >)
|
||||
and ^Y: (static member eval: Ty< ^Y > -> Ty< ^Y' >)
|
||||
and ^Y': (static member sub: Ty< ^X' > * Ty< ^Y' > -> Ty< ^Z >) =
|
||||
ty< ^Z >
|
||||
|
||||
type Sub<'x, 'y> = Sub of 'x * 'y with
|
||||
static member inline eval (_: Ty<Sub< ^X, ^Y>>) : _
|
||||
when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >)
|
||||
and ^Y: (static member eval: Ty< ^Y > -> Ty< ^Y' >) =
|
||||
(^Y': (static member sub: _*_ -> _) ty< ^X' >,ty< ^Y' >)
|
||||
|
||||
type Two = Sub<Three, One>
|
||||
type Six = Sub<Add<Five, Two>, One>
|
||||
|
||||
let six = eval ty<Six>
|
||||
six |> printfn "%A" // S<S<S<S<S<S<Z>>>>>>
|
||||
|
||||
// let error = eval ty<Sub<Two, Six>>
|
||||
// ^ Type constraint mismatch. The type 'Ty<Z>' is not compatible with type 'Ty<S<'a>>'
|
||||
```
|
||||
|
||||
大小比較も同様。
|
||||
|
||||
```fsharp
|
||||
type Z with
|
||||
static member inline gt (_: Ty<S<_>>, _: Ty<Z>) = ty<True>
|
||||
static member inline gt (_: Ty<Z>, _) = ty<False>
|
||||
|
||||
type S<'n> with
|
||||
static member inline gt (_: Ty<S< ^X >>, _: Ty<S< ^Y >>) : _
|
||||
when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >)
|
||||
and ^Y: (static member eval: Ty< ^Y > -> Ty< ^Y' >)
|
||||
and (^X' or ^Y'): (static member gt: Ty< ^X' > * Ty< ^Y' > -> Ty< ^Z >) =
|
||||
ty< ^Z >
|
||||
|
||||
type GT<'x, 'y> = GT of 'x * 'y with
|
||||
static member inline eval (_: Ty<GT< ^X, ^Y>>) : _
|
||||
when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >)
|
||||
and ^Y: (static member eval: Ty< ^Y > -> Ty< ^Y' >) =
|
||||
((^X' or ^Y'): (static member gt: _*_ -> _) ty< ^X' >,ty< ^Y' >)
|
||||
|
||||
type LT<'x, 'y> = GT of 'x * 'y with
|
||||
static member inline eval (_: Ty<LT< ^X, ^Y>>) : _
|
||||
when ^X: (static member eval: Ty< ^X > -> Ty< ^X' >)
|
||||
and ^Y: (static member eval: Ty< ^Y > -> Ty< ^Y' >) =
|
||||
((^X' or ^Y'): (static member gt: _*_ -> _) ty< ^Y' >,ty< ^X' >)
|
||||
|
||||
let y = eval ty<And<GT<Six, Three>, LT<Sub<Five, One>, Add<One, Three>>>>
|
||||
|
||||
y |> printfn "%A" // False
|
||||
```
|
||||
|
||||
等値判定は正規形の型に判定関数を定義して、`eval` したあとそれを呼び出せばよいが、煩雑な割に別に結果は面白くないので省略する。
|
||||
|
||||
さて、 F# では制約解決器で無限ループを起こさせることができて(制約で自分自身を要求することで起こせる)、
|
||||
|
||||
```
|
||||
error FS0465: Type inference problem too complicated (maximum iteration depth reached). Consider adding further type annotations.
|
||||
```
|
||||
|
||||
というエラーメッセージが出る。この事実は、 F# の型レベル言語が turing complete であることを示唆している。例えば型レベルで de Bruijn indexed な型無しラムダ計算をできるのではないだろうか?そのうち、やってみるかも……(もしやってみてできたら教えてね)
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: ブログエンジンを自作した
|
||||
description: 静的サイトジェネレータ flxble を自作して,ウェブサイトをそれに移行した
|
||||
date: !!timestamp 2019-01-16T10:41:45.6672030+09:00
|
||||
category: Programming
|
||||
tags: ["F#"]
|
||||
---
|
||||
|
||||
注: ウェブサイトを SvelteKit に移行したため,2025年現在は自作ブログエンジンを使っていません……
|
||||
|
||||
------
|
||||
|
||||
静的サイトジェネレータ [flxble](https://github.com/cannorin/flxble) を自作して,ウェブサイトをそれに移行した.
|
||||
|
||||
Markdown プロセッサは [Markdig](https://github.com/lunet-io/markdig) を使い,それ以外はテンプレートエンジンも含めて全て F# で自作した.
|
||||
|
||||
現在 1000 記事あるテスト用のブログを全部レンダリングしてタグや月別アーカイブ,RSS まで生成するのに合計 1.7 秒ほどしかかからないので,
|
||||
Hugo ほどではないが十分高速なブログエンジンの部類に入るだろう.
|
||||
|
||||
他の多くのテンプレートエンジンと違ってわたしの自作したテンプレートエンジンは内部で mutable state を使っていないので[^immutable],自動的にスレッドセーフになっている.
|
||||
|
||||
[^immutable]: F# の標準ライブラリの `List` や `Map` はすべて immutable なので,何も考えてなくても勝手にスレッドセーフになる.ここらへんは ML 系言語の面目躍如ではないだろうか.
|
||||
|
||||
そのお陰で安全に並列化できるのだが,並列化しなくても既存の end-user[^end-user] .NET テンプレートエンジンの中で[おそらく最速](https://github.com/cannorin/flxble/wiki/Flxble.Templating:-Benchmarks)になってしまった.
|
||||
|
||||
[^end-user]: 独自のスクリプト言語を採用しているなどの理由で任意コードの実行ができず,エンドユーザに使わせても安全なもの.Razor は無制限で C# を使えるのでこの枠には入らない.
|
||||
|
||||
せっかくなので Hugo にも勝ちたいが,プロファイラにかけたところ,実行時間のほどんどを Markdig の Markdown パーシング・レンダリングが占めていたので,
|
||||
自分で書いた部分をいくら弄ってもこれ以上速くなる望みがあまりないことがわかった.
|
||||
|
||||
さすがに Markdown 処理系を自作したくはない.
|
||||
@@ -1,299 +0,0 @@
|
||||
---
|
||||
title: エラーメッセージの読み方と対処,検索や質問の原則
|
||||
description: どういうエラーメッセージが出たときは何が原因で,どのように対処すれば解決するのか.その知識・経験の積み重ねこそがあなたを一人前のプログラマにするのだ
|
||||
date: !!timestamp 2019-01-23T00:00:00+09:00
|
||||
category: Programming
|
||||
tags: ["Misc"]
|
||||
---
|
||||
|
||||
> この記事は [以前 Qiita に投稿した記事](https://qiita.com/cannorin/items/eb062aae88bfe2ad6fe5) の転載です.
|
||||
|
||||
プログラミングをしている限り,エラーメッセージに遭遇するのは避けられないことだ.そこで,あなたは周りのできる人に「エラーが出ました」と言って "答え" を聞こうとするだろう.
|
||||
|
||||
でも,もし聞ける人が誰もいなかったら? もし,周りの誰にもわからないようなエラーにぶつかってしまったら?
|
||||
|
||||
あなたが一人前のプログラマになるためには,**自分で**エラーメッセージを読んで,解決できるようにならなければならない.
|
||||
|
||||
どういうエラーメッセージが出たときは何が原因で,どのように対処すれば解決するのか.**その知識・経験の積み重ねこそがあなたを一人前のプログラマにする**のだ.これは安直に "答え" だけを追い求めていてはいつまで経っても身に付かない.
|
||||
|
||||
## エラーメッセージの大原則
|
||||
|
||||
まず最初に頭に入れておくべきなのは,**エラーメッセージは意味不明な暗号ではない** ということだ.その実,エラーメッセージはかなり簡単な英語で書かれている.決まった用語や,決まった形の文を使いまわしている.それらを覚えれば,未知なるエラーメッセージに遭遇したとしても,なんとか解釈することができる.**エラーメッセージは読めるし,読むべきものだ.**
|
||||
|
||||
### 1.動詞を含む短い文を探す
|
||||
|
||||
大量のエラーメッセージを前にしたプログラミング初心者は,とかくその量に圧倒されてしまいがちだ.まずは一旦落ち着いて,**動詞を含む短い文** を探すとよい.
|
||||
|
||||
* 'i' undeclared (first use in this function)
|
||||
* parse error before '}'
|
||||
* undefined reference to 'prnitf'
|
||||
* unterminated string or character constant
|
||||
|
||||
このような短い文章がエラーメッセージの本質的な部分だ(大体は undefined などの動詞から始まる).近くにエラーが発生したファイル名や行番号が表示されていることが多い.
|
||||
|
||||
見て分かるとおりこれらは非常に単純な英語で,ほとんどの場合10語未満だ.使われる単語もほぼ決まっているので,読めるようになるべきだ.**もし読めない場合でも,Google 翻訳を使ってでも自力で読むようにしよう.** 実際に Google 翻訳にかけると,
|
||||
|
||||
* 'i'が宣言されていません(この関数で最初に使用されます)
|
||||
* '}'の前のパースエラー
|
||||
* 'prnitf'への未定義の参照
|
||||
* 終端されていない文字列または文字定数
|
||||
|
||||
となる.この時点で対処方法がはっきりわかるかもしれないし,そうでなくても,少なくともどういうことを言っているのかは理解できるはずだ.
|
||||
|
||||
### 2.ファイル名が書いてあるときは,そのファイルに原因がある
|
||||
|
||||
当たり前のようだが,エラーメッセージにファイル名が出てきているとき,**まさにそのファイルに問題がある**.行番号が表示されているならば,ほとんどの場合,まさにその行にエラーがある.
|
||||
|
||||
次にするべきなのは,その部分や周囲(大体は前)に間違っている部分がないかチェックすることだ.上で見つけた文と合わせれば,どう間違っててどうすれば解決するのかすぐにわかる場合が多い.
|
||||
|
||||
"at" や "before" といった位置を表す前置詞にも注目.関数の定義とその関数を呼び出す部分で何か食い違いがある時,"from" などの前置詞を使って呼び出し元の位置を表示していることもある.
|
||||
|
||||
### 3.長いエラーメッセージは上から順番に読む
|
||||
|
||||
もしエラーメッセージがとてつもなく大量に出てきたとしても慌てる必要はない.
|
||||
|
||||
大多数のコンパイラはソースコードを上から読む.すると**ソースコードの上の方で見つかったエラーほど先に報告する**から,エラーメッセージの上の方に表示されることになる.
|
||||
|
||||
上の方にあるコードというのはそれより下のたくさんの部分で使われている(可能性がある)から,エラーが影響する範囲が大きくなる.だから,**エラーは上から順番に対処したほうがよい**のだ.
|
||||
|
||||
しかも,上の方にあるエラーが原因で,それを使う他の部分でもエラーが起こっていることもある.そのようなエラーは根本的なエラーを直したら消えてしまうことが多い.その意味でも,上の方にあるエラーを先に直すべきだ.
|
||||
|
||||
## エラーの解決の大原則
|
||||
|
||||
エラーメッセージを読めたら,次は解決を試みる.エラーの原因を探るのにはいくつかのコツがある.自分でエラーを解決する経験を積み重ねて自分なりのコツを編み出すことで,プログラミングは上達していく.
|
||||
|
||||
### 1.何をすればエラーメッセージが出てくる・出なくなるのかを見極める
|
||||
|
||||
エラーがある箇所はなんとなく分かるけど,直し方の検討が付かない場合は,**該当の部分を取り除いてエラーが出なくなるかを確かめる.** それでエラーが出なくなれば詳しく調べる必要があるし,何か別のエラーが出始めた場合はそれを先に直す.もしかしたらそっちが原因かもしれない.
|
||||
|
||||
### 2.エラーが再現する小さなプログラムを作る
|
||||
|
||||
エラーが起きる箇所だけを取り出して,**そのエラーが出るような,できるだけ小さいプログラムを新しく作ってみる.** そしてそのプログラムがどのように動いているのかを確認して,何故エラーが出るのかを考えていく.
|
||||
|
||||
これは大きなプログラムを作っている場合は特に有効だ.一見遠回りのようだが,**エラーの原因となる部分に集中することができるから,結果的にはエラーの解決が早くなる.**
|
||||
|
||||
このような,同じエラーが出るできるだけ小さなプログラムのことを,[**最小再現コード (MRE)**](https://en.wikipedia.org/wiki/Minimal_reproducible_example) という.エラーを出すために必要な要素だけが詰まっているために詳しく調べやすいので,後述の Google 検索や質問サイトを使う時は,この MRE を使うとよい.
|
||||
|
||||
### 3.プログラムの動作の流れを追いかける
|
||||
|
||||
コンパイル時にエラーメッセージが出てくれるならばよいのだが,コンパイルは通ったのに実行してみるとエラーが出る,ということも多い.そもそもコンパイラがない言語ならばなおさらだ.
|
||||
|
||||
そういう場合は,プログラムが実際にどのように動いているのか,どのコードがどういう順番で実行されているのかを把握することで,エラーの原因を特定しやすくなる.
|
||||
|
||||
プログラムを動かしてみてエラーの原因(bug,バグ)を探る作業を **デバッグ(debug)** という.デバッグにはいくつかの手法がある.
|
||||
|
||||
* `printf` デバッグ
|
||||
* エラーの原因となっていそうな変数があるとき,`printf` などの画面に表示させる関数を使って,その変数の値を処理の途中途中で表示させる.
|
||||
* どこかのタイミングで変数の値が意図しないものに変わったならば,そこが原因だ.
|
||||
* どこに書き加えた `printf` なのかが分かるように,出力を工夫するとよい.
|
||||
|
||||
* デバッガを使う
|
||||
* 言語によっては,デバッガと呼ばれる,プログラムの実行を途中で止めたり,変数の値を覗いたりすることができるプログラムを使える.
|
||||
* C言語ならば GDB,Python ならば PDB.自分の使っている言語向けのデバッガが何かは自分で調べる.
|
||||
* Visual Studio などの統合開発環境(IDE)と呼ばれるエディタを使っているならば,標準でデバッガが搭載されていることが多い.
|
||||
* `printf` デバッグより取っ付きにくいかもしれないが,こちらのほうが便利だ.使って慣れておこう.
|
||||
|
||||
大きなプログラムをデバッグするのは大変だが,小さなプログラムならば多少デバッグしやすくなる.MRE を作ると,デバッグの役に立つ.
|
||||
|
||||
### 4.仮説・予想・実験・考察のサイクルを回す
|
||||
|
||||
愚直にデバッグしていてもエラーの原因は見つかるかもしれないが,何故エラーが出るのかを考えながらデバッグしたほうがよほど速く見つかる.そこで,
|
||||
|
||||
1.**エラーの原因の仮説を立てる**.
|
||||
2.もしその原因が正しかったと仮定して,**きちんと動く場合/エラーになる場合 を予想する.**
|
||||
3.その場合になるように実際に動かしてみて,**予想通り成功/失敗するか確かめる.**
|
||||
4.予想と違った場合,**改めて考え直して,違う仮説を立てる.**
|
||||
|
||||
というサイクルを回すことで,闇雲に探すよりも速く原因を特定することができる.例えば入力した数式を計算するプログラムを作っていて,計算結果が合わない場合は,
|
||||
|
||||
1.掛け算より足し算が先に計算されているかもしれない.
|
||||
2.もしそうなら,`1+2*3` は `9` になるはずだ.
|
||||
3.実際に `1+2*3` を入力して計算してみる.
|
||||
4.`7` だった.原因は他にある.
|
||||
|
||||
という具合だ.
|
||||
|
||||
なお,やらかしそうな間違いをあらかじめ予想しておいて,**実際にやらかした場合に気付けるように,簡単に実験を回せるようにしておくのが,テストを書くということ**である.
|
||||
|
||||
### 5.これらを組み合わせる
|
||||
|
||||
原則2~4はお互いを高め合う効果がある.
|
||||
|
||||
* MRE を作れれば,コードが短くなってデバッグがしやすくなるし,考慮するべき物事が少なくなるので,仮説を立てやすくなる.
|
||||
|
||||
* デバッグでプログラムの動作を把握すれば,仮説が立てやすくなるし,エラーと関係ない部分が分かって MRE を作りやすくなる.
|
||||
|
||||
* 的確な仮説を立てられるようになれば,MRE 作りやデバッグが手探り状態ではなくなる.
|
||||
* 仮説を複数立てて,それぞれに対応した MRE を書いてみて,どれが本当にエラーになるのか(もしくはどれもならないのか)を調べることができる.
|
||||
* そして,"当たり" の MRE をデバッグして直すことができる.
|
||||
|
||||
### 6.エラーは1つずつ解決する
|
||||
|
||||
**一気に全部のエラーを解決しようとは絶対にしないこと.** 複数のエラーを解決しようとすると MRE が作れなくなるし,考えることがあまりにも多くなって頭がパンクし,何も分からなくなる.
|
||||
|
||||
**困難は分割せよ.** 慌てる必要はない.大量のエラーメッセージも上から順番に一個一個解決していくのが一番早い.一見ひとつの大きな問題に見えることでも,実際は複数の小さな問題が同時に起こった結果だ.それを見極め,細かく切り分けて,一つ一つ対処する.
|
||||
|
||||
### 7.はじめからやり直せるようにしておく
|
||||
|
||||
ファイル入出力を行うプログラムや Makefile など,ファイルの読み書きが発生するものをデバッグする時には,実行前と後で状態が変わってしまうので,**実行する前の状態でディレクトリごとコピーしておいて,めちゃくちゃになっても最初からやり直せるようにしておく.**
|
||||
|
||||
デバッグしているうちに何がどうなっているのか,自分が今何をやっているのか,さっぱりわからなくなるのはよくあることだ.そこで一旦考えをリセットするために,**動いていた・分かっていた段階でバックアップを取っておこう.** もちろん,"エラーは1つずつ解決する" 原則を守っていれば,そういう事態は起こりにくくなる.
|
||||
|
||||
なお,この原則はバージョン管理システム(git など)の考え方に直接繋がる.早めに使えるようにしたほうがよいだろう.
|
||||
|
||||
## Google 検索の大原則
|
||||
|
||||
エラーを自力で解決できない時には,Google などの検索エンジンを使って調べることになる.
|
||||
|
||||
どんなに経験を積んだプログラマであっても,エラーメッセージが出たときに Google 検索に頼ることはある.単純に今まで見たことがなかったり,起こらないと思っていた箇所で発生していたり,そういう場合だ.
|
||||
|
||||
しかし経験を積んだプログラマは,当たり前だけど,Google 検索のやりかたにも経験を積んでいる.Google 検索は便利なツールだけれども,適切な検索キーワードを入れてやらないと,必要な情報にたどり着けないことが多い.彼らはそのうまいやり方を知っているのだ.
|
||||
|
||||
**どういうキーワードで検索すれば欲しい情報が見つかるのか,その感覚を身につける**ことがプログラミング上達への近道だ.
|
||||
|
||||
### 1."動詞を含む短い文" をそのまま Google 検索に入れる
|
||||
|
||||
前述の "エラーメッセージの大原則" で探すように述べた "動詞を含む短い文" をそのまま Google 検索に入れてみるのはかなり賢い選択だ.何故なら,それがまさにエラーの本質的な部分だからだ.
|
||||
|
||||
こういう文は短くて特徴的で目立つから,エラーについて解説しているサイトや,そのエラーについて質問しているページなどでも,その短い文をエラーの名前として使っているものがほとんどだ.
|
||||
|
||||
### 2.エラーコードで検索する
|
||||
|
||||
エラーを分類するコードやIDのようなものが表示されていたら,それを検索キーワードに使うのも有効だ.例えば
|
||||
|
||||
`error CS0246: The type or namespace name 'MyClass' could not be found.`
|
||||
|
||||
というエラーだったら,`error CS0246` で検索してみるのもよい.エラーコードは英語のプログラミング環境でも日本語のプログラミング環境でも同じものが使われるので,日本語の解説がある場合にそれが出てきやすくなる.
|
||||
|
||||
### 3.ファイル名は検索キーワードに入れない
|
||||
|
||||
ファイル名・ディレクトリ名はあなたが勝手に決めたものである場合がほとんどで,インターネット上の誰かが全く同じファイル名を使っている確率はゼロだ.というわけで,それらは検索キーワードに入れない.
|
||||
|
||||
一般に,**あなたが勝手に決めた名前はなるべく検索キーワードに入れないようにするべき**だ.先ほどの例で言えば,`MyClass` なんて名前を付けている人が他にいて,あなたと同じエラーに遭遇している確率はゼロに近いだろう.だから,`The type or namespace name 'MyClass' could not be found.` で検索するより,そういう部分を除いた `The type or namespace name could not be found.` で検索したり,エラーコード `CS0426` で検索したほうが良い結果が得られる.
|
||||
|
||||
### 4.情報を絞り込むキーワードを追加する
|
||||
|
||||
原因が違っていても同じエラーメッセージが出ることはそれなりにある.そのせいで,単純にエラーメッセージで調べても,違う状況について解説したページばかり出てきて,本当に欲しい情報が出てこないことがある.そういうときは,情報を絞り込むためにいくつかキーワードを追加する.
|
||||
|
||||
キーワードには,**今直面している状況の(エラーが出なかった時と比べた)特殊さを端的に表現するもの**を使う.例えば,ソースコードファイルが1つしかなかった時は出なかったエラーが,ファイルを増やした途端出てきたような場合は,「複数ファイル」などのキーワードを追加する.ファイルを読み書きするようなプログラムを書いていて,名前が英数字なファイルは開けるのに日本語が入っていたら開けない,という状況になったならば,「ファイル名 日本語」などのキーワードを追加する.
|
||||
|
||||
また,そのような**キーワードを選ぶ際には,本当にそれが原因なのかどうかをあらかじめ確かめておく**必要がある.もしそれが原因ではなかったならば,全く見当違いな情報が出てくることになるからだ.ソースコードファイルを増やしたらエラーが出てきたと思うなら,一旦ファイルを減らしてみてエラーが消えることを確認するべきだ.上述の **[MRE](#2エラーが再現する小さなプログラムを作る) を作っておけば,この作業は要らなくなる.**
|
||||
|
||||
### 5.英語で調べてみる
|
||||
|
||||
日本人は1億人ちょっとしかいないが,英語の話者は18億人もいる.当然,日本語で書かれた情報よりも,英語で書かれた情報のほうが何倍も多い.日本語で対処方法を解説したページが全く見つからないときでも,**英語でならば,ほとんどの場合,何かしらの情報が見つかる**.
|
||||
|
||||
プログラマをやっていると,どうしても英語の情報を読まなければならない状況は必ずやってくる.そういうときに,英語だから,と尻込みしていても何も解決しない.
|
||||
|
||||
エラーメッセージと同様に,**Google 翻訳を使ってでも自力で読む習慣を付けよう**.読める人に聞いているばかりでは,いつまで経っても自力で解決できるようにはならない.
|
||||
|
||||
### 6.サンプルコードを見つけても,何も考えずにコピペしないこと!!
|
||||
|
||||
それが**何をしていて,何故動くのかを自分で考えて**,それに基づいて自分のコードを書き直すこと.
|
||||
|
||||
何度も言っているように,他人から得た "答え" を書き写しているだけでは,いつまで経っても身に付かないし,それを自力で書けるようにはなれない.
|
||||
|
||||
## 誰かに質問するときの大原則
|
||||
|
||||
Google 検索で探しても欲しい情報が得られないことも時々ある.自力で解決できる見込みがどうやらなさそうな場合は,他人に質問するしかない.
|
||||
|
||||
しかし,本当に欲しい情報を得るためには,質問をする場所や,質問の仕方にもコツが必要だ.暇なプログラマはそれなりにいるが,どこの馬の骨とも知れないサイトに投稿された,わけのわからない質問に答えてくれるほど暇ではない.
|
||||
|
||||
### 1.プログラミング専用の質問サイトを使う
|
||||
|
||||
経験を積んだプログラマが生息するウェブサイトには偏りがある.彼らは大抵の場合,プログラミング専用の質問サイトしか見ていない.
|
||||
|
||||
プログラミング専用の質問サイトで,日本語話者向けのものをいくつか紹介する.
|
||||
|
||||
* [teratail【テラテイル】|ITエンジニア特化型Q&Aサイト](https://teratail.com/)
|
||||
* 初心者でも気軽に質問できるサイト.
|
||||
* 質問のテンプレートや初心者マーク機能などがあるので,初めて質問サイトを使う場合でも安心.
|
||||
* ここで質問の仕方に慣れるといいだろう.上達してきて回答者側になる時も,最初はここがオススメ.
|
||||
|
||||
* [スタック・オーバーフロー (Stack Overflow)](https://ja.stackoverflow.com/)
|
||||
* 英語圏で有名な老舗プログラミング質問サイトの日本語版.
|
||||
* 上級者が集まる傾向がある.ある程度プログラミングが上達してきて,それでもわからないことがある時に使うとよい.
|
||||
* かなり上達してきたプログラマは,本家の英語版しか使わなくなる.一般的にレベルが高く信頼できるサイトとされているので,読むだけでも慣れておこう.
|
||||
|
||||
* [Qiita(キータ)](https://qiita.com/)
|
||||
* 厳密には質問サイトではなく,プログラミングの知識や,問題に遭遇したときの解決方法を,記事にしたためて共有するためのサイト.
|
||||
* 質問自体を投稿するというよりは,エラーの原因が分かって解決した後に,エラーを出してしまった原因と対処方法をまとめて,1つのエラー対策記事として投稿するとよい.
|
||||
* 他の人から「このやり方のほうがよい」などのアドバイスがもらえることもある.
|
||||
* 技術系の記事を書く練習にもなる.Qiita の記事は玉石混交なので,あなたの書いた記事のクオリティが低くてもあまり恥ずかしがることはない.しかし,他人が読んで分かりやすい文章を書けるように努力・練習するべき.
|
||||
|
||||
一方,"Yahoo!知恵袋" や "教えて!goo" などの**一般質問サイトを使うのは本当にやめたほうがいい**.["教えて君"](http://www.redout.net/data/osietekun.html) と ["教えてあげる君"](https://www.weblio.jp/content/%E6%95%99%E3%81%88%E3%81%A6%E3%81%82%E3%81%92%E3%82%8B%E5%90%9B) が跳梁跋扈していて,まずまともな回答が来ない.
|
||||
|
||||
プログラミング専用の質問サイトを使う場合でも,["教えて君"](http://www.redout.net/data/osietekun.html) になってしまわないよう気を付けよう.**プログラマは "教えて君" が本当に嫌い**なので,専用サイトにしか生息していないのだ.
|
||||
|
||||
### 2.質問する前にまず検索する
|
||||
|
||||
プログラミング専用質問サイトには,毎日のようにプログラミングに関する質問が投稿されては解決されている.**あなたが初心者であればあるほど,他の初心者と同じ問題にぶつかっている可能性が高い.** つまり,同じような質問が既にされている可能性も高い.
|
||||
|
||||
Google 検索で出てこなくても,質問サイト内検索では出てくるかもしれない.**質問を投稿する前に,もう一度検索してみよう.** "Google 検索の大原則" はここでも活きる.
|
||||
|
||||
### 3.エラーメッセージは原文通りに貼り付けて,実行環境を詳しく書く
|
||||
|
||||
経験を積んだプログラマは,エラーメッセージの中からあなたが見逃した部分にしっかり気づいて,対処方法を教えてくれる.だから,**エラーメッセージの一部ではなく,全部を貼り付けるようにする.**
|
||||
|
||||
もしエラーメッセージに個人的なファイル名が含まれていてそのまま投稿するのが恥ずかしい場合は,そこだけ隠してしまっても構わない.例えば `C:\Users\Tarou\練習\main.c` なら,`C:\Users\XXX\XXX\main.c` のように隠す.ただし,肝心のソースコードのファイル名はなるべく隠さないこと.また,ファイル名がエラーの原因になっていそうな場合は,当然ながら隠さないほうがいい.
|
||||
|
||||
また,**どのような環境でそのエラーが出たのかを詳しく書くことが大事**だ.特定の環境でしか出ないエラーはよくあるが,他の人があなたと同じパソコンを使っている可能性はゼロだ.どういう環境でプログラミングしているのかは,書いてくれないとわからない.
|
||||
|
||||
* **使っている OS** (Windows,Mac,etc)
|
||||
* できれば OS のバージョンも書いたほうが良い.OS バージョンの調べ方が分からないなら,自分で検索する.
|
||||
* **コンパイラやインタプリタのバージョン**
|
||||
* 例えばC言語を使っていて GCC でコンパイルしているならば,`gcc -v` というコマンドを実行すればバージョンを表示させることができる.
|
||||
* Python なら `python --version`.使っているプログラミング言語のバージョンの出し方が分からないならば,まずそれを検索すること.
|
||||
* エラーメッセージと同じで,出力をそのまま全部載せる.
|
||||
* **プログラムをコンパイルしたり実行したりする時に打ったコマンド**
|
||||
* 他の人が質問を見て,同じようなプログラムを同じようなやり方で実行できる程度には,どういう風に実行したかを書いておくとよい.
|
||||
|
||||
エラーを解決するのに大事な要素に "再現性" がある.エラーに再現性があるとは,あなた以外の人も同じエラーを出せる,という意味だ.**どのようにすればそのエラーを再現できるのかを他の人に的確に伝える**ことができれば,解決方法を探すのを手伝ってくれる.
|
||||
|
||||
上述の **[MRE](#2エラーが再現する小さなプログラムを作る) を作っておくと,他の人も試しやすくなる.** MRE を他人に渡す際は,数ファイル程度の小さいものは [Gist](https://gist.github.com/) などに,何十ファイルもある大きいものは [GitHub](https://github.com/) などに置くとよい.特に Web 系は最小構成であっても各種 config でファイル数が多くなりがちなので,GitHub に置いたほうがよいだろう.
|
||||
|
||||
### 4.自己解決した場合はその解決方法を書き残す
|
||||
|
||||
ある程度プログラミングに上達してくると,質問はしたけれど誰かが答える前に自分で解決できた,ということが起こる.その時に,**「解決しました」とだけ書き残して去ることだけは絶対にやってはいけない.**
|
||||
|
||||
どのようにすれば解決するのかをきちんと書き残すことで,また同じエラーが出た時にそこを見ればよくなるし,**同じようなエラーが出た他の人の助けになる**.
|
||||
|
||||
### 5.複数の質問サイトに同じ質問を投稿しない
|
||||
|
||||
上と似たような理由で,複数の質問サイトに同じ質問を投稿するのもやめたほうがいい.
|
||||
|
||||
回答が付かなかったサイトにも解決方法を書き残す必要が出てきて面倒だし,かといってそれをしなければ,回答のない質問が検索に出てくることになって,**解決方法が載っているものにたどり着きにくくなる**.だから最初から1つの質問サイトで質問したほうがいい.
|
||||
|
||||
もしいくら待っても全然回答が付かなくて,他のサイトで聞いたほうが早そうな場合は,**あくまでも最後の手段として**,「あのサイトに投稿したこういう質問に答えてくれませんか?」という質問を(もちろん対象の質問の URL 付きで)投稿するとよい.
|
||||
|
||||
### 6.答えてくれた人に感謝の気持ちを伝える
|
||||
|
||||
当たり前だけど,答えてもらってお礼を言わないのは非常に印象が悪い.一言でいいので感謝のコメントを付けておこう.
|
||||
|
||||
### 7.サンプルコードをもらっても,何も考えずにコピペしないこと!!
|
||||
|
||||
もう説明は要らないはず.
|
||||
|
||||
## その他
|
||||
|
||||
* **日本語と英語の能力は,時としてプログラミング言語の能力よりも重要になる.**
|
||||
|
||||
* **プログラムは思ったとおりには動いてくれないかもしれないが,少なくとも書いたとおりには動いている.** 自分が今何を書いているのかを,できるだけ把握する.
|
||||
|
||||
* **人に教えることが一番勉強になる.** 自分が理解していることを,他人にわかるようにきちんと文章に起こすことで,あまりよく分かっていない部分に気づいたり,感覚でやっていたことが明文化されたりして,自分の理解が一層深まるからだ.
|
||||
* プログラミングが上達してきたら,今度は質問サイトで答える側に回ってみよう.
|
||||
* 教える側と教えられる側に,上下関係・偉い偉くないは(本質的には)ない.教える側のほうがより難しく厳しい勉強をしているだけのこと.お互いにきちんとリスペクトしあい,自分の立場に責任を持つこと.
|
||||
|
||||
* **"自力で考える・自力でやってみる" 姿勢が一番大事.**
|
||||
* 自力で何もしなくなったプログラマの成長は,残酷なほどすぐに止まる.
|
||||
|
||||
## 参考リンク
|
||||
|
||||
* [ハッカーになろう (How To Become A Hacker) - Eric S.Raymond](https://cruel.org/freeware/hacker.html)
|
||||
* 著名なハッカー(優秀なプログラマの意)の Eric S. Raymond が,ハッカーとしての心構えや行動指針を語る.
|
||||
* 優秀なプログラマは多かれ少なかれ,似たような思想を持って,似たようなことをしている.参考・目標にしよう.
|
||||
* [プログラマの心の健康 - 結城浩](http://www.hyuki.com/kokoro/)
|
||||
* 「数学ガール」シリーズで有名なプログラマ・作家の結城浩氏からのエール.
|
||||
* バグがどうしても取れなくてイライラする時,なにもかもうまく行かない時,自分はプログラミングに向いてないと思う時,そういう時に読みましょう.わたしも,結城氏みたいなすごい人も,みんなそういう経験をしています.
|
||||
* [教えてクン養成マニュアル](http://www.redout.net/data/osietekun.html)
|
||||
* Don't be that guy.(あんなやつになるなよ.)
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: Ionide-vim のメンテナになりました
|
||||
description: がんばります
|
||||
date: !!timestamp 2019-07-18T12:30:00.0000000+09:00
|
||||
category: Programming
|
||||
tags: ["F#", "Diary"]
|
||||
---
|
||||
|
||||
<blockquote class="twitter-tweet" data-lang="en-gb"><p lang="en" dir="ltr">We're really happy to announce new project in Ionide ecosystem - Ionide-vim. It's a vim <a href="https://twitter.com/hashtag/fsharp?src=hash&ref_src=twsrc%5Etfw">#fsharp</a> plugin using Language Server Protocol and FsAutoComplete created by <a href="https://twitter.com/cannorin_pub?ref_src=twsrc%5Etfw">@cannorin_pub</a>. New plugin is a continuation of vim-fsharp plugin created by <a href="https://twitter.com/kjnilsson?ref_src=twsrc%5Etfw">@kjnilsson</a></p>— Ionide (@IonideProject) <a href="https://twitter.com/IonideProject/status/1150506391553085440?ref_src=twsrc%5Etfw">14 July 2019</a></blockquote>
|
||||
|
||||
[ionide/Ionide-vim](https://github.com/ionide/Ionide-vim)
|
||||
|
||||
がんばります
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: 部室 Wi-Fi を支える技術
|
||||
description: 認証コンセントはクソ
|
||||
date: !!timestamp 2019-12-22T00:00:00.0000000+09:00
|
||||
category: Programming
|
||||
tags: ["Diary"]
|
||||
---
|
||||
|
||||
この記事は [Kobe University Advent Calendar 2019](https://adventar.org/calendars/4690) の22日目です.
|
||||
|
||||
## 部室のネットワーク環境の惨状
|
||||
|
||||
我々の部室には大学の無線LANが届かず,有線LANポートが1個しかない.そこで自分で無線LANアクセスポイントを建ててみんなで使えるようにする必要があるが,それがめちゃくちゃ大変という話です.
|
||||
|
||||
## IEEE 802.1X認証という壁
|
||||
|
||||
雑にルータぶっさせばいいじゃんと思われるかもしれないが,それでは全く動かない.そんなに単純な話ではないのだ.
|
||||
|
||||
弊学のインターネット環境は **IEEE 802.1X** 認証というものを採用している.
|
||||
これは認証されたクライアントしかネットワークに接続できなくするための技術で,以下のようになっている.
|
||||
|
||||
<a title="Arran Cudbard-Bell Arr2036 [GFDL (http://www.gnu.org/copyleft/fdl.html)], via Wikimedia Commons" href="https://commons.wikimedia.org/wiki/File:802.1X_wired_protocols.png"><img width="256" alt="802.1X wired protocols" src="https://upload.wikimedia.org/wikipedia/commons/1/1f/802.1X_wired_protocols.png"></a>
|
||||
|
||||
Authenticator というのは認証用の LAN スイッチで,部室には存在してないし,Authentication Server と通信するための secret key がわからないので自分で用意することもできない.
|
||||
つまりどういうことかというと,**まともな手段では同時に一人しかネットワークに接続できない** ということだ.ゴミ
|
||||
|
||||
## 回避策: OpenVPN
|
||||
|
||||
まず,認証を突破するために **誰か(のアカウント)に人柱になってもらう**.
|
||||
|
||||
次に,接続した PC をアクセスポイントかつ OpenVPN Client にする.
|
||||
|
||||
そしてインターネット上に専用の OpenVPN Server を用意し,そこに接続する.
|
||||
|
||||
すると,アクセスポイント化した PC に他の PC を接続することで,インターネットにアクセスできるようになる.
|
||||
|
||||
図で説明すると以下のようである:
|
||||
|
||||

|
||||
|
||||
Authenticator は MAC アドレス等で端末を識別しているようだが,VPN によってクライアント PC からのパケットは人柱 PC からのフレームの中に包まれるため,全て人柱 PC からの通信となって認証を突破することができる.
|
||||
|
||||
## ハマった(てる)ところ
|
||||
|
||||
### UDP 123 (NTP) で OpenVPN サーバを建てていると wget が通らない
|
||||
|
||||
学内ネットワークでは使えるポートがかなり制限されているので,空いてるポートで UDP が使える UDP 123 を使ってみたが,
|
||||
これは本来 NTP が使うポートだ.時刻同期ができなくなるくらい別にいいでしょと思ったら,ping も dig も通るのに
|
||||
wget が動かないという謎の現象が発生した.調べても分からなかったので TCP 123 にしたら動いた.ネットワークなんもわからん
|
||||
|
||||
### RasPi を経由させるとさくらのコントロールパネルにアクセスできない
|
||||
|
||||
意味不明 わからん,なんも……
|
||||
|
||||
|
||||
次回は Tatamo さんです.
|
||||
@@ -6,7 +6,9 @@ category: Programming
|
||||
tags: ["F#", "SRTP"]
|
||||
---
|
||||
|
||||
> この記事は [以前 Qiita に投稿した記事](https://qiita.com/cannorin/items/aaf6abb2a7c1bc7793d4) の転載です.
|
||||
この記事は [以前 Qiita に投稿した記事](https://qiita.com/cannorin/items/aaf6abb2a7c1bc7793d4) の転載です.
|
||||
|
||||
------
|
||||
|
||||
あんまり一般受けするようなネタが思いつかなかったので,SRTP をゴリゴリ書いてる人向けの記事になってしまいました.ごめんなさい……
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ tags: ["F#", "SRTP"]
|
||||
|
||||
この記事は [F# Advent Calendar 2020](https://qiita.com/advent-calendar/2020/fsharp) の12日目の記事です.投稿が遅れたのは [Cyberpunk 2077](https://www.cyberpunk.net/) のせいであって,私のせいではありません.
|
||||
|
||||
昔このブログで [F# で型クラスを実現する方法](https://7colou.red/blog/2018/02-14-fsharp-typeclasses/index.html) について書きましたが,FSharpPlus のメンテナになってからより実用的な手法を学んだので,改めて解説をしようというわけです.
|
||||
昔このブログで [F# で型クラスを実現する方法](/blog/2018-02-14-typeclass) について書きましたが,FSharpPlus のメンテナになってからより実用的な手法を学んだので,改めて解説をしようというわけです.
|
||||
|
||||
この記事では SRTP 自体の解説はしません.
|
||||
|
||||
@@ -127,7 +127,7 @@ let m3 = M 2 >>= fun x -> M (x + 1)
|
||||
```
|
||||
|
||||
ここで起こっているオーバーロード解決は,ユーザ定義型 or `BindImpl` の2択である.
|
||||
[F# で型クラス](https://7colou.red/blog/2018/02-14-fsharp-typeclasses/index.html) でやったことがわかっていれば
|
||||
[F# で型クラス](/blog/2018-02-14-typeclass) でやったことがわかっていれば
|
||||
特に理解に苦しむところはないと思う.
|
||||
|
||||
もしあなたが実装したいモナド(の関数)がこの類ならば,おめでとう.話はここでおしまいである.しかし実際は,これで
|
||||
|
||||
@@ -18,7 +18,7 @@ tags: ["F#", "SRTP"]
|
||||
|
||||
私です.ブログ記事には書き忘れたんですが去年の夏ぐらいから [FSharpPlus](https://github.com/fsprojects/FSharpPlus) のメンテナをやっています[^ionide].
|
||||
|
||||
[^ionide]: [Ionide のとき](https://7colou.red/blog/2019/ionide-vim.html) は記事にしたのにね
|
||||
[^ionide]: [Ionide のとき](/blog/2019-07-08-ionide-vim) は記事にしたのにね
|
||||
|
||||
[FSharpPlus](https://github.com/fsprojects/FSharpPlus) というのは,F# の標準ライブラリの拡張版として開発されているライブラリで,
|
||||
|
||||
@@ -53,7 +53,7 @@ tags: ["F#", "SRTP"]
|
||||
2. 長さや次元の情報を使って,境界外アクセスなどを静的に検出してコンパイルエラーを出す
|
||||
|
||||
の2つができなければならない.
|
||||
これらを自然に実現できる言語はだいたい[依存型や篩型](https://7colou.red/blog/2018/07-07-difference/index.html)などの強力な型システムを搭載している.当然 F# はそれらをサポートしていないので,F# の言語機能の範囲内でなんとかしなければならない.
|
||||
これらを自然に実現できる言語はだいたい[依存型や篩型](/blog/2018-07-07-difference)などの強力な型システムを搭載している.当然 F# はそれらをサポートしていないので,F# の言語機能の範囲内でなんとかしなければならない.
|
||||
|
||||
さらに FSharpPlus は標準ライブラリの拡張版として作られているものなので,F# ユーザにとって馴染みのあるような API を提供する必要がある.例え裏でどんな黒魔術をしていようとも,ユーザを混乱させないためにその事実をなるべく隠して,扱いやすい形にしなければならないわけだ.
|
||||
|
||||
@@ -296,7 +296,7 @@ let b = Singleton<S<S<S<Z>>>>
|
||||
// val b: S<S<S<Z>>> = S (S (S Z))
|
||||
```
|
||||
|
||||
例によって「戻り値の型によってオーバーロードが決まる」タイプの関数なので,[12日の記事](https://7colou.red/blog/2020/fsharp-advent.html)で説明したように,欲しい戻り値の型をダミー引数として渡させることでオーバーロードを解決している.
|
||||
例によって「戻り値の型によってオーバーロードが決まる」タイプの関数なので,[12日の記事](/blog/2020-12-17-typeclass)で説明したように,欲しい戻り値の型をダミー引数として渡させることでオーバーロードを解決している.
|
||||
|
||||
また `Singleton` もインライン展開されるので `Singleton<S<S<S<Z>>>>` は直接 `S (S (S Z))` としてコンパイルされる.
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: ウェブサイトを SvelteKit で書き直した
|
||||
description: cannorin のサイトがモダンになって帰ってきた!
|
||||
date: !!timestamp 2025-02-10T21:00:00.0000000+09:00
|
||||
category: Programming
|
||||
tags: ["Diary"]
|
||||
---
|
||||
|
||||
~修士課程をやっていたら忙しすぎて一年半が経っていた件~
|
||||
|
||||
というわけで,ウェブサイトを [SvelteKit](https://svelte.dev/) で再構築しました.
|
||||
自作のブログエンジンで作ってた前のサイトにも愛着はあったんだけど,古くなったので……
|
||||
|
||||
* さすがに多重人格すぎるので [ポートフォリオ](/) を3ページに分割することにした
|
||||
- 数学関連のページは後程整備します.PDF とか置けるといいね
|
||||
* Markdown は [mdsvex](https://mdsvex.pngwn.io/) で前処理して Svelte コンポーネントに変換するようにした
|
||||
- `+page.md` とか生やすと勝手にページになってくれてすごい
|
||||
* [View Transition API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API) でかっこよくアニメーションするようにした
|
||||
- なお,私のメインブラウザである Firefox では [未対応](https://caniuse.com/view-transitions) なため……
|
||||
|
||||
最近は SvelteKit をよく使うのですが,やはり **Web 標準を大きく外していない** というのが理由として大きいです.業務で Next.js を使ってるときの謎の消耗が本当にない.生 DOM,バンザ~イ!
|
||||
そういえば親友の [Tohlpeaks](https://tohlpeaks.party/) も SvelteKit でサイトを作ったそうです.ビッグウェーブ,来ているのでは?
|
||||
|
||||
とにかく,すっきりかわいい感じのウェブサイトになったのではないでしょうか! ページはちょくちょく増やしていくつもりです[^1].ではでは~.
|
||||
|
||||
[^1]: まずは Links からですかね.相互リンク募集中です!
|
||||
@@ -14,7 +14,9 @@ export async function load() {
|
||||
});
|
||||
}
|
||||
return {
|
||||
posts: posts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()),
|
||||
posts: posts.sort(
|
||||
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
|
||||
),
|
||||
seo: {
|
||||
title: "blog - cannorin.net",
|
||||
description: "cannorin's blog",
|
||||
|
||||
@@ -94,6 +94,8 @@ export default {
|
||||
"code::after": {
|
||||
content: '""',
|
||||
},
|
||||
"blockquote p:first-of-type::before": { content: "none" },
|
||||
"blockquote p:first-of-type::after": { content: "none" },
|
||||
code: {
|
||||
"font-weight": 400,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user