Hatena::Grouphaskell

猫とC#について書く代わりにHaskellとF#について書くmatarilloの日記 このページをアンテナに追加 RSSフィード

2015-12-01

Re: お手軽で実用的なジェネリックスへの道は遠い

| 16:02 | Re: お手軽で実用的なジェネリックスへの道は遠い - 猫とC#について書く代わりにHaskellとF#について書くmatarilloの日記 のブックマークコメント

TypeScriptジェネリックス:可能性が見えると不満がつのる」において、クラス定義や関数定義に型パラメータを渡せるだけでは、ジェネリック・プログラミングは難しいと述べました。そのときの例題はリストの総和だったのですが、より簡単な累乗(ベキ乗、power)計算を例にしてもう一度問題点を説明します。

お手軽で実用的なジェネリックスへの道は遠い - 檜山正幸のキマイラ飼育記

最近のプログラミング言語はジェネリックス機能を持っているものが多いですが、言語の進化に伴って後から付けたものがほとんどで、最初からジェネリックスありきで設計したものって、(少なくともメジャーどころでは)ないんじゃないの。ジェネリックスと演算子オーバーロードと型クラスを中核にしたプログラミング言語が出てきたら面白いのにね。

お手軽で実用的なジェネリックスへの道は遠い - 檜山正幸のキマイラ飼育記

F# はTIOBEランキングで31位 なのでメジャーどころとは言いにくいところですが、ジェネリックスがあって演算子オーバーロードもあって、型クラスはないけどインライン関数の力でポリモーフィックなべき乗を書くことができます。

実行結果はIdeOneで見ることができます。

LanguagePrimitives.GenericOne関数は、プリミティブな数値型、もしくはOneという名前の静的メンバーが定義されている任意の型を引数にとって、その型の「1」を返すというもの。

インライン関数として定義したpowの引数や戻り値の型や型制約を省略しているのでよくわからないかもしれませんが、F#コンパイラが適切な型や型制約を推論してくれます。

たとえばpowの引数xの型については、

  • 演算子 * がオーバーロード定義されていること
  • LanguagePrimitives.GenericOne関数と同じ制約を持つこと、すなわち、プリミティブな数値型、もしくはOneという名前の静的メンバーが定義されている任意の型であること

の2つが要請されます。

その上で、powの呼び出し側で、実際にどんな型を引数として渡しているかが、コンパイル時にチェックされます。今回の例ではint型とfloat型を渡していますが、どちらもプリミティブな数値型であり、*演算子がオーバーロード定義されているので、コンパイルに通ります。

「F#すごいじゃん」と思うかもしれませんが、実は注意点があって、F#ではジェネリクスと(多相)インライン関数は別の仕組みなのでシームレスに使うことはできないというところ。そのあたりのことがMSDNに書いてありますが、一言でいえば「インライン関数はコンパイル時に、ジェネリクスは実行時に型が解決される」という感じです。

(追記)ちなみにScalaの場合は「staticなんてものはないのだ。シングルトンオブジェクトがあるだけだ。」「型からシングルトンオブジェクトのインスタンスが決まる仕組みが整備されれば、それが型クラスになるのだ。」という感じの解決法で、(自分で好んで書きたいとはあまり思わないものの)興味深い仕掛けです。

2015-09-28

本質ではない。

| 13:37 | 本質ではない。 - 猫とC#について書く代わりにHaskellとF#について書くmatarilloの日記 のブックマークコメント

F# 不況側は何かと「C# はここがダメ!F# はC# よりイイ!」って言うけど、F#の成果がぜんぜんアピールされてないというのは真実。

ほかのはやりの言語は自身をわざわざC# なんかと比較したりしないし、そういうこというのF# ぐらいしかないよね。

F#はVisual Studio的に1st class languageじゃないんだけど、でもそっちを求めるのも個人的には違うなと思ってる。(VSエコシステム的には重要なんだろうけどね)どっちかというとクロプラ方面のほうがいいっす。VS Codeが機能向上しちゃうとそっちもC# が出てくるかもしれないけど。

2015-08-30

F#のWebアプリケーションフレームワーク

| 12:09 | F#のWebアプリケーションフレームワーク - 猫とC#について書く代わりにHaskellとF#について書くmatarilloの日記 のブックマークコメント

Guide - Web Programming | The F# Software Foundation に載ってる中でちょっと気になったやつをメモ。

Suave.IOはノンブロッキングでクロスプラットフォーム。ほうほう。結構作りこまれてる様子。

no title

type SuaveTask<'a> = Async<'a option>
type WebPart = HttpContext -> SuaveTask<HttpContext>
open Suave
open Suave.Http
open Suave.Http.Applicatives
open Suave.Http.Successful
open Suave.Web

let app =
  choose
    [ GET >>= choose
        [ path "/hello" >>= OK "Hello GET"
          path "/goodbye" >>= OK "Good bye GET" ]
      POST >>= choose
        [ path "/hello" >>= OK "Hello POST"
          path "/goodbye" >>= OK "Good bye POST" ] ]

startWebServer defaultConfig app

Freyaはステートマシン。シンプルっぽい。

GitHub - xyncro/freya: Freya Web Stack - Meta-Package

