Admittedly something small.

ちょっと小さいのはたしかですが。

【翻訳】PureScriptとHaskellの違い +α

2019年2月21日 ( 6年前に投稿 )

Haskell purescript

hasu.jpg

PureScriptの公式ドキュメントより、『Haskellとの違い』のざっくり翻訳+αです。

(翻訳ここから)


<!--

Evaluation strategy

-->

評価戦略

<!-- Unlike Haskell, PureScript is strictly evaluated. -->

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

<!-- As the evaluation strategy matches JavaScript, interoperability with existing code is trivial - a function exported from a PureScript module behaves exactly like any "normal" JavaScript function, and correspondingly, calling JavaScript via the FFI is simple too. -->

JavaScriptの評価戦略と一致するので、既存のコードとの相互運用はとても簡単です。PureScriptモジュールからエクスポートされた関数は、「通常の」JavaScript関数とまったく同じように振る舞いますし、それに対応して、FFIを通じてのJavaScriptの呼び出しもシンプルになっています。

<!-- Keeping strict evaluation also means there is no need for a runtime system or overly complicated JavaScript output. It should also be possible to write higher performance code when needed, as introducing laziness on top of JavaScript comes with an unavoidable overhead. -->

正格評価を維持するということは、実行時システムや非常に複雑なJavaScript出力も必要ないということを意味します。やむをえないオーバーヘッドもついてきますが、JavaScriptの上に遅延性を導入することで、必要ならより効率のいいコードを書くことも可能です。

Prelude/base

<!-- There is no implicit Prelude import in PureScript, the Prelude module is just like any other. Also, no libraries are distributed with the compiler at all. -->

PureScriptに暗黙のPreludeインポートはありません。Preludeモジュールは他のモジュールとまったく同じものです。また、コンパイラと一緒に配布されるライブラリもありません。

<!-- The generally accepted "standard" Prelude is the purescript-prelude library. -->

一般に広く使われている『標準の』Preludepurescript-prelude です。

<!--

Module Imports / Exports

-->

モジュールのインポートとエクスポート

<!-- Type classes in modules must be specifically imported using the class keyword. -->

モジュールに定義された型クラスは、classキーワードを使って特別にインポートします。

module B where

import A (class Fab)

<!-- There is no qualified keyword in PureScript. Writing import Data.List as List has the same effect as writing import qualified Data.List as List in Haskell. -->

PureScriptにqualifiedキーワードはありません。import Data.List as List というように書くと、Haskellでimport qualified Data.List as Listと書いたのと同じ効果になります。

<!-- Module imports and exports are fully documented on the Modules page. -->

モジュールのインポートとエクスポートについては、Modulesのページで詳しく説明されています。

<!--

Types

-->

<!--

Explicit forall

-->

明示的な forall

<!-- Polymorphic functions in PureScript require an explicit forall to declare type variables before using them. For example, Haskell's list length function is declared like this: -->

PureScriptにおける多相的な関数は、明示的なforallを使って事前に型変数を定義する必要があります。たとえば、Haskellではリストのlength関数は次のように定義することができます。

length :: [a] -> Int

<!-- In PureScript this will fail with the error Type variable a is undefined. The PureScript equivalent is: -->

PureScriptでは、この定義はType variable a is undefinedというエラーになります。PureScriptでこれと同じような定義をするには、次のように書きます。

length :: forall a. Array a -> Int

<!-- A forall can declare multiple type variables at once, and should appear before typeclass constraints: -->

forallでは一度に複数個の型変数を定義することもできます。また、型クラス制約の手前に書かなくてはなりません。

ap :: forall m a b. (Monad m) => m (a -> b) -> m a -> m b

<!--

Numbers

-->

<!-- There is a native Number type which represents JavaScript's standard IEEE 754 float and an Int which is restricted to the range of 32-bit integers. In JavaScript, the Int values and operations are generated with a |0 postfix to achieve this, e.g. if you have variables x, y, and z of type Int, then the PureScript expression (x + y) * z would compile to ((x + y)|0 * z)|0. -->

