SE情報技術研究会’s blog

http://se-info-tech.connpass.com

2016-01-10 『JUnit実践入門』読書会(第6章)の振り返り

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

第6章 テストコンテキスト

  • テストコンテキストとはテストに関する内部状態や前提条件をあらわすもの
  • テストコードは可読性が高くなるようにメンテナンスすべき
  • しかし、プロダクションコードと同じ手法でメンテすると、逆に可読性が低くなる可能性がある

テストコードの整理

整理せず単純にテストケースを作っていくと同じコードがあふれテストケースも読みづらくなる

テストケースのグループ化

  • テストケースで共通の初期化処理を行うものでグループ化すべき
    → 実行前のデータの状態ごと
  • テストケースで検証するメソッド単位でのグループ化は初期化処理の共通化ができないのでやらないほうがよい
  • Enclosedテストランナーを利用することでテストケースをネストしたクラスでまとめることが可能
コメント
  • 実際にp.95であげられているテストケースをそれぞれテストメソッドに実際にしているか?
    → 原則は1テストケースでテスト対象の1アクション
    → 原則はわかっているが実際は簡単なものなど1テストケースにまとめてることもある
    → 原則に従って分けると毎回初期化処理がテストケースを開始するため時間がかかりすぎることもあるのでは?
    → UTの実行が終わるまで時間がかかると短いサイクルでまわせなくなるのでTDDが成り立たない

Enclosedによるテストクラスの構造化

  • org.junit.experimental.runners.Enclosed
  • 対象のテストクラスに@RunWith(Enclosed.class)をつける
  • 初期化処理が共通化しているテストケースをネストしたクラスのまとめることが可能
  • ネストしたクラスにさらにネストすることも可能だが3階層以上になる場合はテストの対象クラスの責務が大きすぎる可能性があるので再検討したほうがよい

著者おすすめの構造化

  • 外側のテストクラスのクラス名は「テスト対象クラス名+Test」
  • ネストするクラスの名前はグループ化するテストケースの前提条件にする ※ 日本語可
  • こうすることでeclipseのクイックアウトライン機能(Ctrl+o)を使うとテストクラスの概要が見やすくなる

例(本より)

@RunWith(Enclosed.class)
public class ItemStockTest {
  
  public static class 空の場合 {
    ItemStock sut;
    
    @Before
    public void setUp() throws Exception {
      sut = new ItemStock();
    }
    
    …テストケース略
  }
  
  public static class 商品Aを1件含む場合 {
    ItemStock sut;
    
    @Before
    public void setUp() throws Exception {
      sut = new ItemStock();
      sut.add("A", 1);
    }
    
    …テストケース略
  }
}

コンテキストのパターン

共通データに着目する

  • 入力値や期待値などのテストで、使用するデータに着目してグループ化するパターン
  • テストケースごとに前や後処理を記述するのではなく、同じデータのテストケースをネストしたクラスにまとめ、そこで前処理と後処理を行ったほうが効率的

共通の状態に着目する

  • テスト対象クラスが状態を持ち事前処理で特定の状態にしないといけない場合のグループ化
  • 対象の状態にするための共通の事前処理をネストしたクラスににまとめ、そこで前処理と後処理を行ったほうが効率的

コンストラクタのテストを分ける

  • インスタンス化テストは他のテストケースとわけてグループ化したほうがよい

テストクラスを横断する共通処理

  • テストクラスの構造化を行うことでテストクラス内の共通処理は整理できるが異なるテストクラス間では共通した処理をまとめられない
  • そのような場合でも継承は使うべきではない
  • 解決策としてユーティリティクラスなどに抽出する
  • 解決策としてルールを使う ※ 第9章にて詳細を説明
コメント
  • 対象の1クラスに対しテストクラスが複数あるのはありか?
    → 原則は1対1なのでは?
    → 1テストクラスにするとソースコードが長くなりメンテが大変になる場合もあるので分ける派もいる

2016-01-10 『JUnit実践入門』読書会(第5章)の振り返り

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

第5章 テストランナー

  • テストクラスに定義されたテストケースをどのように実行するのか制御を行うクラス
  • 通常使うものに対してはJUnitに標準で用意されている
  • テストランナーは@RunWithを使ってテストクラスごとに設定する
  • org.junit.runners.JUnitは標準的なテストランナークラス
  • @RunWithをつけないとJUnit4が自動で使われる
  • 個々のテストケースの拡張には第9章のルールを使う

