LoginSignup
13
3

More than 5 years have passed since last update.

Neverについて

Last updated at Posted at 2018-02-20

この記事は、わりとなんか質問があったので、それを軽くまとめます。

elmのコードを書いていると、Html.programなんていう関数があって、その型は

program : { init : (model, Cmd msg), 
            update : msg -> model -> (model, Cmd msg), 
            subscriptions : model -> Sub msg, 
            view : model -> Html msg } -> 
          Program Never model msg 

となっておりまして、最後のNeverとは一体なんぞや?みたいな質問が多いので、それを軽くまとめようと思います。

Neverとは?

Neverとは一言で言ってしまえば、値が無い型のことです。そんなことを言うと、Maybe aのNothingとかとごっちゃになるという人がいるかもしれませんが、NothingはあくまでMaybe aの値であって、NeverはNeverという型に対応する値がないということです。

elm packageのBasic につぎのように書いてます。

type Never

A value that can never happen! For context:

The boolean type Bool has two values: True and False
The unit type () has one value: ()
The never type Never has no values!

つまりですね、値がないってことはたとえば、Boolだと、2つの値、TrueとFalseがあって、Unit Typeは()という一つの値がありまして、Neverは値はありません!!!!!

ということですね。

さてさてNeverの型はどのように作られているのでしょうか? ということで、Neverの定義を見てみましょう。Basicの実装をとってくると

type Never = JustOneMore Never

ほむほむ。では、JustOneMoreという、コンストラクタがあるんで、Neverの型がつくれるんじゃ?とか思いますが、

tryToCreateNever : Never 
tryToCreateNever = 
 JustOneMore (JustOneMore (JustOneMore ... ))

となり、Neverが再帰的定義になっており、ずーっと型が再帰して値が定義できません。また、Never自体は、opaque typeなので、そもそもこんな定義じゃなくても、neverの値を作る手段は我々にはありません。

Neverのどこが良いのよさ?

とここまで書くと、Neverのについて有効性が気になりますよね。だって、Neverの型の値がないんだもの。どうつかうのよさ?ってなりますよね?

というかそもそも、値がつくれないんだから、どうやってコードに書くのさ?って疑問があるかもしれません。Neverはそもそも、Never単体で記述することはありません。型引数として用います。

たとえば、Maybe a型を考えてみましょう。絶対にNothingを返す、関数を書いてみます。

definitelyNothing : a -> Maybe Int 
definitelyNothing _ = Nothing

さて、この関数は必ずNothingを返します。その返り値の型をMaybe Intとしました。しかし、Maybe Intとする理由はとくにはありません。ただ、Nothingを返すためだけの目的として、Intをつかっただけです。

このときにNeverをつかいます。

definitelyNothing : a -> Maybe Never
definitelyNothing _ = Nothing

さて、上記の2つのコードはどう違うのでしょうか? 違いは、たとえばJustを許容するかどうかの違いにあります。つまりMaybe Intだと、Just 10などを許容してしまうかもしれませんが、Maybe Neverだと、Justの値は取れません。つまり、それだけ、Never型によって関数が制限されています。

もうひとつ例を上げましょう、つぎにResult型を考えてください。


type Result error value = Ok value | Err error

Result型は、成功(Ok)や失敗(Err)を表わします。それが考えられるものを考えます。しかしながら、時には、絶対に成功するけど、Resultで表わしたいときがあります。この場合、Result error valueのerrorの型に値なんて考えられません。このときにNeverを使えます。たとえば、aを取って、絶対にOkを返したいのであれば、次のようにします。

definitelyOk : a -> Result Never a 
definitelyOk a = Ok a

また、たとえば次のような関数があったとしましょう。

exampleFunc : (x -> b) -> (a -> b) -> (Result x a) -> b

とりあえず、実装は考えてませんが、(x -> b)と(a -> b)、Result x aを取り、bを返す関数です。もし、ここで、Resultの型が絶対にOkだけを返すと考えるとすると,

exampleFunc : (a -> b) -> (Result Never a) -> b

と定義できるかもしれません。ここで、気づいて欲しいのは,xという型引数がなくなり、関数としてシンプルになったのではないでしょうか?

最後に冒頭に,紹介したHtml programですが、まずprogramWithFlagsの関数があり、

programWithFlags : { init : flags -> (model, Cmd msg), 
                     update : msg -> model -> (model, Cmd msg), 
                     subscriptions : model -> Sub msg, 
                     view : model -> Html msg } -> 
                   Program flags model msg

このflagsの型引数がなくなったバージョンがHtml programです。

program : { init : (model, Cmd msg), 
            update : msg -> model -> (model, Cmd msg), 
            subscriptions : model -> Sub msg, 
            view : model -> Html msg } -> 
          Program Never model msg 

上記を見比べていると、Programの型にはflagという型引数があるのですが、programの関数の戻り値の型はflagを持たないProgramのであることを表わしており、programWithFlagと比較すると、flagを持つ持たないとで、引数のinitの型が異なることがわかると思います。つまり、flagを持つか持たないかを、Neverを用いることによって型で表現して、関数の型を厳格にしているんですね。

flagに関して詳しくは、https://guide.elm-lang.org/interop/javascript.html を参照してください。

まとめ

最後に簡単にまとめましょう。

  • Neverは、値の無い型である。
  • Neverを型引数に使うことによって、型が厳格になったり、シンプルになったりする。

以上です。

13
3
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
13
3