purescript-halogen-realworldのcode reading
アーキテクチャを学びたい

これが目的
隅から隅まで理解したいわけではない
halogenをちゃんと理解しているわけではないので、詰まったらtutorialに戻る、という感じで読んでいく
色々解説してるっぽい記事あった
Use Cases
e.g. userをfollowする
Data
e.g. UsersやArticlesなどのEntity
Tranformations
stateから別のstateに変換する関数
小さいaction
dataを3種類に分類する
Entities
永続的なidを持つデータ
同じidを持っているなら同じものである
e.g. User, Article
Values
同じ値なら同じものである
e.g. Email, Username, List
Lifecycles
状態を表すデータ
EntityやValueを含むデータ型
具体例を見たい

設計方針
データを設計するための原則
サポートする必要のあるビジネス的なプロセスに依ってデータ型が決まり、
データ型によって、これらのプロセスを関数として実装する方法が決まる
use caseを理解していないと適切なデータ型は作成できない
適切なデータ型がないと関数が混乱する
ここの文章めちゃくちゃ良いな

newtype CustomerId = CustomerId Natural
newtypeでIdを作ることで、「idでの算術」などを不可能にする
必ずしも完全なデータ型を作る必要はない
module内に閉じ込めて、適切に関数を提供すれば安全性は担保できる
EntityとValue
ProcessとLifecycle
アーキテクチャについて
純粋な関数型プログラミングの基本原則は、エフェクトとデータを可能な限り分離すること
コードの大部分は副作用のない関数とデータとして記述されており、アプリケーションの境界でのみEffectが現れる
>型クラスは、取得または処理するために使用するメカニズムではなく、操作する情報を記述するように設計する必要があります
わかるようでわからない
普通に型クラス定義したらそうならん?
具体的な接続先を意識しないようなinterfaceを定義しろということか
アクセス先がRESTでもGraphQLでもlocalStrageでも使えるようなinterfaceを型クラス内に定義する
Capability内で HalogenM st act slots msg m
にだけinstance作っているのはなぜ?
これなしで書いてみたらわかる

Componentは、HalogenMを返り値にするので、その中でcapabilityのmethodを使おうと思うと、毎回liftしないといけない
単純にそれがめんどいので、 HalogenM
に対してinstanceを予め定義している
あー、こういうのもLayer 3的ではあるのか
purs(hs)newtype LogMessage = LogMessage String
logMessage
:: forall m
. Monad m
=> m DateTime -- | How should we fetch the current time?
-> (LogMessage -> m Unit) -- | How should we write this message?
-> LogType -- | What kind of log is this?
-> String -- | What is the input string?
-> m Unit
logMessage getTime writeLog logType msg = do
t <- fmtDateTime <$> getTime
let msg' = LogMessage $ case logType of
Debug -> "[DEBUG: " <> t <> "]\n" <> msg
Info -> "[INFO: " <> t <> "]\n" <> msg
Error -> "[ERROR: " <> t <> "]\n" <> msg
writeLog msg'
これは、Effectにも依存していない、純粋な関数型プログラミングと言える
これを使って、AppMのinstanceを定義する
purs(hs)instance LogMessages AppM where
logMessage log = logMessage log
ここで初めて、AffやEffectと関係を持つ
薄い命令形shell
AppMは通常のMonadに加えて以下のようなものもderivingしておく
purs(hs)derive newtype instance MonadEffect AppM
derive newtype instance MonadAff AppM
derive newtype instance MonadStore Action Store AppM
上2つはまあわかる
一番下は MonadAsk
を実装しているようなもの
Storeはhalogenが提供している別のlibrary
The only tricky part is ..
lのぶぶんで、Storeはtypeで定義するが、
これはinstanceを導出できない
そこで Type.Equality
を利用すればできる
理由は
ここを見よ、みたいにかいているが特に説明はない
>The beauty of these instances is that our logging capability is decoupled from its implementation in AppM
これは、capablity同士が独立しているから、1つの変更が他のcapablityのAppM実装に影響を与えない、という意味かな
#??>Testing our new logger capability
テストの書き方
Envは
このPRでStoreにrenameされている
Store内のcommentにReduxのstoreと似ている、と書いているが、微妙に語弊あると思う

