Skip to content

Java (Scala) で言語判定

Java (Scala) で言語判定 published on Java (Scala) で言語判定 へのコメントはまだありません

やりたい事

  • Java (Scala) で、ある文字列が何語(日本語、英語、など)なのかを判別する
  • 入力文字列は以下の2通り
    • ユーザーから入力された検索文字列(1単語、数文字〜数単語、数十文字)
    • 検索対象となる文章(数十単語〜数十ページ程度)
  • 対応する言語
    • 当初は日本語と英語
    • 今後は5言語程度

なぜこれをやりたいかは、以下のエントリーを参照。

Elasticsearch多言語化その2 – K blog

Continue reading Java (Scala) で言語判定

国際化

i18n: 言語リソース名の命名規則

i18n: 言語リソース名の命名規則 published on i18n: 言語リソース名の命名規則 へのコメントはまだありません

テキストを多言語化する方法は大まかに2パターン

サイト・webサービスなどのテキストを多言語する方法はいくつかあって、詳細はググってもらうとして、(分類の仕方も色々あるけど)大雑把に以下の2つに分けられると思う。

  • gettext のように、ソースファイルに含まれる翻訳元言語(英語が多い)のテキストを _() や __() といった関数で囲んで、それを抽出して他言語のリソースを作成する
  • Java の ResourceBundle のように、任意の識別子(キー)と多言語対応が必要なテキスト(値)のペアからなるエントリーを含んだリソースファイルを各言語毎に作成し、それを使用する

(言葉で説明すると分かりにくいので、上の説明でピンとこない方は gettext, Java ResourceBundle などで検索してほしい。)

で、今回は後者に関して、キーの命名規則を自分達はこうしてるっていう話を書く。

ネットであまりベストプラクティス的なのが見当たらなかったので、とりあえず今やってる方法を晒して、他の人からの意見とかをもらって改善出来れば良いなあという意図。

Continue reading i18n: 言語リソース名の命名規則

API

色んなWebサービスのAPIを使ってみて

色んなWebサービスのAPIを使ってみて published on 色んなWebサービスのAPIを使ってみて へのコメントはまだありません

背景: 沢山のWebサービスのAPIを使ってる

色んなサービスのデータを集めて、横串検索出来るようにしてる

GitHub (issue, PR のコメント, wiki ページ), Slack, Google Drive etc. のデータをAPI経由で集めてきて、横串検索出来るようなサービスを作ってる。

こちら → Commet

経緯はこんな感じ。

  • こんなサービスあったら便利かも。作ってみよう。
  • とりあえず出来た。うん。そこそこ便利。
  • デザインとかできてないけど、ブログで軽く告知してみよう。→ 特に反応無し。
  • まぁ、誰も使ってくれなくても、最悪自分で使ってるし、ちょこちょこ改善して、適当なタイミングでもう一度宣伝しよう
  • 自分で使いつつ、色々修正(長い時間が経過・・・)
  • いい加減、デザインとか綺麗にして、一般公開しよう。← イマココ

ということで、もう少し体裁を整えてから、ちょっと宣伝とかしてみようと思ってる。

が、今回の本題は、技術的な話。

当然、色んなAPIを使ってる

色んなサービスからデータを集めるので、当然、色んな API を使わなければならない。現在、以下のサービスのAPIを使って、データを取ってきている。

  • GitHub (issue, PR, wiki)
  • Slack
  • Bitbucket (issue, wiki)
  • ChatWork
  • Backlog (issue, PR, file)
  • Google (Drive, Gmail)
  • Facebook (グループ)

(その他、webhook 経由で Redmine にも対応しているけど、詳細は省略。)

これだけ色々使ってみると、それぞれの API の癖や、長所・短所などが見えてくるので、そういう事について書いてみる。

Continue reading 色んなWebサービスのAPIを使ってみて

Play! frameworkのJsPath.readNullableの挙動

Play! frameworkのJsPath.readNullableの挙動 published on Play! frameworkのJsPath.readNullableの挙動 へのコメントはまだありません

はじめに

Play! framework の JSON ライブラリって、慣れればまぁ普通に使えるけど、ドキュメントがイマイチだし、やりたいことをどう書けばいいかが分からなくて時間を使うことが多い。

今回書くのは、JSONデータである要素が存在しない場合にそれをどう扱うか、という話題。

「なんだ readNullable 使うだけでしょ?」

