iOSDC 2017 でさらっと出てきた Phantom Type さらっとやった話

これはなに

先日開催された iOSDC 2017 に参加しました記事です。

Ray Fix 氏の 視覚化とSwiftのタイプについて という発表で

protocol CoordinateSpace {}

enum ModelSpace:   CoordinateSpace {}
enum UserSpace:    CoordinateSpace {}
enum DeviceSpace:  CoordinateSpace {}

struct CGPointT<Space: CoordinateSpace>: Point2DProtocol {
  var xy: CGPoint
  // 実装
}

struct CGAffineTransformT<From: CoordinateSpace, To: CoordinateSpace> {
  var matrix: CGAffineTransform
  public func inverted() -> CGAffineTransforT<To, From> {
    return CGAffineTransformT<To, From>(matrix.inverted())
  }
}

public func * <From, To, I>(from: CGAffineTransformT<From, I>,
                            to: CGAffineTransformT<I, To>) -> CGAffineTransformT<From, To> {
  return CGAffineTransformT<From, To>(from.matrix.concatenating(to.matrix))
}

みたいなサンプルコードが出てきて

let modelToUser: CGAffineTransformT<ModleSpace, UserSpace> = ...
let userToDevice: CGAffineTransformT<UserSpace, DeviceSpace> = ...
let modelToDevice = modelToUser * userToDevice // CGAffineTransformT<ModelSpace, DeviceSpace>

これ型パラメータ実装ないし、実質処理はなにもしてなくない?

というのが、幽霊型 (Phantom Type) です。

Phantom Type

上のコードがほぼ全てですが、型の状態 (モデル空間にある頂点なのかユーザー空間にある頂点なのか) を型パラメータとして表現することで、実行時ではなくコンパイル時に状態を検査することができます。が、この型パラメータは実装では利用されておらずコンパイルしたら消えてしまうので、幽霊型 (Phantom Type) というわけです。洒落てますね。

状態に依存したコード

struct StateDependent {
    var ready: Bool = false

    func doPo() {
        precondition(ready, "Not ready!")
        print("Po")
    }
}

let stateDependent = StateDependent()
hoge.doPo() // Boom! Exception in runtime!

Phantom Type で状態を型で表現したコード

protocol State {}
enum Ready: State {}
enum NotReady: State {}

struct PhantomTypeSafe<S: State> {
    static func create() -> PhantomTypeSafe<NotReady> {
        return PhantomTypeSafe<NotReady>()
    }

    func toReady() -> PhantomTypeSafe<Ready> {
        return PhantomTypeSafe<Ready>()
    }
}

extension PhantomTypeSafe where S == Ready {
    func doPo() {
        print("Po")
    }
}

let phantomTypeSafe = PhantomTypeSafe<NotReady>.create()
//phantomTypeSafe.doPo() // Cannot compile
phantomTypeSafe.toReady().doPo()

Phantom Type = コンパイル時に型検査するためのタグのようなもの

上記サンプルだと、状態を表す変数 var ready をフィールドに持った型では、実行時までエラーが検出できませんが、Phantom Type を用いた型では extensionwhere 句による制約を設けることで、ある 型の状態 でないと呼べない関数を表現することができており、コンパイル時に静的にエラーにできます。

Ray Fix 氏のサンプルだと、CGAffineTransformTModelSpace でも UserSpace でも型としての性質や実装は変わらないので、下手に型を増やさずに Phantom Type で状態を表現して、変換同士の接続に破綻がないかを型で検証できています。

繰り返しになりますが、型パラメータ自体は利用されておらず、あくまで状態を表現する変数のようなものとして扱われているところがおもしろいところです。

Phantom Type で型の条件付け

これだけだとアレなのでもうひとネタ。

ジェネリクスのある言語ならこの Phantom Type のテクニックは使えると思うのですが、関数の引数に受け取る型に制約をつけるといえば他にもあった気がしてきました。型クラスですね

というわけで Scala で Phantom Type と impicit を組み合わせて、受け入れる型に制約をつけてみます。

sealed abstract class =:=[From, To] extends (From => To) with Serializable
private[this] final val singleton_=:= = new =:=[Any,Any] { def apply(x: Any): Any = x }

object =:= {
  implicit def tpEquals[A]: A =:= A = singleton_=:=.asInstanceOf[A =:= A]
}

def doPoWhenEqualType[A, B](implicit et: A =:= B) = "Po"

val po = hoge[Int, Int] // Po
// val po = hoge[Int, String] // compile error

上記の =:= の定義は実際に Predef という、Scala が標準で読み込む定義に含まれています。

Swift でもやってみました。

