F# TutorialをC#と比較しながらでF#を学ぶ

F#はMicrosoft発の関数型言語で、Visual Studio 2010に標準搭載されます。Visual Studio 2010 Beta 2も出たことだし、話題の?新言語を少し勉強してみることにします。F#の新規プロジェクト一覧にTutorialというのが用意されているので、これの中身を、C#と比較しながら見ていきたいと思います。追記:Microsoft Visual Studio 2010 First Look Beta 2日本語版も公開されました。

基本

```fsharp open System let int1 = 1 let int2 = int1 + 3 ```

using System;
var int1 = 1;
var int2 = int1 + 3;
```</p>

名前空間の利用の設定と基本的な変数の代入方法。といったところでしょうか。そのまんまだし、別にC#と違いは特にないっぽい。C#ではvar、F#ではlet。どちらも推論が効くのでほとんど同じ。末尾セミコロンはいらないようです。#lightがどうたらこうたら、というのは略。それともう一つ、F#はこのように定義したint1に再代入は出来ません。int1 = 100とすると、比較になります(==ではなく=が比較)。再代入的なint1 <- 1000はコンパイルが通らない。不変(immutable)なのです。C#だとreadonly、はフィールドにしかつけられないので、同じことを再現するのは無理なよう。

Print
---
```fsharp
printfn "peekResult = %d" peekResult
printfn "listC = %A" listC

F# TutorialではPrintは最後にあるのですが、あのですね、出来ればprintは冒頭にしていただきたいです。なんというか、私がF#で一番戸惑ったのが、printfn int1ってのが出来ないことなんですね。いやほら、とりあえずlet int1 = 1って書いたじゃないですか、最初に。で、書いたらとりあえず表示して確認したいでしょ?Console.WriteLineにあたるのはprintfnか、って来るわけです。でも、書いても動かないの。で、まあ、つまるところstring.Format的なものであり書式指定が必要、というところまで行くわけですが、そこで書式って何を書けばいいの?ということになるわけです。"%d"とか予告なく言われても分からないし。もうブチ切れですよ。え、Cの書式指定と一緒だって?いやあ、Cの書式指定も全然覚えてられません、あれあんま良くないと思うんですが……(ついでに言えば私はC#の書式指定も全然覚えてない、必要な度にMSDN見に行ってる)。しかもF#のは書式も色々拡張されてない?より一層分からん!int1の出力で挫折する!

ということなので、もっと頭の方にprintfnのきちんとした解説を載せてくれないと辛いです。ただ、どうしてもアレならConsole.WriteLine int1とでも書けば動く。おお、いきなり.NET Frameworkがそのまま使えることの有難味が(笑) と、冗談はさておき、この「書式指定に何が使えるのか分からない」状態はひっじょーに気持ち悪いので、検索してすぐ分かるような場所に一覧が、欲しい、です。真面目にこれは挫折理由になってます。しょうがないので検索して出てきた Google BooksのExpert F#の解説を見てようやくホッとできた。

%b(bool), %s(string), %d(10進), %f(float), %O (Object.ToString()) それと%A(Any)を覚えておけば問題ない、でしょーか。ほんと予告なく%Aとか言われても困るんですよ、泣きたいですよ。%Oとの違いは、人間が見た時に良い感じに整形してくれるのが%A、でしょうか。文字列は""で囲まれ、配列は展開して出力してくれる。

C#ではcw->TabTab->変数名、といった感じにコードスニペットを活かして手早く記述出来たわけですが、それに比べるとF#は書式指定が必要な時点で、非常にカッタルイ。カッタルイのですが、かわりに、より型に厳格です。printfn "%s" trueとか書くとコンパイル通らない。良し悪し、でしょうか。でも学習用にやってる間は面倒くさいだけですね。どうしても嫌ならば「let p x = printfn "%A" x」とでも定義しておけば良いのでしょうけれど。

gdgd言う前にF C# 言語リファレンスを見ろ、って話なのかもしれない。私は情けないことにここから書式指定を記した部分を見つけられませんでしたが。あと、選択範囲で囲んでAlt+EnterでF# Interactiveに送られるのでそれ見て確認しろって話も少しはありそう。

関数

```fsharp let f x = 2*x*x - 5*x + 3 let result = f (int2 + 4) let rec factorial n = if n=0 then 1 else n * factorial (n-1) ```

