haskell-jp / questions #60

こんにちは。型クラスについて質問です。
Int64をラップしたMyNumberという型をつくりました。
この型について、以下のような操作を定義したいのですが、どうもうまくいきません。そもそもこのようなことが可能なのか、可能であればどうしたらよいのか、ぜひ教えてください。

やりたいこと:
- 許可:MyNumber 同士の 足し算、引き算
- 許可:MyNumber の整数倍
- 許可:MyNumber 同士の大小比較
- 不許可: MyNumber と整数の足し算、引き算
- 不許可:MyNumberと 整数の大小比較
- 不許可:MyNumber同士の掛け算。

不許可については、コンパイル時にエラーとなるのが理想です。

こんな感じで書き進めてみたものの、なにか根本的に違うような気がしています。
作戦として、MyNumberをNumやOrdのインスタンスにして、不許可の操作を実装しない、もしくは何らかの方法でコンパイルエラーにする、ということを考えました。

haskell
module Main where

import qualified  as GI (Int64)

newtype MyNumber = MyNumber { getNumber::GI.Int64} deriving(Eq, Show)
instance Num MyNumber where
    (+) (MyNumber x) (MyNumber y) = MyNumber $ x + y
    -- (*) _ _ = undefined -- not sure how to achieve "MyNumber 150 * 2"
    -- fromInteger x = undefined -- to avoid comparison against Num

instance Ord MyNumber where
    (<=) (MyNumber x) (MyNumber y) = x <= y

main :: IO ()
main = do
    putStrLn $ (++) "Allow MyNumber + MyNumber : " $ show $ MyNumber 100 + MyNumber 200 == MyNumber 300
    putStrLn $ (++) "Allow MyNumber > MyNumber : " $ show $ MyNumber 330 > MyNumber 100 -- not detected by compiler
    putStrLn $ (++) "Allow MyNumber * Integer :" $ show $ (MyNumber 150) * 2  -- show can I do this?
    putStrLn $ (++) "Don't allow MyNumber > Integger : " $ show $ (MyNumber 330) > 100 -- not detected by compiler
    putStrLn $ (++) "Don't allow MyNumber * MyNumber : " $ show $ (MyNumber 330) * (MyNumber 100) -- not detected by compiler?
1. 許可:MyNumber 同士の 足し算、引き算
2. 許可:MyNumber の整数倍
3. 許可:MyNumber 同士の大小比較
4. 不許可: MyNumber と整数の足し算、引き算
5. 不許可:MyNumberと 整数の大小比較
6. 不許可:MyNumber同士の掛け算。

便宜上ナンバリング。
Num a 型クラスは型 a 同士の足し算 (+) と掛け算 (*) を両方定義する必要があるので、その方針( Num のインスタンスにする)で 1 と 6 を実現することはできないです。
まぁ方法としては
(1) 足し算だけの型クラス Add a なんかを独自で定義する
(2) 足し算を Monoid a 型クラスかなんかで定義する(演算子が (<>) に変わってしまうが)

があるかなぁ。。。
演算子の見かけにこだわりたいのであれば、カスタムPreludeを作って(+)や(-)を上書きすればいいでしょうね。
(やり過ぎ感は否めませんが…)
https://haskell.e-bigmoon.com/posts/2018/05-23-extended-prelude.html
http://hackage.haskell.org/package/vector-space 演算子は変わりますがvector-space を使ってみてはどうでしょうか?まさに加算とスカラー倍のためのインターフェイスです
Numに全部ぶちこんだのはHaskellの初期設計過ち四天王に入りそうで入らなさそう
Numeric Prelude ってどうなんですか?
Taichi Ishikawa
@Taichi Ishikawa has joined the channel
@ has joined the channel
Haskellで「送られてきたメールアドレスの少なくともドメインがメールサーバとして成立しているかどうか(DNSでMXもしくはAもしくはAAAAを持つか)」どうか調べる必要があって,汎用性があるからOSSにしようかと思ってるのですが既出なら教えてください
後DNS周りで楽に取れるやつが欲しい…パフォーマンス気にしなければ http://hackage.haskell.org/package/resolv-0.1.1.2/docs/Network-DNS.html で良いんですが…いや別に気にしなくても良いのか
いやパフォーマンスの問題ではなく使い勝手の問題ですねこれは
which is why we recommend using try rather than catch for ordinary exception recovery.
https://www.stackage.org/haddock/lts-13.23/base-4.12.0.0/Control-Exception.html#g:3
とあるので try の方を使うかという気持ちなんですが、複数の型の例外を捕捉したいとき何かイディオムあるんですかね?
つまり catch ならこう ↓ なるケース( catches を使ってもいい)
print $ (head []) `div` (0 :: Int)
  `catch` (\e -> putStrLn $ "arith " <> displayException (e :: ArithException))
  `catch` (\e -> putStrLn $ "some " <> displayException (e :: SomeException))

