PureScriptの得新型実態

はじめに

PureScript Advent Calendar 2017 - Qiitaの8日目の記事です。 今日は「得新型実態」について書きます。

「得新型実態」ってなによ

derive newtype instanceのことを指しています。面白いと思って全部漢字にしてみたんですけど、次の日に見たら恥ずかくなって消すかもしれません。

そもそもnewtypeってなんやねん

PureScriptで代数的データ型を定義する時は、例えば、以下のように定義すると思います。

data HumanValue
  = Money Int
  | Personality
  | Face
  | Body

newtype上記とは別の意味をもった型宣言のやりかたです。ある型を別の型に見せかけたい、というような時に使います。

newtype Money = Money Int

これで、Money型とInt型は、コンパイル時に別の型として扱われます。

さて、このnewtype宣言には「値コンストラクタは1つで、それが持てる値も1つだけ」という制約があります。

ハンターハンターで覚えたんですが、制約があるってことはその代わりに良いことがあるみたいです。(全然関係ないけど、自分に特殊能力があることを期待して、水見式やったことある人、いると思います。)

以下のコードを見てください。

newtype FirstName = FirstName String

data LastName = LastName String

foo :: FirstName
foo = FirstName "foo"

bar :: LastName
bar = LastName "bar"

これをコンパイルするとこのようになります。

var LastName = (function () {
    function LastName(value0) {
        this.value0 = value0;
    };
    LastName.create = function (value0) {
        return new LastName(value0);
    };
    return LastName;
})();
var FirstName = function (x) {
    return x;
};
var foo = "foo";
var bar = new LastName("bar");

dataで宣言したものはゴニョゴニョと処理があるのに対して、newtypeで宣言した型は、ランタイム上ではnewtypeの中身の型として扱われています。

こう見ると、余計なことしない分、newtypeのほうがパフォーマンスはよさそうですね。これは制約によりもたらされた良いことの1つと言えるでしょう(実感として、この差にどのくらいありがたみを感じるかはアプリケーションによりますが)。

しかしながら、この「ランタイムとしては同じ型として扱われること」によって、もう1つ良いことが発生します。

ということで、次のセクションへ進みます。

derive newtype instance

さて、このnewtypeですが、こいつをなんらかの型クラスのインスタンスにしたい時ってありますよね?

例として、上述したFirstName型をEqインスタンスにしたいお気持ちが湧いてきたとしましょう。

instance eqFirstName :: Eq FirstName where
  eq (FirstName s1) (FirstName s2) = eq s1 s2

こんな感じでインスタンスにできますね。

ただ、ちょっと待って欲しい。FirstNameは単にStringをくるんだだけで、かつ、StringEqインスタンスです。

「なんで俺が温もりのある手作業でeqを定義しなければいけないんだ!なんかもっといい感じにできそうな雰囲気あるだろうが!」

このように思う人はいるはずです。

そんなときにはderive newtype instanceの出番です。

derive newtype instance eqFirstName :: Eq FirstName

あーら不思議、eqの定義をせずに済みました。これはランタイム上ではnewtypeの値コンストラクタの中身の型と同じ扱いであることによって実現できています。

さらには、こんな型があるとします。

newtype App eff a = App (StateT Int (ExceptT String (Eff eff)) a)

上のEqの例だけ見ると手作業が許せるかもしれませんが、この例を見ると、さすがにこの型を手作業で各種型クラスのインスタンスにするのはうんざりしそうですね。

derive newtype instance functorApp :: Functor (App eff)
derive newtype instance applyApp :: Apply (App eff)
derive newtype instance applicativeApp :: Applicative (App eff)
derive newtype instance bindApp :: Bind (App eff)
derive newtype instance monadApp :: Monad (App eff)

これで万事OKです。人間の幸福度が少し上がりましたね。

これが制約によりもたらされたもう一つの良いことです。

最後に

みなさんは6系統の念能力のどれに憧れましたか?

私は具現化系でした。