haskell-jp / questions #63

適宜その場所で log 出力したい派だったので
全部 Either にして最後に error ログだったら ExceptT でも良いと思います
ありがとうございます。authenticateメソッドの定義自体が無い状態ですので調べて実装してみます。
@Nori Hayashi has joined the channel
なさそうなので自作するか。
https://haskell.e-bigmoon.com/posts/2018/06-26-cont-param.html 演算子として使えるとさらに便利なのでぜひ :bikeshed:
演算子、センスが求められる…!
思い切ってPull request送ってerrorsパッケージの一部にしちゃうのはいかがでしょう :smirk:
なるほど?
でも僕、errors 使わないですよ。。。
どっちかというと rio に入れて欲しいw
以下のようにauthenticateを作ってみたのですが、Invalid username/password combinationの
エラーになって行き詰まっています。
authenticate creds = liftHandler $ runDB $ do
  maybeClientAuth <- getBy $ UniqueClientAuth $ credsIdent creds
    case maybeClientAuth of
      Just (Entity clientAuthId _) -> return $ Authenticated clientAuthId
      Nothing -> return $ UserError InvalidUsernamePass

ドキュメントのauthenticateの説明が2行だけなのでどのように書くと正しく認証されるのかが分からず、困っています。
Perform authentication based on the given credentials.
Default implementation is in terms of getAuthId
古いバージョンでは以下のように書くサンプルを見かけたのですが、
getAuthId creds = getAuthIdHashDB AuthR (Just . UniqueUser) creds
getAuthIdは廃止予定のようですし、getAuthIdHashDBは既に無いようです。
どのようにauthenticateを実装するときちんと認証されるのでしょうか?
ContTから脱出する部分は独立して実装できるので、こんな感じにするといいのではと思いました
{-# LANGUAGE TypeFamilies #-}
import Control.Monad.Trans.Cont

class Fallible f where
  type Failure f :: *
  tryFallible :: f a -> Either (Failure f) a

instance Fallible Maybe where
  type Failure Maybe = ()
  tryFallible = maybe (Left ()) Right

instance Fallible (Either e) where
  type Failure (Either e) = e
  tryFallible = id

(??=) :: (Applicative f, Fallible t) => t a -> (Failure t -> f a) -> f a
t ??= k = either k pure $ tryFallible t
{-# INLINE (??=) #-}
infixl 1 ??=

(???) :: (Applicative f, Fallible t) => t a -> f a -> f a
t ??? k = t ??= const k
{-# INLINE (???) #-}
infixl 1 ???

(!?=) :: (Monad m, Fallible t) => m (t a) -> (Failure t -> m a) -> m a
t !?= k = t >>= (??=k)
{-# INLINE (!?=) #-}
infixl 1 !?=

(!??) :: (Monad m, Fallible t) => m (t a) -> m a -> m a
t !?? k = t >>= (???k)
{-# INLINE (!??) #-}
infixl 1 !??

exit :: m r -> ContT r m a
exit = ContT . const
{-# INLINE exit #-}
sqlite + hashDB の最小のサンプルが手元にあったので、参考になれば・・・。
ひょえぇ抽象度が一気に上がったw
今回のユースケースで実際に使うのは !?? だけっすね
-- さっきの疑似コードがこうなる
main :: IO ()
main = evalConstT $ do
  target <- List.lookup isTarget <$> Http.get "" !?? exit (pure ())
  hoge <- List.lookup (isHoge target) <$> Http.get "" !?? exit (pure ())
  lift $  (show hoge)
ありがとうございます。参考にさせて頂きます。

見た所、`authenticate`の実装はほぼ同じ内容なので原因は別の所にありそうですね。
psqlで手入力で認証情報をテーブルにinsertしました。

settings.yml の DB とは違うところに insert してる可能性もあるかもしれないですね。
これ以上は実際のコード見てみないと、僕には解決できなさそうです。
ということで99%上記の fumi san のコードまんまですがパッケージを作成しました!(GitHub にしか置いてませんけど)
https://github.com/matsubara0507/fallible
https://github.com/matsubara0507/fallible#usage にある slackstack のtypeですかね :thinking_face:
ほんとだw どうもありがとう!
いえいえw
すごく今更なんですが

fromMaybeWith :: Monad m => m (Maybe b) -> m r -> ContT r m b
fromMaybeWith m e = withCont (maybe e) $ lift m

fromEitherWith :: Monad m => m (Either a b) -> (a -> m r) -> ContT r m b
fromEitherWith m act = withContT (either act) $ lift m


って書けますね
個人的には、fumiさんのexitだけを何処かで定義しておいて

do
...
mr <- action
r <- maybe (exit "なんとか") return mr


みたいに書く事が多いです
返信ありがとうございます。

マイグレーションでYesodが生成してくれたテーブルにpsqlで手入力したのでDBが違うという事はないと思います。

auth-hashdbが勝手にsaltとかつけてるのかも知れないと思い、psqlから手入力ではなくてyesodから管理者アカウントを登録してみようと思っています。migration時に管理者アカウントを追加できれば良いのですが、モナドトランスフォーマーの理解が不十分なので上手く行かなくて苦戦しています。

それで入力フォームを先に作成しようと頑張っているのですが、まだHaskell自体に不慣れなので苦戦しています。

--認証情報のフォーム(src/Handler/ClientAuth.hs)
clientAuthForm :: Html -> MForm Handler (FormResult ClientAuth, Widget)
clientAuthForm = renderDivs $ ClientAuth
    <$> areq (selectFieldList clients) "クライアント" Nothing
    <*> areq (selectFieldList systems) "システム" Nothing
    <*> areq textField "Username" Nothing
    <*> areq textField "Password" Nothing
    where
 	clients :: [(Text,Key Client)]
        clients = do
            list <- runDB $ selectList [] [Asc ClientId]
            fmap (\(Entity clientId c) -> (clientName c::Text,clientId)) list
	
	systems :: [(Text,Key System)]       
        systems = do
            list <- runDB $ selectList [] [Asc SystemId]
            fmap (\(Entity systemId s) -> (systemType s::Text,systemId)) list

 --認証情報のモデル(config/models)
ClientAuth                                                      
    clientId ClientId
    systemId Int       
    username Text      
    password Text      
    UniqueClientAuth username
    deriving Show    
 


このフォームがコンパイルできないんですが、ClientIdやSystemIdはIntに変えないとダメなんでしょうか?
そうすると正しくない数値を受け付けてしまいそうなんですが、、、。

    • Couldn't match type ‘Int’ with ‘Key Client’
      Expected type: AForm (HandlerFor App) (Key Client)
        Actual type: AForm (HandlerFor App) Int
    • In the second argument of ‘(<$>)’, namely
        ‘areq (selectFieldList clients) "クライアント" Nothing’
      In the first argument of ‘(<*>)’, namely
        ‘ClientAuth <$> areq (selectFieldList clients) "クライアント" Nothing’
      In the first argument of ‘(<*>)’, namely
        ‘ClientAuth <$> areq (selectFieldList clients) "クライアント" Nothing
           <*> areq (selectFieldList systems) "システム" Nothing’
   |
11 |     <$> areq (selectFieldList clients) "クライアント" Nothing
auth-hashdbが勝手にsaltとかつけてるのかも知れないと思い

内部では特に salt とかは付けていなかったと思います。

このフォームがコンパイルできないんですが、ClientIdやSystemIdはIntに変えないとダメなんでしょうか?

これは modelssystemId Int と宣言しているのに、form では Key System の値を返しているからだと思います。

ClientAuth                                                      
    clientId ClientId
    systemId Int       
    username Text      
    password Text      
    UniqueClientAuth username
    deriving Show    


けど、エラーメッセージは、なんか違いますね・・・。

• Couldn't match type 'Int' with 'Key Client'
      Expected type: AForm (HandlerFor App) (Key Client)
        Actual type: AForm (HandlerFor App) Int
ありがとうございます。ご指摘の通り modelの
`systemId Int`
の部分に問題がありました。それを直したらエラーがそれぞれ1箇所だけになりました。


```
-- ソース
clientAuthForm :: Html -> MForm Handler (FormResult ClientAuth, Widget)
clientAuthForm = renderDivs $ ClientAuth
<$> areq (selectFieldList clients) "クライアント" Nothing
<*> areq (selectFieldList systems) "システム" Nothing
<*> areq textField "Username" Nothing
<*> areq textField "Password" Nothing
where
clients :: [(Text,Key Client)]
clients = do
list <- runDB $ selectList [] [Asc ClientId]
fmap (\(Entity clientId c) -> (clientName c::Text,clientId)) list

systems :: [(Text,Key System)]
systems = do
list <- runDB $ selectList [] [Asc SystemId]
fmap (\(Entity systemId s) -> (systemType s::Text,systemId)) list
```

```
-- エラー
/Users/you/haskell/CustomerLedger/src/Handler/ClientAuth.hs:18:21: error:
• Couldn't match type ‘HandlerFor site1’ with ‘[]’
Expected type: [[Entity Client]]
Actual type: HandlerFor site1 [Entity Client]
• In a stmt of a 'do' block:
list <- runDB $ selectList [] [Asc ClientId]
In the expression:
do list <- runDB $ selectList [] [Asc ClientId]
fmap
(\ (Entity clientId c) -> (clientName c :: Text, clientId)) list
In an equation for ‘clients’:
clients
= do list <- runDB $ selectList [] [Asc ClientId]
fmap
(\ (Entity clientId c) -> (clientName c :: Text, clientId)) list
|
18 | list <- runDB $ selectList [] [Asc ClientId]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

/Users/you/haskell/CustomerLedger/src/Handler/ClientAuth.hs:23:21: error:
• Couldn't match type ‘HandlerFor site0’ with ‘[]’
Expected type: [[Entity System]]
Actual type: HandlerFor site0 [Entity System]
• In a stmt of a 'do' block:
list <- runDB $ selectList [] [Asc SystemId]
In the expression:
do list <- runDB $ selectList [] [Asc SystemId]
fmap
(\ (Entity systemId s) -> (systemType s :: Text, systemId)) list
In an equation for ‘systems’:
systems
= do list <- runDB $ selectList [] [Asc SystemId]
fmap
(\ (Entity systemId s) -> (systemType s :: Text, systemId)) list
|
23 | list <- runDB $ selectList [] [Asc SystemId]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```

なぜこのコードでExpected typeが2重のリストになってしまうのでしょうか?
データベースからEntityのリストを所得してそれをタプルのリストにするという処理のつもりなのですが、
どこに問題があるのでしょうか?_
どこから説明するべきか悩みますが、以下の clients は明らかにコンパイルが通らないコードです。

clients :: [(Text,Key Client)]
clients = do
    list <- runDB $ selectList [] [Asc ClientId]
    fmap (\(Entity clientId c) -> (clientName c::Text,clientId)) list


なぜなら dorunDB があるので、正しい clients の型は HandlerFor site a のような型になっていて欲しいはずです。
まずは、実際にそれぞれの式の型がどのようになっているか、ghci で確認してみると良いと思います。

# クラス制約は省略しているので、正確な結果ではありません。

ghci> :t selectList
  [Filter record]
    -> [SelectOpt record]
    -> ReaderT backend m [Entity record]

ghci> :t selectList []
  [SelectOpt record]
    -> ReaderT backend m [Entity record]

ghci> :t selectList [] []
  ReaderT backend m [Entity record]

ghci> :t runDB
  ReaderT (YesodPersistBackend site) (HandlerFor site) a
     -> HandlerFor site a


ここで runDB (selectList [] []) の型は HandlerFor site [Entity record] になります。
型変数はそれぞれ backendYesodPersistBackend site, mHandlerFor site, a[Entity record] に対応します。

list <-runDB (selectList [] [])


この時の list の型は当然 [Entity record] です。(この辺りが良くわからない場合は、モナドの 使い方 を簡単に復習しておくと良いと思います。)

なぜこのコードでExpected typeが2重のリストになってしまうのでしょうか?

fmap の型から考えてみましょう。

ghci> :t fmap
   Functor f => (a -> b) -> f a -> f b


fmap の第二引数は f a の型である必要があるので、 list の型は f a になりますよね。
そして、さらに

list <- runDB (...)


というコードになっているため、 runDB (...) の結果の型 (clients の型) は m (f a) になっている必要がありますよね。
この mf が両方とも リスト ([]) に推論され、二重のリストを期待するというコンパイルエラーになっています。

こんな感じで伝わりますか?
ローカルで試したら問題無く動きました。
サンプルコードあげておきます。
https://github.com/waddlaw/yesod-hashdb-example-psql
postgres-hashdb-example=# select * from "user";
 id | ident  |                                    password                                     
----+--------+---------------------------------------------------------------------------------
 1 | admin | sha256|17|gQ3ny1p2WBkGrxrz5krLXg==|bKJ2rPrKczl3eFjQLRwsZi3OpiVepEH0511ocjcAO98=
(1 rows)


user テーブルはこんな感じです。
Haskell初心者です
Haskellのデータ構造についての質問です。
haskellを使って簡単なparserを書きたいと考えていています

parser :: [String] -> Ast


ですが、このAstがどんな感じの表現になるのかがわかりません。
普段はTypeScriptを書いていて、jsonに慣れているのでつい

const ast = {
    lhs: {
        lhs: 2,
        op: '*',
        rhs: 3
    },
    op: '+',
    rhs: {
        lhs: 2,
        op: '-',
        rhs: 4
    }
}


のように、「プロパティ名: 値」の入れ子の構造を考えてしまいます。
そして、tsなら console.log(ast) で、上のような文字列が表示されるので、なるほどってなるのですが、Haskellの場合はどうやって表現し、それを出力(?)するのでしょうか。

上の例のように

data Ast = Ast {
    op :: TokenType,
    lhs :: String,
    rhs :: String
} deriving (Show, Eq)

のようにするのかもしくはもっと簡潔にS式のような構造で表現したりするのでしょうか。

1+2*3のような簡単な数式をAstに変換した際の文字列を示して頂けると、すぐにわかる気がします。

自分でもどこがわかっていないのかわかっていない状態なので、すごくわかりにくい質問文になってしまってます。すみません。

よろしくおねがいします
Haskell と json の違いというよりは、静的な型がある言語と無い言語での発想の違いかな、という気がします。
data Expr = Add Expr Expr -- "+" 演算子を表現
          | Sub Expr Expr -- "-" 演算子を表現
          | Mul Expr Expr -- "*" 演算子を表現
          | Div Expr Expr -- "/" 演算子を表現
         | LitInt Integer -- リテラルの整数値を表現

みたいな感じにするのがよくあるパターンなんじゃないかなと思います
あえて data Ast = と書かなかったのは、たぶんこの Expr という型は Ast 全体ではなくてその一部になるだろう、という予想をしたからです。おそらく Ast としては「式」以外に「文」とかも扱うことになると思うのですがそれはそれでさらに別の型を必要とすることになり、そういうものを組み合わせてようやく目的の Ast 型を作ることができると思います。
json は、 json さえあれば何でも表現できてしまうので、そういう発想をしないんですよね…。
printデバッグをしたい場合は Debug.Trace パッケージの traceShowId という関数が使えます。 Show なやつは traceShowId (hoge) みたいにくるんでやると hoge が評価される時にprintされます。
なるほど!!ありがとうございます
ちょっと試してみます
詳しい説明とサンプルコードありがとうございます。問題解決致しました。

意外な所にミスがあってお恥ずかしい限りなのですが、DBに登録されているpasswordのダブルコーテーションが問題でした。:sweat_smile:


"sha256|17|gQ3ny1p2WBkGrxrz5krLXg==|bKJ2rPrKczl3eFjQLRwsZi3OpiVepEH0511ocjcAO98="


sha256|17|gQ3ny1p2WBkGrxrz5krLXg==|bKJ2rPrKczl3eFjQLRwsZi3OpiVepEH0511ocjcAO98=

フォームに関してはモナドとモナドトランスフォーマー辺りの勉強が足りてないのが原因だと思うので頂いた情報を元にghciで型を調べながら進めていこうと思います。

丁寧にご回答頂いてとても助かりました。ありがとうございました。
@honda has joined the channel
@kamo has joined the channel
@ has joined the channel
こちらのコミット https://github.com/iij-ii/direct-hs/commit/4f45018eb92bc557de3e563e90db2637f46d02eb に書いたcabal.projectを用意して、開発中のパッケージをGHC 8.8.1-alpha2でビルドしてみてるところなのですが、
下記のようなエラーで依存関係の解決に失敗してしまいます。
comonadパッケージがSetup.hsでCabalパッケージに依存していることが原因のようなんですが、
実際にComonadパッケージのcabalファイルの該当箇所 https://github.com/ekmett/comonad/blob/d9aeb5e09d3e98f0efccffda52c63ceed2a2ec9e/comonad.cabal#L43 を読む限り、特にCabalパッケージのバージョンに制約を加えてはいません。これはどう回避すればいいでしょうか?

> cabal v2-build direct-hs
Resolving dependencies...
cabal.exe: Could not resolve dependencies:
[__0] trying: skews-0.1.0.2 (user goal)
... 省略 ...
[__6] next goal: comonad:setup.Cabal (dependency of comonad)
[__6] rejecting: comonad:setup.Cabal-3.0.0.0 (constraint from maximum version
of Cabal used by Setup.hs requires <2.6)
[__6] rejecting: comonad:setup.Cabal-2.4.1.0, comonad:setup.Cabal-2.4.0.1,
comonad:setup.Cabal-2.4.0.0, comonad:setup.Cabal-2.2.0.1,
... 省略 ...
comonad:setup.Cabal-1.1.6, comonad:setup.Cabal-1.24.1.0 (constraint from
project config TODO requires >=3)
[__6] fail (backjumping, conflict set: comonad, comonad:setup.Cabal)
Backjump limit reached (currently 2000, change with --max-backjumps or try to
run with --reorder-goals).
Masako Shinomiya
@Masako Shinomiya has joined the channel
こんにちは。どなたか、もしこのライブラリの使い方のサンプルコードご存知でしたら教えてください。 AWS SecretsManagerから、SecretString を取得することができるはずなのですが。。 ソース読まないとだめかな。
http://hackage.haskell.org/package/amazonka-secretsmanager-1.6.1/docs/Network-AWS-SecretsManager-GetSecretValue.html
もしかするとhead.hackageの内容に食い違いがあるのかもしれないですね… 以下を追加したらghc-pathsで止まるようになりました
source-repository-package
  type: git
  location: 
  tag: d9aeb5e09d3e98f0efccffda52c63ceed2a2ec9e
cabalの3.0ブランチの54464304b707c57b233eb910210b79a9ba5154b3を使い、cabal-installをインストールしたらビルドできるようになりました
exitというContTのコンビネータは唐突すぎるので、名前を変えたり別のところに移したほうが良い気がしてきました…
いじっているうちに出来ました。お騒がせしました。
ありがとうございます!試してみます!
需要あるかどうかわかりませんが、一応公開。
https://github.com/smasuda/haskell-aws-secretsmanager
cabal-installのビルドに少し手こずったのでメモしておきます。

git clone 
cd cabal
git checkout origin/3.0
cd cabal-install
cabal v2-build --with-ghc=<適当なGHC 8.6.5のプロジェクトでstack exec which ghcして得たGHCへのパス>

でビルドできました。
@s_tomita has joined the channel
お疲れ様です。
unsafePerformIの中でperformGC呼んでも安全?(定義が難しいですが、ランタイム的にとしておきましょうか。)
でしょうか?やってはいけない操作がありますか?
@ has joined the channel
Template Haskellでコードを生成している途中に発生したエラーの、バックトレースを表示させることは出来ないでしょうか?
とあるTemplate Haskellのマクロがコードを生成する際に reify を何カ所かで呼ぶのですが、そのどこかでエラーが発生してしまうので、その詳細を調査したいです。
stack build --profile しただけではうまくいきませんでした...。