let rec内での多相とlocally abstract typeとpolymorphic type constraints
こういう記事を最近読んだ:
(書かれたのは10年以上前だが)
こういうコードは型チェック通らないよ、という話:
let rec f x = x and g () = f 1 and h () = f true;; Line 3, characters 13-17: Error: This expression has type bool but an expression was expected of type int
let rec ... and ... and ...
という構文の中ではlet多相が効かないということらしい。
let rec ... in ...
ならもちろんlet多相で大丈夫:
let rec f x = x in let g () = f 1 in let h () = f true in (g ()), (h ());; - : int * bool = (1, true)
じゃあ明示的に型をつければ?とナイーブな付け方をしてみると:
let rec f : 'a -> 'a = fun x -> x and g () = f 1 and h () = f true;; Line 3, characters 13-17: Error: This expression has type bool but an expression was expected of type int
これは失敗する。その理由については以前記事を書いた:
のでこういう風には多相を強要できず、let rec ... and ... and ...
が(この構文の中では)単相になってしまう。
locally abstract type
記事を読んで手元で試したときは「はえー」と思ったくらいだったのだが、ここ数日GADTについて調べているときにlocally abstract typeに遭遇した。
ふとこの問題にlocally abstract typeが使えるのでは?と思って試してみた:
let rec f : type a. a -> a = fun x -> x and g () = f 1 and h () = f true;; val f : 'a -> 'a = <fun> val g : unit -> int = <fun> val h : unit -> bool = <fun>
これは通るので成功。locally abstract type、小技として便利そう。
Polymorphic type constraints
ここまで記事を書いてちょっと寝かせておいたら、こんな記事に遭遇した:
The punchline is that OCaml actually does have syntax for expressing polymorphic constraints – and it even involves an explicit quantifier – but sadly it's not often taught to beginners:
let id : 'a. 'a -> 'a = (fun x -> x + 1)
知らなかった・・・
let rec f : 'a. 'a -> 'a = fun x -> x and g () = f 1 and h () = f true;; val f : 'a -> 'a = <fun> val g : unit -> int = <fun> val h : unit -> bool = <fun>
これで問題なくうまくいくようだ・・・
この構文で
let rec ... and ...
などで関数が単相にならないようにする- 関数の実装的に単相的なものを型検査で弾く
の両方ができるらしい。
しかしGADTで:
type 'a t = Int : int -> int t | Char : char -> char t let get : 'a. 'a t -> 'a = function Int x -> x | Char x -> x
とは書けない。ここの違いに関してはまだ理解できていない・・・ ('a. 'a -> 'a
がダメな理由はなんとなくわかるが、locally abstract typeはそれ以上の何をしているのだろう?)
追記1
こういうご指摘を@camloebaさんにいただいた:
let rec で再帰定義されている値は、定義中で多相型に推論するのが決定不能なので諦めています。and が無くても同じようなことが起きます。例えば let rec f : 'a. 'a -> unit = fun x -> f x; f (x,x)
— Jun Furuse 🐫🌴 (@camloeba) March 18, 2021
なるほど・・・ この例だと実行時は最初のf x
で無限ループになるが、こういうこともできるわけだ:
let rec f : 'a. ('a * int) -> unit = fun (x, y) -> if y = 0 then () else (print_endline "hello"; f ((x,x), y-1));;
引数のタプルの第一要素の型は再帰するたびに変わる(倍々になっていく)がpolymorphic type constraintを入れているので大丈夫。let rec f = fun ...
と型制約を入れない場合は多相だと型推論しないのでエラー。
追記2
こちらは@dico_lequeさんに教えていただいた:
これは、locally abstract typesで通るというよりは、locally abstract typesの
— でこれき (@dico_leque) March 18, 2021
Polymorphic syntaxで通ると言うべきではhttps://t.co/TMDsXeciiJhttps://t.co/kyUY35hz0a
たしかに・・・(type a) : a -> a
のようなOCaml 3.12で追加されたlocally abstract type構文ではダメで:
let rec f (type a) : a -> a = fun x -> x and g () = f 1 and h () = f true;; Line 3, characters 13-17: Error: This expression has type bool but an expression was expected of type int
4.00で追加されたlocally abstract typeのpolymorphic syntax let rec f : type a. a -> a
を使う必要がある。(GADTで使うのもこちらの構文)
@camloebaさん、@dico_lequeさんご教示いただきありがとうございます!