Haskellの静的解析ツール HLint を使おう

  • 16
    Like
  • 0
    Comment

この記事はLint Advent Calendar 2016の23日目の記事です

はじめに

軽い気持ちでHaskellのリントツールである HLint を紹介するよ

なかなかHaskellのコードレビューをしてもらう機会ってないと思うんだけど、こいつは結構頼りになる奴なんだ

動作も軽快だし、stack使ってるならインストールも一瞬だよ

命名規則

スネークケースをキャメルケースにしろ、って

snake.hs
some_func x = x + 2
$ hlint snake.hs
snake.hs:1:1: Suggestion: Use camelCase
Found:
  some_func x = ...
Why not:
  someFunc x = ...

1 hint

do

1行の場合のdoは不要だよ、って

do.hs
check int = case int of
    (Just x) -> do
        putStrLn "just!"
        print x

    Nothing  -> do
        putStrLn "nothing"

main = do
    check (Just 5)
$ hlint do.hs
do.hs:6:17: Warning: Redundant do
Found:
  do putStrLn "nothing"
Why not:
  putStrLn "nothing"

do.hs:9:8: Warning: Redundant do
Found:
  do check (Just 5)
Why not:
  check (Just 5)

2 hints

2箇所出てるね

そんな関数あるよ

自分で関数組み合わせちゃった場合、まんまそんなメソッドがあると教えてくれる

f.hs
main = print $ take 5 $ repeat 3
$ hlint f.hs
f.hs:1:16: Warning: Use replicate
Found:
  take 5 $ repeat 3
Why not:
  replicate 5 3

1 hint

repeat 3は無限に3がつまったリストを作り、take 5で先頭5つを取り出す、結果[3, 3, 3, 3, 3]となる
replicate 5 335回繰り返してくれるので、やはり[3, 3, 3, 3, 3]となる

すげー!

ポイントフリースタイル

できるよ、って

左辺と右辺の最後の引数が同じ場合は省略できること
allTwicexsを渡すのがmap (*2)xsを渡すのと同じなら、つまりallTwicemap (*2)は同じ処理、みたいな?

pfs.hs
allTwice xs = map (*2) xs
$ hlint pfs.hs
pfs.hs:1:1: Warning: Eta reduce
Found:
  allTwice xs = map (* 2) xs
Why not:
  allTwice = map (* 2)

1 hint

allTwicemap (* 2)です、って書くのがCoolだ

冗長な無名関数(ラムダ式)

evenは1つのIntを受ける関数で、\x -> even xは1つのIntを受けてevenに渡すので、どちらも挙動は同じなんだ

lambda.hs
trimOdd :: [Int] -> [Int]
trimOdd xs = filter (\x -> even x) xs
$ hlint lambda.hs
lambda.hs:2:1: Warning: Eta reduce
Found:
  trimOdd xs = filter (\ x -> even x) xs
Why not:
  trimOdd = filter (\ x -> even x)

lambda.hs:2:16: Warning: Avoid lambda
Found:
  \ x -> even x
Why not:
  even

2 hints

\x -> even xevenでいいだろ!って出ているし、ついでにさっきのポイントフリースタイルの警告も出ているね

lambda.hs
trimOdd :: [Int] -> [Int]
trimOdd = filter even

これでtrimOddって関数はevenfilterするよ、って読めるね
こんな感覚で新たな関数を元からある関数を組み合わせるだけで作る、ってのは結構ある

Data.Function.on

なんかimportしてもいないのに型が合うと「こんな関数あるけどお前なんで使わないの?」って言ってくれる

on.hs
import Data.Function

data Card = Card { value :: Int, label :: String } deriving Show

sameValue x y = value x == value y

main = do
    let card1 = Card 1 "foo"
    let card2 = Card 1 "bar"

    print $ sameValue card1 card2
$ hlint on.hs
No hints

お...およよよよよよ

警告が出なくなってる...ヽ(´o`;モシモシ?
前と同じ感じで書いてると思うんだけどなー...

ちなみに、前に見た警告は以下みたいなのでした

Found:
x y -> value x == value y
Why not:
(==) `Data.Function.on` value

なになにわからん
onの型はなんじゃろ

ghci> :t Data.Function.on
Data.Function.on :: (b -> b -> c) -> (a -> b) -> a -> a -> c

なんだこれー
(ちなみに知人に コナミコマンド って言われたw)

どうやらこれが

sameValue x y = value x == value y

こうできるらしい

sameValue x y = (on (==) value) x y

aa(a -> b)bbにして、(b -> b -> c)cにするんだね
2つの値両方に同じ変換をした上で、その2つを何かにするって感じ

onはバッククオートで囲って演算子化して間に置くのがCoolです

sameValue x y = ((==) `on` value) x y

そして、いつものポイントフリースタイルで後ろの引数を2つとも消しちゃって

sameValue = (==) `on` value

こんな!
sameValueとは両方にvalueを適用してから(==)するって事だよ って宣言的になったね!すてき!

終わりに

ぶっちゃけ、onすげーーー!!!何こんなのまで言ってくれるの!!??
「なんでonつかわねーの?」とか言うけど、そんな標準ライブラリでもない関数シラネーwww

って去年くらいの感動をちゃちゃっと書くだけのつもりだったのに、まさかそれが出ないとは...
企画倒れも良いところ...

どなたか理由等わかる方がいたらご教授くださいm(_ _)m

ただまぁちょっと気まぐれでHLintかけるだけでも結構勉強になったりするので、
そういうことが出来るHaskellは「これマジオススメ」ってことで ノシ