type Freya<'a> = FreyaState -> Async<'a * FreyaState>
let double x =
    freya {
        return x * 2 }

2015-08-29

演算子のオーバーロード #5(完結編)

| 12:01 | 演算子のオーバーロード #5(完結編) - 猫とC#について書く代わりにHaskellとF#について書くmatarilloの日記 のブックマークコメント

ちょっと間が空いた。

さて、ここまでを踏まえれば、最初の記事に載せた

.net - F# Overloaded Operator ( *= ) with floats not working with other floats - Stack Overflow

はほぼ理解できる。

  • 組み込みのfloatとユーザー定義のvector2の両方で使えるように、2項演算子 *= をオーバーロードしたい
  • しかし、float同士の*=演算を、後付けで定義することができない
  • そこで、以下のハックを使う
    • 演算子オーバーロードのためだけに、新たなユーザー定義型 Overloadsを定義する
    • ユーザー定義型 Overloadsに、3項演算子 ?<-を定義する
    • その演算子は、Overloads型の値を1つ、*=演算子適用したい型の値を2つ受け取るが、Overloads型の値は捨ててしまい、本来やりたかった*=演算だけを実装する
  • 以上の準備ができたなら、グローバルレベルでのインライン演算子定義で、*=を実装し、Overloads型の?<-演算子にぶん投げる
    • Overloads型の値は演算子オーバーロードの解決に使われた後は捨てられるので、unit的に値が1つしかない型で十分。よって、Overloads型は値が1つしかない判別共用体として定義してもいい。

なんとまあ。分かってしまえば何ということはなかった。?<-を使っているのは単にそれが3項演算子だからってだけのようだし、Overloads型が値が1つしかない判別共用体であることにも大きな意味はなかった。

bleis-tiftbleis-tift2015/09/30 11:31https://code.google.com/p/fsharp-typeclasses/
http://nut-cracker.azurewebsites.net/blog/2011/11/15/typeclasses-for-fsharp/

このあたりの話ですね。
このライブラリは現在はFsControlとして (?<-) を使わない形でより使いやすく作り直されています。

matarillomatarillo2015/11/20 21:07気づくのがすごーく遅くなりました。
https://twitter.com/lyrical_logical/status/667427660218413059 とか https://en.wikipedia.org/wiki/Ad_hoc_polymorphism を読んでて、頭のなかが少しつながりました。

2015-08-24

演算子のオーバーロード #4

| 18:47 | 演算子のオーバーロード #4 - 猫とC#について書く代わりにHaskellとF#について書くmatarilloの日記 のブックマークコメント

グローバルレベルで定義された演算子演算子のオーバーロードを隠してしまう。それはわかった。それならそれで、グローバルレベルで定義された演算子は多相(ジェネリック)にできないのだろうか。

結論から言うとできる。ただし気を付けなければならないことがある。

まず、関数の場合は山かっこ(<および>)を関数名の直後に置ける。

let addCount<'a when 'a :> System.Collections.ICollection>
    (x: 'a) (y: 'a) = x.Count + y.Count

この書き方は演算子ではできない。代わりにこのように書く。

// 演算子の場合はこう書くしかない
let (+) (x: 'a) (y: 'a): int
    when 'a :> System.Collections.ICollection =
    x.Count + y.Count

// なお関数なら両方の書き方が許される
let addCount (x: 'a) (y: 'a): int
    when 'a :> System.Collections.ICollection =
    x.Count + y.Count

こうすればジェネリックな演算子ができるのだが、F#のジェネリクスは.NETのジェネリクスを基盤にしているから、F#の型制約も基本的にはその範囲でしか書けない。つまり、(new制約や構造体制約は別とすれば)指定したクラスやインターフェースを継承している場合に、その型のインスタンスメソッドが呼び出せる、という程度のことしかできない。オーバーロードされた演算子はスタティックメンバー扱いだからジェネリックなメソッドからは呼び出せない。したがって、グローバルレベルで定義されたジェネリックな演算子の実装から呼び出すこともできない。C#と一緒である*1

ただ、F#の強力なところは、インライン関数を使うことでその制限を超えることができることだ。インライン関数はコンパイル時に静的に型が解決されるため、明示的なメンバーの制約をかけることで、型階層によらず、また静的メンバーとインスタンスメンバーを問わず、あるメンバーを持っている型が使われることを要請できる。

VSで書くといまいちわかりづらいのでF# Intaractiveを使う。

> let inline (%*) x y = x * y;;

val inline ( %* ) :
  x: ^a -> y: ^b ->  ^c
    when ( ^a or  ^b) : (static member ( * ) :  ^a *  ^b ->  ^c)

%*演算子の型が、通常のジェネリック型引数で使われる 'a'bではなく、静的に解決される型を表す ^a^bになっている。%*演算子を使う際には、左項(x)または右項(y)のどちらかの型に対して*演算子のオーバーロードが定義されており、もう一方の項の型もその定義に適合するのであれば、どんな型であってもこの新しい演算子適用できる。

もちろん、この演算子はコンパイル時にインライン展開(β変換)されるので、通常のジェネリックな型に適用することはできない。ジェネリックな型は実行時に解決されるものだからである。

(続く)

*1:F#では列挙型制約やデリゲート制約をつけられるが、C#ではできないという違いはある