いまさらhaskellの状態管理に関して一言いっとくか
今回の記事はパッと出の思いつきで書いたものなのでぶっちゃけ読まないでいいです。例が面白くないです。
タイトルはそろそろFreeモナドに関して一言いっとくかのパクリオマージュです。
「そろそろhaskellの状態管理に関して一言いっとくか」ではありません、「いまさら」です。要するに特に目新しいことではありません。もっと言うと状態管理というかほとんどモナドトランスフォーマーの話です。
導入
純粋関数型言語であるhaskellは「状態がないから状態管理が不得意だ」なんて言われることがあったりなかったりします。かく言う私も、昔はそんなことを思っていました。
しかし実際はhaskellはそんじょそこらのプログラミング言語よりも状態管理が得意かもしれません。少なくともある点においては明確に得意と言えるでしょう。
Stateモナド
状態と言えば英語でState!haskellのStateモナドはその名前の通り、状態を扱うためのモナドです。
準備運動も兼ねて少し遊んでみましょう。後でStateT
を使うのでControl.Monad.Trans.State.Lazyに定義してあるState
を使います。
import Control.Monad.Trans.State addOne :: State Int () addOne = modify succ doubleEven :: State Int () doubleEven = do n <- get if even n then put $ n * 2 else return () main :: IO () main = do print $ execState (addOne >> doubleEven) 5 -- 12 print $ execState (doubleEven >> addOne) 5 -- 6 print $ execState (doubleEven >> addOne) 6 -- 13
状態を裏配線して、get
、put
、modify
で状態を更新したり取得しながら計算を行っています。
StateT
ここからが本題です。State
モナドはState
モナド以上でも以下でもありません。初期値を元に計算を行って、結果を受け取る以上のことはできませんし、アプリを作る際に広い範囲で使うようなことは難しいでしょう。
そこでモナドトランスフォーマーの出番です。モナドトランスフォーマーを使えば、モナドの軛を逃れ、更なる力を得ることができます。ほぼどこでも好きなところで状態を扱えるにも関わらず、非純粋な言語のグローバル関数のようにどこでそれが使われているか分からない無秩序なものではありません。
骨組み
ある程度具体的な例を使って説明しましょう。
あなたはあるアプリケーションを作りたくなりました。そのアプリでは最初に設定ファイルから設定を読み込み、時折設定を変更しながら様々な処理をします。現時点で設定をどこかで変更したり、また別のどこかで設定を使うことは予想できていますが、プログラムの全容はまだ見えていません。
今、我々が問題としているのは設定の扱い方についてです。このプログラムが何をするかは誰も知りませんが、状態に関わる部分を作っていきましょう。
とりあえず設定を保持するStateT
に加えてExceptT
も使うとして、アプリ専用のモナドを構築します。
type App e s a = ExceptT e (StateT s IO) a runApp :: App e s a -> s -> IO (Either e a, s) runApp app s = runStateT (runExceptT app) s evalApp :: App e s a -> s -> IO (Either e a) evalApp app s = evalStateT (runExceptT app) s
ついでに結果だけを取り出すevalApp
も作っておきました。
いろいろ
さて、今回はただの例なので扱う状態は簡単にInt
ということにして、このアプリで使うであろう色々な関数を書いてみます。
import Data.Char -- 状態をインクリメントする関数 incState :: Monad m => StateT Int m () incState = modify succ -- 今の状態を出力する関数 printState :: StateT Int IO () printState = get >>= lift . print -- 失敗するかもしれない状態を扱う関数 doubleNonZero :: Monad m => ExceptT String (StateT Int m) () doubleNonZero = do n <- lift get case n of 0 -> throwE "zero!" n -> lift . put $ n * 2 -- ユーザから入力を受け付ける失敗するかもしれない関数 echo :: (MonadTrans t, Monad (t IO)) => ExceptT String (t IO) () echo = do lift . lift $ putStr "type 'haskell': " cs <- lift . lift $ getLine if cs == "haskell" then lift . lift $ putStrLn "yes, haskell!" else throwE "boo, not haskell" -- ユーザに指示を仰ぐ失敗するかもしれない状態を扱う関数 tellNumber :: Monad m => ExceptT String (StateT Int IO) () tellNumber = do lift . lift $ putStr "tell a number: " s <- lift . lift $ getLine if isNumbers s then lift . put $ read s else throwE "not number" where isNumbers = all isNumber -- ユーザをバカにする関数 fool :: IO () fool = putStrLn "Hey! You fool!!"
一体何に使うのか分からない関数もありますが、とりあえずこんなところでしょうか。
これらの関数は適切に持ち上げることでApp
にすることができます
lift incState :: App e Int () lift printState :: App e Int () doubleNonZero :: App String Int () echo :: App String Int () tellNumber :: App Int () lift . lift $ fool :: App e s ()
感想
聡明な皆さんならば既にお気付きのことでしょう。これらの関数は型を見るだけでその関数が状態を扱えるかどうかが分かります。実際に型注釈にStateT
が入っているincState
とprintState
とdoubleNonZero
とtellNumber
は状態を扱っています。逆に型注釈にStateT
が入っていないecho
とfool
は状態を扱っていません。IO
が入っているかどうかも同様です。
StateT
が入っていれば必ず状態を扱うわけではないですが、確実に言えることは「StateT
が入っていない関数は絶対に状態を扱えない」ということです。こうすれば実質グローバルな状態でもわざわざ使わないStateT
を書かない限りはどこで状態を使っているかが一目瞭然です。変な場所で状態が変わっているかもしれない心配とはサヨナラ!
最後に
注意するべきことはこのグローバル変数を本当にグローバル変数として良いか考えることです。モナドトランスフォーマーを積み重ねすぎるとパフォーマンスに影響が出ますし、高く積もったモナドトランスフォーマーは見通しが悪く、バグの元になります。状態を扱う部分をモジュールとして切り出せないか考えるべきです。
TAPL4章の型無し算術式の実装
型無し算術式の実装 - プログラミング勉強日記に影響されて実装。
data Term = TmTrue | TmFalse | TmIf Term Term Term | TmZero | TmSucc Term | TmPred Term | TmIsZero Term deriving(Eq, Show) isnumericval :: Term -> Bool isnumericval TmZero = True isnumericval (TmSucc t) = isnumericval t isnumericval _ = False isval :: Term -> Bool isval TmTrue = True isval TmFalse = True isval t | isnumericval t = True isval _ = False eval1 :: Term -> Maybe Term eval1 (TmIf TmTrue x _) = pure x eval1 (TmIf TmFalse _ y) = pure y eval1 (TmIf b x y) = TmIf <$> eval1 b <*> pure x <*> pure y eval1 (TmSucc x) = TmSucc <$> eval1 x eval1 (TmPred TmZero) = pure TmZero eval1 (TmPred (TmSucc x)) | isnumericval x = pure x eval1 (TmPred x) = TmPred <$> eval1 x eval1 (TmIsZero TmZero) = pure TmTrue eval1 (TmIsZero (TmSucc x)) | isnumericval x = pure TmFalse eval1 (TmIsZero x) = TmIsZero <$> eval1 x eval1 x = Nothing eval :: Term -> Term eval t = maybe t eval $ eval1 t
アプリカティブファンクターとmaybe
が便利だと思いました(小並感)
Eitherを使うことでどこでeval1
が失敗したか分かるようにしてる。本当は失敗したところの1つ前の段階を出力して欲しかったけど眠いのでこれでいいや。
2016/04/13 色々と勘違いをしていたのでソースを修正しました。
ghci > eval $ TmIf (TmIsZero TmZero) TmFalse TmZero TmFalse ghci > eval $ TmIf (TmIsZero (TmSucc TmZero)) (TmSucc $ TmSucc $ TmSucc TmZero) TmZero TmZero ghci > eval $ TmIf (TmIf TmZero TmFalse TmTrue) TmZero TmZero TmIf (TmIf TmZero TmFalse TmTrue) TmZero TmZero
eval1
の最後の節で評価が終わったことを表すNothing
を返すことで、eval
で結果(=これ以上評価ができないもの)を返しています。これはOCamlの元ネタにおいて例外を投げたステップで結果を返すことに相当します。
ghciでの最後の例は1ステップ目で、内側のTmIf
の第一引数であるTmZero
を評価しようとしてNothing
を返すので、このステップの値である全体がそのまま返ってきています。OCamlのコードを読んだした感じでもそういう実装っぽい(OCamlの環境がないので予想)。
TmIf
、TmSucc
、TmPred
、TmIsZero
は1ステップの中で引数(TmIf
は第一引数)をもう一度評価するので、そこが評価ができないと全体を返すっぽいぽいぽい!!
今回はここまで。 最後にみなさんご一緒に。
Haskellと夕立はいいぞ。
elixir1.3で入りそうな機能
こんにちは。最近haskellの書き方を忘れてきたtechno-tanoCです。
「elixirのwhen
ってEitherっぽいよなーでも失敗した瞬間失敗したものが返ってくるから使いづらいなーもう自分でマクロ作ろうかなー」と思いながらネットの海を漁っていたら(海だけに)、when
にelse
節が入りそうなコミットを見つけました。
そのコミットのリンクはこちら
issueを見た感じ、「マッチに成功した時は良い感じに平坦化してくれるんだけど、エラーケースではイケてない」ってことらしいです。禿同。
具体的には
with {:ok, res} <- 41, do: res, else: ({:error, error} -> error; res -> res + 1) #=> 42
みたいになるようです。
加えてこのコミットでwith
でガードが使えるようになるようです。やったね。
こっちは
with x when x < 2 <- 4, do: :ok #=> 4
のようになるみたいです。
なぜ初心者にHaskellのファンクターは怖いと言われるのか(翻訳)
英語の勉強のためにWhy say the design of functor in Haskell is terrible for NEWBIE | neutronestを翻訳してみました。
以下翻訳
ファンクターって何?
今日私が話したい"ファンクター(functor)"は圏論(category theory)の概念ではなく、Haskellの中心的機能と言語の特性です。実はHaskellのファンクターのデザインは数学のコンセプトを起源にしています。最初にファンクターの定義の詳細を見なおしてみましょう。
class Functor f where fmap :: (a -> b) -> f a -> f b
このコードで、(a -> b)
という型シグネチャはa
という型の値を適用するとb
という型の値が返ってくる関数を意味します。ここでf
は関手の構造(functorial structure)(気をつけて!このf
は(a->b)
のような関数ではありません)です。従って、f a
は引数a
をf
に与えたという意味ではありませんが、型a
のものをf
という構造に入れたことを表します。
f
が困惑させる
今のところHaskell初心者には全く問題ないでしょう。私達マヌケな人間(そう、もちろん私も含めて)はHaskellの新しい重要な機能を理解することに大喜びします。続けてインスタンス実装です。例えば、リストは
instance Functor [] where fmap f [] = [] fmap f (x:xs) = (f x) ++ (fmap f xs)
ええと、待って、待って。ここのf
はどういう意味?他の関手の構造?もちろん違います。なぜなら[]
はファンクターの定義と対応しているf
なのでf
は(a -> b)
であるという型情報を私達は知っているからです。
多分誰かがこの違いはそんなに重要ではないのでファンクターの実装の型シグネチャをチェックするときだけ気をつければよいと言うかもしれません。OK、それらを一緒に混ぜた私達Haskell初心者をとても混乱させる例を見せましょう。
Prelude> let rmp = const 'p' Prelude> :t rmp rmp :: b -> Char Prelude> let lms = [Just "123", Nothing, Just "abc"] Prelude> :t lms lms :: [Maybe [Char]]
何も問題ないね?いくよ。
Prelude > fmap rmp lms "ppp"
簡単です。Chatのリスト(訳注:Charのタイポ?)を作るためにlms
のそれぞれの要素を'p'
に置き換えただけです。従って"ppp"
になります。
もっと複雑なもの
Prelude> (fmap . fmap) rmp lms [Just 'p',Nothing,Just 'p'] Prelude> :t (fmap . fmap) rmp lms (fmap . fmap) rmp lms :: [Maybe Char] Prelude> :t (fmap. fmap) (fmap. fmap) :: (Functor f, Functor f1) => (a -> b) -> f (f1 a) -> f (f1 b)
ここでは何が起きた?なぜfmap
を加えるとMaybe
型が出てくるのでしょう?(fmap . fmap)
のf (f1 a)
は何を意味しているのでしょうか?入れ子になった二重の関数呼び出し?実はコンパイラはf
とf1
が両方ともファンクターであることを知っています。しかし多くの人、Haskell初心者はf (f1 a)
を2つの関数呼び出しと見做すかもしれません。まずf1 a
を計算し、その値を最終的な結果を得るためにf
に適用するというふうに。
実際、この例は最新のHaskellの本(この本は最新のGHCのたくさんの機能と変更をカバーしていてとても良いです)から来ています。正直に言うと私はマヌケなので自分でファンクターの定義をもう一度書くまでの数日間ここで行き詰まりました。f (f1 a)
は私を困惑させ、入れ子になった関数ではなく入れ子の構造を意味していると理解するまでに時間がかかりました。
ファンクター?関数?
私はファンクターと関数が圏論で高いレベルで抽象化されて同じものになっているかどうか知りません。みんなが圏論で武装した研究者ではないのです。もしHaskell研究者が「ファンクターはほにゃららな構造で〜」と2つは別物であると決めても、f
は別物であるとしない方が良いです。
ファンクターへの個人的な変更
私がこの例にしばらく詰まったあと、本の関連するセクションを読み直してもっと簡単に理解するためにファンクターを書き直しました。
class Structor s where smap :: (a -> b) -> s a -> s b instance Structor [] where smap f [] = [] smap f (x:xs) = (f x)++(fmap f xs)
例を再検査して型情報を見る。
Prelude> (smap . smap) rmp lms [Just 'p',Nothing,Just 'p'] Prelude> :t (smap . smap) rmp lms (smap . smap) rmp lms :: [Maybe Char] Prelude> :t (smap. smap) (smap. smap) :: (Functor s, Functor s1) => (a -> b) -> s (s1 a) -> s (s1 b)
Functor
をStructor
に、fmap
をsmap
に、f
をs
(sはstructureの意味)に変えた以外何もしていません。これでs
とf
に惑わされることはありません。その上、s (s1 a)
を見た時にすぐに入れ子の構造であると分かります。
まとめ
今回私が話した問題はちっぽけです(ただ名前を選んだだけ)。数学の概念とそれに関連するHaskellの機能を持ち込むことは時には不必要です。初心者が理解することの障害になります。熟練したHaskellプログラマははっきりと分かっているのでこの問題を放置するかもしれません。モナドでも同様です。知識はないけどやる気のある人に困難を作らないでください。
翻訳ここまで
途中で「二重の関数呼び出しじゃなくて二重の構造」って言ってるのに、直後で「その2つを別物と思わないほうが良い」って書いてあるような気がするんですがどういうことでしょう。
精巣捻転になりました
こんにちはtechno_tanoCです。
学校の定期試験がひとまず終わって、追試に向けて勉強をしなくてはと思いながらも怠惰な性格なもので勉強せずに部屋でゴロゴロしていたのですが、精巣捻転になりました。治療を受けたところすっかり良くなって、今のところ痛みは全くないです。
事の発端は今日の13時頃、トイレに行ったあともうちょっとゴロゴロしようと布団に潜り込んだ時に腹部に違和感。最初は「お腹の調子が悪いのかなー」ぐらいに考えていたのですが、いくら経っても全く治まる気配がありません。20分ほど痛みに悶ていたのですが、そこでふと気づきました。「そういえばこの痛み、金的を食らった時と似ている」。男性であれば一度は体験したことのあるあの痛みです。睾丸からお腹に響いてくるあの重くも鋭い痛みです。私の場合、睾丸そのものよりもお腹の痛みが大きく気づくのが遅くなりましたが確かに睾丸の方も痛く、睾丸が動くと痛みも激しくなる。陰嚢に触ってみると右の睾丸が腫れていました。まずすることはもちろんググることです。「睾丸 腫れ」と検索をかけたところ、「精巣腫瘍(睾丸腫瘍)」「精巣炎」「 精巣捻転」「6~8時間以内に処置しないと壊死する」などの怖そうな文章がズラズラ出てきてやっぱりヤバそうだと確信しました。その時は精巣捻転だと思っていたわけではないのですが、「もし精巣捻転だったら早く処置しないとヤバい」という情報を見て放置してはいけないと思いました。
痛みで若干朦朧としながら、症状と一緒に調べておいた最寄りの泌尿器科に行きます。この時が大体14時だったので発症から1時間ですかね。徒歩1分の場所に泌尿器科の診療所があってラッキーでした。これなら痛みを我慢して歩いていけそうです。
が、診療所の前まで行ってみると入り口が閉ざされており、よく見ると今日の診療時間は15時からだそうです。痛いお腹を抱えてグルグル考えた結果、「別の泌尿器科で最も近いものは電車でひと駅、そこまで辿り着く自信がない。もう一時間待ってから来よう」となり、とぼとぼ家に帰りました。
家に帰っても痛みは全く引くことはなく、布団のなかで悶え続けていました。金的の痛みがずっと続くことを想像して頂ければ良いと思います。すごく辛いです。今朝見たスライムを引き伸ばしたり引き千切ったりする夢を思い出しながら「俺が何をしたっていうんだ・・・!」とか考えていました。この一時間は本当に長かったです。
そんな感じでなんとか1時間痛みに耐え、再度診療所に向かいました。若干ふらついていたと思います。
診療所に入り、問診票を受け取ったのですが、歩いたせいか症状が悪化し冷や汗が止まらなく意識も遠のいて問診票を書くことができませんでした。それを見た診療所の方にすぐに診察室に運び込まれ、処置を受けました。
診察台に寝かされ、医師が来るのを待っている最中、離れたところから看護師の方の「捻転?」という声が聞こえて怖かったです。横になったら少し楽になって冷や汗も引きました。その時に測った血圧は92/70、体温は36.2℃だったと思います。目眩で目がよく見えなかったのですが、手も蒼白になっているらしい。
本来受付が15時で治療は15時半からだったのですが、急を要すると判断されたのかすぐに医師の先生がいらっしゃいました。やはり精巣捻転だったらしく、処置が始まりました。ネットで精巣捻転の項目を見た時に「切開して精索の捻れを戻す」とか書いてあったので、「あぁ手術するのか・・・」と覚悟していましたが、実際に行われた処置は違いました。
簡単に言うと「直接玉を引っ張って捻じれを直す」。ただでさえ痛い睾丸を引っ張られました。ものすごく痛かったです。変な声出ました。なんだこのダイナミックチンポジ直し。
そんな感じに数分間引っ張られた結果、医師の先生曰く「腫れも引いた」とのこと。腫れは捻転したせいで血流が途絶えたのが原因だったらしいです。そしてあの痛みはなんだったのかというほど痛みはなくなり、数分後には看護師の方に貰った蜂蜜飴を舐めながら横になっていました。あと最後にエコーを撮られました。
しばらく様子を見て、もう大丈夫そうだと判断されたので処方箋を貰って退院(?)。動いても痛みはなく、妙に感動しました。処方箋に書かれたロキソプロフェンナトリウムの文字を見て「これ学校でやったやつだ!」とか思う余裕があるほど無痛です。薬局で薬を貰い、「また発症したらどうしよう」と若干不安になりながら帰宅。今に至ります。
体験レポートとしてはこんな感じでしょうか。
まとめというほどではないですが、身体に痛いところがないって素晴らしいですね。みなさんも身体に不調があればすぐにお近くの医療機関に行くことをお勧めします。私の場合、あのまま放置していたらと思うと恐ろしいです。
WordPressで作られたサイトにログインする
やりたいことはタイトルそのまま。haskellで書く。
使うパッケージはhttp-contuid 2.1.8とutf8-string 1.0.1.1(stackのlts-3.14)
{-# LANGUAGE OverloadedStrings #-} import qualified Data.ByteString.Lazy.UTF8 as U import Network.HTTP.Conduit ap = [("account", "name"), ("pwd", "password")] main = do man <- newManager tlsManagerSettings log' <- parseUrl "http://piyopiyo/wp-login.php" let log= urlEncodedBody ap log' cookies <- httpLbs log man >>= return . responseCookieJar req' <- parseUrl "http://piyopiyo/target.html" let req = req' { cookieJar = Just cookies } httpLbs req man >>= return . U.toString . responseBody >>= putStrLn
main
の前半でセッション情報を含んだCookieを取得して、後半でそれを使ってログインしていないと見れないページを取得。
requestBody
にログイン情報を乗せて失敗してハマった。urlEncodedBody
を使うのがポイント。
「関数型」というジャンル
多くのプログラミング言語で関数型プログラミング要素が取り込まれるようになって久しいです。
- Java8でラムダ式が導入された時は大きな話題になりました。
- C#では2.0でジェネリクスが使えるようになり、3.0ではラムダ式と型推論(のようなもの)も使えるようになりました。
- ClojureやScalaでは基本的にイミュータブルな値を使うコーディングが推奨され、関数合成も使います。時には遅延評価も使います。
- Rubyのブロックは実質、高階関数ですね。
- Shemeでは末尾再帰最適化が言語仕様として要求されます。
一部、関数型と言うにはやや強引なものもありますがラムダ式、ジェネリクス、型推論、イミュータブル、関数合成、遅延評価、高階関数、末尾再帰最適化、モナドなど関数型プログラミングには多くの側面があります。
今回は「関数型とは何か」のような話はしません。関数型というある意味バズワードとなってしまった言葉への一つの考え方を話します。
突然ですが私はEDMやハードコアミュージックが好きです。techno-tanoCというハンドルネームもHARDCORE TANO*Cが元ネタです。ハードコアテクノと一口に言ってもその分類はたくさんあり、wikiには現在12種類の分類が載っていますがこれもハードコアテクノの分類の一部に過ぎません。MainStreamGabbaやBrostepなどのようにここ数年生まれたような音楽ジャンルもあります。ジャンルの移り変わりの速さや新しいジャンルの発生の多様性はIT業界と同じかそれ以上と思います。
そんな音楽の世界でジャンルとは何なのでしょうか?BPS(beat per second)や使う音の傾向で分類されることが多いですが、これから外れる曲も多いです。一番無難な分類法は作曲者による宣言でしょう。youtubeやニコニコ動画に上げられる多くの曲にはタイトルにジャンルが書かれていることが多く、「この曲は作曲者がこのジャンルだと言っているからこのジャンルだ」と考えることです。もちろん聞く人によって「この曲はこのジャンルの方が適切ではないか」ということはあると思いますし、私も時々「この曲は自分の思うジャンルの傾向と違う」と思うことはありますがそれを批難する人は原理主義者ぐらいでしょう。
閑話休題、「関数型」というのもこれに似たものではないでしょうか? 先ほど挙げた特徴を以って言語を「関数型」と分類することは簡単です。しかし「関数型」には明確な定義がありません。従って個々人の思う「関数型」には差異があります。これは言語設計者にも言えることです。「関数型」言語あるいは「関数型」の機能を持った言語は言語設計者の思う「関数型」を体現したものです。
時々「その定義だと○○という言語は関数型ではなくなる」「その定義だと△△という言語まで関数型言語になってしまう」という言葉を見かけますが、それはその言語設計者が思う「関数型」との差異があったというだけで、言語設計者を絶対視した原理主義者の言葉に他なりません。
「関数型」という単語は一定のコンセンサスを以って用いるには便利ですが、定義がない以上乱用は混乱を招きます(言語設計者も例外ではありません)。誰かを絶対視した原理主義者にはなりたくないものです。
Rubyで正規表現を使った抽出
rubyって楽で良いですよね。今日もその良さを感じたのでお裾分け。
最初にマッチした部分を取得したいことってよくありますよね。
文字列の中からfooの後に連なった最初の数字を取得したいとします。冗長に書くとこんな感じですかね?
if match = "foo123bar456".match(/foo(\d+)/) digit = match[1] end
String#matchを使ってもString#=~を使ってもどっちみち$1などは作られるのでさっさと=~と$1を使ったコードにしましょう。マッチを行う場所とマッチした部分を使う場所が遠いでもない限りあまり問題にはならないことが多いでしょう。でも良い子のみんなは時と場合を選びましょう。
if "foo123bar456" =~ /foo(\d+)/ digit = $1 end
こんなコード書こうものなら熟練rubyistからのマサカリが飛んできそうです。すばやく後置ifに書き換えます。
digit = $1 if "foo123bar456" =~ /foo(\d+)/
これでifの外からでもdigitが使えますね。これで熟練rubyistもニッコリです。
いやいや、コードを左から読んでいると真っ先に$1という部分が来て見た目がなんかアレです。
digit = "foo123bar456" =~ /foo(\d+)/ && $1
はい。みんな大好き&&
さんです。後置ifとどちらを使うかは時と場合を選んでください(二度目)。ruby使い以外からしたらトリッキーなコードですね。
え?マッチしなかった時はどうする?一行でif-then-end
書いた方がマシ?
みんな違ってみんな良いということですね!!!
寒いノリでくだらないネタ書いたことを反省している。夜のテンション怖い。
Haskellで正規表現
- 最初にマッチした部分を取得
firstCapture :: String -> String -> String firstCapture source pattern = head . mrSubList $ source =~ pattern firstCapture "abcdefg" =~ "a(\\w\\w)d\\w\\w) == "bc"
matchResultでは最初にマッチしたものだけを補足するので、[[String]]
でマッチさせて、探した方が良いかも。
括弧が1つならhead . head $ source =~ pattern
- パターン中の特殊文字
"123abc" =~ "\\d\\d\\d\\w\\w\\w"
\\
を付ける。
Tera TermでDockerにattachしたら面白かった(小並感
Tera Termを複数開いて、同じサーバにログイン後、同じコンテナにattachするとコンソールが同期する。
それだけ。