2019年こそAkka.NET + F#の最強(当社比)の組み合わせがどうにか流行ってほしい

はじめに

あけましておめでとうございました

1月・2月は弊社にとっては2018年の13月と14月みたいなものだったので、実質3月から2019年がスタートした感じです。

2019年も相変わらず不遇な扱いのF#と日本での知名度はもやはゼロに等しいAkka.NETですが、個人的には大好きな技術スタックなので勘を取り戻すために復習してみました。

参考文献

mikhail.io

www.fsharpreactivepatterns.com

ベース

ますは最も単純な、アクターにレコードを投げつけるだけのサンプルです。

アクターの定義がいまいちよくわからない定義方法になってますが、弊社もよくわかっていません。

open System
open Akka
open Akka.FSharp

(* アクターシステムを生成 *)
let system = System.create "fsharp" <| Configuration.load ()

(* アクター間でやり取りを行うメッセージを定義 *)
type Value =
    | Value of value: int

(* メッセージを受け取ってコンソールに出力するアクターの定義 *)
let printToConsole (mailbox: Actor<_>) =
    let rec loop ()  = actor {
        let! Value(value) = mailbox.Receive ()  
        printfn "Value is %A" value
        return! loop ()
    }
    loop ()

[<EntryPoint>]
let main argv =
    
    (* アクターの生成 *)
    let processorRef = spawn system "printToConsole" printToConsole

    (* アクターにメッセージを送信 *)
    processorRef <! Value(42)
    
    (* アクターシステムを停止 *)
    system.Terminate().Wait()
    0

C#でAkkaを使うとき一番イマイチなのが、アクター間のメッセージを定義するときなんですよね。

アクター間でやり取りされるメッセージはスレッドセーフじゃないといけないからクラスを定義するときに不変性に気を配らないとならないってのが面倒なんですよね。 だからと言って適当に定義してのちのち地雷を踏むのも嫌だし。

F#ならレコードなり判別共用体で不変なデータ構造を一撃で定義できるし、メッセージの値のパターンマッチや値の束縛を走らせるのもお家芸なのでAkkaはF#の方が書きやすいよねって思います。

フィルタリング

メッセージを消費するアクターの前段にメッセージをフィルタするアクターを配置します。 ここでは生命、宇宙、そして万物についての究極の疑問の答えのみを通すフィルターを定義して配置します。

open System
open Akka
open Akka.FSharp
open Akka.Actor

(* アクターシステムを生成 *)
let system = System.create "fsharp" <| Configuration.load ()

(* アクター間でやり取りを行うメッセージを定義 *)
type Value =
    | Value of value: int

(* メッセージを受け取ってコンソールに出力するアクターの定義 *)
let printToConsole (mailbox: Actor<_>) =
    let rec loop ()  = actor {
        let! Value(value) = mailbox.Receive ()  
        printfn "Value is %A" value
        return! loop ()
    }
    loop ()

(* 述語を満たすメッセージのみを次のアクターに渡すアクターの定義 *)
let filter (predicate: 'a -> bool) (next: IActorRef) (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        if predicate(message) then
            next <! message
        return! loop ()
    }
    loop ()

[<EntryPoint>]
let main argv =
    
    (* アクターの生成 *)
    let printToConsole = spawn system "printToConsole" printToConsole

    let pred42 (value: Value) =
        match value with
        | Value(42) -> true
        | _ -> false

    let filter42 = spawn system "filter42" (filter <| pred42 <| printToConsole)

    (* アクターにメッセージを送信 *)
    filter42 <! Value(42)
    filter42 <! Value(43)
    
    (* アクターシステムを停止 *)
    system.Terminate().Wait()
    0

やっていることは単純で、あらかじめ述語を受け取り述語を満たすメッセージのみを後段に流すアクターを定義しているだけです。

変換

open System
open Akka
open Akka.FSharp
open Akka.Actor

(* アクターシステムを生成 *)
let system = System.create "fsharp" <| Configuration.load ()