class TypeTag<T, U> {}

func doPoWhenEqualType<T>(_ tag: TypeTag<T, T>) -> String {
    return "Po"
}

//let po = doPoWhenEqualType(tag: TypeTag<Int, String>()) // Cannot compile
let po = doPoWhenEqualType(TypeTag<Int, Int>())

なんかもっとうまく書けそうな気がします…

というわけで、Phantom Type をさらっとだけ見てみました。

おしまい。

HyperX cloud 2 を買ってはいけない

先日購入した HyperX cloud 2 という バーチャル7.1サラウンドサウンド を謳う 誇大広告商品 があまりにも悪質なので記事を書きます。

結論: 消費者を騙すつもりの製品を売る HyperX ブランド、特に HyperX cloud 2 については完全に誇大広告・事実誤認を意図した商品であるため絶対に購入するべきではない

ゲーミングサラウンドサウンドヘッドセット

この記事を読む時点でゲーミング、とくに 7.1 ch のバーチャルサラウンドサウンドヘッドセットを探している人だと思うので詳細は省きますが、5.1 ch や 7.1 ch 等のマルチチャンネルオーディオは FPS 等のゲームで音の到来方向がわかりやすくて有利なんて話もあり、昨今の PC ゲーミング市場の盛り上がりもあっていろいろと製品が投入されています。

マルチチャンネルオーディオとサラウンドサウンド

そもそも 7.1 ch のサラウンドサウンドオーディオは、7 個のスピーカーを物理的に利用者の周囲に配置して、ゲーム内で後ろから鳴る音はちゃんと後ろのスピーカーから鳴る (から敵の位置とかがわかりやすい) というやつです。もちろんこの 7 個のスピーカーはそれぞれの方向の音源を代表しており、ほぼそれっぽくなるようにそれぞれのチャンネル (7.1 “ch” のチャンネルはこれのこと。スピーカーの数と思ってもらってとりあえずは問題ないです) ごとに計算して鳴らしています。

もちろんこのそれぞれのチャンネルのオーディオを計算するには ゲーム内の情報が必要です。どこで音が鳴っているかを知っているのはゲームだけであり、そのゲーム内の幾何情報をもとにどのスピーカーでどんな音を流せばいいかを計算することで、はじめてサラウンドサウンドが実現できます。当然 2 ch ステレオで生成されたオーディオから 7.1 ch の情報を再現することはできません

ヴァーチャルサラウンドサウンド(本物)

まともなヴァーチャルサラウンドサウンドヘッドセットは、入力ソース自体が当然マルチチャンネルオーディオとなっており、事前にゲーム側でゲーム内情報をもとに計算された 7.1 ch オーディオを、ある空間で (例えば部屋) などで 7 個のスピーカーを置いた場合に耳 = 2 ch の入力で聞くとどのように聞こえるかを計算して、2 ch のヘッドホンで聞いても 7 個のスピーカーから音がなっているように感じることができる、という技術です。(詳しくは伝達関数・とくに頭部伝達関数等で調べてください)

7.1 ch の信号から、それをあたかも部屋で聞いたかのように 2 ch の信号に変換することから各社 バーチャル サラウンドサウンドと呼称しています。実際には 7 個のスピーカーはないのに 7 個のスピーカーから音がなっているように感じるからヴァーチャル(実質的) 7.1 ch なわけです。重要なのは 7.1 ch の情報からシミュレートして 2 ch の情報に情報量を落とすことはできるが逆はできない ということです。

HyperX cloud 2 の事実誤認誘導 “ヴァーチャルサラウンドサウンド”

HyperX cloud 2 の web ページ等での広報文言を引用します。

バーチャル7.1サラウンドサウンド

バーチャル7.1サラウンドサウンドによりオーディオの精度がさらに上がり、ゲーミングにおいて優位性を高めることができます

Cloudゲーミングヘッドセット - Cloud Core、Cloud、Cloud II、CloudX | HyperX - 主な特徴

この文言を見て、みなさん HyperX cloud 2 は 7.1 ch のサラウンドサウンドの 2 ch への変換に対応していると感じたことと思います。しかしこれは上述の 7.1 ch ヴァーチャルサラウンドサウンドとは 全く異なる技術 であり、ユーザーが期待するような空間情報を持ったサラウンドサウンドは 一切実現できません。HyperX cloud 2 はそもそも 2 ch オーディオしか受け付けることができません。そして一度 2 ch に生成された音声信号から 7.1 ch 信号を復元することはできません

前述の通り、まともな 7.1 ch ヴァーチャルサラウンドサウンドは、もともとゲーム内の幾何情報を活用して作成された 7.1 ch の信号をもとに 7 個のスピーカーが鳴っているように感じさせる 2 ch 音源を生成する技術でした。

