$30 off During Our Annual Pro Sale. View Details »

Pythonからみる多相の話

 Pythonからみる多相の話

PyCon mini Hiroshima 2016 の発表資料の一部

出てくるコードのソースコード
https://gist.github.com/eiel/089b705820ce1c83de22440a3252029d

Tomohiko Himura

November 11, 2016
Tweet

More Decks by Tomohiko Himura

Other Decks in Programming

Transcript

  1. Python͔ΒΈΔଟ૬ͷ࿩
    2016-11-21 PyCon mini Hiroshima 2016

    View Slide

  2. ͻΉΒ ͱ΋ͻ͜
    ΞυϗοΫͱ͍͏ݴ༿ͷҙຯ͕ͳ͔ͳ͔ཧղ
    Ͱ͖ͳ͔ͬͨ

    View Slide

  3. ଟ૬ͷ࿩

    View Slide

  4. ポリモーフィズムとか
    多態性とか

    View Slide

  5. 同じ名前の関数を
    状況に応じて
    選択してくれる機能

    View Slide

  6. from __future__ import print_function
    class Hello:
    def say(self):
    print("Hello")
    class Goodbye:
    def say(self):
    print("Goodbye")
    2つのクラスがあるとします
    sayメソッドを持つ

    View Slide

  7. def say(self):
    self.say()
    self.say()が呼ばれると

    何がおきる?

    View Slide

  8. def say(self):
    self.say()
    self.say()が呼ばれると

    何がおきる?
    selfの種類によって決まる

    View Slide

  9. say(Hello()) # Hello
    say(Goodbye()) # Goodbye

    View Slide

  10. from __future__ import print_function
    class Hello:
    def say(self):
    print("Hello")
    class Goodbye:
    def say(self):
    print("Goodbye")
    selfがHelloの時の処理
    selfがGoodbyeの時の処理
    HelloクラスとGoodbyeクラスは
    別のファイルにあっても良い

    View Slide

  11. def say(self):
    self.say()
    def say(self):
    if isinstance(self, Hello):
    Hello.say(self)
    if isinstance(self, Goodbye):
    Goodbye.say(self)

    View Slide

  12. def say(self):
    self.say()
    同じように動くようにメソッドを関数
    として使ってみる
    def say(self):
    if isinstance(self, Hello):
    Hello.say(self)
    if isinstance(self, Goodbye):
    Goodbye.say(self)

    View Slide

  13. 多相というのは
    プログラミング言語が
    分岐を自動的に実装してくれる
    機能と考える事ができます

    View Slide

  14. ࿩͔͚Δ૬खΛ
    ม͑ΒΕΔΑ͏ʹͯ͠Έ·͢

    View Slide

  15. from __future__ import print_function
    class Hello:
    pass
    class Goodbye:
    pass
    class Teacher:
    def __init__(self, name):
    self.name = name
    class Friend:
    def __init__(self, name):
    self.name = name

    View Slide

  16. say(Hello(), Teacher("ʹΌΜ͜")) # ͜Μʹͪ͸ɺʹΌΜ͜ઌੜ
    say(Hello(), Friend("ϞϯςΟ")) # ΍͋ ϞϯςΟ
    say(Goodbye(), Teacher("ʹΌΜ͜")) # ʹΌΜ͜ઌੜɺ͞Α͏ͳΒ
    say(Goodbye(), Friend("ϞϯςΟ")) # ·ͨͶ ϞϯςΟ

    View Slide

  17. def say(self, target):
    if isinstance(self, Hello):
    if isinstance(target, Teacher):
    print("͜Μʹͪ͸ɻ%sઌੜ" % target.name)
    elif isinstance(target, Friend):
    print("΍͋ %s" % target.name)
    elif isinstance(self, Goodbye):
    if isinstance(target, Teacher):
    print("%sઌੜɺ͞Α͏ͳΒ" % target.name)
    elif isinstance(target, Friend):
    print("·ͨͶ %s" % target.name)

    View Slide

  18. つらい

    View Slide

  19. def say(self, target):
    selfの種類によって分岐する
    機能があった
    selfとtargetの種類の組み合わ
    せで分岐する機能があったら便
    利ではないだろうか?

    View Slide

  20. Scalaで できます

    View Slide

  21. case class Hello()

    case class Goodbye()

    case class Teacher(name: String)

    case class Friend(name: String)

    View Slide

  22. // ݺͼग़ͨ͢ΊͷΠϯλʔϑΣΠε

    trait Say[A, B] {

    def say(a: A, b: B): Unit

    }


    // ෼ذΛࣗಈੜ੒͢ΔͨΊͷ৔ॴ

    object Say{

    def say[A, B](a: A, b: B)(implicit obj: Say[A, B]) = {

    obj.say(a,b)

    }

    }

    View Slide

  23. // ࣮ࡍͷಈ࡞

    object SayOps {

    implicit val helloTeacherSay: Say[Hello, Teacher] = new Say[Hello, Teacher] {

    def say(self: Hello, target: Teacher): Unit = {

    println(s"͓͸Α͏͍͟͝·͢ɻ${target.name}ઌੜ")

    }

    }


    implicit val helloFriedSay: Say[Hello, Friend] = new Say[Hello, Friend] {

    def say(self: Hello, target: Friend): Unit = {

    println(s"΍͋ ${target.name}")

    }

    }


    implicit val goodbyeTeacherSay: Say[Goodbye, Teacher] = new Say[Goodbye,
    Teacher] {

    def say(self: Goodbye, target: Teacher): Unit = {

    println(s"${target.name}ઌੜɺ͞Α͏ͳΒ")

    }

    }


    implicit val goodbyeFriendSay: Say[Goodbye, Friend] = new Say[Goodbye,
    Friend] {

    def say(self: Goodbye, target: Friend): Unit = {

    println(s"·ͨͶ ${target.name}")

    }

    }

    }
    分岐後の処理が独立しているのがポイント
    (ifなどの分岐の中にない)

    View Slide

  24. // ࣮ࡍʹಈ͔͢෦෼
    object Main extends App {

    import Say.say

    import SayOps._


    say(Hello(), Teacher("ʹΌΜ͜")) // ͜Μʹͪ͸ɻʹΌΜ͜ઌੜ

    say(Hello(), Friend("ϞϯςΟ")) // ΍͋ ϞϯςΟ

    say(Goodbye(), Teacher("ʹΌΜ͜")) // ʹΌΜ͜ઌੜɺ͞Α͏ͳΒ

    say(Goodbye(), Friend("ϞϯςΟ")) // ·ͨͶ ϞϯςΟ

    }


    View Slide

  25. 大きく5つの部分がある
    • A 分岐の要素となるデータの種類の定義
    • B インターフェイスの定義
    • C 分岐が自動生成される部分
    • D 動作を実装する部分
    • E 使う部分

    View Slide

  26. case class Hello()

    case class Goodbye()

    case class Teacher(name: String)

    case class Friend(name: String)
    A 分岐の要素となるデータの種類の定義

    View Slide

  27. // ݺͼग़ͨ͢ΊͷΠϯλʔϑΣΠε

    trait Say[A, B] {

    def say(a: A, b: B): Unit

    }


    // ෼ذΛࣗಈੜ੒͢ΔͨΊͷ৔ॴ

    object Say{

    def say[A, B](a: A, b: B)(implicit obj: Say[A, B]) = {

    obj.say(a,b)

    }

    }
    B インターフェイスの定義
    C 分岐が自動生成される部分

    View Slide

  28. // ࣮ࡍͷಈ࡞

    object SayOps {

    implicit val helloTeacherSay: Say[Hello, Teacher] = new Say[Hello, Teacher] {

    def say(self: Hello, target: Teacher): Unit = {

    println(s"͓͸Α͏͍͟͝·͢ɻ${target.name}ઌੜ")

    }

    }


    implicit val helloFriedSay: Say[Hello, Friend] = new Say[Hello, Friend] {

    def say(self: Hello, target: Friend): Unit = {

    println(s"΍͋ ${target.name}")

    }

    }


    implicit val goodbyeTeacherSay: Say[Goodbye, Teacher] = new Say[Goodbye,
    Teacher] {

    def say(self: Goodbye, target: Teacher): Unit = {

    println(s"${target.name}ઌੜɺ͞Α͏ͳΒ")

    }

    }


    implicit val goodbyeFriendSay: Say[Goodbye, Friend] = new Say[Goodbye,
    Friend] {

    def say(self: Goodbye, target: Friend): Unit = {

    println(s"·ͨͶ ${target.name}")

    }

    }

    }
    D 動作を実装する部分

    View Slide

  29. // ࣮ࡍʹಈ͔͢෦෼
    object Main extends App {

    import Say.say

    import SayOps._


    say(Hello(), Teacher("ʹΌΜ͜")) // ͜Μʹͪ͸ɻʹΌΜ͜ઌੜ

    say(Hello(), Friend("ϞϯςΟ")) // ΍͋ ϞϯςΟ

    say(Goodbye(), Teacher("ʹΌΜ͜")) // ʹΌΜ͜ઌੜɺ͞Α͏ͳΒ

    say(Goodbye(), Friend("ϞϯςΟ")) // ·ͨͶ ϞϯςΟ

    }

    E 使う部分

    View Slide

  30. Haskellだと

    View Slide

  31. {-# LANGUAGE MultiParamTypeClasses #-}

    import Text.Printf


    data Hello = Hello

    data Goodbye = Goodbye

    data Teacher = Teacher String

    data Friend = Friend String
    A 分岐の要素となるデータの種類の定義
    おまじない

    View Slide


  32. class Say a b where

    say :: a -> b -> IO ()

    B インターフェイスの定義

    View Slide

  33. instance Say Hello Teacher where

    say _ (Teacher name) = printf "͜Μʹͪ͸ɻ%sઌੜ\n" name


    instance Say Hello Friend where

    say _ (Friend name) = printf "΍͋ %s\n" name


    instance Say Goodbye Teacher where

    say _ (Teacher name) = printf "%sઌੜɺ͞Α͏ͳΒ\n" name


    instance Say Goodbye Friend where

    say _ (Friend name) = printf "·ͨͶ %s\n" name
    D 動作を実装する部分

    View Slide

  34. main = do

    say Hello (Teacher "ʹΌΜ͜")

    say Hello (Friend "ϞϯςΟ")

    say Goodbye (Teacher "ʹΌΜ͜")

    say Goodbye (Friend "ϞϯςΟ")
    E 使う部分

    View Slide

  35. シンプル

    View Slide

  36. Pythonでも…?

    View Slide

  37. def say(self, target):
    self で分岐できるのだから
    targetをselfにまとめればよい

    View Slide

  38. class HelloTeacher:
    def __init__(self, name):
    self.name = name
    def say(self):
    print("͜Μʹͪ͸ɻ%sઌੜ" % self.name)
    class HelloFriend:
    def __init__(self, name):
    self.name = name
    def say(self):
    print("΍͋ %s" % self.name)
    class GoodbyeTeacher:
    def __init__(self, name):
    self.name = name
    def say(self):
    print("%sઌੜɺ͞Α͏ͳΒ" % self.name)
    class GoodeByeFriend:
    def __init__(self, name):
    self.name = name
    def say(self):
    print("·ͨͶ %s" % self.name)
    A 分岐の要素となるデータの種類の定義
    D 動作を実装する部分

    View Slide

  39. def say(self):
    self.say()
    say(HelloTeacher("ʹΌΜ͜"))
    say(HelloFriend("ϞϯςΟ"))
    say(GoodbyeTeacher("ʹΌΜ͜"))
    say(GoodbyeFriend("ϞϯςΟ"))
    C 分岐が自動生成される部分
    E 使う部分

    View Slide

  40. デメリットはあるけど
    同じことはできる

    View Slide

  41. σʔλ ΠϯλʔϑΣΠε ੜ੒Օॴ ࣮૷
    1ZUIPO ඞཁ
    μοΫλΠϐϯά
    Ͱෆཁ
    ඞཁ ඞཁ
    4DBMB ඞཁ ඞཁ ඞཁ ඞཁ
    )BTLFMM ඞཁ ඞཁ ෆཁ ඞཁ

    View Slide

  42. ·ͱΊ

    View Slide

  43. 多相は引数の種類によって処理を変
    える機能と考えることができる
    Pythonではメソッドにselfがあるた
    め説明しやすい
    言語よっては第1引数のself以外で
    も処理を変えられる
    アドホック多相や型クラスで調べると出てきます

    View Slide

  44. できることはifなどをつかって
    表現できることと大差ない
    工夫しだいでは近いことができる
    無くても困らないけどあると便利

    View Slide

  45. コード
    • https://gist.github.com/eiel/
    089b705820ce1c83de22440a3252029d

    View Slide