Hatena::Grouphaskell

lnzntのHaskell日記 このページをアンテナに追加 RSSフィード

2014年11月24日(月)

モジュール

| 22:00 |  モジュール - lnzntのHaskell日記 を含むブックマーク はてなブックマーク -  モジュール - lnzntのHaskell日記  モジュール - lnzntのHaskell日記 のブックマークコメント

module 宣言

module 宣言の例。

module FileUtils where

この場合、モジモジュールで定義したすべてのエンティティがエクスポートされる。

エクスポートするエンティティの限定する例
module FileUtils (makePath, forceRemove) where

() 内をエクスポートリストと呼ぶ。

インポートしたモジュールのエンティティをすべてまとめて再エクスポートする例
module FileUtils (module Hoge.Fuga) where

import Hoge.Fuga

エクスポートリストに「module モジュール名」と書く。

データコンストラクタをエクスポートする例
data SomeType = ConsA String | ConsB Int

module Hoge (SomeType(ConsA), a, b, c) where

データコンストラクタ型コンストラクタとともにエクスポートリストに書く。

複数エクスポートする場合は以下。

module Hoge (SomeType(ConsA, ConsB), a, b, c) where
または
module Hoge (SomeType(..), a, b, c) where  -- 「..」の場合はすべてのデータコンストラクタを指定したことになる

Main モジュール

module 宣言を省略した場合、ファイルの先頭には以下のようなモジュール宣言が補われる。

module Main (main) where

import 宣言

import 宣言の例

import Hoge.Fuga
特定のエンティティをインポートする例
import Hoge.Fuga (foo, bar)

() 内をインポートリストと呼ぶ。

データコンストラクタのインポートの書式はエクスポートと同様。

hiding 構文
import Hoge.Fuga hiding (baz)

特定のエンティティのインポートを防止する。

qualified 構文
import qualified Hoge.Fuga

エンティティが完全修飾名でのみインポートされる。

as 構文
import HogeHogeHoge.FugaFugaFuga as Hoga

as により一時的な別の完全修飾名を使えるようになる。

型クラス

| 19:09 |  型クラス - lnzntのHaskell日記 を含むブックマーク はてなブックマーク -  型クラス - lnzntのHaskell日記  型クラス - lnzntのHaskell日記 のブックマークコメント

型と型クラス。自分には難解です。

とりあえず、参考書籍見ながら勉強したことをメモしておく。

----

型クラス

型クラスは型のクラス

多相性に制約を付けられる。制約を付けられた多相性をアドホック多相と呼ぶ。それに対して、制約のない多相性をパラメータ多相と呼ぶ。

以下は例。

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

「(Ord a) =>」が「a は Ord クラスの制約を満たす型」という制約を示す。この時、型 a は Ord クラスインスタンスでなければならない。

クラスメソッド

Eq クラスの (==)、(/=)関数のように、そのクラスを特徴付ける関数をクラスメソッドと呼ぶ。Eq クラスインスタンスの型の値はすべてそのクラスメソッドの引数にできる。

クラスメソッドインスタンスごとに異なる実装でもよい。(多重定義(overloading))

class 宣言

class 宣言の例

class Eq a where
  (==), (/=) :: a -> a -> Bool

  x == y = not (x /= y)  -- (==)クラスメソッドのデフォルトの実装
  x /= y = not (x == y)  -- (/=)クラスメソッドのデフォルトの実装

継承を含む class 宣言の例は以下。

class (Eq a) = > Ord a where
    compare     :: a -> a -> Ordering
      :

「(Eq a) =>」がスーパークラスの指定。Ord が Eq のサブクラスである。


instance 宣言 と deriving 宣言

インスタンスであることを宣言するには instance 宣言を使う。

deriving 宣言は instance 宣言の簡易な方法だが、使えるクラスが「Eq」「Ord」などに限られる。

deriving 宣言の例

data A = A String String deriving Show

aElement = A "http://localhost/" "myComputer"

main = do
    print $ aElement   -- 「A "http://localhost/" "myComputer"」と表示される
data A =data A = A { aUri :: String, aLabel :: String } deriving Show

aElement = A "http://localhost/" "myComputer"

main = do
    print $ aElement
    -- 「A {aUri = "http://localhost/", aLabel = "myComputer"}」と表示される。

instance 宣言の例

data A = A String String

instance Show A where
    show (A uri label) = "<a href=\"" ++ uri ++ "\">" ++ label ++ "</a>"

aElement = A "http://localhost/" "myComputer"

main = do
    print $ aElement
    -- 「<a href="http://localhost/">myComputer</a>」と表示される。

show は Show クラスクラスメソッド。オーバロードしている、という解釈でいいのかな。スーパクラスのデフォルト実装を上書きしているのでオーバライドのような感覚だけど、異なる型での関数適用を別途に定義しているのでオーバロードかなあ。よくわからない。

この例で (data A ... の行で) deriving 宣言を併用すると、コンパイル時に「Duplicate instance declarations:」エラーになる。

| 16:48 |  型 - lnzntのHaskell日記 を含むブックマーク はてなブックマーク -  型 - lnzntのHaskell日記  型 - lnzntのHaskell日記 のブックマークコメント

型と型クラス。自分には難解です。

とりあえず、参考書籍見ながら勉強したことをメモしておく。

----

data 宣言

新しい型を定義するには「data 宣言」を使う。

data 宣言で定義できる型は代数的データ型(algebraic*1 type)と呼ばれる。

例えば、以下。HTML の A 要素を表す型。String 型の「フィールド」を2つもつ、と宣言してみる。

data A = A String String

data の次の A が「型名」であり「型コンストラクタ」である。(後述の型変数を使った宣言でないので両者は一致している)

等号(=) の次の A が「データコンストラクタ(値コンストラクタ)」。

型コンストラクタデータコンストラクタは異なる名前空間にあるので同じ名前でもよい。

使うのは、以下のような感じ。

data A = A String String

innerHtml :: A -> String
innerHtml (A uri label) = "<a href=\"" ++ uri ++ "\">" ++ label ++ "</a>"

main = do
    print $ innerHtml (A "http://localhost/" "myComputer")
                       -- <a href="http://localhost/">myComputer</a> と表示される

あるいは、こんな感じ。

data A = A String String

anchorUri :: A -> String
anchorUri (A uri label) = uri

aElement = A "http://localhost/" "myComputer"

main = do
    print $ anchorUri aElement        -- "http://localhost/" と表示される
フィールドラベル

フィールドフィールドラベルを付けて宣言することもできる。

data A = A { aUri :: String, aLabel :: String }

使うのは、こんな感じ。

data A = A { aUri :: String,  aLabel :: String }

anchorUri :: A -> String
anchorUri (A { aUri = u } ) = u

aElement = A "http://localhost/" "myComputer"

main = do
    print $ anchorUri aElement        -- "http://localhost/" と表示される
セレクタ

セレクタ」とはフィールドラベルと同名のフィールドラベルの値を取得できる関数。これは自動的に定義される。

data A = A { aUri :: String,  aLabel :: String }

aElement = A "http://localhost/" "myComputer"

main = do
    print $ (aUri aElement)           -- "http://localhost/" と表示される
フィールドの更新

以下のように、フィールドの値を別の値にして値全体を作りなおすことができる。

data A = A { aUri :: String,  aLabel :: String }

aElement = A "http://localhost/" "myComputer"

main = do
    print $ (aUri aElement)
                    -- "http://localhost/" と表示される
    print $ (aUri aElement { aUri = "http://127.0.0.1" })
                    -- "http://127.0.0.1/" と表示される   
「|」を使った宣言

こんな感じ。

data Element = A   { aUri :: String,  aLabel :: String }
             | IMG { aSrc :: String }

isA :: Element -> Bool
isA (A _ _) = True
isA _       = False

isIMG :: Element -> Bool
isIMG (IMG _) = True
isIMG _       = False

aElement = A "http://localhost/" "myComputer"

main = do
    print $ isA aElement      -- True  と表示される
    print $ isIMG aElement    -- False と表示される
型変数を使った宣言
data MyStack a = MyStack [a]

boolStack    = MyStack [True, False]
intStack     = MyStack [1, 2, 10, 100]
stringStack  = MyStack ["foo","bar"]

型名は「MyStack a」。型コンストラクタは「MyStack」。

データコンストラクタも「MyStack」(こっちは等号の(=) の次のMyStack)。

type 宣言

既存の型の別名を宣言する。

type FilePath = String
type MyList [a] = [a]

newtype 宣言

既存の型を元に新しい型を宣言する。

newtype MyStack a = MyStack [a]

data 宣言との違いがよくわからないのですが、以下の記事に「値コンストラクタが正格であること」が違いだと書かれていました。難しい。。。

Haskell で 初めての FizzBuzz

| 11:07 |  Haskell で 初めての FizzBuzz - lnzntのHaskell日記 を含むブックマーク はてなブックマーク -  Haskell で 初めての FizzBuzz - lnzntのHaskell日記  Haskell で 初めての FizzBuzz - lnzntのHaskell日記 のブックマークコメント

基本構文もわかったし、そろそろ FizzBuzz が書けるのでは、と書いてみた。

fizzBuzz :: [Int] -> [String]

fizzBuzz ns = map fizzBuzz' ns
              where
                fizzBuzz' n
                    | n `mod` 15 == 0 = "FizzBuzz"
                    | n `mod`  3 == 0 = "Fizz"
                    | n `mod`  5 == 0 = "Buzz"
                    | otherwise       = show n

main = do
    print $ fizzBuzz [1..100]

ここで fizzBuzz ns の ns が不要であることに気付いたので削る。

fizzBuzz :: [Int] -> [String]

fizzBuzz = map fizzBuzz'
           where
             fizzBuzz' n
                 | n `mod` 15 == 0 = "FizzBuzz"
                 | n `mod`  3 == 0 = "Fizz"
                 | n `mod`  5 == 0 = "Buzz"
                 | otherwise       = show n

main = do
    print $ fizzBuzz [1..100]

参考書籍によると、このように関数を関数で定義するのを「ポイントフリースタイル」というらしい。ここでいう「ポイント」は「.」でなく「圏論(数学の一分野:category theory)」の用語で Haskell でいう値のことらしい。(???まったくわからない。。。)

しげしげ、見ていると fizzBuzz' がうざい。本来こっちが主役の関数で map はその頭を修飾しているだけのようなので書き直す。(自分の考えが手続き的なので「map する」に囚われすぎた?)

fizzBuzz :: Int -> String

fizzBuzz n | n `mod` 15 == 0 = "FizzBuzz"
           | n `mod`  3 == 0 = "Fizz"
           | n `mod`  5 == 0 = "Buzz"
           | otherwise       = show n

main = do
    print $ map fizzBuzz [1..100]

こっちのがいい気がする。

「map fizzBuzz」を一関数にまとめたければ、

fizzBuzzMap = map fizzBuzz
    :
    print $ fizzBuzzMap [1..100]

とすればいい。

対比の Ruby版。

#!/usr/bin/env ruby
# -*- coding: UTF-8 -*-

fizzBuzz = -> n do
              n % 15 == 0 ? "FizzBuzz" :
              n %  3 == 0 ? "Fizz"     :
              n %  5 == 0 ? "Buzz"     :
                            "#{n}"
           end

p (1..100).map &fizzBuzz

「map &fizzBuzz」をまとめるにはメソッド等を定義する。以下ではラムダを使う。

fizzBuzzMap = -> ns { ns.map &fizzBuzz }

p fizzBuzzMap.(1..100)

*1:「あるじぇぶりっく」または「あるじぇぶらいく」と読む