(* アクター間でやり取りを行うメッセージを定義 *)
type Value =
    | Value of value: int

(* メッセージを受け取ってコンソールに出力するアクターの定義 *)
let printToConsole (mailbox: Actor<_>) =
    let rec loop ()  = actor {
        let! Value(value) = mailbox.Receive ()  
        printfn "Value is %A" value
        return! loop ()
    }
    loop ()

(* 述語を満たすメッセージのみを次のアクターに渡すアクターの定義 *)
let filter (predicate: 'a -> bool) (next: IActorRef) (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        if predicate(message) then
            next <! message
        return! loop ()
    }
    loop ()

(* 指定されたコンバータを適用して次のアクターに渡すアクターの定義 *)
let convert (converter: 'a -> 'a) (next: IActorRef) (mailbox: Actor<_>) =
    let rec loop () = actor {
        let! message = mailbox.Receive ()
        next <! (converter <| message)
        return! loop ()
    }
    loop ()

[<EntryPoint>]
let main argv =
    
    (* アクターの生成 *)
    let printToConsole = spawn system "printToConsole" printToConsole

    let twice (value: Value) =
        match value with
        | Value(v) ->  Value(v * 2)

    let converter = spawn system "converter" (convert <| twice <| printToConsole)

    let pred42 (value: Value) =
        match value with
        | Value(42) -> true
        | _ -> false

    let filter42 = spawn system "filter42" (filter <| pred42 <| converter)

    (* アクターにメッセージを送信 *)
    filter42 <! Value(42)
    filter42 <! Value(43)
    
    (* アクターシステムを停止 *)
    system.Terminate().Wait()
    0

github.com

おわり

Shift_JISと死の文字化け

はじめに

この授業は闇の魔術に対する防衛術の21時限目です。

昨日の授業@aimof先生でした。

Windowsが覇権を握る現代社会では、たとえLinuxmacOSユーザでもShift_JISに関わらずに生きることは非常に困難です。*1

さらに他の文字コードと異なりWindowsでは正しく表示されるが、なぜかLinuxに持ってきたとたん(主に苗字が*2*3)マッハで化けてフロアが不夜城と化すことも珍しくありません。

そんな闇の魔術ことShift_JISと正しく戦う術を皆さんと共有出来たらと思います。

Shift_JIS is 何?

そもそもShift_JISとは何でしょう。はい、ハーマイオニー

JIS X 0201JIS X 0208を符号化文字集合とした文字符号化方式エンコーディングスキーム)*4

素晴らしい。

知らない単語が幾つか出てきましたので、一つずつ確認していきましょう。

ちなみにShift_JISであって、Shift-JISではありません。今後間違えるごとに5点ずつ減点です。

符号化文字集合文字符号化方式

厳密に言ってしまうと『文字コード』という用語が持つ意味はその『文字コード』が登場する文章のコンテキストに依存します。つまり、定義が無くみんなが思い思いにフワッと使用してるに過ぎません。

いわゆる文字コードは以下の用語のどちらかもしくは両方を指す用語として使用されることが多いです。

ただ、JIS X 0201JIS X 0208は規格中で文字符号化方式も規定しているので文字符号化方式でもあります。が、実際使われているかと言うとほとんど使われていない*6ので符号化文字集合として扱います。

ちなみに、UnicodeはUCS(Universal Coded Character Set(符号化文字集合))とUTF(Unicode Transformation Format(文字符号化方式))等を含んだ規格の名称です。

JIS X 0201

規格名は『7ビット及び8ビットの情報交換用符号化文字集合』でISO/IEC 646に準拠した規格となります。

ASCII文字*7にいわゆる半角カタカナを追加した文字集合です。

まぁ、こちらは特に複雑な事もないので軽くスルーしましょう。

JIS X 0208

規格名は『7ビット及び8ビットの2バイト情報交換用符号化漢字集合』で、何気に世界初のマルチバイト符号化文字集合で結構凄いやつなんです。

