Haskellとは異なり、PureScriptは正格評価です。
JavaScriptに合わせた評価戦略になっています。既存のコードとの相互運用性は些細なものであり、PureScriptのモジュールから生成された関数はJavaScriptの「普通の」関数と正確に同じように振る舞います。同様に、FFIを通じてJavaScriptを呼び出すことも単純です。
正格評価を保つことは、実行時システムや過度に複雑なJavaScriptコードを必要としないことも意味します。必要なときには高いパフォーマンスのコードを書くこともできるべきであり、JavaScriptの上に遅延を導入することは避けられないオーバーヘッドを招くことになります。
PureScriptではPrelude
を他のライブラリと同様に扱い、暗黙的なインポートを行いません。また、コンパイラと共に配布されるライブラリはありません。
一般に受け入れられている「標準の」Prelude
ライブラリはpurescript-prelude
です。
モジュール内の型クラスはclass
キーワードと共に明示的にインポートされる必要があります。
module B where
import A (class Fab)
PureScriptにはqualified
キーワードがありません。単にimport Data.List as List
と書くことで、Haskellのimport qualified Data.List as List
と同様の効果を持ちます。
モジュールのインポートと公開についてはModulesで完全にドキュメント化されています。
PureScriptの多相関数は、型変数の使用前宣言のために明示的なforall
を必要とします。例えば、Haskellのリストのlength
関数は次のように宣言されています:
length :: [a] -> Int
PureScriptでは、これはType variable a is undefined
エラーによって失敗するため、次のように書く必要があります:
length :: forall a. Array a -> Int
forall
は複数の型変数を一度に宣言することができます。また、型クラス制約の前に記述する必要があります:
ap :: forall m a b. (Monad m) => m (a -> b) -> m a -> m b
JavaScriptの標準であるIEEE 754単精度浮動小数点数を表すNumber
型と、32ビットに制限された整数のInt
があります。JavaScriptでは、これを達成するためにInt
値と演算子は接尾辞|0
によって生成されます。例えば、Int
型の変数x
、y
、z
があるとき、PureScriptの式(x + y) * z
は((x + y)|0 * z)|0
にコンパイルされます。
PureScriptにはHaskellの()
に対応するUnit
型があります。Prelude
モジュールがその型を持つ値unit
を提供しています
PureScriptはリスト型の糖衣構文を提供しません。リストはData.List
にあるList
を用いて生成されます。
JavaScriptの配列のためにArray
型もありますが、これはList
と同じパフォーマンス特性を持ちません。Array
値は[x, y, z]
リテラルとして生成できますが、型の場合は注釈としてArray a
が必要になります。
PureScriptは列型を用いてJavaScriptスタイルのオブジェクトを直接エンコードできるので、Haskellスタイルのレコード定義が全く異なる意味を持ちます。
data Point = Point { x :: Number, y :: Number }
このようなHaskellの定義は現在の環境にいくつかのことを導入します:
Point :: Number -> Number -> Point
x :: Point -> Number
y :: Point -> Number
しかしPureScriptで導入されるのは、オブジェクト型を受け入れるPoint
コンストラクタだけです。実際、オブジェクト型を使うときにはデータコンストラクタを必要としないことがよくあります:
type PointRec = { x :: Number, y :: Number }
オブジェクトとオブジェクト型の定義はJavaScriptのそれと似た構文で構成されます:
origin :: PointRec
origin = { x: 0, y: 0 }
また、x
、y
のアクセサ関数を導入する代わりに、x
、y
はJavaScriptのプロパティのようにアクセスできます:
originX :: Number
originX = origin.x
PureScriptはHaskellと似たレコード更新の構文も提供します:
setX :: Number -> PointRec -> PointRec
setX val point = point { x = val }
気を付けるべきよくある間違いは、上記のPoint
のようなデータ型を受け入れる関数を書くときに起こります。そのオブジェクトはまだPoint
の内側にあるので、次のようにすると失敗します:
showPoint :: Point -> String
showPoint p = show p.x <> ", " <> show p.y
代わりに、Point
型を分解してオブジェクトを得るようにします:
showPoint :: Point -> String
showPoint (Point obj) = show obj.x <> ", " <> show obj.y
スーパークラスと共に型クラスを宣言している時、矢印は逆向きになります。例えば:
class (Eq a) <= Ord a where
...
これは=>
を常に論理包含として読むことができるようにするためで、上記の場合、Ord a
インスタンスはEq a
インスタンスを意味します。
PureScriptでは、インスタンスは名前を持つ必要があります:
instance arbitraryUnit :: Arbitrary Unit where
...
Haskellのようにインスタンスの重複は許されていません。インスタンス名はコンパイル後のJavaScriptの可読性を向上するために使用されます。
Haskellとは異なり、PureScriptはデータ型の宣言時に導出を行うことができません。例えば、次のようなコードは動きません:
data Foo = Foo Int String deriving (Eq, Ord)
しかし、PureScriptはStandaloneDeriving
機能を持ちます:
data Foo = Foo Int String
derive instance eqFoo :: Eq Foo
derive instance ordFoo :: Ord Foo
この方法で導出される型クラスの例は、Eq
、Functor
、Ord
です。その他の型クラスの一覧はこちらを参照してください。
Bounded
、Monoid
、Show
のような型クラスのために、総称を使って一般的な実装を行うことも可能です。一般的な実装を持つ他の型クラスの一覧はgenerics-repライブラリを参照してください。一般的な実装を持つ型クラスを書く方法も説明されています。
Haskellとは異なり、PureScriptでは孤児インスタンスを完全に禁止しています。孤児インスタンスを宣言しようとするとコンパイラエラーになります。
インスタンスが同一モジュール内で宣言できない場合、これを回避する方法の一つはユーザ定義型ラッパを使用することです。
現在、型クラスのデフォルトメンバ実装を宣言することはできません。今後可能になるかもしれません。
多くの型クラスの階層はHaskellよりも細かくなっています。例えば:
Category
は(<<<)
を提供するスーパークラスSemigroupoid
を持ち、恒等関数を必要としません。Monoid
は(<>)
を提供するスーパークラスSemigroup
を持ち、恒等関数を必要としません。Applicative
は(<*>)
を提供するスーパークラスApply
を持ち、pure
の実装を必要としません。
PureScriptはタプルのための特別な構文を持ちません。レコードがより意味のある型とアクセサを持つ利点によってn-組と同じ役割を果たすことができるためです。
2-組のTuple
型はpurescript-tuplesライブラリから使用可能です。Tuple
は他の型やデータコンストラクタと同様に扱えます。
PureScripteでは右から左への関数合成のために.
ではなく<<<
を使います。これは.
がプロパティへのアクセサや修飾名のために使われていることによる文法的な曖昧さを避けるためです。左から右への合成のために>>>
もあります。
<<<
演算子は実際には半群や圏にも適用されるより一般的な射合成演算子です。Prelude
モジュールは->
型のためにSemigroupoid
インスタンスを提供しており、これが関数合成にあたります。
PureScriptではreturn
を使っていた過去がありますが、現在はpure
で置き換えられています。もともとpure
の別名だったので、この置き換えは単に別名を削除したことで実装されました。
PureScriptは配列の内包表記に特別な構文を提供しません。代わりにdo
記法を使ってください。purescript-control
にあるControl.MonadPlus
モジュール内のguard
関数はフィルタ結果のために使われます:
import Prelude (($), (*), (==), bind, pure)
import Data.Array ((..))
import Data.Tuple (Tuple(..))
import Control.MonadZero (guard)
factors :: Int -> Array (Tuple Int Int)
factors n = do
a <- 1 .. n
b <- 1 .. a
guard $ a * b == n
pure $ Tuple a b
GHCは$
演算子のための特別な型付け規則を提供するため、以下のランク2runST
への自然な適用は関数を正しく型付けされています。
runST $ do
...
PureScriptはこの規則を提供しないので、どちらかが必要です
- 演算子を省略する:
runST do ...
- 代わりに丸括弧を使用する
runST (do ...)
Haskellでは、次の自然な構文によって演算子の定義が可能です:
f $ x = f x
PureScriptでは、名前を持つ関数の別名として演算子を提供します。演算子を使用した関数の定義はバージョン0.9で削除されました。
apply f x = f x
infixr 0 apply as $
Haskellでは、中置演算子の部分適用のために糖衣構文があります。
(2 ^) -- これは`(^) 2`または`\x -> 2 ^ x`に脱糖されます
(^ 2) -- これは`flip (^) 2`または`\x -> x ^ 2`脱糖されます
PureScriptでは、演算子セクションが少し異なります。
(2 ^ _)
(_ ^ 2)
PureScriptコンパイラはGHCのような言語拡張をサポートしません。しかし、GHC拡張と同様の(少なくとも似ている)いくつかの「組み込み」言語機能があります。現在は次のものがあります:
- DataKinds (以下の注意事項を見てください)
- EmptyDataDecls
- ExplicitForAll
- FlexibleContexts
- FlexibleInstances
- FunctionalDependencies
- KindSignatures
- MultiParamTypeClasses
- PartialTypeSignatures
- RankNTypes
- RebindableSyntax
- ScopedTypeVariables
※DataKinds
の注意事項:Haskellとは異なり、ユーザ定義種は元から使えますが、推奨されていません。これは、これらのコンストラクタが型レベルでのみ使用可能であることを意味します。詳しくは種システムを参照してください。
error
のために、purescript-exceptions
パッケージのEffect.Exception.Unsafe.unsafeThrow
を使えます。
undefined
は、purescript-unsafe-coerce
パッケージのUnsafe.Coerce.unsafeCoerce unit :: forall a. a
で模倣できます。purescript/purescript-prelude#44 も見てください。
しかし、PureScriptの正格性によってHaskellの場合とは異なる振る舞いをする可能性に注意してください。
ドキュメントを書いている時には、全コメント行の先頭にパイプ文字|
を書く必要があります。詳しくは文書化コメントのための文書を参照してください。
PureScriptはHaskellのレガシーコードを引き継いでいないので、いくつかの演算子や関数は異なる名前を持っています。
(>>)
は(*>)
です。Apply
はMonad
のスーパークラスなので、Monad
に特化する必要がないからです。- 0.9.1以来、
Prelude
ライブラリはappend
/(<>)
(Haskellのmappend
)の別名の別名である(++)
を含んでいません。 mapM
はtraverse
です。これはリストだけでない任意の一筆書き可能な構造に適用されるより一般的な形で、Monad
ではなくApplicative
のみを必要とします。同様に、liftM
はmap
です。- Haskellの
Data.List
にある多くの関数は、より一般的な形でData.Foldable
またはData.Traversable
から提供されます。 some
とmany
は、それらが操作する型のリストと共にData.Array
またはData.List
で定義されています。- 型付き穴には
_foo
の代わりに?foo
を使います。穴には名前が必要で、?
は許されません。 - レンジは
[1..2]
ではなく1..2
と書かれます。Haskellの[2..1]
は空リストですが、PureScriptの2..1
は[2,1]
になるという違いがあります。