try でベタで書くとこうなる
do
  v <- try $ do
    v <- try $ print $ (head []) `div` (0 :: Int)
    case v of
      Right _ -> pure ()
      Left e -> putStrLn $ "arith " <> displayException (e :: ArithException)
  case v of
    Right _ -> pure ()
    Left e -> putStrLn $ "some " <> displayException (e :: SomeException)

https://wandbox.org/permlink/FSlRioZ1EPWcWXjf
dns: DNS library in Haskell - http://hackage.haskell.org/package/dns もありますが、使い勝手は似たようなものっぽいですね
口頭での議論のとおりですが、簡潔に書くなら諦めて catches するしかなさそうな気がします。

which is why we recommend using try rather than catch for ordinary exception recovery.

の部分ですが、例外処理が十分に軽い処理(非同期例外がmaskされてもすぐに終わる)であれば問題ないでしょうし。
1つのアプリーケーション内なら、直和型を宣言して適宜ラップするとか
実際に try 関数が catch を使ってやっているように、直和型でラップするだけ、というのが正解なんでしょうね
参考: https://hackage.haskell.org/package/base-4.12.0.0/docs/src/Control.Exception.Base.html#try
こんなのはどうでしょう?
https://gist.github.com/mizunashi-mana/c2d6b87206e9c1f43046f84b135fd445

やってることは大体 catches と同じです
なるほでぃうす
@atled has joined the channel
みなさんありがとうございました。vector-spaceは良さげですね!直接は大小比較をサポートしていない(ベクトルだから当たり前ですが)ので、その部分を手当してあげる必要はありそうです。
Haskellでデータをファイルに書き出し,読み込みできるようなライブラリはなにがあるでしょうか.
思い付くのだとAesonを使うしかないのかなと考えています.
より効率がいいものがあるような気がするのですが,見付けられませんでした.
シリアライズライブラリという認識でよろしいでしょうか.
こちらの方で有志の方々がシリアライズのパフォーマンス比較をなさっておられるので参考になると思います.
https://scrapbox.io/haskell-shoen/シリアライズ
ありがとうございます!
たすかりました!
自分で簡単なパーサーを作ろうと思っています。失敗した時の位置情報を出すくらいの機能はつけたいのですが、その実装について述べられているページや基本的なアイディアなとご存知でしたら教えていただければ幸いです(Parsecあたりのソースコードを読め、と言われればそうなんですが……)
読んでたらなんとなくわかってきたかも……もうちょい自分で頑張ってみます
「パーサー」ではなく「パーサーコンビネーターライブラリー」ですよね?
parsecもmegaparsecも読んでませんが、ちょっと思い浮かぶ感じだと、Stateとして現在の位置情報を表すカウンターを持っておいて、1文字進めるごとに +1 する感じですかね。
Text.Parser.Pos に SourcePos というのがあって、 getPosition :: Monad m => ParsecT s u m SourcePos でいつでも取得できます。 igrep さんがいうようなメンテナンスは、 setPosition :: Monad m => SourcePos -> ParsecT s u m () でやることができるのですが、 Text.Parser.Char に含まれてるようなコンビネータはもともと一文字単位でいい感じに setPosition してるのだと思うので、自分で書く必要はほとんどないのでは、と思います
あとは、 AST に SourcePos の情報を混ぜ込んで扱うのが吉だと思います。 https://qiita.com/Mizunashi_Mana/items/115855bf2af9b9970198 個々の例だと AST に a の穴をあけておいてそこに SourcePos をいれていますね
ちなみにガチの AST の例として LLVM の C++ フロントエンドである Clang の AST をみてると、 AST に対応するソースコード位置の情報を「その AST に対応する部分の先頭位置・終了位置・代表的な位置(エラーメッセージにつけるのにもっともよさげな位置)」と三種類ぐらい持っていた気がします。そこまでやらないとあの親切なエラーメッセージは出せないんだなあ、っていう感じでした
あ、ひょっとしてパーサコンビネータライブラリを作る、っていうもう一個メタな話をしていらっしゃるんでしたら、釈迦に説法でしたか…^^;;
もし、パーサコンビネータに頼らずにパーサを書くとなると、結局「1文字パースするたびにアップデートする基礎の層」を作ってそれをメンテナンスできる State をもっておいて、適宜 get しておくようにしつつ、結局は AST に混ぜ込むようにするという話になって、それは AST を設計する際に考えましょうっていう話になると思います。
@acoman has joined the channel
haskell初心者です
テストに関して質問させてください