で、この情報交換用というのが厄介で、『高』『髙』、『神』『神』のように意味の同じで異なる字体*8包摂の概念の元、規格としては同じものとして扱うことになっています。

髙田さんや神崎さんは激おこぷんぷん丸かもしれませんが、現実問題そうしないと規格として定義する文字が膨大になり規格に準拠するコストが爆上げになります。*9

つまり、『高』と『髙』、『神』と『神』はJIS X 0208の規格上は等価です。また、『﨑』は収録されていないため『崎』で代用するほかありません。*10

また、JIS X 0208には符号空間に空きがあり、当事者間で合意がとれていれば空いている空間に任意の文字を追加することが出来ます。

Shift_JISとCP932

CP932はWindows上でのShift_JISの実装になります。つまり、Windows上でShift_JISというとCP932を指すことになります。

で、歴史的な背景*11があり幾つかの文字が追加されています。 はい、そうです。『髙』や『﨑』などの漢字、また『①』のような丸付き数字は純粋なShift_JISでは表現できないCP932(JIS X 0208)に独自に追加された文字なのです。

つまり、Windows上で追加文字を含むテキストファイルを作成し、それをLinux等に転送後にShift_JISとして解釈させると化けます。

対策

じゃあどうすればいいのでしょうか。

例えばWindowsLinuxとデータをやり取りする場合を考えてみましょう。 すると以下のパターンが考えられます。

  1. そもそもShift_JISとして転送しない(最初から最後までUTF-8等のUnicodeを使用する)
  2. 強い意志でCP932の追加文字を使わない・使わせない(Shift_JISを使用することの当事者間の合意を形成する)
  3. すべての闇を受け入れてCP932を使用する(CP932を使用することの当事者間の合意を形成する)

『﨑』等の追加文字を使用しなければLinux側でShift_JISとして解釈しても化けることはありません。 が、これはビジネス要件に依存する上にステークホルダーを納得させるのは割と難しいです。

UTF-8等のUnicodeを使用すればCP932の追加文字も正しく扱える上に、平成も終わる今日では一般的な文字コードですのでLinux側の対応もしやすいです。 ただ、Windows側でUTF-8に対応してない等の事情があると使えません。(ExcelCSVとかExcelとかExcelとか)

最終手段としてLinux側でもCP932を正しく扱えるように頑張るというのがありますが、こちらはnkfを処理のパイプラインに挟む等の対策ぐらいしか思い浮かびません。

おわりに

結論を申しますとUTF-8で全人類は救済される*12し、マイクロソフトExcelPythonを載せる前にやるべきことがあるといったところで今日の授業は以上となります。

明日の授業は@----_先生です。楽しみですね

おまけ

全角・半角文字とEUC-JP

ここからはちょっと余談なのですが、よく全角カタカナと半角カタカナの違いについて、

1バイトで表現されるのが半角カタカナ、2バイトで表現されるのが全角カタカナ

と説明されているのを見かけますが、残念ながらそれはShift_JISでしか成り立ちません。

EUC-JPでは半角カタカナ・全角カタカナ共に2バイトで表現されますし、UTF-8では半角カタカナ・全角カタカナ共に3バイトで表現されます。

UTF-16に至ってはすべての文字が2バイトもしくは4バイトで表現されます。

メモ帳の『ANSI

メモ帳の文字コードには『ANSI』なる謎文字コードで保存できるオプションがあります。

f:id:jyuch:20181209133727p:plain

歴史的な経緯等は原典を見つけられなかったのですが、『ANSI』はそのロケールの既定の文字コードを表す単語らしく日本語だとShift_JIS(CP932)を表します。

他のロケールWindowsですとそれぞれのロケールの既定の文字コードを表すので注意が必要です。

参考書籍

文字コード超研究 改訂第2版 深沢千尋
JIS X 0208 『7ビット及び8ビットの2バイト情報交換用符号化漢字集合』

*1:Excelから吐き出されたCSVとか