Func<int, int> f = x => 2 * x * x - 5 * x + 3;
var result = f(int2 + 4);
Func<int, int> factorial = null;
factorial = n => (n == 0) ? 1 : n * factorial(--n);
```</p>

Tutorialには最大公約数を求めるものもありましたがfactorialと同じなので省略。F#は関数型言語ということで、やっぱ関数ですよね!キーワードはletのままで、ふつーの変数と区別なく定義できる。 C#では汎用デリゲートであるFuncとActionを使うことでそっくり再現できる。C#では型を書いてやらなければならないのだけど、F#ではより強力に推論が効くようで型の明示は不要、のようです。

再帰は、F#はlet recキーワードでそのまま書けるのに対し、C#では一度nullを代入して名前を事前に宣言しておかなければならない。というぐらいで、見た目はほとんど変わらない。そういえばifが式ですね。なのでelseは省略できないようです。else ifの連打はelifで。というわけで、このif式(?)はC#の三項演算子とほとんど同じような感じです。

```fsharp
let add1 x y = x + y
printfn "%A" (add1 1.0 2.0)
printfn "%A" (add1 1 2) // Compile Error
let add2 x y = x + y
printfn "%A" (add2 1 2)
printfn "%A" (add2 1.0 2.0) // Compile Error

少し脱線して型推論の話を。C#の推論は単純なだけ分かりやすくて、これは型書いてやらないといけないな、推論させるための材料を与えてあげないといけないな、というのが結構直感的だったんですが、F#だと強力な分だけ、どう推論されるのか難しい。今は漠然と、全体を見るんだなー、ぐらいにしか分かっていません。例のコードですが、add1はfloat->float->floatで、add2はint->int->intに推論されます。let add1 x y = x + yの時点ではxの型もyの型も分からないけれど、「最初に呼ばれた時に」引数の型は判明する、ということは戻り値の型も判明する。なので、その型で決定する。ということなのかなー、と。この部分はC#と全然違っていて、面白いし強力だなー、と。

Tuple

```fsharp let data = (1, "fred", 3.1415) let Swap (a, b) = (b, a) ```

var data = Tuple.Create(1, "hogehoge");
static Tuple<T2,T1> Swap<T1,T2>(Tuple<T1,T2> tuple)
{
    return Tuple.Create(tuple.Item2, tuple.Item1);
}
```</p>

TupleはC#4.0から導入されます。F#は括弧で括るという専用記法があるので簡単に記述出来る。のに対して、C#ではふつーのclassなのでふつーにclassとして使うしかないのが残念。Swapですが、Tupleはimmutable(不変)なので、新しく生成する。だけ。です。temp用意して入れ替えて、などしない。潔く新しく作る。

Boolean, Strings
---
<p>```fsharp
let boolean1 = false
let boolean2 = not boolean1 && (boolean1 || false)
let stringA  = "Hello"
let stringB  = stringA + " world."
var boolean1 = false;
var boolean2 = !boolean1 && (boolean1 || false);
var stringA = "Hello";
var stringB = stringA + "world.";
```</p>

F#では否定が!ではなくnotなのですね。あとは一緒。

List
---
<p>```fsharp
let listA = [ ]
let listB = [ 1; 2; 3 ]
let listC = 1 :: [2; 3]
let oneToTen = [1..10]
let squaresOfOneToTen = [ for x in 0..10 -> x*x ]
var listA = Enumerable.Empty<object>();
var listB = new[] { 1, 2, 3 }.ToList();
var listC = Enumerable.Repeat(1, 1).Concat(new[] { 2, 3 }).ToList();
var oneToTen = Enumerable.Range(1, 10 - 1 + 1).ToList();
var squaresOfOneToTen = Enumerable.Range(0, 10 - 0 + 1).Select(x => x * x).ToList();
```</p>

リストを扱うとC#と大分差が出てきます。まず第一に、空リストは、C#だと該当するものは作れない。と思う。とりあえずobjectで代替することにしましたが、多分正しくありません。listBはただの整数リストなわけですが、F#だと;で区切るようです。一応、配列とリストは違うということで、C#側のコードはListにしていますがListとも違うので、まあ、気分だけ。listCの::はConsということで、一つの値とリストを連結するものです。C#に該当する関数はありません。しいていえばConcatが近いので、Repeat(value, 1)で長さ1のシーケンスを作って連結、という手を取ることにしました。

F#は[1..10]で最小値-最大値の連続したリストが作れるのですが、これはC#のEnumerable.Rangeとは、違います。Rangeの第二引数は最大値ではなく個数なので。正直言って、個数よりも最大値のほうが使いやすいと思うのだけどなー。というわけで、最大値-最小値+1 = 個数。ということにしています。最後のリスト内包表記は、うん、ええと、私は苦手です。値の動きが右行ったり左行ったりなのが嫌です。Linqのほうが好き。C#でイメージするなら、foreach (var x in [0..10]) yield return x * x; ってとこですかね。

パターンマッチ
---
<p>```fsharp
let rec SumList xs =
    match xs with
    | []    -> 0
    | y::ys -> y + SumList ys
