Skip to content

Latest commit

 

History

History
994 lines (814 loc) · 28.5 KB

Syntax.md

File metadata and controls

994 lines (814 loc) · 28.5 KB

構文

空白規則

構文は空白に敏感です。一般的な経験則としては、複数行に分けられた宣言の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つの書き方は同等ですが、トリプルクォートを使う方が、書く時も保守する時も簡単です。これは複雑な正規表現を書くときに更に有用です。

真理値

真理値リテラルはtruefalseです。

関数

関数値(ラムダ式と呼ばれることもあります)はバックスラッシュのあとに引数リストを書くことで表現できます:

\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 }

2項演算子

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つのパートから成ります:

  • 結合規則: infixlinfixrまたは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 4infixl 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式

caseofのキーワードは、値のコンストラクタに基づいたロジックを作成するために値を分解するために使用します。ケース側に,で区切って記述することで、複数の値にマッチすることができます。

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式

ifThenelseのキーワードはJavaScriptの3項演算子に似た条件式を作成するために使用します。elseブロックは必須です:

conditional = if 2 > 1 then "ok" else "oops"

let・where束縛

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記法

doキーワードはモナド式のシンプルな糖衣構文です。

例として、Maybe型のモナドを使います:

maybeSum :: Maybe Number -> Maybe Number -> Maybe Number
maybeSum a b = do
  n <- a
  m <- b
  let result = n + m
  pure result

maybeSumMaybe Number型の2つの引数を取り、valueがNothingでなければ、それらの合計を返します。

do記法を使う時、戻り値の型に対応するモナド型クラスのインスタンスが存在しなければなりません。

文は以下のような形式になります:

  • a <- xx >>= \a -> ...のように脱糖されます。
  • xx >>= \_ -> ...のように脱糖されるか、 文の最終行である場合は単にxになります。
  • let a = xはlet束縛です。inキーワードがないことに注意してください。

例として、maybeSumの脱糖後は以下のようになります:

maybeSum a b =
  a >>= \n ->
    b >>= \m ->
      let result = n + m
      in pure result

※注: (>>=)はPreludeパッケージで定義されているBind型のbind関数です。