型推論（Type Inference）は、プログラミング言語において、変数や式の型を明示的に指定せずに、コンパイラやインタプリタがその型を推測する機能です。型推論により、プログラマはコードを簡潔に書くことができ、同時に型安全性を保証できます。
型推論は、静的型付け言語や動的型付け言語の両方で利用されますが、特に静的型付け言語でよく使用されます。静的型付け言語では、変数や関数の型がコンパイル時に決定されるため、型推論は型の冗長な指定を省略し、コードの可読性を向上させます。
型推論の機能は言語ごとに異なりますが、一般的には以下のような手法が使われます。
ヒントに基づく推論（Type Hinting）:プログラマが型のヒントを与えることで、コンパイラやインタプリタがその型を推論します。これにより、型の一部を明示的に指定しながら、型の推論をサポートすることができます。
制約ベースの型推論（Constraint-Based Type Inference）:制約ソルバーを使用して、変数や式の型に関する制約を解決し、最適な型を推論します。制約ベースの型推論は、Hindley-Milner型システムなどの多くの型推論アルゴリズムで使用されます。
ユニフィケーションベースの型推論（Unification-Based Type Inference）:型の等式制約を解くために、ユニフィケーションアルゴリズムを使用します。この手法は、Prologなどの論理プログラミング言語や、ML系言語などで使用されます。
型推論の利点は多岐にわたります。まず第一に、コードの記述量を減らし、タイプミスや型不一致によるバグを減らすことができます。さらに、型の推論により、型安全性が向上し、コードの品質が向上します。ただし、型推論が複雑な場合、コンパイル時間が長くなる場合があります。また、推論された型が意図しないものになることもありますので、プログラマは注意が必要です。
型推論を実装したプログラミング言語にはさまざまなアルゴリズムがあります。
以下にいくつかの代表的なアルゴリズムとそれを採用している言語を示します。
Hindley-Milner型推論:
主な特徴: 多くの静的型付け言語で採用されており、単相型（monomorphic type）の推論に使用される。
Damas-Hindley-Milner型推論:
主な特徴: Hindley-Milner型推論を拡張し、多相型（polymorphic type）の推論を行う。
Algorithm W:
主な特徴: Hindley-Milner型推論の特殊なケースであり、変数の一般化（generalization）と単一化（unification）を行う。
Bidirectional型推論:
主な特徴: 型の推論を前方向と後方向の2つのフェーズに分け、より柔軟な型推論を可能にする。
これらのアルゴリズムは、静的型付け言語において一般的に使用されており、それぞれ異なる特性や利点を持っています。
例えば、Hindley-Milner型推論は単相型の推論に優れており、Algorithm Wはその特殊なケースを扱うことができます
。一方、Bidirectional型推論は、より柔軟な型システムを提供するために、前方向と後方向のフェーズを組み合わせています。
以下に、各言語ごとにコード例とその型推論アルゴリズムを解説します。
-- 関数の例
add x y = x + y
この例では、関数 add の型を推論します。Hindley-Milner型推論により、x と y の型が数値型であることが推論され、関数の型は Num a => a -> a -> a となります。ここで、Num a => は型クラス制約を示し、a は任意の数値型を表します。この推論は、変数や式の型を利用して、最も一般的な型を推測することで行われます。
// 関数の例
def add(x: Int, y: Int) = x + y
Scalaでは、一部の場合にHindley-Milner型推論が使用されますが、その他の場合にはより複雑な型推論アルゴリズムが使用されます。この例では、関数 add の引数 x と y の型が Int であることが明示されています。そのため、この関数の型は Int => Int => Int と推論されます。この推論は、式の型や型アノテーションを使用して行われます。
// 関数の例
function add(x, y) {
    return x + y;
}
TypeScriptでは、Bidirectional型推論が使用されます。この例では、関数 add の引数 x と y の型が明示されていませんが、関数内での演算 x + y から、引数の型が数値であることが推測されます。そのため、この関数の型は (x: number, y: number) => number と推論されます。この推論は、関数の呼び出し元や関数内の式を使用して行われます。
-- 関数の例
add x y = x + y
----
Haskellでは、関数の引数や戻り値の型を指定することなく、コンパイラが自動的に型を推論します。
Haskellの型推論の例:
-- リストの型推論の例
myList = [1, 2, 3, 4, 5] -- myListはNum a => [a]型と推論される
-- 関数の型推論の例
add x y = x + y -- add関数の型はNum a => a -> a -> aと推論される
-- タプルの型推論の例
myTuple = (1, "hello", True) -- myTupleは(Num a, IsString b) => (a, [Char], Bool)型と推論される
-- ジェネリクスの型推論の例
myMaybe = Just 42 -- myMaybeはNum a => Maybe a型と推論される
-- 関数の型推論の例
myFunc x = x * 2 -- myFuncはNum a => a -> a型と推論される
このコードでは、関数addの引数xとyの型を指定していません。しかし、コンパイラはNum型クラスのインスタンスである型を推論し、正しい結果を得ることができます。
OCamlの型推論では、変数に対して型を明示的に指定する必要はありません。
OCamlの型推論の例:
(* リストの型推論の例 *)
let my_list = [1; 2; 3; 4; 5] (* my_listはint list型と推論される *)
(* 関数の型推論の例 *)
let add x y = x + y (* add関数の型はint -> int -> intと推論される *)
(* タプルの型推論の例 *)
let my_tuple = (1, "hello", true) (* my_tupleはint * string * bool型と推論される *)
(* ジェネリクスの型推論の例 *)
let my_maybe = Some 42 (* my_maybeはint option型と推論される *)
(* 関数の型推論の例 *)
let my_func x = x * 2 (* my_funcはint -> int型と推論される *)
C++では、autoキーワードを使用して、変数の型を推論することができます。
C++の型推論の例:
int main() {
    // ベクターの型推論の例
    auto myVector = std::vector{1, 2, 3, 4, 5}; // myVectorはstd::vector型と推論される
    // 関数の型推論の例
    auto add = [](int x, int y) { return x + y; }; // add関数の型は自動的に推論される
    // マップの型推論の例
// myMapはstd::map型と推論される
    // 関数ポインタの型推論の例
    auto myFunc = [](int x) -> int { return x * 2; }; // myFuncはstd::function型と推論される
    // ラムダ式の型推論の例
    auto myLambda = [](int x) { return x * x; }; // myLambdaはstd::function型と推論される
    std::cout << "Vector: ";
    for (auto& num : myVector) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    std::cout << "Map: ";
    for (const auto& pair : myMap) {
        std::cout << "{" << pair.first << ": " << pair.second << "} ";
    }
    std::cout << std::endl;
    std::cout << "Result of add(3, 4): " << add(3, 4) << std::endl;
    std::cout << "Result of myFunc(5): " << myFunc(5) << std::endl;
    std::cout << "Result of myLambda(6): " << myLambda(6) << std::endl;
    return 0;
}
この例では、my_variableはint型であることが推論されます。
Rustでは、変数の型を指定することなく、コンパイラが自動的に型を推論します。
Rustでの型推論の例:
fn main() {
    // ベクターの型推論の例
    let my_vector = vec![1, 2, 3, 4, 5]; // my_vectorはVec型と推論される
    // 関数の型推論の例
    let add = |x: i32, y: i32| x + y; // add関数の型は自動的に推論される
    // ハッシュマップの型推論の例
    let mut my_map = std::collections::HashMap::new();
    my_map.insert(String::from("a"), 1);
    my_map.insert(String::from("b"), 2);
    my_map.insert(String::from("c"), 3); // my_mapはHashMap型と推論される
    // クロージャの型推論の例
    let my_closure = |x: i32| x * 2; // my_closureはクロージャ型と推論される
    // Option型の型推論の例
    let my_maybe = Some(42); // my_maybeはOption型と推論
    println!("Vector: {:?}", my_vector);
    for (key, value) in &my_map {
        println!("Key: {}, Value: {}", key, value);
    }
    println!("Result of add(3, 4): {}", add(3, 4));
    println!("Result of my_closure(5): {}", my_closure(5));
}
TypeScriptは静的型付け言語であり、変数の型を指定する必要がありますが、型推論によりコンパイラが自動的に型を推論することができます。
 