自分で定義したリスト型を返す関数のテストをhspecを用いて実行しようとしています。

-- src/Lexer.hs
module Lexer (TokenType, Token, lexer) where
data TokenType = TK_NUM | TK_OP | TK_EOF deriving (Show)
data Token = Token {
    tokenType :: TokenType,
    valueString :: String
} deriving (Show)

-- 中略

lexer :: String -> [Token]

-- 中略



このlexer関数に“3+3”を入力に実行した結果をprintすると

[Token {tokenType = TK_NUM, valueString = "3"},Token {tokenType = TK_OP, valueString = "+"},Token {tokenType = TK_NUM, valueString = "3"}]


と出力されます。
これが正しい結果であることのテストを書きたいです。

-- tests/LexerSpec.hs
module LexerSpec (spec) where

import Test.Hspec
import Lexer

spec :: Spec
spec = do
  describe "Lexer" $ do
    it "number and `+` and `-`" $ do
        lexer "3" `shouldBe` [Token {tokenType = TK_NUM, valueString = "3"}] -- ←ここ


stack testを実行すると、以下のようなエラーが出ます
 Not in scope: data constructor 'Token'
 Not in scope: data constructor 'tokenType'
 Not in scope: data constructor 'valueString'


理解が曖昧な点がたくさんあり、どこに問題があるのかわかっていない状態です。(module、テスト、などなど)

考えられる問題点
- そもそもhpecを用いるべきでない
- testファイルでのimportがうまくいっていない
- exportがうまくいっていない
- テストの書き方がまずい

よろしくおねがいします:man-bowing:
パッと見た感じ
• exportがうまくいっていない
が原因だと思います.

ちょっと分かりにくいのですが,
data A = A { valueA :: Int }

というプログラムは, 2 つのものを作ります.それは,
A という名前の型 (この名前は左側から類推されます)
A という名前の値コンストラクタ (data constructor) (この名前は右側から類推されます)
です.そして, export の際それぞれ export するかが選べます.
今回問題になっているのは,
module Lexer (TokenType, Token, lexer) where

の部分で,データ型の export は
module M
  ( 型名( 値コンストラクタ1, 値コンストラクタ2, ..., フィールド名1, ...)
  ) where

という風に書きます.この場合, 型名とコンストラクタ,フィールドがそれぞれ export され外部モジュールから使えます (書かなければ使えません) .なお, () の部分は省略できて,それが今回使われてる書き方になります.

なので, Token という型名は export されてるものの, 値コンストラクタ Token やフィールド tokenType / valueString は export されていないため上記のエラーが起きているのだと思います.

データ型の全てを export したい場合は 型名(..) という記法も用意されています.なので,それを使って
module Lexer (TokenType, Token(..), lexer) where

と修正するか,または
module Lexer (TokenType, Token(Token, tokenType, valueString), lexer) where

と修正するのがいい気がします.
丁寧な回答有り難うございます!!!
Token(..)でexportする方法があるんですね!
少し進むことが出来ました
しかし、また別のエラーが出てしまいました
(①~④という番号は僕が振っています)
   ① • No instance for (Eq Token) arising from a use of 'shouldBe'
   ②• In a stmt of a 'do' block:
        lexer "3"
          `shouldBe` [Token {tokenType = TK_NUM, valueString = "3"}]
      ③In the second argument of '($)', namely
        'do lexer "3"
              `shouldBe` [Token {tokenType = TK_NUM, valueString = "3"}]'
      ④In a stmt of a 'do' block:
        it "number and `+` and `-`"
          $ do lexer "3"
                 `shouldBe` [Token {tokenType = TK_NUM, valueString = "3"}]


テストコード側の、shuoldBeの後の部分での[Token]の書き方が合ってる気がしないのですが、どうしたら良いのでしょうか..
-- tests/LexerSpec.hs
module LexerSpec (spec) where

import Test.Hspec
import Lexer

spec :: Spec
spec = do
  describe "Lexer" $ do
    it "number and `+` and `-`" $ do
        lexer "3" `shouldBe` [Token {tokenType = TK_NUM, valueString = "3"}] -- ←ここ


そもそも、僕はHaskellのエラーの読み方がわかっていないようです。
エラーとして表示されている①~④はどれを参考にすればよいのでしょうか。
①はshoulBe関数にはEq Token型クラスのインスタンスではない、という意味でしょうか(だとしても解決方法がわからない)
②~④は同じようなことを言っているようですが、意味がわかりません
①~④は別々のエラーではなくてそれで一続きのメッセージですね。①のエラーが起きた場所が②の部分であり、その②の部分は(より大きな範囲で見ると)③であり、それは(さらに大きな範囲で見ると)④である、と言っているだけなので、①と②ぐらいを見て場所がわかるのならそれ以降を見る必要はないと思います。
で、①のエラーは「shouldBe を使うことに起因して、 Token 型についての Eq クラスのインスタンス宣言が無いことが問題」だと言っているので、 instance Eq Token where … を必要とする、ということになります。あるいは data Token … の後ろに deriving Eq を書いて自動的に Eq のインスタンス宣言を導出するのでもよいと思います
すでに deriving Show が書いてあるので deriving (Show, Eq) のようにカッコにくくってカンマで列挙して書くことになりますね
全般的なお話をすると、GHCが出す型エラーは、一つのエラーが概ね以下のような形式で、必ず複数の行に渡ります。

Path/To/Module.hs:123:45: error:
    ・<エラーメッセージの内容1>
    ・<エラーメッセージの内容2 以下、詳しい診断情報がいくつか続く>
    ・<エラーメッセージの内容3>
      <エラーメッセージの内容3。一つの診断情報が複数行に及ぶ場合「・」以降をインデントする
    ・...


いずれにしても、一つの型エラーは「エラーが発生した場所( :point_up: で言うところの Path/To/Module.hs:123:45: error: )」で始まる行からインデントしている範囲が一つの型エラーです。
型エラーもオフサイドルールに従っていると思っていただけると分かりやすいかと。
「必ず」複数の行に渡ります、はさすがに言いすぎではないでしょうか。シンプルな構文エラーだと
Main.hs:1:13: error: parse error on input 'whree'
てな感じですし…(where をタイプミスした例)
あ、「型エラー」は、と書かれていました、失礼しました。型エラーは確かに必ず複数行になりますね
Hiromi ISHII / mr_konn
@Hiromi ISHII / mr_konn has joined the channel
@koyama41
あああ、できました!!!ありがとうございます…

