質問: data, newtype のパターンマッチの動作の違いについて

314 views
Skip to first unread message

happyman

unread,
Jul 16, 2012, 7:49:33 AM7/16/12
to haske...@googlegroups.com

皆様、いつも的確なアドバイスありがとうございます。

著書「すごいHaskell楽しく学ぼう!」を読んでいて、
また、些細な点が気になりましたので、質問させて頂きます。

P.262(12.1 既存の型を新しい型にくるむ)では、

    helloMe :: CoolBool -> String
    helloMe (CoolBool _) = "hello"

という関数をもとに、data と newtype キーワードの動作の違いについて説明しています。

■ dataキーワードで CoolBool 型を定義した場合

    data CoolBool = CoolBool { getCoolBool :: Bool }

    <実行結果>

    ghci> helloMe undefined
    "*** Exception: Prelude.undefined

■ newtypeキーワードで CoolBool 型を定義した場合

    newtype CoolBool = CoolBool { getCoolBool :: Bool }

    <実行結果>

    *Main> helloMe undefined
    "hello"

この違いについては、

> そして Haskell は、newtype キーワードはコンストラクタを1つしか作れないと
> 知っているので、helloMe関数の引数を評価することなく、引数が (CoolBool _)
> パラメータに合致すると判定できます。

と説明しています。

しかし、data キーワードを使う場合でも 、コンパイラは、helloMeをコンパイルするとき、
CoolBool 型のコンストラクタをチェックしているので、
それが1引数のコンストラクタを1つしか持たない、ことを知っているはずです。
それなら newtype と同じように動作させることが可能ではないでしょうか。
data キーワードで定義されたコンストラクタのパターンマッチは、
コンパイル時に候補がひとつしかなければ、それとマッチングする、という仕様にしておけば、
dataキーワードとnewtypeキーワードの動作に違いが出ず、仕様が簡単になるような気がします。
何か誤解に基づいた考えかもしれませんが、どなたかご意見をお聞かせ頂けたらと思います。


hiratara さん、

> http://d.hatena.ne.jp/hirataraSideB/20090606/p1
> dataとnewtypeのちがいのわかりやすい例が消えている。本当に困っている。

dataとnewtypeのちがいのわかりやすい例
http://web.archive.org/web/20070122231856/http://haskell.g.hatena.ne.jp/jmk/20061203

こちらで読めました。

Honma Masahiro

unread,
Jul 16, 2012, 9:08:17 AM7/16/12
to haske...@googlegroups.com
hirataraです。

> という関数をもとに、data と newtype キーワードの動作の違いについて説明しています。

http://www.haskell.org/onlinereport/decls.html#sect4.2.3

の辺りにありますが、newtypeは「コンストラクタが1つしか定義できないdata」
というよりは、既存の型の完全な別名(renaming types)を作る機能ですので、
パターンマッチするという発想がそもそもないってことだと思います。
おっしゃるとおりdataのコンストラクタが1つの場合だけ特別扱いする
動作にもできるでしょうが、この方が仕様に一貫性がなくなるでしょう。
newtypeは要らないのでは、と言う指摘であればある程度わかりますが、
仕様として存在している以上そこを議論しても仕方ないと思います。

なお、すごいHaskellは読んでないのですが(すみません)、パターンマッチに
~をつければ遅延パターンになるので、newtypeと同様の振る舞いになります。
(今回の質問とあまり関連はない気はしますが・・・)

> こちらで読めました。

うわ、本人もこんなの書いたの覚えてませんでした(笑)。情報ありがとうございます。


2012年7月16日 20:49 happyman <happy.lov...@gmail.com>:

Kazu Yamamoto

unread,
Jul 16, 2012, 9:50:44 PM7/16/12
to haske...@googlegroups.com
山本です。


> それが1引数のコンストラクタを1つしか持たない、
> ことを知っているはずです。
> それなら newtype と同じように動作させることが可能ではないでしょうか。

そういう議論は以前あったと思いますが、どういう結論が出たか覚えていません。

newtype は、いかにもプログラマーに最適化を押し付けている仕様なので、なくなるのが理想的ですね。

--かず

2012/7/16 happyman <happy.lov...@gmail.com>

happyman

unread,
Jul 17, 2012, 6:52:19 AM7/17/12
to haske...@googlegroups.com

hirataraさん、Kazu さん、

お二方とも、早々のご回答有難うございました。

