Microsoft MVP for Visual Studio and Development Technologiesを再受賞しました
今年もなんとか受賞できました、6年連続とは我ながらびっくりですね。 関係各位ありがとうございます。
Visual Studio and Development Technologiesと言っても相変わらず F# が主戦場です。 とはいえ最近は.NET Core対応云々の話が多かったかもしれません。 あとはILやコンパイラの知識が地味に増えたので手を広げられる範囲が少し増えたかも…? なんにせよ引き続きインプットとアウトプットですね。
OSSは相変わらずメンテしているようなしていないような…反応が遅くなってしまい申し訳なさががが。 重い腰を上げるスピードを速める手段を確立したいところです。 雑に作るのはできるんですけどね…難しい。
ところで最近はもう少し F# に関するアウトプットを増やしたいなと思いつつ、とはいえプライベートのみでの活動にも限界を感じつつ、そのプライベートもあれやりたいこれ試したいと発散気味なのでどうにかしないと…うーん時間が足りない。 あと半年くらいで三十路に突入するのもあっていろいろ悩みつつ、まぁ何かしら調査や試作や実装やメンテナンスをやっていく所存です。
そういうわけで今年度もよろしくお願いします。
進捗大陸03に参加しました
紹介記事を書こう書こうと思っていたら、気がつけば技術書典04から数日すぎてました。
どういう流れで進捗大陸に拾っていただいたかとか、中身についてはno-maddoさんの記事をご覧ください。
私の記事ではBuckleScriptでなんちゃってaltJSを作る方法について書きました。 サンプルコードはGitHubで公開しています。
https://github.com/pocketberserker/shinchoku-tairiku-03-bucklescript
このレベルなら字句解析や構文解析はいらないでしょ、という話もあるかもしれませんが、規模の大きな言語でもこのあたりの設定はかわらないと思うので参考にどうぞ。
他の記事はぜひ購入して中身を確認していただけると…!
F# 向けモックライブラリPersimmockを試作
モックフレームワークやモックライブラリは.NET界隈にいくつかあって、特にF#にはFoqがあるわけですが、現状で満足か、と問われると微妙な顔持ちになります。
以下、1年半くらい前にFoqの行けてないところをzeclさんに尋ねたときの会話です。
使ってみるとすぐにわかることですが、SetupMethodとSetupの使い分けがだるい(し、型安全じゃない)点が最大にビミョイところかと思います。メソッドチェーンよりも (|>) でSetupしたいとかもありますが、それは難しいのかもしれません
— ぜくる (@zecl) 2016年10月18日
実際、Foqは(おそらく意図的に)緩めのAPIが定義されています。
例えばMock<IHoge>().Setup(fun mock -> <@ mock.Hoge(any(),any()) @>).Calls<string*int>(fun (_,x) -> x)
だと、mock.Hoge(any())
でもコンパイルに成功するはずです(テストは失敗する)。
.Calls<string*int>(fun (_,x) -> x)
も引数の型をみているわけではなく、<string*int>
というユーザの自己申告を信じています。
仕方がないといえばそこまでですが、もう少し型安全に挑戦してみても良いのではないか、という気持ちが少し芽生えます。
というわけでPersimmockというライブラリを試作しました。
解説?
https://www.nuget.org/packages/Persimmock/0.0.1
https://github.com/persimmon-projects/Persimmock
名称的にはPersimmonの系譜ですが、現状はPersimmon非依存です。 1年前にリポジトリだけ作って放置していたものを、謎の気力上昇によって2日くらいででっちあげた感じです。
このライブラリを1行で説明すると”ガワ変えのFoq”です。 どのくらいFoqかというと、動的生成部分やVerification部分のAPIはそのまま拝借した程度にはFoqです。
mock<IHoges> { method (fun mock -> mock.Hoge) call (fun (_, x) -> x) }
こんな感じで書けます。
コンピュテーション式なのは、ReflectedDefinition(true)
が使えるいい感じのAPIを思いつかなかったからです。
メソッドの引数の型を取るためにメソッドを直接とります。
FoqのMethod
とほぼ同一です。
mock<IHoges> { setup (fun mock -> mock.Get(It.IsAny(), It.IsAny())) hook (fun (_, x: int) -> x) }
いちおうFoqらしい書き方もできますが、基本推奨しません。 前者だとオーバーロードメソッドやsetterがうまく処理できないので緊急回避的に残した次第(どうやったらsetterを関数として取得できるんだ…?)。
hook
は名前が思いつかずとりあえずで付けたものなので、たぶんそのうち変わります。
mock<IFuga> { property (fun x -> x.StringProperty) returns (fun () -> value) }
プロパティは別APIで、returns
には即値を渡せません。
パフォーマンスと書きやすさとコンピュテーション式でオーバーロードできない問題を天秤にかけた結果です。
let xs = mock<IList<int>> { method (fun xs -> xs.Contains) args (any<int>) returns (fun () -> true) } xs.Contains(1) |> ignore Mock.Verify(<@ xs.Contains(0) @>, never) Mock.Verify(<@ xs.Contains(any<int>) @>, once)
args
で引数を渡します。
頑張って型安全みをだしてみましたが、かわりにジェネリックメソッドに対して無力になりやすいです…。
Foqのany
だと型推論が誤作動して2引数をタプルとして解釈し、実行時にエラーになりやすかったため、any
は型パラメータを明示する形にしました。
ただし、その分融通が利きません。
あえて型推論に身を任せたい場合はIt.IsAny()
を使います。
VerifyはFoqそのままなので特になし。
未実装機能
Mock.~ByName
は型安全にできないのであえて実装していません。
Mock.As
はなんか過剰な気がしたのと、現状の実装と相性が悪すぎたので実装していません。
まとめ
型安全にしようとするとどこかが割を食う、ということがよくわかる試作結果でした。 もう少しどうにかできないものか…研究は続きそうです。
Foqのバックエンドよくできているので、ぶっちゃけそんなに手を入れる必要はないと思っています。
余談
ここまで作っておいてなんですが、個人的にはinterface + オブジェクト式でやっていこうなという気持ちになりました。
paket.referenceで複数のバージョン依存を設定する方法
.NET Core SDKというかnew fsprojになってから、以下のような依存関係を書く機会によく遭遇する。
<ItemGroup Condition="'$(TargetFramework)'=='net45'"> <PackageReference Include="FSharp.Core" Version="3.1.2.5" /> </ItemGroup> <ItemGroup Condition="'$(TargetFramework)'=='netstandard1.6'"> <PackageReference Include="FSharp.Core" Version="4.1.18" /> </ItemGroup>
古い環境ではなるべく最低ラインのバージョンを指定しつつ、netstandardでは比較的新しめのバージョンを指定するというもの。 ただこの書き方は現時点のVS2017だとちゃんと認識されないので困った。
というわけでこれと同等の書き方をPaketで再現させる。
まずはpaket.dependencies
を用意。
framework: netstandard1.6 source https://api.nuget.org/v3/index.json nuget FSharp.Core >= 4.1.18 lowest_matching:true group Legacy framework: net45 source https://api.nuget.org/v3/index.json nuget FSharp.Core >= 3.1.2.5 lowest_matching:true
framework targetごとにグループをわけておく。 これで異なるバージョンの、最も小さいバージョンが指定できる。
paket.reference
側では次のように記述する。
FSharp.Core group Legacy FSharp.Core
二つのグループについて依存関係を追加している。 これで、frameworkごとに異なるバージョンを指定できる。 この方法だとVS2017も正しく依存関係を認識する。
ちなみにpaket.template
では、以下のようにLOCKEDVERSION-グループ名
で記述する。
dependencies framework: net45 FSharp.Core >= LOCKEDVERSION-Legacy framework: netstandard1.6 FSharp.Core >= LOCKEDVERSION
某講座のミニゲームを F# で実装してみよう
https://dwango.github.io/articles/shachiku-chan-vol3/この記事関連で以下の反応をみかけた。
いきのこれ!社畜ちゃん4巻にはいよいよ F# が登場すると聞いて(嘘
— はぇ~☆ (@haxe) 2018年1月23日
じゃあ試しにやってみましょうか、F# (というかFable)で。
https://github.com/pocketberserker/SCV3
実質の実装箇所は以下。
https://github.com/pocketberserker/SCV3/blob/11dc1bece095314b4118e5350530f635576bedb2/src/App.fs
- 面倒だったので全部floatで操作
loop
の第2引数は使わないけど定義しておかないとBrowser.FrameRequestCallback
と型があわなくて怒られるU3.Case1
の初見殺し感よaddEventListner
系に渡す関数は雑に作りたかったのでnull
を返しておく(つらい…)- 本当は
Browser.document.getElementById("canvas")
でcanvasを取得したかったが、なぜか取得できなかったのでBrowser.document.getElementsByTagName_canvas().[0]
でお茶を濁す(そのうち調べます…) - 元コードが全力副作用なので素直に移植したらそりゃ代入祭りになるよね、という気持ち
F# で `a == 1 && a == 2 && a == 3` と `2 + 2 == 5`
もう誰かやってるかもしれない。 記事にネタがなくて困っていたので許して。
a == 1 && a == 2 && a == 3
https://stackoverflow.com/questions/48270127/can-a-1-a-2-a-3-ever-evaluate-to-true
let (&&) _ _ = true
警告されるが
warning FS0086: The '&&' operator should not normally be redefined. Consider using a different operator name
実行可能(以下>
と;;
がついた部分はfsharp interactive)
> let a = 1;; val a : int = 1 > a = 1 && a = 2 && a = 3;; val it : bool = true
2 + 2 == 5
加算は警告なし。
let (+) _ _ = 5
> 2 + 2 = 5;; val it : bool = true
=
だと
let (=) _ _ = 5
警告あり
warning FS0086: The '=' operator should not normally be redefined. To define equality semantics for a type, override the 'Object.Equals' member in the definition of that type.
番外編
「1+1は?」
— Hideyuki Tanaka (@tanakh) 2018年1月23日
Haskell「みそスープ」 pic.twitter.com/SF9wi21zeJ
let (+) _ _ = "ミソスープ" 1 + 1 |> printfn "%A"
あるいは、パイプライン演算子をミソスープ出力器にする。
let (|>) _ _ = printfn "ミソスープ" 1 + 1 |> printfn "%A"
???「ところで、printfn
をミソスープにしてもかまわんのだろう?」
let printfn _ _ = printfn "ミソスープ" 1 + 1 |> printfn "%A"
???「ミソスープだけを作る機械かよ!?」
golangのcobraで、サブコマンドとして外部コマンドを実行する
新年が明けたのでgolangに入門し、お試しにとコマンドラインツールを作っているところです。
その際に、外部コマンドをサブコマンドのように実行するにはどうすれば…と思って見つけたのが以下の記事です。
[Go言語]codegangsta/cliで、サブコマンドとして外部コマンドをとれるようにしてみた - Qiita
urfave/cli*1を使えばまさにやりたいことができそう…と思っていたのですが、上記記事のコードでは引数に未知のflagが含まれているとエラーになってしまいます。
どうにか解決できないか、あるいは自分の勘違いなのではと調査したものの進展なし。 早々に諦めて別のライブラリを調査することに。
https://github.com/spf13/cobra
結果、わりと知名度が高そうなcobraで同様のことができたので掲載しておきます。 importやコマンド名は適宜置き換えてください。
// main.go package main import "sample/cmd" func main() { cmd.Execute() }
main.go
は特筆すべきことなどありません。
// cmd/root.go package cmd import ( "fmt" "github.com/spf13/cobra" "os" "os/exec" ) var rootCmd = &cobra.Command{ Use: "sample", Run: func(cmd *cobra.Command, args []string) {}, } func Execute() { args := os.Args if len(args) > 1 { trySearchSubCommand(args) } err := rootCmd.Execute() if err != nil { fmt.Println(err) os.Exit(1) } } func trySearchSubCommand(args []string) { subcommand := args[1] for _, c := range rootCmd.Commands() { if c.Use == subcommand { return } } path, err := exec.LookPath(rootCmd.Use + "-" + subcommand) if err != nil { return } sub := &cobra.Command{ Use: subcommand, DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { c := exec.Command(path, args...) c.Stdout = os.Stdout c.Stdin = os.Stdin c.Stderr = os.Stderr return c.Run() }, } rootCmd.AddCommand(sub) } func init() {}
rootCmd.Execute
実行よりも前に探索を行うrootCmd.Commands
で既存サブコマンド一覧を取得し、名前が一致するなら何もしないexec.LookPath
を使ってPATHの通った実行可能ファイルの絶対パスを探す- 見つかったなら
rootCmd.AddCommand
でサブコマンドとして登録DisableFlagParsing: true
にしてflagの解析を行わないようにする
- 見つからない場合はrootコマンドの可能性が高いので何もしない
かなり無理矢理感がある、これで本当に良いのでしょうかね…golangのcliライブラリに詳しい方にご教授いただきたいところ。
追記
今回の実装はrootコマンドのflagはparseしてほしい、かつ外部コマンドをサブコマンドとして実装したい場合の方法です。 rootにflagがない場合は下記で指摘されている通り、rootコマンドのRunで解析したほうが楽です。
これ、ちょっと調べていて気付いたんですが、 cobra は sub-command に match する物が無い場合、Root .Run を叩きに来ます。なのでそこで実行するのが簡単だと…
— 眼力 玉壱號 (@objectxplosive) 2018年1月13日
多少調整が必要なんですが、こんな感じの root にすると flag 含めてそのままやってきます… pic.twitter.com/PbchC6IKY4
— 眼力 玉壱號 (@objectxplosive) 2018年1月14日
objectxplosiveさん、指摘ありがとうございました。
…と、ここまでかいたタイミングで「rootの実態すら別コマンドとして実装して、root.Runではそいつを呼び出せばよかったのでは?」と思ったわけですが、試したわけではないので動くかわかりません。