generated at
purescript-routing-duplex
Unified parsing and printing for routes in PureScript
Simple bidirectional parser/printers for your routing data types.

README見ればだいたい分かる


purs(hs)
data Route = Root | Profile Username | Post Username PostId | Feed { search :: Maybe String, sorting :: Maybe Sort } route :: RouteDuplex' Route route = root $ G.sum { "Root" : G.noArgs -- / , "Profile": "user" / username -- e.g. /user/mrsekut , "Post" : "user" / username / "post" / postId -- e.g. /user/mrsekut/post/1 , "Feed" : "feed" ? { search : optional <<< string -- e.g. /feed?search=purescript&sorting=asc , sorting: optional <<< sort } }
実際は username の定義など20行ぐらい他に書いているがcoreとなる部分はこんな感じ
/ ? などの演算子をうまく使ってめちゃくちゃ良い感じに定義できるのが楽しい



with Halogen
haskell cake実装
realworldのminimal実装


Why?のところいまいち何を言っているかわからん
2つの問題と解決する
pathの文字列を解析して、PursのData型に変換する
これがparse
Pursデータ型を、path文字列として出力する
これがprint
bidirectionalってそういう意味かmrsekut

イメージ的にはAesonの、Jsonとの相互変換と同じ感じかなmrsekut

Routingを定義する
Recordで定義する

同義の書き換えがいくつかあって若干ややこしいがreadme見ればわかる
基本的にはもっとも洗練したやつで書くだろうからさほど問題にはならないだろう

purs(hs)
module Router where import Prelude import Data.Generic.Rep (class Generic) import Data.Show.Generic (genericShow) import Routing.Duplex (RouteDuplex', path, root, segment, string, parse, print) import Routing.Duplex.Generic as G data Route = Home | Profile String derive instance Generic Route _ instance Show Route where show = genericShow route :: RouteDuplex' Route route = root $ G.sum { "Home": G.noArgs -- "/" , "Profile": path "profile" (string segment) -- "/profile/jake-delhome" } a = parse route "/profile/jake-delhomme" -- Right (Profile "jake-delhomme") b = print route $ Profile "jake-delhomme"-- "/profile/jake-delhomme"
Recordのfield名の箇所( "Profile" のところ)が、Route型に対応する
見た目は文字列だが、ここもしっかり型安全になっている
気持ちとしては、parser combinatorと同じ
root は、pathの最初の / にmatchする
文字列をparseしても良い感じに型変換できる
/user/1 なら User Int 型だよ、的な
例を見ればほぼ説明がなくても理解できるmrsekut
recordにすることでtype-level演算子ではなくなっっているのだなmrsekut


/user/mrsekut/post/1/hoge/2 みたいに連鎖するときはどう書くの?
productをネストするの?

productは良い感じに書き換えられる
普通に書いた場合
purs(hs)
, "Post": G.product -- /user/mrsekut/post/1 (path "user" (string segment)) (path "post" (int segment))
こう書ける
purs(hs)
import Routing.Duplex.Generic.Syntax ((/)) .. , "Post": "user" / segment / "post" / int segment -- /user/mrsekut/post/1
/ が使える
string segment string は省略できる
path も省略できる

paramsも良い感じに書き換えられる
purs(hs)
route = root $ sum { ... , "Feed": path "feed" (record # _search := optional (param "search")) } where _search = Proxy :: Proxy "search"
最終的にこう書ける
purs(hs)
, "Feed": "feed" ? { search: optional <<< string }

paramsの取りうる型も型安全にできる
e.g. ?sort=asc ?sort=desc のいずれかしか許さん
ここは独自に相互変換する関数を用意しておく必要がある
purs(hs)
data Sort = Asc | Desc derive instance genericSort :: Generic Sort _ sortToString :: Sort -> String sortToString = case _ of Asc -> "asc" Desc -> "desc" sortFromString :: String -> Either String Sort sortFromString = case _ of "asc" -> Right Asc "desc" -> Right Desc val -> Left $ "Not a sort: " <> val




CRUD用のpathも作っておけば上のやつに対して同一ルールでpatuの定義ができる
例えば /user に対して使えば、 /user/edit ならupdateみたいな

Eventとの関連は、purescript-routingのやつを使うらしい
hashとpush Stateのやつ
ここの説明だけ雑すぎるmrsekut




purs(hs)
data RouteDuplex i o = RouteDuplex (i -> RoutePrinter) (RouteParser o) type RouteDuplex' a = RouteDuplex a a
型を見れば分かる通り、input→ RoutePrinter RouteParser →outputの
input, outputの部分を引数に取るが、
普通はこれは同じ型なので、エイリアスとして ' 付きの型が提供されている
基本的には ' の方を使う