TypeScriptでの型推論の例:
// 配列の型推論の例
const myArray = [1, 2, 3, 4, 5]; // myArrayはnumber[]型と推論される
// 関数の型推論の例
function add(x: number, y: number) {
    return x + y;
} // add関数の型は(x: number, y: number) => numberと推論される
// オブジェクトの型推論の例
const myObject = { a: 1, b: 2, c: 3 }; // myObjectは{ a: number, b: number, c: number }型と推論される
// ジェネリクスの型推論の例
const myArray2 = [1, 2, 3, 4, 5]; // myArray2はnumber[]型と推論される
const myOption = myArray2.find(x => x === 3); // myOptionはnumber | undefined型と推論される
// 関数の型推論の例
const myFunc = (x: number) => x * 2; // myFuncは(x: number) => number型と推論される
このコードでは、関数addの引数xとyの型を指定していませんが、コンパイラはそれらをnumber型と推論します。
Scalaは静的型付け言語であり、変数の型を指定する必要がありますが、型推論によりコンパイラが自動的に型を推論することができます。
 
Scalaでの型推論の例:
object Main extends App {
  // リストの型推論の例
  val myList = List(1, 2, 3, 4, 5) // myListはList[Int]型と推論される
  // 関数の型推論の例
  val add = (x: Int, y: Int) => x + y // add関数の型は(Int, Int) => Intと推論される
  // マップの型推論の例
  val myMap = Map("a" -> 1, "b" -> 2, "c" -> 3) // myMapはMap[String, Int]型と推論される
  // ジェネリクスの型推論の例
  val myOption = Some(42) // myOptionはOption[Int]型と推論される
  // 関数リテラルの型推論の例
  val myFunc = (x: Int) => x * 2 // myFuncはInt => Int型と推論される
  println("myList: " + myList)
  println("Result of add(3, 4): " + add(3, 4))
  println("myMap: " + myMap)
  println("myOption: " + myOption)
  println("Result of myFunc(5): " + myFunc(5))
}
このコードでは、関数addの引数xとyの型を指定していますが、変数xとyの型を指定していません。しかし、コンパイラはそれらをInt型と推論します。
Kotlinは静的型付け言語であり、変数の型を指定する必要がありますが、型推論によりコンパイラが自動的に型を推論することができます。
 
