Haxe 2 から 3 へ

Haxe のバージョンを 2.10 から 3.0.0 に更新した。
Haxe 2 の時のコードが結構動かなくなったので、変更点をメモ。

interface 継承の変更

implements -> extends

  • haxe 2
interface Hoge {}
interface Piyo implements Hoge {}
  • haxe 3
interface Hoge {}
interface Piyo extends Hoge {}

複数 interface を実装する場合の指定の変更

implements A, implements B -> implements A implements B

  • haxe 2
interface A {}
interface B {}
class C implements A, implements B {}
  • haxe 3
interface A {}
interface B {}
class C implements A implements B {}

構造的部分型の配列を作る際の初期化子の型が厳密化

構造体と型 で、「えー」っていってたのができなくなった。

  • haxe 2
using Lambda;
typedef X = { id: Int, name: String, msg: String, hoge: Int }
class Hoge {
	public static function hoge(x: List<X>) {}
	public static function main() {
		hoge([{ name: "hoge" }, { msg: "hoge" }].list());
	}
}
  • haxe 3
using Lambda;
typedef X = { id: Int, name: String, msg: String, hoge: Int }
class Hoge {
	public static function hoge(x: List<X>) {}
	public static function main() {
		hoge([
			{ id: null, name: "hoge", msg: null, hoge: null },
			{ id: null, name: null, msg: "hoge", hoge: null },
		].list());
	}
}

ヘテロな配列を作る際に、Array の指定が必須

  • haxe 2
class Hoge {
	public static function main() {
		var as = [{ name: "hoge" }, { msg: "hoge" }];
		trace(as);
	}
}
  • haxe 3
class Hoge {
	public static function main() {
		var as: Array<Dynamic> = [{ name: "hoge" }, { msg: "hoge" }];
		trace(as);
	}
}

Std.format 廃止

Std.format("~") -> '~'

  • haxe 2
class Hoge {
	public static function main() {
		var x = 10;
		var str = Std.format("${x}");
		trace(str);
	}
}
  • haxe 3
class Hoge {
	public static function main() {
		var x = 10;
		var str = '${x}';
		trace(str);
	}
}

window などが、js.Lib -> js.Browser に移動

  • haxe 2
class Hoge {
	public static function main() {
		var url = js.Lib.window.location.href;
		trace(url);
	}
}
  • haxe 3
class Hoge {
	public static function main() {
		var url = js.Browser.window.location.href;
		trace(url);
	}
}

Array の first メソッド等廃止

  • haxe 2
class Hoge {
	public static function main() {
		var xs = [1, 2, 3];
		var x = xs.first();
		trace(x);
	}
}
  • haxe 3
class Hoge {
	public static function main() {
		var xs = [1, 2, 3];
		var x = xs[0];
		trace(x);
	}
}

Void 型の扱いが変更

  • haxe 2
using Lambda;
class Hoge {
	public static function main() {
		var f: Void -> Int = function(x) { return 1; };
		var xs: List<Void> = [null, null].list();
		var ys = xs.map(f);
		trace(ys);
	}
}
  • haxe 3
class Hoge {
	public static function main() {
		var f: Void -> Int = function() { return 1; };
		var xs: List<Void> = [null, null].list();
		//var ys = xs.map(f); // error: Void -> Int should be (Void) -> Unknown<0>
		//trace(ys);
	}
}

関数型内の Dynamic で Void が受け取れなくなった

  • haxe 2
using Lambda;
class Hoge {
	public static function hoge(x: List<Dynamic>) {}
	public static function piyo(f: Int -> Dynamic) {}
	public static function main() {
		var xs: List<Void> = [null, null].list();
		hoge(xs);
		var f: Int -> Void = function(x) {};
		piyo(f);
	}
}
  • haxe 3
using Lambda;
class Hoge {
	public static function hoge(x: List<Dynamic>) {}
	public static function piyo<T>(f: Int -> T) {}
	public static function main() {
		var xs: List<Void> = [null, null].list();
		hoge(xs);
		var f: Int -> Void = function(x) {};
		piyo(f);
	}
}

