Haskell Language
一般的なGHC言語拡張
サーチ…
備考
これらの言語拡張は、グラスゴー・ハスケル・コンパイラ(GHC)を使用している場合には、通常、承認されたHaskell 2010言語レポートの一部ではないため、利用可能です。これらの拡張機能を使用するには、 フラグを使用してコンパイラに通知するか、ファイル内のmodule
キーワードの前にLANGUAGE
プログラムを置く必要があります。公式文書はGCHユーザーガイドのセクション7にあります。
LANGUAGE
プログラムの形式は{-# LANGUAGE ExtensionOne, ExtensionTwo ... #-}
です。それはリテラル{-#
後にLANGUAGE
続いて、その後にコンマで区切られた拡張子のリスト、そして最後には#-}
です。複数のLANGUAGE
プログラムを1つのファイルに配置することができます。
MultiParamTypeClasses
これは、複数の型パラメータを持つ型クラスを可能にする非常に一般的な拡張です。 MPTCは型間の関係として考えることができます。
{-# LANGUAGE MultiParamTypeClasses #-}
class Convertable a b where
convert :: a -> b
instance Convertable Int Float where
convert i = fromIntegral i
パラメータの順序は重要です。
MPTCはタイプファミリに置き換えられることがあります。
FlexibleInstances
通常のインスタンスには、
All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.
つまり、 [a]
のインスタンスを作成することはできますが、 [Int]
インスタンスを作成することはできません。 FlexibleInstances
緩和します:
class C a where
-- works out of the box
instance C [a] where
-- requires FlexibleInstances
instance C [Int] where
オーバーロードされた文字列
通常、Haskellの文字列リテラルはString
型( [Char]
型名)です。これは小規模な教育プログラムにとっては問題ではありませんが、実際のアプリケーションではText
やByteString
などの効率的なストレージが必要になることがよくあります。
OverloadedStrings
は、リテラルのタイプを
"test" :: Data.String.IsString a => a
そのような型を予期する関数に直接渡せるようにします。多くのライブラリは、 [Char]
よりも時間と空間の点で優れているData.TextやData.ByteStringなどの文字列型のインタフェースを実装しています。
また、 Postgresql-simpleライブラリのようなOverloadedStrings
いくつかのユニークな使い方があります。これは、SQLクエリを通常の文字列のように二重引用符で書き込むことができますが、SQLインジェクション攻撃の悪名高いソースである不適切な連結に対して保護します。
IsString
クラスのインスタンスを作成するには、 fromString
関数をfromString
必要があります。例† :
data Foo = A | B | Other String deriving Show
instance IsString Foo where
fromString "A" = A
fromString "B" = B
fromString xs = Other xs
tests :: [ Foo ]
tests = [ "A", "B", "Testing" ]
†この例題は、Lyndon Maydwell(GitHubのsordina)のsordina
を受けています 。
TupleSections
セクション形式でタプルコンストラクタ(演算子)を適用する構文拡張。
(a,b) == (,) a b
-- With TupleSections
(a,b) == (,) a b == (a,) b == (,b) a
N-タプル
また、2より大きいアリティを持つタプルに対しても機能します
(,2,) 1 3 == (1,2,3)
マッピング
これは、セクションが使用されている他の場所で役に立ちます:
map (,"tag") [1,2,3] == [(1,"tag"), (2, "tag"), (3, "tag")]
この拡張子を除いた上記の例は、次のようになります。
map (\a -> (a, "tag")) [1,2,3]
UnicodeSyntax
特定のビルトイン演算子と名前の代わりにUnicode文字を使用できる拡張機能。
ASCII | Unicode | 使用する |
---|---|---|
:: | ∷ | 型を持っている |
-> | → | 関数型、lambda、 case ブランチなど |
=> | ⇒ | クラスの制約 |
forall | ∀ | 明示的多型 |
<- | ← | do 表記 |
* | ★ | タイプの種類(または種類)( Int :: ★ ) |
>- | ⤚ | Arrows proc 表記 |
-< | ⤙ | Arrows proc 表記 |
>>- | ⤜ | Arrows proc 表記 |
-<< | ⤛ | Arrows proc 表記 |
例えば:
runST :: (forall s. ST s a) -> a
になる
runST ∷ (∀ s. ST s a) → a
ことに注意してください*
対★
例は若干異なります。ので、 *
予約されていない、 ★
も同じように動作します*
乗算に、または指定した任意の他の機能(*)
およびその逆。例えば:
ghci> 2 ★ 3
6
ghci> let (*) = (+) in 2 ★ 3
5
ghci> let (★) = (-) in 2 * 3
-1
バイナリリテラル
標準Haskellは、(任意の接頭辞なし)は、小数の整数リテラルを記述することを可能にする進数(が先行0x
または0X
)、及び(が先行進0o
又は0O
)。 BinaryLiterals
拡張子はバイナリ( 0b
または0B
前に付いています)のオプションを追加します。
0b1111 == 15 -- evaluates to: True
ExistentialQuantification
これは存在量化、または、他の言葉で、唯一のランタイム†でインスタンスを取得型の変数を持っているタイプを可能型システムの拡張機能です。
実在型の値は、OO言語の抽象ベースクラスの参照と似ています。つまり、containsの正確な型はわかりませんが、型のクラスを制約できます。
data S = forall a. Show a => S a
GADT構文を使用すると、次のようになります。
{-# LANGUAGE GADTs #-}
data S where
S :: Show a => a -> S
存在するタイプは、ほぼ異種のコンテナのようなものへの扉を開きます:上記のように、実際にはS
値にはさまざまなタイプがありますが、それらはすべてshow
することができます。
instance Show S where
show (S a) = show a -- we rely on (Show a) from the above
このようなオブジェクトのコレクションを作成することができます:
ss = [S 5, S "test", S 3.0]
これはまた、多態的な振る舞いを使うことを可能にします:
mapM_ print ss
存在者は非常に強力ですが、ハスケルではあまり頻繁に必要ではないことに注意してください。上記の例では、 Show
インスタンスで実際に行うことができるのは、値を表示する(つまり、文字列表現を作成する)ことだけです。したがって、 S
タイプ全体は、表示する文字列と同じくらい多くの情報を含んでいます。したがって、通常は、文字列をすぐに格納する方が良いです。特に、Haskellが遅延しているため、文字列は最初は未評価のサンクだけになります。
一方、存在感はいくつかのユニークな問題を引き起こす。例えば、タイプ情報が存在感の中で「隠される」方法。 S
値をパターンマッチさせると、スコープに含まれる型(より正確にはShow
インスタンス)になりますが、この情報はスコープをエスケープすることはできません。そのため、「秘密の社会」の一部になります。コンパイラその型が既に外部から知られている値を除いて、スコープからエスケープすることはできません。これは、 Couldn't match type 'a0' with '()' 'a0' is untouchable
ような奇妙なエラーにつながります。
† 一般的なパラメトリック多形性とは対照的ですが、これは一般にコンパイル時に解決されます(フルタイプの消去が可能です)。
Existential型はRank-N型とは異なります。これらの拡張子は、おおまかに言えば互いに重複しています。実在型の値を実際に使用するには、例のshow
ような(多分束縛された)多型関数が必要です。多型関数は普遍的に定量化されています。つまり、与えられたクラスの任意の型に対して機能しますが、実在の定量化は先験的に未知の特定の型に対して機能します。多{-# LANGUAGE Rank2Types #-}
場合は、引数などの多相関数を渡すのには十分ですが、 {-# LANGUAGE Rank2Types #-}
が必要です。
genShowSs :: (∀ x . Show x => x -> String) -> [S] -> [String]
genShowSs f = map (\(S a) -> f a)
ラムダケース
\arg -> case arg of
代わりに\arg -> case arg of
\case
を書くことを可能にする構文拡張。
次の関数定義を考えてみましょう。
dayOfTheWeek :: Int -> String
dayOfTheWeek 0 = "Sunday"
dayOfTheWeek 1 = "Monday"
dayOfTheWeek 2 = "Tuesday"
dayOfTheWeek 3 = "Wednesday"
dayOfTheWeek 4 = "Thursday"
dayOfTheWeek 5 = "Friday"
dayOfTheWeek 6 = "Saturday"
関数名を繰り返さないようにするには、次のように記述します。
dayOfTheWeek :: Int -> String
dayOfTheWeek i = case i of
0 -> "Sunday"
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
4 -> "Thursday"
5 -> "Friday"
6 -> "Saturday"
LambdaCase拡張を使用すると、引数に名前を付けることなく、関数式として書くことができます:
{-# LANGUAGE LambdaCase #-}
dayOfTheWeek :: Int -> String
dayOfTheWeek = \case
0 -> "Sunday"
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
4 -> "Thursday"
5 -> "Friday"
6 -> "Saturday"
RankNTypes
次のような状況を想像してみてください。
foo :: Show a => (a -> String) -> String -> Int -> IO ()
foo show' string int = do
putStrLn (show' string)
putStrLn (show' int)
ここでは、値をStringに変換し、その関数を文字列パラメータとintパラメータの両方に適用して両方を出力する関数を渡したいと考えています。私の心の中で、これが失敗する理由はありません!私たちは渡している両方の型のパラメータに作用する関数を持っています。
残念ながら、これはタイプチェックを行いません! GHCは、関数本体での最初のオカレンスに基づいてa
型を推定します。すなわち、我々がヒットするとすぐ:
putStrLn (show' string)
GHCはその推測されますshow' :: String -> String
するので、 string
あるString
。それはshow' int
をshow' int
しようとしている間に爆破を進めます。
RankNTypes
では、代わりに、次のように型シグニチャを記述し、 show'
型を満たすすべての関数を定量化できます。
foo :: (forall a. Show a => (a -> String)) -> String -> Int -> IO ()
これはランク2の多型です。私たちは、 show'
機能は、私たちの機能の中のすべてa
のために機能しなければならないと主張しています。
RankNTypes
拡張では、型シグネチャのforall ...
ブロックを任意に入れ子にすることができます。換言すれば、ランクNの多型を可能にする。
オーバーロードリスト
GHC 7.8で追加されました 。
似OverloadedLists、 OverloadedStringsは 、次のようにリストリテラルは脱糖することができます:
[] -- fromListN 0 []
[x] -- fromListN 1 (x : [])
[x .. ] -- fromList (enumFrom x)
これはSet
、 Vector
、 Map
などの型を扱うときに便利です。
['0' .. '9'] :: Set Char
[1 .. 10] :: Vector Int
[("default",0), (k1,v1)] :: Map String Int
['a' .. 'z'] :: Text
GHC.Exts
IsList
クラスは、この拡張で使用するためのものです。
IsList
は、1つのタイプの関数Item
と3つの関数fromList :: [Item l] -> l
、 toList :: l -> [Item l]
とfromListN :: Int -> [Item l] -> l
fromListN
はオプションです。典型的な実装は:
instance IsList [a] where
type Item [a] = a
fromList = id
toList = id
instance (Ord a) => IsList (Set a) where
type Item (Set a) = a
fromList = Set.fromList
toList = Set.toList
機能的な依存性
引数a、b、c、およびxを持つ複数パラメータの型クラスがある場合、この拡張により、型xをa、b、およびcから一意に識別できることを表現できます。
class SomeClass a b c x | a b c -> x where ...
そのようなクラスのインスタンスを宣言すると、他のすべてのインスタンスと照合して、関数の依存関係が保持されていることを確認します。つまり、 abc
は同じでx
は異なるインスタンスは存在しません。
複数の依存関係をコンマ区切りリストで指定できます。
class OtherClass a b c d | a b -> c d, a d -> b where ...
たとえば、MTLでは次のように見えます。
class MonadReader r m| m -> r where ...
instance MonadReader r ((->) r) where ...
あなたがタイプの値を持っている場合今、 MonadReader a ((->) Foo) => a
、コンパイラはそれを推測することができますa ~ Foo
2番目の引数は完全に最初に決定し、以来、それに応じて種類を簡素化します。
SomeClass
クラスは、 x
という結果をもたらす引数abc
関数として考えることができます。そのようなクラスは、型システムにおける計算を行うために使用できます。
GADTs
従来の代数的データ型は、その型変数においてパラメトリックである。たとえば、ADTのように定義すると
data Expr a = IntLit Int
| BoolLit Bool
| If (Expr Bool) (Expr a) (Expr a)
IntLit :: Int -> Expr a
の型がIntLit :: Int -> Expr a
定量化されているため、これは静的に非型付き条件をIntLit :: Int -> Expr a
することを期待していますが、期待どおりに動作しません: a
任意の選択に対してa
Expr a
。具体的には、のためにa ~ Bool
、我々は持っているIntLit :: Int -> Expr Bool
、私たちのようなもの構築することができ、 If (IntLit 1) e1 e2
のタイプのものですIf
コンストラクタは除外しようとしていたが。
一般化された代数的データ型は、データ構築子の結果の型をパラメトリックでないように制御することができます。 Expr
型をGADTとして次のように書き直すことができます:
data Expr a where
IntLit :: Int -> Expr Int
BoolLit :: Bool -> Expr Bool
If :: Expr Bool -> Expr a -> Expr a -> Expr a
ここで、コンストラクタIntLit
型はInt -> Expr Int
であるため、 IntLit 1 :: Expr Bool
は型検査を行いません。
GADT値のパターンマッチングにより、返されたタームのタイプが洗練されます。たとえばExpr a
ようにExpr a
評価Expr a
を書くことができます。
crazyEval :: Expr a -> a
crazyEval (IntLit x) =
-- Here we can use `(+)` because x :: Int
x + 1
crazyEval (BoolLit b) =
-- Here we can use `not` because b :: Bool
not b
crazyEval (If b thn els) =
-- Because b :: Expr Bool, we can use `crazyEval b :: Bool`.
-- Also, because thn :: Expr a and els :: Expr a, we can pass either to
-- the recursive call to `crazyEval` and get an a back
crazyEval $ if crazyEval b then thn else els
我々が使用することができることに注意してください(+)
上記の定義であるためとき例えばIntLit x
マッチしたパターンは、我々はまた、学ぶことであるa ~ Int
(と同様のためnot
とif_then_else_
ときa ~ Bool
)。
ScopedTypeVariables
ScopedTypeVariables
を使用すると、宣言内の普遍的な数値型を参照できます。より明示的にするには:
import Data.Monoid
foo :: forall a b c. (Monoid b, Monoid c) => (a, b, c) -> (b, c) -> (a, b, c)
foo (a, b, c) (b', c') = (a :: a, b'', c'')
where (b'', c'') = (b <> b', c <> c') :: (b, c)
重要なことは、 b
、 b
、 c
を使用しa
、宣言の部分式( where
句のタプルと最終結果の最初のa
でコンパイラに指示できることです。実際には、 ScopedTypeVariables
は複雑な関数を部品の合計として記述するのを支援し、プログラマーは具体的な型を持たない中間値に型シグネチャーを追加することができます。
PatternSynonyms
パターン同義語は、関数が式の抽象化であるのと同様のパターンの抽象化です。
この例では、 Data.Sequence
インターフェイスのインターフェイスを見て、パターンシノニムを使ってData.Sequence
を改善する方法を見てみましょう。 Seq
型は、内部的に、 複雑な表現を使用してさまざまな操作、特にO(1)(un)consingと(un)snocingの両方で良好な漸近的複雑さを達成するデータ型です。
しかし、この表現は扱いにくく、その不変量のいくつかはHaskellの型システムで表現できません。このため、 Seq
型は抽象型としてユーザーに公開され、不変のままのアクセサーとコンストラクター関数も保持されます。
empty :: Seq a
(<|) :: a -> Seq a -> Seq a
data ViewL a = EmptyL | a :< (Seq a)
viewl :: Seq a -> ViewL a
(|>) :: Seq a -> a -> Seq a
data ViewR a = EmptyR | (Seq a) :> a
viewr :: Seq a -> ViewR a
しかし、このインターフェイスを使用するのはちょっと面倒かもしれません:
uncons :: Seq a -> Maybe (a, Seq a)
uncons xs = case viewl xs of
x :< xs' -> Just (x, xs')
EmptyL -> Nothing
私たちはビューパターンを使って幾分それを掃除することができます:
{-# LANGUAGE ViewPatterns #-}
uncons :: Seq a -> Maybe (a, Seq a)
uncons (viewl -> x :< xs) = Just (x, xs)
uncons _ = Nothing
PatternSynonyms
言語拡張を使用すると、パターンマッチングでコンスまたはスヌークリストを持つふりをすることができます。
{-# LANGUAGE PatternSynonyms #-}
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
pattern Empty :: Seq a
pattern Empty <- (Seq.viewl -> Seq.EmptyL)
pattern (:<) :: a -> Seq a -> Seq a
pattern x :< xs <- (Seq.viewl -> x Seq.:< xs)
pattern (:>) :: Seq a -> a -> Seq a
pattern xs :> x <- (Seq.viewr -> xs Seq.:> x)
これにより、非常に自然な形でuncons
を書くことができます:
uncons :: Seq a -> Maybe (a, Seq a)
uncons (x :< xs) = Just (x, xs)
uncons _ = Nothing
RecordWildCards
RecordWildCardsを参照してください