構文は空白に敏感です。一般的な経験則としては、複数行に分けられた宣言の2行目以降は1行目よりも深い位置にあるべきです。
これは正しいです:
foo = bar +
baz
これは正しくありません:
foo = bar +
baz
1行コメントは--
で始まります:
-- ここはコメントです
複数行コメントは{-
と-}
で囲みます:
{-
コメント
ここもコメント
-}
パイプ文字|
で始まるコメントはドキュメントと見なされ、ドキュメント生成ツールpsc-docs
の出力とPursuitで見えるようになります。例をお見せします:
-- | `bool`は`if`文のような`Boolean`データ型のケース分析を行います。
bool :: forall a. Boolean -> a -> a -> a
bool true x _ = x
bool false _ x = x
Haskellとは異なり、ドキュメントに含めるべき行は全てパイプ文字から始まらなければなりません。したがって以下のようなことが可能です:
-- | `Ord`インスタンスから成る配列をソートします。
-- |
-- | この実装は`O(n^2)`時間で終了します。
-- | nは配列の長さです。
-- TODO: これを最適化しますか?
sort :: forall a. (Ord a) => Array a -> Array a
sort xs = [...]
モジュールのトップレベルの値は、名前の後に等号とそれに関連づける値を書くことで宣言されます:
one = 1
等号の左側にパターンの羅列を書くことで、トップレベルに関数を宣言することもできます。
add x y = x + y
ここで使えるパターンの種類についてさらに詳しく知りたいなら、パターンマッチの章を見てください。
パターンマッチを使う関数は異なるパターンに対応するために複数回定義されることがあります:
isEmpty [] = true
isEmpty _ = false
これは、関数が異なる数や引数の型によって勝手にオーバーロードされることを意味しているのではありません。
ガードはこのような定義にも使用できます:
isEmptyAlt xs | length xs == 0 = true
isEmptyAlt _ = false
トップレベルの宣言は一般的に型シグネチャと共に定義されます。
multiply :: Number -> Number -> Number
multiply x y = x * y
型シグネチャは一般的にはトップレベル宣言の際に必須ではありませんが、良い作法です。詳しくは型の章を見てください。
関数適用は関数とその引数を並べることで表します:
add 10 20
PureScriptの関数はカリー化されているので、部分適用に特別な構文は必要ありません:
add10 = add 10
実は、add 10 20
は(add 10) 20
として解析されます。
数値リテラルは整数(Int
型)または浮動小数点数(Number
型)になります。浮動小数点数は小数点によって識別されます。
16進表記の整数の前には0x
を書く必要があります:
16 :: Int
0xF0 :: Int
16.0 :: Number
文字列リテラルはダブルクォーテーションによって囲まれています。複数行にするためには、改行をバックスラッシュによって囲む必要があります。
"Hello World"
"Hello \
\World"
このように書かれているときには、改行は文字列から省略されます。
出力に改行が必要なら文字列中に\n
を含めることができますが、その代わりにダブルクォートを3つ使うことで、エスケープされた記号の特殊な解析を防ぐことができます:
jsIsHello :: String
jsIsHello = """
function isHello(greeting) {
return greeting === "Hello";
}
"""
この文字列宣言の方法は、正規表現文字列を書くときに特に便利です。
regex ".+@.+\\..+" noFlags
regex """.+@.+\..+""" noFlags
上記の正規表現は非常にシンプルなメールアドレス妥当性検証器です。2つの書き方は同等ですが、トリプルクォートを使う方が、書く時も保守する時も簡単です。これは複雑な正規表現を書くときに更に有用です。
真理値リテラルはtrue
とfalse
です。
関数値(ラムダ式と呼ばれることもあります)はバックスラッシュのあとに引数リストを書くことで表現できます:
\a b -> a + b
これは以下のJavaScriptに対応します:
function (a) {
return function (b) {
return a + b;
}
}
配列リテラルはJavaScriptと同様に角括弧で囲まれています:
[]
[1, 2, 3]
レコードリテラルはJavaScriptと同様に波括弧で囲まれています:
{}
{ foo: "Foo", bar: 1 }
ワイルドカードを含むレコードリテラル表記は、レコードを作成する関数を生成します:
{ foo: _, bar: _ }
これは以下と同等です:
\foo bar -> { foo: foo, bar: bar }
レコードのプロパティにアクセスするためには、JavaScriptと同様に、ドットの後にプロパティ名を書きます:
rec.propertyName
部分適用のアクセサもあります。アンダースコアの後にプロパティ名を書きます:
_.propertyName
これは以下と同等です:
\rec -> rec.propertyName
これはプロパティがいくつ並んでも可能です:
_.nested.property.name
レコードのプロパティは以下の構文を使って更新できます:
rec { key1 = value1, ..., keyN = valueN, nestedKey { subKey = value, ... } }
キーの全てまたは一部を一度に更新することもでき、レコード内のレコードも更新されます。
例えば、以下の関数は引数のプロパティfoo
の値をインクリメントします:
\rec -> rec { foo = rec.foo + 1 }
ネストされたレコードの更新は以下のようにします:
r = { val: -1
, level1: { val: -1
, level2: { val: -1 }
}
}
r' = r { level1 { val = 1 } }
ワイルドカードを更新に使用して、部分適用の更新を生成することも可能です:
rec { foo = _ }
これは以下と同等です:
\foo -> rec { foo = foo }
アンダースコアは更新器のオブジェクトの位置に書くことも可能です:
_ { foo = 1 }
これは以下と同等です:
\rec -> rec { foo = 1 }
PureScriptの演算子は単なる2変数関数です。そして、言語に組み込まれた演算子はなく、大半はPrelude
などのライブラリで定義されているため、このリファレンスの範囲外です。
演算子は既に存在する関数の別名として定義することができます。その関数は2変数関数(a -> b -> c
など)である必要があります。例えば:
data List a = Nil | Cons a (List a)
append :: forall a. List a -> List a -> List a
append xs Nil = xs
append Nil ys = ys
append (Cons x xs) ys = Cons x (append xs ys)
infixr 5 append as <>
この関数は以下のように使用できます:
oneToThree = Cons 1 (Cons 2 (Cons 3 Nil))
fourToSix = Cons 4 (Cons 5 (Cons 6 Nil))
oneToSix = oneToThree <> fourToSix
演算子の別名の宣言は4つのパートから成ります:
- 結合規則:
infixl
、infixr
またはinfix
。 - 優先順位: 0から9の整数。上記の例では5。
- 関数: 上記の例では
append
。 - 演算子(関数の別名): 上記の例では
<>
。
この宣言によって、演算子を括弧で囲む方法を決定します。
infixl
は、括弧で囲まれる適用の繰り返しが左から始まることを意味します。例えば、Preludeの#
は左結合で、このような式は:
products # filter isInStock # groupBy productCategory # length
以下のように括弧で囲まれます:
((products # filter isInStock) # groupBy productCategory) # length
同様に、infixr
は左結合を意味し、括弧で囲まれる適用の繰り返しが右から始まることを意味します。例えば、Preludeの$
は右結合なので、このような式は:
length $ groupBy productCategory $ filter isInStock $ products
以下のように括弧で囲まれます:
length $ (groupBy productCategory $ (filter isInStock $ products))
infix
は非結合を表します。非結合の演算子を繰り返し使うことは許されていません。例えば、 Preludeの==
は非結合です。これは以下のような式が:
true == true == true
非結合性エラーに含まれる以下のエラーを出力するということです:
Cannot parse an expression that uses multiple instances of the non-associative operator Data.Eq.(==).
Use parentheses to resolve this ambiguity.
(非結合演算子のインスタンスであるData.Eq.(==)を複数使用した式を解析できません。
この曖昧さを解決するために丸括弧を使用してください。)
infix
による非結合はx `f` (y `f` z)
と(x `f` y) `f` z)
が必ずしも同じではなく、結合性の代数的規則を満たさない演算子f
に最も適しています。
優先順位は演算子が括弧で囲まれる順番を決定します。優先順位の高い演算子から括弧に囲まれます。例えば、 Preludeから*
と+
を考えると、*
の優先順位が7である一方、+
の優先順位は6です。したがって、このように書くと:
2 * 3 + 4
以下のように括弧で囲まれます:
(2 * 3) + 4
異なる結合性を持つ演算子は、それらが同じ優先順位を持っていない限り同時に現れることがあります。この制限は、コンパイラがそのような式に対する括弧の付け方に関してそれほど賢い選択ができないために存在しています。例えば、Preludeの==
と<$>
の演算子はそれぞれinfix 4
とinfixl 4
の固定式を持ちます。これは以下のような式が与えられた時:
f <$> x == f <$> y
コンパイラは以下のように括弧を付けるか:
(f <$> x) == (f <$> y)
または以下のように付けるか:
f <$> (x == f) <$> y
わからないということです。したがって、MixedAssociativityErrorが返されます:
Cannot parse an expression that uses operators of the same precedence but mixed associativity:
Data.Functor.(<$>) is infixl
Data.Eq.(==) is infix
Use parentheses to resolve this ambiguity.
(優先順位が同等かつ結合性の混在する式を解析することはできません:
Data.Functor.(<$>)は左結合です
Data.Eq.(==)は非結合です
この曖昧さを解決するために丸括弧を使用してください。)
演算子は丸括弧で囲むことにより普通の値として使用することができます:
and = (&&)
演算子は片方のオペランドとしての_
と共に丸括弧で囲むことで、部分適用が可能です:
half = (_ / 2)
double = (2 * _)
関数はバッククォートで囲むことにより中置演算子として使うことができます:
foo x y = x * y + y
test = 10 `foo` 20
演算子セクションの演算子としても使うことが可能です:
fooBy2 = (_ `foo` 2)
case
とof
のキーワードは、値のコンストラクタに基づいたロジックを作成するために値を分解するために使用します。ケース側に,
で区切って記述することで、複数の値にマッチすることができます。
f :: Maybe Boolean -> Either Boolean Boolean -> String
f a b = case a, b of
Just true, Right true -> "Both true"
Just true, Left _ -> "Just is true"
Nothing, Right true -> "Right is true"
_, _ -> "Both are falsと`of`のキーワードはe"
f (Just true) (Right true)
トップレベル宣言のように、case
式ではガードを使用できます。
f :: Either Int Unit -> String
f x = case x of
Left x | x == 0 -> "Left zero"
| x < 0 -> "Left negative"
| otherwise -> "Left positive"
Right _ -> "Right"
マッチさせる式の代わりにアンダースコアを使用することで、束縛を避けることができます。この時、アンダースコアは匿名引数です。
case _ of
0 -> "None"
1 -> "One"
_ -> "Some"
これは以下と同等です:
\x -> case x of
0 -> "None"
1 -> "One"
_ -> "Some"
if
とThen
、else
のキーワードはJavaScriptの3項演算子に似た条件式を作成するために使用します。else
ブロックは必須です:
conditional = if 2 > 1 then "ok" else "oops"
let
キーワードは局所宣言の集まりを説明します。局所宣言は互いに再帰することが可能で、型宣言を持つことも可能です:
factorial :: Int -> Int
factorial =
let
go :: Int -> Int -> Int
go acc 1 = acc
go acc n = go (acc * n) (n - 1)
in
go 1
where
キーワードも局所宣言に用いられ、これは値宣言の末尾に書きます。
factorial :: Int -> Int
factorial = go 1
where
go :: Int -> Int -> Int
go acc 1 = acc
go acc n = go (acc * n) (n - 1)
束縛の本体は重要です。let-inブロックのように複数の束縛を定義するなら、各宣言の頭を同じ位置に揃える必要があります。束縛の定義の本体は、さらにインデントされていなければなりません。例えば:
f =
-- `let-in`ブロックは2スペースによる字下げから始まるので、
-- この束縛は2以上の字下げから始まらなければなりません。
let
-- `x`は4スペースで字下げされているため、`y`も4スペースで字下げされなければなりません。
-- その本体は4以上のスペースで字下げされる必要があります。
x :: Int -> Int
x a =
-- この本体は2スペースで字下げされています。
a
y :: Int -> Int
y c =
-- この本体は4スペースで字下げされています。
c
in do
-- `m`は`let`の始まりから数えて4スペースで字下げされているので、
-- `n`も4スペースで字下げされなければなりません。
-- その本体は4以上のスペースで字下げされる必要があります。
let m =
-- この本体は2スペースで字下げされています。
x (y 1)
n =
-- この本体は4スペースで字下げされています。
x 1
log "test"
do
キーワードはモナド式のシンプルな糖衣構文です。
例として、Maybe
型のモナドを使います:
maybeSum :: Maybe Number -> Maybe Number -> Maybe Number
maybeSum a b = do
n <- a
m <- b
let result = n + m
pure result
maybeSum
はMaybe Number
型の2つの引数を取り、valueがNothing
でなければ、それらの合計を返します。
do
記法を使う時、戻り値の型に対応するモナド
型クラスのインスタンスが存在しなければなりません。
文は以下のような形式になります:
a <- x
はx >>= \a -> ...
のように脱糖されます。x
はx >>= \_ -> ...
のように脱糖されるか、 文の最終行である場合は単にx
になります。let a = x
はlet束縛です。in
キーワードがないことに注意してください。
例として、maybeSum
の脱糖後は以下のようになります:
maybeSum a b =
a >>= \n ->
b >>= \m ->
let result = n + m
in pure result
※注: (>>=)はPreludeパッケージで定義されているBind
型のbind
関数です。