Hash 廃止

Hash -> Map

  • haxe 2
class Hoge {
	public static function main() {
		var m: Hash<Int> = new Hash();
		m.set("hoge", 1);
	}
}
  • haxe 3
class Hoge {
	public static function main() {
		var m: Map<String, Int> = new Map();
		m.set("hoge", 1);
	}
}

生成されるJSがお行儀よくなった

  • "use strict" がつくようになった
  • 無名関数にくくられ、グローバルを汚さないようになった

Haxe側にあるパッケージと同じ名前空間をJS側からexportしたい場合にうまく動かない

ローカル変数で実現されているパッケージが、グローバルの名前を隠蔽してしまう。

hoge/aa/AA.hx

package hoge.aa;
extern class AA {
	public static function create(): AA;
}

hoge/Hoge.hx

  • haxe 2
package hoge;
import hoge.aa.AA;
class Hoge {
	public static function main() {
		var a = AA.create();
		trace(a);
	}
}
  • haxe 3
package hoge;
import hoge.aa.AA;
class Hoge {
	public static function main() {
		//var a = AA.create(); // runtime error: Cannot read property 'AA' of undefined
		var a: AA = untyped __js__("window.hoge.aa.AA.create()");
		trace(a);
	}
}

prototype でつけた関数をファーストクラスで使うと this がしぬ

はまりかけた。
コンストラクタで設定すればおk。

function Hoge() {
    var me = this;
    me.print1 = function() {
        console.log(me);
    };  
}
Hoge.prototype.print2 = function() {
    console.log(this);
}
var hoge = new Hoge();
hoge.print1(); // Hoge {print1: function, print2: function}
hoge.print2(); // Hoge {print1: function, print2: function}

var apply = function(f) {
    f();
};
apply(hoge.print1); // Hoge {print1: function, print2: function}
apply(hoge.print2); // undefined


昔々にこれではまっていたようだ。

型パラメータとミックスイン

  • Mix.hx
interface A {}
interface B {}
class C implements A, implements B {
	public function new() {}
}
class Mix {
	public static function hoge(x: A) {}
	public static function piyo(x: B) {}
}
  • Hoge.hx
using Mix;
class Hoge {
	public static function main() {
		var c = new C();
		c.hoge();
		c.piyo();
	}
}

こんな感じで、2つ以上のインタフェースに対してミックスインをつけることもできる。

インターフェースが型引数を持っていても、一つなら大丈夫そう。

  • Mix.hx
interface A {}
interface X<T> {}
class C implements X<A> {
	public function new() {}
}
class Mix {
	public static function hoge(x: X<A>) {}
}
  • Hoge.hx
using Mix;
class Hoge {
	public static function main() {
		var c = new C();
		c.hoge();
	}
}


が、下は通らない。

  • Mix.hx
interface A {}
interface B {}
interface X<T> {}
class C implements X<A>, implements X<B> {
	public static function new() {}
}
class Mix {
	public static function hoge(x: X<A>) {}
	public static function piyo(x: X<B>) {}
}
  • Hoge.hx
using Mix;
class Hoge {
	public static function main() {
		var c = new C();
		c.hoge();
		c.piyo();
	}
}
  • 実行
$ haxe hoge.hxml
Hoge.hx:5: characters 2-8 : C has no field hoge

piyo はあるらしい。
どうも、最後の implememt だけ有効みたいだが・・・

えー。

構造体と型

typedef X = { id: Int, name: String }
class Hoge {
	public static function hoge(x: X) {}
	public static function main() {
		hoge({ id: 0 });
	}
}

これはエラー。

$ haxe hoge.hxml
Hoge.hx:5: characters 7-16 : { id : Int } has no field name
Hoge.hx:5: characters 7-16 : For function argument 'x'

フィールド足りないし、まぁ分かる。

