LoginSignup
1
0

More than 5 years have passed since last update.

200 - 10を求めるお話

Posted at

200 - 10を求めるお話

お題

  • Haskellを用い、できるだけたくさんの方法で200 - 10を求めてみます。

(ちなみに、このお題の他にbonus track(2の階乗のリスト:[1,2,4,8,16,32,64,128,256,512,1024]を得る方法)も補足に書き書きしましたので、そちらも合わせてご覧いただければ。)

動機

すごいHaskellたのしく学ぼう! p.64より何種類くらい作れるか気になった1。割り算は型的に面倒くさそうと思ったので、もっとシンプルな題材に変更した! 半分は備忘録のため、冗長な部分もかなり残してあります。

Note) 私のレベル自体はAdvanced beginner(http://qiita.com/lotz/items/0d68c8440d1f362d0c32 )だと思います。なので、その範囲くらいで。

ルール

  • 答えが出れば、速度は無視です。

  • [190]と出てもOKとします(head [190]で一般性を失わない).Just 190とかは190:[]とモナドというくくりでは共通しているので、それはなしで。

  • 200 - 1 - 1 - 1..とか、演算そのものを変えてしまうのは✖︎。あくまで200 - 10という演算をしているということを意味している式を書くこと。(-10 + 200の解釈はOKとしよう.0をinitとして、-10+200をたたみ込む動作は際どいから△マークをつけます)

  • ワンライナーでかけるものだけOKとします2

  • syntax sugarで実質同じものとかあるけど、気にしない方向で。

Note) 自身はstack(Haskellのパッケージをビルドしたりインストールしたりする便利ツール)を用いています。stack exec ghciで対話的インタプリタ ghciが起動できます。

スタート!

> 200 - 10
-- (200-) :: Num a => a -> a
-- 2項演算子では、演算子の左右どちらかに引数を与えたうえで
-- ()で囲んでも関数にできる(セクション)([1, p.64] or [4, p.161]) 
> (200-) 10
-- (+) :: Num a => a -> a -> a
> (+) 200 (-10)
> (-) 200 10
> sum [200, -10]

-- マイナス記号はsubtract関数でも表現出来る([1, p.64])
-- (-)演算子とは第一引数と第二引数の順序が逆であることに注意
> subtract 10 200
-- flip関数は[1, p.67]参照
> flip subtract 200 10
-- subtract関数 を'-'とすると、(-10) :: Intと認識されるので、✖︎ ([1, p.64])
> (subtract 10) 200

-- 中置記法([1, p.3])
> 10 `subtract` 200
> (`subtract` 200) 10


-- $:関数適用演算子[1, p.83]
> (+ 200) $ -10
-- ポイントフリースタイル[1, p.87]も用いてみる 際どいので一応△で。
> (+ 200) . negate $ 10

-- ($) operator(関数適用演算子, [1, p.84, p.240])
-- ($ 200) は (\f -> f 200)と同じ。
> ($ 200) (subtract 10) 
> ($ 200) $ subtract 10 

-- List Comprehension (リスト内包表記[1, p.15])
> [200 - x | x <- [10]] 
> [x-y | x<-[200], y <-[10]]

-- with map function [1, p.68]
> map (200 -) [10]
-- リストはFunctor型クラスで、fmapの定義はmapと同等([1, p.152])
> fmap (200 -) [10]
-- [1, p.232] (->)(関数作用)そのものもファンクタ。
-- :t fmap (+ 200) (negate) :: Num b => b -> b
> fmap (+ 200) (negate) 10
-- 際どいから△
> fmap (subtract 10) (+200) $ 0

-- with ($) operator [1, p.84]
-- this expression equals map ($ 200) [(subtract 10)]
-- ($ 200) は (\f -> f 200)と同じ。
> map ($ 200) $ map subtract [10]
> map ($ 200) $ map (flip (-)) [10]

-- with lambda expression ([1, p.73])
> (\x -> subtract x 200) 10
> (\x y -> x - y) 200 10
-- タプルもラムダ式中に入れこめる
> (\(a, b) -> (a - b)) (200, 10)

-- with let expression ([1, p.46])
> let fn = \x -> 200 - x in map fn [10]
> let fn = \f -> f 200 in fn $ subtract 10
-- with where keyword([1, p.43])
> let y = map f [10] where f = \x -> 200 - x in y
-- [2, p.68] カーリーブレイスを用いて、束縛式を複数書ける
> let {x = 200; y = 10} in x - y
-- 汚いけれど、一行に無理やり押し込んでみる
-- let {変数1 = 式1; 変数2 = 式2; ...} in 式n([3]の再帰定義の章を参照)
> let {x = 200; y = 10; fn :: Int -> Int -> Int; fn a b = a - b} in fn x y