しかし、HyperX cloud 2 はもともとデバイスの仕様上 2 ch のオーディオしか受け付けることができないため、そもそも正確な位置情報を持つサラウンドサウンドを再現することは不可能です。では、HyperX cloud 2 の言う “バーチャル7.1サラウンドサウンド” とは何なのでしょうか?これが大変お粗末で、単に 2 ch の信号にリバーブ等の適当にぼんやり 7.1 ch 風に聞こえるエフェクトを加える機能 です。はっきり言っておもちゃです。FPS 等のゲームをする際には むしろ音の到来方向がわからなくなる最悪な機能 です。これが ゲーミング ヘッドセットだというのだから笑わせてくれます。

HyperX cloud 2 の悪質な点

これだけだったら無知なメーカーの広報が何かを間違ってマーケティングをしてしまっただけかもしれませんが、残念ながら HyperX は 明らかに意図的にこの不誠実な表現を用いており非常に悪質です

以下、HyperX およびそのブランドを展開している Kingston が “バーチャル7.1サラウンドサウンド” がヴァーチャル 7.1 ch サラウンドサウンドではないということを自覚した上で意図的に消費者を騙そうとしている証拠を挙げます。

もう気づいた方もいるかもしれませんが、HyperX cloud 2 の問題の機能名は “バーチャル7.1サラウンドサウンド” です。これは私の誤植ではありません。"バーチャル7.1 ch サラウンドサウンド" ではないのです。

HyperX cloud の紹介サイトでは巧みにこの 7.17.1ch という表現が 使い分けられています。そう、この表現は脱字などではなく、明らかに意図的に ch という表記が落とされています。

以下、HyperX cloud の紹介ページの冒頭部分の一部を引用します。

多彩なCloudシリーズのヘッドセットファミリーは、あらゆるゲーマーのニーズ、プレー環境、プレースタイル、パーソナルスタイルに対応するように設計されています。PCゲームでも据え置きゲーム機でも、7.1chバーチャルサラウンド対応ヘッドセットをお探しならCloudシリーズからご検討ください。

ここで言う “7.1chバーチャルサラウンド” というのはユーザーが探しているものであり、HyperX cloud 2 の特徴ではないため嘘はありません。私だって 7.1 ch ヴァーチャルサラウンドサウンドヘッドセットを探していました。HyperX はここではわざわざ ch の表記を用いています。

続いて、HyperX cloud 2 の問題の機能について紹介した文言を改めて引用します。

バーチャル7.1サラウンドサウンド

バーチャル7.1サラウンドサウンドによりオーディオの精度がさらに上がり、ゲーミングにおいて優位性を高めることができます。

ここではわざわざ ch を外し、"バーチャル7.1サラウンドサウンド" と表記しています。前述の通り、HyperX cloud 2 は 2 ch オーディオしか受け付けないため、7.1 ch と書けば なにが 7.1 ch なのかすぐさま問い詰められてしまうでしょう。そこで HyperX はここの ch を削除することでなんの意味だかわからない 7.1 という数字だけを付け加えるという作戦に出ます。7.1 は 7.1 であって、別に 7.1 ch とは言っていない、ということでしょう。これ以降も、製品の仕様に関わる部分については “バーチャル7.1サラウンドサウンド” という文言で逃げ切っています。

さらに、HyperX cloud 2 のこの事実誤認狙いの 7.1 表現を知った上で、上記の紹介文言を読むとその狡猾さがさらに浮き彫りになります。

紹介文言には

バーチャル7.1サラウンドサウンドによりオーディオの精度がさらに上がり、ゲーミングにおいて優位性を高めることができます。

とあり、サラウンドサウンドでゲームに優位になる = 音の定位 (音の到来方向) がわかりやすく優位になるという誤認を誘起するような内容になっていますが、

  • 定位や音の到来方向といった具体的表現を避ける
  • オーディオの “精度” という何を言っているかわからない表現を使う

等、巧妙に誇大広告と取られかねない表現を回避しています。

これらを見るに、HyperX が HyperX cloud 2 について、誠実に製品広報を行う気がないことは言い逃れようのないものだと思われます。

オーディオは「わかりづらい」

私も上記の HyperX の狡猾な戦略にハマり HyperX cloud 2 を購入してしまったのですが、それをはじめて使ったときの違和感と不信感というのはなんとも表現しがたいものがあります。