let listD = SumList [1; 2; 3]  
Func<IEnumerable<int>, int> SumList = null;
SumList = xs => (!xs.Any())
    ? 0
    : xs.First() + SumList(xs.Skip(1));

var sum1 = SumList(new[] { 1, 2, 3 });
var sum2 = new [] { 1, 2, 3 }.Sum(); // こらこら
```</p>

まず、listDとかF# Tutorialには書いてあるんですが、これintなのでlistじゃないでしょ!紛らわしい。さて、match with | ->という目新しい記述がパターンマッチという奴ですね? 引数のリストxs(リストは通常変数名にxsとかysとかを用いるようです)が空配列の時は0を、そうでない時はyとysに分解して、ysの方は再帰して足し合わせる。ふむぬん。C#に直すとif-else if-else ifの連打。値を返すから、三項演算子のネストですな。という程度の理解しかしていません。三項演算子ネストより綺麗に書けて素敵。という浅すぎる理解しか、今はしていません。まあ、そのうちそのうち。

y::ysという表記ですが、これは配列中の最初のものがy、それ以外がysになります。つまりLinqだとFirst()とSkip(1)ですね。let x::xs = [3..5]とすれば、xが3でxsが4,5になる。警告出ますが。基本はパターンマッチ時用ってことなのかしらん。この辺はちょっと良く分かりません。

C#のほうの、IEnumerableのままSkipをゴロゴロと繋げていくのは実行効率がアレな悪寒。かといってToArrayを毎回使うのもなあ、というわけで上手い落し所が見つからない。QuickSortのように一本の配列に対し、境界の数字を渡していくってのやるとゴチャゴチャするし。あ、でもF#のも結局ysってのはxsとは別の、新しい配列ですよね?C#で表すのならば、xs.Skip(1).ToArray()ということかしらん。だとしたら、この程度の「効率」なんて奴は、気にしたら負けだと思っている。でいいのかもしれない。よくないかもしれない。

配列・コレクション
---
<p>```fsharp
let arr = Array.create 4 "hello"
arr.[1] <- "world"
arr.[3] <- "don"
let arrLength = arr.Length        
let front = arr.[0..2]
let lookupTable = dict [ (1, "One"); (2, "Two") ]
let oneString = lookupTable.[1]
var arr = Enumerable.Repeat("hello", 4).ToArray();
arr[1] = "world";
arr[3] = "don";
var arrLength = arr.Length;
var front = new string[3];
Array.Copy(arr, 0, front, 0, 3);
// もしくはSkip->Take. 実行効率は劣りますが、私はこちらの記述方法のほうが好き
var front2 = arr.Skip(0).Take(3).ToArray();
var lookupTable = new Dictionary<int, string> { { 1, "One" }, { 2, "Two" } };
var oneString = lookupTable[1];
```</p>

配列とlistとの違い。listは不変(immutable)で、配列は可変(mutable)ということかしらん。あと配列なら.NET Frameworkのメソッド・プロパティが全部使える。mutableなものへの値の再代入は=ではなく&lt;-で行う。あとは、Array.createは中身がnullな配列ではなく、初期値を指定して全部それで埋めるメソッドのようです。ふむ。あ、最後のslicing notationはいいですね。C#だとArray.Copyを使うのが等しいでしょうけど、記述が冗長すぎてねえ……。どうせ実行時間に対して差は出ないでしょ、と思う場合はLinqでSkip->Takeにしたほうがすっきり書けて良い。あ、あとインデクサは.[]が対応してるようです。ドット。ドット。

辞書の初期化は、タプルを放り投げるだけ。素晴らしい!見た目に分かりやすくスッキリするのがいいです。C#だとコレクション初期化子で近い形にはなりますが、{ {と、全て波括弧で記述するのはどうかなあ、と思うところがあるので。あとは一応、[C# 3.0 における疑似 Map 生成リテラル - NyaRuRuの日記](http://d.hatena.ne.jp/NyaRuRu/20071211/p3)なんてことも出来ますけれど、やりませんものね。

関数(その2)
---
<p>```fsharp
let Square x = x*x              
let squares1 = List.map Square [1; 2; 3; 4]
let squares2 = List.map (fun x -> x*x) [1; 2; 3; 4]
let squares3 = [1; 2; 3; 4] |> List.map (fun x -> x*x) 
let SumOfSquaresUpTo n = 
  [1..n] 
  |> List.map Square 
  |> List.sum