JavaScriptの標準のIEEE754浮動小数点数を表す、組み込みのNumber型が存在し、Int型ではそれが32ビット整数の値域に制限されます。Intの値と操作には|0が後ろに付加されてJavaScriptが生成されることでこれが実現されています。たとえば、もしInt型の変数xyzがあるとしたら、PureScriptの(x + y) * z((x + y)|0 * z)|0へとコンパイルされます。

Unit

<!-- PureScript has a type Unit used in place of Haskell's (). The Prelude module provides a value unit that inhabits this type. -->

PureScriptはUnit型を持っており、これはHaskellの()に相当します。Preludeモジュールでは、このUnit型の値としてunitという値を提供しています。

[a]

<!-- PureScript does not provide syntactic sugar for list types. Construct list types using List from Data.List. -->

PureScriptではリスト型に対して構文糖は提供していません。Data.ListListを使ってリストを構築してください。

<!-- There is also an Array type for native JavaScript arrays, but this does not have the same performance characteristics as List. Array values can be constructed with [x, y, z] literals, but the type still needs to be annotated as Array a. -->

組み込みのJavaScript配列に対応するものとしてはArray型がありますが、これはListと同じようなパフォーマンス特性があるわけではありません。Array[x, y, z]というようなリテラルで構築することができますが、型についてはArray aというように型注釈をすることが必要です。

<!--

Records

-->

レコード

<!-- PureScript can encode JavaScript-style objects directly by using row types, so Haskell-style record definitions actually have quite a different meaning in PureScript: -->

PureScriptではJavaScript形式のオブジェクトを行型(Row types)を使って直接エンコードすることができます。Haskell形式のレコード定義は、PureScriptのものとはまったく異なる意味を持っています。

data Point = Point { x :: Number, y :: Number }

<!-- In Haskell a definition like this would introduce several things to the current environment: -->

Haskellでは、上の定義は現在の環境へ次のようないくつかのものを導入します。

Point :: Number -> Number -> Point
x :: Point -> Number
y :: Point -> Number

<!-- However in PureScript this only introduces a Point constructor that accepts an object type. In fact, often we might not need a data constructor at all when using object types: -->

それに対して、PureScriptではオブジェクト型ひとつを保持するPoint構築子だけを導入します。実のところ、オブジェクト型を使うときは、データコンストラクタをまったく必要としないことが大半です。

type PointRec = { x :: Number, y :: Number }

<!-- Objects are constructed with syntax similar to that of JavaScript (and the type definition): -->

オブジェクトはJavaScriptと同じような構文で構築されます(型の定義も同様です)。

origin :: PointRec 
origin = { x: 0, y: 0 }

<!-- And instead of introducing x and y accessor functions, x and y can be read like JavaScript properties: -->

そしてPureScriptでは、Haskellのようにxyアクセサ関数が導入されるのではなく、JavaScriptのプロパティと同じようにxyを読むことができます。

originX :: Number
originX  = origin.x

<!-- PureScript also provides a record update syntax similar to Haskell's: -->

PureScriptではHaskellと同じようなレコード更新構文も提供します。

setX :: Number -> PointRec -> PointRec 
setX val point = point { x = val }

<!-- A common mistake to look out for is when writing a function that accepts a data type like the original Point above—the object is still wrapped inside Point, so something like this will fail: -->

気をつけたほうがいいよくある間違いとして、先程の本来のPointと同じようにデータ型を受け入れる関数を書いているときに、このオブジェクトはPointの中に包まれたままだというものがあります。そのため、次のようなコードはうまくいきません。

showPoint :: Point -> String
showPoint p = show p.x <> ", " <> show p.y

<!-- Instead, we need to destructure Point to get at the object: -->

そうではなく、Pointを分解してオブジェクトからオブジェクトを取り出す必要があります。

showPoint :: Point -> String
showPoint (Point obj) = show obj.x <> ", " <> show obj.y

<!--

Type classes

-->

型クラス

<!--

Arrow direction

-->

矢印の方向

<!-- When declaring a type class with a superclass, the arrow is the other way around. For example: -->

スーパークラスをもつ型クラスを定義するとき、PureScriptとHaskellでは矢印は逆向きになります。たとえば、PureScriptでは次のように書きます。