オーディオ沼や、もう少し強い言い方をすればオーディオオカルトという言葉を聞いたことがあるかと思います。デジタル信号を伝送するケーブルを変えると音質が変わるとか、発電所によって音色が変わるとか、高音質の SD カードとかいうアレです。大概のリテラシのある人は、こういった商品について「何を馬鹿な」と一蹴できることと思いますが、微妙なのはオーディオの良し悪しというのは実際非常にわかりづらいのです。だからこそこんなインチキ商売がなりたっているのです。こんなに高価だし良いものらしいが、違いがわからないのは自分の耳が悪いだけかもしれない。実際に良くなっているに違いない。という消費者の不安等につけ込む悪質なビジネスを許してしまったオーディオ業界の傲慢です。

HyperX cloud 2 を買ってきて “バーチャル7.1サラウンドサウンド” を試したときの私はまさにこの状態でした。体感としては明らかに定位が悪くなっているのです。しかしまさか…という思いでしばらく試行錯誤をすすめるものの、結論は “バーチャル7.1サラウンドサウンド” を切ったほうが音の定位がはっきりするということでした。この結論、自分の耳の正しさを信じるまでには非常に不安でストレスがかかりました。

その後、7.1 ch 音源の再生を試してみるとやはりフロント LR チャネルしか鳴らないなど疑念は確信に変わり、Kingston に対する “バーチャル7.1サラウンドサウンド” は単なる WOW エフェクトではないか? という問い合わせをした記事も発見し、HyperX cloud 2 が事実誤認を誘導する悪質な商品であることがわかりました

HyperX cloud 2 は決して買ってはいけない

ここまで説明してきたとおり、HyperX cloud 2 は「こう言っとけば勝手に騙されるだろう」という消費者を馬鹿にした態度で作られた製品であり、決して買うべきではありません。特に安価な商品でもないため、他のまともなメーカーのサラウンドサウンドヘッドセットを購入することをおすすめします。

HyperX は Kingston が展開するゲーミングブランドとのことですが、このような詐欺まがいの商品展開をするメーカーの品など不安で絶対に買えません。HyperX シリーズはもちろん、Kingston 製品を買うことも二度とないでしょう。

こういった不誠実なメーカーが淘汰されることを願ってやみません。

おまけ HyperX のお粗末な(しかしやはり悪質な)マーケティング

HyperX cloud のページには 賞歴という項目があります。普通の感性を持っていたら、まぁそれなりに権威のある(もしくはせめて金で買い付けたものでも)賞が並んでいるのかと思ったらそうではありません。なんと 商品レビューブログ 記事へのリンクなんです。しかもサンプルを渡して書かせたようなやつで、内容は推して知るべしというか素人ユーチューバーのそれを思い浮かべてください… 賞歴とは…

どうせ賞歴とかこんなところまともに読むやついないやろw とりあえずたくさんあったらすごそうじゃんw みたいな感じだったんですかね。徹底的に消費者を馬鹿にするそのスタイルにもはや清々しさすら感じます。

しかし最悪なのが、レビューしているのが本当にこういったことに疎い素人なのか、HyperX の狡猾な戦略にまんまとハマって 7.1 ch ヴァーチャルサラウンド みたいにブログ記事で書いちゃってるんですよね。地獄かよ…

というわけで貴重な時間を無駄にしました。おしまい。

20 分で IntelliJ IDEA と Gradle で Kotlin 開発環境を作って fatJar ビルドしてコマンドラインツールしたり Kotlin Playground したりする話

Kotlin 入門の入門

Google IO で Android の開発言語として Kotlin が正式に採用されたりなど、最近またにわかに注目を浴びている Kotlin ですが

  • 別に Android 開発がしたいわけじゃない
  • Better Java として Kotlin は触っておきたいけど、Android Studio 入れたり実機で動作させるのは面倒
  • Kotlin 自体を学ぶのに Android の周辺知識が必要なのはちょっと…

のように感じている人もわりといるのではないかと思います。

そういう人Scala をやるといいですよのための、Kotlin 入門のための入門記事です。

この記事では

  • Android に依存せず Kotlin が気軽に書ける IDE とビルドシステムの導入
  • Kotlin で非 Android アプリケーション開発・ビルド・fatJar パッキング
  • Swift PlayGround 的な Kotlin PlayGround 環境の構築

20 分でできる ことを目的とします。

扱わないこと

  • Kotlin 自体の言語仕様
  • Gradle ビルドシステムの詳細

IntelliJ IDEA のインストー

www.jetbrains.com

Kotlin は IntelliJ IDEA を開発している Jetbrains 社が開発している言語なので、IntelliJ でのサポートはばっちりというか、概ね IntelliJ をインストールしたら Kotlin の開発環境は整います。コンパイラ・Gradle こみなので他のダウンロードは不要です。