public static IEnumerable<TR> Map<T, TR>(this Func<T, TR> selector, IEnumerable<T> source)
{
    return source.Select(selector);
}

// ↑という拡張メソッドを定義して

Func<int, int> Square = x => x * x;
var squares1 = Map(Square, new[] { 1, 2, 3, 4 });
var squares2 = new Func<int, int>(x => x * x).Map(new[] { 1, 2, 3, 4 });
var squares3 = new[] { 1, 2, 3, 4 }.Select(x => x * x).ToArray();
// もしくは Array.ConvertAll(new[] { 1, 2, 3, 4 }, x => x * x)
Func<int, int> SumOfSquaresUpTo = n =>
    Enumerable.Range(1, n - 1)
        .Select(i => Square(i))
        .Sum();
```</p>

関数が先で、それに適用する配列を渡す、という順序はC#ばかり触ってる身としては、新鮮な印象です。そういえば[Achiral](http://d.hatena.ne.jp/NyaRuRu/20080115/p1)にも同種のオーバーロードが沢山定義されているのですが、私は違和感から、IEnumerable始点のものばかり使っています。あとSelect->ToArrayはArray.ConvertAllで書けるのですが、私はLinqで書くほうが好き。というかArrayの静的メソッドは、基本Obsoleteなぐらいの気持ちでいたりいなかったりする。

ラムダ式は「fun 引数 -> 本体」ですね。C#のほうがキーワードが必要ない分だけすっきりしてガガガ。でもnew Func<型>という不格好なものをつけなければならなかったりする悪夢。var hoge = (int x) => x * xもダメなんですよねえ。理由は、例えば「delegate int Func2(int i);」というのが定義出来るから。引数intで戻り値intだから、Func<int, int>とFunc2は同じ。でも型は違う。なので、見分けがつかず推論できないので、どのデリゲートを使うか、まで指定する必要がある。これは、悲しくウザい話です。ActionとFunc以外のデリゲート型なんて滅びてしまえばいいのに。

「|>」という見慣れない演算子が、パイプライン演算子で、左から右に値を流す。C#だと、Listに対してはLinqで、値に対しては、そういえば前に書いたような……。[neue cc - ver 1.3.0.3 / ちょっとした拡張メソッド群](http://neue.cc/2009/07/16_177.html)のTapの一個目が近い感じでしょーか。いいですよね、こういうの。

Mutable
---
<p>```fsharp
let mutable sum = 0
for i in 0..10 do
  sum <- sum + i
while sum < 100 do
  sum <- sum + 5
var sum = 0;
foreach (var i in Enumerable.Range(0, 10))
{
    sum += i;
}
while (sum < 100)
{
    sum += 5;
}
```</p>

最初にF#の値はimmutableだと書きましたが、mutableにしたい時は、mutableキーワードを足せばおk。再代入時は&lt;-演算子を使う、と。C#だとデフォルトがmutableなので、まんまです。そして、このforは、foreachですね。インデントが波括弧代わりなので、doだけどendは要りません。普通のforは「for i = 1 to 10 do」ですが、これならforeachでいいやあ、という気はする。

Types: unions
---
<p>```fsharp
type Expr = 
  | Num of int
  | Add of Expr * Expr
  | Mul of Expr * Expr
  | Var of string
  
let rec Evaluate (env:Map<string,int>) exp = 
    match exp with
    | Num n -> n
    | Add (x,y) -> Evaluate env x + Evaluate env y
    | Mul (x,y) -> Evaluate env x * Evaluate env y
    | Var id    -> env.[id]
  