@koyama41 @igrep
なるほど!!ありがとうございます
エラー文の読み方、参考になりました:man-bowing:
Haskellの問題ではない可能性も高いのですが,
SQLite3(SQLite.Simple)で複数回の書き込み後にErrorCan’tOpenがでてしまいます.
おそらく100回程度の書き込み(成功) -> しばらくErrorCan’tOpen -> 時間を置いて復活
という謎挙動でまいっています...
拙作ですがコメント以外そのままのコードを載せます.
```
logger :: String -> Middleware
logger db app req sendResponse = do
t0 <- getCurrentTime
app req $ \rsp -> do
let isRaw =
case rsp of
ResponseRaw{} -> True
_ -> False
t1 <- getCurrentTime
let logRow = LogField
{ getDatetime = t0
, getRemoteHost = remoteHost req
, getRequestMethod = requestMethod req
, getStatusCode = if isRaw then Nothing else Just $ H.statusCode . responseStatus $ rsp
, getPath = rawPathInfo req
, getQuery = rawQueryString req
, getRequestHeaderHost = requestHeaderHost req
, getRequestHeaderReferer = requestHeaderReferer req
, getRequestHeaderUserAgent = requestHeaderUserAgent req
, getResponseTime = diffUTCTime t1 t0
}
void $ forkIO $ do
insertLogRetry db logRow

sendResponse rsp
where
insertLogRetry :: String -> LogField -> IO()
insertLogRetry db' logRow = insertLog db' logRow
`catch` (\x -> case sqlError x of
ErrorBusy -> insertLogRetry' db' logRow
ErrorLocked -> insertLogRetry' db' logRow
e -> putStrLn $ "unknown error: " ++ show e ++ "\n" ++ show logRow) -- ErrorCan'tOpenが発生する
insertLogRetry' :: String -> LogField -> IO()
insertLogRetry' db' lr = (retry 100 $ do
gen <- Rand.newStdGen
let (t, _) = Rand.randomR (1, 10) gen
threadDelay t
insertLog db' lr)
`catch` (\x -> case sqlError x of
e -> putStrLn $ show e ++ "error occurs after retry 100 times")
insertLog :: String -> LogField -> IO()
insertLog db' lf = do
conn <- open db'
execute conn "INSERT INTO log VALUES (null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" lf
close conn
```
もしどなたか心当たりあれば不確実な情報でもお願いします…_(:3 」∠ )_
さきほどレポジトリの設定をPublicにしたので,こちらから見れます...
https://gitlab.com/sarashino/prida
多分 https://gitlab.com/sarashino/prida/blob/master/src/Web/Prida/Logger.hs#L127-131insert する度にコネクションを開いてしまっているのが原因なんじゃないかと思います。
WAIのmiddlewareの設定からして、レスポンスを書く度にforkしてログに書き込んでいるわけですよね?
となると、リクエストを受け取る度に書き込むスレッドをforkしてDBをオープンしていることになるので、結果として同時にオープンできる量の限界に達してしまっているのではないかと思います。
通常、Webアプリケーションが使うDBへのコネクションは、コネクションプールを使って、一定量のコネクションを常時開けておくのが定石です。
Haskellでそれをやる場合、 http://hackage.haskell.org/package/resource-pool にコネクションプールを任せ、 Pool オブジェクトを ReaderT で引き回すか、愚直に必要な関数の引数に渡して回ることになるかと思います。

:thinking_face: もう少しWebアプリに慣れた人へ ほかにいいパッケージありますかね... 試したことないのでちょっと自信ない
しかし sqlite3 なのでコネクションといっても単にローカルファイルを open してるだけな気がするのですよね…100個ぐらいで限界がくるものなのだろうか?という気がします
書き込みの際はDB全体でロックがかかっている、と読めますね。。。 :cold_sweat:
https://stackoverflow.com/questions/9017762/what-is-the-maximum-connections-for-sqlite3-database