😇

[PureScript] Rebindable do で遊ぶ

3 min read

半分ネタ記事です.

Rebindable do

とは……

https://jordanmartinez.github.io/purescript-jordans-reference-site/content/11-Syntax/06-Modifying-Do-Ado-Syntax-Sugar/index.html

bind(とdiscard)を定義すればそれを使ってくれるものです.面白いですね.遊んでみます.

遊ぶ

そのままdo

identityDo :: Int
identityDo = do
  x <- 1
  y <- 2
  x + y
  where
  bind = (#)

パイプライン関数(#)は,型を見てみるとMonadbindと似ていることが分かります.

-- Monadにおけるbind
bind :: forall m a b. Monad m => m a -> (a -> m b) -> m b
-- パイプライン演算子#
(#) :: forall a b. a -> (a -> b) -> b

bind(#)を使ってあげることで,Monadを使わないdo記法が完成します.上の例はx1を,y2を代入して足してるだけです.なんじゃこりゃ.

もしかしたらMonadは使わないけど,順序が大事な計算をしたい!ってときには使えるかもしれませんね.

湯婆婆do

yubabaDo :: String
yubabaDo = do
  x <- "千"
  y <- "尋"
  x <> y
  where
  bind :: forall a b. a -> (a -> b) -> a
  bind = const

実行結果: "千"

湯婆婆なので1行目しか実行してくれません.実用性ナシ

カウンターdo

data Natural

foreign import data Zero :: Natural
foreign import data Succ :: Natural -> Natural

data Counter (n :: Natural) a = Counter a

class Add (a :: Natural) (b :: Natural) (c :: Natural) | a b -> c

instance Add Zero b b
instance Add a (Succ b) c => Add (Succ a) b c

type One = Succ Zero
type Two = Succ One
type Three = Succ Two
type Four = Succ Three

counterDo :: Counter Three Int
counterDo = do
  x <- pure 1
  y <- pure 2
  pure $ x + y
  where
  bind
    :: forall a b x y z
     . Add x y z
    => Counter y a
    -> (a -> Counter x b)
    -> Counter z b
  bind (Counter a) f =
    let
      Counter b = f a
    in
      Counter b

  pure :: forall a. a -> Counter One a
  pure = Counter

bindする回数を指定できます.わかりにくいので一部だけ抜き取ります.

counterDo :: Counter Three Int
counterDo = do
  x <- pure 1
  y <- pure 2
  pure $ x + y

次のように書くと型エラーが起きます.

counterDo :: Counter Three Int
counterDo = do
  x <- pure 1
  y <- pure 2
  z <- pure 3
  pure $ x + y + z

こうすれば大丈夫です.

counterDo :: Counter Four Int
counterDo = do
  x <- pure 1
  y <- pure 2
  z <- pure 3
  pure $ x + y + z

つまり,Counter x axの部分でbindする回数を絞り込めるんですね!

実用性は……ナシ

(副作用のあるMonadと組み合わせれば色々面白いことができるかもしれない)

終わりdo

data IsEnd

foreign import data Continue :: IsEnd
foreign import data End :: IsEnd

data Cancel (e :: IsEnd) a = Cancel a

end :: forall a. a -> Cancel End a
end = Cancel

cancelDo :: Cancel End Int
cancelDo = do
  x <- pure 1
  y <- pure 2
  z <- pure 3
  end $ x + y + z
  where
  bind :: forall a b x. Cancel Continue a -> (a -> Cancel x b) -> Cancel x b
  bind (Cancel a) f =
    let
      Cancel b = f a
    in
      Cancel b

  pure :: forall a. a -> Cancel Continue a
  pure = Cancel
  discard = bind

重要なところだけ抜き出します.

cancelDo = do
  x <- pure 1
  y <- pure 2
  z <- pure 3
  end $ x + y + z

最後の行にendが書かれているのでこれ以上の式の追加はできません.

cancelDo = do
  x <- pure 1
  y <- pure 2
  z <- pure 3
  end $ x + y + z
  pure 4 -- エラー!

下のようにendの代わりにpureを用いれば大丈夫です.

cancelDo = do
  x <- pure 1
  y <- pure 2
  z <- pure 3
  pure $ x + y + z
  pure 4 -- OK!

これで終わらせるぞという意思を表明したいときに使えます.

end

Discussion

ログインするとコメントできます