Prim モジュールを読む

Prim モジュール

PrimモジュールはPureScript組み込みの型などを提供しているモジュールということなので読みます。

PureScript Haskell
Function :: Type -> Type -> Type
(->) :: Type -> Type -> Type
const x y = x (->)
Array [1,2,3] List
Record { name: "wado", age: 0} -
Number 12.34 Double
Int 100 Int
String "Hello world" String
Char 'a' Char
Boolean true, false Bool
Partial - -
Type - Type
Symbol - Symbol

この表はあくまで雰囲気を掴むための対応表です。

jsを生成するために以下のコマンドを使いました。

λ yarn spago bundle-module --main <module_name> --to index.js  

Function

function type (関数型) ですね。

PureScriptでは(->)Functionのエイリアスとして定義されています。
例えば以下のようなconstconst'は同じ型を意味します。

module Function where  

const :: Int -> String -> Int    
const x y = x  

const' :: Function Int (Function String Int)    
const' x y = x  

実際にPSCiで動かしてみます。

> import Function   
> const 1 "a"  
1  

> const' 1 "a"  
1  

> :k (->)  
Type -> Type -> Type  
> :k Function  
Type -> Type -> Type  

また、実際に生成されるコードも見ておきましょう。こんな感じのjsが出力されます。

// Generated by purs bundle 0.13.4  
var PS = {};  
(function($PS) {  
  // Generated by purs version 0.13.4  
  "use strict";  
  $PS["Function"] = $PS["Function"] || {};  
  var exports = $PS["Function"];  
  var const$prime = function (x) {  
      return function (y) {  
          return x;  
      };  
  };  
  var $$const = function (x) {  
      return function (y) {  
          return x;  
      };  
  };  
  exports["const"] = $$const;  
  exports["const'"] = const$prime;  
})(PS);  
module.exports = PS["Function"];  

この出力を見ると、Function型はJavaScriptバックエンドにおいては関数として定義されるというドキュメントの記述とも一致しています。あと、デフォルトでカリー化されるんですね。

Array

配列型です。

> [1,2,3]  
[1,2,3]  

> :t [1,2,3]  
Array Int  

> :k Array  
Type -> Type  

Haskellでは[1,2,3]と書くとリストになるので、この辺はちょっと違いますね。

生成されるコード

module Array where  

arr :: Array Int    
arr = [1,2,3]   

// Generated by purs bundle 0.13.4  
var PS = {};  
(function($PS) {  
  // Generated by purs version 0.13.4  
  "use strict";  
  $PS["Array"] = $PS["Array"] || {};  
  var exports = $PS["Array"];  
  var arr = [ 1, 2, 3 ];  
  exports["arr"] = arr;  
})(PS);  
module.exports = PS["Array"];  

Record

これはHaskellには無い型ですね。

> :k Record  
# Type -> Type  

# Type っていう謎のカインドが出てきました。row kinds(列カインド) って言うカインドらしいです。ドキュメントによると、順序の無いカインドkの型のコレクションをラベルによって分類するために使われる。と書いてあります。

あと面白いのはHaskellでいうところのレコード構文{..}Record型のエイリアスとして定義されているという点です。

type Person = Record (name :: String, age :: Number)  
type Person = { name :: String, age :: Number }  

例えばこんな感じのコードで動作確認してみましょう。

module RecordEx where  

p1 :: Record (name :: String, age :: Int)  
p1 =  
  { name: "p1"  
  , age: 1  
  }  

p2 :: { name :: String, age :: Int }  
p2 =  
  { name: "p2"  
  , age: 2  
  }  

p3 :: { age :: Int, name :: String }  
p3 =  
  { name: "p2"  
  , age: 2  
  }  

レコード型は順序を気にしないのでp2p3は同じ型であり、また同じ値です。

> import RecordEx  
> import Data.Eq  

> :t p2  
{ age :: Int      
, name :: String  
}                 

> :t p3  
{ age :: Int      
, name :: String  
}      

> p1 == p2  
false  

> p1 == p3  
false  

> p2 == p3  
true  

生成されるコード

// Generated by purs bundle 0.13.4  
var PS = {};  
(function($PS) {  
  // Generated by purs version 0.13.4  
  "use strict";  
  $PS["RecordEx"] = $PS["RecordEx"] || {};  
  var exports = $PS["RecordEx"];  
  var p3 = {  
      name: "p2",  
      age: 2  
  };  
  var p2 = {  
      name: "p2",  
      age: 2  
  };  
  var p1 = {  
      name: "p1",  
      age: 1  
  };  
  exports["p1"] = p1;  
  exports["p2"] = p2;  
  exports["p3"] = p3;  
})(PS);  
module.exports = PS["RecordEx"];  

ラベルの重複

ドキュメントには重複ラベルもOKみたいに書いてあったので試してみます。

type Dup = { name :: String, age :: Int, name :: Boolean }  

この定義はnameが重複していますが、実際にコンパイル可能です。ただし、Dup型の値を普通に作ろうとしても作れないようです。(まだよくわかっていない)

例えば素朴に値を作ろうとすると

badDup :: Dup  
badDup =  
  { name: "dup"  
  , age: 0  
  , name: unit  
  }  

こんな感じで怒られます。

Error found:  
in module Example  
at src/Example.purs:27:3 - 30:4 (line 27, column 3 - line 30, column 4)  

  Label name appears more than once in a row type.  

while checking that expression { name: "dup"  
                               , age: 0       
                               , name: unit   
                               }              
  has type { age :: Int      
           , name :: String  
           , name :: Unit    
           }                 
in value declaration dup  

Dup型にnameラベルが2つあることに対するエラーですね。重複したラベルのレコード値を作ることはできないようです。

なのでnameを1つに減らしてみます。

badDup :: Dup  
badDup =  
  { age: 0  
  , name: unit  
  }  

これも以下のようなエラーになります。

Error found:  
in module Example  
at src/Example.purs:27:3 - 29:4 (line 27, column 3 - line 29, column 4)  

  Type of expression lacks required label name.  

while checking that expression { name: "dup"  
                               , age: 0       
                               }              
  has type { age :: Int      
           , name :: String  
           , name :: Unit    
           }                 
in value declaration dup  

ラベルが足りないというエラーですね。

とりあえず重複ラベルのレコードの値を作ってみたいのでpurescript-recordパッケージの力を借りることにします。

λ yarn spago install record  

コードはこんな感じです。

module Nub where  

import Record  

type Dup    = { name :: String, age :: Int, name :: Number }  
type Nubbed = { name :: String, age :: Int }  

nubbed :: Nubbed  
nubbed = nub dup  

dup :: Dup  
dup = union { name: "dup1", age:0 } { name: 0.5 }  

実際に repl で確かめてみます。

> dup  
{ age: 0, name: "dup1", name: dup1 }  

> nubbed  
{ age: 0, name: "dup1" }  

> :t dup  
{ age :: Int      
, name :: String  
, name :: Number  
}                 

> :t nubbed   
{ age :: Int      
, name :: String  
}  

後から出現するラベルが無視されるようになっているんですね。

Number

倍精度浮動小数点数 (IEEE754)

> :t 12.3  
Number  

> 12.3  
12.3  

生成されるコード

module Number where  

d :: Number  
d = 12.3  

// Generated by purs bundle 0.13.4  
var PS = {};  
(function($PS) {  
  // Generated by purs version 0.13.4  
  "use strict";  
  $PS["Number"] = $PS["Number"] || {};  
  var exports = $PS["Number"];  
  var d = 12.3;  
  exports["d"] = d;  
})(PS);  
module.exports = PS["Number"];  

Int

符号付きの32bit整数

> :t 10  
Int  

> 10  
10  

生成されるコード

module Int where  

n :: Int  
n = 10  

// Generated by purs bundle 0.13.4  
var PS = {};  
(function($PS) {  
  // Generated by purs version 0.13.4  
  "use strict";  
  $PS["Int"] = $PS["Int"] || {};  
  var exports = $PS["Int"];  
  var n = 10;  
  exports["n"] = n;  
})(PS);  
module.exports = PS["Int"];  

String

文字列型 (sequences of UTF-16 code units)

module String where  

str1 :: String  
str1 = "Hello World"  

str2 :: String  
str2 = """  
Hello  
World  
"""  

複数行文字列リテラルはHaskellにも欲しい・・・。

> str1  
"Hello World"  

> str2  
"\nHello\nWorld\n"  

生成されるコード

// Generated by purs bundle 0.13.4  
var PS = {};  
(function($PS) {  
  // Generated by purs version 0.13.4  
  "use strict";  
  $PS["String"] = $PS["String"] || {};  
  var exports = $PS["String"];  
  var str2 = "\x0aHello\x0aWorld\x0a";  
  var str1 = "Hello World";  
  exports["str1"] = str1;  
  exports["str2"] = str2;  
})(PS);  
module.exports = PS["String"];  

Char

文字型

> :t 'a'  
Char  

> 'a'  
'a'  

生成されるコード

module Char where  

c :: Char  
c = 'a'  

// Generated by purs bundle 0.13.4  
var PS = {};  
(function($PS) {  
  // Generated by purs version 0.13.4  
  "use strict";  
  $PS["Char"] = $PS["Char"] || {};  
  var exports = $PS["Char"];  
  var c = "a";  
  exports["c"] = c;  
})(PS);  
module.exports = PS["Char"];  

Boolean

true, false リテラルが用意されてる。

> :t true  
Boolean  

> :t false  
Boolean  

> true  
true  

> false  
false  

生成されるコード

module Boolean where  

t :: Boolean  
t = true  

f :: Boolean  
f = false  

// Generated by purs bundle 0.13.4  
var PS = {};  
(function($PS) {  
  // Generated by purs version 0.13.4  
  "use strict";  
  $PS["Boolean"] = $PS["Boolean"] || {};  
  var exports = $PS["Boolean"];  
  var t = true;  
  var f = false;  
  exports["t"] = t;  
  exports["f"] = f;  
})(PS);  
module.exports = PS["Boolean"];  

Partial 型クラス

関数が部分関数であることを表すクラス制約。

例えばこんな感じのコードはコンパイルに失敗する。

module PartialEx where  

f :: Boolean -> Int  
f false = 0  

Error found:  
in module PartialEx  
at src/PartialEx.purs:3:1 - 3:20 (line 3, column 1 - line 3, column 20)  

  A case expression could not be determined to cover all inputs.  
  The following additional cases are required to cover all inputs:  

    true  

  Alternatively, add a Partial constraint to the type of the enclosing value.  

while applying a function $__unused  
  of type Partial => t0 -> t0  
  to argument case $0 of    
                false -> 0  
while checking that expression $__unused (case $0 of    
                                            false -> 0  
                                         )              
  has type Int  
in value declaration f  

where t0 is an unknown type  

ここで、明示的にPartialをつけることでコンパイルできるようになる。

module PartialEx where  

f :: Partial => Boolean -> Int  
f false = 0  

なかなか良いですね。

生成されるコード

// Generated by purs bundle 0.13.4  
var PS = {};  
(function($PS) {  
  // Generated by purs version 0.13.4  
  "use strict";  
  $PS["PartialEx"] = $PS["PartialEx"] || {};  
  var exports = $PS["PartialEx"];  
  var f = function (dictPartial) {  
      return function (v) {  
          if (!v) {  
              return 0;  
          };  
          throw new Error("Failed pattern match at PartialEx (line 3, column 1 - line 3, column 31): " + [ v.constructor.name ]);  
      };  
  };  
  exports["f"] = f;  
})(PS);  
module.exports = PS["PartialEx"];  

カインド

TypeカインドとSymbolカインドが定義されている。

> :k Boolean  
Type  

> :k "aaa"  
Symbol  

感想

  • レコード周りについてはHaskellとはかなり違うので少し勉強する必要がありそう。特に row-polymorphism とか。
  • だいたいHaskellと同じなので雰囲気で読めるところはかなり多い
  • 型エラーが複雑すぎてヤバイ

次は Prelude を読む予定。

参考リソース