class (Eq a) <= Ord a where
  ...

<!-- This is so that => can always be read as logical implication; in the above case, an Ord a instance implies an Eq a instance. -->

この=>は論理包含と同じように読むことができます。この場合、Ord aインスタンスが存在する『ならば』Eq aインスタンスが存在します。

<!--

Named instances

-->

名前付きのインスタンス

<!-- In PureScript, instances must be given names: -->

PureScriptでは、インスタンスには次のように名前が付きます。

instance arbitraryUnit :: Arbitrary Unit where
  ...

<!-- Overlapping instances are still disallowed, like in Haskell. The instance names are used to help the readability of compiled JavaScript. -->

Haskellと同じように重複インスタンスは禁止されます。インスタンスの名前はコンパイル後のJavaScriptの可読性のために使われます。

<!--

Deriving

-->

インスタンスの導出

<!-- Unlike Haskell, PureScript doesn't have deriving functionality when declaring data types. For example, the following code does not work in PureScript: -->

Haskellとは異なり、PureScriptにはデータ型の定義の構文自体にはインスタンスの自動導出の機能がありません。例えば、次のようなコードはPureScriptでは動きません。

data Foo = Foo Int String deriving (Eq, Ord)

<!-- However, PureScript does have StandaloneDeriving-type functionality: -->

しかしそのかわり、PureScriptには型のStandaloneDerivingという機能があります。

data Foo = Foo Int String

derive instance eqFoo :: Eq Foo
derive instance ordFoo :: Ord Foo

<!-- Examples of type classes that can be derived this way include Eq, Functor, and Ord. See here for a list of other type classes. -->

この方法での導出が可能な型クラスの例としては、EqFunctorOrdがあります。その他の型クラスの一覧については、こちらを参照してください。

<!-- Using generics, it is also possible to use generic implementations for type classes like Bounded, Monoid, and Show. See the generics-rep library for a list of other type classes that have generic implementations, as well as an explanation of how to write generic implementations for your own type classes. -->

ジェネリクスを使うと、BoundedMonoidShowなどの型クラスについてのジェネリックな実装を使うことが可能になります。ジェネリックな実装を持つ他の型クラスについてや、独自の型クラスに対してジェネリックな実装を書く方法についての説明は、the generics-rep libraryを参照してください。

<!--

Orphan Instances

-->

はぐれたインスタンス

<!-- Unlike Haskell, orphan instances are completely disallowed in PureScript. It is a compiler error to try to declare orphan instances. -->

Haskellとは異なり、PureScriptでははぐれたインスタンス(Orphan instances)は完全に禁止されます。これは、はぐれたインスタンスの定義はコンパイルエラーを引き起こすことがあるからです。

<!-- When instances cannot be declared in the same module, one way to work around it is to use newtype wrappers. -->

同じモジュール内にインスタンスが定義できないときは、newtypeで包むことで回避する方法があります。

<!--

Default members

-->

デフォルトのメンバ

<!-- At the moment, it is not possible to declare default member implementations for type classes. This may change in the future. -->

今のところ、型クラスにデフォルトのメンバを定義することはできません。これは将来的には変更される可能性があります。

<!--

Type class hierarchies

-->

型クラス階層

<!-- Many type class hierarchies are more granular than in Haskell. For example: -->

多くの型クラス階層はHaskellよりも段階的になっています。たとえば次のようになっています。

<!--

  • Category has a superclass Semigroupoid which provides (<<<), and does not require an identity.

  • Monoid has a superclass Semigroup, which provides (<>), and does not require an identity.

  • Applicative has a superclass Apply, which provides (<*>) and does not require an implementation for pure. -->

  • Categoryはスーパークラスとして(<<<)を提供する型クラスであるSemigroupoidを持ちます。Semigroupoidは単位元を要求しません。**(※訳注:HaskellではCategory型クラスが射の合成(.)と単位元idの両方を持ちますが、PureScriptではこれらのメンバは射の合成(<<<)を提供する型クラスSemigroupoidと単位元identityを提供する型クラスCategoryに分かれており、SemigroupoidCategoryのスーパークラスになっています。Haskellではひとつの型クラスであったものが、PureScriptでは親子関係のあるふたつの型クラスに分割されているということです。)**

  • Monoidはスーパークラスとして(<>)を提供するSemigroupを持ちます。Semigroupは単位元を要求しません。

  • Applicativeはスーパークラスとして(<*>)を提供するApplyを持ちます。Applypureの実装は要求しません。

