Hello, polyglot!

きっかけ: るびま これを読んでpolyglotするかーって思った.
今回のコンセプトは

  • 初めてなのでやさしくHello, world!
  • 好きな言語を盛り込む
  • 140文字以下

以下,Schemeと言いつつGauche決め打ちである.

ソースコード

できあがったのがこれ.

;(#)={-"^<"v
'v+55p44p41<
(define puts print)
;')#>:#,_@-}0;puts=putStrLn;main=
 (puts"Hello, world!")

SchemeRubyHaskellとBefungeで動く.102打.

言語紹介

Scheme

S式と呼ばれる単純な文法で記述する言語.括弧で括ると何かが起きる.行コメント ; ネスト可能なブロックコメント #| ... |# に加えて式を一つコメントアウトする式コメント #; expr がある.
" は文字列リテラルに使われるが, ' はquoteといって続く式は評価されずリテラルになる.'foo はシンボルへ,'(foo bar baz) はリストへと読まれる.
いろんな文字が識別子に使えるのも特徴.+ - < > など.

Ruby

独特の複雑な文法で記述する,改行が意味を持つタイプの言語.あちこちで括弧を省略できる.# で行コメントはできるが,複数行コメントはちょっと使い辛い.
"' はどちらも文字列リテラルに使われる.似たようなものに / で開始する正規表現リテラルがある他,%!...!%q!...! (! は任意の非英数字)等の多様なリテラルを持つ.

Haskell

インデントが意味を持つタイプの言語.トップレベルには(多分)宣言と定義しか書けないので,今回使用した言語の中では最も文法上の制約が強い.行コメントは -- ネスト可能なブロックコメントは {- ... -} でできる.
静的型付き言語なので,型の整合性も取らなければならない.

Befunge

1文字1命令で,実行の向きを変えながら二次元格子状のコードを実行していく難解言語.コメントのための機能は無いが,意味のある命令以外は無視される.
スタック型.小さい整数を扱う事ができる.実行中のコードを読み書きできる.

Scheme着色版

;(#)={-"^<"v
'v+55p44p41<
(define puts print)
;')#>:#,_@-}0;puts=putStrLn;main=
 (puts"Hello, world!")

Ruby着色版

;(#)={-"^<"v
'v+55p44p41<
(define puts print)
;')#>:#,_@-}0;puts=putStrLn;main=
 (puts"Hello, world!")

Haskell着色版

;(#)={-"^<"v
'v+55p44p41<
(define puts print)
;')#>:#,_@-}0;puts=putStrLn;main=
 (puts"Hello, world!")

Befunge着色版



;(#)={-"^<"v
'v+55p44p41<
(define puts print)
;')#>:#,_@-}0;puts=putStrLn;main=
(puts"Hello, world!")

解説

1行目

SchemeコメントアウトHaskellでは演算子 (#) の定義の途中から4行目の途中までコメントアウト.これによってRubyも開き括弧だけしてコメントアウト.Befungeでは前半は無意味なコードで,後半にコード書き換え用の文字をスタックに積んで下へ.

2行目

Schemeでは quote により離脱.同時にRubyは4行目の頭まで文字列リテラルにより離脱.Befungeではここを右から実行してきて,5行目の文字列を読むための命令を5行目に書き込む.

3行目

Schemeでputsを定義.Rubyは文字列リテラル中,Haskellはコメント中,Befungeは素通りでいずれも影響無し.

4行目

SchemeコメントアウトRubyはここで文字列リテラルから復帰して括弧を閉じ,あとはコメント.Befungeではここ >:#,_ が出力部になっていて,スタックに積まれた文字列を出力し終了するようになっている.後半はHaskellがコメントから復帰し,(#) の定義を終えて putsを定義,main=を置いておく.

5行目

SchemeRubyHaskellはここで出力.Befungeはコード書き換えにより

;(#)={-"^<"v
'v+55p44p41<
(define puts print)
;')#>:#,_@-}0;puts=putStrLn;main=
 <pu^s"Hello, world!")

となっていて,ここで出力する文字列をスタックに積む.そして4行目の出力部へ行き,出力して終了.

まとめ

文字列リテラル部が共有できてていい感じ.最初はもっとややこしそうなコードを書いていたつもりだったのだけど,縮めていくうちにシンプルになっていった.単純は短し.
文法的にはHaskellの制約が強い一方,ショートコーディングとしてはBefungeの性格が強く出た.

没コード集

その1
;{->{55+48+v
(define putStr display)
'|#:\g5+9: <-1_'
}=>0};alias putStr puts
#>:#,_@|;-}main=
 (putStr"Hello, world!\n")

121打.(define putStr display)Rubyでも文法上は妥当なことを利用し,ラムダ式 ->{ ... } をコメント的に利用したコード.Rubyでは括弧を補うと (define(putStr(display()))) と解釈される.実行されなければどうということはない.1行目の 55+48+v はBefungeのためのコードだが,2行目と同様にRubyでも文法OKなので書ける.
Scheme|...| が一つのシンボルになることを利用した式コメントが気持ちいい.
出力関数の名前はHaskellputStr に合わせている.
Befungeは文字列リテラル部分をg命令で読み取るようになっていて,長さが埋め込みなのがちょっと残念.

その2
eval'=eval'&&'p'
{-0=>(alias putStr puts)}
">   55+48+v
|#:\g6++49:<-1_
>:#,_@"#t(define putStr display)'
;#-}where _&&x=x
main=(putStr"Hello, world!\n")

153打.evalSchemeでもRubyでも定義されていて,Befungeが下に脱出できるのを利用したコード.Befungeの変なコードなんか全部文字列にしちゃえば簡単じゃないかというコンセプト.しかし長くなりすぎた.似たような方法に format を使う手がある.

その3
;{->{55+48+v
(define puts print)
 '|#:\g4+7:<-1_'
}=>':#,_@|;'}#-}puts=putStrLn;main=
 (puts"Hello, world!")

108打.その1の改良版.出力関数の名前はRubyputs に合わせている.

その4
;{->{"2^<"^
(define puts print)
'|0/5p42p4<'
}=>':#,_@|1;'}#-}puts=putStrLn;main=
 (puts"Hello, world!")

104打.その3の改良版.Befungeのアプローチを変えて,文字列の長さが埋め込みでなくなった.

Racketの継続プリミティヴ

Racketの継続がどうなっているのか,Racketのドキュメントを読みつつ分かったことをまとめる.
(注意: この記事は継続の解説ではない.寧ろ継続自体は大体分かっている前提で進める)
まず,主な継続プリミティヴについて簡単に説明する.次に,組み合わせたときの動作を例を出しつつ示す.さらに,これらの継続プリミティヴを使って shift / reset や他の演算子が実装できることを確認する.

Racketの主な継続プリミティヴ

(call-with-continuation-prompt proc [prompt-tag handler] arg ...)

略称 call/prompt

継続に区切り(継続プロンプト)を付けて proc を呼ぶ.区切られた継続の部分を継続フレームと呼ぶ(多分).継続プロンプトは prompt-tag で識別される.

reset はこれのラッパマクロである.

handler は,後述の abort/cc によってこの call/prompt 呼び出しへ脱出してきた時に呼び出される.handler には abort/cc に渡した残りの引数が渡される.handler のデフォルト値は,(lambda (abort-thunk) (call-with-continuation-prompt abort-thunk prompt-tag #f)) である.

残りの引数は単に proc に渡される.

(abort-current-continuation prompt-tag v ...)

略称 abort/cc

現在の継続を破棄する.破棄されるのは,prompt-tag に対応する直近の継続プロンプトまでである.残りの引数は call/prompt のハンドラへと渡される.

(make-continuation-prompt-tag [sym])

新しいプロンプトタグを作る.

(default-continuation-prompt-tag)

プロンプトタグを指定しない時に使われるデフォルトプロンプトタグを得る.

(call-with-current-continuation proc [prompt-tag])

略称 call/cc

直近の継続プロンプトまでの継続を取り出す.この継続を起動すると現在の継続の一部を破棄する: 起動しようとする継続と現在の継続で継続フレームを共有している場合は,そのような最初の継続フレームまでが破棄される.さもなくば,直近の継続プロンプトまでの継続が破棄される.

(call-with-composable-continuation proc [prompt-tag])

略称 call/comp

直近の継続プロンプトまでの継続を取り出す.この継続を起動しても現在の継続は破棄されない.

(continuation-prompt-available? prompt-tag [cont])

((省略時は)現在の)継続において prompt-tag で識別される継続プロンプトが有効なら #t ,さもなくば #f を返す.

(dynamic-wind before body after)

基本的にR5RSのと同じだが,継続プロンプトによって継続を区切り,切り取ることができるので,ちょっと様子が異なる.dynamic-wind による動的ハンドラは,継続フレームにくっついている(多分)ので,継続プロンプトによって区切られ,call/cc 等で捕捉される継続に含まれない継続フレームに相当する部分の動的ハンドラは保存されない.

Racketの継続プリミティヴの相互作用

call/prompt + call/comp
(cons 'a (reset (cons 'b (call/comp (lambda (k) (cons 'c (k '()))))))) ; => (a b c b)

call/comp の呼び出しから reset (即ち call/prompt) に戻るまでの継続に k が束縛される.

call/prompt + call/cc

call/comp と同様に call/cc の呼び出しから reset に戻るまでの継続を捕捉するのだけど,この継続は起動したときに現在の継続の一部を破棄する性質を持つ.

(cons 'a (reset (cons 'b (call/cc (lambda (k) (cons 'c (k '()))))))) ; => (a b)

k を起動すると,まず現在の継続のうち reset までが破棄される.つまり (cons 'b ... (cons 'c <>) ...) の部分が破棄される.そして (cons 'a (reset <>)) の部分は残る.次に k に保存されていた継続を復元し,継続は (cons 'a (reset (cons 'b <>))) となる.
この場合は,継続フレームが共有されているので,そのような最初の継続フレームまでが破棄されるパターンである.
以下のように,継続の起動を別の継続フレームにしても,根元の継続フレームが共有されているので同じ結果になる.

(cons 'a (reset (cons 'b (call/cc (lambda (k) (cons 'c (reset (k '())))))))) ; => (a b)

