Skip to content

Instantly share code, notes, and snippets.

@asufana
Last active January 13, 2016 15:59
Show Gist options
  • Save asufana/a1dcca23102ea5609257 to your computer and use it in GitHub Desktop.
Save asufana/a1dcca23102ea5609257 to your computer and use it in GitHub Desktop.
F# (FSharp) snippets

F# (FSharp) snippets

拡張メソッド

既存の型に関数を追加する

#!/usr/bin/env fsharpi

module MyEx =
  type System.String with
    member x.smile() = x + " :)"

open MyEx
"Hello World".smile() |> printfn "%A"
// "Hello World :)"

例外処理

fsharp には try-with と try-finally がそれぞれ用意されているため、try-catch-finally する場合にはネストする必要がある

#!/usr/bin/env fsharpi

//例外が発生する関数
let someFunction1 = function
    | n when n < 1 -> failwith "例外です"
    | n            -> n

let test01 =
  try
    try
      someFunction1 0 |> printfn "result : %d"
    with
    | e -> printfn "Error: %s" e.Message
  finally
    printfn "Finallyです"

test01
//Error: 例外です
//Finallyです

処理結果をEitherで返却し、結果をパターンマッチでハンドルする

#!/usr/bin/env fsharpi

open System

type Either<'T, 'U> =
  | Left  of 'T
  | Right of 'U

let someFunction2 = function
  | n when n < 1 -> Left (ArgumentException "例外です")
  | n            -> Right n

let test02 =
  match (someFunction2 0) with
  | Left e  -> printfn "%s" e.Message
  | Right x -> printfn "result: %d" x

test02
//Error: 例外です

ActivePattern

パターンマッチ処理の機能は、1)分類する、2)処理するに分けられる。何らかの値を分類することに特化した機能がActivePattern。

EvenとOddと2つの分類を定義し、渡された数値をそのいずれかに分類する

#!/usr/bin/env fsharpi

let (|Even|Odd|) n =
  if n % 2 = 0 then Even else Odd

let evenOrOdd n =
  match n with
  | Even -> printfn "%A is Even." n
  | Odd  -> printfn "%A is Odd." n

evenOrOdd 1 // 1 is Odd.
evenOrOdd 2 // 2 is Even.
evenOrOdd 3 // 3 is Odd.
evenOrOdd 4 // 4 is Even.

System.DateTimeを引数に取り、年月日に分類する

#!/usr/bin/env fsharpi

let (|Date|) (dt:System.DateTime) =
	(dt.Year, dt.Month, dt.Day)

(|Date|) System.DateTime.Now |> printfn "Date: %A"
//Date: (2015, 12, 9)

失敗する場合の分類は (|XXX|_|) とする

#!/usr/bin/env fsharpi

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 = function
  | Fizz fizz & Buzz buzz -> fizz + buzz //ANDできる
  | Fizz fizz             -> fizz
  | Buzz buzz             -> buzz
  | n                     -> string n

[1..20] |> List.map fizzBuzz |> printfn "FizzBuzz1: %A"
//FizzBuzz1: ["1"; "2"; "Fizz"; "4"; "Buzz"; "Fizz"; "7"; "8"; "Fizz"; "Buzz"; "11"; "Fizz"; "13"; "14"; "FizzBuzz"; "16"; "17"; "Fizz"; "19"; "Buzz"]

Fizz も Buzz も処理構造は同じなので引数を2つ取る関数でまとめられる

#!/usr/bin/env fsharpi

let (|Mul|_|) m n = if n % m = 0 then Some 1 else None
let fizzBuzz2 = function
  | Mul 15 _ -> "FizzBuzz"
  | Mul 5 _  -> "Buzz"
  | Mul 3 _  -> "Fizz"
  | n        -> string n

[1..20] |> List.map fizzBuzz2 |> printfn "FizzBuzz2: %A"
//FizzBuzz2: ["1"; "2"; "Fizz"; "4"; "Buzz"; "Fizz"; "7"; "8"; "Fizz"; "Buzz"; "11"; "Fizz"; "13"; "14"; "FizzBuzz"; "16"; "17"; "Fizz"; "19"; "Buzz"]

文字列が正規表現でマッチするかをActivePatternで定義する

#!/usr/bin/env fsharpi

open System.Text.RegularExpressions

//Log文字列パーサー
let (|LogParser|_|) text =
  let regex =
    Regex (
      @"^(?<date>[0-9\/]*?) " +
      @"(?<time>[0-9:]*?) " +
      @"(?<log>.*)$",
      RegexOptions.IgnoreCase)

  let matchResult = regex.Match(text)
  if matchResult.Success then
    let matchedValue (matchResult:Match) (key:string) = matchResult.Groups.[key].Value.Trim()
    let ms = matchedValue matchResult
    Some (ms "date", ms "time", ms "log")
  else
    None

//Log型
type Log = {
    Date: string
    Time: string
    Log:  string
    }

//Parserがマッチしたら、Log型で返却する
let parser = function
  | LogParser (date, time, log) -> Some { Date = date; Time = time; Log = log}
  | _                           -> None

//ログ行をパースする
[ "Apache...";
  "2015/12/05 11:43 hoge...";
  "2015/12/05 11:44 moge..." ]
  |> List.map parser
  |> List.filter(fun log -> log.IsSome)
  |> printfn "Log: %A"

//Log: [Some {Date = "2015/12/05"; Time = "11:43"; Log = "hoge...";}; Some {Date = "2015/12/05"; Time = "11:44"; Log = "moge...";}]

コンピュテーション式

コンピュテーション式で Maybeモナドを定義する