<!--

Tuples

-->

タプル

<!-- PureScript has no special syntax for tuples as records can fulfill the same role that n-tuples do with the advantage of having more meaningful types and accessors. -->

レコードを使えば同じような役割を果たす任意個のタプルをもっと意味のある型とアクセサを持って実現できるので、PureScriptではタプルに特別な構文は持っていません。

<!-- A Tuple type for 2-tuples is available via the purescript-tuples library. Tuple is treated the same as any other type or data constructor. -->

2要素のタプルについては、purescript-tuples ライブラリを通じて使うことができます。Tupleは他の型やデータコンストラクタとまったく同じように扱われます。

<!--

Composition operator

-->

合成演算子

<!-- PureScript uses <<< rather than . for right-to-left composition of functions. This is to avoid a syntactic ambiguity with . being used for property access and name qualification. There is also a corresponding >>> operator for left-to-right composition. -->

PureScriptでは右から左に合成する関数として、.ではなく<<<を使います。これは.のプロパティアクセスや名前の修飾の構文とのあいまいさを避けるためです。また、左から右への合成については、対応する>>>演算子があります。

<!-- The <<< operator is actually a more general morphism composition operator that applies to semigroupoids and categories, and the Prelude module provides a Semigroupoid instance for the -> type, which gives us function composition. -->

実際にはこの<<<演算子は、semigroupoids とCategoryに適用されるより抽象的な「射の合成演算子」です。Preludeモジュールは->型についてのSemigroupoidインスタンスを提供しており、これが関数合成になっています。

return

<!-- In the past, PureScript used return. However, it is now removed and replaced with pure. It was always an alias for pure, which means this change was implemented by simply removing the alias. -->

過去にはPureScriptでもreturnを使っていたときがありました。しかし、現在はこれは削除され、pureに置き換えられています。returnpureの単なる別名でしたので、この変更というのは単に別名が削除されたというだけです。

<!--

Array Comprehensions

-->

配列内包表記

<!-- PureScript does not provide special syntax for array comprehensions. Instead, use do-notation. The guard function from the Control.MonadPlus module in purescript-control can be used to filter results: -->

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

<!--

No special treatment of $

-->

$を特別扱いしない

<!-- GHC provides a special typing rule for the $ operator, so that the following natural application to the rank-2 runST function is well-typed: -->

GHCは$演算子に特別な型付けルールを適用しますが、そのためランク2のrunST関数への$の適用が正しく型付けされます。

runST $ do
  ...

<!-- PureScript does not provide this rule, so it is necessary to either -->

PureScriptではこのようなルールは提供していませんので、次のどちらかに従います。

<!--

  • omit the operator: runST do ...
  • or use parentheses instead: runST (do ...) -->
  • runST do ...のようにして演算子をとる
  • または、runST (do ...)のようにして括弧で囲む

<!--

Defining Operators

-->

演算子の定義

<!-- In Haskell, it is possible to define an operator with the following natural syntax: -->

Haskellでは、次のような自然な構文で演算子を定義することができます。

f $ x = f x

<!-- In PureScript, you provide an operator alias for a named function. Defining functions using operators is removed since version 0.9. -->

PureScriptでは、演算子は名前付き関数の別名として提供することになります。演算子を使った関数の定義は、バージョン0.9で廃止されました。

apply f x = f x
infixr 0 apply as $

<!--

Operator Sections

-->

演算子のセクション

<!-- In Haskell, there is syntactic sugar to partially apply infix operators. -->

Haskellでは、中置記法の演算子の部分適用には構文糖があります。

(2 ^) -- desugars to `(^) 2`, or `\x -> 2 ^ x`
(^ 2) -- desugars to `flip (^) 2`, or `\x -> x ^ 2`

