ラベル 型クラス の投稿を表示しています。 すべての投稿を表示
ラベル 型クラス の投稿を表示しています。 すべての投稿を表示

2010年1月7日木曜日

if と真偽値

Java の if 文での検査は真偽値のみ

最初に馴染んだ言語が Java だったので、「if 文での検査で使えるのは当然 true または false でしょ」という感覚が染み込んでいる。例えば、Java で

if (0){
 ...
}

をコンパイルしようとすると、

Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - 互換性のない型
  期待値: boolean
  検出値:    int

というエラーが表示される。

Java言語規定 ブロック及び文 の 「14.8 if 文」によると、

if ( Expression ) Statement

… 式(Expression) は,論理型をもたなければならない。そうでなければ,コンパイル時エラーが発生する。

(太字は引用者による)

という仕様であるため。

 

C 言語 の if 文での検査は 0 が 偽

しかし、C 言語では上記のコードでもコンパイルが通る。なぜなら、初級C言語Q&A(5) によると、

Q 【真偽値】

他の言語には真偽の値を表現するための型が用意されているものがあるが、なぜC言語には用意されていないのか。

… 最も大きな理由は、それがなくても実用上差し支えないからでしょう。実際、1という値を真、0という値を偽であることにすれば、整数型を使って真偽を表現することが可能ですから。

この `0’  を偽 と見なすのは最初違和感があった。。 (@_@;

数値の比較をしてみると、

1 == 1

で 1 が返り、

1 < 1

で 0 が返る。

この仕様だと、自分でブール型を用意しなければ、真偽値を返すことを意図した関数と、数値を返す関数をコンパイラが区別できない。 (+_+)

ただし、プログラミング言語 C の新機能 によると C99 で、

今度の C 言語では新しい整数型 _Bool 型を導入することでその問題を解決します。この型は 0 と 1 が入れば十分な大きさとされており、必ずしも int 型と同じサイズであるとは限りません。

となり、型的にはスッキリとする。

 

Python, Ruby の真偽テスト

Python はたくさんの値が 偽 と解釈される

Python は 2.3.1 真値テスト で述べられているように、たくさんの型の値が条件文で 偽 として扱われる。例えば、リストと文字列の上位にシーケンス型があり、空文字・空リスト

”” , []

は 偽 。(これにより、if の検査で リストと文字列を透過的に扱う ことができる。)

これを「真偽テスト」というのは言い過ぎでないの?ってくらい多い。 ^^; バグを誘発しそうで怖い。。

 

Ruby は false に加えて nil のみ

Ruby はこれに対して、false に加え nil が偽と判定されるのみ。 Python 比べたらスッキリしている。ただし、Java で

if (null) {

なんて書いたらエラーになるので、これもやや違和感を覚えた。

しかし、Ruby で上記のような書き方は実際に多用されているのだろうか?いや、そもそも多用されていたら Null Object パターン に持ちこむか。 JavaScript では DOM の操作でよく見るけれど。

 

JavaScript の falsy

4873113911JavaScript の場合は、

以下に、条件式が falsy, つまり偽と評価される値を示す。

  • false
  • null
  • undefined
  • 空文字
  • 数値の 0
  • 数値の NaN

(JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス, p.14 より)

false ならぬ falsy という単語で述べられているように、Python に負けず劣らず 偽 と解釈される値が多い。 (@_@;

(ちなみに、最初 JavaScript 見て嫌だなと思ったのがこの真偽テストに絡んでいる。「え?この if で何を確認してるの?」と、馴染まない内はイラつかされるオブジェクトの存否チェック、そしてネストネストでズラズラズラ…。あ~、どこに本質的な仕事が書かれているんだろう?とコードの森で迷い。。)

総じて Script 系の言語は、さっと書くという利便性のために、「どこまでを if の検査で 偽 と見なすか」について設計思想が違うということかな。

 

型とは

4873112753話変わって、「型」とは何だっただろうか? 以前 にも引用したが 「データベース実践講義」 の 「2.3 型とは」 (p34) によると、

型とはいったい何か。基本的には、値の名前付き有限集合である。…

すべての型が、その型の値もしくは変数に作用する演算子の連想集合を持つ …

とのこと。

ある値の集合が型であり、その型に属する値に対して適用できる関数や操作が定義されるという見方。

 

Java のプリミティブ型としての boolean

Java に戻り、真偽値について確認してみると、

boolean は,リテラル true 及び false (3.10.3) で示す二つの可能な値をもつ論理的な量を表す。

(Java言語規定 型,値及び変数 の 4.2.5 boolean型 及び boolean値 より)

値の集合が定義され、適用できる論理演算子が言語仕様として示されている。ただし、プリミティブ型である true, false が Java 言語で書ける枠内で、型に属する値として定義されているわけでない。 &&, || のような論理演算子についても同様。 Java を使う側は概念的イメージとして boolean 型を意識し、それに即してコードを書くとコンパイラが型をチェックしてくれる。

 

C の真偽は数値

前述の通り、C言語では C99 より前は真偽値に対応する型は用意されてなかった。真偽値として見なしたい値は数値なので、数値に対する演算子が適用できてしまう。 boolean 型として扱うとプログラマが意識したところで、コンパイラにとっては区別不能。

 

Ruby の TrueClass , FalseClass

Ruby では、

Ruby の面白いのは、Boolean というクラスは無く、 true と false はそれぞれ TrueClass と FalseClass というクラスのインスタンスである。

(「各言語におけるtrue/falseまとめ - 床のトルストイ、ゲイとするとのこと」 より)

これ読むまで、てっきり Boolean というクラスがあると思っていた。 ^^;

確認のため TrueClass のスーパークラスとインクルードしているモジュールを表示させてみると、

p TrueClass.ancestors   #=> [TrueClass, Object, Kernel, BasicObject]

となり、true と false だけをまとめる型は定義されていない。つまり、Ruby の言語の枠内でブール型というものが独立して明示されていない。だから、 and や or のようなものが、

再定義できない演算子(制御構造)

(演算子式 - Rubyリファレンスマニュアル より)

として、何となく微妙な感じの位置付けになっているのかな?

 

Python の真偽は数値のサブタイプ

ついでなので Python についても調べたら、 3.2 標準型の階層

ブール型 (boolean)

… ブール型は整数のサブタイプで、ほとんどの演算コンテキストにおいてブール型値はそれぞれ 0 または 1 のように振舞います。ただし、文字列に変換されたときのみ、それぞれ文字列 "False" および "True" が返されます。

うげぇ~ (@_@; 整数のサブタイプだったとは。。

ということは、次の計算がエラーとならない。

print True + True + False #=> 2
print True * 10           #=> 10

上記のような計算を成立させることの積極的な意味は何だろう???

ともあれ、`0’ が if 文において 偽 と解釈される理由はこういう設計に依るのかも。どうしても ごちゃごちゃしているという感が否めない。 (+_+)

PEP 285 -- Adding a bool type では次のように述べられている。、

There's a small but vocal minority that would prefer to see
"textbook" bools that don't support arithmetic operations at
all, but most reviewers agree with me that bools should always
allow arithmetic operations.

6) Should bool inherit from int?

=> Yes.

ということで、この設計はこのままずっと行くみたい。

 

Haskell の Bool 型

これに比べて Haskell はシンプル。 Data.Bool に、型とその値の集合が次のように定義されており、

data Bool  = False  | True

Bool 型に適用できる関数が示されている。

(&&) :: Bool -> Bool -> Bool
(||) :: Bool -> Bool -> Bool
not  :: Bool -> Bool

そして、条件式 である if – then – else の検査では Bool 型しか許されない。 case 式と同等なものとして以下のことが成り立つようになっている。

if e 1 then e 2 else e 3 = case e 1 of { True -> e 2 ; False -> e 3 }

( The Haskell 98 Report: Expressions  3.6 Conditionals より)

先ほどの 「型とは何か?」 という視点から見ると、なんてスッキリとしているんだろう。 ^^ 

… とは言ったものの、最初は「何でこんなものが普通の関数と同じように定義されているの?」 と思ったけれど。 ^^;

 

型ごとに if の解釈を変えたいならオーバーロードで

もし、Python のように Bool 型に加え、空リスト、空文字、0 で False としたいなら、if-then-else とは異なる if’ 関数を定義し、それぞれの型でオーバーロードするのがいいかも。

if を関数で定義するとは、表計算で、

if (条件, 真の場合, 偽の場合)

のように使うときのイメージで。 (cf. Data.Bool.HT の if’ 関数)

 

まずは、値に適用したら Bool 型を返す isTrue 関数を持つ、型クラス Boolean を定義。

class Boolean b where
    isTrue :: b -> Bool

Bool, Integer, [a] 型を、型クラス Boolean のインスタンスにして、それぞれの型に応じた True , False を定義。

instance Boolean Bool where
    isTrue True  = True
    isTrue False = False

instance Boolean Integer where
    isTrue 0 = False
    isTrue _ = True

instance Boolean [a] where
    isTrue [] = False
    isTrue _  = True

これを使って if’ 関数を定義。 x の値が 真 と見なした値の場合 e1 が評価され、偽 の場合 e2 が評価される。

if' x e1 e2 = if isTrue x then e1 else e2

 

試しに使ってみる。数値の場合、

*Main> if' 0 "hoge" "piyo"
"piyo"
*Main> if' 1 "hoge" "piyo"
"hoge"

リストの場合、

*Main> if' [] "hoge" "piyo"
"piyo"
*Main> if' [1,2,3] "hoge" "piyo"
"hoge"
*Main> 

リストで定義しているので、文字列の場合でもちゃんと動作する。

*Main> if' "" "hoge" "piyo"
"piyo"
*Main> if' "test" "hoge" "piyo"
"hoge"

 

if’ を適用できる型を忘れてしまっても :i で調べられる

上記の if’ 関数の型を調べると、

*Main> :i if'
if' :: (Boolean b) => b -> t -> t -> t

もし、if’ 関数は何の型を 真・偽 と見なすか忘れてしまっても、 :i で 型クラス Boolean のインスタンスを表示できる。

*Main> :i Boolean
class Boolean b where t :: b -> Bool
instance Boolean Bool
instance Boolean Integer
instance Boolean [a]

 

if の解釈は所与のものであってほしくない

上記のように if – then – else は ブール型の検査のみで、それ以外の値を 真・偽 と見なして評価したいなら別腹で定義する方が好み。これなら評価の方法を自分の好きなように任意の型で定義できる。

Python でも同じように、クラスごとに真偽テストの挙動をカスタマイズできる。

以下の値は偽であると見なされます …

__nonzero__() または __len__() メソッドが定義されているようなユーザ定義クラスのインスタンスで、それらのメソッドが整数値ゼロまたは bool 値の False を返すとき

(2.3.1 真値テスト より)

(このメソッド名を見ても、あくまでも bool は int のサブタイプだぞ、覚えておけよ ! という気迫 を感じる。 ^^;)

結局のところ、if で型ごとに 真偽 が異なることは、オーバーロードのことだと解釈できる。これを言語が所与のものしてしまうと、「一体それはどういう訳なんだろう?」と疑問がわき、頭の中がごちゃごちゃになり、すぐに忘れるこの脳みそ。 (+_+)

 

Scheme

最近ちょっといじりはじめた Scheme ではどうなっているか調べたら、Structure and Interpretation of Computer Programs によると、

