Skip to content

Instantly share code, notes, and snippets.

@adacola
Last active December 31, 2015 22:59
Show Gist options
  • Save adacola/8057517 to your computer and use it in GitHub Desktop.
Save adacola/8057517 to your computer and use it in GitHub Desktop.
F# Advent Calendar 21日目の記事で使用したコードです。 http://qiita.com/adacola/items/b65752b678e81bc8e354
// 単位の定義
[<Measure>] type m
[<Measure>] type s
[<Measure>] type kg
[<Measure>] type 歳
// 単位の別名
[<Measure>] type meter = m
// 組立単位は単位を組み合わせて定義します。単位 N は kg * m / (s * s) の別名扱いです。
[<Measure>] type N = kg * m / (s * s)
// * は省略可能。べき乗も使用可能。べき乗は乗除よりも優先順位が高くなるので、下の例の「s^2」部分はカッコ不要。
[<Measure>] type J = kg m^2 / s^2
// 無単位と同じ次元数の単位の定義
[<Measure>] type mol = 1
// 単位のついた値の定義
let height = 1.8<m>
let weight = 60<kg>
// 単位を付与可能な基本型
let i8 = 1y<m> // sbyte
// let ui8 = 1uy<m> // byte コンパイルエラー
let i16 = 1s<m> // int16
// let ui16 = 1us<m> // uint16 コンパイルエラー
let i32 = 1<m> // int32
// let ui32 = 1u<m> // uint32 コンパイルエラー
let i64 = 1L<m> // int64
// let ui64 = 1UL<m> // uint64 コンパイルエラー
let f32 = 1.f<m> // float32
let f64 = 1.<m> // float64
let d = 1M<m> // decimal
// let bi = 1I<m> // bigint コンパイルエラー
// 単位を使用した関数の定義。戻り値の単位も型推論されます。
let circumference (radius : float<m>) = 2. * radius * System.Math.PI
// val circumference : radius:float<m> -> float<m>
// 単位の付いた数値型同士の演算。乗除算すると新たな単位が生成されます。
let speed (distance : float<m>) (time : float<s>) = distance / time
// val speed : distance:float<m> -> time:float<s> -> float<m/s>
// let invalidSpeed (distance : float<m>) (time : float<s>) = distance - time // コンパイルエラー。異なる単位の加減算は不可能。
// 自分で定義した組立単位にするには明示的に型を指定してあげればOK
let force (mass : float<kg>) (acceleration : float<m/s^2>) = mass * acceleration
// val force : mass:float<kg> -> acceleration:float<m/s ^ 2> -> float<kg m/s ^ 2>
let force2 (mass : float<kg>) (acceleration : float<m/s^2>) : float<N> = mass * acceleration
// val force2 : mass:float<kg> -> acceleration:float<m/s ^ 2> -> float<N>
// 別名単位同士は演算可能
let f1 = force 1.<kg> 1.<m/s^2>
// val f1 : float<kg m/s ^ 2> = 1.0
let f2 = force2 2.<kg> 3.<m/s^2>
// val f2 : float<N> = 6.0
let f = f1 + f2
// val f : float<kg m/s ^ 2> = 7.0
// 無単位の数値に単位を付与する方法
let floatToSecond (x : float) = x * 1.<s>
let floatToSecond' x : float<s> = LanguagePrimitives.FloatWithMeasure x
let floatToSecond'' x = LanguagePrimitives.FloatWithMeasure<s> x
// LanguagePrimitivesには各数値型ごとの~WithMeasure関数が用意されている
let intToSecond x : int<s> = LanguagePrimitives.Int32WithMeasure x
// 単位を除去する方法
let toPlainFloat (x : float<s>) = x / 1.<s>
let toPlainFloat' (x : float<s>) = x * 1.</s> // 現在の単位の逆の次元の単位を掛ける
let toPlainFloat'' (x : float<s>) = float x
let toPlainInt (x : int<s>) = int x
// パフォーマンス計測
// 単位を使わないパターン
let noMeasure() =
let mutable r = 0.
for i = 1 to 10000000 do r <- r + float i * 42.
printfn "%f" r
// 単位を使うパターン
let withMeasure() =
let mutable r = 0.<m>
for i = 1 to 10000000 do r <- r + float i * 42.<m>
printfn "%f" <| float r
// 内部的には単位を使って計算するが、最終的に単位を消去するパターンその1。単位を打ち消す演算をして消去。
let eraseMeasure() =
let mutable r = 0.
for i = 1 to 10000000 do r <- r + float i * 42.<m> * 1.</m>
printfn "%f" r
// 最終的に単位を消去するパターンその2。float関数を使って消去。
let eraseMeasure2() =
let mutable r = 0.
for i = 1 to 10000000 do r <- r + (float i * 42.<m> |> float)
printfn "%f" r
(*
> noMeasure();;
2100000210000000.000000
リアル: 00:00:00.131、CPU: 00:00:00.125、GC gen0: 0, gen1: 0, gen2: 0
val it : unit = ()
> withMeasure();;
2100000210000000.000000
リアル: 00:00:00.129、CPU: 00:00:00.125、GC gen0: 0, gen1: 0, gen2: 0
val it : unit = ()
> eraseMeasure();;
2100000210000000.000000
リアル: 00:00:00.128、CPU: 00:00:00.125、GC gen0: 0, gen1: 0, gen2: 0
val it : unit = ()
> eraseMeasure2();;
2100000210000000.000000
リアル: 00:00:00.131、CPU: 00:00:00.125、GC gen0: 0, gen1: 0, gen2: 0
val it : unit = ()
*)
// 標準に入っているSI単位系
open Microsoft.FSharp.Data.UnitSystems.SI.UnitSymbols
// フルネーム版
// open Microsoft.FSharp.Data.UnitSystems.SI.UnitNames
// F# PowerPack に含まれている物理定数
open Microsoft.FSharp.Math.PhysicalConstants
// 単位 kg, m, N は Microsoft.FSharp.Data.UnitSystems.SI.UnitSymbols から
// 万有引力定数 G は Microsoft.FSharp.Math.PhysicalConstants から
let forceOfGravity (m1 : float<kg>) (m2 : float<kg>) (r : float<m>) : float<N> = G * m1 * m2 / (r * r)
// 単位変換
[<Measure>] type ft
[<Literal>]
let FeetPerMeter = 3.2808399<ft/m>
let meterToFeet (x : float<m>) = x * FeetPerMeter
let feetToMeter (x : float<ft>) = x / FeetPerMeter
let oneMeter = meterToFeet 1.<m>
// val oneMeter : float<ft> = 3.2808399
let oneFeet = feetToMeter 1.<ft>
// val oneFeet : float<m> = 0.3047999995
// 単位にstaticなプロパティやメソッドを追加可能
[<Measure>]
type km =
static member PerMeter = 0.001<km/m>
static member toMeter (x : float<m>) = x * km.PerMeter
let kmDistance1 = 2500.<m> * km.PerMeter
// val kmDistance1 : float<km> = 2.5
let kmDistance2 = km.toMeter 40000.<m>
// val kmDistance2 : float<km> = 40.0
// ジェネリック単位を使用した関数。'lengthがジェネリックな単位。
let calcSpeed (x : float<'length>) (time : float<s>) = x / time
// val calcSpeed : x:float<'length> -> time:float<s> -> float<'length/s>
let speed2 = calcSpeed 100.<m> 5.<s>
// val speed2 : float<m/s> = 20.0
// let add1 (x : float<'u>) = x + 1.<'u> // コンパイルエラー
let add1 (x : float<'u>) = x + 1.<_>
// warning FS0064: このコンストラクトによって、コードの総称性は型の注釈よりも低くなります。単位変数 'u' は単位 '1' に制約されました。
let add1' (x : float<'u>) = x + LanguagePrimitives.FloatWithMeasure 1.
let add0 (x : float<'u>) = x + 0.<_> // 0に省略したジェネリック単位を付与することはなぜか可能。使い道は0との比較をしたい場合くらい?
// ジェネリック単位の次数
let mySqrt (x : float<'u ^ 2>) = sqrt x
// val mySqrt : x:float<'u ^ 2> -> float<'u>
let area = 9.<m^2>
let length = mySqrt area
// val length : float<m> = 3.0
// 組立単位の次数も自動的に判別
let two = sqrt 4.<m^2/s^2>
// val two : float<m/s> = 2.0
// let akan = sqrt 4.<m/s^2> // コンパイルエラー 基本単位の m 部分の次数が2ではないので型が合わない
// let p = pown 2.<m> 3 // コンパイルエラー pown や ** では単位を使用できない(次数が静的に決定しないから)
let square (x : float<'u>) = x * x // 静的に次数が決定する場合はOK
// 任意の単位を持つメンバを含むレコード
type CurrencyRatio<[<Measure>]'currency, [<Measure>]'baseCurrency> = {
Currency : float<'currency>
BaseCurrency : float<'baseCurrency>
} with
member x.Ratio = x.Currency / x.BaseCurrency
[<Measure>]type USD
[<Measure>]type JPY
[<Measure>]type EUR
let jpyPerUsd = { Currency = 100.<JPY>; BaseCurrency = 1.<USD> }
let ratio = jpyPerUsd.Ratio
// val ratio : float<JPY/USD> = 100.0
let oldJpyPerUsd = { jpyPerUsd with Currency = 360.<JPY> }
// let eurPerUsd = { jpyPerUsd with Currency = 1.37<EUR> } // コンパイルエラー レコード変更後の型は変更前の型と一致しなければならない
let eurPerUsd2 = { Currency = 1.37<EUR>; BaseCurrency = 1.<USD> }
// 任意の単位を持つメンバを含む判別共用体
type Price<[<Measure>]'currency> =
| Money of amount : int<'currency>
| Article of unitPrice : int<'currency> * count : int
with
member x.TotalPrice = match x with Money a -> a | Article(u, c) -> u * c
let money = Money 100<JPY>
let moneyPrice = money.TotalPrice
// val moneyPrice : int<JPY> = 100
let article = Article(unitPrice = 5<USD>, count = 3)
let articlePrice = article.TotalPrice
// val articlePrice : int<USD> = 15
// 任意の単位を持つメンバを含むインターフェース、クラス
type ILength<[<Measure>]'length> =
abstract Length : float<'length>
type Person<'name, [<Measure>]'length>(name : 'name, age : int<歳>, height : float<'length>) =
member x.Name = name
member x.Age = age
member x.Height = height
interface ILength<'length> with
member x.Length = height
let mimorin = Person("三森すずこ", 27<歳>, 1.59<m>)
// val mimorin : Person<m>
let mimoHeight = mimorin.Height
// val height : float<m> = 1.59
let mimoLength = (mimorin :> ILength<m>).Length
// val mimoLength : float<m> = 1.59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment