haskell-jp / questions #87

返信遅くなりましたがうまく行きました!!!
ありがとうございます!!
Lensはoverやsetやviewと使うものだという固定概念がありました…今度からもっと型を注意してみるようにしてみます

Prelude Control.Lens> _2 (\a -> print ("PRINTED: " ++ show a) >> return (Just "a")) (("hoge",6), 1.20)
"PRINTED: 1.2"
(("hoge",6),Just "a")
Prelude Control.Lens> (_1 . _2) (\a -> print ("PRINTED: " ++ show a) >> return (Just "a")) (("hoge",6), 1.20)
"PRINTED: 6"
(("hoge",Just "a"),1.2)

そうなんですねwすごい偶然
初歩的な質問ですが、お尋ねさせてください。
仮定として、Main.hsに以下のような相対パス指定のファイル読み込みのコードが書かれているとします。

readFile "../hoge.txt" --1つ上の階層のパスからファイルを読み込み

この時、current working directoryに依存せずに、「Main.hsファイルを起点とした相対パス指定」でファイルを読み込みたいです。
(「stack exec XXX-exe」でプロセスを起動すると、current working directory起点とした相対パス指定になるため、場合によりうまく動かないです。)

上記のやりたいことを実現するための、お決まりのパターンとかってありますか?
getCurrentDirectoryだと、簡単には行かなそうです。
私が同じような問題に当たっときは「コンパイル時に相対パス指定したファイルを埋め込む」という方法を取りました。
以下のパッケージがそのような手段を提供しています。
https://hackage.haskell.org/package/file-embed-0.0.12.0
他の方法をご存じの方がいらっしゃいましたらよろしくお願いします(私も気になってます…)
@maton
回答ありがとうございます。上記の方法はなかなかテクニカルですね。。
意外と手軽にできる方法はなかったりするのですかね:cry:
コンパイル時のことって基本的に忘れられてしまうので難しいですね。
イマイチ目的が想像できませんが、プロジェクトルートからの相対パスか、 https://www.haskell.org/cabal/users-guide/developing-packages.html?highlight=paths_#accessing-data-files-from-package-code などに書かれている Paths_pkgname を使った方が賢明かと思います。
それでもコンパイルされる Main.hs の場所が欲しい、ということであれば、Template Haskellの location が使えるかな、と思ったけどこれはフルパスはとれないっぽいな
http://hackage.haskell.org/package/template-haskell-2.16.0.0/docs/Language-Haskell-TH-Syntax.html#v:location
自分も Paths_pkgname が正攻法に思います
コンパイル時に指定したパスが実行時にも存在することが保証されている前提ならCPPを有効にして __FILE__ マクロを使う手もあります。
@igrep @kakkun61 @maoe
なるほど、ありがとうございます!
確かに

コンパイル時のことって基本的に忘れられてしまうので難しいですね。
これが真理な気がしてきました。。
基本的にstackを利用してprojectを作成する際は、実行体である.exeファイルはstackのサンドボックス内に作られるので、プロセスの起動もサンドボックス内の特定のディレクトリで行う、という形をとるのが素直な気がしてきました:bow:
@ has joined the channel
@kuu has joined the channel
@Roki has joined the channel
@Shota has joined the channel
@kazuki has joined the channel
「プログラミングHaskell 第2版」13章の「モナドパーサー」を読んでいますが、
以下の式がどう簡約されていき、最終的に[("1", "a")]と評価されているのか分からず、苦戦中。。

これ、どのような順序で式が評価されていってるのでしょう。。
>parse (some digit) "1a"
[("1","a")]

以下の簡約の仕方だと、永遠に評価が終わらない。

-- many x = some x <|> pure []
-- some x = pure (:) <*> x <*> many x

parse (some digit) "1a"
parse (pure (:) <*> digit <*> many digit) "1a"
parse (pure (:) <*> digit <*> (some digit <|> pure [])) "1a"
parse (pure (:) <*> digit <*> ((pure (:) <*> digit <*> many digit) <|> pure [])) "1a"
…(以下略)


ちなみに、以下のコードを叩いてみたら、次の結果となったので、末尾の相互再帰(many/some)の部分は遅延評価されているように思えますが、具体的にどう簡約されていくのかが分からないです。。

>parse (some digit) (repeat 'a')
[] --これは直ちに結果(empty)を返す

>parse (some digit) (repeat '1')
*** Exception: stack overflow --スタックオーバーフローが発生


<補足>
上記の元となる実装は以下です。

newtype Parser a = Parser (String -> [(a, String)])                                                                                                                                                                                           

parse :: Parser a -> String -> [(a, String)]
parse (Parser p) inp = p inp 

instance Alternative Parser where
  empty  = Parser (\inp -> []) 
  p <|> q = Parser (\inp -> case parse p inp of
                              [] -> parse q inp 
                              [(a, out)] -> [(a, out)])
  many x = some x <|> pure []
  some x = pure (:) <*> x <*> many x


