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ではそいつを呼び出せばよかったのでは?」と思ったわけですが、試したわけではないので動くかわかりません。
F# を使って雑にExcelから取り出したデータをElasticsearchに放り込む
この記事はF# Advent Calendar 2017の25日目の記事です。 大遅刻しましたごめんなさい…。
とあるExcelファイルがでかすぎて閲覧するのに苦痛だったので書きなぐった時のメモ。 .NET Coreです。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.0</TargetFramework> </PropertyGroup> <ItemGroup> <Compile Include="Program.fs" /> </ItemGroup> <ItemGroup> <PackageReference Include="EPPlus" Version="4.5.0.1-beta" /> <PackageReference Include="Nest" Version="5.6.0" /> </ItemGroup> </Project>
まだbeta段階ですがEPPlusもnetstandard版があります。
open System open System.IO open OfficeOpenXml open Nest let getSheet (sheet: string) (excel: ExcelPackage) = excel.Workbook.Worksheets.[sheet] type Questionnaire = { Value: string } let getQuestionnaire (excel: ExcelPackage) = let sheet = getSheet "アンケート" excel seq { for i in [ 2 .. Seq.length sheet.Cells - 1] -> { Value = sheet.Cells.[i, 1].Text } } let questionnairesIndex = IndexName.op_Implicit "questionnaires" let createIndex (name: IndexName) (client: ElasticClient) = client.CreateIndex(name) |> ignore let insertQuestionnaire (client: ElasticClient) (messages: Questionnaire seq) = client.IndexMany(messages, questionnairesIndex, TypeName.op_Implicit "questionnaire") |> ignore [<EntryPoint>] let main argv = let file = argv.[0] let url = argv.[1] let user = argv.[2] let pass = argv.[3] use excel = new ExcelPackage(File.OpenRead(file)) printfn "try to connect %s" url use settings = (new ConnectionSettings(Uri(url))) .BasicAuthentication(user, pass) .DisableDirectStreaming() let client = ElasticClient(settings) createIndex questionnairesIndex client getQuestionnaire excel |> insertQuestionnaire client 0
仕様だったりCellにheaderが付いていた関係だったりでindexが少々ずれていますが気にしないでください。 雑に取得してdockerで立ち上げたElasticsearchに放り込むくらいだったらこれで十分ですはい。
とはいえElasticsearch 6.0系からmulti typeが廃止されたという事実に気づかずどハマりしましたが…。
dotnet new用のテンプレートを使う、作る
この記事はF# Advent Calendar 2017の6日目の記事です。 F#固有の話ではないですが、知っていると少し捗るかもしれない話をします。
dotnet new
コマンドでnugetに公開されたテンプレートを使う
dotnet new
は.NET Core SDKでテンプレートを用いてプロジェクトを作成するコマンドです。
https://docs.microsoft.com/ja-jp/dotnet/core/tools/dotnet-new?tabs=netcore2x
リポジトリはこちら:
https://github.com/dotnet/templating
標準でいくつかのテンプレートが搭載されていますが、dotnet new install
コマンドを使えばnugetに公開されているテンプレートをインストールできます。
テンプレートを探すには下記サイトが便利です(公式のwikiで紹介されていたサイト)。
たとえば、私を含む数名(?)で開発しているPersimmonのテンプレートはこんな感じでヒットします。
http://dotnetnew.azurewebsites.net/template/Persimmon.Templates/Persimmon.FSharp
テンプレートを作る
テンプレートの構成や設定は公式のwikiをみるのが一番です。
https://github.com/dotnet/templating/wiki/%22Runnable-Project%22-Templates
プロジェクトファイルは言語にあわせて用意します。
F#の場合は.fsproj
ですね(と申し訳程度のF#要素を挟んでおく)。
設定のうちshortName
は実際にdotnet new
コマンドで使うので名前を吟味したほうが良いでしょう。
あとは、groupIdentity
が同一でtags
のlanguage
が異なるテンプレートが登録されていると-lang
オプションで言語選択できるようになります。
あと、複数言語のテンプレートを用意したいプロジェクト向けとして、1つのnugetパッケージには複数のテンプレートをほうり込めます。 これに関しては具体例をみたほうが早いので、nunitのリポジトリをとりあげておきます。
https://github.com/nunit/dotnet-new-nunit
テンプレートをnugetで公開する
いつものごとくnuspecを書くわけですが、テンプレート用のパッケージであることを明示するためにmetadata
タグ下にpackageTypes
を追加します。
<?xml version="1.0" encoding="utf-8"?> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> ... <packageTypes> <packageType name="Template" /> </packageTypes> </metadata> <files> <file src="..\templates\**" target="" /> </files> </package>
ファイルはテンプレートを置いているディレクトリ下のコードを全部取り込んでも問題ないことがほとんどだと思います。
おわりに
CLIで完結するのでテンプレートを作るハードルがそこそこ低いです。 publicなものに限らず社内NuGetに放り込んで運用することも可能…なはずなので、覚えておいて損はないと思います。
F# で Entity Framework Coreる
この記事はF# Advent Calendar 2017の5日目の記事です。 遅刻しましたごめんなさい…。
F# + .NET Core + Entity Frameworkの組み合わせて開発してみたときの備忘です。 もしかしたら多少古い知識が混ざっているかもれません(全部再検証するには時間がたりなかった)。
モデル定義
CLIMutable
にしてEntityFramework側で処理できるようにしておきます。
また、マイグレーション機能を使うためにいくつかAttributeをつけて起きます。
open System open System.ComponentModel.DataAnnotations open System.ComponentModel.DataAnnotations.Schema type UserId = int64 [<CLIMutable>] type User = { [<Key; DatabaseGenerated(DatabaseGeneratedOption.Identity)>] Id: UserId [<Required; StringLength(32)>] Name: string }
DBContext
だいたいC#のサンプルを翻訳していくだけです。
open Microsoft.EntityFrameworkCore open Microsoft.Extensions.PlatformAbstractions type HogeDbContext() = inherit DbContext() [<DefaultValue>] val mutable user : DbSet<User> static member val ConnectionString = "" with get, set member this.Users with get() = this.user and set v = this.user <- v override __.OnConfiguring(optionsBuilder: DbContextOptionsBuilder) = optionsBuilder.UseMySql(SpellbookLibraryDbContext.ConnectionString) |> ignore
ここではMySQLでの例をだしましたが、他のDBでも大した違いはないはずです。
マイグレーションコード
C#であれば Microsoft.EntityFrameworkCore.Tools.DotNet
をインストールしてコマンドを叩いてあげれば C# なマイグレーションコードが生成されます。
ではF#であればF#のコードをだして…くれません。
https://github.com/aspnet/EntityFrameworkCore/pull/10289
このpull requestと関連issueを読めばわかりますが、C#以外の言語はコミュニティに任せたそうな雰囲気を感じます。 とはいえ、このpull requestを含む2.1.0はまだリリースされていませんし、コミュニティでツールが作成されるのもそれなりに時間がかかるでしょう。
では、マイグレーションを諦めるのか…といえば、いやいや、方法はあります。
後者に関しては、F#で書いたモデルクラスからもマイグレーションコードを生成できるので、マイグレーションコードさえなんとかしてしまえば良いわけです。
ただし、2テーブルの作成に200行ほどのF#を書かないといけないので労力に見合っていないのがネックです。
気合で書くか、待つか、ツールを実装するか。 お好みのものをお選び下さい。