次の例は,継続フレームを共有していないパターン.

(let ((k #f))
  (cons 'a (reset (cons 'b (call/cc (lambda (k1) (set! k k1) '())))))
  (cons 'c (reset (cons 'd (k '()))))) ; => (c b)

k には (cons 'b <>) の部分しか含まれていないので,この例は継続フレームを共有していないパターンになる.k 起動時の継続からは (cons 'd <>) の部分だけが破棄され,k に保存されている (cons 'b <>) に挿げ替わることになる.

call/prompt + dynamic-wind + x の準備

簡単のために

(define d displayln)
(define (d$ x) (lambda () (d x)))
(define (%dw x thunk) (dynamic-wind (d$ `(,x before)) thunk (d$ `(,x after))))
(define-syntax dw (syntax-rules () ((_ x body ...) (%dw x (lambda () body ...)))))

と定義しておく.
出力例

(dw 0 (d 'body))
;; (0 before)
;; body
;; (0 after)

(d (let/cc return (dw 0 (return 42))))
;; (0 before)
;; (0 after)
;; 42
call/prompt + dynamic-wind + abort/cc

call/prompt のデフォルトのハンドラがサンクを受け取るようになっているのは,(多分) shift 等の限定継続演算子を実装する時に都合がいいから.shift の「脱出してから本体を実行する」という流れが,abort/cc に本体を包んだサンクを渡すことで実現できる.

(reset (dw 0 (abort (d 'foo))))
;; (0 before)
;; foo
;; (0 after)

(reset (dw 0 (abort/cc (default-continuation-prompt-tag)
                       (lambda ()
                         (d 'foo)))))
;; (0 before)
;; (0 after)
;; foo

abort/cc に渡されるサンクは動的環境を巻き戻した後に呼び出されることになる.
abort

(define (abort . args)
  (abort/cc (default-continuation-prompt-tag)
            (lambda ()
              (apply values args))))

と定義できる.

call/prompt + dynamic-wind + call/comp or call/cc
(let ((k #f))
  (dw 0 (reset (d (call/comp (lambda (k1) (set! k k1) 'a)))))
  (dw 1 (k 'b)))
;; (0 before)
;; a
;; (0 after)
;; (1 before)
;; b
;; (1 after)

継続 k に保存される継続フレームには (dw 0 ...) の呼び出しでインストールされた動的ハンドラ(以下 dw0 と呼ぶ)は含まれていない.間で reset によって区切られているからである.よって k は,dw0 の内側で捕捉されたにもかかわらず,呼び出しても dw0 は呼び出されない.
また call/comp によって捕捉された継続の呼び出し時には脱出を伴わない(=現在の継続は破棄されない)ため,(1 after)k の呼び出しから返ってきた後で出力される.

(let ((k #f))
  (dw 0 (reset (d (call/cc (lambda (k1) (set! k k1) 'a)))))
  (reset (dw 1 (k 'b))))
;; (0 before)
;; a
;; (0 after)
;; (1 before)
;; (1 after)
;; b

一方 call/cc で捕捉した継続では,呼び出し時に脱出を伴う(=現在の継続が破棄される)ので,そのタイミングでafter thunkが実行される.

タグ付き限定継続

プロンプトタグを指定した場合,そのプロンプトタグによって識別される継続プロンプト以外の継続プロンプトは存在しないかのように扱われる(はず.でないと未知の継続プロンプトに干渉されることになるし,それを防ぐためのタグだよね).
以下,

(define t1 (make-continuation-prompt-tag 't1))
(define t2 (make-continuation-prompt-tag 't2))

と定義しておいて,

(reset-at t1 (cons 'a (reset-at t2 (cons 'b (call/comp (lambda (k) (k '()))
                                                       t1))))) ; => (a b a b)
(reset-at t1 (cons 'a (reset-at t2 (cons 'b (call/comp (lambda (k) (k '()))
                                                       t2))))) ; => (a b b)

とすると,タグに対応する継続プロンプトまでの継続が捕捉される.

(let ((k #f))
  (reset-at t1 (cons 'a (reset-at t2 (cons 'b (call/cc (lambda (k1) (set! k k1) '())
                                                       t1)))))
  (reset-at t1 (cons 'c (reset-at t2 (cons 'd (k '())))))) ; => (a b)
(let ((k #f))
  (reset-at t1 (cons 'a (reset-at t2 (cons 'b (call/cc (lambda (k1) (set! k k1) '())
                                                       t2)))))
  (reset-at t1 (cons 'c (reset-at t2 (cons 'd (k '())))))) ; => (c b)

call/cc で捕捉した継続を起動したときも同様で,どの継続プロンプトまでの継続を破棄するかはタグで識別される.


dynamic-wind は(恐らく)継続フレームに関連付けられるもので,タグの識別はない.

限定継続演算子を使って他の限定継続演算子を実装する

reset-at + call/cc で call/prompt + abort/cc
(define *current-prompt-alist* (make-parameter '()))

(define (abort/cc tag . args)
  (apply (cdr (assoc tag (*current-prompt-alist*))) args))

(define call/prompt
  (case-lambda
   ((proc tag handler . args)
    (let* ((tag (or tag (default-continuation-prompt-tag)))
           (handler (or handler
                        (lambda (thunk)
                          (call/prompt thunk tag #f)))))
      (reset-at tag
        (call/cc
          (lambda (return)
            (handler
             (call/cc
               (lambda (abort)
                 (parameterize ((*current-prompt-alist*
                                 (cons (cons tag abort) (*current-prompt-alist*))))
                   (return (apply proc args))))
               tag)))
          tag))))
   ((proc tag)
    (call/prompt proc tag #f))
   ((proc)
    (call/prompt proc #f #f))))

call/cc と継続プロンプトを設置するだけの reset-at で,ハンドラ機能のついた call/promptabort/cc が実装できた(はず).ついでに1引数の continuation-prompt-available? もすぐに実装できると思う.call/cc の取り出す継続はその call/cc 自体の継続と同じだけど,隙の生じぬ二段構えで call/prompt のように普通にリターンしてくるのと脱出してくるのを区別できる.


parameterize の所これでいいのかちょっと不安だけど実は大丈夫…?

call/comp + abort/cc で call/cc
(define call/cc
  (case-lambda
   ((proc tag)
    (call/comp
     (lambda (k)
       (define call/cc-tag (make-continuation-prompt-tag 'call/cc))
       (reset-at call/cc-tag
         (proc
          (lambda args
            (if (continuation-prompt-available? call/cc-tag)
                (abort/cc call/cc-tag
                          (lambda ()
                            (apply values args)))
                (abort/cc tag
                          (lambda ()
                            (apply k args))))))))
     tag))
   ((proc)
    (call/cc proc (default-continuation-prompt-tag)))))

call/cc で捕捉された継続を起動すると,その継続に入る前に脱出する.継続の中身自体は call/comp と同じはず.abort/cc を使うと丁度トランポリンできるので,そこで call/comp で捉えた継続を呼び出すようにすればいいみたい.
起動する継続と現在の継続で継続フレームを共有している場合,つまりその call/cc の動的寿命の中にいるときは,その call/cc 呼び出しまで脱出する必要がある.ある継続フレームの動的寿命の中にいるかどうかは,reset-atcontinuation-prompt-available? で判定できる.そして call/cc-tag を作っておいて一先ずそれで脱出する.でその後再び abort/cc を使って(継続フレームを共有していない場合と同様に) (abort/cc tag (lambda () (apply k args))) してもいいんだけど,ここで破棄しようとしている継続は k と同じなので,ここは単に (apply values args) でいい.

call/prompt + call/comp + abort/cc で shift
(define (call-with-shift proc)
  (call/comp
   (lambda (k)
     (abort/cc (default-continuation-prompt-tag)
               (lambda ()
                 (proc (lambda args (reset (apply k args)))))))))

(define-syntax shift
  (syntax-rules ()
    ((_ k e ...)
     (call-with-shift (lambda (k) e ...)))))

shift のやることは,「限定継続を捕捉→脱出→限定継続を継続プロンプトで包んで本体に渡す」なので,call/promptcall/compabort/cc があればすぐできる.
多分包みの reset を抜けば control も同様に実装できる.shift0 / reset0reset0 側でハンドラがトランポリンするときにプロンプトを再設定しないようにすればいいんじゃないかな.control0 / prompt0 も同様.

考察

call/prompt call/comp abort/cc の三つを基本にして call/ccshift 等を構成すると考えるのが素直かなと思った.何故なら,継続フレームについて考えるなら call/cc は複合的な演算子ということになるから.

call/cc しか提供しない世界観では,call/cc は単純な単一の操作を行う手続き(を生成する手続き)なんだけど,継続フレームという概念とともに reset を導入すると,call/cc はより細かいいくつかの手続きに分解できる複合的な演算子に見えるようになる.
(4/8 追記等)

MIT/GNU Scheme 9.1.1

MIT/GNU Scheme - GNU Project - Free Software Foundation
今回は無気力なのとなんかいろいろあってよく分からないのでバイナリを入れた."Compiled on OS X 10.6" だし.x86-64のをダウンロードしてコピーして終わり.


かと思いきやパスにコロンが入ってて PATH が通らないので(よく分からなかった)リンクをはる方向に.と思ってたら丁度そういうサイトをみつけた.
Using MIT-Scheme in MacOS X on the Command Line - Dustin Ingram
リンクして

sudo ln -s /Applications/MIT\:GNU\ Scheme.app/Contents/Resources/mit-scheme /usr/local/bin/mit-scheme

設定ファイルして

export MITSCHEME_LIBRARY_PATH="/Applications/MIT\:GNU\ Scheme.app/Contents/Resources"

動いた.

s id id idに型が付かない件について

s id id idに型が付かない.

ghci> let s f g h = f h $ g h
ghci> :t s id id id

<interactive>:1:5:
    Occurs check: cannot construct the infinite type: a = a -> b
    Probable cause: `id' is applied to too few arguments
    In the second argument of `s', namely `id'
    In the expression: s id id id

フンギャー

gosh> (define (s f g x) ((f x) (g x)))
s
gosh> (s values values values)
#<subr values>

Schemeなら問題なく動くのに!ムキー!
って考えてたらもっと短く,型が付かないコードを見付けた.

ghci> let w x = x x

<interactive>:1:10:
    Occurs check: cannot construct the infinite type: t = t -> t1
    Probable cause: `x' is applied to too many arguments
    In the expression: x x
    In the definition of `w': w x = x x

いや,待てよ.Haskellには魔法の関数 unsafeCoerce があるじゃないか!

ghci> import Unsafe.Coerce
ghci> let w' x = x $ unsafeCoerce x
ghci> (w' id :: a -> a) 42
42

これを使えばさっきの関数も…!

ghci> let s' f g h = f h $ unsafeCoerce $ g h
ghci> :t s'
s' :: (t -> b1 -> b) -> (t -> a) -> t -> b
ghci> (s' id id id :: a -> a) 42
42

動いた!わーい!

何かがおかしい…

ところで s'a -> a に特化した s'' を書こうと思ったのだが

ghci> let s'' f g h = s' f g h :: a -> a
<interactive>:1:16:
    Inferred type is less polymorphic than expected
      Quantified type variable `a' is mentioned in the environment:
        f :: t -> b1 -> a -> a (bound at <interactive>:1:8)
    In the expression: s' f g h :: a -> a
    In the definition of `s''': s'' f g h = s' f g h :: a -> a
ghci> let s'' f g h = s' f g h `asTypeOf` h
ghci> :t s''
s'' :: (t -> b1 -> t) -> (t -> a) -> t -> t
ghci> s'' id id id

<interactive>:1:4:
    Occurs check: cannot construct the infinite type: t = b1 -> t
    Probable cause: `id' is applied to too many arguments
    In the first argument of `s''', namely `id'
    In the expression: s'' id id id
ghci> let s'' :: (a -> a) -> (b -> b) -> (c -> c) -> c -> c; s'' f g h = s' f g h

<interactive>:1:54:
    Occurs check: cannot construct the infinite type: c = c -> c
    When generalising the type(s) for `s'''

分からん…

TAPLを読む

「Types and Programming Languages」(通称TAPL)の訳本,「型システム入門 −プログラミング言語と型の理論−」を読んでいる.読書日記をつけるといいんじゃないかと思い立ったが面倒臭くなった.読み始めは2/4頃.10章まで読んだ.
序文に章の依存関係が載っていて,これを見て目当ての章が依存している章を読んでいくという読み方もあるらしいけど,今回は一先ず全部読むことを目標にする.

c-wrapperのインストール

c-wrapper,c-wrapper は更新されていないけど http://hg.koguro.net/c-wrapper で更新されていた.ダウンロード→タグからダウンロードできる.

./DIST gen
./configure CC=/usr/bin/gcc
make
DYLD_INSERT_LIBRARIES=/usr/lib/libffi.dylib make check

普通にやると (use c-wrapper) でエラーになる.

$ gosh
gosh> (use c-wrapper)
*** ERROR: Compile Error: Compile Error: Compile Error: failed to link /usr/local/lib/gauche-0.9/site/x86_64-apple-darwin10.8.0/c-ffi.so dynamically: dlopen(/usr/local/lib/gauche-0.9/site/x86_64-apple-darwin10.8.0/c-ffi.so, 10): Symbol not found: _ffi_type_double
  Referenced from: /usr/local/lib/gauche-0.9/site/x86_64-apple-darwin10.8.0/c-ffi.so
  Expected in: flat namespace
 in /usr/local/lib/gauche-0.9/site/x86_64-apple-darwin10.8.0/c-ffi.so
"/usr/local/share/gauche-0.9/site/lib/c-wrapper/c-parser.scm":31:(define-module c-wrapper.c-parser (u ...

"/usr/local/share/gauche-0.9/site/lib/c-wrapper.scm":31:(define-module c-wrapper (use srfi-1 ...

"(standard input)":1:(use c-wrapper)

Stack Trace:
_______________________________________
  0  (eval expr env)
        At line 179 of "/usr/local/share/gauche-0.9/0.9.4_pre3/lib/gauche/interactive.scm"

DYLD_INSERT_LIBRARIES=/usr/lib/libffi.dylib をつけると動くので,これが見えていないのが問題らしい.otoolとかinstall_name_toolとかあたってみたがよく分からず,DYLD_INSERT_LIBRARIES を指定しなくてもすむ方法は分からなかった.

$ otool -L /usr/local/lib/gauche-0.9/site/x86_64-apple-darwin10.8.0/c-ffi.so
/usr/local/lib/gauche-0.9/site/x86_64-apple-darwin10.8.0/c-ffi.so:
	/usr/local/lib/libgauche-0.9.dylib (compatibility version 0.0.0, current version 0.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.11)

ここに/usr/lib/libffi.dylibがないといけないのではないかと予想したのだけどなぁ.
それとなんかmakeの最初の方でconfigureみたいなのが走っているのだけど,rm: conftest.dSYM: is a directoryって出てこけまくっているように見える.大丈夫なのだろうか.

型クラスのパラメタ多相?

Either a b(但しaとbは何らかの共通の制約を持つ)に適用できる f g = either g g という関数を書こうとしたが,書けなかった.特定の型クラスに限定すれば書くことができる.

{-# LANGUAGE RankNTypes #-}

fshow :: (Show a, Show b) => (forall c. (Show c) => c -> d) -> Either a b -> d
fshow g = either g g

feq :: (Eq a, Eq b) => (forall c. (Eq c) => c -> d) -> Either a b -> d
feq g = either g g

-- f :: (k a, k b) => (forall c. (k c) => c -> d) -> Either a b -> d
-- f g = either g g
-- => malformed class assertion

g h = print $ map h [Left 42, Right 'a']

main = g (fshow show) >>
       g (feq (\x -> x == x))

ShowEq のところをパラメタにすればよさそうに思ったのだけど,GHCではコンパイルエラーになってしまった.一体どうすれば…