<!-- In PureScript, operator sections look a little bit different. -->

PureScriptでは、演算子セクションはちょっと異なる見た目になります。

(2 ^ _)
(_ ^ 2)

<!--

Extensions

-->

拡張

<!-- The PureScript compiler does not support GHC-like language extensions. However, there are some "built-in" language features that are equivalent (or at least similar) to a number of GHC extensions. These currently are: -->

PureScriptコンパイラはGHCのような言語拡張を提供しません。しかし、GHC拡張のいくつかと同等の機能(もしくは少なくとも似たような機能)が、『組み込みの』言語機能として存在しています。

  • DataKinds (see note below)
  • EmptyDataDecls
  • ExplicitForAll
  • FlexibleContexts
  • FlexibleInstances
  • FunctionalDependencies
  • KindSignatures
  • MultiParamTypeClasses
  • PartialTypeSignatures
  • RankNTypes
  • RebindableSyntax
  • ScopedTypeVariables

<!-- Note on DataKinds: Unlike in Haskell, user-defined kinds are open, and they are not promoted, which means that their constructors can only be used in types, and not in values. For more information about the kind system, see https://github.com/purescript/documentation/blob/master/language/Types.md#kind-system -->

DataKindsについては注意してください。Haskellとは異なり、ユーザは種を自分で定義することができますが、それらは昇格されません。つまり、これらのコンストラクタは型でのみ使うことができ、値では使うことができません。種のシステムについての詳しい情報は、以下を参照してください。

https://github.com/purescript/documentation/blob/master/language/Types.md#kind-system

<!--

error and undefined

-->

errorundefined

<!-- For error, you can use Effect.Exception.Unsafe.unsafeThrow, in the purescript-exceptions package. -->

errorについては、purescript-exceptionsパッケージのEffect.Exception.Unsafe.unsafeThrowを使うことができます。

<!-- undefined can be emulated with Unsafe.Coerce.unsafeCoerce unit :: forall a. a, which is in the purescript-unsafe-coerce package. See also https://github.com/purescript/purescript-prelude/issues/44. -->

undefinedpurescript-unsafe-coerceパッケージにある Unsafe.Coerce.unsafeCoerce unit :: forall a. aで模倣することができます。 https://github.com/purescript/purescript-prelude/issues/44 を参照してみてください。

<!-- Although note that these might have different behaviour to the Haskell versions due to PureScript's strictness. -->

ただし、PureScriptの正格性が原因で、これらはHaskell版とは異なる振る舞いを持つことがあるのに注意してください。

<!--

Documentation comments

-->

ドキュメントコメント

<!-- When writing documentation, the pipe character | must appear at the start of every comment line, not just the first. See the documentation for doc-comments for more details. -->

ドキュメントを書くときは、ドキュメントコメントの最初の一行だけではなく、ドキュメントコメントのすべての行の先頭にパイプ文字|を書かなければなりません。詳しくはthe documentation for doc-commentsを参照してください。

<!--

Where is ... from Haskell?

-->

Haskellの◯◯はどこにあるの?

<!-- As PureScript has not inherited Haskell's legacy code, some operators and functions that are common in Haskell have different names in PureScript: -->

PureScriptはHaskellのコードをそのまま引き継いでいるわけではないので、Haskellの演算子や関数のなかには、PureScriptでは異なる名前を持っているものがあります。

<!--

  • (>>) is (*>), as Apply is a superclass of Monad so there is no need to have an Monad-specialised version.
  • Since 0.9.1, the Prelude library does not contain (++) as a second alias for append / (<>) (mappend in Haskell) anymore.
  • mapM is traverse, as this is a more general form that applies to any traversable structure, not just lists. Also it only requires Applicative rather than Monad. Similarly, liftM is map.
  • Many functions that are part of Data.List in Haskell are provided in a more generic form in Data.Foldable or Data.Traversable.
  • some and many are defined with the type of list they operate on (Data.Array or Data.List).
  • Instead of _foo for typed holes, use ?foo. You have to name the hole; ? is not allowed.
  • Ranges are written as 1..2 rather than [1..2]