17 ``Interpreted as either true or false'' means this: In Scheme, there are two distinguished values that are denoted by the constants #t and #f . When the interpreter checks a predicate's value, it interprets #f as false. Any other value is treated as true. (Thus, providing #t is logically unnecessary, but it is convenient.)

真偽は #t, #f の定数として定義され、#f のみが偽で、それ以外は真。 #t は実質的にはいらないと。

うーーーん (@_@; 違和感を感じるなぁ~。

 

関連記事

 

参考サイト

2009年5月18日月曜日

Haskell のモジュールの階層化と、型クラス - パラメータ多相とアドホック多相

0. 目次

  • 1. 同じ名前のフィールドラベルを持つ型を定義したい
  • 2. モジュールを分割し、階層化する
  • 3. 型の意味
  • 4. 型クラスの役割
  • 5. パラメータ多相と、アドホック多相
  • 6. 多相性とオブジェクト指向
  • 7. 演算子の意味
  • 8. 継承とジェネリクス
  • 9. Ad hoc の意味
  • 10. 型クラスの定義と、インスタンス化
  • 11. 余談: モジュールに分けない場合

 

1. 同じ名前のフィールドラベルを持つ型を定義したい

2 つの型が、類似している場合、フィールド名に同じ名前を使いたい。

例えば、「名前」と「年齢」を持つ、「犬」型を定義する。

このとき、フィールドラベルを使うなら、

data Dog = Dog {name :: String, age :: Int} deriving Show

「犬」型と同様のフィールドを持つ、「猫」型も定義。

data Cat = Cat {name :: String, age :: Int} deriving Show

しかし、同一モジュール (ここでは Main モジュール) で、同じフィールド名を持つ型を定義すると、エラーが発生する。 (+_+)

Multiple declarations of `Main.name'
...
Multiple declarations of `Main.age'
...

理由は、3.15 フィールドラベルをもつデータ型3.15.1 フィールドの選択 によると、、

フィールド名は選択子関数として使用する。変数として使用するときは、 フィールド名はオブジェクトからそのフィールドを取り出す関数として働く。 選択子はトップレベルの束縛なので局所変数によって覆い隠される。しかし、 同じ名前の他のトップレベルの束縛とは衝突することは出来ない。…

(太字は引用者による)

フィールド名は、単なる名前ではなく、フィールドの値を返す関数。名前空間は、トップレベルに所属するので、代数的データ型の中に書いているように見えても、バッティングに注意が必要ということ。

つまり、以下のように、フィールドラベルは「関数」となるので、同じ名前の関数を二つ作れない。

*Main> name $ Dog "pochi" 3
"pochi"

 

2. モジュールを分割し、階層化する

では、同じフィールド名を持つ、異なる型を定義したい場合、どうすればいいのだろう?

2.2.1. Modules vs. filenames によると、

How does GHC find the filename which contains module M? Answer: it looks for the file M.hs, or M.lhs.

GHC の場合、各々の型を別モジュールに定義し、別ファイルに含めるということ。

つまり、先ほどの例の場合、「犬」と「猫」型に対応したモジュールを作成し、それぞれのファイルに記述すれば良い。

説明に従い、最初に、「犬」型を ファイル Dog.hs に定義。

module Dog where
    data Dog = Dog {name :: String, age :: Int} deriving Show

ところで、GHC ではモジュールを階層化できる。

The Glorious Glasgow Haskell Compilation System User's Guide, Version 6.10.25.6.1. Haskell source files によると、

Usually, the file should be named after the module name, replacing dots in the module name by directory separators. For example, on a Unix system, the module A.B.C should be placed in the file A/B/C.hs, relative to some base directory.

モジュールのベースとなるディレクトリを想定し、そこからの相対位置で、モジュール名が決まる。モジュール名は、モジュールを配置したディレクトリの階層に対応させ、`.’ により階層化していることを示すということ。

例えば、先ほどの「猫」型をやめ、「三毛猫」型を Cat 階層に作りたい。この場合、

  1. Cat ディレクトリを作成し、
  2. 下記のモジュールを Mike.hs に記述し、Cat ディレクトリに配置。
  3. その際、モジュール名は、モジュールを配置したディレクトリに対応させるために Cat.Mike とする。
module Cat.Mike where
    data Mike = Mike {name :: String, age :: Int}

全体では、以下のようにファイルを配置する。

Dog.hs
Cat
-- Mike.hs

ただし、メインモジュールにおいて、フィールドラベルを用いて「名前」を表示したい場合、関数名 (フィールドラベル) をモジュール名で修飾しなくてはならない。

import Dog
import Cat.Mike
main = do print $ Dog.name $ Dog "pochi" 3
          print $ Cat.Mike.name $ Mike "tama" 2

できることなら、関数を適用するとき、モジュール名で修飾せず、シンプルに name と書きたい。

そのためには、「型クラス」を使い、関数のオーバーロードを行う必要がある。

 

3. 型の意味

その前に、「型」について整理しておく。

データベース実践講義」の「2.3 型とは」(p34) によると、

型とはいったい何か。基本的には、値の名前付き有限集合である。…

すべての型が、その型の値もしくは変数に作用する演算子の連想集合を持つ …

Data type - Wikipedia, the free encyclopedia には、

In a broad sense, a data type defines a set of values and the allowable operations on those values.

つまり、「型」とは

  1. 値の集合と、
  2. その値に対する、操作が定義されたもの。

上記より、オブジェクト指向における「クラス」を連想した。なぜなら、

  1. 「値」に相当するインスタンスと、
  2. 「操作」に相当するメソッドを持つため。

Data type - Wikipedia には、型 (データ型) と呼ばれるものには、いくつか種類があることが示されている。

上記データ型の違いの一例を挙げると、オブジェクト型では、内部状態を持つのに対して、Haskell のような代数的データ型では、値の集合を定義するのみで、操作を定義する場合、別に関数定義する。

 

4. 型クラスの役割

では、「型クラス」とは何か?

Type class - Wikipedia によると、

Type classes first appeared in the Haskell programming language, and were originally conceived as a way of implementing overloaded arithmetic and equality operators in a principled fashion.

 A Gentle Introduction to Haskell: Classes には、

There is one final feature of Haskell's type system that sets it apart from other programming languages. The kind of polymorphism that we have talked about so far is commonly called parametric polymorphism. There is another kind called ad hoc polymorphism, better known as overloading.

Here are some examples of ad hoc polymorphism:

  • The literals 1, 2, etc. are often used to represent both fixed and arbitrary precision integers.
  • Numeric operators such as + are often defined to work on many different kinds of numbers.
  • The equality operator (== in Haskell) usually works on numbers and many other (but not all) types.

(太字は引用者による)

型クラスは、Haskell の特徴の一つで、Haskell で初めて導入された。多相性には、パラメータ多相と、アドホック多相があり、後者はオーバーロードと呼ばれる。

型クラス(Type class - Wikipedia) の説明に戻る。

a type class is a type system construct that supports ad-hoc polymorphism. This is achieved by adding constraints to type variables in parametrically polymorphic types. Such a constraint typically involves a type class T and a type variable a, and means that a can only be instantiated to a type whose members support the overloaded operations associated with T.

090513-002.png上記について、「Haskell の代数的データ型と型クラス、instance 宣言の関係」で使い方を確認した。しかし、そのとき、代数的データ型と、型クラスの関係が理解しにくく、知識として定着しなかった。

オブジェクト指向におけるオーバーロード、オーバーライドには馴染みがある。しかし、パラメータ多相、アドホック多相、それに加えて、型クラスがどう絡んでいるのかイメージがしずらい。(+_+)

「アドホック多相は、関数を適用する対象を制約するための手段。 Haskell では、それを型クラスによって実現している。」

と頭に叩き込もうとした。しかし、直観的に理解できず、しっくり来なかった。 (@_@;)

090514-006.pngしかし、次のように考えたら、スッキリした。

  1. 型は値をグループ化する。
  2. 型クラスは、型をグループ化する。
  3. その結果、型クラスの制約が付いた関数は、その型クラスのグループに属していない型には適用できない。
  4. インスタンス化とは当該の型クラスに所属する宣言。

 

5. パラメータ多相と、アドホック多相

Type polymorphism - Wikipedia によると、Christopher Strachey が、二つの異なる多相について述べていたとのこと。

それによると、「アドホック多相」とは、

If the range of actual types that can be used is finite and the combinations must be specified individually prior to use …

それに対して、「パラメータ多相」とは、

If all code is written without mention of any specific type and thus can be used transparently with any number of new types …

つまり、アドホック多相は、関数を適用する型を制限するのに対して、パラメータ多相は、具体的な型について言及しないことにより、新しい型に対応できるようにするということ。

 

6. 多相性とオブジェクト指向

パラメータ多相と、アドホック多相は、オブジェクト指向において、どのように対応しているのだろうか?

Polymorphism (computer science) - Wikipedia によると、

In object-oriented programming, subtype polymorphism or inclusion polymorphism is a concept in type theory wherein a name may denote instances of many different classes as long as they are related by some common super class.[1] Inclusion polymorphism is generally supported through subtyping, i.e., objects of different types are entirely substitutable for objects of another type (their base type(s)) and thus can be handled via a common interface. Alternatively, inclusion polymorphism may be achieved through type coercion, also known as type casting.

オブジェクト指向においては、サブタイプ多相と呼ばれる。オブジェクトが、共通のインターフェイスを実装している場合、他のオブジェクトに置き換えることができるというもの。

また、Operator overloading - Wikipedia によると、

(less commonly known as operator ad-hoc polymorphism) …

operators like +, =, or == have different implementations depending on the types of their arguments.

演算子オーバーロードは、アドホック多相に相当するとのこと。

オーバーロードと言えば、以下のようにいくつか種類がある。

  • Function overloading, a software engineering process whereby multiple functions of different types are defined with the same name
  • Operator overloading, a software engineering process whereby operators such as + or - are treated as polymorphic functions having different behaviours depending on the types of arguments used
  • Method overloading a type of polymorphism where different functions with the same name are invoked based on the data types of the parameters passed

    (Overload - Wikipedia, the free encyclopedia より)

  •  

    7. 演算子の意味

    ところで、「演算子」というと、Java しか知らなかったとき、メソッドとの違いを明確にイメージしていた。

    `+’ のように、「メソッド名にできない記号が演算子」と言う意識。

    この Java の仕様は、以下で述べられている。

    Sun deliberately chooses not include operator overloading in the Java language.

    (Operator overloading - Wikipedia, the free encyclopedia より)

    Ruby は Java と違い、再定義可能な演算子 がある。

    |  ^  &  <=>  ==  ===  =~  >   >=  <   <=   <<  >>
    +  -  *  /    %   **   ~   +@  -@  []  []=  `

    Haskell は、 Haskell 98 字句構造 で述べられている。

    Python は、__XXXXX__() という形の 特殊メソッド を、クラスが実装することによって、同様のことが可能。

    そういえば、Haskell に触れるようになってから、関数と演算子の差異をあまり感じなくなった。なぜなら、関数の中置記法があるため。

    演算子 – Wikipedia とは、

    コンピュータプログラミングにおいては、主に記号を用いて演算を指示するものが演算子と呼ばれる。概ね数式などの記述を模倣しているが、一部の演算子に通常と異なる記号が用いられたり、副作用を持っていることがあるなど、数学の演算子とは異なる点もある。

    関数 f(x) の "f( )" も単項演算子であり、符牒となる文字列 "f" を関数子などと呼ぶ場合もある。関数子としては任意の文字列を使用することができ、代表的なものとして三角関数 "sin", "cos", "tan" などが挙げられる

    つまり、演算子も関数も、使える記号と記法が違うだけで、本質的な違いはない。

    考えてみれば、メソッドオーバーロードは、同一クラス内で、異なる引数に対する処理に、同じ名前を付けること。メソッドの引数を、関数プログラミングで言う適用する対象と見れば、メソッドオーバーロードは、メソッドが所属するクラスは同じでも、適用する対象が異なるという点で、演算子オーバーロードと似ている。

    「メソッドのシグニチャは、なぜ返り値を含まないんだ?」

    と、以前から疑問に思っていた。しかし、1 + 2 と 3 + 4 の結果、型が異なるような実装ができたらおかしいか。

     

    8. 継承とジェネリクス

    話を戻して、オブジェクト指向におけるアドホック多相とは、オーバーロードに相当する。

    先に挙げた Christopher Strachey  の言うところの

    「メソッドの引数の型がある範囲に限られている」

    ということによる。

    型が「限られている」という点から見ると、

    Ad-hoc polymorphism is generally supported through object inheritance, …

    (Type polymorphism - Wikipedia より)

    オブジェクト指向の継承も、アドホック多相に相当する。

    逆に、「限定されていない」と言うのは、090518-002.png

    In the object-oriented programming community, programming using parametric polymorphism is often called generic programming.

    ジェネリクスのこと。

     

    9. Ad hoc の意味

    ところで、「アドホック」というと

    アドホックな仮説 - Wikipedia

    という使われ方を連想する。言葉自体に、良いイメージがない。

    そもそもの意味は Yahoo!辞書 - ad hoc によると、

    ((限定))そのためだけに[の], 特別に[な]

    Ad hoc - Wikipedia には、

    Ad hoc is a Latin phrase which means "for this [purpose]". It generally signifies a solution designed for a specific problem or task, non-generalizable, and which cannot be adapted to other purposes.

     

    10. 型クラスの定義と、インスタンス化

    さて、最初の「犬・猫」型のコードに戻る。型クラスを使い、異なる型に、同じ関数名を適用できるようにしたい。

    そのためには、型をグループ化する、型クラスを定義する。

    Name.hs

    module Name where
        class Name a where
                getName :: a -> String

    次に Dog.hs

    module Dog where
        import Name
        data Dog = Dog {name :: String, age :: Int} deriving Show
        instance Name Dog where
               getName (Dog name age) = name

    同じようにして Cat/Mike.hs

    module Cat.Mike where
        import Name
        data Mike = Mike {name :: String, age :: Int}
        instance Name Mike where
                    getName (Mike name age) = name

    ついでに、メインモジュールにおいて、getName 関数を利用して

    「こんにちは!○○.」

    と出力する hello 関数も定義する。

    import Dog
    import Person
    import Name
    import Cat.Mike
    
    hello :: Name a => a -> String
    hello x = "Hello! " ++ getName x ++ "."
    
    main = do print $ getName (Mike "mike" 100)
              putStrLn $ hello (Mike "mike" 30)

     

    11. 余談: モジュールに分けない場合

    もし、モジュールに分けずにシンプルに書くとしたら、

    1. 犬と猫に共通の Pet 型を作り、
    2. そこで名前と年齢を持たせる。

    ついでに、犬と猫を同じ型にして、定義してみた。

    data Pet = Pet { name :: String, age :: Int }
    data MyPet = Dog Pet | Cat Pet
    
    getName :: MyPet -> String
    getName (Dog p) = name p
    getName (Cat p) = name p
    
    main = do print $ getName $ Dog (Pet "Pochi" 10)
              print $ getName $ Cat (Pet "Tama" 3)

     

    関連記事

    関連サイト