Skip to content

Latest commit

 

History

History
595 lines (486 loc) · 25.5 KB

Differences-from-Haskell.md

File metadata and controls

595 lines (486 loc) · 25.5 KB

評価戦略

Haskellとは異なり、PureScriptは正格評価です。

JavaScriptに合わせた評価戦略になっています。既存のコードとの相互運用性は些細なものであり、PureScriptのモジュールから生成された関数はJavaScriptの「普通の」関数と正確に同じように振る舞います。同様に、FFIを通じてJavaScriptを呼び出すことも単純です。

正格評価を保つことは、実行時システムや過度に複雑なJavaScriptコードを必要としないことも意味します。必要なときには高いパフォーマンスのコードを書くこともできるべきであり、JavaScriptの上に遅延を導入することは避けられないオーバーヘッドを招くことになります。

Prelude/base

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型の変数xyzがあるとき、PureScriptの式(x + y) * z((x + y)|0 * z)|0にコンパイルされます。

ユニット

PureScriptにはHaskellの()に対応するUnit型があります。Preludeモジュールがその型を持つ値unitを提供しています

[a]

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 }

また、xyのアクセサ関数を導入する代わりに、xyは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

この方法で導出される型クラスの例は、EqFunctorOrdです。その他の型クラスの一覧はこちらを参照してください。

BoundedMonoidShowのような型クラスのために、総称を使って一般的な実装を行うことも可能です。一般的な実装を持つ他の型クラスの一覧はgenerics-repライブラリを参照してください。一般的な実装を持つ型クラスを書く方法も説明されています。

孤児インスタンス

Haskellとは異なり、PureScriptでは孤児インスタンスを完全に禁止しています。孤児インスタンスを宣言しようとするとコンパイラエラーになります。

インスタンスが同一モジュール内で宣言できない場合、これを回避する方法の一つはユーザ定義型ラッパを使用することです。

デフォルトメンバ

現在、型クラスのデフォルトメンバ実装を宣言することはできません。今後可能になるかもしれません。

型クラスの階層

多くの型クラスの階層はHaskellよりも細かくなっています。例えば:

  • Category(<<<)を提供するスーパークラスSemigroupoidを持ち、恒等関数を必要としません。
  • Monoid(<>)を提供するスーパークラスSemigroupを持ち、恒等関数を必要としません。
  • Applicative(<*>)を提供するスーパークラスApplyを持ち、pureの実装を必要としません。

タプル(組)

PureScriptはタプルのための特別な構文を持ちません。レコードがより意味のある型とアクセサを持つ利点によってn-組と同じ役割を果たすことができるためです。

2-組のTuple型はpurescript-tuplesライブラリから使用可能です。Tupleは他の型やデータコンストラクタと同様に扱えます。

合成演算子

PureScripteでは右から左への関数合成のために.ではなく<<<を使います。これは.がプロパティへのアクセサや修飾名のために使われていることによる文法的な曖昧さを避けるためです。左から右への合成のために>>>もあります。

<<<演算子は実際には半群や圏にも適用されるより一般的な射合成演算子です。Preludeモジュールは->型のためにSemigroupoidインスタンスを提供しており、これが関数合成にあたります。

return

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の場合とは異なる振る舞いをする可能性に注意してください。

文書化コメント

ドキュメントを書いている時には、全コメント行の先頭にパイプ文字|を書く必要があります。詳しくは文書化コメントのための文書を参照してください。

Haskellの○○はどこにある?

PureScriptはHaskellのレガシーコードを引き継いでいないので、いくつかの演算子や関数は異なる名前を持っています。

  • (>>)(*>)です。ApplyMonadのスーパークラスなので、Monadに特化する必要がないからです。
  • 0.9.1以来、Preludeライブラリはappend(<>)(Haskellのmappend)の別名の別名である(++)を含んでいません。
  • mapMtraverseです。これはリストだけでない任意の一筆書き可能な構造に適用されるより一般的な形で、MonadではなくApplicativeのみを必要とします。同様に、liftMmapです。
  • HaskellのData.Listにある多くの関数は、より一般的な形でData.FoldableまたはData.Traversableから提供されます。
  • somemanyは、それらが操作する型のリストと共にData.ArrayまたはData.Listで定義されています。
  • 型付き穴には_fooの代わりに?fooを使います。穴には名前が必要で、?は許されません。
  • レンジは[1..2]ではなく1..2と書かれます。Haskellの[2..1]は空リストですが、PureScriptの2..1[2,1]になるという違いがあります。