テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

右に行っては左に行きを繰り返す関数を書いてみる

人間の日常で良くあること. 二分探索ではないですが, とりあえず適当に真ん中あたりから始めて, 次は右に行って, 違ったら左に行って, みたいなことを繰り返す. 何か調整する時とかはこんな感じの動きをすることが多い. これをリストして, [5, 6, 4, 7, 3, 8, 2, 9, 1]みたいな感じなリストを作るのってどうすればいいのだ?と思ったので書いてみた.

import Control.Applicative

makeAltList :: Int -> Int -> [Int]
makeAltList start len = makeAltList' (start + sublen) sublen
        where
                sublen = len `div` 2

makeAltList' :: Int -> Int -> [Int]
makeAltList' mid sublen = mid:xs
        where
                xs = take (sublen * 2) $ zipWith (+) (repeat mid) (opList sublen)
                opList sublen = (\x y -> y * x) <$> [1..sublen] <*> [1, -1]

main = do
        -- [5,6,4,7,3,8,2,9,1]
        print $ makeAltList 1 9

アルゴリズムの要点は, opListというリストを作るところで, Applicativeを使ってるくらい. Applicativeについて詳細な調査は後日行う.

自分的に嵌ったところは, +11Haskellでは別ものとみなされるところと, 以下のコードが動かないので困ったところ.

*Main Control.Applicative Control.Monad> (\x f -> f x) <$> [1..5] <*> [(+1), (-1)]

<interactive>:85:38:
    No instance for (Num (b0 -> b0))
      arising from a use of syntactic negation
    Possible fix: add an instance declaration for (Num (b0 -> b0))
    In the expression: (- 1)
    In the second argument of `(<*>)', namely `[(+ 1), (- 1)]'
    In the expression: (\ x f -> f x) <$> [1 .. 5] <*> [(+ 1), (- 1)]

そもそもやりたいことと違ってるのだが, それは置いといて, 調べると型が予想と違う. 両方とも(Num a) => a -> a型を想定している.

*Main Control.Applicative Control.Monad> :t (+1)
(+1) :: Num a => a -> a
*Main Control.Applicative Control.Monad> :t (-1)
(-1) :: Num a => a

(+1)は単項演算だが, (-1)は単なる値として認識されている. 一つの方法は, 明示的にラムダ式¥x -> x - 1と書くことだが, もっと良い方法はないのかな?見つからなかった. 知ってる人は教えてください.