elmでランダムな値を合成してみよう！

elmは小さい言語仕様ながら関数型プログラミングのパワーを十分に引き出すことができるパワフルな言語です。そのうちの一つであるランダム値の合成を試してみましょう。今回のお題は、[rgba](https://developer.mozilla.org/ja/docs/Web/CSS/background-color)を自動生成するシチュエーションを考えてみます。

[デモ](https://suspicious-shaw-f02011.netlify.com)
[ソースコード](https://github.com/ababup1192/elm-rgba-random)

<img width="384" alt="スクリーンショット 2019-05-29 21.20.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48687/f657173d-74ee-9618-651e-69191fa6d467.png">

一応おさらいですが、RGBAの数値の範囲は以下のようになります。

```elm
[R]ed:   0 ~ 255
[G]reen: 0 ~ 255
[B]lue:  0 ~ 255
[A]lpha: 0 ~ 1.0 
```

# 値を使うシチュエーション(view関数)から考える

アプリケーション開発で手戻りを発生させないための大事な考え方としてデータや関数から開発する**ボトムアップな考え方よりも、なるべくユーザに近いトップダウンの形で考える**方が手戻りが無く最終的なゴールが想像しやすく効果的です。elmのArchitectureではview関数から考えましょう。以下のように、` { red, green, blue, alpha } `を取り出して*background*に指定する*rgba()*文字列を組み立てると良さそうです。

```elm
view : Model -> Html Msg
view { color } =
    let
        { red, green, blue, alpha } =
            color

        rgba =
            String.join ","
                [ String.fromInt red
                , String.fromInt green
                , String.fromInt blue
                , String.fromFloat alpha
                ]
    in
    div [ class "container" ]
        [ button [ onClick GenerateRGBA ] [ text "Generate Random" ]
        , div
            [ class "colorPanel", style "background" <| "rgba(" ++ rgba ++ ")" ]
            []
        ]
```

つまりModelにはレコードである`Color`がランダムな値として取得できると幸せそうですね？

```elm
type alias Color =
    { red : Int
    , green : Int
    , blue : Int
    , alpha : Float
    }


type alias Model =
    { color : Color
    }
```

# Colorを受け取るMsgを作る

それでは実際に、Colorを受け取るシチュエーションを考えてみましょう。非常にシンプルなupdate関数の分岐になるはずです。elmのランタイムがランダムな値を集めたColor(Record)を`GeneratedRGBA` Msgと共に渡してくるので、それをModelにセットしてあげるだけです。とっても簡単ですね？

```elm
-- 再掲
type alias Color =
    { red : Int
    , green : Int
    , blue : Int
    , alpha : Float
    }

type Msg
    = GeneratedRGBA Color


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        GeneratedRGBA color ->
            ( { model | color = color }, Cmd.none )
```

# Generatorを作る

一番始めに掲載した乱数の範囲をイメージしましょう。4つの乱数。その内訳はRGBが0から255の整数(*Int*)。Aは、0から1.0の浮動小数点数(*Float*)です。

```elm
[R]ed:   0 ~ 255
[G]reen: 0 ~ 255
[B]lue:  0 ~ 255
[A]lpha: 0 ~ 1.0 
```

それら一つ一つをGeneratorとして表し、mapN関数により合成を行います。今回はランダムな値が4つなので[Random.map4](https://package.elm-lang.org/packages/elm/random/latest/Random#map4)関数を利用します。


```elm
rgbaGenerator : Random.Generator Color
rgbaGenerator =
    Random.map4 Color
        (Random.int 0 255)
        (Random.int 0 255)
        (Random.int 0 255)
        (Random.float 0 1.0)

-- (\r g b a -> { red: r, green: g, blue: b, alpha: a } )
-- (\r g b a -> Color r g b a )
-- コレ自体がRecord(Color)を作る関数
-- Color 
```

# Cmdで副作用を起こす

欲しい形に加工したGeneratorは[Random.generate](https://package.elm-lang.org/packages/elm/random/latest/Random#generate)関数を利用することで、Cmdに変換することができます。

```elm
genRGBA : Cmd Msg
genRGBA =
    Random.generate GeneratedRGBA rgbaGenerator
```

あとはこのCmdをランダム値を発生したい箇所に散りばめるだけです。

```elm
init : () -> ( Model, Cmd Msg )
init _ =
    ( Model (Color 0 0 0 1.0), genRGBA )

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        GeneratedRGBA color ->
            ( { model | color = color }, Cmd.none )

        GenerateRGBA -> -- ボタン onClick 時
            ( model, genRGBA )
```

# ソースコード全体

```elm
module Main exposing (Model, Msg(..), init, main, update, view)

import Browser
import Browser.Navigation as Nav
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Random as Random



-- ---------------------------
-- MODEL
-- ---------------------------


type alias Color =
    { red : Int
    , green : Int
    , blue : Int
    , alpha : Float
    }


type alias Model =
    { color : Color
    }


init : () -> ( Model, Cmd Msg )
init _ =
    ( Model (Color 0 0 0 1.0), genRGBA )



-- ---------------------------
-- UPDATE
-- ---------------------------


rgbaGenerator : Random.Generator Color
rgbaGenerator =
    Random.map4 Color
        (Random.int 0 255)
        (Random.int 0 255)
        (Random.int 0 255)
        (Random.float 0 1.0)


genRGBA : Cmd Msg
genRGBA =
    Random.generate GeneratedRGBA rgbaGenerator


type Msg
    = GeneratedRGBA Color
    | GenerateRGBA


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        GeneratedRGBA color ->
            ( { model | color = color }, Cmd.none )

        GenerateRGBA ->
            ( model, genRGBA )



-- ---------------------------
-- VIEW
-- ---------------------------


view : Model -> Html Msg
view { color } =
    let
        { red, green, blue, alpha } =
            color

        rgba =
            String.join ","
                [ String.fromInt red
                , String.fromInt green
                , String.fromInt blue
                , String.fromFloat alpha
                ]
    in
    div [ class "container" ]
        [ button [ onClick GenerateRGBA ] [ text "Generate Random" ]
        , div
            [ class "colorPanel", style "background" <| "rgba(" ++ rgba ++ ")" ]
            []
        ]



-- ---------------------------
-- MAIN
-- ---------------------------


main : Program () Model Msg
main =
    Browser.document
        { init = init
        , update = update
        , view =
            \m ->
                { title = "RGBA"
                , body = [ view m ]
                }
        , subscriptions = \_ -> Sub.none
        }
```

# まとめ

ランダム値を合成する方法を紹介しました。生成された値を上手く使うためには、使われるシチュエーションを想定して、そこから逆算することでシンプルに思考することができました。是非elmだけでなく普段の開発にお役立てください。それでは素敵なelmライフを！