global stateと言う意味では同じだけど、実行中に更新されること無いから
あー、でもmainでの注入時にActionやreduceという名前の関数を使ってるのか。
ならredux的と言っても良さそう
一発目だけの話なので、誤解は与えそうだけど
この辺の話はstoreのlibraryを見るのが良さそう
Componentsに関して
Reactと比べるとややモノリシックな構成になる
componentも純粋に保つ
capabilityへアクセスすることもできる
型で表現すればいい
できるだけ、状態を持つcomponentではなく、純粋なHTMLとして作るのが良い
もうちょい見えてきてから再読しよう


API系
Endpoint.purs
Request.purs
なぜ自分で RequestMethod
型を定義しているのかと言うと、 Data.HTTP.Method
とかのは汎用的にするために冗長になっているから
利用に過不足が無いように自分で定義する
Utils.purs
Component周りの
H.forkとかまだよくわかっていない
dir.
├── assets
│ ├── index.html
│ └── logo.png
├── dev
│ ├── index.html
│ └── logo.png
├── index-dev.js
├── index.js
├── LICENSE
├── package-lock.json
├── package.json
├── packages.dhall
├── README.md
├── shell.nix
├── spago.dhall
├── src
│ ├── Api
│ │ ├── Endpoint.purs
│ │ ├── Request.purs
│ │ └── Utils.purs
│ ├── AppM.purs -- Application Monad
│ ├── Capability -- Capability (後述)
│ ├── Component
│ │ ├── HigherOrder
│ │ │ └── Connect.purs
│ │ ├── HTML
│ │ │ ├── ArticleList.purs
│ │ │ ├── Footer.purs
│ │ │ ├── Header.purs
│ │ │ └── Utils.purs
│ │ ├── Part
│ │ │ ├── FavoriteButton.purs
│ │ │ └── FollowButton.purs
│ │ ├── RawHTML.js
│ │ ├── RawHTML.purs
│ │ ├── Router.purs
│ │ ├── TagInput.purs
│ │ └── Utils.purs
│ ├── Data -- ValueとEntityの定義(後述)
│ ├── Store.purs
│ ├── Foreign
│ │ ├── Marked.js
│ │ └── Marked.purs
│ ├── Form
│ │ ├── Field.purs
│ │ └── Validation.purs
│ ├── Main.purs
│ └── Page
│ ├── Editor.purs
│ ├── Home.purs
│ ├── Login.purs
│ ├── Profile.purs
│ ├── Register.purs
│ ├── Settings.purs
│ └── ViewArticle.purs
└── test
└── Main.purs
Capability
LogMessages.purs
Navigate.purs
Now.purs
Resource
Article.purs
Comment.purs
Tag.purs
User.purs
Data
User ??
user idで識別
Userを表すEntity
loginなど認証が必要なactionに使われる
Avatar
Profile
usernameで識別
Userに関する公開情報を表すEntity
記事やコメントを書いたUserとか、followの対象を表す
Article
slugで識別
記事のEntity
Followの状態はBoolでは表さない
いいね

purs(hs)data Relation
= Following
| NotFollowing
| You
自分のページということも型に含める
また、この仕様は元のrealworldの仕様と異なるため、 ProfileRep
型の外に定義する
ProfileRep
に Relation
を加えたものとして、 Author
型を定義する
Comment
identified by an id combined with the slug of a particular article. It also refers to an author.
Email
Log
PaginatedArray
PreciseDateTime
Route
Username
smat constructorの名前は
parse
なのか

mkUsername
ではないんだな