タプルをそれぞれの変数に展開するときにも `@` が使える
FP in Scala の日本語訳読んでます。
読んでることとは直接関係ないですが、例えばこんな関数があったとします。
// 第6章 純粋関数型の状態から // RNG は乱数生成の状態を表すオブジェクトだと思ってください。 def nextInt: (Int, RNG) = ???
この関数を変数に取る際、それぞれ別の変数に分割して取ることができます。
// i には生成した乱数が、nextRng には状態が取れる。 val (i, nextRng) = nextInt
「べつの変数にも分割して取りたいけど、パターンマッチみたいにタプルも一緒に取りたいなー」と思ってたら、同じ書き方で取ることができるようです。知らなかった・・・。
// tpl には結果のタプルがそのまま取れる。 val tpl @ (i, nextRng) = nextInt
私が知らなかっただけかもしれませんが、少し感動したのでブログ書きました。
C#の「nameof」をScalaで実装してみる
C# vNext に、nameof
という演算子が追加されています。
どういうものかというと、指定したメンバー名を string 型で受け取ることができるというとてもイケてる演算子です。
// こんなクラスがあって class Person { public string Name { get; set; } public int Age { get; set; } } // こんなことができる var p = new Person(); Console.WriteLine(nameof(p.Name)); // => "Name" Console.WriteLine(nameof(p.Age)); // => "Age"
参照先の記事にあるように、文字列を指定してリフレクションをしていたコードが安全に記述できるようになります。
「これって Scala のマクロ使えば似たようなことができるんじゃ?」と思ったので、実験してみました。
目標
インスタンスのメンバー名を取れるよりは、型を指定してそのメンバーを取れる方がいいなと思ったので、以下のコードでメンバー名を取得できることを目標にしました。
// こんなクラスがあって case class Person(name: String, age: Int) // こんな定義で def nameOf[A](f: A => Any): String = macro... // こんなことができる nameOf[Person](_.name) // => "name"
コード
注)Scala のマクロを書くのはほぼ初めてです。実験ですのでエラーハンドリング等はちゃんとやってません。
あまり説明もできませんので、コードを張って終わりにしたいと思います・・・。
まとめ
マクロの勉強不足なのでうまくかけていないところもありますが、とりあえず取得できるということが分かりました。 実用的に使えるようにしたいところです。
ScalaでC#のasync, awaitを実現するライブラリ「async」の紹介
この記事は Iwate Advent Calendar 2014 の22日目の記事です。昨日はnana4gontaさんのFirefox Developer Editionを使って他ブラウザをリモートデバッグする - Qiitaでした。明日はayokuraさんです。
岩手関連の記事を書こうと思い、仕事で使おうとしてるD3.jsを使って岩手県を書こうとしたらズバリそのままのサイトを発見して挫折しました。
岩手県ぬりえ
http://acuerdo.m18u.net/iwate_nurie/
結局思いつかなかったので全然関係ない話題を書きます。
続きを読む型パラメータ付きメソッドを持つトレイトとコンパニオンオブジェクト
タイトルはなんだか難しいですが、要は以下のコードのコンパイルを通したいということです。
trait Test { def apply[A](value: A): String } object Test { def apply[A](f: A => String): Test = new Test { def apply[A](value: A): String = f(value) } }
上記のソースだと、Testオブジェクト#applyの型パラメータAとTestトレイト#applyの型パラメータが別々のものなので、コンパイルが通りません。
error: type mismatch; found : value.type (with underlying type A) required: A def apply[A](value: A): String = f(value) ^
どうにかする方法はないでしょうか・・。
Play2.3でSecureSocialを使う(2014年6月時点)
※(2014年6月27日)この情報はすでに古く、Play2.3に対応した公式モジュールがリリースされている可能性があります。公式サイトやリポジトリをご確認ください。
PlayFrameworkに認証/認可機能を追加できるSecureSocial
というモジュールがあります。
- 公式サイト
SecureSocial - Authentication for Play Framework Applications
http://securesocial.ws/ - リポジトリ
jaliss/securesocial
https://github.com/jaliss/securesocial
SecureSocialの特徴は以下のとおりで、必要とされている機能はひと通り用意されており、認証機能を手軽に構築できるモジュールです。(ほぼ上記ページの内容ですが。)
- ScalaだけでなくJavaでも使える。
- Play2だけでなくPlay1でも使える。
- ユーザ/パスワード認証だけでなく、OAuth1,2やOpenIDにも対応。また、TwitterやFacebookなど向けのモジュールも用意されている。
- 既存アプリへの組込みも簡単。
- 拡張性があり、新たな認証にも対応できる。
- ユーザ登録時やパスワードリセット時などにメールを送信する機能が使える。
さて、このモジュールですが残念ながら現在(2014/06/27)時点でPlay2.3向けのモジュールが提供されていません。 現在の状況としては、PRは投げられていますがメイン開発者が他の機能に注力しているため、レビュー・取り込みされていません。
ですが、PR元のプロジェクトをsbtで参照することで、Play2.3に対応したSecureSocialが使えますので、今回はその手順をまとめてみました。
- Playプロジェクトを作成する
- Play2.3対応版SecureSocialをプロジェクト参照する
1. Playプロジェクトを作成する
まずは、空のプロジェクトを作成してきます。Play2.3からはplayコマンドではなく、Typesafe Activatorを利用してプロジェクトを作成するように変更されていますのでご注意ください。
今回は、play23-securesocial
というプロジェクト名でplay-scala
というテンプレートを利用して作成します。
> activator new play23-securesocial play-scala
Typesafe Activatorとは?
Typesafe Activatorとは、Scalaプロジェクトをあらかじめ用意されたひな形を利用して作成できるツールです。実際には、Webからソースコードを編集するUIなども用意されているようですが、ここでは説明しません。
Typesafe Activatorをインストールしていない方は、公式サイトZIPファイルをダウンロードし、任意の場所に展開します。 その後、実行ファイルにPATHを通せばactivatorコマンドが利用できるようになります。
PlayFrameworkのインストールに関するドキュメント
Installing
http://www.playframework.com/documentation/2.3.x/InstallingTypesafe Activatorのインストールに関するドキュメント
Build Reactive Applications with Typesafe Activator | Typesafe
http://typesafe.com/platform/getstarted
Typesafe Activatorをインストールしたくない!
Typesafe Activatorをインストールしたくない方は、@xuwei_kさんがズバリのブログ記事を公開していますので、そちらを参考にしましょう。
- typesafe activatorを使わないplayframework2.3の始め方 - scalaとか・・・
http://d.hatena.ne.jp/xuwei/20140531/1401525122
ただし、この記事ではTypesafe Activatorを利用して作成したプロジェクトを元に話を進めます。build.sbt
の中身がTypesafe Activatorを利用した場合と@xuwei_kさんの記事とで若干異なりますのでご注意ください。
2. Play2.3対応版SecureSocialをプロジェクト参照する
プロジェクトが作成できましたので、Play2.3に対応したSecureSocialを参照するように設定します。
といっても、冒頭に記載したとおりPlay2.3に対応したモジュールはまだリリースされていませんので、libraryDependencies
では参照できません。そのため、GitHubのリポジトリを直接参照するように、build.sbt
を書いていきます。
//build.sbtの一部 lazy val root = (project in file(".")).enablePlugins(PlayScala) .dependsOn(ProjectRef(uri("https://github.com/ewiner/securesocial.git#play-2.3"), "mainModule"))
dependsOn
以降を追加することにより、uriで指定した、Play2.3対応SecureSocialのGitプロジェクトが参照されます。
まとめ
Play2.3対応のSecureSocialについて調査していたところ、上記の方法で使えるという情報があったのでまとめてみました。 それより、sbtにGitプロジェクトを直接参照する機能があることを知らず感動しました。知らないことはまだまだいっぱいありますね・・。
認証/認可に必要な機能はひと通り揃っているモジュールですので、興味のある方はSecureSocialを使ってみてください。
FluentdのWindowsブランチに対してテストを流してみた
※Rubyはほとんど触ったことありません。
FluentdのWindowsブランチに結構手が入ってるようでしたので、テストを流してみました。
結果はこちら。
実行環境
2013-12-19現在のWindowsブランチのHEADに対してテストを実施しました。
- OS: Windows 8 x64
- Ruby: ruby 1.9.3p362 (2012-12-25) [i386-mingw32]
- Bundler: Bundler version 1.2.3
- Rake: rake, version 10.1.0
実行方法
何も考えずにbundle exec rake test
を実行すると、すぐエラーが発生してしまうので、以下のバッチファイルを作成して1ファイルずつテストを流しました。
@echo off @call bundle install for %%f in ("test\*.rb") do ( echo [Test start: %%f] @call bundle exec rake --trace test TEST='"%%f"' >test_log\%%f.log 2>&1 ) for %%f in ("test\plugin\*.rb") do ( echo [Test start: %%f] @call bundle exec rake --trace test TEST='"%%f"' >test_log\%%f.log 2>&1 ) pause exit /b 0
結果
プラグインで結構失敗が多いですね。中にはRuby側?なのか、[BUG] win32_mutex_lock: WAIT_ABANDONED
と出力されていました。
in_tail.rb
のテストで発生したエラーを追いかけてみると、shutdown
メソッドの@thread.join
を実行した際に上記のエラーが発生していました。Cool.io絡みなのかとおもいつつ、それを使っているプラグインにはテストが成功しているものもあるので、一概には言えない感じですね・・。Unixにひもづく処理だったり、ファイルを扱う処理なのでしょうか。
あと、そのままテストを流すと、おそらく$platformwin
がうまく設定されていない感じだったので、それも原因の一つかもしれないです。Issueに、Windows版の場合はソースを分けるというものが上がっていたので、その対応次第では通るテストも増えるかもしれません。
まとめ
やはりWindows対応は大変そうだなーという印象でした。もう少しRuby力をあげて、直せそうなところから手をつけていきたいです。
CSharpCodeProvider + MEFで実行時に生成したDLLを簡単に扱う
これは「C# Advent Calendar 2013」の18日目の記事です。
昨日は matarillo さんの「Java8とC# - 猫とC#について書くmatarilloの雑記」でした。
さいきん(やっと)、C#で手をつけてなかった async/await や、MEFなどを勉強しています。勉強している中で、CSharpCodeProviderとMEFを組み合わせたら実行時にDLLを生成してもその読み込みに苦労しないんじゃないかと思ったので、紹介しようと思います。動的コード生成ということで、yfakariyaさんとネタが被ってヒヤヒヤしました・・。
(もしかしたらこの組み合わせは普通なのかもしれませんが、個人的に感動したので記事にしちゃいます。)
もくじ
コンソールで入力した文字をそのまま返すクラスを動的に生成して、それをMEFで読み込んで実行してみようと思います。(実用性は・・・)
- 文字からコードを生成する
- 生成したコードからDLLを作成する
- MEFを使いDLLを読み込む
1. 文字からコードを生成する
CSharpCodeProvider
を使って、入力された文字からコードとDLLを生成します。
今回は、以下のインターフェースを実装したクラスを作成し、その中で入力された文字をそのまま返すコードを生成します。
public interface IMessage { string GetMessage(); }
さて、コンソールで入力された文字からコードを生成していきますが、今回はRazorEngine
を使ってみようと思います。説明は特に要りませんよね!?
PM> Install-Package RazorEngine
App_Data
ディレクトリを作成し、その中にテンプレートIMessageTemplate.razor
を作成します。
using System.ComponentModel.Composition; using CSharpCodeProviderAndMEF; [Export("@(Model.Id)", typeof(IMessage))] public class @(Model.Id)Message : IMessage { public string GetMessage() { return @@"@(Model.Message)"; } }
ポイントは、クラスにつける属性ExportAttribute
です。この属性をつけることで、MEFのカタログに公開するクラスであることを指定しています。また、ExportAttributeのプロパティに名前をつけることで、生成した後に名前を指定してインスタンスを取得できるようにしています。
Razorに渡すモデルとして、以下のクラスも作成します。
public class MessageModel { public string Id { get; set; } public string Message { get; set; } }
準備は整ったので、Razorを使ってコードを生成するクラスCodeFactory
を作成します。
public class CodeFactory { private const string TemplatePath = @"App_Data\IMessageTemplate.razor"; private const string CacheName = "message"; public CodeFactory() { Initialize(); } public string CreateCode(string id, string message) { var model = new MessageModel { Id = id, Message = message }; var code = RazorEngine.Razor.Run(CacheName, model); return code; } // private private void Initialize() { var template = File.ReadAllText(TemplatePath); RazorEngine.Razor.Compile<MessageModel>(template, CacheName); } }
このクラスを使ってコードを生成してみます。
class Program { static void Main() { var codeFactory = new CodeFactory(); while (true) { Console.Write("文字を入力してください。> "); var message = Console.ReadLine(); if (string.IsNullOrEmpty(message)) break; var id = CreateId(); var code = codeFactory.CreateCode(id, message); Console.WriteLine("==== {0} ====", id); Console.WriteLine(code); } } static string CreateId() { var id = Guid.NewGuid().ToString("N"); return "_" + id; } }
CteateId
メソッドでは、Guidからクラス名とMEFのカタログに公開する際の名前を生成しています。先頭にアンダーバーをつけているのは、クラス名は数値から始められないからです。
実行して、なにか文字を入力してみると、生成されたコードが表示されます。
2. 生成したコードからDLLを作成する
ソースコードの生成ができましたので、次はDLLを作成していきます。DLLの作成にはCSharpCodeProvider
を利用します。
CSharpCodeProvider クラス (Microsoft.CSharp)
@IT:.NET TIPS プログラムからソース・コードをコンパイルするには? - C# VB.NET
DLLを生成する処理については、ほぼ@ITの記事そのままですので、特に説明はしません。注意する点としては、参照するDLLを指定する際に、自分自身のexeファイルを指定する必要があることです。
class DllFactory { public const string ExtensionDirectoryPath = @"App_Data\Extensions"; private CSharpCodeProvider csc; public DllFactory() { Initialize(); } public CompilerResults Compile(string id, string code) { var parameter = CreateParameter(id); var result = csc.CompileAssemblyFromSource(parameter, code); return result; } // private private void Initialize() { if (!Directory.Exists(ExtensionDirectoryPath)) { Directory.CreateDirectory(ExtensionDirectoryPath); } csc = new CSharpCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v4.0" } }); } private CompilerParameters CreateParameter(string id) { var dllPath = Path.Combine(ExtensionDirectoryPath, id + ".dll"); var parameter = new CompilerParameters(new[] { "mscorlib.dll", "System.dll", "System.Core.dll", "System.ComponentModel.Composition.dll", "CSharpCodeProviderAndMEF.exe" // 自分を含めるのを忘れずに!! }, dllPath); return parameter; } }
このクラスを使って、DLLを生成するように、Mainメソッドを書き換えます。
static void Main() { var codeFactory = new CodeFactory(); var dllFactory = new DllFactory(); while (true) { Console.Write("文字を入力してください。> "); var message = Console.ReadLine(); if (string.IsNullOrEmpty(message)) break; var id = CreateId(); var code = codeFactory.CreateCode(id, message); var compileResult = dllFactory.Compile(id, code); Console.WriteLine("CompileResult: {0}", compileResult.NativeCompilerReturnValue); if (compileResult.NativeCompilerReturnValue != 0) { Console.WriteLine(string.Concat(compileResult.Output.Cast<string>())); } } }
実行してみると、文字を入力するたびにApp_Data\Extensions
ディレクトリ以下にDLLがポンポンと作成されていきますw
3. MEFを使いDLLを読み込む
DLLが作成できるようになったので、MEF (Managed Extensibility Framework)
を使い作成したDLLを読み込むクラスを作成していきます。
Managed Extensibility Framework (MEF)
MEFについては、okazukiさんのブログに入門記事がまとまっています。とても分かりやすいです。
Managed Extensibility Framework入門 まとめ - かずきのBlog@hatena
DLLを読み込む手順は、以下のとおりになります。
- カタログを作成する
- カタログを指定してコンテナを作成する
- インターフェースと名前を指定してコンテナからインスタンスを取得する
それぞれの細かい手順については、うえで紹介した入門記事にすべてまとまっています。(丸投げ)
上記の処理を実行するクラスMessageContainer
を作成します。
public class MessageContainer { private DirectoryCatalog dirCatalog; private CompositionContainer container; public MessageContainer() { Initialize(); } public IMessage Resolve(string id) { var instance = container.GetExportedValue<IMessage>(id); return instance; } public void Refresh() { // ディレクトリ以下の最新ファイルでカタログを更新する dirCatalog.Refresh(); } // private private void Initialize() { var asmCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); dirCatalog = new DirectoryCatalog(DllFactory.ExtensionDirectoryPath); var catalog = new AggregateCatalog(asmCatalog, dirCatalog); container = new CompositionContainer(catalog); } }
ポイントは、Refresh
メソッドです。RefreshメソッドではDirectoryCatalog#Refresh
を呼び出しています。このメソッドを呼び出すことで、コンストラクタで指定したディレクトリ以下に新しいDLLがあれば、それを含むようにカタログを更新してくれます。DLLを生成後にRefreshメソッドを呼び出すことで、毎回1からコンテナを作成する必要がありません。
Resolve
メソッドに、コード生成とDLL作成時に指定したIDを指定することで、生成したクラスのインスタンスが取得できます。そのインスタンスのメソッドGetMessage
を実行することで、入力した文字が取得できるようになります。
Mainメソッドを次のように書き換えます。ついでに時間も測っておきます。
static void Main() { var codeFactory = new CodeFactory(); var dllFactory = new DllFactory(); var container = new MessageContainer(); while (true) { Console.Write("文字を入力してください。> "); var message = Console.ReadLine(); if (string.IsNullOrEmpty(message)) break; var sw = Stopwatch.StartNew(); var id = CreateId(); var code = codeFactory.CreateCode(id, message); var compileResult = dllFactory.Compile(id, code); if (compileResult.NativeCompilerReturnValue != 0) { Console.Error.WriteLine(string.Concat(compileResult.Output.Cast<string>())); continue; } container.Refresh(); var instance = container.Resolve(id); Console.WriteLine( "from MEF. [Message: {0}, Time: {1}]", instance.GetMessage(), sw.Elapsed); }
実行してみると、マシンの性能によりますが、コード生成からメッセージ出力までだいたい200ミリ秒くらいでできます。
また、一度生成したDLLについてはMEFのコンテナでキャッシュされていますので、時間がかかりません。IDをキャッシュして、コンソールからの入力がなかった場合は前回のメッセージを出力するように修正してみます。
static void Main() { var codeFactory = new CodeFactory(); var dllFactory = new DllFactory(); var container = new MessageContainer(); string preId = null; while (true) { Console.Write("文字を入力してください。> "); var message = Console.ReadLine(); var sw = Stopwatch.StartNew(); if (string.IsNullOrEmpty(message)) { var preInstance = container.Resolve(preId); Console.WriteLine( "cached MEF. [Message: {0}, Time: {1}]", preInstance.GetMessage(), sw.Elapsed); continue; } var id = CreateId(); var code = codeFactory.CreateCode(id, message); var compileResult = dllFactory.Compile(id, code); if (compileResult.NativeCompilerReturnValue != 0) { Console.Error.WriteLine(string.Concat(compileResult.Output.Cast<string>())); continue; } container.Refresh(); var instance = container.Resolve(id); Console.WriteLine( "from MEF. [Message: {0}, Time: {1}]", instance.GetMessage(), sw.Elapsed); preId = id; } }
まとめ
CSharpCodeProvider と MEFを組み合わせることで、実行時に生成したDLLを簡単に扱う方法を紹介しました。今回の例は特に役に立たないと思いますが、これを使うことでLINQPadのようなものが簡単に作れます。実際に、Fluentdを使ってMongoDBに集めたログなどのデータに対して、入力したLINQを利用して検索・集計するWebツールを作ってます。
CSharpCodeProviderではなく、いま話題(?)のRoslynなどを使えば、もっと面白いことができるかもしれないですね。まだ追いかけてませんが・・・。
ということで、CSharpCodeProvider + MEFを組み合わせる方法の紹介でした。明日は yfakariya さんです。
今回のソースコードは、以下においてあります。