コマンドラインからの実行

  • junit.runner.JUnitCoreを使用する
  • 実行時いはクラスパスにJUnitのJARが指定されている必要がある
  • テストクラスのmainメソッドを実装する
public static void main(String[] args) {
  JUnitCore.main(CalcTest.class.getName());
}

JUnit4

  • テストクラスの実行すべき全テストケースを実行するテストランナー
  • JUnit4テストランナーは原則1つのテストクラスが対象

次のテストケースを収集し実行する

  • publicのテストメソッド
  • @Testがついている
  • 戻り値がvoidで引数を持たない

privateにしたり戻り値をintに変えたりして試してみたら、テストケースとして認識されず実行されなかった。

Suite

  • 複数のテストクラスを束ねて実行するテストランナー
  • クラス名には慣例として「~Tests」としている
  • テストクラスに@RunWith(Suite.class)に加えて@SuiteClassesを追加する
  • @SuiteClassesには対象となるテストクラスを指定する
@RunWith(Suite.class)
@SuiteClasses({ CalculatorTest.class, CalcTest.class })
public class AllTest {
}
  • クラスの中身の実装は不要

実運用では

  • テストスイートはテストクラスを手動で追加しないといけないの手間
  • Marvenなどで多くのツールJUnitをサポートしていて、柔軟なテストケース収集ができ実行できる
  • ツールを使えば「Test」で終わるすべてのクラスを実行などテストスイートを使わなくても行える
    → そのため命名規約は大事

