これは、 @kmizu さんの「初学者向けの Scala Tips」を勝手に F# にポートした記事です。
-
-
Save Gab-km/11242018 to your computer and use it in GitHub Desktop.
F# の方でも Option.map
があります。ただ Option.map
は Option
モジュールの持ち物であり、また map
関数のシグネチャが ('T -> 'U) -> 'T option -> 'U option
であるという違いがあります。
Option.map
を使わない時:
let result =
match exp1 with
| Some(v1) ->
let vx = //expression using v1
Some(vx)
| None -> None
Option.map
を使う時:
let result =
Option.map (fun v1 ->
let vx = //expression using v1
vx
) exp1
こんな感じですね。
より一般的なコードでは、flatMap()を使う事が有用な事がありますが、
Scala の flatMap
メソッドに対応するのは、 F# だと bind
関数だと思います。 Option
以外の collections
名前空間にいるようなコンテナ型(List
や Seq
など)だと collect
関数というのがそれに当たります。
null 死すべし。慈悲はない。これは F# でも尊ばれている思想です。平安時代の何とかっていうサムライも言ってた。
let fsMap: Map<string, string> = Map.ofList listOfTupleValues
let value: string = Map.find key fsMap
// 実は、以下のコードはあまり意味がない
if value <> null then
...
else
...
さて、このサンプルコードですが、kmizu さんの Scala 版とは挙動が違います。何が違うかというと、 Map.find
は対応する key
が fsMap
にない場合、null を返したりせず KeyNotFoundException
を送出します。ダメじゃん。
ということで、 Map.find
を Map.tryFind
で書き換えたものがこちら。Map.tryFind
は結果を Option<'T>
に包んで返してくれるので、元記事の2つ目のサンプルコードとほとんど同じコードになります。
let fsMap: Map<string, string> = Map.ofList listOfTupleValues
let value: string option = Map.tryFind key fsMap
match value with
| Some(v) -> ...
| None -> ...
なお、今回のサンプルには出てきませんでしたが、null から Option<'T>
型の値にする標準関数は提供されていません(何だと…)。ご入用でしたら、次のような関数を定義しておくと便利かもしれません。
let toOption (value: 'T when 'T : null) =
if value = null then
None
else
Some(value)
@kmizu さんの記事で、
この機能、しばしば型に対するswitch-caseとして使われていることがあるようです。
とあったんですが、折角のパターンマッチをそんな風に使うなんてもったいない!
ところで、 Scala の方で case class
を使っていますが、 F# では判別共用体を使います。
type Value =
| Hoge of int
| Foo of string
| Bar of string list
さて、この Value をわざわざ型でパターンマッチする、というサンプルを用意したかったんですが、残念ながら F# の判別共用体ではそれが出来ません。それは、 Scala で case class
を用いている箇所は、 F# においては「ラベルとコンストラクタ」(これを、ケース識別子、とかいう)としてしかアクセス出来ません。そして、このケース識別子をコンストラクタとして用いて得た値は、 Value
型の値となります。つまり、型チェックのしようがないのです…。
ですので、パターンマッチのサンプルコードとしては後者の方が F# で書きなおすことが出来ます。
match value with
| Hoge(v) -> doHoge v
| Foo(v) -> doFoo v
| Bar(v) -> doBar v
ちなみに、 F# で型に対するパターンマッチをするには、次のようにします:
match value with
| :? Derived1 -> printfn "Derived1 class"
| :? Derived2 -> printfn "Derived2 class"
| _ -> printfn "Base class"
ということで、 F# の判別共用体では、普通に識別子パターンによるマッチングをしましょう。
はい、今回は書くことが無いですね…。 F# のパターンマッチは、中括弧(brace)で囲んだりする必要がありません。
強いて言えば、同じ処理をさせたいケースをまとめることができる、くらいでしょうか。
// 1つずつ書く
match value with
| 2 -> printfn "東工大生「うおおぉぉぉぉ!!!」"
| 3 -> printfn "東工大生「うおおぉぉぉぉ!!!」"
| 5 -> printfn "東工大生「うおおぉぉぉぉ!!!」"
| 7 -> printfn "東工大生「うおおぉぉぉぉ!!!」"
| _ -> printfn "東工大生「…」"
// まとめて書く
match value with
| 2 | 3 | 5 | 7 -> printfn "東工大生「うおおぉぉぉぉ!!!」"
| _ -> printfn "東工大生「…」"
F# ですと、次のようなコード:
List.map (fun x ->
match x with
| A -> ...
| B -> ...
| _ -> ...
)
に対して、このような書き換えができるかなぁという感じです:
List.map (function
| A -> ...
| B -> ...
| _ -> ...
)
なお、 PartialFunction
は Scala 特有というか、F# にはない概念ですね。しいて言えば、パーシャル アクティブ・パターンが近いかな?
えーと、今回は F# 側で対応する機能がありません…。
しいて言えば、F# では三重引用符を使わなくても文字列中に改行を含めることが出来ます。
> let hoge = """
- type Foo() =
- member self.foo() = println "foo"
- """;;
val hoge : string = "
type Foo() =
member self.foo() = println "foo"
"
> let fuga = "
- type Foo() =
- member self.foo() = println \"foo\"
- ";;
val fuga : string = "
type Foo() =
member self.foo() = println "foo"
"
名前空間を分けておくというの、F# においても大事な設計判断です。
(* 注: 判別共用体にフィールド名を使えるのは F# 3.1 以降です *)
type Expression =
| Addition of lhs: Expression * rhs: Expression
| Subtraction of lhs: Expression * rhs: Expression
| Multiplication of lhs: Expression * rhs: Expression
| Division of lhs: Expression * rhs: Expression
| Number of int
ここで、まず普通に名前空間に型を配置することが出来ます。
namespace AST
type Expression =
| Addition of lhs: Expression * rhs: Expression
| Subtraction of lhs: Expression * rhs: Expression
| Multiplication of lhs: Expression * rhs: Expression
| Division of lhs: Expression * rhs: Expression
| Number of int
一方、F# にはモジュールというものがあり、モジュールに型を持たせることも出来ます。
module AST =
type Expression =
| Addition of lhs: Expression * rhs: Expression
| Subtraction of lhs: Expression * rhs: Expression
| Multiplication of lhs: Expression * rhs: Expression
| Division of lhs: Expression * rhs: Expression
| Number of int
どちらの場合でも、 AST.Expression
とか AST.Subtraction
とやって使うことが出来ます。