instance Functor Parser where
  fmap f p = Parser (\inp -> case parse p inp of
                               [] -> []
                               [(a, out)] -> [(f a, out)])

instance Applicative Parser where
  pure v = Parser (\inp -> [(v, inp)])
  pf <*> pa = Parser (\inp -> case parse pf inp of
                                [] -> []
                                [(f, out)] -> parse (fmap f pa) out)

instance Monad Parser where
  return = pure
  p >>= f = Parser (\inp -> case parse p inp of
                         [] -> []
                         [(v, out)] -> parse (f v) out)

item :: Parser Char
item = Parser (\inp -> case inp of
                          [] -> []
                          (x:xs) -> [(x, xs)])

sat :: (Char -> Bool) -> Parser Char
sat p = do
  x <- item
  if p x then return x else empty

digit :: Parser Char
digit = sat isDigit

ちょっと今試す余裕がないので取り急ぎの回答を。
引数の文字列を消費していないからあたかも無限に続くように見えるのです。先に parse を展開してみてください。
ただ、手で簡約するのはしんどいと思うので、`parse` や many , some の引数と戻り値を評価するタイミングで trace (`many` や some ではそのままでは traceShowId が使えないので注意) してみたり、その上でGHCi の :sprint で戻り値がどう評価されるか見たり、 あるいはもしかしたら http://felsin9.de/nnis/ghc-vis/ が役に立つかも知れません。
もう一点訂正させてください。
末尾の相互再帰(many/some)の部分
manysome における再帰は末尾再帰ではないです。(末尾再帰ちゃんとできてるならスタックオーバーフローもしないはずですし

末尾再帰の正確な定義については検索してください :pray:
基本的には、遅延評価と覚えるのではなく、いちばん外側の式から簡約されていくと思えばいいです

parse (some digit) "1a"(parse (some digit)) "1a" の略記なので、いちばん最初は e1 e2 (`e1 = parse (some digit)`、`e2 = "1a"`) の形の簡約から始まります。

e1 e2 の形の簡約方法は、`e1` を (\x -> ...) の形かコンストラクタへの部分適用まで評価した後、`e2` を適用するというものです。今回は、まず parse (some digit) をその形まで簡約します。

parse (some digit) もやっぱり、`e1 e2` の形なので、さらに parse の簡約を行います。`parse` はその定義から \(Parse p) inp -> p inp の形に簡約されます。
次に、`(\(Parse p) inp -> p inp) (some digit)` の適用が始まります。

(\(Parse p) inp -> p inp)\x inp -> case x of { Parse p -> p inp } の略記なので、まず適用結果は \inp -> case (some digit) of { Parse p -> p inp } になります。
で、一番最初の parse (some digit) "1a"(\inp -> case (some digit) of { Parse p -> p inp }) "1a" になり、適用操作が始まります。その結果 case (some digit) of { Parse p -> p "1a" } になります。

ここからがちょっと特殊で、`Parse` 型が datanewtype かで簡約方法が異なります。今回は newtype なので、その場合の簡約方法を見ていきます。

まず、今の一番外側の式の形は case e1 of { ... } になるので、この形の簡約方法が適用されます。`case` の形は結構複雑で、その全容は https://www.haskell.org/onlinereport/haskell2010/haskellch3.html#x8-580003.17 に書かれています。

今回の case e1 of { Parse p -> p "1a" } でのパターン Parse p の場合、`newtype` によるコンストラクタ Parse はないものとして扱われ、`e1` は p に直接紐づかされます。これは、Haskell の式では表現できないですが、気分的には cast<String -> ...>(some digit) "1a" みたいな感じで e1Parse 型ではなく String -> ... 型の値として扱われるようになります。

で、また e1 e2 の形の簡約が適用されます。この場合 cast<...>( ) はないものと思ってもらってよくて、`some digit` をまず簡約するという流れになります
some digit はやっぱり e1 e2 の形をしているので、今までの流れと同じく some の定義から (\x -> pure (:) <*> x <*> many x) digit になった後、`x` に digit が紐づき pure (:) <*> digit <*> many digit のように簡約されます

(実際には変数 x の領域が作られ、そこに digit と紐づいてることが記憶され、簡約結果は変数 x を残しそこの簡約が走ると記憶領域が参照されその時初めて digit に置き換わりますが、今回は省略します)
pure (:) <*> digit <*> many digit<*> が左結合なので、`(<*>) ((<*>) (pure (:)) digit) (many digit)` の略記になります。なので、`e1 = (<*>) ((<*>) (pure (:)) digit)`、`e2 = many digit` においていちばん外側の式の形が e1 e2 になる式です。
よって、今までの流れ通り (<*>) ((<*>) (pure (:)) digit) の簡約が実行されます。これは (<*>) の定義が (\pf pa -> Parser (\inp -> case parse pf of ...)) なのでそれに展開され、その後 pf(<*>) (pure (:)) digit が紐づいて \pa -> Parser (\inp -> case parse ((<*>) (pure (:)) digit) inp of ...) になります。

で、最初に戻って今度は pamany digit が紐づいて Parser (\inp -> case parse ((<*>) (pure (:)) digit) inp of ...) になります。

newtypeParser コンストラクタは無視してよいので cast<...>(some digit)\inp -> case parse ((<*>) (pure (:)) digit) inp of ... にようやく簡約されることになります
で、`inp` に "1a" が紐づき case parse ((<*>) (pure (:)) digit) "1a" of ... の簡約が始まります
case e1 of ... は基本的にはマッチするパターンを最初から探していき、その過程で e1 が簡約されていきます。今回は、
case parse ((<*>) (pure (:)) digit) "1a" of
  [] -> []
  [(f, out)] -> parse (fmap f (many digit)) out

が省略なしで書いた現在の簡約結果なので、最初のパターンは [] になります。この場合、`[]` は data による型なので parse ((<*>) (pure (:)) digit) "1a"\x -> ... かコンストラクタへの部分適用の形になるまで評価します。

その過程で ((<*>) (pure (:)) digit) の部分が簡約されますが、これは今までの流れと大体同様なので、その過程は省略します。
最終的に、`parse ((<*>) (pure (:)) digit) "1a"` は
case parse (pure (:)) "1a" of
  [] -> []
  [(f, out)] -> parse (fmap f digit) out

になります。

で、また parse (pure (:)) "1a" の簡約が実行されることになります。これは、進めると [((:), "1a")] になります。

よって、
case [((:), "1a")] of
  [] -> []
  [(f, out)] -> parse (fmap f digit) out

に簡約が進むことになります
これは [] のパターンマッチに失敗し、今度は [(f, out)] のパターンが参照されます。こちらには成功するので、`f` に (:) が、`out` に "1a" が紐づき parse (fmap (:) digit) "1a" に簡約が進みます
ここからは省略気味で進めます。`parse (fmap (:) digit) "1a"` は、
case parse digit "1a" of
  [] -> []
  [(a, out)] -> [((:) a, out)]

に簡約が進み、`parse digit "1a"` の簡約が始まります。

parse digit "1a" の簡約において、`digit` の簡約が始まります。`digit` はその定義から、`sat isDigit` に展開され、`sat` の定義から
do
  x <- item
  if isDigit x then return x else empty

に簡約されます。ところで、この式は、以下の略記になります
item >>= \x -> if isDigit x then return x else empty
で、この式はさらに (>>=) item (\x -> if isDigit x then return x else empty の略記なので、`(>>=)` の定義から
\inp -> case parse item inp of
  [] -> []
  [(v, out)] -> parse ((\x -> if isDigit x then return x else empty) v) out

に展開されることになります。後は inp"1a" が紐づいて
case parse item "1a" of
  [] -> []
  [(v, out)] -> parse ((\x -> if isDigit x then return x else empty) v) out

の簡約が始まることになります
これまでと同様 [] パターンから parse item "1a" の簡約が始まり、
case "1a" of
  [] -> []
  (x:xs) -> [(x, xs)]

へと簡約が行われ、`"1a"` は ['1', 'a'] の略記で、これは '1':('a':[]) の略記なので、`(x:xs)` のパターンマッチに成功し [('1', "a")] が出てくることになります
その手順を続けていくと、`parse (fmap (:) digit) "1a"` は
[((:) '1', "a")]

になることになります。後は、そのまま簡約を上記の手順で進めていくと、`parse (some digit) "1a"` が最終的に [("1", "a")] になる過程が分かると思います
@
詳細な解説ありがとうございます!:sob:
後でじっくり読ませていただきます!:man-bowing:

@igrep
デバッグ手順のご教示ありがとうございます!勉強になります:smile:
あ、末尾再帰ではないですね:sweat_drops:日本語の表現がおかしくてすみません:man-bowing:
:sprintは後で試してみます!
ついでに、`parse (some digit) (repeat '1')` が stack overflow するやつは、ぱっと見
many x = some x <|> pure []

この定義が問題ですね (詳しく見てないので、間違ってたらすいません)

ずっとマッチし続ける場合 some x のパースがどんどん進行していきその過程で many x がどんどん再帰的に展開されます。で、`many x` が展開された際、使用されない pure [] が遅延されたまま残り続けることになります。

なので、`many x` が展開された分だけ、遅延された pure [] に相当するオブジェクトが増え続けます。

通常この遅延されたオブジェクトはヒープ領域に格納されていくので、通常はヒープオーバフローになるんですが、実は Haskell の GHCi は通常のコンパイル結果と異なる簡易的なスタックマシンでコードを実行するので、GHCi 上ではスタックにこの遅延されたオブジェクトが積まれていき、スタックオーバフローになるんだと思いますね
Haskell の実行マシンはちょっと特殊で、全ての関数呼び出しは単なるジャンプ (末尾呼び出し相当) なので、末尾再帰でないからスタックオーバーフローが起きるというわけではありません。

ただ、通常の言語と傾向としては似ていて、many / some の相互再帰で、many が some を実行した結果を見て pure [] を計算するか判断しなければいけなくて、そのためには many が展開された時のフレームを何らかの方法で退避しておかなければいけないのは共通です。

それが通常の言語ではスタック退避なのが、Haskell ではフレームの代わりに遅延オブジェクトをヒープに退避させる (GHCi ならスタックに退避させる) ことになる傾向はあります

(ただ、これは一概に完全に対応するわけではなく、たまたま Haskell だとうまくオブジェクトが消費されたり、逆に普通の言語だと退避が起きない状況で退避が起きたりということはありますね)
@Jahon has joined the channel
途中で寝てしまってる間に他の方の回答が付いた&途中で力付きたのですが手動簡約の供養として貼りました(どこかミスしてる自身がある)
@kakkun61
ありがとうございます!:sob:
勉強になります:man-bowing:
TemplateHaskell の Type データを,(型クラス自分で定義する以外で) 型から作る方法ってありますか?イメージとしては,
import qualified  as TH

_ :: (...) => Proxy a -> TH.Type

みたいな型の関数が書けることを想定してます.
パッと思いついたのは、TypeableのsplitTyConAppで型情報を分解して、各TyConをNameに変換するというやり方です。自分ではまだ試していませんが
その方法で,うまくいきそうです.ありがとうございます
GHCiによるデバッグ時に、出力される内容に分からないことがありましたので質問させてください。
(GHCiデバッグに限らない内容ですが、ポチポチしているときに改めて気になりました。。)

GHCiにて、「:break 2」コマンドを打つと、現在ロードされているモジュールの2行目にブレークポイントが貼られ、
以下のような内容が出力されます。
*Main> :break 2
Breakpoint 0 activated at qsort.hs:2:15-46

上記の、
qsort.hs:2:15-46

の末尾の「15-46」という数字は何を指しているのでしょうか?
この数字が、デバッグ時に役立つ情報なのかどうかが分からず。

ちなみに、上記のqsort.hsファイルは次のような内容で、全体で5行しかないため、
ファイルの行数とは無関係とは思われますが。。

qsort [] = []
qsort (a:as) = qsort left ++ [a] ++ qsort right
 where (left,right) = (filter (<=a) as, filter (>a) as)

main = print (qsort [8, 4, 0, 3, 1, 23, 11, 18])

※qsort.hsの内容や、操作したコマンドは以下のURLと同じです。
ポチポチしていた時に気になったため質問させていただきました。

https://downloads.haskell.org/~ghc/7.6.3/docs/html/users_guide/ghci-debugger.html
2:15-46 で2行目の15文字目から46文字目という範囲、すなわち qsort left ++ [a] ++ qsort right を表しています。
どこかにはっきり説明があるというわけでもない (たぶん) ので、ハマりますよね…
@
ありがとうございます!
そういうことだったんですね!モヤモヤが晴れました:smile:
Aquila Minakami
@Aquila Minakami has joined the channel
補足: https://ghcguide.haskell.jp/8.0.2/users_guide/ghci.html#setting-breakpoints でも触れているとおり、識別子や行番号・桁番号でもブレークポイントの位置を指定できるのでそうなっているものと思われます。
@igrep
なるほど!
そういう時に役立ちますね!
attoparsec、あるいはApplicativeやAlternativeの基本についてご質問です


やろうとしているのは
`Dest=Comp;Jump` ないし `Dest=Comp` という形式のテキストのパースです


スレッド欄にて
* こんな風に書きたいが、正しくないコード(Ideal)
* 正しいが、汚いと思うコード(Reality)
をご覧ください


目的はIdealの簡潔さをなるべく保ちながらRealityの正しさを実現することです


`Ideal.test` を実行すると、入力 `MD=...` については
`aDest` が `M` しか消費しないので、残る `D=...` が `=` にマッチせず失敗しています


`Reality.test` を実行すると、結果は all isRight で正しくパースされているのですが
* `aDest` の `<* char '='`
* `aComp` の `A <$ "A"`
が冗長と感じます、もっとスッキリ書けないでしょうか?


`aDest` の定義で `<|>` でつないだ並びは実際には数が多くコピペで済ませたいので
これを正しくソートするという方法は避けたいです
こんな風に書きたいが、正しくないコード(Ideal)
正しいが、汚いと思うコード(Reality)