21
21

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.

doブロックとコンピュテーション式

Last updated at Posted at 2016-06-30

Haskellでdoブロックを勉強したら、何となくコンピュテーション式の作り方が見えて来ました。現段階の理解を書いてみます。

※ 必ずしもHaskellを経由しないと理解できないわけではありません。個人的な経験として、コンピュテーション式は自由度が高過ぎて、うまく取っ掛かりがつかめなかったのだと思います。

この記事には続編があります。

Haskell

doブロックにおける 変数 <- アクション が基本的な構文です。アクションから値を取り出して変数に束縛することを表現しています。

doブロック

関数モナドの例を示します。

Haskell
test = do
    a <- (+ 1)
    b <- (* 2)
    return (a, b)

main = do
    print (test 3)
    print (test 5)
実行結果
(4,6)
(6,10)

<-を「取り出して束縛」と解釈すれば、割と直観的なコードに見えます。何を取り出しているかと言うと、この場合はtestに引数として渡した値を適用した結果です。

  • (+ 1) 33 + 1 = 4
  • (* 2) 33 * 2 = 6
  • (+ 1) 55 + 1 = 6
  • (* 2) 55 * 2 = 10

doを剥がす

doブロックは糖衣構文で、剥がすと>>=で連結された式が出て来ます。

Haskell
test =
    (+ 1) >>= \a ->
    (* 2) >>= \b ->
    return (a, b)

main =
    print (test 3) >>= \_ ->
    print (test 5)
実行結果
(4,6)
(6,10)

a(+ 1)の位置が逆になっているのに注目してください。(後でまとめて比較します)

関数で書く

testはただの関数なので普通の書き方に直してみます。

mainは追求しても仕方ないのでdoに戻します。

Haskell
test x =
    let a = (+ 1) x in
    let b = (* 2) x in
    (a, b)

main = do
    print (test 3)
    print (test 5)
実行結果
(4,6)
(6,10)

このコードと比較すれば、doブロックはポイントフリースタイルのようになって引数が省略されていると見なせます。(後でまとめて比較します)

F♯

【注】F#ではアクションという呼び方はしませんが、ここでは便宜上Haskellに合わせています。

コンピュテーション式における let! 変数 = アクション が基本的な構文です。アクションから値を取り出して変数に束縛することを表現しています。

ビルダーを自前で定義するのは大変なので、Haskellを逆にたどります。

関数で書く

セクションがないので普通の中置記法で書きます。

F#
let test x =
    let a = x + 1
    let b = x * 2
    (a, b)

do
    printfn "%A" (test 3)
    printfn "%A" (test 5)
実行結果
(4, 6)
(6, 10)

ラムダ式で書く

引数を関数に適用していることを明示するため、セクションの代用としてラムダ式で書きます。

F#
let test x =
    let a = (fun x -> x + 1) x
    let b = (fun x -> x * 2) x
    (a, b)

do
    printfn "%A" (test 3)
    printfn "%A" (test 5)
実行結果
(4, 6)
(6, 10)

強烈に無駄なことをしているように見えますが、次につながります。

自前定義

>>=retを自前定義してHaskellに似せます。

F#
let (>>=) m f = fun x -> f (m x) x
let ret   x   = fun _ -> x

let test =
    (fun x -> x + 1) >>= fun a ->
    (fun x -> x * 2) >>= fun b ->
    ret (a, b)

do
    printfn "%A" (test 3)
    printfn "%A" (test 5)
実行結果
(4, 6)
(6, 10)

先ほどの例と比較してa(fun x -> x + 1)の位置が逆転していることに注目してください。

bind

>>=bindという関数にして前置で呼びます。

F#
let bind m f = fun x -> f (m x) x
let ret  x   = fun _ -> x

let test =
    bind (fun x -> x + 1) (fun a ->
    bind (fun x -> x * 2) (fun b ->
    ret (a, b)))

do
    printfn "%A" (test 3)
    printfn "%A" (test 5)
実行結果
(4, 6)
(6, 10)

ビルダー

ビルダーを定義して直接使用します。

F#
type FuncBuilder() =
    member __.Bind(m, f) = fun x -> f (m x) x
    member __.Return(x)  = fun _ -> x
let func = FuncBuilder()

let test =
    func.Bind((fun x -> x + 1), fun a ->
    func.Bind((fun x -> x * 2), fun b ->
    func.Return(a, b)))

do
    printfn "%A" (test 3)
    printfn "%A" (test 5)
実行結果
(4, 6)
(6, 10)

コンピュテーション式

コンピュテーション式で使います。

F#
type FuncBuilder() =
    member __.Bind(m, f) = fun x -> f (m x) x
    member __.Return(x)  = fun _ -> x
let func = FuncBuilder()

let test = func {
    let! a = fun x -> x + 1
    let! b = fun x -> x * 2
    return (a, b) }

do
    printfn "%A" (test 3)
    printfn "%A" (test 5)
実行結果
(4, 6)
(6, 10)

afun x -> x + 1の位置が逆転していることに注目してください。

まとめ

HaskellとF#で、各種構文を比較します。

Haskell F#
1.通常関数

test x =
    let a = (+ 1) x in
    let b = (* 2) x in
    (a, b)

let test x =
    let a = (fun x -> x + 1) x
    let b = (fun x -> x * 2) x
    (a, b)
2.糖衣構文

test = do
    a <- (+ 1)
    b <- (* 2)
    return (a, b)

let test = func {
    let! a = fun x -> x + 1
    let! b = fun x -> x * 2
    return (a, b) }
3.脱糖衣

test =
    (+ 1) >>= \a ->
    (* 2) >>= \b ->
    return (a, b)

let test =
    func.Bind((fun x -> x + 1), fun a ->
    func.Bind((fun x -> x * 2), fun b ->
    func.Return(a, b)))

特徴を押さえておくと、構文変換がイメージしやすくなると思われます。

  • 1と2は構文的に似ている(特にF#)。2は引数を明示していない(ポイントフリースタイル)。
  • 2と3は左辺 a と右辺 (+ 1) が逆になっている。3は変数束縛が引数によって実現されている。
  • Haskellの方が構文がシンプル。F#の方が多機能。

doよりもコンピュテーション式の方が表現力は高いのですが、一度に覚えるのは困難です。BindReturnだけでもかなりのことができるので、まずはそこを出発点にすると良いのではないでしょうか。(それはつまりモナドの勉強になってしまうかもしれませんが)

21
21
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
21
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?