(相互)再帰 in full-sessions

忘れた頃に full-sessions の開発をやっている。
これまでセッション型の再帰を扱いづらかったので、もっとイージーに使えるようにした。

unwind という関数で、再帰の開始点を表すことにした*1。 例えば、

{-# LANGUAGE NoMonomorphismRestriction #-}

import FullSession

-- 単純な再帰プロセス
simple c = 
    unwind c >>>
    send c "abc" >>>
    simple c

では、チャネルc の使用が再帰的になっていて、これを Haskellの型でどう表現するかという問題がある。
開発中のfull-sessionsでは、simpleの型は

*Main> :t typechekc1 simple
typecheck1 simple
  :: (SList ss', SList ss) =>
     Session t (ss :> Rec Z (Send [Char] (Var Z))) ss' a

となる*2。 チャネル c のセッション型は Rec Z (Send [Char] (Var Z)) という部分だが、これはちょうど νx. Send [Char] x というセッション型を表現している。 このν記法(Haskellにはない)で、νx.f(x) は x=f(x) の最大不動点を表している。
むろん Haskellの型は structuralではないので Rec Z (Send [Char] (Var Z)) と Send [Char] (Rec Z (Send [String] (Var Z)) という型は別の型として区別され、互換性はない。 -rectypesが欲しいけれど、まあしょうがない。

相互再帰もうまくいった。

-- 相互再帰するセッションを含むプロセス
mutual c = 
    unwind c >>> 
    send c "abc" >>> 
    let y = unwind c >>> 
            send c True >>> 
            offer c (mutual c) y 
     in y

はチャネルcの使われ方が相互再帰的になっているが、

*Main> :t typecheck1 mutual
typecheck1 mutual
  :: (SList ss', SList ss) =>
     Session
       t
       (ss
        :> Rec
             Z
             (Send [Char] (Rec (S Z) (Send Bool (Offer (Var Z) (Var (S Z)))))))
       ss'
       a
*Main> 

のように型が推論される(やったね!)。 このプロセスのチャネル c のセッション型は Rec Z (Send [Char] (Rec (S Z) (Send Bool (Offer (Var Z) (Var (S Z))))))) で、ν記法を使うと νx. Send String (νy. Send Bool (Offer x y)) となる。
相互再帰を扱うために型変数を区別する必要があったので 型変数の比較 で触ったときのテクニックを使った。 このため (元々 GHCでしか動作しないコードだったけれど、さらに) -XIncoherentInstances が必要になっている。

*1:名前の由来は、νx.f(x)をf(x){νx.f(x)/x}に展開するから

*2:typecheck1は型推論で型をすっきりさせるためのヘルパー関数, SListはセッション型のリストを表す型クラス