これも駄目。

using Lambda;
typedef X = { id: Int, name: String, msg: String, hoge: Int }
class Hoge {
	public static function hoge(x: List<X>) {}
	public static function main() {
		hoge([{ name: "hoge" , msg: "hoge" }].list());
	}
}

まぁ、そうだよな。


しかし、下は通る。

using Lambda;
typedef X = { id: Int, name: String, msg: String, hoge: Int }
class Hoge {
	public static function hoge(x: List<X>) {}
	public static function main() {
		hoge([{ name: "hoge" }, { msg: "hoge" }].list());
	}
}

えー。

type 演算子?

プログラム中のどこでも,ある表現の型を知るためには,type 演算子を使うことができます。コンパイル時に,type 演算子は除去され,表現だけが残ります :

var x : Int = type(0);

この例ではコンパイル時に Int と表示され,type が使われなかった場合と同じようにコンパイルされます。
これは,クラスやドキュメントを参照せずに型を手早く知るためには便利です。

Type Inference - Haxe

だそうだが、

  • hoge.hxml
-js hoge.js
-main Hoge
  • Hoge.hxml
class Hoge {
	public static function main() {
		var x : Int = type(0);
	}
}
  • 実行
$ haxe hoge.hxml
Hoge.hx:3: characters 16-20 : Unknown identifier : type

えー。

今のバージョンでYesodアプリを動かすまで

なんかうまく動かなくなってたので、メモ。

まずは、Haskell Platform を確認。
気がついたら64bitが入っていた。
多分 2012.4.0.0 にするときに誤って入れたらしい。
一旦今の Haskell 環境を消して、

$ sudo /Library/Haskell/bin/uninstall-hs --remove thru 7.4.2
$ rm -rf ~/.cabal/ ~/.ghc/ ~/cabal-dev/
$ rm -rf ~/Library/Haskell/
$ sudo rm -rf /Library/Haskell/
$ sudo rm -rf /Library/Frameworks/GHC.framework

で、32bitをいんすこした。


次。cabal-dev を入れる。

$ cd ~/Downloads
$ git clone git@github.com:creswick/cabal-dev.git
$ cd cabal-dev
$ cabal install


次。yesod-platform を入れる。

$ cd
$ cabal-dev install yesod-platform


次。PATHを当てる。
自分の場合は .bashrc を弄る。
(そのうち zsh に移ろうと言い続けて何年だろう・・・)

export PATH=〜:$HOME/cabal-dev/bin:$HOME/Library/Haskell/bin:〜:$PATH


ここまでで yesod コマンドが使えるはずなので確認。

$ yesod version
yesod-core version:1.1.6.1
yesod version:1.1.4.1


適当に yesod アプリを作ってみる。

$ yesod init
... (適当に hoge っていうプロジェクトを作ってみた)


で、ビルド。

$ cd hoge
$ cabal-dev install
...
Preprocessing library http-conduit-1.8.5.1...
[ 1 of 11] Compiling Network.HTTP.Conduit.Util ( Network/HTTP/Conduit/Util.hs, dist/build/Network/HTTP/Conduit/Util.o )
[ 2 of 11] Compiling Network.HTTP.Conduit.Chunk ( Network/HTTP/Conduit/Chunk.hs, dist/build/Network/HTTP/Conduit/Chunk.o )
[ 3 of 11] Compiling Network.HTTP.Conduit.Types ( Network/HTTP/Conduit/Types.hs, dist/build/Network/HTTP/Conduit/Types.o )
[ 4 of 11] Compiling Network.HTTP.Conduit.Parser ( Network/HTTP/Conduit/Parser.hs, dist/build/Network/HTTP/Conduit/Parser.o )
[ 5 of 11] Compiling Network.HTTP.Conduit.ConnInfo ( Network/HTTP/Conduit/ConnInfo.hs, dist/build/Network/HTTP/Conduit/ConnInfo.o )
***/cabal-dev//lib/Crypto/Random/AESCtr.hi
Declaration for $wmakeParams:
  Bad interface file: ***/cabal-dev//lib/Crypto/Cipher/AES.hi
      Something is amiss; requested module  cipher-aes-0.1.5:Crypto.Cipher.AES differs from name found in the interface file cryptocipher-0.3.7:Crypto.Cipher.AES
