shokosブログ

プログラミング

Haskell Platform 2011.4.0.0をいれたよ!

Haskellのwebフレームワークyesodをいれようと思ったらattoparsecまわりの依存関係で怒られてインストールできず。(とあるライブラリはattoparsec0.10以上が必要だけど別のライブラリは0.10未満が必要なので競合するとかそんな感じで怒られた。ヤムチャ言いやがって・・・。)


解決しようと模索しているうちにだんだんゴチャゴチャになってきたので、いっそHaskell環境まっさらにすることに。


今月リリースしたばっかりの、Haskell Platform 2011.4.0.0 64 bit をmac(雪豹)に入れました。
ダウンロードはここから。Download Haskell


前まで2.0.xだったのに、いきなり4.0.0に。3どこいってもうたんや。


たぶん今回からだと思うのだけど、アンインストールのコマンドが用意されていた。


下記コマンドを叩くと、過去のバージョンのghc(7.0.3以下)と、cabal install してきたライブラリを全消去できる。

$ sudo /Library/Haskell/bin/uninstall-hs thru 7.0.3 --remove

これ便利!
なぎはらえる!!



あとLion完全対応したとの噂なので、これを機にアップデートしようかな。

定数固有メソッド実装

Enumで抽象メソッドを定義して、それを定数ごとにOverrideできるってことを、今日Effective Java読んではじめて知りました。
定数固有メソッド実装というらしい。かっこいい。

具体的にはこんな書き方ができる。

package jp.ne.hatena.syoko_sasaki;

public enum Character {

	AZU {
		@Override
		String mederu(String name) {
			return name + "にゃんぺろぺろ";
		}
	},
	RUIZU {
		@Override
		String mederu(String name) {
			return name + "!" + name + "!" + name + "!" + name
					+ "ぅぅうううわぁああああああああああああああああああああああん!!! あぁああああ…ああ…あっあっー!あぁああああああ!!!"
					+ name + name + name + "ぅううぁわぁああああ!!!";
		}
	};

	abstract String mederu(String name);

}

Java書き始めて2年半たつけど知らなかった・・・。
Effective Javaしっかり読もう。おもしろいし。

剰余もfor文も使わないでHaskellでfizzbuzzを書いてみた

遅延評価バンザイヽ(^o^)丿

fizz = cycle ["","","fizz"]
buzz = cycle ["","","","","buzz"]

fizzbuzz = zipWith fb [1..100] $ zipWith (++) fizz buzz
        where fb n a
                | a == "" = show n
                | otherwise = a

cycleは循環リストを作る関数なので、GHCiでなにげなく試すとけっこう慌てる。

windowsでHaskellのIOまわりで日本語を文字化けなく表示させる方法

ghc7.0.2

utf8-stringをインストール。
こちら参照
http://hackage.haskell.org/cgi-bin/hackage-scripts/package/utf8-string

cabal install utf8-string


System.IO.UTF8のモジュールを見ると、だいたいIO周りが揃っている!

appendFile      hGetLine        openBinaryFile  readFile
getContents     hPStr         print           readLn
getLine         hPStrLn       pStr          withBinary
hGetContents    interact        pStrLn        writeFile


試しに

Prelude> import System.IO.UTF8 as S
Prelude System.IO.UTF8> S.writeFile "test.txt" "にゃん"

うまくいった!

CSVファイルで各行の先頭を見て、重複した値があったら取り出すスクリプト

業務で必要になったので、勉強がてらHaskellで書いてみた。

import Data.List.Split

main = do
	csv <- readFile "./foo.csv"
	print $ check $ dataList csv

--各行の先頭をリスト化する
dataList :: String -> [String]
dataList csv = foldr (\x xs -> (head (splitOn "," x)) : xs) [] (lines csv)

--リスト内に重複したデータがあるかチェック
check :: [String] -> [String]
check [] = []
check (x:xs) | elem x xs = x : (check xs)
	     | otherwise = check xs
lines csv

行ごとにリストにする処理がたったこれだけで書けるのにちょっぴり感動した。

checkは再帰で書いたけれど、もう少し考えれば高階関数使って書けそうだな。

Haskellが短く書けてステキな件について

(主に私のまわりで)流行っているからというミーハーな理由でHaskellを勉強中です。

単純に短く書けてすごいなと思います。
例えば、リストを引数に渡して、偶数の要素だけ返す関数なんかこれだけでおk。

evenList :: [Int] -> [Int]
evenList [] = []
evenList (x:xs)| even x = x : evenList xs
		| otherwise = evenList xs

リスト内包表記を使うともっと短い。

evenList :: [Int] -> [Int]
evenList xs = [ n | n <- xs, even n]

リスト内包表記はプログラマ厨二病心にドンピシャだったのだけど、複雑な処理の場合わかりずらい見た目になりそうで、気をつけようと思った。



ちなみに重複を取り除けなんて仕様変更があってもへっちゃらです。

evenList3 xs = nub $ evenList xs