*2:名前は戸籍法の施行規則で常用漢字表人名用漢字表に限定されているのでマイナーな漢字が入ることはあまりないのですが、苗字は手書きの戸籍の名残で字体が崩れたのがそのまま文字として認識されてしまった等の経緯でマイナー漢字の宝庫と化します

*3:さらに文化的な側面もあるので苗字に使用可能な漢字を法律等で規制するというのはかなり困難です

*4:JIS X 0208の代わりにJIS X 0213を使用するShift_JIS-2004という文字コードもありますが、従来のShift_JISと互換性が失われる上にUnicodeに対しての優位性も無いので流行っていません。

*5:Unicodeでは符号化形式および符号化スキームを厳密に区別していますが、その辺のガチの定義は仕様書をさんしょうしてくださいよろしくおねがいします

*6:私は使用例を知らないので知っている人はコメントください

*7:厳密にいうと記号が少し違うが、ISO/IEC 646は記号を置き換えることを許している

*8:定義で言うと『図形文字の図形表現としての形状についての抽象的概念』、平たく言うと文字を頭の中に思い浮かべたときの思い浮かべた文字の事

*9:法務省戸籍統一文字はこのような戸籍で登録されている文字を正確に扱うために策定されています。見てもらうと分かりますが結構すさまじいことになっています。

*10:『﨑』はJIS X 0208の包摂基準には記述が無いのでおそらく包摂ではないです

*11:気になる人は参考書籍を買って読んでください

*12:と言いたいところなのですが、こちらはこちらでCJK統合漢字のアレソレや、近年のハイカオス絵文字・異体字セレクタなどでこちらもなかなかの闇です。

VB.NETでもナウいCIサーバでテストをギュイーンしたい

はじめに

この記事はVisual Basic Advent Calendar 2018の15日目の記事です。

どうも、闇落ちJenkinsおじさんです。

オンプレのCIサーバといえばJenkinsが筆頭に上がりますが、Jenkinsのビルド環境が秘伝のタレ化してスレーブを追加する際に死ぬほどコンパイラやらSDKやらをぶち込まないとならず、自動化の裏側でソウルジェムをマッハで濁らせながらメンテナンスしている管理者も多いのではないでしょうか。

こんな時、コンテナでテスト環境は生成・破棄出来たらどんなに幸せでしょうか。オンプレで

コンテナは投げ捨てるもの

JenkinsでもPipelineを使えばDockerコンテナ上でビルド・テストを実行出来ますが、いかんせんGroovyで記述しなくてはならないというところが個人的にはつらみポイントでした。*1

という感じで色々迷っていたところ、drone.ioというオンプレにも展開できるDockerコンテナを使用したCIサーバを見つけたので試してみました。*2

Dockerパーリナイ

というわけで以下の図のようにLinuxインスタンスを生やしまくります。

f:id:jyuch:20181121220915p:plain

今回の検証に使用したソフトウェアは以下な感じです。

  • dnsmasq
    • 弊社は3桁以上の数字は覚えられないのでDNSサーバを立てます。
  • Gitea
    • Giteaを使うと割と簡単に構築できるそうなので今回はGiteaを使用しました。
  • drone
    • 今回の主役です。今回はサーバとエージェントを分ける構成で構築してみました。

今回はdnsmasq以外はすべてDocker上で構築します。これは別にコンテナ至上主義とかそういうのではなく、単純にそうしたほうが楽だから程度の理由です。

Gitea

まずはソースコードをホストするためにGiteaを立てます。

Giteaはデータ格納用にPostgres・MySQLSQL Server(とSQLite)が選べますが、マイクロソフト信者の私たちはもちろん最強のデータベースであるSQL Serverを選びますよね?

version: '3'
services:
  mssql:
    image: 'microsoft/mssql-server-linux:2017-CU12'
    volumes:
      - ./data:/var/opt/mssql/data
    ports:
      - '1433:1433'
    environment:
      - ACCEPT_EULA=Y
      - SA_PASSWORD=mssqllinux@1
      - MSSQL_LCID=1041
      - MSSQL_COLLATION=Japanese_CI_AS
version: "2"