let envA = Map.of_list [ "a",1 ;
                         "b",2 ;
                         "c",3 ]
             
let expT1 = Add(Var "a",Mul(Num 2,Var "b"))
let resT1 = Evaluate envA expT1
```</p>

F# Tutorialですが、ここで途端に説明が無くなって放り出されます。鬼すぎる。今までのわりとゆるふわなところから途端にコレです。意味分からないし。unionsとか言われても分けわからない。と、嘆いていても始まらないので理解するよう頑張ります。そういえば(env:Map&lt;string,int>)も初出なのよね。推論じゃなく明示的に型を与える時は、こうするそうです。型定義がC#とは逆で、コロン後の末尾。違和感がシンドい。ActionScriptなんかも同じで非常にシンドい。

unionはC#だとenumが近いかなー、と思うのですが、enumがintのみなのに対し、F#のunionはそれぞれが別の型を持てる。といった認識。更に値は外から定義可能。というわけでenumとは全然違いますな。むしろ普通にclassに近い。of intで型を定義している(Expr * ExprはTuple)し、値は外から与えているし(コンストラクタのように!) けれど、値は一個。

じゃあclassで作れるかと言ったら、どうだろー。戻り値の型がバラバラになるので、interfaceで一個に纏められるわけでもなく上手いやり方ってあるのかしらん。パターンマッチと同じく、C#には無い概念、と素直にとらえた方が良いかも。一応、interface、じゃなくてダミーに近い型の下にぶら下げて、Evaluateのところでisで派生型を判定して分岐、といった感じでやってみましたが、ゴミですね……。

```csharp
public class Expr
{
    // privateにしたいつもり(これは酷い)
    public class _Num : Expr
    {
        public int Value { get; set; }
    }
    public class _Add : Expr
    {
        public Expr E1 { get; set; }
        public Expr E2 { get; set; }
    }
    public class _Mul : Expr
    {
        public Expr E1 { get; set; }
        public Expr E2 { get; set; }
    }
    public class _Var : Expr
    {
        public string Value { get; set; }
    }

    private Expr() { }

    public static Expr Num(int value)
    {
        return new _Num { Value = value };
    }
    public static Expr Add(Expr e1, Expr e2)
    {
        return new _Add { E1 = e1, E2 = e2 };
    }
    public static Expr Mul(Expr e1, Expr e2)
    {
        return new _Mul { E1 = e1, E2 = e2 };
    }
    public static Expr Var(string value)
    {
        return new _Var { Value = value };
    }
}

static int Evaluate(IDictionary<string, int> env, Expr exp)
{
    return // どうしょうもなく酷い
          (exp is Expr._Num) ? ((Expr._Num)exp).Value
        : (exp is Expr._Add) ? Evaluate(env, ((Expr._Add)exp).E1) + Evaluate(env, ((Expr._Add)exp).E2)
        : (exp is Expr._Mul) ? Evaluate(env, ((Expr._Mul)exp).E1) + Evaluate(env, ((Expr._Mul)exp).E2)
        : (exp is Expr._Var) ? env[((Expr._Var)exp).Value]
        : 0;
}

static void Main(string[] args)
{
    var envA = new Dictionary<string, int> { { "a", 1 }, { "b", 2 }, { "c", 3 } };
    var expT1 = Expr.Add(Expr.Var("a"), Expr.Mul(Expr.Num(2), Expr.Var("b")));
    var resT1 = Evaluate(envA, expT1);
    Console.WriteLine(resT1); // 確認
}

見なかったことにしてください。私の脳みそなんてこんなもんです。

Types: records

```fsharp type Card = { Name : string; Phone : string; Ok : bool } let cardA = { Name = "Alf" ; Phone = "(206) 555-8257" ; Ok = false } let cardB = { cardA with Phone = "(206) 555-4112"; Ok = true } let ShowCard c = c.Name + " Phone: " + c.Phone + (if not c.Ok then " (unchecked)" else "") ```

class Card
{
    public string Name { get; set; }
    public string Phone { get; set; }
    public bool Ok { get; set; }

    public Card() { }

    public Card(Card with)
    {
        // structならthis=withで一発なのですが
        // F#のrecordはstructじゃないとのことなので
        this.Name = with.Name;
        this.Phone = with.Phone;
        this.Ok = with.Ok;
    }
}