と思う人もいるかもしれない。まぁ実際にはそうなんだけど、readNullable の挙動が若干分かりにくかったので、それについて。

やりたいこと

JSONデータで、ある要素が存在する場合と存在しない場合が考えられるとき、その要素が存在する場合は Some 、存在しない場合は None としてパースしたい。

具体的な例で説明すると、Facebook APIから以下のようなJSONが返ってくるとする。内容はあるFacebookグループへの書き込み。

Continue reading Play! frameworkのJsPath.readNullableの挙動

Comparing nullable columns in Squeryl

Comparing nullable columns in Squeryl published on Comparing nullable columns in Squeryl へのコメントはまだありません

How to compare nullable columns in Squeryl

Suppose we have a table definition like this:

case class T1 (
  id: Int,
  col1: Option[Int]
)
object FooDb extends Schema {
  val t1 = table[T1]("t1")
}

And, we want to execute a query like the following:

select * from t1 where col1 < 10

The correct statement in Squeryl is shown below:

from(FooDb.t1)( t1 =>
  where(t1.col1 lt Some(10))
  select(t1)
)

This isn’t very intuitive, and actually, it took me a lot of time to get to this answer.

Here are some that I tried and failed

The most intuitive one doesn’t compile:

  where(t1.col1 lt 10) // -> doesn't compile

This one causes NoSuchElementException:

  where(t1.col1.get lt 10) // -> NoSuchElementException

Another one that causes NoSuchElementException:

  where(t1.col1.map(_ lt 10).get) // -> NoSuchElementException

Official site should have example

I’ve found some guys that were having the same issue:

I think the Squeryl web site should have an example that uses nullable column in the where clause.

Scala や Play! framework で DI

Scala や Play! framework で DI published on Scala や Play! framework で DI へのコメントはまだありません

自分用メモ

 

PHPからGoogle Spreadsheets APIを使う

PHPからGoogle Spreadsheets APIを使う published on PHPからGoogle Spreadsheets APIを使う へのコメントはまだありません

同じことやるの2度目なのに、すっかり忘れてしまっていたので、3度めに備えてメモ。

やりたいこと

(MySQLに入っている)あるデータを、Google Spreadsheets APIを使ってスプレッドシートに書き込む。

使うもの

以下のライブラリを使う。

Continue reading PHPからGoogle Spreadsheets APIを使う

elastic4s でエラー処理

elastic4s でエラー処理 published on elastic4s でエラー処理 へのコメントはまだありません

Scala から Elasticsearch を操作するクライアントライブラリ

Elasticsearch のサイトには、以下の4つが紹介されている

更新具合その他を考えて、elastic4sを使うことにした。

Elastic4s の使い方 → RTFM

基本的な使い方は以下を参照。

エラー処理

ドキュメントだと、エラーが起きた時にどう処理をすれば良いかが書いていない。

例えば、documentを追加するコードは

esClient.execute {
   index into s"index1/type1" fields ("field1" -> "val")
}

みたいに書くけど、エラーが起きたらどうするんだろうと思った。

上の式のシグニチャーをみると Future[IndexResponse] らしい。IndexResponse は Elasticsearch の API で提供されている。ドキュメントはこの辺

ドキュメントに追加できなかった場合は、isCreated メソッドが false を返す。それ以外に、サーバー側でエラーが起きたりとかした場合には例外が投げられる。

ということで、例えば以下のように処理できる。

import com.sksamuel.elastic4s.ElasticDsl._
import org.elasticsearch.action.index.IndexResponse
import org.elasticsearch.transport.RemoteTransportException

val f: Future[Boolean] = esClient.execute {
   index into s"index1/type1" fields ("field1" -> "val")
} map (_.isCreated) recover {
  case e: RemoteTransportException =>
    Logger.error(e.getMessage)
    false
}

この辺はニーズに応じて好きなようにすれば良いかなと。

その他

コードをみたけど、結構ちゃんと書かれていた(と個人的には思った)。

まとめ?

Elasticsearch 便利。elastic4s も便利。

Cordova 3.6 + Geolocation plugin in iOS 8

Cordova 3.6 + Geolocation plugin in iOS 8 published on Cordova 3.6 + Geolocation plugin in iOS 8 へのコメントはまだありません

Cordova + Geolocation Plugin で iOSアプリを作ってる

