[kotlinfp]関数型システム


'코틀린으로 배우는 함수형 프로그래밍'을 읽고 정리하는 내용입니다.この章では、コトリン型システムではなく、関数型言語がどのようなタイプのシステムに基づいているかを紹介します.

タイプシステム


静的タイプのシステムでは、コンパイル時に実行時に発生する可能性のあるエラーが発生し、IDEは静的情報に基づくさまざまな機能を提供し、生産性を向上させることができます.

関数型言語の静的タイプシステム


関数型言語では、オブジェクトだけでなく式(expression)もあります.関数にもタイプがあります.
次の関数のタイプはfun product(x: Int, y:Int): Intです.
fun product(x: Int, y: Int): Int {
  doDangerousIo()
  return x * y
}

大量データ型


代数タイプとは、集合の他のタイプが形成する合成タイプであり、積タイプ(producttype)と和タイプ(sum type)が存在する.
代数データ型のコアは기존의 타입들을 결합하여 새로운 타입을 정의である.

2つのタイプの例と制限


デュアルタイプとは、1つのデータ構造で複数のタイプを一度に定義し、凡例または記録を例にとるものです.2種類以上がANDの形態に結合している.
class Circle(val name: String, val x: Float, val y: Float, val radius: Float)
CircleクラスはStringタイプのnameとFloatタイプのx,y,radiusをAndに結合して新しいタイプを定義する.
SquareクラスとCircleを表す単一のタイプを作成する方法.
クラスにはタイプ間のANDしかマージできないため、抽象クラスを作成する必要があります.以下に示すように、抽象クラスを追加してANDをマージします.
open class Shape(name: String, x: Float, y: Float)
class Circle(name: String, x: Float, y: Float, val radius: Float): Shape(name, x, y)
class Square(name: String, x: Float, y: Float, val radius: Float): Shape(name, x, y)
CircleとSquareはShapeを継承する階層を作成し、2つのクラスを単一のタイプとして表します.汎用propertyとメソッドはShapeクラスに実装され、各サブクラスは差異の汎用継承構造のみを実装する.
ここで、線を単一のタイプのShapeとしてどのように表現しますか?
open class Shape(val name: String)
class Circle(name: String, val x: Float, val y: Float, val radius: Float): Shape(name)
class Square(name: String, val x: Float, val y: Float, val length: Float): Shape(name)
class Line(name: String, val x1: Float, val y1: Float, val x2: Float, val y2: Float): Shape(name)
階層はますます複雑になっている.複雑な階層構造は、メンテナンスや柔軟性に悪影響を及ぼします.
デュアルタイプのもう一つの特徴はwhen構文を使用するときに現れることです.
fun main(args: Array<String>) {
  println(getGirthLength(Circle("원", 1.0f, 1.0f, 1.0f)))
  println(getGirthLength(Square("정사각형", 1.0, 1.0, 1.0f)))
|

fun getGirthLength(shape: Shape): Double = when (shape) {
  is Circle -> 2 * Math.PI * shape.radius
  is Square -> 4 * shape.length.toDouble()
  is Line -> {
    val x2 = Math.pow(shape.x2 - shape.x1.toDouble(), 2.0)
    val y2 = Math.pow(shape.y2 - shape.y1.toDouble(), 2.0)
    Math.sqrt(x2 + y2)
  }
  else -> throw IllegalArgumentException()
}
ここでelse文を使用する場合は、else文を記述する必要があります.複合タイプは、構成タイプのCircle、Square、Lineの合計を意味しない.コンパイラはShapeのサブクラスの数を予測できないため、else構文で他の入力を考慮する必要があります.

合型連結ORの使用


タイプとは異なり、タイプによって2つ以上のタイプがORに結合されます.コトリンはsealed classを使用して合型を作成します.sealed classを使用してシェイプを再定義します.
sealed class Shape
data class Circle(var name: String, val x: Float, val y: Float, val radius: Float): Shape()
data class Square(val name: String, val x: Float, val y: Float, val length: Float): Shape()
data class Line(val name: String, val x1: Float, val y1: Float, val x2: Float, val y2: Float): Shape()
ここで、形状はCircleまたはSquareまたはLineである.この結合タイプは、複数のタイプをORに結合し、新しい単一のタイプを作成することができる.
コトリンの列挙タイプは、すべての値のパラメータタイプが同じで、1つの構造関数しか持たない制限的なタイプである必要があります.
sealed classを使用すると、前に作成したgetGirthLengthが次のように再作成されます.
結合タイプでは、部分和が全体になるため、else構文を記述する必要はありません.
コンパイラは、3つのタイプを除いて、他のタイプは入らないと予測できます.
合型の利点は,モードマッチングが容易で,付与効果(else構文)を処理する必要がないことである.また、結合タイプは、複雑な継承構造を回避しながら、拡張しやすいタイプを定義できます.
fun getGirthLength(shape: Shape): Double = when (shape) {
  is Circle -> 2 * Math.PI * shape.radius
  is Square -> 4 * shape.length.toDouble()
  is Line -> {
    val x2 = Math.pow(shape.x2 - shape.x1.toDouble(), 2.0)
    val y2 = Math.pow(shape.y2 - shape.y1.toDouble(), 2.0)
    Math.sqrt(x2 + y2)
  }
}

関数プログラミングにおける代数データ型


リストは代数タイプです.リスト内の値のタイプは同じですが、タイプに合わせて新しいタイプを定義できます.
sealed class FnList<out T> {
  object Nil: FunList<Nothing>()
  data class: Cons<out T>(val head: T, val tail: FunList<T>) : FunList<T>()
}
FunListのタイプはsealed classによって宣言されるので、FunListはタイプに合います.NilとConsのORを組み合わせたもので,2種類の和はFunListである.
関数式プログラミングでは、代数データ型は、FunListなどの和型として定義される.タイプに含まれるすべてのタイプの定義が明確であるため、コンパイラはタイプを予測しやすい.そのため、以下の利点があります.
  • タイプは、
  • の統合と拡張が容易です.
  • ジェネレータモードマッチングにより、簡潔なコード
  • を記述することができる.
  • 徹底的なタイプチェックによりよりよりより安全なコード
  • を記述する

    タイプのコンポーネント


    タイプは、式がどのカテゴリに含まれているかを示すラベルのようなものです.関数式プログラミングでは、関数のタイプを理解または宣言することが重要です.
    次に、タイプを理解するために作成される用語とタイプの方法について説明します.

    タイプ変数

    fun <T> head(list: List<T>): T = list.first()
    コートリンでは,汎用と称されるTをタイプ変数(typevariable)と呼ぶ.タイプ変数を持つ関数を多形関数と呼びます.
    多形関数の具体的なタイプを決定するにはhead関数を使用します.以下に示します.
    head(listOf(1, 2, 3, 4))
    ここで、関数のタイプは、タイプによってfun head(list: List<Int>): Intと推論される.
    タイプ変数は、新しいタイプを定義するためにも使用できます.
    class Box<T>(t: T) {
      val value = t
    }
    
    val box = Box(1)
    Box(1)が呼び出された瞬間、boxのタイプはBox<Int>に決定される.タイプはコンパイラによって推定されるため、明示的にタイプを記述する必要はないが、タイプが複雑になるとコードでタイプを推定するのが難しいという欠点がある.
    以下に、注意すべき類型推論の例を示す.
    val listOfBox = listOf(Box(1), Box("String"))
    リストには、IntタイプとStringタイプが含まれます.このとき,コンパイラはコートリンのトップオブジェクトList<Any>と推定する.プログラマがそれを意識していない場合は、予期せぬ動作の関数を呼び出すか、目的の関数が見つからない可能性があります.

    値作成者


    タイプでは、値コンストラクション関数(value constructor)がタイプの値を返します.Boxの例では、値作成者はclass Box<T>(t: T)です.classまたはsealed classでは、値ジェネレータ自体もタイプとして使用できます.enumの場合、値ジェネレータは値としてのみ使用でき、タイプとして使用できません.
    sealed class Expr
    data class Const(val number: Double): Expr()
    data class Sum(val e1: Expr, val e2: Expr): Expr()
    object NotANumber: Expr()
    
    fun getSum(p1: Double, p2: Double): Sum {
      return Sum(Const(p1), Const(p2))
    }
    SumはExprの値ジェネレータですが、getSum関数のタイプ宣言に使用できます.
    enum class Color(val rgb: Int) {
      RED(0xFF0000),
      GREEN(0x00FF00),
      BLUE(0x0000FF)
    }
    
    fun getRed(): Color {
      return Color.RED
    }
    // compile error 
    fun getRed(): Color.RED {
      return Color.RED
    }
    Enumの場合、Colorはタイプですが、値ジェネレータColorです.REDは値としてのみ使用できます.

    タイプ作成者とタイプパラメータ


    値作成者が値パラメータを受信して新しい値を作成するように、タイプ作成者(type constructor)はパラメータ化タイプを取得して新しいタイプを作成することができます.
    以下、Boxはタイプジェネレータであり、Tはタイプパラメータである.
    class Box<T>(t: T)
    次を見て
    sealed class Maybe<T>
    object Nothing: Maybe<kotlin.Nothing>()
    data class Just<T>(val value: T): Maybe()
    上で、Maybeはタイプジェネレータ、Tはタイプパラメータです.Maybeはタイプではなくタイプジェネレータなので、特定のタイプになるにはすべてのパラメータを入力する必要があります.

    動作のタイプを定義


    コトリンのような現代言語は행위를 가진 타입を定義する方法を提供している.オブジェクト向けプログラミングでは,動作のタイプを定義する方法として,インタフェース,抽象クラス,特徴(trait),ハイブリッド(mixin)などがある.

    インタフェース


    インタフェースはクラスの機能リストです.クラスの動作をインプリメンテーションを作成せずにメソッドの署名として定義します.複数の継承が可能であり、それ自体がインスタンス化されず、継承インタフェースのクラスは関数の実装部分を記述する必要があります.
    interface Developer {
      val language: String
      
      fun writeCode()
    }

    トレイ


    インターフェースと同様であるが、トレイは、実施形態を含む方法を定義することができる.
    トレイから実施部に定義する方法は、トレイを継承するクラスに実施部を作成する必要はない.
    interface Developer {
      val language: String
      
      fun writeCode()
      
      fun findBugs(): String {
        return 'findBugs"
      }
    }

    抽象クラス


    抽象クラスは、継承関係の抽象オブジェクトを表すために使用されます.任意のタイプのpropertyとジェネレータを持つことができ、多重継承はできません.
    abstract class Developer {
      abstract val language: String
      
      abstract fun writeCode()
      
      open fun findBugs(): String {
        return "findBus"
      }
    }

    かき混ぜる


    ミキシングはクラス間で何らかのプログラムや方法を結合する.方法は再利用性が高く,柔軟であり,マルチ継承における曖昧性(diamond problem)問題を解決した.
    interface Developer {
      val language: String
      
      fun writeCode() {
        println("write $langauge")
      }
    }
    
    interface Backend: Developer {
      fun operateEnvironment(): String {
        return "operateEnvironment"
      }
      
      override val langauge: String
        get() = "Haskell"
    }
    
    interface Frontend: Developer {
      fun drawUI(): String {
        return "drawUI"
      }
      
      override val language: String
        get() = "Elm"
    }
    
    class FullStack: Frontend, Backend {
      override val language: String
        get() = super<Frontend>.language + super<Backend>.language
    }
    FrontendとBackendの言語を混ぜ合わせる.

    宣言タイプクラスとタイプクラスのインスタンス


    関数型言語では、タイプクラスは타입의 행위를 선하는 방법を意味します.
    コトリンでは、インタフェースと同様に、次の機能があります.
  • 行為を宣言することができます.
  • が必要であれば、行為の実施部を含んでもよい.
  • コトリンのタイプクラス宣言を見てみましょう.
    interface Eq<in T> {
      fun equal(x: T, y: T): Boolean
      fun notEqual(x: T, y: T): Boolean
    }
    Eqタイプクラスには、2つの値が等しいかどうかを比較する比較関数NotEqualがあります.
    現在も行為の実施部も含まれているでしょう.
    interface Eq<in T> {
      fun equal(x: T, y: T): Boolean = x == y
      fun notEqual(x: T, y: T): Boolean = x != y
    }
    Eqタイプクラスの動作を持つ代数タイプは、次のように定義できます.
    sealed class TrafficLight: Eq<TrafficLight>
    object Red: TrafficLight()
    object Yellow: TrafficLight()
    object Green: TrafficLight()
    
    fun main(args: Array<String>) {
      println(Red.equal(Red, Yellow))
      println(Red.notEqual(Red, Yellow))
    }