読者です 読者をやめる 読者になる 読者になる

Yesod で1ページに複数個フォームがある場合は identifyForm を使う

Haskell

resolver lts-5.4 で確認。

問題

1ページに複数個フォームがある場合、runFormPost はそれぞれのフォームの区別をしてくれません。どういうことかというと、例えば下記のような2つのフォームを利用するとします。

data AFormData = AFormData Text
aForm :: Html -> MForm Handler (FormResult AFormData, Widget)
aForm = renderDivs $ AFormData <$> areq textField "A Text" Nothing

data BFormData = BFormData Text
bForm :: Html -> MForm Handler (FormResult BFormData, Widget)
bForm = renderDivs $ BFormData <$> areq textField "B Text" Nothing

この2つのフォームを1つのページに配置し、ブラウザーでアクセスしどちらか一方のフォームに “Hello” とでも入力し送信します。すると runFormPost aFormrunFormPost bForm も返り値の FormResult Xxx 型の部分が FormSuccess xxx となり、xxx の部分はそれぞれ AFormData "Hello"BFormData "Hello" となります。

これは困ります。

解決

identifyForm を使います。

identifyForm
  :: Monad m
  => Text -- ^ Form identification string.
  -> (Html -> MForm m (FormResult a, WidgetT (HandlerSite m) IO ()))
  -> (Html -> MForm m (FormResult a, WidgetT (HandlerSite m) IO ()))

第1引数にユニークな文字列を指定してください。

aForm = identifyForm "a-form" $ renderDivs $ AFormData <$> areq textField "A Text" Nothing

これで、対応するフォームに入力されたときだけ runFormPostFormSuccess を返すようになります。

参考

Issue yesod-web/yesod #649

Yesod と HDBC-mysql と haskell-relational-record で “Commands out of sync”

Haskell

Yesod と HDBC-mysqlhaskell-relational-record を一緒に使っているのだが、MySQL サーバーに「Commands out of sync; you can’t run this command now」って言われてぐぬぬってなって、最近デバッグをがんばっている。とりあえず途中経過をメモしておく。

そもそも MySQL プロトコルの呼び出し順とか知らないので苦戦していた。

この辺でエラーが出ている。結合をしなければエラーは出ない。

import Import hiding (on)
import Database.Relational.Query (on)
import qualified Model.Table.Account as Account
import qualified Model.Table.Tweet as Tweet
import qualified Model.Table.User as User

getTweetR :: AccountIdParam -> TweetIdParam -> Handler Html
getTweetR accountIdParam tweetIdParam = do
    user <- requireAuth
    p <- runRelational $ do
             accounts <- runQuery Account.selectAccount accountIdParam
             ts <- flip runQuery () $ relationalQuery $ relation $ do
                       t <- query Tweet.tweet
                       u <- query User.user
                       on $ t ! Tweet.userId' .=. u ! User.id'
                       wheres $ t ! Tweet.id' .=. value tweetIdParam
                       return $ (,) |$| t |*| u
             return (accounts, ts)
    case p of
        ([account@(Account _ _ _ _)], [(tweet@(Tweet _ _ _ _ _), tweetUser@(User _ _ _ _))]) -> do
            form <- generateFormPost commentForm
            defaultLayout $ do
                headerWidget $ Just user
                tweetWidget account user tweet form
        _ -> notFound

とりあえず HDBC-mysqlFFI で libmysqlclient を呼んでるので C のレベルでどの関数を呼んでいるのかログに出してみる。コードはこんな感じ。めっちゃ地道にログを挟んでいった。一部引用すると下記。

mysql_stmt_prepare :: Ptr MYSQL_STMT -> CString -> CInt -> IO CInt
mysql_stmt_prepare mysql a1 a2 = do
  s1 <- peekCString a1
  hPutStrLn stderr $ "mysql_stmt_prepare " ++ (show mysql) ++ " " ++ (show s1) ++ " " ++ (show a2)
  cr <- mysql_stmt_prepare_ mysql a1 a2
  hPutStrLn stderr $ "\t→ " ++ (show cr)
  return cr

そのログ出力が下記(別ページで開く)。同じ接続・同じステートメントに同じ色を塗った。

どうも mysql_stmt_execute した後に mysql_stmt_fetch せずに mysql_stmt_prepare するとダメっぽい。C で確認してみるとこのコードコメントアウトしてるところのありなしでエラーが出たり出なかったりするのでおそらく合ってる。

