LoginSignup
12
9

More than 3 years have passed since last update.

Nimでclass定義

Last updated at Posted at 2019-10-29

はじめに

Nimはサポートは最小限だけどOOP(オブジェクト指向プログラミング)ができる。

NimでOOPのやり方

以下に良記事があるので参考されたしー。

マクロを使ってclass定義

上のNim by Exampleにマクロを使っていい感じにclass定義できるマクロが載ってる
Nim by Example - Macros
こんな感じに定義できる。

クラス定義コード引用
class Animal of RootObj:
  var name: string
  var age: int
  method vocalize: string = "..."
  method age_human_yrs: int = self.age  # `self` is injected

class Dog of Animal:
  method vocalize: string = "woof"
  method age_human_yrs: int = self.age * 7

class Cat of Animal:
  method vocalize: string = "meow"

イケてるけど

pythonみたいにself使えたり
new+クラス名でコンストラクタを定義できたり
かなりイケてるんだけどちょっと不満点が。

継承元を定義する必要がある。

継承元を定義しなかったらRootObjを継承させればいんじゃね?

exportが不自然

できるけどこんな感じクラス名とアスタリスクの間にスペースがはいるのでちょっと不自然。

class Animal * of RootObj:
  var name: string

もうデフォでexportすればいいんじゃね(過激派)

オブジェクト以外も定義したい

タプルとかも同じように書いて関数を定義できたらいいなと。
こんな感じでさ。

class Size of tuple[width, height: int]:
  proc area(): int =
    self.width * self.height

methodとprocしか定義できない

IteratorとかTemplateも定義したいね。

作った

個人的に使う分には問題ない。

マクロ

class.nim
import macros

proc typeName(head: NimNode): NimNode =
  if head.len == 0:head else: head[1]

proc baseName(head: NimNode): NimNode =
  if head.len == 0: newIdentNode("RootObj") else: head[2]

proc isObjectDef(head: NimNode): bool =
  head.len == 0 or head[2].kind == nnkIdent

proc buildObjectTypeDecl(head: NimNode): NimNode =
  template typeDecl(a, b): untyped =
    type a* = ref object of b
  getAst(typeDecl(head.typeName, head.baseName))

proc buildBasicTypeDecl(head: NimNode): NimNode =
  newNimNode(nnkTypeSection)
    .add(newNimNode(nnkTypeDef)
      .add(newIdentNode($head[1]))
      .add(newNimNode(nnkEmpty))
      .add(head[2]))

proc buildTypeDecl(head: NimNode): NimNode =
  if head.isObjectDef:
    head.buildObjectTypeDecl
  else:
    head.buildBasicTypeDecl

macro class*(head, body: untyped): untyped =
  let
    typeName = head.typeName
    ctorName = newIdentNode("new" & $typeName)
    isObjectDef = head.isObjectDef
  var recList = if isObjectDef: newNimNode(nnkRecList) else: nil
  result = newStmtList()
  result.add(head.buildTypeDecl)
  for node in body.children:
    case node.kind:
      of nnkMethodDef, nnkProcDef, nnkIteratorDef, nnkTemplateDef:
        if node.name.kind != nnkAccQuoted and node.name == ctorName:
          node.params[0] = typeName
        else:
          node.params.insert(1, newIdentDefs(ident("self"), typeName))
        result.add(node)
      of nnkVarSection:
        if not isObjectDef:
          error "Invalid node: " & node.lispRepr
        for n in node.children:
          recList.add(n)
      else:
        result.add(node)
  if isObjectDef:
    result[0][0][2][0][2] = recList

when isMainModule:
  class Animal:
    var name: string
    var age: int

    method vocalize: string {.base.} =
      "..."

    method age_human_yrs: int {.base.} =
      self.age

  class Dog of Animal:
    method vocalize: string =
      "woof"

    method age_human_yrs: int =
      self.age * 7

  class Cat of Animal:
    method vocalize: string =
      "meow"

  class Rabbit of Animal:
    proc newRabbit(name: string, age: int) =
      result = Rabbit(name: name, age: age)

    method vocalize: string =
      "meep"

    proc `$`: string =
      "rabbit:" & self.name & ":" & $self.age

  let rabbit = newRabbit("Fluffy", 3)
  var animals: seq[Animal] = @[
    Dog(name: "Sparky", age: 10),
    Cat(name: "Mitten", age: 10),
    rabbit
  ]
  for a in animals:
    echo a.vocalize()
    echo a.age_human_yrs()
  echo rabbit


サンプル

sample.nim
import class
import strformat

class Person:
  var name: string

  proc newPerson(name: string): Person =
    Person(name: name)

  method greed() {.base.} =
    echo fmt"hello my name is {self.name}"

class Size of tuple[width, height: int]:
  proc area(): int =
    self.width * self.height

class FizzBuzz:
  var max: int

  iterator items(): string =
    for n in 1..self.max:
      var str: string
      if n mod 3 == 0: str = "fizz"
      if n mod 5 == 0: str &= "buzz"
      if str.len == 0: str = $n
      yield str

class TemplateSample:
  var name: string

  template nameWith[T](other: T): string =
    self.name & " and " & $other

when isMainModule:
  newPerson("Alice").greed
  echo (width:2, height:3).area
  for s in FizzBuzz(max: 15):
    echo s
  let ts = TemplateSample(name: "Bob")
  echo ts.nameWith(43)
  echo ts.nameWith('a')
  echo ts.nameWith("string")
実行結果
hello my name is Alice
6
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
Bob and 43
Bob and a
Bob and string

以上〜。

12
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9