Cannot continue after interface file error
Failed to install http-conduit-1.8.5.1
Configuring classy-prelude-conduit-0.4.2...
...
cabal: Error: some packages failed to install:
hoge-0.0.0 depends on http-conduit-1.8.5.1 which failed to install.
authenticate-1.3.2 depends on http-conduit-1.8.5.1 which failed to install.
clientsession-0.8.0.1 failed during the building phase. The exception was:
ExitFailure 1
http-conduit-1.8.5.1 failed during the building phase. The exception was:
ExitFailure 1
http-reverse-proxy-0.1.0.7 depends on http-conduit-1.8.5.1 which failed to install.
yesod-1.1.7 depends on http-conduit-1.8.5.1 which failed to install.
yesod-auth-1.1.3 depends on http-conduit-1.8.5.1 which failed to install.
yesod-core-1.1.6.1 depends on clientsession-0.8.0.1 which failed to install.
yesod-default-1.1.3 depends on clientsession-0.8.0.1 which failed to install.
yesod-form-1.2.0.2 depends on clientsession-0.8.0.1 which failed to install.
yesod-json-1.1.2 depends on clientsession-0.8.0.1 which failed to install.
yesod-persistent-1.1.0.1 depends on clientsession-0.8.0.1 which failed to install.
yesod-static-1.1.1.1 depends on clientsession-0.8.0.1 which failed to install.


どうも http-conduit のビルドでこけている。
cipher-aes-0.1.5 の Crypto.Cipher.AES と、cryptocipher-0.3.7 の Crypto.Cipher.AES があって、
モジュール名が同じじゃねーか!的なエラー。

http-conduit は cprng-aes に依存していて、
cprng-aes はデフォルトでは cipher-aes に依存するようになっている。
また、http-conduit は tls に依存していて、
tls は cryptocipher に依存している。

それとは別に、yesod init によって作られたプロジェクトは
clientsession に依存するようになっていて、
clientsession は cryptocipher に依存している。

cprng-aes の設定を見てみると、Flag によって cipher-aes ではなく cryptocipher に依存するようにできるらしい。


ということで、cprng-aes だけ設定を変えて先に入れるようにして
再度ビルドしてみる。

$ rm -rf cabal-dev/ 
$ cabal-dev install -f -fastaes cprng-aes 
$ cabal-dev install 

無事ビルドできた。


さて 動かしてみる。

$ yesod --dev devel
Yesod devel server. Press ENTER to quit
Resolving dependencies...
Configuring hoge-0.0.0...
Rebuilding application... (using cabal-dev)
Building hoge-0.0.0...
Preprocessing library hoge-0.0.0...
In-place registering hoge-0.0.0...
ERROR: Could not read BuildInfo file: dist/setup-config
Make sure that cabal-install has been compiled with the same GHC version as yesod.
and that the Cabal library used by GHC is the same version
cannot parse contents
yesod: ExitFailure 1

・・・あれ?

$ dist/build/hoge/hoge Development
Migrating: CREATE TABLE "user"("id" INTEGER PRIMARY KEY,"ident" VARCHAR NOT NULL,"password" VARCHAR NULL,CONSTRAINT "unique_user" UNIQUE ("ident"))
Migrating: CREATE TABLE "email"("id" INTEGER PRIMARY KEY,"email" VARCHAR NOT NULL,"user" INTEGER NULL REFERENCES "user","verkey" VARCHAR NULL,CONSTRAINT "unique_email" UNIQUE ("email"))
127.0.0.1 - - [23/Dec/2012:16:43:43 +0900] "GET / HTTP/1.1" 200 - "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.101 Safari/537.11"
^C