MYSQL_BIND result;
char display_name[100];
unsigned long display_name_length;
my_bool display_name_error;
memset(&result, 0, 1);
result.buffer = display_name;
result.buffer_length = sizeof(display_name) * sizeof(display_name[0]);
result.is_null = NULL;
result.length = &display_name_length;
result.error = &display_name_error;
{
    my_bool error = mysql_stmt_bind_result(stmt, &result);
    if (error) {
        fprintf(stderr, "mysql_stmt_bind_result\n");
        mysql_close(mysql);
        exit(1);
    }
}
// {
//     int fetch_result = mysql_stmt_fetch(stmt);
//     switch (fetch_result) {
//         case MYSQL_NO_DATA:
//             printf("mysql_stmt_fetch: MYSQL_NO_DATA\n");
//             break;
//         case MYSQL_DATA_TRUNCATED:
//             printf("mysql_stmt_fetch: MYSQL_DATA_TRUNCATED\n");
//             break;
//         default:
//             printf("mysql_stmt_fetch: %d\n", fetch_result);
//     }
//     printf("display_name: %s\n", display_name);
//     switch (result.buffer_type) {
//         case MYSQL_TYPE_BLOB:
//             printf("display_name type: MYSQL_TYPE_BLOB\n");
//         default:
//             printf("display_name type: %d\n", result.buffer_type);
//     }
//     printf("display_name_length: %ld\n", display_name_length);
//     printf("display_name_error: %d\n", display_name_error);
// }
{
    my_ulonglong rows = mysql_stmt_affected_rows(stmt);
    printf("mysql_stmt_affected_rows: %ld\n", (long)rows);
}
{
    MYSQL_STMT *stmt2 = mysql_stmt_init(mysql);
    if (stmt == NULL) {
        fprintf(stderr, "mysql_stmt_init: error\n");
        mysql_close(mysql);
        exit(1);
    }
    {
        const char * const stmt_str = "SELECT `email` FROM `user` WHERE `id` = ?";
        int error = mysql_stmt_prepare(stmt2, stmt_str, strlen(stmt_str));
        if (error) {
            fprintf(stderr, "mysql_stmt_prepare: error\n");
            fprintf(stderr, "error message: %s\n", mysql_stmt_error(stmt2));
            mysql_close(mysql);
            exit(1);
        }
    }
}

この FFI 呼び出しがどういう経緯で呼ばれてるかを探すのはこれから。

接続を使い回すようにしたのがダメなのかもしれない。

解決しました。(2017.03.17)

正格版の runQuery' があったのですね。

『遠回りして学ぶ Yesod 入門』誤字脱字等を修正した電子版を公開しました

先日の投稿で公開した正誤表の内容を反映した電子版を公開しました。

kakkun61.hatenablog.com

コミックマーケット 91 で購入した方は対面電書で、オンラインで購入した方は BOOTH で更新後のファイルを入手することができます。

『遠回りして学ぶ Yesod 入門 C91 版』正誤表

Haskell 同人誌

『遠回りして学ぶ Yesod 入門 C91 版』の正誤表です。

kakkun61.booth.pm

ページ 章節 補足
3 はじめに Yesod の初歩的な解説が書かれた本の上巻です。 Yesod の初歩的な解説が書かれた本の上巻です。
7 1 リンク切れの検出や リンク切れの検出
17 3.2 これは quasi quote(準引用)と呼ばれる これは quasi quotes(準引用)と呼ばれる 表記の統一
20 4.1 モリーに文字のデータが モリーに文字のデータが
23 5.1 GHC Users Guidehttp://wiki.haskell.org/GHC GHC Users Guide http://wiki.haskell.org/GHC 空白の追加
45 8.2 stack build yesod-bin stack install yesod-bin
47 8.4 テンプレートの / のページは図 8.1 です。 yesod-mysql テンプレートの / のページのスクリーンショットは図 8.1 です。 分かりにくかったのでより説明的に
50 8.6 yesod-mysql を例に、 yesod-mysql テンプレートを例に 表記の統一
53 9.2 Elm を使えるようにしたライブラリー等もあります。 (どうも Yesod で Elm を使うライブラリーは古く使えなさそうです。)
56 10.2 型制約の ~ は両辺の型が同値であるという制約を表します。 (この文は、次の「新出 API の型」についての解説です。紙面の修正をしたときに入れ違いになってしまいました。)

他に見付けた場合は、ここのコメント等でお知らせください。

Adobe Typekit のフォントファイルの場所(Windows)

Adobe

アプリがフォントをうまく認識しない場合にフォントファイルを直接インストールして対処したりできる。同一マシンならライセンス的にも大丈夫なはず。保証はしません。

環境

場所

%appdata%\Adobe\Adobe Photoshop CC 2017\CT Font Cache にメタファイルがある。↓ のような項目が並んでいる。