-- 畳み込み
-- using foldl or foldr function([1, p.77])
> let z = 200 in foldl (\acc x -> acc ++ [z-x]) [] [10]
> let res = foldl fn [] [10] where fn acc x = acc ++ [200-x] in res
> let z = 200 in foldr (\x acc ->  (z-x) : acc) [] [10]
-- foldl (\acc x -> acc - x) 200 [10] と同じ。(accはアキュムレーター(結果の値))
> foldl (-) 200 [10]
-- foldr (\x acc -> x - acc) 0 [200, 10] と同じ
> foldr (-) 0 [200,10]
> foldr (flip (-)) 200 [10]
-- セコイ気がする..△かな? [1, p.355] 参照 (idは恒等関数)
> let f = foldr (.) id [(+200)] in f $ -10
-- もっとセコイ気がする...△
> let f = foldr (.) id [(+200), (subtract 10)] in f 0

-- 左側の要素から評価される
-- たとえば、 foldl1 (-) [200, 10, 500] は ((-) ((-) 200 10) 500)と同じ。
> foldl1 (-)  [200, 10]
-- 右側の要素から評価される
-- 例えば、 foldr1 (-) [500, 200, 10] は ((-) 500 ((-) 200 10))と同じ。
> foldr1 (-) [200, 10]

-- [1, p.66]
> zipWith (-) [200] [10]


-- Applicative Functor(アプリカティブファンクター)
-- [1, p.243]
-- fs <*> xs = [f x | f <- fs, x <- xs] ([1, p.245]参照)
> fmap (-) [200] <*> [10]
> pure (-) <*> [200] <*> [10]
-- :: (Applicative f, Num b) => f b
> pure (-) <*> pure 200 <*> pure 10
-- (<$>)の詳細は[1, p.244]参照
> (-) <$> [200] <*> [10]
-- liftA2 f a b = f <$> a <*> b -- 上と同じ[1, p.252]
> liftA2 (-) [200] [10]
-- △
> let fn = (+) <$> (+200) <*> (subtract 10) in fn 0
-- ZipListについては[1, p.250]参照。 import Control.Applicative が必要
> getZipList $ (-) <$> ZipList [200] <*> ZipList [10]
-- [1, p.254]
> sequenceA [(200-)] 10


-- モノイドが助けに来た![1, p.270]
-- import Data.Monoid が必要
> getSum $ Sum 200 `mappend` Sum (-10)
> getSum . mconcat . map Sum $ [200, -10]
-- foldMapについては、[1, p.278] moduleのimportは不要。
> getSum $ foldMap Sum [200, -10]

-- モナド計算
> return 200  >>= \x -> [x-10]
-- '=<<'を使えば逆順でかける
> (\x -> [x - 10]) =<< return 200

-- 200をxに束縛し、10をyに束縛する
-- return 200 >>= (\x -> [10] >>= (\y -> [x - y])) 
> return 200 >>= \x -> [10] >>= \y -> [x - y]
-- :: (Num b, Monad m) => m b
> return 200 >>= \x -> return 10 >>= \y -> return (x - y)
-- 無理やり複数行を一行に押し込める part2
> do {x <- return 200; y <- return 10; return (x - y)}
-- monadの合成(<=<)[1, p.312]
-- import Control.Monad が必要
-- h :: (Num c, Monad m) => c -> m c
> let h = (\x -> return (x+200)) <=< (\x -> return (x-10)) in h 0

-- sequence [(-200)] 10と同じ。リスト内の要素のモナドを一つ一つ走査。
-- sequence :: (Traversable t, Monad m) => t (m a) -> m (t a)
-- Preludeの状態であればimportは特に不要。
> sequence (map (-) [200]) 10

-- Writer monadを使用(今回の計算の場合、Writer monadを使用する利点はないので△)
-- import Control.Monad.Writer が必要
> let calc = writer (200, [show 200]) >>= \x -> writer (10, [show 10]) >>= \y -> return (x - y) :: Writer [String] Int in fst $  runWriter calc


-- (->)(関数作用)そのものもMonadであることを利用(Reader monad)
-- △
> let fn = (+200) >>= \x -> (subtract 10) >>= \y -> return (x+y) in fn 0
-- import Control.Monad.Reader が必要
-- △
> let fn = reader (+200) >>= \x -> reader (subtract 10) >>= \y -> return (x + y) in runReader fn 0