networks:
  gitea:
    external: false

services:
  server:
    image: gitea/gitea:1.6
    environment:
      - USER_UID=1000
      - USER_GID=1000
    restart: always
    networks:
      - gitea
    volumes:
      - ./gitea:/data
    ports:
      - "80:3000"
      - "222:22"

f:id:jyuch:20181121222947p:plain

drone

droneもDockerで立てていきます。というよりはDockerでしか立てられません。

version: "2"

services:
  server:
    image: drone/drone:1.0.0-rc.1
    environment:
      - DRONE_GIT_ALWAYS_AUTH=false
      - DRONE_GITEA_SERVER=http://gitea.opabinia.co.jp/
      - DRONE_RPC_SECRET=2039906835c92213b78cce47d38be1e9
      - DRONE_SERVER_HOST=drone.opabinia.co.jp
      - DRONE_SERVER_PROTO=http
      - DRONE_TLS_AUTOCERT=false
    restart: always
    volumes:
      - ./drone:/data 
    ports:
      - "80:80"
      - "443:443"

f:id:jyuch:20181121224256p:plain

drone agent

こちらに至ってはOSインストールしてDockerインストールして以下のコマンドをコピペでぶち込めばそれで終わりです。

docker run \
  --volume=/var/run/docker.sock:/var/run/docker.sock \
  --env=DRONE_RPC_SERVER=http://drone.opabinia.co.jp \
  --env=DRONE_RPC_SECRET=2039906835c92213b78cce47d38be1e9 \
  --env=DRONE_RUNNER_CAPACITY=4 \
  --env=DRONE_RUNNER_NAME=dad96u1 \
  --restart=always \
  --detach=true \
  --name=agent \
  drone/agent:1.0.0-rc.1

プロジェクトの作成

まぁ、Linuxとかが出てきてお察しの通り、今回は.NET Coreで検証しています。droneのエージェントにはWindowsも使えるのでもしかしたら.NET Frameworkの方も行けるかもしれませんが今回は検証していないです。

Public Class Class1

    Public Function Add(x As Integer, y As Integer) As Integer
        Return x - y
    End Function

End Class

みたいなクラスがあって

Imports System
Imports Xunit
Imports ClassLibrary1

Public Class Class1Test

    <Fact>
    Sub Add_return_valid_value()
        Dim x = 10 : Dim y = 20
        Dim expected = x + y

        Dim c = New Class1()

        Assert.Equal(expected, c.Add(x, y))
    End Sub

End Class

みたいなテストを用意します。そして、手元でテストを回してばっちり失敗することを確認してから以下の.drone.ymlを追加してからプッシュします。

kind: pipeline
name: default

steps:
- name: classlibrary1
  image: microsoft/dotnet:2.1-sdk
  commands:
  - dotnet test

ちゃんと失敗しています。

f:id:jyuch:20181121230617p:plain

なおすと

f:id:jyuch:20181121230955p:plain

成功します。

おわりに

多分ドキュメントの整備が追い付いていないのか、ちょっとドキュメント類が不足しているなというところが率直な感想です。 また、開発途中だから無いのかそれともエンタープライズ版との差別化なのかは分かりませんが、ブラウザから操作できることが非常に限られている感じです。ブラウザからは現在いくつのエージェントがぶら下がっているのかも分かりません。*3

今回はクッソ簡単な例しか試していませんが、これが大規模になった場合、ビルド実行用のDockerイメージを用意したり、用意したDockerコンテナをローカルでホストするためリポジトリ(Sonatype Nexus OSSとか)を用意したり、独自ライブラリをNuGet経由で配布するためにパッケージを作ったり、それをホストするためのNuGetリポジトリを立てたりと想像するだけで大変そうです。

逆にその辺を解決できれば自動化インフラ管理者にとっての救済となるかもしれません。

おわり

github.com

*1:似たような理由でGradleもいまだに苦手意識ががが

*2:飛ぶほうのドローンが流行っているせいでググラビリティは割と低め

*3:エージェントについては意識するなという事?