%BeginFont
Handler:DirectoryHandler
FontType:Type1
FontName:AdobeDevanagari-Bold
FamilyName:Adobe Devanagari
StyleName:Bold
FullName:Adobe Devanagari Bold
MenuName:Adobe Devanagari
StyleBits:2
FamilyNameNative:Adobe Devanagari
StyleNameNative:Bold
FullNameNative:Adobe Devanagari Bold
WritingScript:Devanagari
OutlineFileName:C:\Program Files\Adobe\Adobe Photoshop CC 2017\Required\typesupport\.f\\.1
DataFormat:sfntData
UsesStandardEncoding:yes
isCFF:yes
hasSVG:no
FileLength:335268
FileModTime:1480434360
WeightClass:700
WidthClass:5
AngleClass:0
DesignSize:110
NameArray:0,Mac,4,Adobe Devanagari Bold
NameArray:0,Win,1,Adobe Devanagari
%EndFont

FullName で目当てのフォントを見つけて、OutlineFileName のファイルに ttf 拡張子を付けると Windows がフォントファイルとして認識してくれるので、インストールするなどする。

%appdata%\Adobe\CoreSync\plugins\livetype\r にもフォントファイルがある模様。

参考

stackoverflow.com

Mac の情報とちょっと古い Windows の情報がある。

コミックマーケット 91 ふりかえり

コミックマーケットお疲れさまでしたー。

印刷分完売しました。ありがとうございますー。技術書典のときよりも早く売り切れたのはまさかという感じでした。技術書典が特別技術系が売れるのかと思っていたんですが、そうでもないんですね。もっと刷ればよかった。

スケジュール

8月

もうやばい。

