haskell-jp / questions #51

もし本当に「リストの長さが違っていて長い方のリストの後ろの方に Nothing がある場合も Nothing」にしたいなら、先にそれぞれ sequence してから liftA2 zip して fmap fromList すればよさそうですね
fmap fromList $ liftA2 zip (sequence keys) (sequence values)
せっかくだから fromList <$> liftA2 zip (sequence keys) (sequence values) と書きたいか^^;
なぜapplicativeスタイルで書かないのだ?
いや書きましたw
liftA2 までやるとちょっと読みにくいかなーと^^;
丸括弧で囲む。
fromList <$> (zip <$> sequence keys <*> sequence values) ですね、はい^^;
あ、これちがうな、なんかペアがつくれてないな
これだと keyばっかりのリストとvalueばっかりのリストがペアになるだけでした^^;
なるほど!とても勉強になります!!
あれ、いや
ちゃんとペアできてた
等価な変換で結果が違う方がおかしい。
いや、元のやつが間違ってるかと思ったのでした…目がおかしかったようです
こんな初心者の質問に皆さんいろんな意見を出して頂けてとても嬉しいです!:relaxed:
で、もちろんそれぞれ sequence するということはそれぞれスキャンし終えてからもう一度 zip でたどることになるんですけど、それが仕様ならそうするしかないですよね
あ、ちなみに今回の僕のケースだとkeysとvaluesでサイズが違う場合があります
ただ、まあ、元のコードはスキャンの回数よりもむしろ fromJust が部分関数なあたりが怖いので、それをなくせれば良さそうな感じはしますね
はい!allでチェック済みとはいえ部分関数を使っていたのですごくどうにかしたい気持ちがありました
僕も勉強になった!そして最終的にこうなった。(これは間違っています)

some :: (Ord key) => [Maybe key] -> [Maybe value] -> Maybe (M.Map key value)
some keys values = M.fromList <$> zipWithM f keys values
  where
    f :: Maybe key -> Maybe value -> Maybe (key, value)
    f mkey mvalue = (,) <$> mkey <*> mvalue
ああ、つまり「長い方の後ろのほうにある Nothing」は無視するほうの仕様になったんですね
そうだった。。忘れてた。。もう寝ます
各リストをちょうど一回ずつ舐めるようにするなら
foldM step (M.empty,values) keys >>= (\(acc,l) -> acc <$ sequence_ l)
        where
        step (!acc, []) mkey = (acc, []) <$ mkey
        step (!acc, mval:vals) mkey = 
            (,vals) <$> (M.insert <$> mkey <*> mval <*> pure acc)

みたいな感じですけど、さすがにkazuさんのいう通り再帰関数書いた方が読みやすいですね。
あ、ちなみに今回の僕のケースだとkeysとvaluesでサイズが違う場合があります

それなら自分ならこう

pairWith f (Just x) (Just y) = Just (f x y)
pairWith _ _        _        = Nothing

pair = pairWith (,)
left = pairWith const
-- right = pairWith (flip const)
cross f g (x, y) = (f x, g y)

some :: [Maybe a] -> [Maybe b] -> Maybe [(a, b)]
some xs ys = sequence ps `left` sequence rs
  where
    dummy = repeat (Just undefined)
    (ps, rs) = go xs ys []
    go []     ys     ps = (ps, zipWith pair dummy ys)
    go xs     []     ps = (ps, zipWith pair xs dummy)
    go (x:xs) (y:ys) ps = cross (pair x y:) id $ go xs ys ps

多分最初に書いたのもfの実装で欲しかったのはむしろpairだったか.
後ろを Just undefined で詰めるんですね^^;
undefinedは積極的に使っていくタチです.(undefined忌み嫌っているけど...)
pairWith って liftA2 だったりしませんか
@cutsea110 さんの見ながら直してたらこんなんできました
(<:>) = liftA2 (:)
(<.>) = liftA2 (,)
left  = liftA2 const

some :: [Maybe a] -> [Maybe b] -> Maybe [(a,b)]
some (x:xs) (y:ys) = (x <.> y) <:> some xs ys
some []     ys     = Just [] `left` sequence ys
some xs     []     = Just [] `left` sequence xs
# <,> が作りたかったけどカンマは無理だった…
liftA2のためにControl.Applicative入れるんならcrossもControl.Arrow ((***))をimportするか.
最近はこのテの小さいサンプルコードの場合にはimportを書かずに済ませたいのだけどbaseの範囲なら別に良いか.
たしかにこれ、 Control.Applicative をまるっと import すると some がぶつかるんですよね^^;
hylomorphism の具体例として書くのがいいとおもいます.recursion-schemesを使えばカッコいいけど,まぁリスト限定で以下のようにするのもありかな.
import Data.Map (Map)
import qualified Data.Map as M