Cordova で iOS アプリを初めて作ってる。JavaScript は比較的得意なので、HTML + JS で簡単にアプリが作れて便利。

スマホアプリの場合、位置情報機能を使ったものが多くて、その場合には Cordova の Geolocation plugin というものが使えるんだけど、2点ほどつっかえたところがあったのでメモ。

環境

  • Apache Cordova 3.6.3
  • Geolocation plugin 0.3.11
  • (多分あまり関係ないけど)Xcode 6.1.1

対象プラットフォームは、今の所 iOS のみ。

インストールとか

通常通り plugin をインストールすればいいので、省略。

使ってみたら、エラーが2つほど

1. plist に何か必要らしいが・・・

一つ目のエラーは、以下の様なログ。

[Warning] No NSLocationAlwaysUsageDescription or NSLocationWhenInUseUsageDescription key is
 defined in the Info.plist file.

ググってみると、SOにこんなのが。 Info.plist に記述を追加する必要があるらしい?

でも、この辺や、オフィシャルの情報(これこれ)見るとpluginがiOSに対応したって書いてあって、ドキュメントにも特にplistを直接いじるような事も書いてない。

結論から言うと、最新のプラグインを使っていれば問題ない。

自分の場合は、plist を手動でゴニョゴニョ修正するhookを使っていたので、それが原因だった。

2. getCurrentPosition が失敗

ようやくプラグインが動作したと思ったら、以下の様なエラーが。

1行目はプラグインが直接コンソールに出力した(?)ログで、2行目と3行目は、navigator.geolocation.getCurrentPosition の第2引数のコールバック(エラー時に呼ばれる)関数の中で、受け取ったエラーコードとエラーメッセージを表示させたもの。

locationManager::didFailWithError (null)
code: 2
message: The operation couldn<E2><80><99>t be completed. (kCLErrorDomain error 0.)

これは、どうやらiPhone Simulator のみで起こる問題っぽい。SOのこれを見て、ダメ元でDebug -> Location で “Custom Location” 以外を選択したら問題が解消した。その後、再度 Custom Location を選択しても問題なし。

まとめ

Cordova + Geolocation plugin は便利だけど、エラーが起きた時の情報がちょっと少なくて、予想外に時間がかかってしまうことがあるなぁと。

Scalaの型引数とかサブ型とか

Scalaの型引数とかサブ型とか published on Scalaの型引数とかサブ型とか へのコメントはまだありません

コンパイルが通らない

以下のコードのコンパイルが通らないので、どうしたものかと質問した。

abstract class C
case class C1() extends C
case class C2() extends C

trait Service[T <: C] {
  def getFoo(): Option[T]
}

object Service {
  // found   : List[Option[Any]]
  // required: List[Option[T]]
  def getAllFoos(services: List[Service[_]]): List[Option[C]] = {
    services.map(_.getFoo())
  }
}

(答えだけ知りたい場合は、@gakuzzzz さんのコメントが簡潔かつ完全な内容なのでそちらを参照。)

エラーの原因

さて、エラーの原因は @gakuzzzz さんの解説にある通り。

ポイントは trait Service[T <: C] にした場合、 Service[C] と Service[C1] と Service[C2] が全然関係ない型になってしまうので、統一的に扱えないって所ですね。

(なぜ全然関係ない型になってしまうのだろう。調べないと。)

試行錯誤

@xuwei-k さんに教えてもらった方法

@xuwei-k さんに教えてもらった方法だとコンパイルは通った。

object Service {
  def getAllFoos[A <: C](services: List[Service[A]]): List[Option[C]] = {
    services.map(_.getFoo())
  }
}

ただ、元のgistで端折ってしまったんだけど、@gakuzzzz さんが書いたように、実際にはServiceは以下のように継承して使うことを前提としてる。

object C1Service extends Service[C1] { def getFoo(): Option[C1] = None }
object C2Service extends Service[C2] { def getFoo(): Option[C2] = None }

servicesにC1ServiceとC2Serviceが混ざったものを渡すとエラーになった。

scala> Service.getAllFoos(List(C1Service))
res4: List[Option[C]] = List(None)

scala> Service.getAllFoos(List(C2Service))
res5: List[Option[C]] = List(None)

scala> Service.getAllFoos(List(C1Service, C2Service))
<console>:17: error: no type parameters for method getAllFoos: (services: List[Service[A]])List[Option[C]] exist so that it can be applied to arguments (List[Service[_ >: C2 with C1 <: Product with Serializable with C]])
 --- because ---