Kotlinでの型推論の例:
fun main() {
    // リストの型推論の例
    val myList = listOf(1, 2, 3, 4, 5) // myListはList型と推論される
    // 関数の型推論の例
    val add = { x: Int, y: Int -> x + y } // add関数の型は(Int, Int) -> Intと推論される
    // マップの型推論の例
    val myMap = mapOf("a" to 1, "b" to 2, "c" to 3) // myMapはMap型と推論される
    // 関数リテラルの型推論の例
    val myFunc = { x: Int -> x * 2 } // myFuncは(Int) -> Int型と推論される
    println("myList: $myList")
    println("Result of add(3, 4): ${add(3, 4)}")
    println("myMap: $myMap")
    println("myNullable: $myNullable")
    println("Result of myFunc(5): ${myFunc(5)}")
}
Swiftでは、変数に対して明示的に型を指定することができますが、通常は必要ありません。
Swiftの型推論の例:
import Foundation
// 配列の型推論の例
let myList = [1, 2, 3, 4, 5] // myListは[Int]型と推論される
// 関数の型推論の例
let add = { (x: Int, y: Int) -> Int in return x + y } // add関数の型は(Int, Int) -> Intと推論される
// 辞書の型推論の例
let myDict = ["a": 1, "b": 2, "c": 3] // myDictは[String: Int]型と推論される
// クロージャの型推論の例
let myClosure = { (x: Int) -> Int in return x * 2 } // myClosureは(Int) -> Int型と推論される
print("myList: \(myList)")
print("Result of add(3, 4): \(add(3, 4))")
print("myDict: \(myDict)")
print("myOptional: \(String(describing: myOptional))")
print("Result of myClosure(5): \(myClosure(5))")
この例では、my_variableはString型であることが推論されます。
Goでも、変数の型を明示的に指定することができますが、通常は必要ありません。
Goの型推論の例:
package main
import "fmt"
func main() {
    // スライスの型推論の例
    myList := []int{1, 2, 3, 4, 5} // myListは[]int型と推論される
    // 関数の型推論はありませんが、無名関数を使うことで型の指定を省略できます。
    add := func(x, y int) int { return x + y } // add関数の型は明示的に指定されています
    // マップの型推論の例
    myMap := map[string]int{"a": 1, "b": 2, "c": 3} // myMapはmap[string]int型と推論される
    // 変数の型推論の例
    var myVariable = 42 // myVariableはint型と推論される
    fmt.Println("myList:", myList)
    fmt.Println("Result of add(3, 4):", add(3, 4))
    fmt.Println("myMap:", myMap)
    fmt.Println("myVariable:", myVariable)
}
この例では、my_variableはstring型であることが推論されます。
Zigでも、変数の型を明示的に指定することができますが、通常は必要ありません。
Zigの型推論の例:
const my_variable = 42;
この例では、my_variableはconst int型であることが推論されます。const intは、int型の定数を表します。