6
2

More than 1 year has passed since last update.

RowToListのすごく簡単な具体例をひとつ(+解説)

Last updated at Posted at 2019-10-15

はじめに

PureScriptの記事です。

RowToListのすごく簡単な例をひとつだけ、解説と共に書き残します。本記事の型クラス制約は長いだけですごく簡単なので、これまで諦めてきた人にも読んでみてほしいです🙂

また、別に必要のないところに無理やり入れた感じですが、Proxyも使ってます。

使う関数によってはどうしても必要な型クラス制約も現れてきますが、そうでないものは「ある型から別の型への変換作業」として見るといいと思います。

この例は何か難しいこととか便利なことをしているわけではなく、ただのRowToListを使った型定義の一例です🙂

具体例(解説コメント付き)

Main.purs
module Main where

import Type.RowList

import Prelude (Unit, unit)
import Type.Prelude (Proxy(..))
import Type.Row (RProxy(..))

type MyRow1 = ( a :: Int, b :: String, c :: Int )

type MyRow2 = ( h :: Number )

type MyRowList = Cons "f" String (Cons "g" Int Nil)

func
  :: forall t1 t2 t3 list1 list2 list3 list4 list5 list6 list7 list8 list9 list10 row  -- 変数宣言みたいなもの
   . RowToList MyRow1 list1               -- MyRow1(# Type)をlist1(RowList)に変換
  => RowListRemove "b" list1 list2        -- list1からbを削除したものをlist2とする
  => RowListRemove "a" list2 list3        -- list2からaを削除したものをlist3とする
  => RowListSet "d" Number list3 list4    -- list3にNumber型のdを追加したものをlist4とする
  => RowListSet "e" t1     list4 list5    -- list4にt1型のeを追加したものをlist5とする
  => RowListSet "f" t2     list5 list6    -- list5にt2型のfを追加したものをlist6とする
  => RowToList t3 list7                   -- t3型をlist7(RowList)に変換
  => RowListAppend list6 MyRowList list8  -- list6とMyRowListを結合したものをlist8とする
  => RowListAppend list8 list7     list9  -- list8とlist7を結合したものをlist9とする
  => RowListNub list9 list10              -- list9からラベルの重複(今回はラベルf)を削除したものをlist10とする
  => ListToRow list10 row                 -- list10(RowList)をrow(# Type)に変換
  => Proxy t1                             -- t1型は実引数のProxyを通じて渡されたInt型だとわかる
  -> Proxy t2                             -- t2型は実引数のProxyを通じて渡されたString型だとわかる
  -> RProxy t3                            -- t3型は実引数のRProxyを通じて渡されたMyRow2型だとわかる
  -> { | row }                            -- Record rowと同義
  -> Unit
func _ _ _ _ = unit

main :: Unit
main = func
         (Proxy :: Proxy Int)      -- t1にIntを渡す
         (Proxy :: Proxy String)   -- t2にStringを渡す
         (RProxy :: RProxy MyRow2) -- t3にMyRow2を渡す
         { c: 1
         , d: 1.0
         , e: 1
         , f: "meow"
         , g: 3
         , h: 2.0
         }

順番に見る+解説

型制約を少しずつ積み重ねて見てみます。

1. MyRow1をRowListに変換する

type MyRow1 = ( a :: Int, b :: String, c :: Int )
   . RowToList MyRow1 list1

  -- list1は今こんな状態:
  -- Cons "a" Int (Cons "b" String (Cons "c" Int Nil))

2. b、aを削除する

   . RowToList MyRow1 list1
  => RowListRemove "b" list1 list2
  => RowListRemove "a" list2 list3

  -- list3は今こんな状態:
  -- Cons "c" Int Nil

3. d、e、fを追加する

   . RowToList MyRow1 list1
  => RowListRemove "b" list1 list2
  => RowListRemove "a" list2 list3
  => RowListSet "d" Number list3 list4
  => RowListSet "e" t1     list4 list5
  => RowListSet "f" t2     list5 list6

  -- list6は今:
  -- Cons "c" Int (Cons "d" Number (Cons "e" t1 (Cons "f" t2 Nil)))

4. t3をRowListに変換する

   . RowToList MyRow1 list1
  => RowListRemove "b" list1 list2
  => RowListRemove "a" list2 list3
  => RowListSet "d" Number list3 list4
  => RowListSet "e" t1     list4 list5
  => RowListSet "f" t2     list5 list6
  => RowToList t3 list7

  -- list6:
  -- Cons "c" Int (Cons "d" Number (Cons "e" t1 (Cons "f" t2 Nil)))

5. MyRowListとlist6を連結する

type MyRowList = Cons "f" String (Cons "g" Int Nil)
   . RowToList MyRow1 list1
  => RowListRemove "b" list1 list2
  => RowListRemove "a" list2 list3
  => RowListSet "d" Number list3 list4
  => RowListSet "e" t1     list4 list5
  => RowListSet "f" t2     list5 list6
  => RowToList t3 list7
  => RowListAppend list6 MyRowList list8

  -- list8:
  -- Cons "c" Int (Cons "d" Number (Cons "e" t1 (Cons "f" t2 (Cons "f" String (Cons "g" Int Nil)))))

6. list7とlist8を連結する

list7は 4. で作ったt3のRowListです。

   . RowToList MyRow1 list1
  => RowListRemove "b" list1 list2
  => RowListRemove "a" list2 list3
  => RowListSet "d" Number list3 list4
  => RowListSet "e" t1     list4 list5
  => RowListSet "f" t2     list5 list6
  => RowToList t3 list7
  => RowListAppend list6 MyRowList list8
  => RowListAppend list8 list7     list9

  -- list9:
  -- Cons "c" Int (Cons "d" Number (Cons "e" t1 (Cons "f" t2 (Cons "f" String (Cons "g" Int t3)))))

7. 重複ラベルを削除する

ラベルfが消えます。

   . RowToList MyRow1 list1
  => RowListRemove "b" list1 list2
  => RowListRemove "a" list2 list3
  => RowListSet "d" Number list3 list4
  => RowListSet "e" t1     list4 list5
  => RowListSet "f" t2     list5 list6
  => RowToList t3 list7
  => RowListAppend list6 MyRowList list8
  => RowListAppend list8 list7     list9
  => RowListNub list9 list10

  -- list10:
  -- Cons "c" Int (Cons "d" Number (Cons "e" t1 (Cons "f" t2 (Cons "g" Int t3))))

8. RowListからRowに変換する

まだ未定の型たち(t1、t2、t3)が残っていますが、rowのラベルと形は決定します!

   . RowToList MyRow1 list1
  => RowListRemove "b" list1 list2
  => RowListRemove "a" list2 list3
  => RowListSet "d" Number list3 list4
  => RowListSet "e" t1     list4 list5
  => RowListSet "f" t2     list5 list6
  => RowToList t3 list7
  => RowListAppend list6 MyRowList list8
  => RowListAppend list8 list7     list9
  => RowListNub list9 list10
  => ListToRow list10 row

  -- row:
  -- ( c :: Int, d :: Number, e :: t1, f :: t2, g :: Int | t3 )

9. Proxyを使って型の受け取り口を作る

左側の矢印が変わります。最後の「=>」の前までが制約で、その次の「Proxy t1」からは普通の関数の型定義が始まります。

関数の呼び出し時にProxyに型注釈を付けることで、型を引数として受け渡すことができます。幽霊型と呼ばれるものです。

   . RowToList MyRow1 list1
  => RowListRemove "b" list1 list2
  => RowListRemove "a" list2 list3
  => RowListSet "d" Number list3 list4
  => RowListSet "e" t1     list4 list5
  => RowListSet "f" t2     list5 list6
  => RowToList t3 list7
  => RowListAppend list6 MyRowList list8
  => RowListAppend list8 list7     list9
  => RowListNub list9 list10
  => ListToRow list10 row
  => Proxy t1
  -> Proxy t2
  -> RProxy t3

10. 関数を定義する

やっと引数を書き終わって、関数本体の定義も終わります。

   . RowToList MyRow1 list1
  => RowListRemove "b" list1 list2
  => RowListRemove "a" list2 list3
  => RowListSet "d" Number list3 list4
  => RowListSet "e" t1     list4 list5
  => RowListSet "f" t2     list5 list6
  => RowToList t3 list7
  => RowListAppend list6 MyRowList list8
  => RowListAppend list8 list7     list9
  => RowListNub list9 list10
  => ListToRow list10 row
  => Proxy t1
  -> Proxy t2
  -> RProxy t3
  -> { | row }
  -> Unit
func _ _ _ _ = unit

Proxyは型の受け渡しのために使うだけなので、仮引数は無視して「_」にしています。今回やりたいのは型定義と型検査の確認だけなので、レコードの仮引数も無視してしまいます。最終的な型であるUnitに合わせるために、本体には「unit」とだけ書きます。(型さえ合っていれば)何を渡されてもunitを返すだけのくだらない関数の完成です。

ちなみに、制約を除いて書くとこんな関数です。

制約を除いた関数定義
func :: Proxy t1 -> Proxy t2 -> RProxy t3 -> { | row } -> Unit
func _ _ _ _ = unit

すごく簡単です!

11. 関数を呼び出す

ついに関数を呼び出すことができます。Proxyの型を通してt1、t2、t3を教えることができます。

type MyRow2 = ( h :: Number )
func
  :: forall t1 t2 t3 list1 list2 list3 list4 list5 list6 list7 list8 list9 list10 row
   . RowToList MyRow1 list1
  => RowListRemove "b" list1 list2
  => RowListRemove "a" list2 list3
  => RowListSet "d" Number list3 list4
  => RowListSet "e" t1     list4 list5
  => RowListSet "f" t2     list5 list6
  => RowToList t3 list7
  => RowListAppend list6 MyRowList list8
  => RowListAppend list8 list7     list9
  => RowListNub list9 list10
  => ListToRow list10 row
  => Proxy t1
  -> Proxy t2
  -> RProxy t3
  -> { | row }
  -> Unit
func _ _ _ _ = unit

main :: Unit
main = func
         (Proxy :: Proxy Int)      -- t1にIntを渡す
         (Proxy :: Proxy String)   -- t2にStringを渡す
         (RProxy :: RProxy MyRow2) -- t3にMyRow2を渡す
         { c: 1
         , d: 1.0
         , e: 1
         , f: "meow"
         , g: 3
         , h: 2.0
         }

Proxyも型制約も除くと、こんな関数を作ったのと同じことです。

func :: { c :: Int, d :: Number, e :: Int, f :: String, g :: Int, h :: Number } -> Unit
func _ = unit

この場合の呼び出しはもちろん、以下のようなものが許されますね!

main :: Unit
main = func
         { c: 1
         , d: 1.0
         , e: 1
         , f: "meow"
         , g: 3
         , h: 2.0
         }

まとめ

ここまで読んでくださってありがとうございます!

Twitterフォローしていただけると嬉しいです!
https://twitter.com/_matoruru

6
2
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
6
2