some :: Ord k => ([Maybe k], [Maybe v]) -> Maybe (Map k v)
some = hylo phi z psi
  where
    z = Just M.empty
    phi (Just Nothing, _) _ = Nothing
    phi (_, Just Nothing) _ = Nothing
    phi _ Nothing = Nothing
    phi (Nothing, _) _     = z
    phi (_, Nothing) _     = z
    phi (Just (Just k), Just (Just v)) (Just m) = Just (M.insert k v m)
    psi ([], [])   = Nothing
    psi (k:ks, []) = Just ((Just k, Nothing), (ks, []))
    psi ([], v:vs) = Just ((Nothing, Just v), ([], vs))
    psi (k:ks,v:vs) = Just ((Just k, Just v), (ks,vs))
    
hylo :: (b -> c -> c) -> c -> (a -> Maybe (b, a)) -> a -> c
hylo phi z psi x = case psi x of
  Nothing      -> z
  Just (y, x') -> phi y (hylo phi z psi x')
hylomorphism まで出てくると、 [The Evolution of a Haskell Programmer]() を思いだしました :sweat_smile:
つ「関数プログラミングの楽しみ」「関数プログラミング珠玉のアルゴリズムデザイン」
:grinning:
初見これhylo?って思ったけどナルホド

リストにspecializeされてるから関手がMaybeなので分からなかったけどphi . fmap hylo . psi versionの方なのね.

cata . anaと同じもんだとザツに理解してたけどまたちょっと流れが違って面白いもんですね
合成するパスが違えば流れも違うんだなぁと当たり前のことを今更納得しました

ただ今回の場合はMaybeが3階層に出現しててpsiの意味を把握するのなかなか大変だと思う

これもしMaybeのリストじゃなくてMaybeのbinary treeだったらMaybe -Either-Maybeとかになってもっと判別しやすかったかな
recursion-schemes パッケージの Data.Functor.Foldable をつかったほうが,むしろ,スッキリわかるかも?
buildMap :: Ord k => ([Maybe k], [Maybe v]) -> Maybe (Map k v)
buildMap = hylo phi psi
  where
    z = Just M.empty
    phi Nil                                            = z
    phi (Cons (Just Nothing, _)              _      )  = Nothing
    phi (Cons (_, Just Nothing)              _      )  = Nothing
    phi (Cons _                              Nothing)  = Nothing
    phi (Cons (Nothing, _)                   _      )  = z
    phi (Cons (_, Nothing)                   _      )  = z
    phi (Cons (Just (Just k), Just (Just v)) (Just m)) = Just (M.insert k v m)
    psi ([], [])    = Nil
    psi (k:ks, [])  = Cons (Just k, Nothing) (ks, [])
    psi ([], v:vs)  = Cons (Nothing, Just v) ([], vs)
    psi (k:ks,v:vs) = Cons (Just k, Just v)  (ks,vs)
Maybe は内側が元データのもの,外側が元データのリストにあるかどうかを表していて,短い分はNothingが充填されています.
元のコードでphiのrhsのNothingもdouble meaningくさいですよね.
doubleじゃなくduplexか
最初のはunfoldの停止サインで2,3番目のパターンのNothingはNothingなkかvがあるから潰していると
はいそうです.
3重Maybeの一番外は[]のベース函手ListF aのNil と Cons を表現したものですね.
hyloって自分で書くときは気にならないと思いますが中間データ型として何を見て(想定して)いるのかが隠れてしまって分かりづらいので敢えてローカルでも型を明示すると良いのかなと思ってます
ghc packageって初めて使ってみたけど-package ghcを与えるんですね.
emacs使ってるんですけどロードできなくてコンパイル通すのも辛い.
なんか設定しないとやってらんないですね,これは...
スレッドの内容をページに反映させました。空白を含む場合について全然考えていませんでした。ありがとうございます:star:
https://haskell.e-bigmoon.com/stack/etc/stylish-haskell.html
https://twitter.com/hsjoihs/status/1103965106616983552
「Haskellの演算子に使う記号で、なんかこういうのにはこれを使うみたいな慣習」がまとまっていたりする記事とかってありますかね?
一応学術誌に頻出するようなのは、それと近いものにするとかあるみたいですが、後は型が別の演算子に似てたらそれに似せるとか、そういう程度ではないのでしょうか。 正直無数にありすぎて実質無法地帯な感は否めませんが、、、 と個人的に思ってます
@trueshot86 has joined the channel
@K.Iida has joined the channel