2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

このletは何をしているのか?

Posted at

F#はMicrosoftのDon Syme氏が開発したプログラミング言語で、命令型、オブジェクト指向、関数型の要素を併せ持つマルチパラダイム言語と位置付けられています。実装はオープンソースのMITライセンスのもと、githubのリポジトリで公開されています。

実行環境は.NET Frameworkおよび.NET CoreやMonoといったマルチプラットフォームに対応していますが、サポート状況については各プラットフォームごとの情報を参照してください(「F# の概要」など)。

なお、ここで対象とするF#のバージョンは4.1(FSharp.Core 4.4.1.0)とします。

letは定義するもの

F#のletは変数や関数を定義するのに使われます。どこに出てくるかによって役割が変わることがあります。また、letに追加するキーワードによっても定義されるものの使われ方が変わります。

変数に値を設定するとき、そのあとに値を変更できるようにするにはlet mutableとしなければなりません。値を変更するときは=ではなく<-を使います。

letによる定義
(* 変数の定義 *)
let a = 1    // 変数に値を設定(値の変更不可)
let mutable b = 2    // 値の変更可
b <- 3    // 値を変更

関数の定義でlet mutableを使うときは、関数をfun args -> ...のように定義しなくてはなりません。

letによる定義
(* 関数の定義 *)
let f a b = a + b  // 関数の内容を変更できない
f 2 3    // 5
let mutable g = fun a b -> a + b  // 変数の内容を変更できる
g 2 3  // 5
g <- fun a b -> a * b
g 2 3  // 6

また、繰り返し処理で使われる再帰関数(自分自身を呼び出す関数)を定義するときはlet recで定義します。

recで再帰関数を定義
let rec len n = match n with h :: t -> 1 + len t | [] -> 0  // リストの長さ(要素の数)を数える
len [5; 3; 7]  // 3

変数や関数の型を指定するときは、その名称に:型名を続けます。

変数や関数の型を限定
let n: int = 3
let f (a: int) (b: int) = a + b
let g: int -> int = fun x -> x * 2

letでは複数の変数それぞれに値を定義できます。また、タプルの各要素の値を別々の変数に設定できます。

letで複数の値を同時に定義
let a, b, c = 3, 1, 2    // a = 3, b = 1, c = 2
let a, b, c = (3, 1, 2)  // a = 3, b = 1, c = 2

letの中にブロック

letは変数や関数を定義するものですが、その中でより複雑な処理の内容を定義するために、2行目以降にインデントをつけて処理のまとまりを表すブロックを定義できます。インデントの深さ(行の書き始めの位置)が同じなら同じ層のブロックとなります。

letで処理の定義
let rec sum n =
  (* 2行目以降にインデントをつけるとブロックになる *)
  match n with
  | h :: t -> h + sum t
  | []     -> 0

sum [5; 3; 7]  // 15 (各要素の合計)

letの中にlet

ブロック中には別のletも定義できます。ブロック内で定義された変数や関数はその中のみで使えます。また、ブロック内で最後に得られた結果がそのブロックの処理結果となります。

letの中にlet
(* リストの各要素の平均値を計算する関数 *)
let avg n =
  let rec len n = match n with h :: t -> 1 + len t | [] -> 0
  let rec sum n = match n with h :: t -> h + sum t | [] -> 0
  sum n / len n  // ブロックの最終結果 → この関数の処理結果

avg [5; 3; 7]  // 15

(* avg内のlenやsumはavgの外では実行できない *)
// len [5; 3; 7]
// sum [5; 3; 7]

letでは終われない

こうして複数行にわたって書かれるブロックはletで終われないことになっています。そのため、ブロックはletの次にそれ以外の処理を実行してから終わるようにします。

letでは終われないブロックの例
let f a b =
  let x = a
  let y = b
  ()  // letでは終われない

for i in {1..5} do
  let a = i
  ()  // letでは終われない

type C(x: int) =
  do
    let a = x
    ()  // letでは終われない

#if INTERACTIVE
#nowarn "25"
#endif
try
  let a = None
  let (Some x) = a  // パターンが一致しない(withブロックへ)
  ()  // letでは終われない
with
  ex -> eprintfn "%s" ex.Message  // "一致条件が不完全でした"

アクティブパターン

アクティブパターンは判定の結果を表す識別子を定義するもので、すべての結果に識別子をつけるものと、結果の一部のみに識別子を定義するパーシャルアクティブパターンがあります。

アクティブパターンはletの次に(|識別子|...|)で識別子を定義し、右辺で識別子を割り振る処理を定義します。

letによるアクティブパターンの定義
let (|Odd|Even|) n = if n % 2 = 0 then Even else Odd  // アクティブパターンの定義
let a = 1
match a with Odd -> "odd" | Even -> "even"  // "odd"

パーシャルアクティブパターンはletの次の識別子の定義を(|識別子|_)とし、処理の内容をSome 値Noneのどちらかが割り振られるように定義します。

letによるパーシャルアクティブパターンの定義
let (|Fizz|_|) n = if n % 3 = 0 then Some "Fizz" else None
let (|Buzz|_|) n = if n % 5 = 0 then Some "Buzz" else None

let fizzbuzz n =
    match n with
    | Fizz s ->
        match n with
        | Buzz t -> s + " " + t  // "Fizz Buzz"
        | _      -> s            // "Fizz"
    | _      ->
        match n with
        | Buzz s -> s         // "Buzz"
        | _      -> string n  // それ以外の数

[1..15] |> List.map fizzbuzz
// ["1"; "2"; "Fizz"; "4"; "Buzz"; "Fizz"; "7"; "8"; "Fizz"; "Buzz"; "11";  "Fizz"; "13"; "14"; "Fizz Buzz"]

列挙型の識別子と値の変換

letenumを使うと、列挙型の識別子と、それに定義づけられた値とを変換できます。

letで列挙型の識別子と値の変換
(* 列挙体の定義 *)
type En =
| A = 1
| B = 2

(* 列挙体の値を変数に設定(以下のletはいずれも等価) *)
let ea = En.A        // 識別子を指定
let ea = enum<En> 1  // 値とジェネリックを指定
let ea: En = enum 1  // 変数に型を指定

ea = En.A  // true

判別共用体の値を取り出す

letには判別共用体の値を取り出す機能もありますが、パターンに当てはまらない場合があるときは「この式のパターン マッチが不完全です」という警告が出ることがあります。

判別共用体の値を取り出す
type DU = A of int
let (A a) = A 1    // a = 1

type DU = A of int * string
let (A (a, b)) = A (1, "uno")  // a = 1, b = "uno"

type DU = A of int | B of string
let (A a) = A 1      // 警告「この式のパターン マッチが不完全です」(Aしか当てはまらない)
let (B b) = B "uno"  // 警告「この式のパターン マッチが不完全です」(Bしか当てはまらない)

mutableにはレコード型にも

mutableletだけでなくレコード型にも定義できます。これにより、レコード型の値も後から変更できるようになります。

mutableはレコード型にも定義できる
type R = {mutable a: int; b: string}  // aを変更できるレコード型
let r = {a = 1; b = "uno"}            // レコード型の定義
r.a <- 2  // {a = 2; b = "uno"}       // aの値を変更

測定単位と型

測定単位を使うと、数値に付随する単位を加味した計算ができます。測定単位は[<Measure>] type 単位のように定義し、単位を持つ数値は数値<単位>のように表します。こうした単位が付随する数値の型名は数値型<単位>となります。単位は、たとえば1<m>1<s>で割ると1<m/s>というように計算の結果が逐次反映されます。

数値に単位をつけるには1<単位>を掛けます。逆に数値から単位をはずすにはintfloatなどでキャスト(型変換)します。

測定単位と型
(* 測定単位の設定 *)
[<Measure>] type m
[<Measure>] type cm
[<Measure>] type s

(* 単位を使った計算 *)
let v = 6.75<m> / 1.5<s>  // 4.5<m/s>
let a = v / 1.5<s>        // 3.0<m/s^2>

(* 単位を変換する関数 *)
let m2Cm (n: float<m>)  = n * 100.0<cm/m>
let cm2M (n: float<cm>) = n / 100.0<cm/m>

(* 関数の実行例 *)
m2Cm 1.5<m>     // 150.0 : float<cm>
cm2M 150.0<cm>  // 1.5 : float<m>

(* 単位の付加 *)
let setM n = n * 1.0<m>
setM 1.5  // 1.5<m>

(* 単位の除去 *)
let rejM (n: float<m>) = float n
rejM 1.5<m>  // 1.5

module内のlet

モジュール内でletにより定義された変数や関数は、モジュールの外からでも、モジュールにアクセス可能であれば利用できます。しかしモジュール内でlet privateにより定義されたものはモジュールの外からアクセスできません。

module内のlet(モジュール並行)
module MA =
  let a = 1
  let private pa = 3

module MB =
  let b = 2
  let private pb = 4

MA.a  = 1  // true
// MA.pa = 3  にはアクセスできない
MB.b  = 2  // true
// MB.pb = 4  にはアクセスできない
module内のlet(モジュール内包)
(* コメント行で示したようにはアクセスできない *)
module MA =
  module MB =
    let b = 2
    let private pb = 4
  let a = MB.b
  // let private pa = MB.pb

let c = MA.a
let d = MA.MB.b
// let pd = MA.MB.pb

クラス内のlet

プライマリコンストラクタを持つクラスでは、インスタンスを生成するごとにtype内にあるletが実行されます。これは抽象クラスと具象クラスのどちらでも行われます。このletではコンストラクタ変数を使った処理も実行できます。letで定義された変数や関数はインスタンスのメンバーもアクセスできます。ただし、これらに対しインスタンスの外部からはアクセスできません。

クラス内のlet
(* プライマリコンストラクタのあるクラスではインスタンス生成時にletが実行される *)
(* 抽象クラス *)
[<AbstractClass>]
type AC(x: int, y: int) =
  let a, b = x, y
  let f a b = a + b
  do
    printfn "%i + %i = %i" a b <| f a b
  abstract member F: int with get

(* 具象クラス(ACを継承) *)
type C(x, y) as self =
  inherit AC(x, y)
  let a, b = (x + 1), (y + 1)
  let f a b = a * b
  do
    printfn "%i * %i = %i" a b <| self.F
  override this.F = f a b

let c = C(2, 3)  // "2 + 3 = 5", "3 * 4 = 12"の表示(doブロックの実行)
c.F  // 12
// c.a, c.b, c.fにはアクセスできない

ここをご覧いただいた方々の参考になればと思います。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?