argument expression's type is not compatible with formal parameter type;
 found   : List[Service[_ >: C2 with C1 <: Product with Serializable with C]]
 required: List[Service[?A]]
              Service.getAllFoos(List(C1Service, C2Service))
                      ^
<console>:17: error: type mismatch;
 found   : List[Service[_ >: C2 with C1 <: Product with Serializable with C]]
 required: List[Service[A]]
              Service.getAllFoos(List(C1Service, C2Service))

エラーの意味がよく分からないが、「Service[C] と Service[C1] と Service[C2] が全然関係ない型になってしま」ってるという事なのかな。

@gakuzzzzさんに書いてもらった方法1

こちら

abstract class C
case class C1() extends C
case class C2() extends C

trait Service[+T <: C] { // ここがポイント
  def getFoo(): Option[T]
}

object Service {
  def getAllFoos(services: List[Service[C]]): List[Option[C]] = {
    services.map(_.getFoo())
  }
}

object C1Service extends Service[C1] { def getFoo(): Option[C1] = None }
object C2Service extends Service[C2] { def getFoo(): Option[C2] = None }

Service.getAllFoos(List(C1Service, C2Service))

うまく行った。

これを trait Service[+T <: C] とすれば、Service[C1]Service[C2]Service[C] のサブ型と見做せるようになるので Service[C] として統一的に扱えるようになります。

とのこと。

@gakuzzzzさんに書いてもらった方法2

gistはこちら

abstract class C
case class C1() extends C
case class C2() extends C

trait Service {
  type T <: C
  def getFoo(): Option[T]
}

object Service {
  def getAllFoos(services: List[Service]): List[Option[C]] = {
    services.map(_.getFoo())
  }
}

object C1Service extends Service { type T = C1; def getFoo(): Option[C1] = None }
object C2Service extends Service { type T = C2; def getFoo(): Option[C2] = None }

Service.getAllFoos(List(C1Service, C2Service))

こちらに関しても先生のコメント。

型引数を止めて、抽象メンバ型でやれば、Service という型は一つになるので、こちらも Service で統一的に扱えるようになります。 type T = C1 が必要なのはこの場合ですね。

使い分け

これも先生のコメントそのまま書いとく。

共変を使うか、抽象メンバ型を使うか、の判断基準としては、「C1に特化したServiceの型を扱いたいかどうか」になるかと思います。

なるほど。

考えたこと

これだけだと、@xuwei-k さんと @gakuzzzz さんのコメントを切り貼りしただけなので、もう少し書いておく。

そもそもの動機

実際のコードだと、C1とかC2はユーザーの外部アカウントとの連携情報で、例えば以下の様なもの(簡略化してるけど)。どれとも連携していない人もいれば、全てと連携している人もいる。

  • Facebook連携の情報: UserFacebookInfo
  • Twitter連携の情報: UserTwitterInfo
  • GitHub連携の情報: UserGitHubInfo

それぞれが抽象クラスUserExternalAccountInfoを継承している。

で、Serviceに当たるのはいくつかあるけど、例えばDAOで、それぞれ違うテーブルにデータが入っているので(それぞれ user_facebook_info, user_twitter_info, user_github_info テーブル)、別々のDAOを用意した。以下の感じ。

trait ExternalAccountInfoDao[T <: UserExternalAccountInfo] {
  def getInfo(userId: Int): Option[T]
}

object ExternalAccountInfoDao {
  def getAllAccounts(userId: Int): List[Option[UserExternalAccountInfo]] = {
    val daos = List(UserFacebookInfoDao, UserTwitterInfoDao, UserGithubInfoDao)
    daos.map(_.getInfo(userId))
  }
}

object UserFacebookInfoDao
 extends ExternalAccountInfoDao[UserFacebookInfo] {
  def getInfo(userId: Int): Option[UserFacebookInfo] = { /* code */ }
}
// UserTwitterInfoDao, UserGithubInfoDao も同様

(多分もっと良い設計がありそうなので、コメント大歓迎です。)

その他

コメントで書いてもらったコードを見れば内容は分かるんだけど、自分であういうコードがパッと出てこないところが実力不足だと思った。

人のコードを沢山読んだり、定石(いわゆるデザインパターン)をもっと勉強しないといけないなと思った。