ダウンロードしたcsvファイルの中身が想定通りかどうか確かめるテスト

wicketでつくったページからダウンロードしたcsvファイルの中身が想定通りかどうか確かめるテスト!!!

mainのパッケージに置いたshokos.csvをダウンロードして、testのパッケージに置いたまったく同じ内容のexpected.csvと比較するテストを書きます。

ファイルの中身は1行目がヘッダーで、あとにデータが続く構成です。

名前,年齢,職業
shokos,20,フラワーアレンジメント
hoge,22,学生

ページのclass

package jp.ne.hatena.syoko_sasaki;

import java.io.IOException;
import java.io.OutputStream;

import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.request.target.resource.ResourceStreamRequestTarget;
import org.apache.wicket.util.io.IOUtils;
import org.apache.wicket.util.resource.AbstractResourceStreamWriter;

public class DownloadPage extends WebPage {

	public DownloadPage(PageParameters parameters) {

		Link<Void> downloadLink = new Link<Void>("download") {

			private static final long serialVersionUID = 1L;

			@Override
			public void onClick() {
				this.getRequestCycle().setRequestTarget(
						new ResourceStreamRequestTarget(
								new AbstractResourceStreamWriter() {
									private static final long serialVersionUID = 1L;

									public String getContentType() {
										return "application/octet-stream;charset=MS932";
									}

									public void write(OutputStream output) {
										try {
											IOUtils.copy(
													DownloadPage.class
															.getResourceAsStream("shokos.csv"),
													output);
										} catch (IOException e) {
											e.printStackTrace();
										}
									}
								}, "shokos.csv"));

			}
		};
		add(downloadLink);
	}

}

対応するhtmlファイル

<html>
    <head>
        <title>ダウンロード画面</title>
    </head>
    <body>
        <strong>ダウンロード</strong>
        <br/><br/>
		<button wicket:id="download">CSV出力</button>
    </body>
</html>


テストコード

package jp.ne.hatena.syoko_sasaki;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.protocol.http.MockHttpServletResponse;
import org.apache.wicket.util.io.IOUtils;
import org.apache.wicket.util.tester.WicketTester;
import org.junit.Before;
import org.junit.Test;

public class DownloadPageTest {

	WicketTester tester = new WicketTester();

	@Before
	public void before() {
		tester.startPage(DownloadPage.class);
	}

	@Test
	public void ページ表示() throws Exception {
		tester.assertRenderedPage(DownloadPage.class);
		tester.assertComponent("download", Link.class);
	}

	@Test
	public void ダウンロードできること() throws Exception {
		tester.clickLink("download");
		String dir = System.getProperty("java.io.tmpdir");
		MockHttpServletResponse response = tester.getServletResponse();
		assertThat(response.getContentType(),
				is("application/octet-stream;charset=MS932"));
		File file = new File(dir + File.separator + "shokos.csv");
		FileOutputStream responceOutputStream = new FileOutputStream(file);
		responceOutputStream.write(response.getBinaryContent());
		responceOutputStream.close();
		assertCsvFile(dir + File.separator + "shokos.csv", "expected.csv");
	}

	private void assertCsvFile(String actualFile, String expectedFile) {
		String actual = null;
		String expected = null;
		try {
			actual = IOUtils.toString(new InputStreamReader(
					new FileInputStream(actualFile), "MS932"));
			expected = IOUtils.toString(
					getClass().getResourceAsStream(expectedFile), "MS932");
		} catch (IOException e) {
			e.printStackTrace();
		}
		List<Map<String, String>> actualList = parse(actual);
		List<Map<String, String>> expectedList = parse(expected);

		assertThat(actualList.size(), is(expectedList.size()));
		for (int i = 0; i < actualList.size(); i++) {
			Map<String, String> acutualMap = actualList.get(i);
			Map<String, String> expectedMap = expectedList.get(i);

			assertThat(acutualMap.size(), is(expectedMap.size()));
			for (String key : expectedMap.keySet()) {
				assertThat(acutualMap.get(key), is(expectedMap.get(key)));
			}
		}
	}

	List<Map<String, String>> parse(String csv) {
		String cammma = ",";
		List<String> lines = Arrays.asList(csv.split("\r"));

		String headLine = lines.get(0);
		String[] header = headLine.split(cammma);
		lines = lines.subList(1, lines.size());

		List<Map<String, String>> list = new ArrayList<Map<String, String>>();

		for (String line : lines) {
			Map<String, String> map = new HashMap<String, String>();
			String[] split = line.split(cammma);
			for (int i = 0; i < split.length; i++) {
				map.put(header[i], split[i]);
			}
			list.add(map);
		}
		return list;
	}

}


parseメソッドで、
ファイル1行目(ヘッダー)をカンマで区切ってkeyとし、配列で持つ。
2行目以降をカンマで区切って、keyと一緒にMapにputしていく。
1行分のMapができたらListにaddする。

この処理をダウンロードしたファイルと想定ファイル2つ分して、各リストの要素を比較!