はじめに
2020/11/11(水)にリリースされた F# 5.0 の新機能について簡単に紹介します。
抜け漏れ誤りがあったらごめんなさい🙇
※現在まだプレビューの機能については触れません
F# 5.0 新機能
F#スクリプトでのNugetパッケージ参照
今までF#スクリプトではNugetパッケージを直接参照することができませんでした。
それがF# 5.0から可能になりました。
#r "nuget:Flavedo.Zest"
open Flavedo.Zest
// do something
詳しくは こちらの記事 を参照してください
String Interpolation
C# でいうところの 挿入文字列リテラル が F# にも追加されました。
これは文字列リテラル中に値を挿入できるというものです。
$"文字列 {挿入したい値} "
let name = "Midoliy"
let age = 31
let message = $"{name} is {age} years old."
printfn "%s" message
Midoliy is 31 years old.
また、sprintf関数 と同様にフォーマット指定子を利用することで挿入する値の型を指定することが可能です。
フォーマット指定子を利用することで printf関数 や sprintf関数 と同様、コンパイル時に意図しない型が挿入されることを防ぐことができます。
$"文字列 フォーマット指定子{挿入したい値} "
let name = "Midoliy"
let age = 31
let message = $"%s{name} is %d{age} years old."
printfn "%s" message
Midoliy is 31 years old.
nameof
C# の nameof式 と同様のものが F# にも追加されました。
nameofを使うことで変数・型・メンバーなどの名前が文字列定数として生成されます。
nameof 対象
let f x = nameof x
let y = 10
printfn "%s" (f y)
printfn "%s" (nameof y)
printfn "%s" (nameof int)
x
y
int
この機能はいろいろな利用シーンがありますが、例えば WPF のバインディングに必須な INotifyPropertyChanged を実装する際、文字列リテラルを直指定ではなく C# と同様に nameof式 で指定することが可能になったりします。
open System.ComponentModel
type Rectangle () =
let mutable height = 0f
let mutable width = 0f
let propertyChanged = Event<_, _>()
interface System.ComponentModel.INotifyPropertyChanged with
[<CLIEventAttribute>]
member __.PropertyChanged = propertyChanged.Publish
member __.Height
with get() = height
and set(value) =
height <- value
propertyChanged.Trigger(__, PropertyChangedEventArgs(nameof height))
member __.Width
with get() = width
and set(value) =
width <- value
propertyChanged.Trigger(__, PropertyChangedEventArgs(nameof width))
member __.Area with get() =
height * width
Open Type declarations
C# でいうところの using static ディレクティブ に似た機能が F# にも追加されました。
Open Type declarations は型名を指定せずに静的メンバにアクセスできるだけでなく、特定のモジュール内にある判別共用体やenum、レコードなどにアクセスすることが可能となっています。
open type 型名
open type System.Math
let x = Min(1.0, 2.0)
printfn $"%f{x}"
1.000000
module M =
type Foo = { X:int; Y:int; }
type Bar = { X:int; Y:int; }
// open type を利用した場合
open type M.Foo
let foo = { X = 10; Y = 20; }
printfn $"%A{foo} : %A{foo.GetType()}"
{ X = 10; Y = 20 } : Program+M+Foo
module M =
type Foo = { X:int; Y:int; }
type Bar = { X:int; Y:int; }
// 通常の open を利用した場合
open M
let v = { X = 10; Y = 20; }
printfn $"%A{v} : %A{v.GetType()}"
{ X = 10; Y = 20 } : Program+M+Bar
Sliceの機能強化
組み込みデータ型に対してスライスした際の挙動を統一
let list = [ 0..10 ]
let array = [| 0..10 |]
let str = "hello!"
list.[-2..(-1)] |> printfn("%A")
array.[-2..(-1)] |> printfn("%A")
str.[-2..(-1)] |> printfn("%A")
[ ]
throw exception
throw exception
let list = [ 0..10 ]
let array = [| 0..10 |]
let str = "hello!"
list.[-2..(-1)] |> printfn("%A")
array.[-2..(-1)] |> printfn("%A")
str.[-2..(-1)] |> printfn("%A")
[ ]
[| |]
""
3次元・4次元配列に対するスライスの強化
以下のような3次元配列を考えます。
z=0
xy | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 2 | 3 |
z=1
xy | 0 | 1 |
---|---|---|
0 | 4 | 5 |
1 | 6 | 7 |
このときの y = 0, z = 1
の x行 = 4, 5
をスライスで簡単に取得できるようになりました。
let dim = 2
let m = Array3D.zeroCreate<int> dim dim dim
let mutable cnt = 0
for z in 0..dim-1 do
for y in 0..dim-1 do
for x in 0..dim-1 do
m.[x,y,z] <- cnt
cnt <- cnt + 1
// F# 5.0 から以下のようなスライスが可能に!
m.[*, 0, 1]
|> printfn "%A"
[| 4; 5 |]
F# Quotationsの機能改善
F# Quotationsは強力な機能ですが、SRTP制約が課されている場合などでそのセマンティクスを十分に表すような呼び出しが行われていませんでした。
F# 5.0となりその機能が改善されました。
open FSharp.Linq.RuntimeHelpers
let eval q = LeafExpressionConverter.EvaluateQuotation q
let inline negate x = -x
// negateは以下のようなシグニチャを持つ関数です。
// val inline negate: x: ^a -> ^a when ^a : (static member ( ~- ) : ^a -> ^a)
<@ negate 1.0 @> |> eval
// 上記のコードはnegateに課せられているSRTP制約を守っていますが、以前のバージョンでは例外が発生していました。
// F# 5.0からはこのコードが有効となります。
Applicative Computation Expressions
「すべての処理が独立」しており「その結果を蓄積させる」だけのコンピュテーション式を作成する場合に、より高パフォーマンスとなる機能が追加されました。
この機能はライブラリ作成者向けの機能ですが、利用者側にとっても今までのコンピュテーション式となんら変わらない使い心地のままパフォーマンスが上がるので両者にメリットがある機能アップです。
大きく分けると "BindReturn" と "MergeSources" の2種類が追加されました。
BindReturn
F# 4.7 までは let!
と return
を別々に定義していましたが、これを1つにまとめたようなものが BindReturn
です。
type OptionBuilder() =
member _.Bind(x, f) = Option.bind f x
member _.Return(x) = x
let option = OptionBuilder()
let r : int option =
option {
let! a = Some 10
return a
}
printfn $"%A{r}"
Some 10
type OptionBuilder() =
member _.BindReturn(x, f) = Option.map f x
let option = OptionBuilder()
let r : int option =
option {
let! a = Some 10
return a
}
printfn $"%A{r}"
Some 10
厳密には上記のコードは同じではありません。
たとえば、BindReturn
のみを定義した場合には2個目の let!
を利用することはできません。
let r : int option =
option {
let! a = Some 10
let! b = Some 20 // コレはダメ!Bindを定義する必要あり
return a + b
}
MergeSources
「独立した処理」を表現するための構文が BindReturn
であるならば、「結果を蓄積する」ための構文が MergeSources
になります。
結果を蓄積させるために and!
を利用する必要があることに注意してください。
type OptionBuilder() =
member _.BindReturn(x, f) = Option.map f x
member _.MergeSources(x, y) = (x, y) ||> Option.map2 (fun x y -> (x, y))
let option = OptionBuilder()
let r : int option =
option {
let! a = Some 10
and! b = Some 20
return a + b
}
printfn $"%A{r}"
let r2 : int option =
option {
let! a = Some 10
and! b = None
return a + b
}
printfn $"%A{r2}"
Some 30
None
.NET相互運用の改善
C# に追加されているインターフェースのデフォルト実装への対応やnull許容型との相互運用性の向上などが行われました。
異なる型でのジェネリックインターフェース実装
異なる型で同じインターフェースを実装できるようになりました。
type IFoo<'T> =
abstract member Function : 'T -> 'T
type Foo() =
interface IFoo<int> with
member _.Function x = x + x
interface IFoo<string> with
member _.Function s = $"string: %s{s}"
let foo = Foo()
printfn $"%d{ (foo :> IFoo<int>).Function(10) }"
printfn $"""%s{ (foo :> IFoo<string>).Function("abc") }"""
20
string: abc
インターフェースのデフォルト実装
C# で書かれたインターフェースにデフォルト実装がなされている場合に、F# でもそのデフォルト実装されたものを呼び出すことができるようになりました。
using System;
namespace Sample
{
public interface IFoo
{
int Value => 100;
}
}
open Sample
type Foo() =
interface IFoo
let foo = Foo() :> IFoo
printfn $"%d{foo.Value}"
100
null許容型との相互運用性の改善
今まで Nullable でラップしなければならなかったところが必要なくなりました。
特に F# から C# のライブラリを利用する場合に便利になります。
using System;
namespace Sample
{
public class Foo
{
public int Function(int? x)
=> (x ?? 0) * 100;
}
}
open Sample
let foo = Foo()
// Nullable<int> でラップしなくても良い
let x = foo.Function(100)
printfn $"%d{x}"
10000
その他
float32
今まではfloat32型を定義する場合に "." が必要でしたが、F# 5.0からはサフィックスのみでよくなりました。
let x = 123.f
let x = 123f
uint32
int32 は int と省略できますが、uint32 は uint と省略できませんでした。
F# 5.0からはそれが可能となりました。
let f (x:uint32) = x * 10
let f (x:uint) = x * 10
おわりに
この他にも現在プレビュー中の機能があったりして、今後も目が離せないですね!
今回の F# 5.0 に関するアナウンスは ココで 確認すると良いと思います。