hiratara wrote:
> 仕様として存在している以上そこを議論しても仕方ないと思います。
 
仕様の妥当性を確認したいという気持ちで質問させて頂きましたが、
もともと、次の記述について確認したかったからです。
 
すごいHaskell楽しく学ぼう! wrote:

> そして Haskell は、newtype キーワードはコンストラクタを1つしか作れないと
> 知っているので、helloMe関数の引数を評価することなく、引数が (CoolBool _)
> パラメータに合致すると判定できます。

私の考え方が可能なら、この説明は、
newtype を使った場合の helloMe undefined が評価できる理由となっているが
data を使用した場合の helloMe undefined が評価できない理由にはなっていない、
ように思います。
 

hiratara wrote:
> おっしゃるとおりdataのコンストラクタが1つの場合だけ特別扱いする
> 動作にもできるでしょうが、

と回答頂いたお陰で、この辺のモヤモヤは晴れました。

なお、

> http://www.haskell.org/onlinereport/decls.html#sect4.2.3
> 日本語 http://www.sampou.org/haskell/report-revised-j/decls.html#sect4.2.3
> 4.2.3 Datatype Renamings
> ...
> Unlike algebraic datatypes, the newtype constructor N is unlifted, so that N _|_ is the same as _|_.

を確認すると newtype はリフトされないようですが、これに対して、
代数的データ型が、持ち上げられている(lifted)というのは、
意味論の重要なポイントのようでした。(まだよく理解してませんが)
 

Haskell/Denotational semantics ボトムと部分関数 ⊥ ボトム(Bottom)
http://ja.wikibooks.org/wiki/Haskell/Denotational_semantics#.E3.83.9C.E3.83.88.E3.83.A0.E3.81.A8.E9.83.A8.E5.88.86.E9.96.A2.E6.95.B0

Kazu wrote:
> そういう議論は以前あったと思いますが、どういう結論が出たか覚えていません。

少し気になります。

happyman

unread,
Jul 17, 2012, 6:55:22 AM7/17/12
to haske...@googlegroups.com

(続き)
newtype の存在意義について、

hiratara wrote:
> newtypeは要らないのでは、と言う指摘であればある程度わかりますが、

Kazu wrote:
> newtype は、いかにもプログラマーに最適化を押し付けている仕様なので、なくなるのが理想的ですね。

お二方以外にも、酒井さんのブログに

λ. newtypeはHaskellの仕様に不要では?
http://msakai.jp/d/?date=20061206#p01

と書かれています。

確認させて頂きたいのですが、

・外部モジュールで型シグネチャに直接書けないようにする。例えば World -> (a, World) など。
・型クラスの複数のインスタンスを定義する。
 

という用途には、data で十分ということですよね。

ところが、当の酒井さんが、先日、nobsunさんからご紹介頂いた記事のコメントに
 
> http://haskell.g.hatena.ne.jp/nobsun/20060907#c1157826957
> 酒井 2006/09/10 03:35
> 些細かつ非本質的な点ですが、Actionの定義にはnewtypeを使った方が良いと思います。
 
とコメントを残されているので混乱しました。
結局のところ、data より newtype が優れているのは効率だけと言えそうですか。

Masahiro Sakai

unread,
Jul 18, 2012, 12:04:13 AM7/18/12
to haske...@googlegroups.com
酒井です。

2012年7月17日火曜日 19時55分22秒 UTC+9 happyman:
> (続き)
> newtype の存在意義について、
> hiratara wrote:
> > newtypeは要らないのでは、と言う指摘であればある程度わかりますが、 
> Kazu wrote:
> > newtype は、いかにもプログラマーに最適化を押し付けている仕様なので、なくなるのが理想的ですね。
> お二方以外にも、酒井さんのブログに
> λ. newtypeはHaskellの仕様に不要では?
> と書かれています。

値の集合を考えると、newtypeで定義したデータ型(例: newtype T1 = T1 Int)は
正格性フラグ付きの1引数の単一コンストラクタからなるdataで定義するデータ型
(例: data T2 = T2 !Int)と同等で、上の記事に書いたようにnewtypeは原理的には
不要です。(data T3 = T3 Int は同等ではないので注意)

ただ、newtypeを単純に無くしてしまって、同等なdata宣言を用いることにすると、
newtypeと同じ効果を実現するために、正格性フラグや遅延パターンなど、他の
ややこしい機能を利用しなくてはならなくなってしまいます。