こっちは動く。


ま、とにかく動かせるようになったので、今回はここまで。

同じようなフィールドを持ったレコードへの変換

RecordWildCards を使って、同じようなフィールドを持つレコードを簡単に変換できる。

  • RecordA.hs
module RecordA where

data A = A { a :: Int, b :: String, c :: Int } deriving Show
  • RecordB.hs
module RecordB where

data B = B { a :: Int, b :: String, c :: Int } deriving Show
  • RecordC.hs
module RecordC where

data C = C { a :: Int, b :: String, c :: String } deriving Show
  • RecordMain.hs
{-# LANGUAGE RecordWildCards #-}
module Main where

import qualified RecordA
import qualified RecordB
import qualified RecordC

createA :: RecordA.A
createA = RecordA.A {..}
 where
  a = 1
  b = "b"
  c = 100

-- A をそのまま B に変換
toB1 :: RecordA.A -> RecordB.B
toB1 (RecordA.A {..}) = RecordB.B {..}

-- A の一部を上書きして B に変換
toB2 :: RecordA.A -> RecordB.B
toB2 (RecordA.A {..}) = RecordB.B {..}
 where
  b = "bb"

-- A を C に変換 (C.c = "aaa") (A.c は使わない)
toC1 :: RecordA.A -> RecordC.C
toC1 (RecordA.A {..}) = RecordC.C {..}
 where
  c = "aaa"

-- A を C に変換 (C.c = show A.c)
toC2 :: RecordA.A -> RecordC.C
toC2 (RecordA.A { c = ca, ..}) = RecordC.C {..}
 where
  c = show ca

main :: IO ()
main = do
  print $ createA
  print $ toB1 $ createA
  print $ toB2 $ createA
  print $ toC1 $ createA
  print $ toC2 $ createA
  • 実行
$ runhaskell RecordMain.hs
A {a = 1, b = "b", c = 100}
B {a = 1, b = "b", c = 100}
B {a = 1, b = "bb", c = 100}
C {a = 1, b = "b", c = "aaa"}
C {a = 1, b = "b", c = "100"}


DTO/Entity 変換や、DTO 同士の変換に良さげ。

Haskellの言語拡張たち 2

前のやつ の続き。

今回調べた拡張

  • レコード系
    • RecordWildCards
  • 型系
    • ExistentialQuantification

RecordWildCards

レコードワイルドカードが使える。
レコードパターン中で .. とすれば、レコード内のフィールドを一気に束縛したり、現在のスコープの変数から与えたりできる。

{-# LANGUAGE RecordWildCards #-}

data Ele = Ele { a :: Int, b :: Int, s1 :: String, s2 :: String }

-- RecordWildCards 拡張が必要
create :: Ele
create = Ele { b = 2, ..}
 where
  a = 3
  s1 = "hoge"
  s2 = "piyo"

-- RecordWildCards 拡張が必要
str :: Ele -> String
str (Ele { a = 1, ..}) = s1
str (Ele { a = 2, ..}) = s2
str (Ele { a = 3, ..}) = s1 ++ s2
str (Ele {..}) = s1

main = putStrLn . str $ create
$ runhaskell record_wildcarts.hs
hogepiyo

ExistentialQuantification

存在量化されたデータ構築子が書ける。
型クラスをインタフェースのように使った動的多態のようなことができる。

{-# LANGUAGE ExistentialQuantification #-}

-- ExistentialQuantification 拡張が必要
data Ele = forall a . (Show a) => Ele a

instance Show Ele where
  show (Ele x) = show x

eles :: [Ele]
eles = [Ele "hoge", Ele 1, Ele (3,6), Ele $ Just 2, Ele [4,3], Ele ["a", "b"], Ele 1.23]

main = putStrLn . unlines $ show `map` eles
$ runhaskell existential_quantification.hs
"hoge"
1
(3,6)
Just 2
[4,3]
["a","b"]
1.23