LoginSignup
2
1

More than 5 years have passed since last update.

「GenerarizedNewTypeDerivingで自分用Monadを作る」ってなに!?(具体例編)

Last updated at Posted at 2017-03-31

この記事の目的

 現在おそらくまだ、主流なMonadインスタンスの作り方である、GenerarizedNewTypeDerivingを用いた方法の理解の手助け。
軽く検索した限り、本記事タイトルについての具体例が(わかりやすさ特化な例はありましたが、具体特化の例は)見つからなかったので、紹介してみます。

あと、Monadを作ることの楽しさの布教。

 GenerarizedNewTypeDeriving拡張を学ぶ筋道と、実例を示します!

そもそもGenerarizedNewTypeDerivingとは?

 Haskellのnewtypeは、元となる型と(圏論的な)同型を作るキーワードです。
(値構築子と、パターンマッチの解凍 もしくは レコードの関数、を同型射とした同型。間違ってたら教えてください ><)

つまりは「元となる型ができることはnewtypeで作る型もできる」ってことなのですが、newtypeがstrong typedefであるが故に、型クラスの情報をnewtypeは忘れ去ってしまいます。
その情報を思い出させるのがGenerarizedNewTypeDeriving拡張です!

 具体的には、以下を可能にします。

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}

import Data.String (IsString)
import Data.Text (Text)
import qualified Data.Text.IO as TIO

newtype MyString = MyString { unMyString :: Text }
  deriving (IsString)  -- GenerarizedNewTypeDerivingがないと、ここを書けない

txt :: MyString
txt = "アホ毛"

main :: IO ()
main = TIO.putStrLn . unMyString $ txt

実際どのように、自分用Monadを作るのか?

 これについては、こちらがわかりやすいかなと思います。

具体例

 さて本題ですが、今回僕が作ったものは
ログと、構文上のブロックのネスト数という状態を保持した、不純なパーサです。

実際使っているプロジェクトはここにあります(ただしWIP)

まあ…不純なパーサは個人的には好ましくないとは思いますが……。

ここは言い訳なので読まないで良いんですが
なぜ良くないと思うのにParsecTをMonadStateとして使ったのかというと、パースログを取りたかったんですよね。
でもmegaparsecのParsecTはMonadWriterではなかったので、MonadStateを使おうということに決めました。 苦し紛れ。
そしたらやっぱり、状態も使いたいじゃないですか。 めっちゃ見やすくしたくなった。
ということで使いました……。
実際はログへのみの副作用として状態が使われています。
状態を読み出してパース結果に影響を与えたりしてないので、セーフセーフ!!

-- | Current state of ElinParser
data ElinState = ElinState
  { _parseLogs      :: [ParseLog]
  , _parseNestLevel :: Int
  } deriving (Show)

-- | Parser with parsing logs
newtype ElinParser a = ElinParser { _runElinParser :: ParsecT Dec Text (State ElinState) a }
  deriving ( Functor, Applicative, Monad
           , Alternative, MonadPlus
           , MonadState ElinState, MonadParsec Dec Text
           )

-- | A log of ElinParser
data ParseLog = Message Text     -- ^ Simple message
              | ParsedItem Text  -- ^ Ex: '(', ''', ')' or some symbol
  deriving (Show)

-- | Run parser and extract result and logs
runElinParser :: ElinParser a -> Text -> (Either (ParseError Token Dec) a, [ParseLog])
runElinParser parser source =
  let initialState = ElinState [] 0
      bareness     = _runElinParser parser
      (result, ElinState logs _) = flip runState initialState $ runParserT bareness "" source
  in (result, reverse logs)

ElinParser a

  • ElinStateという状態を持ち
  • Textを食べる

というパーサです。

ElinStateは後にmakeLensesされます。

おわり

 Operationalモナドも使ってみたいんですが、まだ使ったことないんですよね〜。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1