これはhirataraさんの書いている「既存の型の完全な別名をつける」という典型的
なユースケースに比べて複雑すぎるので、最近は単純なことを単純にすませるため
の妥協として、newtypeはあっても良いと思っています。

> 確認させて頂きたいのですが、
> ・外部モジュールで型シグネチャに直接書けないようにする。例えば World -> (a, World) など。
> ・型クラスの複数のインスタンスを定義する。
> という用途には、data で十分ということですよね。

上の例でいうと、T1やT2ではそれぞれ T1 ⊥ = ⊥, T2 ⊥ = ⊥ なのに対して、
T3 ⊥ ≠ ⊥ という違いはあります。
(型T1,T2がリフトされていないのに対して、型T3はリフトされている)

が、そういった違いと効率を除けば data で充分です。

> ところが、当の酒井さんが、先日、nobsunさんからご紹介頂いた記事のコメントに
> > 酒井 2006/09/10 03:35
> > 些細かつ非本質的な点ですが、Actionの定義にはnewtypeを使った方が良いと思います。
> とコメントを残されているので混乱しました。
> 結局のところ、data より newtype が優れているのは効率だけと言えそうですか。

ここでnewtypeを使った方が良いと書いたのは、効率もありますが、
不要なリフトが気持ち悪いという理由もありました。
後者の問題は、newtypeの代わりにdataと正格性フラグを使っても解決できます。

-- 酒井 政裕

happyman

unread,
Jul 18, 2012, 7:56:07 AM7/18/12
to haske...@googlegroups.com

酒井さん、

丁寧なご説明ありがとうございました。
よく理解できました。

> 不要なリフトが気持ち悪いという理由もありました。

なるほど、納得しました。
正格フラグを使わず、data キーワードで元の型を包むとき場合、
元の型がリフトされる、即ち、値集合が変わる、ということですね。

data ではなく newtype で型を包む理由として、
効率が挙げられることがありますが、
こちらの方が、より本質的な理由に思えます。

Hiromi ISHII

unread,
Jul 18, 2012, 8:07:12 AM7/18/12
to haske...@googlegroups.com
いしいです。

GHC では GeneralizedNewtypeDeriving 拡張を使うことで、 newtype に包む前の型インスタンスを自動的に包んだ後の型に延長することが出来ます。

例えば、通常 Num クラスを導出することは出来ませんが、この拡張を使えば、

> {-# LANGUAGE GeneralizedNewtypeDeriving #-}
> newtype MyInt = MyInt { unMyInt :: Int }
> deriving (Show, Eq, Ord, Num)

このように、Int のインスタンス情報を基に、そのまま Num のインスタンスを導出させることが出来ます。

正し、Show, Eq, Ord などは通常の導出と同じ方法で導出されるので、
> show (12 :: MyInt) == "MyInt {unMyInt = 12}"
と云う結果になります。

他にも:

> newtype Machine a = Machine { runMachine :: StateT Int IO a }
> deriving (Functor, Monad, MonadIO)

といったような事も出来ます。
こういったことは、newtype の内部表現が元の型と一致しているから出来ることだと思います。

# 勿論、一つしか型引数を持たないような data 宣言に対してだけ導出出来るようにする、という選択も考えられますが、ちょっと非対称かなあ、と思います。
# 他の Deriving 機能とややこしくなってしまいそうですし。

あと、newtype 宣言は元の同じ性質を持ちつつ、少し違うような性質(Maybe に対する First や Last モノイドなど)を付け足したかったり、既存のインスタンスとは異なるインスタンスを付け足したい時に使う、という感じです。
殆んど別名みたいなもの、というタグ、と云うか。

----- 石井 大海 ---------------------------
konn....@gmail.com
早稲田大学基幹理工学部
数学科 三年
----------------------------------------------

happyman

unread,
Jul 18, 2012, 8:48:03 AM7/18/12
to haske...@googlegroups.com

いしいさん、

> GHC では GeneralizedNewtypeDeriving 拡張を使うことで、 newtype に包む前の型インスタンスを自動的に包んだ後の型に延長することが出来ます。

そうか! そうですね。
GeneralizedNewtypeDeriving 拡張は RWH で目にしましたが、
newtype のメリットとして認識してませんでした。
情報ありがとうございました。

Reply all
Reply to author
Forward
0 new messages