worksap-ate/aws-sdk — pull request #102 by amkkun (2013-01-05 08:53:53+00:00)
Title: QueryParamの改造

QueryParamを改造してみました．
すべてのAPIでテストはしていませんが，HSpecのテストは通りました．

### 良くなったこと
- 型からパラメータへの変換関数が容易に作れるようになりました．
- 再帰的なデータ型にしてあるので，すでにある型に対する変換関数があれば，それを再利用できます．
- 小さな変換関数を組み合わせて大きな変換を作れるようにしました．
- 文字列の中に"."が入らないようにできます(`"Example." <> toText n`のようになるのは負けな気が)
- これまではひとつの変換関数の中に同じ文字列が複数入っていることが多かったですが，その重複をなくすことができます．
- 見た目もシンプルになります．
- 保守性も高くなると思います．

### 良くないこと
- 演算子が少しわかりにくいかもしれないです．

型
---
```
data QueryParam
= Leaf Text Text
| Inner Text [QueryParam]
```
Leafは木構造の葉，Innerは木構造の内部ノードのことです．
型の内部はあまり意識しなくても変換が作れるようになっています．

演算子
------
基本的にはこれらの演算子を使うことで型からパラメータへの変換を記述できます．
`(=)`はパラメータの=，`(.)`はパラメータの.，`(?)`はMaybe，`(#)`は番号，`(|)`はQueryParam関連の演算子である，という意味です．

### (|=) :: Text -> Text -> QueryParam
変更前のValueParamと同じです．
```
"Value" |= "value"

->

Value = value
```

### (|.) :: Text -> [QueryParam] -> QueryParam
TextでそれぞれのQueryParamをつなぎます．
```
"Example" |. [ Key=key
, Value=value
]

->

Example . Key = key
|
. Value = value
```

### (|=?) :: Text -> Maybe Text -> QueryParam
`(|=)`のMaybe版
Nothingの場合は後述する`nothingParam`を返します．

### (|.?) :: Text -> Maybe [QueryParam] -> QueryParam
`(|.)`のMaybe版
Nothingの場合は後述する`nothingParam`を返します．

### (|.#=) :: Text -> [Text] -> QueryParam
変更前のArrayParamと同じです．
```
"Example" |.#= ["value1", "value2"]

->

Example . 1 = value1
|
. 2 = value2
```

### (|.#.) :: Text -> [[QueryParam]] -> QueryParam
変更前のStructArrayParamと似ています．
内側のQueryParamのリストを番号で結び，それをTextで結びます
```
"Example" |.#. [ [ Key = key1, Value = value1 ]
, [ Key = key2, Value = value2 ]
, [ Key = key3, Value = value3 ]
]

->

Example . 1 . Key = key1
| |
| . Value = value1
|
. 2 . Key = key2
| |
| . Value = value2
|
. 3 . Key = key3
|
. Value = value3

```

### (|.+) :: Text -> QueryParam -> QueryParam
TextをQueryParamの先頭に追加します．
```
"Example" |.+ (Value = value)

->

Example . Value = value
```

関数
----
ほとんどの場合は上の演算子のみで大丈夫だと思いますが，それでもダメな場合はこれらの関数を使います．

### filtersParam :: [Filter] -> QueryParam
変更前のFilterParamsと同じです．

### putNumberV :: [Text] -> [QueryParam]
put a number to each valueの略．
それぞれの値に番号をつけます．
```
putNumberV ["value1", "value2", "value3"]

->

[ 1 = value1
, 2 = value2
, 3 = value3
]
```

### putNumberP :: [[QueryParam]] -> [QueryParam]
put a number to each parametersの略．
それぞれのパラメータのリストに番号をつけます．
```
putNumberP [ [ Key = key1, Value = value1 ]
, [ Key = key2, Value = value2 ]
, [ Key = key3, Value = value3 ]
]

->

[ 1 . Key = key1
|
. Value = value1
, 2 . Key = key2
|
. Value = value2
, 3 . Key = key3
|
. Value = value3
]
```

### maybeParam :: Maybe QueryParam -> QueryParam
Justなら中身を，NothingならnothingParamを返します．

### nothingParam
何も入っていないパラメータです．
```
nothingParam

->

"" . []
```
ドット以下が空リストなので，最終的に文字列に変換される際にこのパラメータは無視されます．

エクスポートされない関数
------------------------

### partition :: QueryParam -> [QueryParam]
パラメータの木を一分木に分けます．
```
partition Example . 1 . Key = key1
| |
| . Value = value1
|
. 2 . Key = key2
| |
| . Value = value2
|
. 3 . Key = key3
|
. Value = value3

->

[ Example . 1 . Key = key1
, Example . 1 . Value = value1
, Example . 2 . Key = key2
, Example . 2 . Value = value2
, Example . 3 . Key = key3
, Example . 3 . Value = value3
]
```

### paramsToMap :: [QueryParam] -> Map ByteString ByteString
パラメータをすべて一分木にし，Mapにします．

具体例
------
例えばあるAPIが次のようなパラメータを受け付けるとします．
```
Name :: Text
Id.n :: Text
Description :: Maybe Text
Example.n.Value.m :: Text
Example.n.Number :: Maybe Int
Example.n.Item.m.Key :: Text
Example.n.Item.m.Value :: Text
```

作る関数はこのような感じになります．
```
exampleApi
:: (MonadResource m, MonadBaseControl IO m)
=> Text -- ^ Name
-> [Text] -- ^ Ids
-> Maybe Text -- ^ Description
-> [Example] -- ^ Examples
-> EC2 m ...
exampleApi name ids desc examples =
ec2Query "ExampleApi" params $ ...
where
params = ...
```

APIに渡すパラメータを作るために，まず対応する型を作ります．
```
data Example = Example
{ exampleValues :: [Text]
, exampleNumber :: Maybe Int
, exampleItems :: [ExampleItem]
}
deriving (Show, Read, Eq)

data ExampleItem = ExampleItem
{ exampleItemKey :: Text
, exampleItemValue :: Text
}
```

次に型からQueryParamへの変換関数を作りますが，
まず小さい型であるExampleItemからパラメータへの変換関数を作っていきます．
```
exampleItemParams :: ExampleItem -> [QueryParam]
exampleItemParams item =
[ "Key" |= exampleItemKey item
, "Value" |= exampleItemValue item
]
```

次にExample型の変換関数を作ります．
```
exampleParams :: Example -> [QueryParam]
exampleParams example =
[ "Value" |.#= exampleValues example
, "Number" |=? toText <$> exampleNumber example
, "Item" |.#. map exampleItemParams (exampleItems example)
]
```

最後にAPIに渡すパラメータを作ります
```
exampleApi
:: (MonadResource m, MonadBaseControl IO m)
=> Text -- ^ Name
-> [Text] -- ^ Ids
-> Maybe Text -- ^ Description
-> [Example] -- ^ Examples
-> EC2 m ...
exampleApi name ids desc examples =
ec2Query "ExampleApi" params $ ...
where
params =
[ "Name" |= name
, "Id" |.#= ids
, "Description" |=? desc
, "Example" |.#. map exampleParams examples
]
```

このように，小さい変換関数を組み合わせて大きい変換関数を簡単に作れるようになりました．
もっと複雑なものも以前のものより遥かに簡単に作ることができると思います．