にこ、希と一緒に学ぶHaskell(番外)「あまり知られていないGHC拡張の紹介」
この記事はHaskell (その3) Advent Calendar 2017の 21日目の記事です!
この記事にはSS表現、ラブライブが含まれます。 これらが苦手な方はブラウザバックを推奨します。
Outline
- MultiParamTypeClasses
- ConstrainedClassMethods
- RebindableSyntax
- InstanceSigs
- ImpredicativeTypes
- ExplicitNamespaces
- 参考ページ
前回の型ライブ!
放課後、部室に集まって、よく知られたGHC拡張について語り合う にこっち と うち。
希「GHC拡張っていっぱいあるんやね!」
にこ「Haskell reportに『実験的な機能を取り入れた、この言語の拡張や 変形の出現はのぞむところである。』って書いてあるくらいだしね」
調べれば調べるほど出てくるGHC拡張に対して、改めて全能感を感じる。
でも、Haskellの力はまだまだこんなもんやない。
そんな充実感を抱きながらお風呂から上がると、うちのGoogleHangoutに、謎のビデオ電話がかかってきた!
にこののぞみ
希「はい、もしもし」
にこ「こんばんは、希」
希「にこっちやん。どうしたの?」
にこ「そのね、今日は珍しくママ……じゃなくてっ!」
にこ「お母さんが早く帰ってきてて、チビたちの面倒を見てくれてるから暇なんだけど、
Haskellのリモート勉強会しない?
……なんて。」
希「おっ、ええやん!
うちも実は、今ひまやったんよ」
にこ「そ、そう! じゃ、じゃあ…… 放課後だけじゃあんまり語り尽くせなかったし、 またGHC拡張について話さない?」
希「いいね! 実はさっき面白いページ見つけてな。 知らないGHC拡張がいっぱいあったんよ。 これ見ていくの、どうやろ?」
にこ「ええ、いいわねっ」
MultiParamTypeClasses
希「さっき見つけたのが面白いのがあるんよ」
希「MultiParamTypeClasses
については、にこっち多分知っとると思うんやけど」
にこ「ええ」
希「これって、0個の型に対するインスタンスも定義できるらしいやね」
{-# LANGUAGE MultiParamTypeClasses #-}
class Nullary
instance Nullary
main :: IO ()
main = return ()
にこ「!? 型クラスの受け取る型を、1個以上…… じゃなくて0個以上として一般化する拡張だったのね……」
にこ「これ、何にどうやって使うのかしら」
希「うーん、わからへん……」
ConstrainedClassMethods
にこ「なにこれ、しらないわ」
希「型クラスの関数に、型制約を設けることができるようになるみたいやね」
{-# LANGUAGE ConstrainedClassMethods #-}
class UnitedConvertible a where
toItself :: a -> a
-- ConstrainedClassMethods allows to restrict constraints in the type class function
toString :: Show a => a -> String
main :: IO ()
main = return ()
希「Haskell98は型クラス関数に型制約を設けることを禁止してるらしくって」
希「それの回避策らしいみたい。
Haskell98がそれを禁止してるの、なんでやろな?」
にこ「Haskell reportは実装に言及しないらしいし、実装都合じゃあなさそうね。
なんでかしら」
にこ「ていうか、型クラスの関数って、型制約付けられなかったのね。 型クラス関数に他の型クラスの制約をつけることなんてあまりないものね……」
RebindableSyntax
にこ「名前からして面白そうね」
希「やね!ふむふむこれは……
Haskel reportでは以下の変換規則が規定されているらしいんやけど」
1
↓
Prelude.fromInteger (1 :: Integer)
希「それを以下の変換規則に付け替えるんやって。」
1
↓
fromInteger (1 :: Integer)
にこ「ふむふむ、Preludeの定義を参照はずのものを、ローカルの定義への参照に変えられるのね」
にこ「うわっ、putStrLn 1
式を合法にできた……」
{-# LANGUAGE RebindableSyntax #-}
-- RebindableSyntax turns on NoImplicitPrelude
import Prelude hiding (fromInteger)
-- In the Haskell report specification,
-- a literal `1` is expanded to `Prelude.fromInteger (1 :: Integer)`.
--
-- RebindableSyntax changes it to that is expanded to `fromInteger (1 :: Integer)`,
-- also it means `1`'s convertion is able to rebind.
fromInteger :: Integer -> String
fromInteger _ = ";P"
main :: IO ()
main = putStrLn 1
-- {output}
-- ;P
希「putStrLn 1
、字面がすごいわ……。
ああこれ、NoImplicitPrelude
も有効になるんね」
希「他にはこんな変換規則を付け替えられるみたいやね」
252.52 |
Prelude.fromRational (252.52 :: Rational) |
x == y |
x Prelude.== y |
x - y |
x Prelude.- y |
x >= y |
x Prelude.>= y |
- x |
negate x |
if x then y else z |
ifThenElse x y z |
#nozo (if OverloadedLabels is used) |
fromLabel @ "nozo" |
リスト色々 | OverloadedLists - GHC User's Guide |
希「あとはdo
, mdo
, リスト内包記とArrows
についても変換規則があるみたいやけど、明記はされてないんね。
do
は多分こんな感じかなあ。
本当は多分パターンマッチまで考慮してそうやね」
do
x' <- x
y
z
↓
x Prelude.>>= \x' ->
y Prelude.>> z
NoImplicitPrelude(補足)
にこ・希(読んでの通り、暗黙的にimport Prelude
を行わないための拡張。
これを指定しない限り、GHCは暗黙的にそれを行う)
InstanceSigs
にこ「これは、インスタンスを定義するときに、型クラスの関数の具体的シグネチャをインスタンス側で書けるやつね」
希「さすがにこっちやね、知っとったか」
{-# LANGUAGE InstanceSigs #-}
class Identical a where
id' :: a -> a
instance Identical Int where
-- InstanceSigs allows this type declaration
id' :: Int -> Int
id' = id
main :: IO ()
main = return ()
にこ「ええ。
インスタンス側の型クラス関数が、そのときに使いたい型になっているか不安なときに使ったりするわ。
ScopedTypeVariables
と一緒に使うと、より厳格に型検査をしてもらえるのよね」
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE ScopedTypeVariables #-}
newtype List' a = List'
{ unList' :: [a]
}
-- ランダムなリストを生成するインスタンス
instance (Bounded a, Random a) => Random (List' a) where
randomR :: forall g. RandomGen g => (List' a, List' a) -> g -> (List' a, g)
randomR (List' xs, List' ys) gen =
let randomR' :: a -> a -> (a, g) -- 意図通りの関数を書けてるか、型検査
randomR' = curry $ flip randomR gen
zs' :: [(a, g)] -- 意図通りの結果を受け取れてるか、型検査
zs' = zipWith randomR' xs ys
in ...
にこ「でも、一度コンパイルが通ったら消しちゃうことも少なくないわ…… コンパイル速度が落ちそうな気がしちゃって」
希「どうなんやろな?」
ImpredicativeTypes
にこ「非可述的な……型々?」
希「なんやろこれ??」
にこ「こういう時はここを見るのが一番ね。 あったあった、GHC Wikiページ!」
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE RankNTypes #-}
f :: Bool -> b -> b
f = const id
g :: (forall a. a -> a) -> Int
g = undefined --const 10
foo :: Bool -> Int
foo x = g $ f x
--foo' :: Bool -> Int
--foo' = g . f
main :: IO ()
main = return ()
にこ「なるほど、うーん。
多分、f
の型変数b
と、g
のランク2多相の型変数a
が、同一のものとして具体化できるか
というのが問題っぽいわね」
希「ImpredicativeTypes
を使うと、こんな感じの型変数x
が決定できる?」
f
Bool --> (x -> x)
\ |
\ | g
\ v
> Int
にこ「うーん、そうっぽい?」
希「うーん」
...
にこ「g
の実定義をg = const 10
にしたら、
ImpredicativeTypes
を外してもコンパイルが通るわ」
にこ「逆にfoo'
をコメントインすると、ImpredicativeTypes
を有効にしていてもコンパイルが通らないわ」
希「うーん、さらにいろいろ機能があるみたいやね」
希「ここが参考になりそうやん」
- 10.1. Language options - Glasgow Haskell Compiler 8.2.2 User's Guide
- Boxy type inference for higher-rank types and impredicativity - Microsoft Research
ExplicitNamespaces
にこ「ああ、型演算子を作った時に、
importとmoduleでのエクスポートでtype
って書けるのは
この子のおかげだったのね」
Helper.hs
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module Helper
( type (-<|)
, (-<|)
) where
type family (-<|) a b where
x -<| _ = x
(-<|) :: a -> b -> a
(-<|) = const
Main.hs
{-# LANGUAGE ExplicitNamespaces #-}
-- `ExplicitNamespaces` allows 'type' keyword in import/export
import Helper (type (-<|), (-<|))
x :: (-<|) Int Char
x = 10 -<| 'a'
main :: IO ()
main = return ()
希「このtype
ってなんなん?」
にこ「このHelper.hsで、(-<|)
は型レベルと値レベルのどちらでも定義されてるでしょ?」
にこ「それだとエクスポート時にmodule Helper ((-<|)) where
って書いたときと、
import時にimport Helper ((-<|))
って書いたとき」
にこ「それぞれどちらをインポート、エクスポートしたらいいかわからないわよね」
にこ「だからtype
を指定したときには、型レベルの方を表すの」
希「なるほどな〜。
デフォルトで値レベルの(-<|) :: a -> b -> a
を、
type
を指定したときには型レベルの(-<|)
を扱うって感じやんな!」
夜更け
希「はぁ〜ふぅ」あくび
にこ(……もう24時ね)
にこ「眠い?」
希「うーん、ちょっと眠くなってもうたわ」
にこ「明日眠くなって調子でなかったら大変だし、今日はもう寝ましょうか」
希「せやね〜」
希「楽しかったよ〜。
普段はにこっちから『遊ぼう』って言ってくれることなんてないし」
にこ「悪かったわね。
アイドルに子育てにHaskell。
にこは忙しいのよ」
にこ「でも今日はありがとう、私も楽しかったわ」
希「うんん、いつでも呼んでな。
うち、Haskellerとしてのにこっちのことも、尊敬してるんよ」
にこ「……//」