-->

  • (>>)(*>)に変わりました。ApplyMonadのスーパークラスなので、Monad専用のバージョンは必要ないからです。
  • 0.9.1以降、Preludeライブラリはappendつまり(<>) (Haskellではmappend)のふたつめの別名である(++)をもはや含んでいません。
  • mapMtraverseです。traverseは、リストに限らず、どんなtraversableな構造に対しても適用するより一般的な形式であるからです。また、MonadではなくApplicative であることだけを要求します。同じように、liftMmapになっています。
  • HaskellのData.Listの一部の関数は、 Data.FoldableData.Traversableでもっと一般的な形式で提供されています。
  • somemanyが リストのような型(Data.ArrayData.List)にそれぞれ 定義されています。
  • typed holesには_fooではなく?fooを使ってください。holeには名前をつけなくてはならず、単なる?は禁止です。
  • 範囲は[1..2]ではなく1..2のように書きます。

(翻訳ここまで)

+α

そのほかの違いで、私が思いついたものや、指摘を受けて気付いたものをいくつか付け加えておきます。

文字列の型

Haskellでは文字列の型Stringは文字 Char のリスト[Char] の別名となっています。これは確かに直感的で、リストの関数がすべて String に対しても使えるというわかりやすさはあったものの、パフォーマンスが劣悪で結局は Haskell 最大の失敗のひとつとなりました。 OverloadedStrings拡張などで凌いではいるものの、文字列という基本的な型がStringTextByteStringのように複数入り混じっているのは、わかりやすいとはとても言えません。PureScriptではJavaScriptのStringと対応するStringがひとつあるだけで、それとは別にCharがあります。

Lazyな入出力

評価戦略とも関係するのですが、Haskellのでは古くから遅延評価の仕組みに基づく入出力の関数がいくつか提供されています。しかしこれは実用上はまったく使い物になりませんでした。PureScriptは正格評価なので、このような落とし穴はまったくありません。

型クラス制約

Haskellでは(TypeClassA a, TypeClassB b) => Type a bのようにして、いちどの=>で複数の型クラス制約を書くことができましたが、PureScriptではTypeClassA a => TypeClassB b => Type a bのようにして、=>につきひとつづつの型クラス制約を書きます。これはPureScriptにおける型クラス制約は、暗黙の引数に他ならないためです。たとえば、fortraverseの引数の順序を交換したものに過ぎませんが、次のように定義されています。

for
  :: forall a b m t
   . Applicative m
  => Traversable t
  => t a
  -> (a -> m b)
  -> m (t b)
for x f = traverse f x

これをコンパイルすると、次のようなJavaScriptへと変換されます。

var $$for = function (dictApplicative) {
    return function (dictTraversable) {
        return function (x) {
            return function (f) {
                return Data_Traversable.traverse(dictTraversable)(dictApplicative)(f)(x);
            };
        };
    };
};

型クラス制約が特殊な引数でしかないということを考えると、カリー化された関数のように=>を繰り返し使う構文に一貫性があることがわかると思います。

Eq型クラスのメンバ

HaskellではEq型クラスには(==)(/=)のふたつが定義されますが、PureScriptのEq型クラスでは(==)の本来の名前であるeqだけが定義されており、notEqおよびその別名である(/=)はただの独立した関数になっています。

Haskellで(==)(/=)の両方が実装可能になっているのは最適化の都合だというような話だったかと思うのですが、(/=)が実装可能であることで実用上大きく性能が改善するというようなケースがあるのかは疑わしいです。PureScriptのシンプルな定義のほうが良さそうです。

for_

Haskellでは同じ機能であるfor :: (Traversable t, Applicative f) => t a -> (a -> f b) -> f (t b)forM :: (Traversable t, Monad m) => t a -> (a -> m b) -> m (t b)のふたつが効率上の理由で別々に提供されていますが、PureScriptではforだけが提供されています。

関数をつくるアンダーバー_

演算子のセクションの話題と重複しますが、PureScriptでは式の一部がアンダーバー_になっていると、「その部分を引数として受け取るような関数」を表す式になることがあります。演算子では(_ + 42)\x -> x + 42と同じ意味になりました。それと同様に、次のようなアンダーバーを条件部分においたcase式