-- liftMはfmapのモナド拡張バージョン[1, p.344]
-- import Control.Monad が必要
> liftM (200-) [10]
-- [1, p.345] apは、Applicative Functorにおける<*>のモナド拡張バージョン
> return (-) `ap` [200] `ap` [10]

番外編:文字列そのまま処理する

ここからは、少しずるいですが...

-- words : import Data.List が必要
-- if式はelseが必須なので、case式を用いる。
-- [2, p.68] カーリーブレイスを用いて、束縛式を複数書ける
> let {[sa, sop, sb] = words "200 - 10"; op n = case n of "-" -> (-)} in (op sop) (read sa) $ read sb

参考

リスト内包表記の上の例だと、

> [x-y | x <- [200], y <- [10]]
> do {x <- [200]; y <- [10]; return (x - y)}

[200] >>= \x -> [10] >>= \y -> [x-y]のsyntax sugarになります。


うーん、こんなもんかなぁ。思ったよりもたくさん書けた!(何個かせこいのあるけど笑)


補足

2の階乗をいろんな方法で求めてみる3

  • 上記のコードとややかぶるところがあるので、補足とさせていただきます。
  • [1,2,4,8,16,32,64,128,256,512,1024]を得ることを目標とします。
  • scanlを用いるところはcoolかも?

スタート!

-- List Comprehension (リスト内包表記)
> [2^x | x <- [0..10]]
> [y^x | x <- [0..10], y <- [2]]


-- with map function
> map ($ 2) $ map (flip (^))  [0..10]
-- [1, p.64] セクション
> map (2^) [0..10]
> take 11 $ map (2^) [0..]
> let fn n = map (2^) $ [0..n] in fn 10
> let fn n = map (2^) $ enumFromTo 0 n in fn 10
-- ポイントフリースタイル
> let fn = map (2^) . enumFromTo 0 in fn 10



-- with zipWith function
> zipWith (^) (repeat 2) [0..10]
> zipWith (^) [2,2..] [0..10]
> map (\(x,y) -> y^x) $ zip [0..10] $ repeat 2
-- [1, p.251] import Control.Applicative が必要。
> getZipList $ (^) <$> ZipList (repeat 2)  <*> ZipList [0..10]


-- $:関数適用演算子
> map ($ 2) $ map (flip (^)) [0..10]

-- foldr
> foldr (\x acc ->  (2^x) : acc) [] [0..10]

-- scanl([1, p.82])
> scanl (*) 1 $ replicate 10 2
-- truncate : 整数値にしたい
> map (truncate) $ scanr (flip (/)) 1024 $ replicate 10 2

-- Applicative functor
> pure (2^) <*> [0..10]
> pure (^) <*> pure 2 <*> [0..10]
-- (<$>)の詳細は[1, p.244]参照
>  (^) <$> pure 2 <*> [0..10]
> map (flip (^)) [0..10] <*> pure 2
-- [1, p.252]
> liftA2 (^) (pure 2) [0..10]
-- sequenceAは、Applicative Functorバージョンの関数
> sequenceA (map (flip (^)) [0..10]) 2

-- Monad
> return 2 >>= \x -> [0..10] >>= \y -> return (x^y)
-- [1, p.345]
-- import Control.Monad が必要
> return (^) `ap` return 2 `ap` [0..10]
-- fmap のMonad拡張バージョン
> liftM (2^) [0..10]
-- importは特に不要 リスト中のモナドを順に捜査する
> sequence (map (flip (^)) [0..10]) 2

参考文献

  1. すごいHaskellたのしく学ぼう!

  2. Real World Haskell―実戦で学ぶ関数型言語プログラミング

  3. http://www.geocities.jp/m_hiroi/func/haskell.html

  4. 関数プログラミング実践入門


  1. あと、この本は読み物としては良書なんだけど、欲しい情報があっちこっちに飛び過ぎでいる気がする。なので、なにか縦横無尽にHaskellの文法を駆使する必要のあるお題を探していたのであります。 

  2. とはいうものの、セミコロン、カーリーブレイス({})を用いると、普通なら複数行で書くコードが一行で書けたりするので、そこのところはやりすぎないようにしてます。 

  3. 理系の方なら、「1+1は?」 「2!」「2+2は?」 「4!」的な遊び?をやったことある人は多いのではないでしょうか。 

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0