冬コミの申し込みは夏コミ後すぐなんですねー(棒

郵送じゃなくて宅配だったので追加費用が。

10月

当籤したのにやる気出ず放置。

11月

やっと動きだす。

Bitbucket Pipelines が年内無料ということで自動で Slack に PDF がアップロードされるようにしました。

12月

事前に立てたスケジュールなど完全に無視されて、2週間ぐらい前までシコシコしてました。

いざ入稿と思ったら予約が要ることに気付く。締切の晩い印刷所にしようと思ってたんで、すでに追加費用のない印刷所はなく…… でも、お金で解決できて本当によかったですね……

即売会用什器はちょっといいやつに新調しました。

作成環境

基本的に前回と同じで、追加で CI 環境を調えました。

Bitbucket Pipelines を使って vvakame/review Docker イメージでビルドして Slack に作った #ci チャネルにアップロードするようにしました。

review-ext.rbpagebreakflushright が増えました。

module ReVIEW
    Compiler.defsingle :emptypage, 0
    Compiler.defsingle :pagebreak, 0
    Compiler.defblock :flushright, 0

    class LATEXBuilder
        def emptypage
            puts '\newpage\null'
        end

        def pagebreak
            puts '\newpage'
        end

        def flushright(lines)
            puts <<EOS
\\begin{flushright}
#{lines.join "\n"}
\\end{flushright}
EOS
        end
    end
end

ダウンロードカード、前回は Google Drive にアップロードしたファイルを時限で公開停止にしていたのですが、今回は対面電書のシステムを使ってみました。1つずつ別々のシリアルコードの入ったカードなので印刷が面倒なんですが、1人ずつ初回アクセスから何日間ダウンロード OK みたいなことができるようになりました。

対面電書

前回の後に存在を知った国際標準同人誌番号略して ISDN という ISBN をもじったサービスがあるので、今回はそれを利用して JAN コードらしきバーコードを付けました。普通書籍には2つバーコードがあるのですが、下側は値段の情報が含まれていて、販売方法や時期で値段の変わる同人誌には合わないなということで空白にしています。このバーコードがあるとグッとそれっぽくなりますねー。付近の文字は OCR-B という JIS 規格のフォントを使っています。オープンソースのものがこちらに。あと、ISDN を主宰なさってる方がわざわざ挨拶に来てくださいました。

ISDN

f:id:kakkun61:20161231025312p:plain

この恰好いい表紙(自画自賛)は10月に初めて買ったカメラ Sony α6300 で撮りました。東京タワー初めて近くで見ましたが、すっごい恰好いいですね。写真とか会社のビルから見て知った気になっていましたが、それは所詮知った気でしたね。あと、レンズは用法用量を守って摂取したいと思います。(すでに3本)

とはいえ恰好いい表紙はやっぱり人目を引きますね。「あ、あの人表紙に目が止まった」というのはちょこちょこ気付きました。

72ページあれば背に文字を入れられそうでした。

当日

今回は寝坊せずに、というかダウンロードカードがまだできてなかったのでコンビニプリントしながらの徹夜で向かいました。

8時過ぎぐらいにビッグサイトに着いてサークル入場口で改札して入るときは、お~サークル入場だーって感動しましたね。

前回↓のようなことがあったのでサークルカットを立てるようにしました。

Square リーダーを買ってクレジットカード決済をできるようにしました。1人だけクレジットカード決済しました。あっさりすぎてちゃんと決済できてるのか不安になるくらいでした。

イーゼル・値札立て・ご自由にどうぞのカード立てと全部新調しました。ストアエクスプレスという店舗什器を扱っている問屋がやってる小売店で全部購入しました。確かに店舗でこういうの使ってるわみたいなのが売ってておもしろい。

小銭入れも買ったのですが大きすぎたのでそれは結局使わず終いです。お札も入るのはいいんですが。あと、同人誌即売会だと50・10・5・1円玉は全然使わないので、100円と500円がもっと入るといいですね。CAD 勉強して 3D プリントするかな。

前回の『(上)』購入者には200円の割引をしていました。内容かぶっているのでね。

数字

前回通り全部書いちゃいます。

売上

項目 単価 部数 合計
紙版 1000 14 16000
紙版(Square) 967 1 1000
紙版(割引) 800 1 800
紙版(割引)(Square) 0 0
電子版 800 11 8800
電子版(Square) 0 0
電子版(割引) 600 1 600
電子版(割引)(Square) 0 0
合計 27 24567

結構みなさん紙版を選ぶんですよね。紙が残っている段階で電子版のみを選んだのは1名か2名かだったと思います。自分なら紙はかさばるから電子版のみを買うと思うんですが。なるほど。

支出

項目 金額
紙版 24105
コンビニプリント 100
参加費など 11200
合計 35305

申し込みセット取り寄せと印刷がどっちもギリギリで追加料金だったのでちょっと出費がかさんでますね。前回今回と完売だったので次回以降は30部か40部かぐらい刷って印刷単価下げますかね。

什器は次回以降も使えるからまぁ含めないでおこう。カメラは(ry

というわけで最終的に赤字になるかどうかはオンライン販売にかかってるので何卒何卒。BOOTH で販売開始しています。

kakkun61.booth.pm

今後

技術書典 2 や夏コミどうしますかね。今のところネタがないので夏コミ申し込みセットも購入してこなかったんですよね。とりあえず買ってから考えるかー。

コミックマーケット91 『遠回りして学ぶ Yesod 入門』

Haskell 同人誌

サークル「趣味はデバッグ……」の2冊目の頒布をコミックマーケット91にて行います!

『遠回りして学ぶ Yesod 入門』

f:id:kakkun61:20161223225806p:plain

前回、技術書典で頒布した『遠回りして学ぶ Yesod 入門(上)』の増補改訂版という形になります。

対象読者

対象読者は前回通り、下記ような方になります。

  • Haskell の入門書は読み終わったよ
  • Yesod のサンプルコード見たら知らない文法とかあって読めないよ

内容

内容は、下記となります。

  • ビルドツール Stack
  • 効率のよい文字列の扱い
  • Haskell の言語拡張
  • コンパイル時計算 Template Haskell
  • Web Application Interface とは
  • 簡単な Yesod の解説
  • ロガー用 Middleware を作る

Scotty の章以外は『上』はそのまま含まれています。Scotty はちょっと脇道にそれすぎたなぁと思って省きました。

詳しい内容は見本誌で確認ください。

今回は @syocy さんに1つの章を書いていただきました。

紙面版使用

  • 表紙フルカラー
  • 本文モノクロ
  • B5 判
  • 76ページ(表紙込み)

価格

価格は下記の通りとなります。

  • 現地販売(現金・クレジットカード)
    • 紙面+電子版
      • 1000円
    • 電子版
      • 800円
      • ダウンロード URI を渡します
  • オンライン販売(後日)
    • 電子版
      • 1000円

以前に『上』を購入くださった方には、奥付(表紙を除いて一番最後のページ)を紙でも電子でも構いませんので見せていただければ、現地販売で200円引きいたします。

クレジットカード決済は、Square 社が対応しているものに限ります。

スペース

スペースは「1日目(木曜日)西み-26a」です。

よろしくお願いします!


本記事は Yesod Advent Calendar 2016 の22日目の記事も兼ねています。すみません、宣伝記事でした。


追記 電子版(PDF)販売はこちら。

kakkun61.booth.pm