openした結果モジュールが被ってしまった場合でも両方のモジュールの関数を扱える
この記事はF# Advent Calendar 2014 - connpassの23日目の記事です。
(日にち変更していただきました。ありがとうございます。)
昨日はむろほしりょうたさんの初心者がF#をUnityで使ってみた! - Qiitaでした。
F# のネタリスト - 2つのアンコールより、
「openした結果モジュールが被ってしまった場合でも両方のモジュールの関数を扱えること知りたいです」について書きます。
openした結果
MSDN
F# コンパイラでは、 複数の開いているモジュールまたは名前空間に同じ名前が存在し、 あいまいさが生じる場合でも、エラーや警告を生成しません。 あいまいさが生じた場合、F# では、 開いた時間が新しい方のモジュールまたは名前空間が優先されます。
これですね。
確認してみる
今回は「モジュールが被ってしまった場合」なので、同じ名前のmoduleを用意します。
関数名も同じにしておきました。
(* Tax.fs *) namespace Advent.Tax module Calc = let taxRate = 0.08 let getPrice basePrice = basePrice * (1.0 + taxRate)
税率を指定して、getPriceに本体価格を渡すと税込価格を返してくれる。
(* Sale.fs *) namespace Advent.Sale module Calc = let discountRate = 0.2 let getPrice basePrice = basePrice * (1.0 - discountRate)
値引率を指定して、getPriceに本体価格を渡すと値引き後の価格を返してくれる。
openします。
(* Display.fs *) namespace Advent open Advent.Tax open Advent.Sale module Display = let basePrice = 100.0 let paymentPrice = Calc.getPrice basePrice printf "お支払金額 %A 円" paymentPrice
実行。
お支払金額 80.0 円
2割引のセール価格が表示されました。
open2行の上下をを入れ替えて実行。
(* Display.fs *) namespace Advent open Advent.Sale open Advent.Tax module Display = let basePrice = 100.0 let paymentPrice = Calc.getPrice basePrice printf "お支払金額 %A 円" paymentPrice
お支払金額 108.0 円
税込価格が表示されました。
どちらも後で開いた方が優先されています。
両方のモジュールの関数を扱える
両方のモジュールの関数を使って、
セール価格にした後、消費税をのせた額をお支払金額をとして表示しましょう。
openのタイミングを変える
(* Display.fs *) namespace Advent open Advent.Sale module Display = let basePrice = 100.0 let salePrice = Calc.getPrice basePrice open Advent.Tax (* ここでopen *) let paymentPrice = Calc.getPrice salePrice printf "お支払金額 %A 円 (税抜価格 %A 円)" paymentPrice salePrice
お支払金額 86.4 円 (税抜価格 80.0 円)
セール価格にして、消費税をのせて、お支払金額にすることはできましたが、
両方の関数を扱えるという感じではありません。
Taxのopen前はSaleしか使えず、open後はTaxしか使えません。
フル修飾する
(* Display.fs *) namespace Advent module Display = let basePrice = 100.0 let salePrice = Advent.Sale.Calc.getPrice basePrice let paymentPrice = Advent.Tax.Calc.getPrice salePrice printf "お支払金額 %A 円 (税抜価格 %A 円)" paymentPrice salePrice
お支払金額 86.4 円 (税抜価格 80.0 円)
できました。
1回しか書かないならこれでもいいかもしれません。
別名をつける
(* Display.fs *) namespace Advent module Display = module Sale = Advent.Sale.Calc module Tax = Advent.Tax.Calc let basePrice = 100.0 let salePrice = Sale.getPrice basePrice let paymentPrice = Tax.getPrice salePrice printf "お支払金額 %A 円 (税抜価格 %A 円)" paymentPrice salePrice
お支払金額 86.4 円 (税抜価格 80.0 円)
何度も使うならこれが便利そうですね。
ちなみに
名前が被っていない定義や関数は、特に何もしなくても両方扱えます。
Tax.fsとSale.fsに定義と関数を追加して確認します。
(* Tax.fs *) namespace Advent.Tax module Calc = let taxRate = 0.08 let getPrice basePrice = basePrice * (1.0 + taxRate) (* 以下を追加 *) let oldTaxRate = 0.05 let getOldPrice basePrice = basePrice * (1.0 + oldTaxRate)
(* Sale.fs *) namespace Advent.Sale module Calc = let discountRate = 0.2 let getPrice basePrice = basePrice * (1.0 - discountRate) (* 以下を追加 *) let timesaleDiscountRate = 0.3 let getTimeSalePrice basePrice = basePrice * (1.0 - timesaleDiscountRate)
(* Display.fs *) namespace Advent open System open Advent.Sale open Advent.Tax (* Taxをあとにしてまとめてopen *) module Display = let basePrice = 100.0 let toPercentage rate = rate * 100.0 let paymentPrice = Calc.getPrice basePrice (* TaxのgetPrice *) let taxPer = Calc.taxRate |> toPercentage let oldPaymentPrice = Calc.getOldPrice basePrice let oldTaxPer = Calc.oldTaxRate |> toPercentage let timesalePrice = Calc.getTimeSalePrice basePrice let timesaleDiscountPer = Calc.timesaleDiscountRate |> toPercentage printf "現在のお支払金額 %A 円 (消費税 %A %)\n" paymentPrice taxPer printf "以前のお支払金額 %A 円 (消費税 %A %)\n" oldPaymentPrice oldTaxPer printf "タイムセール価格 %A 円 (%A %引)" timesalePrice timesaleDiscountPer
現在のお支払金額 108.0 円 (消費税 8.0 %) 以前のお支払金額 105.0 円 (消費税 5.0 %) タイムセール価格 70.0 円 (30.0 %引)
ここまでのまとめ
既存モジュールを(擬似的に)拡張したり書き換えたりする
まとめを踏まえて。
既存モジュールと同名のモジュールを作ることで、
既存モジュールを拡張したり書き換えたりしたように見せられる!
ということを書いてみます。
(* StrEx.fs *) namespace Advent.StrEx (* Core.Stringの拡張,書き換え用Stringモジュールを作る *) module String = let greeting (str:string) = "Happy Holidays!" let length (str:string) = str.Length.ToString() + "文字"
(* Program.fs *) namespace Advent open Advent.StrEx (* Core.Stringは暗黙で開かれるのでこれが後になる *) module Execute = let baseString = "aaa" (* 自作関数greetingをString.greetingで使える *) let greetingString = String.greeting baseString printf "%A\n" greetingString (* Core.Stringの関数も使える *) let strings = [baseString; "bbb"] let concatString = String.concat "," strings printf "%A\n" concatString (* Core.StringのlengthではなくStrExのlengthが使える *) let length = String.length baseString printf "%A" length
"Happy Holidays!" "aaa,bbb" "3文字"
アクティブパターン
「match式だけがパターンマッチだと思うなよ」 - memo.txt
本当はこの記事に入るといいねって言われていたアクティブパターンについて。
アクティブ パターンでは、入力データを分割する名前付きパーティションを定義できます。これによって、判別共用体の場合と同様に、パターン マッチ式でそれらの名前を使用できます。アクティブ パターンを使用すると、パーティションごとにカスタマイズした方法でデータを分解できます。
アクティブパターンは関数
定義する
- (|、|)をバナナクリップと呼ぶ。
- let (|パターン識別子|) 引数 = パターン識別子(戻り値)の書式で定義する。
- パターン識別子は大文字始まり。
- (戻り値)部分は任意。
- パターン識別子が1つの場合はlet (|パターン識別子|) 引数 = 戻り値でも定義できる。
let (|Upper|)(str:string) = Upper(str.ToUpper())
引数の文字列を大文字にするstring -> string の関数です。
match式の分岐で使う
- match x with のxを暗黙的に引数にする。
- アクティブパターンの戻り値とパターンを比較する。
let f (str:string) = match str with | Upper "HOGE" -> "HOGE" | Upper "PIYO" -> "PIYO" | x -> "other"
Upperの引数にstrが渡され、大文字になったstrとパターンを比較します。
f "hoge" (* "HOGE" *) f "HOGE" (* "HOGE" *) f "Hoge" (* "HOGE" *) f "pIyO" (* "PIYO" *) f "Foo" (* "other" *) f "" (* "other" *)
パラメータ化されたアクティブパターン
- 最後以外の引数は前から順に部分適用される。
- 最後の引数はmatch x with のxを暗黙的に使う。
let (|Reg|) (pattern:string) (str:string) = Regex.IsMatch(str, pattern) let regex (str:string) = match str with | Reg "^.{2}$" true -> "2文字" | Reg "^.{3}$" true -> "3文字" | _ ->"not match" regex "" (* "not match" *) regex "a" (* "not match" *) regex "aa" (* "2文字" *) regex "aaa" (* "3文字" *) regex "aaaa" (* "not match" *)
パーシャルアクティブパターン
- バナナクリップの中にパターン識別子と_(アンダースコア)を|で区切って書く。
- Opotion<'T>の戻り値がないとエラー。
- 戻り値がSomeの場合、直接値が取れる。
let (|Multiple|_|) (m:int) (n:int) = if n <> 0 && n % m = 0 then Some(n.ToString()) else None let fizzbuzz(n:int) = match n with | n when n < 1 -> "illegal input : " + n.ToString() | Multiple 15 x -> "FizzBuzz : " + x | Multiple 3 x -> "Fizz : " + x | Multiple 5 x -> "Buzz : " + x | _ -> n.ToString() fizzbuzz 3 (* "Fizz : 3" *) fizzbuzz 5 (* "Buzz : 5" *) fizzbuzz 15 (* "FizzBuzz : 15" *) fizzbuzz 1 (* "1" *) fizzbuzz 0 (* "illegal input : 0" *) fizzbuzz -1 (* "illegal input : -1" *)
複数アクティブパターン
- バナナクリップの中に|で区切って複数のパターン識別子を書く。(7つまで)
- すべてのパターン識別子が返されるように定義しないとエラー。
- すべてのパターン識別子にmatchさせないと警告。
- パターン識別子毎に別の戻り値の型を定義できる。
let (|Name|Age|) (input:string) = if Regex.IsMatch(input, "^[0-9]{1,3}$") then Age(int input) else Name(input) let profileCheck(input:string) = match input with |Name x -> x |Age x -> if 10 < x && x < 20 then "target" else "not target" profileCheck "Ann" (* "Ann" *) profileCheck "15" (* "target" *) profileCheck "10" (* "not target" *)
let (|Folder|File|) (input:string) = let attr = File.GetAttributes(input) if attr.HasFlag(FileAttributes.Directory) then Folder else File let pathCheck (input:string) = match input with |File -> "File" |Folder -> "Folder" pathCheck @"c:\" (* "Folder" *) pathCheck @"c:\temp" (* "Folder" *) pathCheck @"c:\temp\MyTest.txt" (* "File" *)
種類と使い分け
1要素のアクティブパターン
- 入力データに名前を付ける。
- すべての入力データに対して行う処理を隠蔽するのに使う。
- 利用者は必ず成功する処理しか書けない。
パーシャルアクティブパターン
- 入力データをある条件に当てはまるデータと、それ以外のデータに分け、当てはまるデータのみに名前を付ける。
- 「それ以外」のデータに興味がない場合に使う。
複数アクティブパターン
- 入力データを分別して、それぞれに名前を付ける。
- データをすべて分別したい場合に使う。
- 利用する際に、使われていないパターンがあると警告が出るので、実装漏れを検知しやすい。
正規表現
お昼休みに正規表現の練習させてもらいました。
※コードはすべてF#です。
「XPathを扱ってみましょう。」
準備
open System.Text.RegularExpressions let matches input pattern = seq { for x in Regex.Matches(input, pattern) -> x} let values input pattern = matches input pattern |> Seq.map(fun x -> x.Value) let x = "/hoge/piyo/text()"
まずは
"/hoge/piyo/text()"から/hoge, /piyo, /textをそれぞれ取り出す。
[]で囲めばいいのでは!
「文字クラスですね。」
values x "/[a-z]"
val it : seq<string> = seq ["/h"; "/p"; "/t"]
...(´・ω・`)
「+つけて。」
values x "/[a-z]+"
val it : seq<string> = seq ["/hoge"; "/piyo"; "/text"]
文字クラス […]
[]内に指定された任意の文字1個をマッチするメタ文字。
文字クラス内の各文字はorが間にあるものとして解釈される。
また、ダッシュ(-)という「文字クラス用メタ文字」は文字の範囲を示す。
例) gr[ae]y : grayとgreyにマッチする。
例) [a-e1-6] : 小文字のアルファベットabcdeと数字123456にマッチする。
プラス記号 +
直前にある要素1個以上を意味するメタ文字。(量指定子)
かたまりで取りたいよね
"/hoge/piyo/text()"から/hoge/piyo/text()を取り出す。
/と()を足せば!
values x "/[a-z/()]+"
val it : seq<string> = seq ["/hoge/piyo/text()"]
/hoge/piyo/text(aaa)は弾きたいよね
"/hoge/piyo/text()"から/hoge/piyo/text()を取り出す。 "/hoge/piyo/text(aaa)"から"/hoge/piyo/text(aaa)"は取り出さない。←new!
()だけ外に出そう。
文字クラスの外に出したら、エスケープが要るんでしたっけ。
values x "/[a-z/]+\(\)"
val it : seq<string> = seq ["/hoge/piyo/text()"]
できたー。
let y = "/hoge/piyo/text(aaa)" values y "/[a-z/]+\(\)"
val it : seq<string> = seq []
よし。
ちょっと書き換える
「[a-z]は\wで書けます。」
「()でくくって、後に+つければいい。」
values x "(/\w+)+\(\)"
val it : seq<string> = seq ["/hoge/piyo/text()"]
単語構成文字 \w *1
.NETでは[a-zA-Z0-9]。
おおむね[a-zA-Z0-9_]と同じ。
ツールによってアンダースコアを含まないこともあれば、そのロケール内のすべての英数字を含むこともある。
Unicodeがサポートされている場合は通常すべての英数字を意味する。
(例外:SunのJava正規表現では、「\w」と「[a-zA-Z0-9_]」は全く同じ意味になる。)
()
以下に用いられるメタ文字。
- 選択の範囲を示す
- 量指定子(?、*、+など)の範囲を示す←今回はこれ
- 後方参照の範囲を示す
/hoge()/piyoは拾いたいね
"/hoge/piyo/text()"から/hoge/piyo/text()を取り出す。 "/hoge/piyo/text(aaa)"から"/hoge/piyo/text(aaa)"は取り出さない。 "/hoge()/piyo"から"/hoge()/piyo"を取り出す。←new!
「| 使って、()で終わるのと終わらないの両方取れるようにすれば。」
()の1と一緒に使えば!
values x "(/\w+\(\)|/\w+)+"
val it : seq<string> = seq ["/hoge/piyo/text()"]
できてる。
values z "(/\w+\(\)|/\w+)+"
val it : seq<string> = seq ["/hoge()/piyo"]
おー。
選択 |
この記号によって隔てられているいずれかの表現とマッチするメタ文字。
ここでお昼休みが終わりましたが
"/hoge/piyo/text(aaa)"から"/hoge/piyo/text(aaa)"は取り出さない。
この記事を書いていて、↑を満たしていないことに気づきました。
values y "(/\w+\(\)|/\w+)+"
val it : seq<string> = seq ["/hoge/piyo/text"]
"/\w+" が "/text(aaa)" から "/text" までを取り出してる。
しばらく悩んで解決できなかったので、相談したところ、FParsecをお勧めいただいたので今回は保留。
宿題:ORは使わなくても書けるよ
"/hoge/piyo/text()"から/hoge/piyo/text()を取り出す。 "/hoge()/piyo"から"/hoge()/piyo"を取り出す。
"/hoge/piyo/text(aaa)"から"/hoge/piyo/text(aaa)"は取り出さない。をとりあえず外す。
values x "(/\w+(\(\))?)+"
val it : seq<string> = seq ["/hoge/piyo/text()"]
できたー。
values z "(/\w+(\(\))?)+"
val it : seq<string> = seq ["/hoge()/piyo"]
今回はここまで。
*1:2014.5.13.修正
「match式だけがパターンマッチだと思うなよ」
この記事は
初めてのアドベントカレンダー
F# Advent Calendar 2013 - connpass 12日目の記事です。
昨日は @k_dispose さんの「F# - SqlEntityConnectionTypeProviderについて - Qiita [キータ]」でした。
明日は id:kyon_mm さんの「F#のなんかすごい話」です。
「パターンマッチを教えてもらった」くらいの内容です
挑発的なタイトルで恐縮です。
「パターンマッチと言えばmatch式」みたいになってるけど、違う! F#では変数を導入する場所ではどこでもパターンマッチできる!
とおっしゃる先輩方に、パターンマッチを教えてもらった備忘録です。
教えていただいたことを、検証しながらまとめました。
パターンマッチとは
(概ね)「単方向の単一化」です。
あるパターンに何かマッチさせること。
MSDN
「パターンは、match 式などの多くの言語構成要素で使用されます。let 束縛、ラムダ式、および try...with 式に関連付けられている例外ハンドラーで関数の引数を処理する場合に使用されます。」
このようにMSDNさんも「match式だけじゃないよ」っておっしゃっています。
変数もパターンマッチ
let x = 3 (* val x : int *)
これもパターンマッチ(変数パターン)。
「変数パターン x に 3 がマッチした」と言える。
変数が導入できるところでは、必ずパターンマッチが使える。
変数を別のパターンに置き換えてみた
let x = 3 の x
let 3 = 3 (* 定数パターン *)
(警告は出るけど)書けました。
let 3 = 4
こう書くとMatchFailureExceptionになります。
let f x = 3 の x
let f (true|false) = 3 (* val f : bool -> int (ORパターン) *)
書けました。
let f x = x の x
type Hoge = | A of string | B of string * int let f (A x | B (x, _)) = x (* val f : Hoge -> string *) (* 識別子パターン・変数パターン・ワイルドカードパターン・ORパターン *)
書けました。
ところで
「これ、どうやって使うんですか。」
「できるよっていうだけ。」
「なるほど。」
「こういうのなら便利だし、よく使うよ!」
(match式以外の)書けて便利なパターンマッチ
レコードを扱う
type Person = { Name : string * string Age : int } let p = { Name = ("NAKAJIMA", "Rika"); Age = 26} let { Name = n; Age = a } = p
p が { Name = n; Age = a }このパターンにマッチする。
val p : Person = {Name = ("NAKAJIMA", "Rika"); Age = 26;} val n : string * string = ("NAKAJIMA", "Rika") val a : int = 26
タプルを扱う
//レコードのコードに↓を追加 let (familyName, lastName) = p.Name
(familyName, lastName)このパターンにp.Nameがマッチする。
val lastName : string = "Rika" val familyName : string = "NAKAJIMA"
レコードとタプルの例はまとめられるよね
type Person = { Name : string * string Age : int } let p = { Name = ("NAKAJIMA", "Rika"); Age = 26} let { Name = (familyName, lastName); Age = a } = p
レコードとタプルの例はこのようにまとめられる。
「パターンはネストすることができるから、便利ですね。」
val p : Person = {Name = ("NAKAJIMA", "Rika"); Age = 26;} val lastName : string = "Rika" val familyName : string = "NAKAJIMA" val a : int = 26
リストを扱う
let (_ :: tail | ([] as tail) ) = [1; 2; 3]
val tail : int list = [2; 3]
一応こっちも。
let (_ :: tail | ([] as tail) ) = [1]
val tail : int list = []
match a with x -> ... の x を扱う
let a b = match b with |([1, 2, 3] | [1, 2, 4]) -> true (* ORパターン *) |other -> false
val a : b:(int * int * int) list -> bool
ちなみに、このORパターン部分は以下のようにも書けます。
let a b = match b with |[1, 2, 3] (* この行頭の | はmatch式の構文 *) |[1, 2, 4] -> true (* この行頭の | はORの | *) |other -> false
さらにORパターンをネストさせてこうも書けます。
let a b = match b with |[1, 2, (3 | 4)] -> true |other -> false
すっきり。
リテラルにもマッチできます
let hoge = 3 match 4 with | hoge -> true | _ -> false
こう書くと1行目のhogeと3行目のhogeは別のhogeになります。
match式内のhogeは変数パターンになり、必ずhogeにマッチしてしまいます。
_ に対して「This rule will never be matched」という警告も出ます。
val hoge : int = 3 val it : bool = true
この3がシステム的に重要な場合など、直接定数を書くとDRYに反する。
そんなときは↓こう書きます。
[<Literal>] let Hoge = 3 match 4 with | Hoge -> true (* Hogeは大文字始まり *) | _ -> false
ちゃんと4にマッチしないためfalseという結果になりました。
val Hoge : int = 3 val it : bool = false
まとめ
- パターンマッチはあるパターンに対して何かをマッチさせること
- 変数が使えるところではどこでもパターンマッチを使える
- match式以外でも便利に使える
Clean Codeを教えてもらった(2)
全4回予定の2回目。
進め方
- いくつか段落を読む
- 感想やわからなかったことを話す
- 教えてもらったりお話したりする(初めに戻る)
これを1時間繰り返す。
教えてもらったこと・気になったこと
デメテルの法則
「オブジェクトを使用する場合、そのオブジェクトの内部について知るべきではない」
「クラスCのメソッドfは次のオブジェクトのメソッドのみを呼び出すべき」= 知らないものを触っちゃいけない
- Cそのもの
class C { boolean a(int x) { return x > 0 } boolean f(int y) { return a(y) } }
- fで生成されたオブジェクト
class C { C2 f1() { new C2() } int f2(int x, int y) { f1().a(x, y) } } class C2 { int a(int x, int y) { x + y } }
- fの引数で渡されたオブジェクト
class C { int f(C2 c2, int x, int y) { c2.(x, y) } } class C2 { int a(int x, int y) { x + y } }
- Cのインスタンス変数に保持されたオブジェクト
class C { C2 c2 = new C2() int f(int x, int y) { c2.a(x, y) } } class C2 { int a(int x, int y) { x + y } }
Clean Codeを教えてもらった(1)
全4回予定の1回目。
進め方
- いくつか段落を読む
- 感想やわからなかったことを話す
- 教えてもらったりお話したりする(初めに戻る)
これを1時間繰り返す。
読んだところ
- 第3章 関数の「動詞とキーワード」から章の終わりまで(P75~86)
- 第6章 オブジェクトとデータ構造の初めから「データ/オブジェクトの非対称性」まで(P137~142)
※サンプルコードは本文の意味が分かる程度しか読んでません。
教えてもらったこと・気になったこと
動詞とキーワード
- 引数が1つの場合、関数名と引数は洗練された動詞/名詞の組み合わせになる。
- 引数が複数の場合、名前に引数を順番通りに表現することで引数の順番を覚えておかなければならない問題を軽減することができる。
戻りコードよりも例外を好む
try/catchブロックの分離・エラー処理も1つの処理
- 「try/catchブロックはそれ自体が不恰好です。」
- tryブロック・catchブロックの中身を関数として外に出す方がいい。
- 関数は1つの処理のみを行うべき。エラー処理も1つの処理。
→エラー処理を行う関数(try/catch)は他のことをすべきでない。
なぜ関数をこのように書くのでしょう?
- 「最初から完璧なものが書けるわけではないのです。そんなことは、おそらく誰にもできないでしょう。」
結論(第3章)
データ抽象化・データ/オブジェクトの非対称性
- データと振る舞いを持つオブジェクト:エンティティオブジェクト
- データだけを持ったオブジェクト:バリューオブジェクト