case _ of 
    Nothing -> "Nothing"
    Just x -> "Just" <> show x

は、次のような関数と同じ意味になります。

\v -> case v of 
    Nothing -> "Nothing"
    Just x -> "Just" <> show x

これと同じようなアンダーバーを、if _ then x else yのようにif式でも使えますし、_.fooのようにプロパティの読み取りに使ったり、_ { x = 42 }_ { x = _ }のようにしてプロパティの更新をする関数を書くこともできます。「アンダーバーに置き換えた部分を引数にとるような関数にする」という振る舞いにおいて構文上の一貫性が取れています。

mapの名前

Haskellにはmap :: (a -> b) -> [a] -> [b] がありますが、これはFunctorfmap :: Functor f => (a -> b) -> f a -> f bによって一般化され、mapは不要になりました。これを踏まえてPureScriptには最初からリスト専用の関数であるmapはありませんので、Functorのほうの一般化された関数のほうにmapの名前を使うことができました。

IOEffect

Haskellでは組み込みの作用の型はIOですが、PureScriptではEffectです。使い勝手のうえではほとんど差はないのですが、これらが入出力以外も含む作用一般のための型であることを踏まえると、強いて言えばEffectのほうが適切な名前なのではないかと思います。もちろん、IOのほうが短くて書くのが楽だというのはあります。

部分関数の排除

Haskellのhead :: [a] -> aは空リストに適用すると実行時エラーになります。このようないわゆる部分関数がHaskellには少なからず存在しており、Haskellはその厳格なイメージに反して実行時エラーに対して脆弱な部分を数多く残しています。PureScriptではそれに相当する関数はhead :: forall a. Array a -> Maybe aとなっており、結果をMaybeで返すようになっているので実行時エラーになることがありません。このように、PureScriptのあらゆるAPIや構文は基本的に実行時エラーを起こさないような設計になっており、実行時エラーが起きることは基本的にありません。このような言語デザインは、同じくHaskell派生の言語であるElmでも同様になっています。

一方で、効率のためや記述性のために、部分関数であるhead :: forall a. Partial => Array a -> aData.Array.Partialという別のモジュールで提供されており、必要であれば安全性と引き換えにしてこちらを使うこともできます。

ライブラリの高い一貫性

Preludeのところでも簡単に触れましたが、Haskellでは多くのライブラリが言語やコンパイラ組み込みになっている一方、PureScriptでは言語に組み込まれたライブラリはPrimモジュールに含まれた幾つかの基礎的な型のみです。特殊な要素を極限まで廃することで、言語自体をシンプルに保つと同時に、例外的に扱われる型が極めて少ない、一貫性のあるライブラリを提供できています。

さいごに

Haskellはある種の伝説というか、関数型プログラミング言語のなかでも特にラディカルな位置にあって、Haskellを『美しい言語』だと評する人もいるようです。しかし、Haskellもまた長い歴史を持ち、歴史的な理由で多くの瑕疵を抱えてきた言語です。この記事にあるように、多くの関数が重複していたりしますし、部分関数も多く提供されており実行時エラーを引き起こしやすく、素のHaskellは機能不足で使いづらく多くのGHC拡張を導入してようやく快適に書ける言語だと言えます。そんなわけで私は決して『美しい』とまでは思いませんが、Haskellはそれを正しく理解し、なるべく互換性も保ちつつも改良を加え続け、泥臭くも着実に正しい方向に進んでいると私は思います。

そうはいっても、これだけ長い歴史のある言語を互換性を維持しながら改良を続けるのは容易ではなく、その点PureScriptはHaskellの精神を受け継ぎつつも反省を踏まえて新たに作られている言語で、Haskellに残された不満が大きく解消しています。Haskellに手を出すような人はかなり身軽に言語を乗り換えるタイプの人だと思いますし、PureScriptにも気軽に興味を示すHaskellユーザがもっと増えてくれればいいと私は思います。

(この記事は同じ筆者が Qiita に投稿した記事の複製です。オリジナル記事)