👋

[PureScript] Visible type applicationで不要な型アノテーションとおさらばする

2023/08/11に公開

少し前にリリースされた PureScript v0.15.10 からVisible type applicationが実装されたようです(Haskellには言語拡張として既に存在する)。

使ってみて、個人的には嬉しいことがあったので、紹介してみようと思います。
https://github.com/purescript/purescript/releases/tag/v0.15.10

Visible type application って何?

Visible type applicationとは、多相関数の型変数を特定の型にインスタンス化する方法です。
(この場合のapplicationとはWebアプリとかスマホアプリとかのapplicationではなく、文脈的に「適用」の意味で用いられていると思われます)

上記の説明ではよくわからないと思うので、リリースノートの例を交えながら説明していきます。

リリースノートでは、次のような多相関数idを例に説明しています。

id :: forall @a. a -> a
id a = a

型変数aに着目してください。頭に@がついています。
この@avisible type variableと呼ばれるもので、このvisible type variableを特定の型にインスタンス化できるのです。

こちらが使用例です。

idInt :: Int -> Int
idInt = id @Int

example :: Int
example = id @Int 0

@Intのように型が明示(可視化)されています。

この例の場合、型推論できるのでVisible type applicationを使わなくてもコンパイルは通りますね。
これだけだと嬉しさが伝わりづらいと思いますので、後でVisible type applicationができると嬉しい例を紹介します。

ちなみにclassdata@を使わずとも自動的にvisible type variableになっています。
なので既存のライブラリのclassdataVisible type applicationに対応するのを待つ、ということはしなくてよいです。

また、この変数の数は複数個いけます。

複数個いけるよ
example = Left @Int @String 0

最後に、visible type variableはワイルドカード(_)でスキップできます。

example = Left @_ @_ 0

e1 :: Either Int String
e1 = example

e2 :: Either Int Int
e2 = example

Visible type applicationの嬉しさ

Visible type applicationの嬉しさは型アノテーションを置き換えられるところにあります。
どういうことかというと、型推論が効かないケースで今までa :: Foo Intのように型アノテーションを書いていたところを、a @Intと書けるわけです。
個人的には簡潔でこちらの方が好きです。

Visible type application元ネタの論文でも、「型アノテーションは回りくどい、型を指定できる方がはるかに直接的」というようなことが書かれています。同意です。

嬉しかった具体例

私はpmockというモックライブラリを開発しています。

このライブラリが提供しているモックはverify系の関数を使うことで、特定の引数で呼び出されたことを検証することができます。

その検証において、任意の値を期待値とするanyという関数を使うことができるですが、これまでは次のように型アノテーションを書く必要がありました。

モック関数が一度も呼ばれていないことを検証する例
import Prelude

import Test.PMock (Param, any, mock, verifyCount, (:>))
import Test.Spec (Spec, describe, it)

spec :: Spec Unit
spec = do
  describe "Example Spec" do
    it "any match example" do
      let
        m = mock $ "Title" :> 2023 :> false
        
      verifyCount m 0 $ (any :: Param String) :> (any :: Param Int)

これがVisible type applicationのおかげで、このように書けるようになったわけです。

verifyCount m 0 $ any @String :> any @Int

シンプル!嬉しい!

その他にも、次のような例があります。

別の例
import Prelude

import Effect.Aff (Aff)
import Test.PMock (fun, mock, verify, (:>))
import Test.Spec (Spec, describe, it)
import Test.Spec.Assertions (shouldEqual)

spec :: Spec Unit
spec = do
  describe "Example Spec" do
    it "Return Monad." do
      let
        m = mock $ "Article Id" :> (pure { title: "Article Title" } :: Aff { title :: String })

      result <- fun m "Article Id"

      result `shouldEqual` {title: "Article Title"}
      
      verify m "Article Id"

この例では文字列を受け取ってpure { title :: String }を返すモック関数を定義していますが、pureが返す型を推論できないため、型アノテーションをつけています。

Visible type applicationを使うとこれを次のように書けます!

mock $ "Article Id" :> pure @Aff { title: "Article Title" }

やったぜ!


いかがでしたか?
型推論が効かない場合や何らかの理由で型を明示したい場合などに、やむなく型アノテーションを書いていたところをVisible type applicationを使うことで簡潔に書けるようになったと思います。
(好き嫌いは個人の感覚に依るところが大きいと思いますが)

わかってしまえば使うのは簡単なので、頭の片隅にでも入れておいて損はないと思います。

では。また。

Discussion