2025年8月25日月曜日

Haskell入門P368 ルーティング、本体

HaskellでSpockを使ったルーティングは、慣れるまで大変そうですが、無駄のない構成という感じがします。

authHook:ログイン認証
・ctx <- getContextについて:ユーザーからのリクエスト関連情報が、ある時点で箱にはいっていて、そこから、とりだしたのがctxへいく、というイメージ。
・mUser <- fmap wrsesUser readSessionについて:オブジェクトにフィールド名を関数として適用すると、該当フィールドの要素を返す仕組みになっている(P96).
readSession :: WRAction (SessionVal UserSession):現在のリクエストに紐づく セッション情報 を取得する関数 data UserSession = UserSession { wrsesUser :: Maybe User....から、取り出しmUserに

spockApp:ルーティング
・外側のprehookは認証なし、内側はauthHookで認証 うまく階層的に配置してわかりやすい構造
・get,postの別やURLと対応するActionが並べられている。
prehook (return emptyContext) $
 ├─ prehook authHook $ do
 │    ├─ get root
 │    └─ post "new_record"
 ├─ post "register"       ← authHook は適用されない
 └─ post "login"          ← authHook は適用されない

weightRecorderMiddleware:WAI ミドルウェアとして起動するための処理
runWeightRecorder cfgで定義されているように、runSpockで呼ばれる。
    ここで、spock spCfg spockApp にreturnがないが、do内のモナド計算なので不要

2025年8月24日日曜日

Haskell入門 P364 グラフ表示部の実装

ここも、分かりにくいところがあり、ChatGPTの助けを借りるが、ほぼ完ぺきにわかりやすく説明してくれるので、非常に助かる。
・weightGraphValueはMustacheへ渡すValueを返すのが役目。

・liftIO $ mapM flat wrsについて:flatでweightはそのまま使い、日付のみ"%m/%d"形式に変換し、 flat::WeightRecord-> IO (String,Double) よりmapM flat wrsはIO[(String,Double)]を返す。このへんは、mapMの働きを覚えてないと使えない、正直言って、前に学習(P174)したことを忘れており、復習して気づいた感じ。IOを含んでいるのでliftIOでWRActionモナドに持ち上げている。

・let wrss = groupBy ((==) `on` fst) . sortBy (compare `on` fst) $ flatWrsについて:
 sortBy (compare `on` fst) は、fstの日付でソートしている。groupBy ((==) `on` fst) は同じ日付のものをまとめるらしい。つまり[ [("08/20",60.5), ("08/20",61.0)],  [("08/21",60.8)]となる。wrssはリストのリストになります。(==)にそういう機能があることなど、非常に細かいところだけどChatGPTがしっかり解説してくれたのには、驚いた。
 本によれば、onは第二引数の関数を適用してから、第一引数の2項演算子を適用するという働きらしい。これと、上記の説明をみて、ようやく意味が理解できた。

・groupToValueについて:  :: [(String, Double)] -> Valueである。
   dt = head $ map fst gr→ グループの日付を取り出す(全部同じなので先頭でOK)。
   wt = avg $ map snd gr→ 複数記録がある場合は平均を取る。
 なるほど、うまくでてきています。これを使えばgroupToValue [("08/20",60.5),("08/20",61.0)]→ object ["day" ~> "08/20", "weight" ~> 60.75]となります。

2025年8月22日金曜日

Haskell入門 P354 各種操作の実装

whenはtrueならアクションを実行、startViewは実行したら次の行にはいかない。
runSqlite で (Connection -> IO m)を引数にとるため、insertUserの部分適用をうまく使っている。 (insertUser  :: IConnection c  => NewUser -> c -> IO Integerなので)
このへんがHaskellの簡潔さにつながっているのだと思う。

Haskell入門 P352 コントローラの開発

P352「type WRApp ctx= SpockCtxN ctx WRConnection WRSession WRState
type WRAciton = SpockActionCtx WRCibtext WRConnection WRSession WRState
WRAppとWRActionはWeightRecoder アプリケーションで利用するモナドです。
先ほどコンテキストにはWRContextを使うものとしてsrc/Web/Core.hsに定義しました。
 しかし、/のコンテキストは必ず()になるため、WRContextは適しません。そのため、ルーティングに用いるWRAppでは()にも対応できるよう、ctxを指定できるよう定義しています。一方で、ロジックを実際に記述するWRActionのコンテキストは、()を受け取らないようにできるため、型変数ではなくWRContext固定としています。このため、ルーティングの根本でコンテキストをWRContextに設定する必要があります。」

 この部分がよくわからなかった。どうやら、型変数ctxの例がWRContextという型などであるようだ。WRappとWRActionは厳密に関連しているのかと思ったけれど、必ずしもそういうことではなかったよう。WRAction が型変数をとらない理由は、型を固定して型安全にしたいからのよう。WRApp はルートや URL ごとに ctx が異なる可能性があるので型変数で柔軟にしている。そういうことらしい。WRappは設定データだから、いろんなデータを設定できる方がいい、でもWRActionは、安全性が求められるのである程度使える設定データは制限したい、そんな感じだろうか。

2025年8月21日木曜日

Haskell入門 P347 ユーザー登録のためのINSERT

 仕組みがわかりにくかった。

そこで、次のような質問をChatGPTにしてみた。
Q1:piNewUserは、どういうふうに使い方をするのですか?
Ans: let ins = HRR.typedInsert User.tableOfUser piNewUser がポイント
typedInsert :: Table User -> Pi User NewUser -> Insert NewUser
第1引数 User.tableOfUser → 「Userテーブルそのもの」
第2引数 piNewUser → 「NewUser から User のカラムに対応付ける射影」

let ins = HRR.typedInsert User.tableOfUser piNewUser
    u'  = u { nuPassword = dec hashed }                ins は INSERT 文のひな型

DHR.runInsert conn' ins u'     と実行すると、
u' :: NewUser の値(ユーザー名とハッシュ済パスワード)を
piNewUser に従って User テーブルの name, password カラムに対応付け
実際に DB へ INSERT

Q2: 要するに、手順を最初にくみたてて、最後に、runInsertで実行するのですね。
Ans:  はい、その理解でバッチリです!
  まとめると:
   piNewUser で「どう射影するか」を定義(テーブルと型の橋渡し)。
   typedInsert で「INSERT文のひな型」を作成。
   runInsert で「実際の値」を流し込んでDBに送信。

※ Haskellは、手続き型のように「値を受け取りながら逐次SQL発行」というやり方はあまりせず、まず「設計図を型安全に作る値を流し込む」という 宣言的な流れ
 型安全性が高い:SQL文やカラム名の間違いをコンパイル時に検出できる
 SQLインジェクション対策が組み込みである:値を直接文字列連結しないので安全
 トランザクション管理が明確:withTransaction の中で設計図に値を流し込むので、途中でエラーがあればロールバック可能
 コードの再利用性が高い:射影(piNewUser のような定義)を作っておくと、INSERTや  SELECTで同じマッピングを使い回せる
 などの利点があるらしい
Q3:makeRecordPersistableDefault ''NewUser はどういう意味?
Ans:NewUser を Relational-Record の「DBに入出力できる型」として登録する宣言、Template Haskell によって必要な型クラスインスタンスを自動生成している
「この型をSQLに流せるようにしますよ!」と最初に宣言しておく儀式 みたいなもの
Q4:Template Haskellとは?(P340にも説明はありますが)
Ans:「Haskellプログラム自身を生成する Haskellプログラム」を書ける
Q5:自動生成ですね。DjangoやRuby on Railsなどでも、DB初期化で似たような処理あったような気がしますね.
Ans:まさに DjangoRuby on Rails と同じ発想です。
Rails/Django = 実行時に自動生成・マッピング
Haskell(TH) = コンパイル時に自動生成・型安全にマッピング
Q6:user <- DHR.runQuery conn q name >>= DHR.listToUniqueのDHR.listToUniqueの働きは?
Ans:[] の場合 → Nothing を返す、[x] の場合 → Just x を返す、[x, y, ...] の場合 → エラーか Nothing を返す。「DB側にユニーク制約があるとは限らない」設計を想定。
Q7:checkHash user | validated = Just user | otherwise = Nothingの意味は?
Ans: if ... else if ... else if ... elseのイメージで、最初にtrueになったところが checkHash userの値。ガードの機能(p62で既習)

2025年8月18日月曜日

Haskell入門 P326 処理のまとめ

stack install で実行ファイルを作り、動作確認してみた。これも、一筋縄ではいかず、Windowsだと、文字エンコードなど配慮しないとうまくいかなかった。エラーメッセージ見たり、ChatGPTに聞いたりしながら、なんとか解決。
 
echo [{"age":"aa","name":"名前","telnumber":"abcdef"},"age":"bb","name":"abcd","telnumber":"jkl"}] > test.json
としてファイルをつくってから
hjq-exe "{\"name\":.[1].name,\"telnumber\":.[1].telnumber}" test.json
としたらうまくいった。ハイフンがあるとうまくいかず、カットする必要あった。なかなか、大変だった。


Hjq.hs
・note:MaybeをEitherに変換    decode:ByteStringをValueに変換
 first:Rightはそのまま、Leftに関数を適用


Haskell入門 P323 テストようやく終了

 本のままでは、動かず新しいバージョンに合わせた修正が必要だった。

よくわからないところを確認してみた。
Spec.hs
・applyFilterTestについて
   ^?  指定データを1部取り出したいとき使用(lens)
 nth 2 リストの3番目要素取り出し
   fmap Right (testData^?key "string-field") だと  fmap::(a->b)->f a->f bでみると
 (a->b)がRight  f aがMaybe Value   f bがJustつまりMaybe(Either e Value)
 これにRightが適用なるようだ

Parser.hs
・queryArrayの jqQueryParser `sepBy` schar ',' について
  sepByは中値だが、2引数関数で schar ','を区切りとして jqQueryParserでパース

Query.hs
・applyFilter (JqField fieldName n) obj@(Object _)
  = join $ noteNotFoundError (T.pack fieldName) (fmap (applyFilter n) (obj ^? key (fromText(T.pack fieldName))))  について
   obj@(Object _)はObjectとか確認し、その値をobjとして利用可能
 joinはMaybeをはずすため

・executeQuery (JqQueryObject o) v
   = fmap (Object . KM.fromList . fmap (\(k,v) -> (Key.fromText k, v)))
    . sequence ②
    . fmap sequence ①
    $ fmap (fmap $ flip executeQuery v) o  について        
    左辺の(JqQueryObject o) vは{"name":.name,"age":.age}::[(Text,JqQuery)]
    右辺の fmap $ flip executeQuery v のflipは引数を逆順にして、部分適用を意図した 通りにするために使う。(flip executeQuery v) q == executeQuery q v 
  fmap (fmap $ flip executeQuery v) は、内側のfmapで( , )への処理を、外側のfmapで上の関数使って[  , , ]への処理を行う。
  sequenceは①で Eitherを外に出していく [(either Text (Text,Value))]
        ②で Ehter Text [(Text,Value)]にする

   パーシングは、なかなか複雑