#!/usr/bin/env fsharpi

//ビルダークラスの定義
type MaybeBuilder() =
  //return時に呼び出される
  member this.Return(x) = Some x

  //let!時に呼び出される( haskellのbind >>= )
  member this.Bind(x, f) =
    match x with
    | Some x -> f x
    | _      -> None

//ビルダークラスのインスタンスを生成
let maybe = new MaybeBuilder()

//ビルダークラスのインスタンスを利用することで、コンピュテーション式が記述できる
//コンピュテーション式はビルダークラスのルールが適用される(ここではNoneを処理をしないというルール、つまり処理の失敗を無視する)
//haskellのdo記述するものと同じ

let someCompEx n =
  //とある処理(Noneになる=失敗する可能性がある)
  let someFunction = function
    | n when n % 2 = 0 -> Some n
    | n when n % 3 = 0 -> Some n
    | _                -> None

  //コンピュテーション式
  maybe {
    let! ret1 = someFunction n
    let! ret2 = someFunction (n + 1)
    return ret1 + ret2
  }

//失敗せずに計算できる
[1..10] |> Seq.map someCompEx |> printfn "%A"
//seq [null; Some 5; Some 7; null; ...]

haskellのmaybeモナドと同じ

class Monad m where
  return :: a -> m a                 --上記Retern関数に相当
  (>>=)  :: m a -> (a -> m b) -> m b --上記Bind関数に相当

instance Monad maybe where
  return = Just            --上記Return定義と同様
  Just x  >>= f = f x      --上記Bind定義と同様
  Nothing >>= _ = Nothing  --上記Bind定義と同様

bind処理 m a -> (a -> m b) -> m b は、「aをbにする関数」を a に適用して、b にするだけ

  maybe {
    let! ret1 = someFunction n        // bind
    let! ret2 = someFunction (n + 1)  // bind
    return ret1 + ret2                // return
  }

つまり上記はJava8で書けば、下記と同様

Optional.of(someFunction(n))
        .flatMap(ret1 -> someFunction(n+1)
        .map(ret2 -> ret 1 + ret2)

NuGetのインストール

下記のコードでは外部モジュールを利用するため、パッケージマネージャNuGetをインストールする

  • https://www.nuget.org/ から latest nuget.exe をダウンロード
  • 適当なフォルダに配置する
  • 以下のシェルスクリプト nuget を作成
#!/bin/sh
script_dir="$(cd "$(dirname "${BASH_SOURCE:-${(%):-%N}}")"; pwd)"
mono --runtime=v4.0 ${script_dir}/nuget.exe $*

正しく実行されることを確認

$ chmod 700 nuget
$ ./nuget

Httpクライアントの利用

ライブラリの取得

$ nuget install FSharp.Data
$ cp FSharp.Data.2.2.5/lib/net40/*.dll .
$ rm -rf FSharp.Data.2.2.5

HTTPリクエスト処理

ダウンロードしたDLLを #r で指定

#!/usr/bin/env fsharpi

#r @"FSharp.Data.dll"

open FSharp.Data
open FSharp.Data.JsonExtensions

let content = Http.RequestString("http://api.zipaddress.net/?zipcode=4530809")
let info = JsonValue.Parse(content)
printfn "content: %A" info

let pref = info?data?pref
printfn "pref: %A" pref

Database接続(MSSQL)

ライブラリの取得

$ nuget install FSharp.Data
$ nuget install FSharp.Data.SqlClient

接続処理

参考:http://fsprojects.github.io/FSharp.Data.SqlClient/configuration%20and%20input.html

#!/usr/bin/env fsharpi

#r @"FSharp.Data.dll"
#r @"Microsoft.SqlServer.TransactSql.ScriptDom.dll"
#r @"FSharp.Data.SqlClient.dll"

open FSharp.Data
open FSharp.Data.SqlClient

//Literal属性で定数化する
//Blogデータベースに sa/password で接続する
[<Literal>]
let connStr = @"Server=10.19.255.84;Initial Catalog=BLOG;User ID=sa;Password=password"
[<Literal>]
let query = "select id,name from dbo.Person"

type SelectUser = SqlCommandProvider<query, connStr>

(new SelectUser())
  .Execute()
  |> Seq.toArray
  |> printfn "result: %A"

Database接続(Postgres)

ライブラリの取得

$ nuget install SQLProvider -prerelease
$ nuget install Npgsql

接続処理

参考:http://fsprojects.github.io/SQLProvider/

#!/usr/bin/env fsharpi

#r @"FSharp.Data.SQLProvider.dll"

open FSharp.Data.Sql

type sql =
  SqlDataProvider<
    ConnectionString =
      """
      Host=127.0.0.1;
      Port=5432;
      Database=Blog;
      Username=postgres;
      Password=password
      """,
    DatabaseVendor = Common.DatabaseProviderTypes.POSTGRESQL,
    ResolutionPath = @"/Users/hana/fsharp/test/",
    UseOptionTypes = true
    >

let ctx = sql.GetDataContext()

//とりあえず取得する
ctx.``[public].[blogpost]``
  |> Seq.map (fun e -> e.postid)
  |> Seq.toArray
  |> printfn "post: %A"

//where句やjoin句を指示して取得する
query {
  for post in ctx.``[public].[blogpost]`` do
  //Join指示
  //comment.postid は nullable なので、Optionとして型推論される、そのために.Valueを付加する
  join comment in ctx.``[public].[blogcomment]`` on (post.id = comment.postid.Value)
  where (post.postid="99999")
  sortByDescending post.id
  select (post.id,commet.id)
  }
|> Seq.toArray
|> printfn "posts: %A"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment