Hatena::Grouphaskell

Haskell卒業!

  Haskellの勉強 -> 演習 -> 卒業
  Haskell&プログラミング卒業しました。その他サイコなことは「route150の日記」に書いています。

2010年03月18日 木曜日

[][]基本文法(fmap、data) 06:36

{-# LANGUAGE UnicodeSyntax #-}

-- fmapって2引数タプルやEitherにも使えるんだ...知らなかった。
-- というか、2引数タプルやEitherはFunctorクラスのインスタンスなんだ...

test1 = fmap (+ 5) (1, 2)
-- 第2要素にだけ適用される...
-- $> test1
-- (1,7)

test2 = fmap (+ 5) (Left  2)
test3 = fmap (+ 5) (Right 1)
-- Rightにだけ適用される...
-- $> test2
-- Left 2
-- $> test3
-- Right 6

-- 代数的データ型と型クラスに関しては、よくわからない。C++のイメージとはちょっと違う。
-- C++っぽいネーミングにしてみた...

-- 代数的データ型はコンストラクタのみ定義できるC++の構造体みたいな感じ、
-- Cの構造体(or 列挙型)に近いかも...
-- 他のメンバ関数(メソッド)は定義できない...
data IChar a ⇒ CString a = CString [a]
  deriving (Show, Eq, Ord)

-- 型クラスはC++のinterfaceのようだけど、微妙に違う...
-- 型変数はギリシャ文字の順番(α, β ⋯ == a, b ⋯)の順で使うのが慣習らしいとどっかで読んだ気がする...
class IChar a where
  toChar ∷ a → Char
  
class IString a where
  strcat ∷ a → a → a

-- Haskellだと、この時点ではCStringとIStringは全く関係がないようだ。C++でも関係ないと言えば
-- 関係ないけど、普通はCStringがIStringを継承してると思う。つまり、型とメンバ関数が
-- 基本的にセットになっている → オブジェクト指向...

-- セットになっていると便利だけど、使わないのに仮想関数を定義する必要があったり、
-- 後から足すのが面倒くさい(デザインパターンのAdapterパターン?で誤魔化すらしい。
-- 自分はCOM関連以外は基本的に多重継承しないのでよくわからないけど...)

-- Haskellではインスタンス宣言して、やっと関連が付く。つまり、必要な機能だけ後付けできる...
instance IChar Char where
  toChar c = c

instance IChar a ⇒ IString (CString a) where
  (CString s1) `strcat` (CString s2) = CString (s1 ++ s2)

-- 型シグネチャ宣言や型注釈で⇒の左側に来るのは文脈(context = 型クラス 型変数)、右側は型変数とか型構築子
-- データ構築子は宣言には現れない...
-- add ∷ Num a ⇒ a → a → a
-- strcat ∷ CString → CString → CString

-- 用語.. 構築子と構成子は同じらしく、共にconstructorの訳らしい。紛らわしいなぁ。
-- 型構築子はtype constructor、データ構築子はdata constructor...

-- 役には立たないけど、意味はわかりやすいかな...
data Object     = Object     deriving Show
data Executable = Executable deriving Show
data Language   = Language { label    ∷ String
                           , compiler ∷ String   → [Object]
                           , linker   ∷ [Object] → Executable
                           }

class Tool a where
  compile ∷ (a, String)   → (a, [Object])
  link    ∷ (a, [Object]) → Executable
  build   ∷ (a, String)   → Executable

instance Tool Language where
  compile (a, s) = (a, compiler a $ s)
  link (a, xs)   = linker a $ xs
  build          = link . compile

instance Show Language where
  show a = label a 

-- 使ってみよう...
main = do
  let cpp = Language "C++" (const []) (const Executable)
  print cpp
  print $ build (cpp, "int main(){return 0;}")

-- $> main
-- C++
-- Executable
-- (0.00 secs, 525292 bytes)

型について勉強していくと、戻ってこれない気がする...

あと、よく考えたら、Languageにlinkerは要らないな...


まとめると、Haskellでは代数的データ型型クラスは明確に区別されているようだ(名前空間は同じっぽいけど...)。C++はオブジェクト指向だけあって、Haskellでいう代数的データ型を逆に型クラスの様に扱うこともできる(予め、統合されてるから。ポインタや参照を扱う場合などは特に顕著、ちょっと言い過ぎだけど)。


だから?


Haskellの型をC++コンテキストで理解しようとすると、いつかドツボにハマる...んじゃないかなと思った。逆にGHCの言語拡張を駆使すれば、代数的データ型型クラスの関係をC++のオブジェクトの様に扱える気はする。

ただ、言語拡張を使い過ぎると、型推論が弱くなると思うのでもう少し慣れてから...


地道にやっていこう...

[]ペル方程式 12:09

"http://www004.upp.so-net.ne.jp/s_honma/"

の備忘録→ペル方程式のアルゴリズムを参考にペル方程式を解いてみた。

利用したのは、上述のHPに記載されていた2つの定理。こんなの知らなきゃ無理...

アルゴリズムは確定しているので、どうやって定義(実装)するかだけだけど...

{-# LANGUAGE UnicodeSyntax #-}

module PellEquation
  ( Root (..)
  , solvePellEquation, solvePellEquation'
  ) where

  import Control.Arrow ((&&&), (***))
  import Data.Ratio (numerator)

  -- 簡単に平方根の計算ができた方がいいかと思ったので...
  -- 複雑な計算はMathematicaとかに任せよう...
  data (Integral a, RealFrac b) ⇒ Root a b = Root a (b, b)
    deriving (Eq, Show)

  -- よく考えたら、hasSameRootが間違ってる...
  -- rが1の時はに計算が変わってしまう。言語拡張(PatternGuard)を使って、rとr'の最大値を
  -- Maybeで返すようにしないといけなかった...
  -- → 修正...(maxで十分ぽい)
  instance (Integral a, RealFrac b) ⇒ Fractional (Root a b) where
    (/) x@(Root r (a, b)) (Root r' (a', b'))
      | hasSameRoot r r' = Root (max r r') ((a'' / d), (b'' / d))
      | otherwise = error $ "(/): Not the same root."
      where
        Root _ (a'', b'') = x * Root r' (a', (negate b'))
        d = a' ^ 2 - b' ^ 2 * fromIntegral r'
    fromRational x = Root 1 (fromRational x, 0)

  instance (Integral a, RealFrac b) ⇒ Num (Root a b) where
    (+) (Root r (a, b)) (Root r' (a', b'))
      | hasSameRoot r r' = Root (max r r') (a + a', b + b')
      | otherwise = error $ "(+): Not the same root."
    (*) (Root r (a, b)) (Root r' (a', b'))
      | hasSameRoot r r' = Root (max r r') (a * a' + b * b' * fromIntegral (max r r'), a * b' + b * a')
      | otherwise = error $ "(*): Not the same root."
    negate (Root r (a, b)) = Root r (negate a, negate b)
    abs x@(Root r _)
      | signum x == mRoot r = negate x
      | otherwise = x
    signum (Root r (a, b))
      | a == 0 && b == 0 = zRoot r
      | signum a >= 0 && signum b >= 0 = pRoot r
      | signum a <= 0 && signum b <= 0 = mRoot r
      | otherwise = if signum a * a ^ 2 + signum b * b ^ 2 * fromIntegral r >= 0
                      then pRoot r
                      else mRoot r
    fromInteger x = Root 1 (fromIntegral x, 0)

  mRoot r = Root r (negate 1, negate 1)
  zRoot r = Root r (       0,        0)
  pRoot r = Root r (       1,        1)
  hasSameRoot r r' = r == 1 || r' == 1 || r == r'

  -- ペル方程式(x ^ 2 - d * y ^ 2 = 1)を満たす最小解を返す。
  -- ペル方程式の解の冪乗は全て解になる。
  -- 引数dに2の冪乗(平方数)は指定できない...
  solvePellEquation ∷ Integer → Root Integer Rational
  solvePellEquation d =
    let a@(Root _ r) = solvePellEquation' d
        (x, y) = (numerator *** numerator) r
    in
      -- 解が-1の方なら2乗して+1の方を返す。
      if x ^ 2 - d * y ^ 2 == -1 then a ^ 2 else a

  -- abs (x ^ 2 - d * y ^ 2) = 1の最小解を返す。
  -- -1の解は存在しない場合がある。
  -- -1の解をaとする時、次の解を得るには、a ^ 2を掛ける(aを掛けると+1の解になってしまう)...
  solvePellEquation' ∷ Integer → Root Integer Rational
  solvePellEquation' d
    | d <= 0                                    = showError $ "is zero or has negative value."
    | (floor . sqrt . fromInteger $ d) ^ 2 == d = showError $ "is a square number."
    | otherwise = p 0 [a0]
    where
      showError = error . ("solvePellEquation': " ++) . ("d(" ++) . (shows d ") " ++)

      d' = toRational d
      a0@(g0, h0, k0) = (0, 1, toRational . floor . sqrt . fromIntegral $ d)

      p n xs@((g, h, k):_)
        | g == g' = ((^ 2) . f $ q 0 (reverse xs) []) / Root d (h, 0) 
        | h == h' = (uncurry (*) . (f &&& f . tail) $ q 0 (reverse (a' : xs)) []) / Root d (h, 0)
        | otherwise = p (succ n) (a' : xs)
          where
            a'@(g', h', k') = (-g + k * h, (d' - g' ^ 2) / h, toRational . floor $ (k0 + g') / h')
            f = Root d . (snd &&& fst) . head

      q _ (_:[]) ys = ys
      q n xs ys
        | n == 0    = q (succ n) xs  ((0, h') : ys) -- 最初の引数はドロップしない...
        | n == 1    = q (succ n) xs' ((1, g ) : ys)
        | otherwise = q (succ n) xs' ((y, x ) : ys)
          where
            ((g', h', k'):xs') = xs
            ((g , h , k ):_  ) = xs'
            ((y' , x' ):ys')   = ys 
            ((y'', x''):_  )   = ys'
            (x, y) = (g * y + h * y', y'' + k' * y')

[]ペル方程式(検証) 12:21

x ^ 2 - d * y ^ 2 = 1で検証してみよう...

計算するのが急に大変になるのは、dが61の時...

$> let a = solvePellEquation 61
(0.00 secs, 0 bytes)
$> a
Root 61 (1766319049 % 1,226153980 % 1)
(0.00 secs, 0 bytes)

(x, y) = (1766319049, 226153980)

が最初の解...

計算にGHCiでコンマ1秒すらかからない、すごいアルゴリズム...


次の解は...

$> a ^ 2
Root 61 (6239765965720528801 % 1,798920165762330040 % 1)
(0.02 secs, 525812 bytes)

(x, y) = (6239765965720528801, 798920165762330040)


100番目の解は...

$> a ^ 100
Root 61 (32276381116960305639325958838172316625136535911085041010916684110022737
36613386000462515801229557129435845772937766560044152546946182368577429998989370
14246606176506177272589212432935321962492310788331475688631514060183808673262909
77725478771518744835759878289507459709835744476153210303248854445123406945592964
76882536282804161068744604490647158207294947854459166375522562425827171826141056
43082877390576010091254781525397872937596483242370481767263272645710970811903509
42439869260895006993155667528727431958743877444658760733837432875611328670524419
49942922238001163850592681369485875196514411809269161377832509713256025630926167
59505846427760489562841384241212690585545482921334500429440088624067079665388960
18505692754217416027734602641548266521017423514205817803944309292725633965454689
91232757675075964907353138221472365672629397838248416276619869675685420344509941
38633630052877346092527824184747853423860284091468255829792883524501976946372200
0001 % 1,41325671337406374897335066676274109108046721085478956603292758897027123
47401868083588052035457728393224718172560031987091693079440925251405192830369027
62497046003091796413876312939413350443999997637595104412338212225559058356269843
26750033971033407447076490152397537960135174470942741561005546304728925361121050
72943763831263012828688852057327640981638689909565283715021472607923798206903004
64897448311388254756594684312682804975858007874163629892282520109926210930754187
65845217196922887971726266663351207174842163607362458747455631352178919867547390
31885253191929760091056926735209201371002664318425592530485152665279492816104015
48927750120613139534506987555225237507182165676183784976412278209117346888801579
32344454000932284311525660244374531219899770139960070335369010999775034942725226
56529434305932005249877716727362031086648363874671191495699779887388351483703106
37798756897110675139973119805323090799349896276876852909285510476471928573081302
000 % 1)
(0.03 secs, 1603260 bytes)

何番目でも、もうただの掛け算...

[]基本文法(mappend) 18:42

{-# LANGUAGE UnicodeSyntax #-}

import Data.List
import Data.Monoid (mappend)
import Data.Maybe (fromMaybe)

-- Just - Justの場合に文字列を繋げたければ、Monoidが使える...
test = [Just "foo", Nothing, Just "bar"]

-- $> sequence test
-- Nothing
-- (0.00 secs, 0 bytes)
-- $> msum test
-- Just "foo"
-- (0.00 secs, 0 bytes)
-- $> mconcat test
-- Just "foobar"
-- (0.00 secs, 528316 bytes)

-- 2引数タプルもMaybeもリストも関数もMonoidのインスタンスで、
-- かつMonoid則は簡単、2つ(単位元の定義、結合法則)しかない...
-- Monadみたいに変に全称量化されてないから(訂正: 類の間違いだった...)、
-- Integerとかもインスタンスにできる...


-- 結合法則から、foldl1 mappend [モノイド] == foldr1 mappend [モノイド]
-- だから、fold系関数と親和性が高い。

-- で、Monoidを使ったfizzbuzzの解...
fizzBuzzList n = map f [1 ⋯ n]
  where f i = fromMaybe (show i) $ foldr (mappend . g) Nothing [3, 5] 
          where g x = case (x, mod i x) of
                        (3, 0)    → Just "fizz"
                        (5, 0)    → Just "buzz"
                        otherwise → Nothing

-- この場合、foldl'でも同じ。何故なら、Nothing == memptyで単位元だから...
fizzBuzzList' n = map f [1 ⋯ n]
  where f i = fromMaybe (show i) $ foldl' (\ar x → ar `mappend` (g x)) mempty [3, 5] 
          where g x = case (x, mod i x) of
                        (3, 0)    → Just "fizz"
                        (5, 0)    → Just "buzz"
                        otherwise → mempty