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
参考
- https://en.wikibooks.org/wiki/Haskell/Syntactic_sugar ... Haskellのsyntax sugar
リスト内包表記の上の例だと、
> [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