Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save genkuroki/e5bf6fe9e2366b817c6605f0df0eb3f2 to your computer and use it in GitHub Desktop.
Save genkuroki/e5bf6fe9e2366b817c6605f0df0eb3f2 to your computer and use it in GitHub Desktop.
Juliaがどうしてobject.method()スタイルを使わないのか?
{
"cells": [
{
"metadata": {},
"cell_type": "markdown",
"source": "# Juliaがどうしてobject.method()スタイルを使わないのか?\n\n* 黒木 玄\n* 2021-05-14~2021-05-15"
},
{
"metadata": {
"toc": true
},
"cell_type": "markdown",
"source": "<h1>Table of Contents<span class=\"tocSkip\"></span></h1>\n<div class=\"toc\"><ul class=\"toc-item\"><li><span><a href=\"#object.methodスタイルを採用するとどうなるか\" data-toc-modified-id=\"object.methodスタイルを採用するとどうなるか-1\"><span class=\"toc-item-num\">1&nbsp;&nbsp;</span>object.methodスタイルを採用するとどうなるか</a></span><ul class=\"toc-item\"><li><span><a href=\"#モジュール-A-で-object.method()-スタイルを採用\" data-toc-modified-id=\"モジュール-A-で-object.method()-スタイルを採用-1.1\"><span class=\"toc-item-num\">1.1&nbsp;&nbsp;</span>モジュール <code>A</code> で <code>object.method()</code> スタイルを採用</a></span></li><li><span><a href=\"#別の人がモジュールBで-object.double()-メソッドを追加\" data-toc-modified-id=\"別の人がモジュールBで-object.double()-メソッドを追加-1.2\"><span class=\"toc-item-num\">1.2&nbsp;&nbsp;</span>別の人がモジュールBで <code>object.double()</code> メソッドを追加</a></span></li><li><span><a href=\"#元のモジュールAにも-object.double()-メソッドが追加された!\" data-toc-modified-id=\"元のモジュールAにも-object.double()-メソッドが追加された!-1.3\"><span class=\"toc-item-num\">1.3&nbsp;&nbsp;</span>元のモジュールAにも <code>object.double()</code> メソッドが追加された!</a></span></li></ul></li><li><span><a href=\"#Juliaのスタイルではどうなるか\" data-toc-modified-id=\"Juliaのスタイルではどうなるか-2\"><span class=\"toc-item-num\">2&nbsp;&nbsp;</span>Juliaのスタイルではどうなるか</a></span></li></ul></div>"
},
{
"metadata": {},
"cell_type": "markdown",
"source": "## object.methodスタイルを採用するとどうなるか"
},
{
"metadata": {},
"cell_type": "markdown",
"source": "### モジュール `A` で `object.method()` スタイルを採用\n\nモジュール `A` の中で、長方形の型 `Rectangle` にメソッド `height`, `width`, `area` を定義し、\n\nそして、`object.method()` スタイルでそれらのメソッドを使えるようにした。"
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "module A\n\nabstract type AbstractRectangle{T} end\nheight(x::AbstractRectangle) = getfield(x, :h)\nwidth(x::AbstractRectangle) = getfield(x, :w)\narea(x::AbstractRectangle) = height(x) * width(x)\n\nstruct Rectangle{T} <: AbstractRectangle{T} h::T; w::T end\n\nfunction Base.getproperty(x::AbstractRectangle, f::Symbol)\n f in fieldnames(typeof(x)) && return getfield(x, f)\n f_d = Symbol(f, \"_\", string(x))\n @eval function $f_d(x...; y...) $f($x, x...; y...) end\nend\n\nBase.propertynames(x::T) where T<: AbstractRectangle = [\n :height, :width, :area\n]\n\nend",
"execution_count": 1,
"outputs": [
{
"output_type": "execute_result",
"execution_count": 1,
"data": {
"text/plain": "Main.A"
},
"metadata": {}
}
]
},
{
"metadata": {
"scrolled": false,
"trusted": true
},
"cell_type": "code",
"source": "R = A.Rectangle(3, 4)\n@show R R.height() R.width() R.area();",
"execution_count": 2,
"outputs": [
{
"output_type": "stream",
"text": "R = Main.A.Rectangle{Int64}(3, 4)\nR.height() = 3\nR.width() = 4\nR.area() = 12\n",
"name": "stdout"
}
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "### 別の人がモジュールBで `object.double()` メソッドを追加\n\nモジュール `A` の作者とは別の人がモジュール `A` の作者に「正方形の型と面積を倍にするメソッドを追加してくれ」と頼んだが拒否された。\n\nそこで、その別の人はモジュール `B` の中で、正方形の型 `Square` を定義し、面積を倍にするメソッドを `object.double()` の形式で使えるようにした。"
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "module B\n\nusing ..A: A\n\n# 正方形の型の追加はうまく行く\nstruct Square{T} <: A.AbstractRectangle{T} s::T end\nside(x::Square) = getfield(x, :s)\nA.height(x::Square) = side(x)\nA.width(x::Square) = side(x)\n\n# 面積を倍にするメソッドの定義もうまく行く\ndouble(x::A.Rectangle) = A.Rectangle(√2*A.height(x), √2*A.width(x))\ndouble(x::Square) = Square(√2*side(x))\n\n# type piracy!\n# 既存の型の引数を持つ新しい函数または\n# 新しい型の引数を持つ既存の函数のオーバーライドは問題ないが、\n# 既存の型の引数を持つ既存の函数のオーバーライドはよくない。\nfunction Base.getproperty(x::A.AbstractRectangle, f::Symbol)\n f in fieldnames(typeof(x)) && return getfield(x, f)\n f_d = Symbol(f, \"_\", string(x))\n if f in (:side, :double)\n @eval function $f_d(y...; z...) $f($x, y...; z...) end\n else\n @eval function $f_d(y...; z...) A.$f($x, y...; z...) end\n end\nend\n\n# type piracy!\nBase.propertynames(x::T) where T<: A.AbstractRectangle = [\n [:height, :width, :area, :double];\n T <: Square ? [:side] : Symbol[]\n]\n\nend",
"execution_count": 3,
"outputs": [
{
"output_type": "execute_result",
"execution_count": 3,
"data": {
"text/plain": "Main.B"
},
"metadata": {}
}
]
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "R = A.Rectangle(3, 4)\n@show R R.height() R.width() R.area()\n@show R.double() R.double().area();",
"execution_count": 4,
"outputs": [
{
"output_type": "stream",
"text": "R = Main.A.Rectangle{Int64}(3, 4)\nR.height() = 3\nR.width() = 4\nR.area() = 12\nR.double() = Main.A.Rectangle{Float64}(4.242640687119286, 5.656854249492381)\n(R.double()).area() = 24.000000000000004\n",
"name": "stdout"
}
]
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "S = B.Square(5)\n@show S S.side() S.height() S.width() S.area()\n@show S.double() S.double().area();",
"execution_count": 5,
"outputs": [
{
"output_type": "stream",
"text": "S = Main.B.Square{Int64}(5)\nS.side() = 5\nS.height() = 5\nS.width() = 5\nS.area() = 25\nS.double() = Main.B.Square{Float64}(7.0710678118654755)\n(S.double()).area() = 50.00000000000001\n",
"name": "stdout"
}
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "### 元のモジュールAにも `object.double()` メソッドが追加された!\n\nところが、しばらくしたら、モジュール `A` の作者はモジュール `A` に、正方形の型 `Square` と(面積ではなく)長さを倍にするメソッド `object.double()` を追加した。\n\n2つの正方形の型は `A.Square` と `B.square` で区別されるので問題は生じない。\n\nしかし、長さを倍にするメソッドと面積を倍にするメソッドの名前がかぶってしまった!\n\nモジュール `B` の作者は困ってしまった。"
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "module A\n\nabstract type AbstractRectangle{T} end\nheight(x::AbstractRectangle) = getfield(x, :h)\nwidth(x::AbstractRectangle) = getfield(x, :w)\narea(x::AbstractRectangle) = height(x) * width(x)\n\nstruct Rectangle{T} <: AbstractRectangle{T} h::T; w::T end\ndouble(x::Rectangle) = Rectangle(2height(x), 2width(x))\n\nstruct Square{T} <: AbstractRectangle{T} s::T end\nside(x::Square) = getfield(x, :s)\nheight(x::Square) = side(x)\nwidth(x::Square) = side(x)\ndouble(x::Square) = Square(2side(x))\n\nfunction Base.getproperty(x::AbstractRectangle, f::Symbol)\n f in fieldnames(typeof(x)) && return getfield(x, f)\n f_d = Symbol(f, \"_\", string(x))\n @eval function $f_d(x...; y...) $f($x, x...; y...) end\nend\n\nBase.propertynames(x::T) where T<: AbstractRectangle = [\n :height, :width, :area, :side, :double\n]\n\nend",
"execution_count": 6,
"outputs": [
{
"output_type": "stream",
"text": "WARNING: replacing module A.\n",
"name": "stderr"
},
{
"output_type": "execute_result",
"execution_count": 6,
"data": {
"text/plain": "Main.A"
},
"metadata": {}
}
]
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "R = A.Rectangle(3, 4)\n@show R R.height() R.width() R.area()\n@show R.double() R.double().area();",
"execution_count": 7,
"outputs": [
{
"output_type": "stream",
"text": "R = Main.A.Rectangle{Int64}(3, 4)\nR.height() = 3\nR.width() = 4\nR.area() = 12\nR.double() = Main.A.Rectangle{Int64}(6, 8)\n(R.double()).area() = 48\n",
"name": "stdout"
}
]
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "S = A.Square(5)\n@show S S.side() S.height() S.width() S.area()\n@show S.double() S.double().area();",
"execution_count": 8,
"outputs": [
{
"output_type": "stream",
"text": "S = Main.A.Square{Int64}(5)\nS.side() = 5\nS.height() = 5\nS.width() = 5\nS.area() = 25\nS.double() = Main.A.Square{Int64}(10)\n(S.double()).area() = 100\n",
"name": "stdout"
}
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "## Juliaのスタイルではどうなるか"
},
{
"metadata": {},
"cell_type": "markdown",
"source": "モジュール `A` の作者は、メソッド達 `height`, `width`, `area`, `double` を `Rectangle` 型の `x` について `f(x)` の形で使えるようにした。モジュール `A` の `double` は長さを倍にする操作になっている。"
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "module A\n\nabstract type AbstractRectangle{T} end\nheight(x::AbstractRectangle) = x.h\nwidth(x::AbstractRectangle) = x.w\narea(x::AbstractRectangle) = height(x) * width(x)\n\nstruct Rectangle{T} <: AbstractRectangle{T} h::T; w::T end\ndouble(x::Rectangle) = Rectangle(2height(x), 2width(x))\n\nend",
"execution_count": 9,
"outputs": [
{
"output_type": "stream",
"text": "WARNING: replacing module A.\n",
"name": "stderr"
},
{
"output_type": "execute_result",
"execution_count": 9,
"data": {
"text/plain": "Main.A"
},
"metadata": {}
}
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "モジュール `B` の作者は `Square` 型と面積を倍にするメソッド `double` を定義した。"
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "module B\n\nusing ..A: A\n\nstruct Square{T} <: A.AbstractRectangle{T} s::T end\nside(x::Square) = x.s\nA.height(x::Square) = side(x)\nA.width(x::Square) = side(x)\nA.double(x::Square) = Square(2side(x))\n\ndouble(x::A.Rectangle) = A.Rectangle(√2*A.height(x), √2*A.width(x))\ndouble(x::Square) = Square(√2*side(x))\n\nend",
"execution_count": 10,
"outputs": [
{
"output_type": "stream",
"text": "WARNING: replacing module B.\n",
"name": "stderr"
},
{
"output_type": "execute_result",
"execution_count": 10,
"data": {
"text/plain": "Main.B"
},
"metadata": {}
}
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "長さを倍にするメソッドと面積を倍にするメソッドは `A.double` と `B.double` で区別されるので問題は生じない。"
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "R = A.Rectangle(3, 4)\n@show A.double(R) A.double(R) |> A.area\n@show B.double(R) B.double(R) |> A.area;",
"execution_count": 11,
"outputs": [
{
"output_type": "stream",
"text": "A.double(R) = Main.A.Rectangle{Int64}(6, 8)\nA.double(R) |> A.area = 48\nB.double(R) = Main.A.Rectangle{Float64}(4.242640687119286, 5.656854249492381)\nB.double(R) |> A.area = 24.000000000000004\n",
"name": "stdout"
}
]
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "S = B.Square(5)\n@show A.double(S) A.double(S) |> A.area\n@show B.double(S) B.double(S) |> A.area;",
"execution_count": 12,
"outputs": [
{
"output_type": "stream",
"text": "A.double(S) = Main.B.Square{Int64}(10)\nA.double(S) |> A.area = 100\nB.double(S) = Main.B.Square{Float64}(7.0710678118654755)\nB.double(S) |> A.area = 50.00000000000001\n",
"name": "stdout"
}
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "その後、モジュール `A` の作者は `Square` 型を追加で定義した。"
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "module A\n\nabstract type AbstractRectangle{T} end\nheight(x::AbstractRectangle) = x.h\nwidth(x::AbstractRectangle) = x.w\narea(x::AbstractRectangle) = height(x) * width(x)\n\nstruct Rectangle{T} <: AbstractRectangle{T} h::T; w::T end\ndouble(x::Rectangle) = Rectangle(2height(x), 2width(x))\n\nstruct Square{T} <: AbstractRectangle{T} s::T end\nside(x::Square) = x.s\nheight(x::Square) = side(x)\nwidth(x::Square) = side(x)\ndouble(x::Square) = Square(2side(x))\n\nend",
"execution_count": 13,
"outputs": [
{
"output_type": "stream",
"text": "WARNING: replacing module A.\n",
"name": "stderr"
},
{
"output_type": "execute_result",
"execution_count": 13,
"data": {
"text/plain": "Main.A"
},
"metadata": {}
}
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "モジュール `B` の作者はモジュール `B` を何も変更しなかった。"
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "module B\n\nusing ..A: A\n\nstruct Square{T} <: A.AbstractRectangle{T} s::T end\nside(x::Square) = x.s\nA.height(x::Square) = side(x)\nA.width(x::Square) = side(x)\nA.double(x::Square) = Square(2side(x))\n\ndouble(x::A.Rectangle) = A.Rectangle(√2*A.height(x), √2*A.width(x))\ndouble(x::Square) = Square(√2*side(x))\n\nend",
"execution_count": 14,
"outputs": [
{
"output_type": "stream",
"text": "WARNING: replacing module B.\n",
"name": "stderr"
},
{
"output_type": "execute_result",
"execution_count": 14,
"data": {
"text/plain": "Main.B"
},
"metadata": {}
}
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "上のコードはそのまま動く。"
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "R = A.Rectangle(3, 4)\n@show A.double(R) A.double(R) |> A.area\n@show B.double(R) B.double(R) |> A.area;",
"execution_count": 15,
"outputs": [
{
"output_type": "stream",
"text": "A.double(R) = Main.A.Rectangle{Int64}(6, 8)\nA.double(R) |> A.area = 48\nB.double(R) = Main.A.Rectangle{Float64}(4.242640687119286, 5.656854249492381)\nB.double(R) |> A.area = 24.000000000000004\n",
"name": "stdout"
}
]
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "S = B.Square(5)\n@show A.double(S) A.double(S) |> A.area\n@show B.double(S) B.double(S) |> A.area;",
"execution_count": 16,
"outputs": [
{
"output_type": "stream",
"text": "A.double(S) = Main.B.Square{Int64}(10)\nA.double(S) |> A.area = 100\nB.double(S) = Main.B.Square{Float64}(7.0710678118654755)\nB.double(S) |> A.area = 50.00000000000001\n",
"name": "stdout"
}
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "`A.Square` 型の `A.double` メソッドも正常に動作している。"
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "S = A.Square(5)\n@show A.double(S) A.double(S) |> A.area;",
"execution_count": 17,
"outputs": [
{
"output_type": "stream",
"text": "A.double(S) = Main.A.Square{Int64}(10)\nA.double(S) |> A.area = 100\n",
"name": "stdout"
}
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "最終的にモジュール `B` の作者はモジュール `B` から `Square` 型を削除し、面積を倍にするメソッド `double` だけを残した。`double` という名前の付け方を変えるつもりはないらしい。"
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "module B\n\nusing ..A: A\n\ndouble(x::A.Rectangle) = A.Rectangle(√2*A.height(x), √2*A.width(x))\ndouble(x::A.Square) = A.Square(√2*A.side(x))\n\nend",
"execution_count": 18,
"outputs": [
{
"output_type": "stream",
"text": "WARNING: replacing module B.\n",
"name": "stderr"
},
{
"output_type": "execute_result",
"execution_count": 18,
"data": {
"text/plain": "Main.B"
},
"metadata": {}
}
]
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "R = A.Rectangle(3, 4)\n@show A.double(R) A.double(R) |> A.area\n@show B.double(R) B.double(R) |> A.area;",
"execution_count": 19,
"outputs": [
{
"output_type": "stream",
"text": "A.double(R) = Main.A.Rectangle{Int64}(6, 8)\nA.double(R) |> A.area = 48\nB.double(R) = Main.A.Rectangle{Float64}(4.242640687119286, 5.656854249492381)\nB.double(R) |> A.area = 24.000000000000004\n",
"name": "stdout"
}
]
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "S = A.Square(5)\n@show A.double(S) A.double(S) |> A.area\n@show B.double(S) B.double(S) |> A.area;",
"execution_count": 20,
"outputs": [
{
"output_type": "stream",
"text": "A.double(S) = Main.A.Square{Int64}(10)\nA.double(S) |> A.area = 100\nB.double(S) = Main.A.Square{Float64}(7.0710678118654755)\nB.double(S) |> A.area = 50.00000000000001\n",
"name": "stdout"
}
]
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "",
"execution_count": null,
"outputs": []
}
],
"metadata": {
"@webio": {
"lastKernelId": null,
"lastCommId": null
},
"_draft": {
"nbviewer_url": "https://gist.github.com/e5bf6fe9e2366b817c6605f0df0eb3f2"
},
"gist": {
"id": "e5bf6fe9e2366b817c6605f0df0eb3f2",
"data": {
"description": "Juliaがどうしてobject.method()スタイルを使わないのか?",
"public": true
}
},
"kernelspec": {
"name": "julia-1.7-depwarn-o3",
"display_name": "Julia 1.7.0-DEV depwarn -O3",
"language": "julia"
},
"language_info": {
"file_extension": ".jl",
"name": "julia",
"mimetype": "application/julia",
"version": "1.7.0"
},
"toc": {
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": true,
"base_numbering": 1,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": true,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment