feature image

2021年8月27日 | ブログ記事

Purescriptを紹介します

こちらは夏のブログリレー・20日目の記事です


注・PureScript歴は1年も行ってないので誤りがあるかもしれません。


こんにちは、そして夜間ならばこんばんは、ゆきくらげです。traPのブログはあまり書いたことがないので、比較的温かい目で見守ってくださるとありがたいです。

さて、早速本題です。世の中には様々なプログラミング言語があります。その中でもJavaScriptは最も身近な言語のうち一つといっても過言ではないでしょう。そしてこのJavaScript、かなり老舗の言語で長い間私達のwebアプリを支え続けています。しかし、それ故言語仕様が古かったりするのです。

したがって、ナウでヤングな私達の中ではJavaScriptを直接書くのが嫌だと言う人が少なくありません。そこで黙っていないのがすごい人たち、Javascriptを別の構文で書こうという言語を多く開発してきました。その総称がAltJS。そしてその中でも純粋関数型静的型付けAltJS、PureScriptに最近ハマっています。なので今日はそれがどのような言語なのかを紹介したいと思います。

基本文法

PureScriptのコードを示します。

import Prelude

a :: Int
a = 4

prod2 :: Int -> Int
prod2 x = x * 2

plus2 :: Int -> Int
plus2 x = x + 2

main :: Effect Unit
main = do
  log "test"
  log $ "test2"
  log $ show $ plus2 $ prod2 a

実行結果

test
test2
10

ここで一つ、PureScriptの構文はHaskellという言語とほとんど同じです。HaskellはAltJSではないのですが、Haskellの文法を持ったAltJSがあったらおもしろくね?という流れで作られたのがPureScriptだからです。(多分)(PureScriptのコンパイラはHaskellで書かれています!)なので困ったらHaskellで調べるといろいろ出て来たり来なかったり……また、今回紹介する特徴はそっくりそのままHaskellのものなので、実質Haskell紹介という側面もあります。閑話休題。

一行目から説明していきます


import Prelude

Preludeというモジュールをインポートしています。モジュールとは便利な関数や値がいっぱい詰まったものという認識で大丈夫です。後述するlog関数やshow関数などはPreludeモジュールに入れられています。インポートすることでそれらの関数や値が使えるようになります。


a :: Int

型宣言です。aの型がIntであることを宣言しています。


a = 4

aに値4を代入しています。これはわかりやすいですね


prod2 :: Int -> Int

こちらも型宣言です。->という記号がついています。これは関数型を構築する記号で、この場合Int型の値をとってInt型の値を返す関数の型を表しています。String -> IntならString型の値をとってInt型の値を返す関数です。もちろん多変数関数も表せます。String -> Int -> CharならString型、Int型の値をとってChar型の値を返す関数です。

prod2 x = x * 2

関数の中身を定義しています。ここでは、受け取った値を二倍にして返す関数です。PureScriptでの関数定義は

関数名 変数 = 式

のように関数名と変数の間はスペースで区切られています。
もちろん多変数関数も表せます。

関数名 変数1 変数2 = 式

のような感じです。


plus2 :: Int -> Int
plus2 x = x + 2

上と同じように、与えられたInt型の値に2を足したInt型の値を返す関数を定義しています。


main :: Effect Unit

Effect Unitについては解説すると長いので飛ばします。mainはプログラムの開始時に呼ばれる場所です。(いわゆるエントリポイント)


main = do
  log "test"
  log $ "test2"
  log $ show $ plus2 $ prod2 a

do構文を使っています。こちらも解説すると長いので、今回は飛ばします。今はdoを書いた次の行からインデントして、縦に実行したいプログラムを並べて書くと上から順に実行される、と思っててくれて良いです。(do文の中だけ手続き型のようになる)

中身を詳しく見ていきます。


log "test" 

実行結果

test

PureScriptでの関数の呼び出しは

関数名 値

です。logString型の値を受けとってそれをコンソールに出力する関数で、Preludeモジュールに入っています。


log $ "test2"

実行結果

test2

こちらは関数と引数の間にドルマーク$が入っています。$は関数適用の演算子で、右の値を左の関数に適用します。上のようなプログラムではないほうがすっきりしますが、下のようなプログラムでは便利に使えます。


log $ show $ plus2 $ prod2 a

showについては後述します。これを$を使わずに書くと

log (show (plus2 (prod2 a)))

となります。カッコ()内は数学と同様、優先的に評価されます。ですが、このままでは括弧が多く、非常に見にくいです。

log show plus2 prod2 a

と書くとどうなるでしょうか。これはlogという4変数関数に値show, plus2, prod2, aを代入しろという意味になりますが、そもそもlogは4変数関数ではありませんので意味が分からないプログラムになります。

log $ show $ plus2 $ prod2 a

と書くことで($が右結合であるという性質によって)まずprod2 aが評価され、これは4 * 2 = 8になります。
次にplus2 8が評価され、これは8 + 2 = 10になります。
次にshow 10です。logの型を思い出します。logString型を取るのですが、10はInt型です。ゆえに、log 10とは書けません。ここでshowが登場します。showPreludeモジュールに入っていて、色々な値をString型に変換する関数です。(色々な値、にも制限がありますが、解説すると長くなります。)とりあえず今はInt型をString型に変換する関数として機能しています。これで晴れてlog "10"となり、10が出力されます。


こんな感じです。
続いて、PureScriptの特徴について述べていきます。

参照透過性

参照透過性とは、値が変化しないという特徴です。例えば

a :: Int
a = 2

と書いたとしましょう。これで、(スコープの関係で例外はあれど)金輪際aは2を表すようになります。どういうことかというと、参照透過性を持つ言語には再代入という操作がそもそもないのです。

a :: Int
a = 4
a = 6

と書いてみます。コンパイルが通りません。こんな構文はないからです。一度変数を定義したら、その値が変わることはありません。これはJavaScriptにおいてconstが常に付いている状態に似ている気もします。さらに、この参照透過性のおかげでPureScriptには(基本的に)上下がありません

a :: Int
a = 4

b :: Int
b = a + 2

これではもちろんbは6となりますが

b :: Int
b = a + 2

a :: Int
a = 4

とも書けます。どこで呼び出しても同じなのですから、天地がいらないのです。

参照透過性は、言い換えればどんな操作をしても値が変わることがない、ということです。これは非常にうれしい性質です。

例えば自分がxという値を持っていたとしましょう。xをある関数fに適用しました。さて、xは適用前のxと同じ値を持っているでしょうか。

この答えは、参照透過性を持たない言語ではわからないのです。fがxの値を変えるかもしれません。さらに、もしかしたら全く関係のない変数の値を変えるかもしれません。どうなっているかを確かめるにはfの中身を調べるか、コメントを信用するかしかありません。

関数型

PureScriptは関数型言語です。ここで関数型言語とは、すごく曖昧ですが関数が参照透過性を持っていて、関数を第一級オブジェクトとして扱えて、さらに関数を組み合わせて殆どすべてを表現していることとしたいです。(「関数型言語」には色々な定義があり、ときに論争を生んでいる(ほんまか?)ので、あくまでこの記事ではこう定義するという話です)

変数の参照透過性については解説しました。関数の参照透過性は変数と同じように、どこで関数を呼び出しても同じ関数が得られる、つまり同じ関数に同じ値を適用すれば、常に同じ値が得られる性質のことです。変数を0変数関数として見ることで自然に話が繋がると思います。

第一級オブジェクトとは、ここでは関数の引数として与えることができるものとします。例えばInt型の値2は関数に適用することができるので第一級オブジェクトです。関数型言語では、関数も関数の引数にすることができます。どういうことか示しましょう。

import Prelude

prod2 :: Int -> Int
prod2 x = x * 2

execute2 :: (Int -> Int) -> Int -> Int
execute2 f x = f $ f x

main :: Effect Unit
main = do
  log $ show $ execute2 prod2 3

実行結果

12

execute2は(Int -> Int型の値f、すなわちIntをとってIntを返す関数f)と(Int型の値x)をとって、Int型の値を返す関数です。中身はf $ f xすなわち、xfに二回適用しています。
このように関数を関数の引数にすることができます。execute2 prod2 3prod23を二回適用するので、(3 * 2) * 2 = 12となります。

次に、関数を組み合わせてすべてを表現するですが、これまでやってきたことが関数の定義と適用の連続だったことを思い出してください。ずばり、それのことです。さらに+*なども本体は関数だったりします。+*はそれぞれaddmulという関数を演算子で呼んでいるにすぎません。例えばaddなら通常は

add 値1 値2

と関数適用するのですが、値1addの左側に持っていったほうが便利なことがあります。そこでaddの演算子+を定義すると

値1 + 値2

のように、一番左側の引数を演算子の左側に持っていく記法に変わります。こうすることで、

add (add 1 2) 3

(1 + 2) + 3

と書けてスッキリします。実際には同じプログラムです。

演算子の定義は次のようにします。

infix 優先度 関数名 as 演算子
infixr 優先度 関数名 as 演算子
infixl 優先度 関数名 as 演算子

infixrは右結合の演算子、infixlは左結合の演算子、indixはどちらでもない演算子(したがって連続で使用することができない)を定義できます。優先度は別々の演算子が使われた時どちらを優先するかを決定する値で、優先度が大きい方から適用されます。

ちなみに$

apply f x = f x
infixr 0 apply as $

addの演算子+

infixl 6 add as +

で定義されています。$の優先度が0であるおかげで

f $ 2 + 3

とかくと
(f $ 2) + 3ではなくf $ (2 + 3)で評価されます。

さらにdo構文がありました。実はあれも一種の関数の糖衣構文だったりします。すげぇや。

静的型付け

PureScriptは静的型付け言語です、すなわち、実行される前に、すべての値の型が決まっているということです。この利点として、まず非常に強力な型推論が働くことが挙げられます。型推論の理論は数学の一大分野などになっていてここでは語れないですし僕もそんなに詳しくないのですが、とにかく型を書くときにIDE側からこんな型ですよね?と推測されたり、そもそも型を書かないでいい場面もたくさんあります。便利。

最後にデカい利点があります。実行時エラーを減らせるということです。静的型付けや参照透過性のおかげで、実行時エラーが少なくなるようにできます。実際にはFFIと呼ばれるJavaScriptとの相互通信を行う仕組みにおいて、実行時エラーが起きる可能性がありますが、逆にそこ以外で起こることはほぼ無い(ほぼというより多分一切無い)はずです。実行時エラーの撲滅はTypeScriptなども目指しているところで、AltJSを使う最も大きいモチベーションの一つだと言えるでしょう。

余白

レコード
代数的データ構造
部分適用
型クラス
モナド
FFI

ここら辺も大事です。(が、FFIについては僕は何も分からずになっていて、辛い)

レコード構文についてはHaskellと大きく異なっていて、個人的にはPureScriptのものの方が好みです。

最後に

この記事はなにもPureScriptを使えと言ってるものではありません。あくまでPureScriptという言語がどんな感じなのかを個人的に紹介したものです。これを読んで面白いと思ってくれれば幸いという話でした。

いかが

でしたか?

明日のブログリレーの担当はUzakiさんとmeraさんです。お楽しみに!

yukikurage icon
この記事を書いた人
yukikurage

色々しますよ

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

ERC20トークンを用いた宝探しゲーム(真)の提案【アドベントカレンダー2018 10日目】 feature image
2018年11月3日
ERC20トークンを用いた宝探しゲーム(真)の提案【アドベントカレンダー2018 10日目】
Azon icon Azon
2021年8月12日
CPCTFを支えたWebshell
mazrean icon mazrean
2021年5月19日
CPCTF2021を実現させたスコアサーバー
xxpoxx icon xxpoxx
2019年4月22日
アセンブリを読んでみよう【新歓ブログリレー2019 45日目】
eiya icon eiya
2018年12月12日
多重スリーブの世界と,各種進捗報告。
Silviase icon Silviase
2017年11月17日
そばやのワク☆ワク流体シミュレーション~MPS編~
sobaya007 icon sobaya007
記事一覧 タグ一覧 Google アナリティクスについて