var cardA = new Card { Name = "Alf", Phone = "(206) 555-8257", Ok = false };
var cardB = new Card(cardA) { Phone = "(206) 555-4112", Ok = true };
Func<Card, string> ShowCard = c =>
    c.Name + " Phone: " + c.Phone + (!c.Ok ? " (unchecked)" : "");
```</p>
    
こちらは割とすんなりと何なのか分かる。withでコピーが作れているところが面白い。ふーむ、C#だとむしろ匿名型のほうが近い感じに見えるかもしれない。

Types: classes
---
<p>```fsharp
type Vector2D(dx:float, dy:float) = 
    let length = sqrt(dx*dx + dy*dy)
    member v.DX = dx
    member v.DY = dy
    member v.Length = length
    member v.Scale(k) = Vector2D(k*dx, k*dy)
class Vector2D
{
    public float DX { get; private set; }
    public float DY { get; private set; }
    public float Length { get; private set; }
    public Func<int, Vector2D> Scale { get; private set; }

    public Vector2D(float dx, float dy)
    {
        var length = (float)Math.Sqrt(dx * dx + dy * dy);
        this.DX = dx;
        this.DY = dy;
        this.Length = length;
        this.Scale = new Func<int, Vector2D>(k => new Vector2D(k * dx, k * dy));
    }
}
```</p>

コンストラクタと定義が一体化していて、随分とシンプルに記述出来るようです。JavaScriptっぽい、なんて思ってしまったりして。C#で再現するとプロパティでメソッドかいな、という違和感があったりなかったり。private変数で蓄える必要がないから、定義が楽といえば楽。ところで思うのは、F#のv.DXとかの、vって何処から来てるの……? これ、別にhogehogeにしてもaaaaaaにしても動くので、何でもいいみたいですが……。

Types: interfaces
----
<p>```fsharp
type IPeekPoke = 
    abstract Peek: unit -> int
    abstract Poke: int -> unit

type Widget(initialState:int) = 
    let mutable state = initialState
    interface IPeekPoke with 
        member x.Poke(n) = state <- state + n
        member x.Peek() = state 
    member x.HasBeenPoked = (state <> 0)

let widget = Widget(12) :> IPeekPoke
widget.Poke(4)
let peekResult = widget.Peek()
interface IPeekPoke
{
    int Peek();
    void Poke(int n);
}

class Widget : IPeekPoke
{
    private int state;
    public bool HasBeenPoked { get { return state != 0; } }

    public Widget(int initialState)
    {
        state = initialState;
    }

    public int Peek()
    {
        return state;
    }

    public void Poke(int n)
    {
        state = state + n;
    }
}

static void Main(string[] args)
{
    var widget = (IPeekPoke)new Widget(12);
    widget.Poke(4);
    var peekResult = widget.Peek();
}
```</p>

interfaceはabstractな型定義を並べる。ということらしい。定義方法は「メソッド名:引数->引数->戻り値」ですねん。unitはC#でいうところのvoidみたいなもの。で、interfaceの実装は、そのまま中に記述してしまえばいいらしい。これは楽ちん。見慣れない「:>」はキャストの記号。とても、カッコイイです……。

結論
---

以上、複数回に分けようかとも思ったのですが一気にやってみました。最初F# Tutorialを開いて、少な!こんなんでチュートリアルになってるの?と思ったのですが、意外とギッシリ詰まってた感じです。しっかりチュートリアルになってました。ただ、やっぱチュートリアルなのでこれを覚えたぐらいじゃF#凄い!F#嬉しい!的にはなりません(比較対象がC#2.0だとなったかもしれませんが)でした。日常的に使って、手に馴染ませないと、良さの理解まではいけなさそうです。

あとまあ、やっぱほとんど説明のない、このTutorialのコードだけじゃ適当な理解になってそうで怖い。きちんと時間割いてMSDN見るなりしないと……。ただ、今のとこがっつし覚えよう!と思えてないところはある。本音として、C#でいいぢゃん、と思っているところがかなりあります。これがJava->Scalaの関係だったら違ったかもしれないんですが、うーん。まあ、あとVisualStudioの補完具合とかかな。IntelliSenseに乗ってゴリゴリ書けるような感触がF#にはないので。別に補完効いてないってわけじゃあないのですけど。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(.NET)
April 2011
|
July 2025

X:@neuecc GitHub:neuecc

Archive