Enclosed

  • 構造化したテストクラスのテストを実行するテストランナー
  • テストランナーとしてネストしたクラスを定義しテストケースは各ネストしたクラスに定義する
  • テスト対象のオブジェクトの状態毎などにネストしたテストクラスを生成することが可能
    → テストケース実行前の状態の設定などの処理を共通化することが可能(setUpメソッド

Theories

  • パラメータ化テストをサポートするテストランナー
  • テストクラスに@RunWith(Theories.class)をつける
  • テストメソッドで使うパラメータのデータに@DatePointsをつける
  • パラメータを使うテストケースに@Theoryをつける
  • パラメータとなるデータはケースごとの配列やListにする
  • パラメータとなるデータはpublicである必要がある
  • テストケースのメソッドの引数にケースごとのパラメータが渡される
@RunWith(Theories.class)
public class CalcTheories {
  
  @DataPoints
  public static final int[][] VALUES = { //
      { 0, 0, 0 }, //
      { 0, 1, 1 }, //
      { 1, 0, 1 }, //
      { 3, 4, 7 } //
  };
  
  @Theory
  public void add(int[] values) {
    Calc sut = new Calc();
    int actual = sut.add(values[0], values[1]);
    assertThat(actual, is(values[2]));
  }
}

Categories

  • テストケースをカテゴリ化し、カテゴリごとに制御するためのテストランナー
  • Suiteクラスを継承している
  • カテゴリ付けのためのマーカーインターフェイス(実装は不要)が必要
  • カテゴリ付けするテストケースに@Categoryをつける
  • @RunWith(Cateogries.class)@SuiteClassesをつけたテストスイートを用意する
  • テストスイートにカテゴリごとの条件のアノテーションをつける

テストケース

  @Test
  @Category(SlowTests.class)
  public void fastTest2() {
    fail();
  }

テストランナー

@RunWith(Categories.class)
@ExcludeCategory(SlowTests.class)
@SuiteClasses(SlowAndFastTest.class)
public class CategoriesTests {

}

org.junit.experimentalパッケージ

  • 実験のためのパッケージ
  • 今後、仕様変更やパッケージ移動や削除などがありうる

JUnit Lambda では色々変わってるらしい

参照: Developer IO JUnit5はどこに向かうのか? http://dev.classmethod.jp/testing/unittesting/where-is-junit5-aheaded/

2015-12-30 『ふつうのHaskellプログラミング』 もくもく読書会(第10章) の振り返り

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

  • 作者: 青木峰郎,山下伸夫
  • 出版社/メーカー: SBクリエイティブ
  • 発売日: 2014/10/05
  • メディア: オンデマンド (ペーパーバック)
  • この商品を含むブログを見る

第10章 モジュール

モジュールの基本概念

  • モジュールとは関数や型などの定義をまとめるために使われるもの
  • 名前空間がモジュールごとにわかれている
  • すべてのモジュールはPreludeモジュールをインポートしている
    → Preludeモジュールのインポート宣言は不要

モジュールの定義とエクスポート

モジュールの宣言

module モジュール名 where

…インポートや変数の宣言やクラスメソッドの宣言などなど
  • モジュール名はアルファベットの大文字で始まり、「.」でつなげれる
  • 下記の記述でエクスポートするクラスメソッドを限定できる
module モジュール名 (クラスメソッド, クラスメソッド, …略) where

次の型があったとする(本の例より)

data SomeType = ConsA String | ConsB Int

これをエクスポートできるようにするには下記のようにする

module AnyModule (SomeType(ConsA, ConsB) ) where
  • ConsBをエクスポートせずConsAのみエクスポートするなら(SomeType(ConsA))
  • 全ての型コンストラクタをエクスポートするならSomeType(..)
  • モジュールがインポートした別のモジュールを(module インポートしたモジュール名)とすることで再エクスポートすることも可能

例(本より):

module LineParser
  (module Text.ParserCombinators.Parsec.Prim,
   LineParser, indented, blank, firstChar, anyLine) where

import Text.ParserCombinators.Parsec.Prim

Mainモジュール

  • module宣言するとMainモジュールになる
  • 下記のようなmodule宣言が暗黙のうちに定義されている
module Main (main) where

インポート

  • import宣言はmodule宣言の後にまとめて書かなくてはならない
  • import宣言がモジュール名のみだとそのモジュールがエクスポートしてる全てのものがインポートされる
  • 次のように書くことでインポートする内容を制限できる
import Text.Regex (mkRegex, matchRegex)
  • 型君巣トラクタをインポートする場合は、エクスポートと同様に型コンストラクタが必要
  • hiding (対象, 対象)をつけると対象のものはインポートされない
  • hidingは予約語ではないので関数名で使うことも可能
  • Preludeに対してもhidingは有効
  • qualifiedをつけるとインポートしたモジュールのエンティティは完全修飾名でないと使えない
  • インポートでas構文をつかうと完全修飾名が変えれる

例(本より):

importb qualified Distribution.Simple.GHCPackageConfig as Conf

上記の場合、Conf.エンティティ名で使えるようになる

deriving Eq について (2015-12-30 『ふつうのHaskellプログラミング』 もくもく読書会(第9章)の振り返り)

前回の第9章で下記の疑問が生じた

例(本より):

data Anchor = A String String deriving Eq

class宣言の記述で、Eqクラスのインスタンスの場合、(==)か(/=)のどちらかを実装しないと無限ループに陥るとあったが、Anchorの例でどちらも実装しないでも問題ないのはなぜ?
→ 実際に実行して試してみたが問題なかった。

anchor1 = A "http://se-info-tech.connpass.com/" "Homepage"
anchor2 = A "http://se-info-tech.connpass.com/" "Homepage"
anchor3 = A "http://se-info-tech.connpass.com/" "Home Page"

上記を読み込み下記をGHCiで実行

> anchor1 == anchor2
True
> anchor1 == anchor3
False

これは本にも前回のコメントにもあるように自動でクラスメソッドが実装されているからなのだが、 そもそも引数が既に実装されたEqクラスのインスタンスであるStringであることを失念していたため、 下記の

instance Eq Anchor where
  (A u l) == (A u' l') =(u == u') && (l == l')

(u == u')(l == l')で無限ループになるのでは?と思っていた。

ただコンストラクタの引数がStringで既にEqのクラスメソッドが実装されていると考えると 自動で実装されても問題ないのだろう。

では、コンストラクタの引数がないEqクラスのインスタンスを宣言した場合、どうなるのかが気になってみた。

そこでコンストラクタ引数なしの下記を宣言して読み込んで

data TestEq = T deriving Eq
test1 :: TestEq
test1 = T
test2 :: TestEq
test2 = T

GHCiで実行してみた。

> test1 == test2
True
> test1 == test1
True

どうやら問題ないようだ。

2016-01-09 第11回 関数型プログラミング勉強会(Scala)の振り返り part.2

第6章 純粋関数型の状態

乱数の例

  • 副作用に依存している場合、状態の更新が副作用として行われるため、再現性が低くなる。
    → テスト、合成、並列化が難しくなる。
  • seed値を持ったRNGが乱数とその乱数を生成するための新たなseedを持ったRNGを生成する
    → このシーケンスを保つことで何回実行しても常に同じ結果が生成される

本より純粋関数型のシンプルな乱数ジェネレータの例:

case class SimpleRNG(seed: Long) extends RNG {
    def nextInt: (Int, RNG) = {
        val newSeed = (seed * …略
        val nextRNG = SimpleRNG(newSeed)
        val n = (newSeed >>> 16).toInt
        (n, nextRNG)
    }
}

Exercise 6.1

GitHubには下記のようにあるが

def nonNegativeInt(rng: RNG): (Int, RNG) = {
  val (i, r) = rng.nextInt
  (if (i < 0) -(i + 1) else i, r)
}

これはabs関数の方が良いのでは?という意見も出た

def nonNegativeInt(rng: RNG): (Int, RNG) = {
  val (i, r) = rng.nextInt
  if (i == Int.MinValue) Int.MaxValue
    (Int.MaxValue, r)
  else
    (abs(i), rng)
}

abs関数を使ったケースだとInt.MaxValueが出るパターンが1つ増えるのでは? と思われたが、そこはいくつもあるケースの中の1ケースなので大した影響ではない。 また、ifの場合も0が出るパターンも増えabs関数を使った時と同様となるが、 ifの場合、absの場合と違い、乱数の範囲をしぼっても重複のパターンが消えることはない。

Exercise 6.2

GitHubにあがってる解答で問題なし。

Exercise 6.3

GitHubにあがってる解答で問題なし。

解答の最後にコメントで下記とある

There is something terribly repetitive about passing the RNG along every time. What could we do to eliminate some of this duplication of effort?

毎回RNGを渡すことの繰り返しだらけになる。 この繰り返しをなくすには何ができるか?

これは、次のExercise 6.4で解決策を示している

Exercise 6.4

GitHubにあがってる解答で問題なし。

// A tail-recursive solution
def ints2(count: Int)(rng: RNG): (List[Int], RNG) = {
  def go(count: Int, r: RNG, xs: List[Int]): (List[Int], RNG) =
    if (count <= 0)
      (xs, r)
    else {
      val (x, r2) = r.nextInt
      go(count - 1, r2, x :: xs)
    }
  go(count, rng, List())
}

go関数のxsは蓄積変数で毎回の処理結果が追加されたリストが渡される。

2016-01-09 第11回 関数型プログラミング勉強会(Scala)の振り返り part.1

第5章 正格と遅延

前回の続きより

Exercise 5.12

GitHubにあがってる解答で問題なし。

まず、fibsViaUnfoldの実装を見てみる。

val fibsViaUnfold = 
  unfold((0,1)) { case (f0,f1) => Some((f0,(f1,f0+f1))) }

これはコメントにあるようにp => p matchを省略してある。

Scala provides shorter syntax when the first action of a function literal is to match on an expression.

Scalaは関数リテラルの最初の処理が式にマッチする場合 より短いシンタックスを供給する

The function passed to unfold in fibsViaUnfold is equivalent to p => p match { case (f0,f1) => ... }, but we avoid having to choose a name for p, only to pattern match on it.

fibsViaUnfoldunfoldに渡した関数は p => p match { case (f0,f1) => ... }と同値であるが パターンマッチでしか使われないpをわざわざ命名することをここでは避けた

あと、fromViaUnfoldを段階的に見てみた。

fromViaUnfold(2)
 ↓
unfold(2)(n => Some((n,n+1)))
 ↓
cons(2, unfold(2+1)(n => Some((n,n+1)))
 ↓
cons(2, cons(3, unfold(3+1)(n => Some((n,n+1)))
 ↓
cons(2, cons(3, cons(4, unfold(4+1)(n => Some((n,n+1)))
 ↓
…
 ↓
Stream(2, 3, 4...略)

また、onesViaUnfoldは下記にもできる

val onesViaUnfold = constantViaUnfold(1)

Exercise 5.13

GitHubにあがってる解答で問題なし。

Exercise 5.14

GitHubにあがってる解答で問題なし。

Exercise 5.15

GitHubにあがってる解答で問題なし。

Exercise 5.16

解答に値を入れて段階的に見ていく。

Stream(1,2,3).scanRight(0)(_ + _)
 ↓
Stream(1,2,3).foldRight((z, Stream(z)))((a, p0) => {
      lazy val p1 = p0
      val b2 = f(a, p1._1)
      (b2, cons(b2, p1._2))
    })._2
 ↓
Stream(2,3).foldRight((0, Stream(0)))((1, (0, Stream(0))) => {
      lazy val p1 = (0, Stream(0))
      val b2 = 1 + 0
      (1, cons(1, Stream(0))
    })._2
 ↓
Stream(3).foldRight(1, Stream(1,0))((2, (1,Stream(1,0)))) => {
      lazy val p1 = (1, Stream(1,0))
      val b2 = 2 + 1
      (3, cons(3, Stream(1,0))
    })._2
 ↓
Stream().foldRight(3, Stream(3,1,0))((3, (3,Stream(3,1,0)))) => {
      lazy val p1 = (3, Stream(3,1,0))
      val b2 = 3 + 3
      (6, cons(6, Stream(3,1,0))
    })._2
 ↓
Stream(6,3,1,0)

その他

ScalaのStreamは次のパターンで表現できる(両方とも同じである)

  • Stream(1,2,3,4)
  • Cons(1, Cons(2, Cons(3, Cons(4, empty))))

2015-12-30 『ふつうのHaskellプログラミング』 もくもく読書会(第9章)の振り返り

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

ふつうのHaskellプログラミング ふつうのプログラマのための関数型言語入門

  • 作者: 青木峰郎,山下伸夫
  • 出版社/メーカー: SBクリエイティブ
  • 発売日: 2014/10/05
  • メディア: オンデマンド (ペーパーバック)
  • この商品を含むブログを見る

第9章 型と型クラス

※ この章は既に前の章で出ている内容も多い

式に対する型宣言

  • 式にも型宣言が可能
コメント

例だと

luckyNumber = (7 :: Int)
unluckyNumber = (13 :: Integer)

とあり、式と言うより値の「7」や「13」に対して型宣言をしている(「7」や「13」がNumではなくIntやIntegerと宣言している)ように思えたので、試しに下記を試してみた

add2 a = a + 2
add2' a = a + 2 :: Integer

ロード後に型を見てみる

> :t add2
add2 :: Num a => a -> a
> :t add2'
add2' :: Integer -> Integer

式とは演算子を使った計算とかのことかと思っていたが、要は評価できるものということで数値も式であると見なせるらしい (数値リテラルは式であり、結果の値でもある的な…)

こうなってくると式の定義がわからなくなてっきたのでWikipediaを見てみると

式(しき、expression)とは、プログラミングにおいて、言語によって定められた優先順位や結びつきの規定に則って評価される値、変数、演算子、関数の組み合わせである。 数学における式と同様、式は評価された値を持つ。

と言うことで数値リテラルは値のみの組み合わせであり評価された値を持っているので式ということなのだろう。

代数的データ型

  • Haskellで新しいデータ型を宣言するにはdata宣言を行う
  • data宣言で定義した型は代数的データ型
  • 代数的データ型は別の種類の型を統一的に扱うことが可能

構造体スタイル

  • 複数の値をまとめるための使われるスタイル

例(本より):

data Anchor = A String String

以下のことを宣言してる

  • 新しい型(上記の例だと「Anchor」)
  • 型の値を作る方法
  • その型が持つフィールド

一般的な構造体スタイルの代数的データ型

data 型コンストラクタ = データコンストラクタ 型12

コンストラクタ

  • 上記の例だと「A」
  • アルファベット大文字で始まらないといけない
  • 部分適用にも関数合成にも使えるが関数ではない

上記の例の場合、「A」は2つのStringの引数を取るAnchorのコンストラクタ

A "http://se-info-tech.connpass.com/" "SE情報技術研究会"

パターンマッチによるフィールドへのアクセス

  • パターンマッチを使ってフィールドにアクセス可能
data Anchor = A String String

compileAnchor = A "http://se-info-tech.connpass.com/" "Homepage"

showUrl :: Anchor -> String
showUrl (A url label) = url

ロード後に実行

> showUrl compileAnchor
"http://se-info-tech.connpass.com/"

フィールドラベル

  • フィールドラベルを使ってフィールドに名前を付けることが可能
data Anchor = A {
  url :: String,
  label :: String
}

セレクタ

  • フィールドラベルを付けると同名の関数が自動的に宣言される
  • フィールドラベルがついたフィールドには下記でアクセス可能
フィールド名 そのフィールドを持つ型の変数名

例:

> url compileAnchor
"http://se-info-tech.connpass.com/"

フィールドの更新

  • Haskellでは再代入できないのでフィールドの更新はできない
> label compileAnchor
"Homepage"
> label compileAnchor { label="test"}
"test"

これはcompileAnchorのlabelを"test"に変えたのではなく、labelに"test"を持つ新たに生成されたAnchorのurlを出力している

多相的な型の宣言

  • フィールドに型変数を持つ代数的データ型も宣言可能
  • その場合、型コンストラクタと一緒に型変数の宣言も必要

例(本より):

data Stack a = MkStack [a]

コンストラクタとデータコンストラクタ

列挙型スタイル

例:

data OpenMode = ReadOnly | WriteOnly | ReadWrite

共用体スタイル

  • 構造体スタイルと列挙型スタイルを融合させたようなスタイル
  • 再帰的な代数的データ型の宣言も可能

型の別名と付け替え

type宣言

  • 型に別名がつけれる
  • 型宣言も使える

例(本より):

type FilePath = String
type MyList a = [a]

newtype宣言

  • 既存の型を元にして新しい型を宣言する方法
  • data宣言とほぼ同じが実行時の表現が違う
コメント

newtyep宣言とdata宣言の違いが本には記述してあるが、結局どう違うのかがよくわからない。
→ 本のようにリストを例にすると、newtypeだと既に定義されている型であるリストとして認識されるが、dataだと新規の型とみなされる
→ そのため実行時にスピードの違いが出るらしい。

型クラス

  • 処理できる型を多相型よりも限定したい場合(多相型に制約をつける場合)に使うもの
  • 型クラスのように制約のついた多相性のことを「アドホック多相」という。 ※ 制約なしだと「パラメータ多相」という
  • 「(型クラス 型変数) =>」で型変数に型クラスの制約がついていることを示す

例:

sort :: (Ord a) => [a] -> [a]

上記は型aがOrdクラスのインスタンスであり、Ordクラスの制約を満たすという意味

クラスメソッド

型クラスの継承

  • 継承することである型クラスのクラスメソッドを前提としてクラスメソッドを実装できる

class宣言

本よりEqの例:

calss Eq a where
  (==), (/=) :: a -> a -> Bool -- クラスメソッドの型宣言
  x == y = not (x /= y)        -- (==)クラスメソッドのデフォルト実装
  x /= y = not (x == y)        -- (/=)クラスメソッドのデフォルト実装
  • 型クラスは1文字目が大文字でないといけない
  • class宣言では型宣言を省略できない
  • デフォルト実装はインスタンスで独自に実装しなかった場合に使われる実装
    → Eqクラスのインスタンスの場合、(==)か(/=)のどちらかを実装しないと無限ループに陥る

継承を含むclass宣言

本よりOrdの例:

class (Eq a) => Ord a where…略

instance宣言

例(本より):

data Anchor = A String String

instance Eq Anchor where
  (A u l) == (A u' l') = (u == u') && (l == l')

上記はAnchor型がEqクラスのインスタンスであることを宣言している

deriving宣言

  • instance宣言を自動生成させる場合に使う宣言
  • deriving宣言を行うとinstance宣言の実装が不要になる
  • 実装が自明の場合にしか使えない

例(本より):

data Anchor = A String String deriving Eq
コメント

class宣言の記述で、Eqクラスのインスタンスの場合、(==)か(/=)のどちらかを実装しないと無限ループに陥るとあったが、Anchorの例でどちらも実装しないでも問題ないの?
→ 実際に実行して試してみたが問題なかった。なぜ?

これについては下記のリンクにて追記

deriving Eq について (2015-12-30 『ふつうのHaskellプログラミング』 もくもく読書会(第9章)の振り返り) - SE情報技術研究会’s blog

練習問題

特に問題なし

コメント

本題とは関係ないが、リストの宣言で下記はコンパイルエラーになる

lines = [ 
  (L 1000 "test_1"),
  (L 2000 "test_2"),
  (L 3000 "test_3") 
]

最後の]に空白を入れてなかったため

本題とは関係ないがimport Data.Listするとlinesという変数名はコンパイルエラーになる

> print $ sortLine lines

<interactive>:28:18:
    Ambiguous occurrence ‘lines’
    It could refer to either ‘Main.lines’, defined at type.hs:51:1
                          or ‘Data.List.lines’,
                             imported from ‘Data.List’ at type.hs:1:1-16
                             (and originally defined in ‘base-4.8.1.0:Data.OldList’)