起動したら初期設定を適当に済ませてください。初期設定時におすすめプラグインとして Scala が出てくるので、インストールしておくと良いでしょう。

ここまで 5 分ぐらい

Gradle プロジェクトの作成と Gradle ビルド設定

プロジェクトの生成

IntelliJ を起動・初期設定したらさっそく Kotlin のプロジェクトを作ります。新しいプロジェクトの作成を選択して、プロジェクトのタイプで Gradle を選択します。扱うフレームワークとして Kotlin にチェックをいれる。

f:id:biacco42:20170524120920p:plain

Gradle 用に GroupId と ArtifactId を設定します。 GroupId は Java の package と同様にトップレベルドメインから。ArtifactId はアプリ名。

f:id:biacco42:20170524121526p:plain

プロジェクトの生成オプション。個人的には全部チェックつけるのがおすすめ。特に Create directries for empty content roots automatically は Gradle のビルド規約に則ったディレクトリを自動生成してくれるので、Gradle に慣れてない人はとりあえずチェックをつけておいたほうがよいです。

f:id:biacco42:20170524122911p:plain

最後に保存ディレクトリを決めて Finish。

エントリポイントの作成

Gradle の規約でソースコードsrc/main/言語/package階層 に配置します。まずは src/main/kotlin/ に適当なパッケージ(jp.co.biacco42.app 等)を追加し、そこに Kotlin ファイル Main.kt を追加します。

Main.kt に以下の通り記述します。

fun main(args: Array<String>) {
    println("Hello Kotlin")
}

Kotlin ではトップレベル関数が定義でき main(args: Array<String>) がいわゆる Java の Main クラスの public static void main(String[] args) エントリポイントに相当します。

Gradle のビルドスクリプト設定

上記プログラムの fatJar が吐けるように build.gradle の最後に以下を追記します。

task fatJar(type: Jar) {
    manifest {
        attributes 'Main-Class': 'jp.co.biacco42.app.tanoshiilife.MainKt'
    }

    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

manifest の attributes では、jar をそのまま起動できるように、エントリポイントを持つクラスを Main-Class として指定しています。

先程、Kotlin はトップレベル関数が定義できると書きましたが、Kotlin はあくまで JVM 向けのバイナリを生成するので、これらのトップレベル関数もコンパイルした時点でなんがしらのクラスに所属することになります。それが manifest に記載した MainKt です。Kotlin でトップレベル関数を定義すると ファイル名Kt というクラスが定義されて、そのクラスのスタティックメソッドとしてコンパイルされます。

ここまで 10 分ぐらい

ビルドとテスト、Kotlin Playground

早速上記のプロジェクトをビルド・実行してみます。

ビルド

IntelliJ の右上にある Gradle タブをクリックして開きます。

f:id:biacco42:20170524142816p:plain

先程追加した Gradle task fatJar は、Tasks -> other にあるので、これをダブルクリックすると Gradel task を実行できます。

f:id:biacco42:20170524162520p:plain

ビルド完了したら、パッキング済みの fatJar が <ProjectDirectry>/build/libs にできているはずです。実行してみましょう。

$ java -jar <ProjectDirectly>/build/libs/tanoshii-life-1.0.0-SNAPSHOT.jar

Hello Kotlin と表示されれば OK です。これで実行可能なバイナリを Kotlin でつくることができました。

ただ、毎回ビルドして実行はめんどくさいので、Gradle の test task を使って PlayGround 的なものを作ります。

ここまで 15 分ぐらい

Kotlin PlayGround をつくる

src/test/kotlin/PlayGround.kt というファイルを追加して、以下をコピペします。

import org.junit.Assert.assertEquals
import org.junit.*

class PlayGround {
    @Test
    fun playGround() {
        println("Hello Kotlin PlayGround")

        assertEquals(true, true)
    }
}

IntelliJ 右側の Gradle パネルから verification -> test をダブルクリックすると、テストが走って画面下部のテストの出力に Hello Kotlin PlayGround と表示されるはずです。

あとは、このファイルに好き勝手にクラスを足すなりなんなりして、playGround メソッドでいろいろつっつきまわせます。これで Kotlin の言語機能を手軽にお試しできると思います。

ここまで 20 分ぐらい

おわり

というわけで、Kotlin で簡単にコードを書く環境ができました。導入は基本 IDE をインストールするだけというお手軽さです。

Kotlin そのものについてはすでにおもしろい記事があるとおもうので、構築した環境でそれらの記事を読みながら手を動かすと理解が深まるかと思います。

qiita.com

qiita.com

おしまい