LoginSignup
11
5

More than 5 years have passed since last update.

Nimのややマイナーな言語仕様(funcとnot nil)

Posted at

はじめに

自分なりに検証したメモに近いので、あんまり役に立たない記事です。

まとめ

  • func
    • 副作用ありの場合にコンパイルエラーになってくれる。
    • ただし、仮引数がポインタ(varptr)や、参照型(ref)の場合は、この仮引数を変更しても検知はしてくれない。
  • not nil
    • 指定した型にnilを代入される可能性がある場合にエラーになる。

func 及び noSideEffectプラグマ

公式マニュアルは以下のとおり。(Google翻訳さん)
要するに副作用がないと定義しているのに、副作用があった場合はコンパイルエラーを出してくれる機能の模様。

noSideEffectプラグマは、副作用を持たないようにproc / iteratorをマークするために使用されます。つまり、proc / iteratorはそのパラメータから到達可能な場所のみを変更し、戻り値は引数にのみ依存します。そのパラメータが型var T、ref T、またはptr Tをいずれも持たない場合、これはロケーションが変更されないことを意味します。コンパイラがこれを検証できない場合にproc / iteratorに副作用がないとマークするのは静的エラーです。

特別な意味規則として、組み込みのdebugEchoは副作用がないように見せかけるので、noSideEffectとしてマークされたルーチンのデバッグに使用できます。

funcは副作用のないprocの糖衣構文です。

func{.noSideEffect.}のシンタックスシュガーなので、基本的にはfuncを用いればよいと思われる。

では、この機能「ほんまか?」を検証する。

echoやファイルI/Oするケース

func fileIOFunc(filename:string) = filename.fileWrite("Side Effect")
# Error: 'fileIOFunc' can have side effect

うむ、ちゃんとエラーになる。

関数外の変数の参照するケース

let i = 0 #iへは再代入はできないが、エラーになる。
func varFunc(arg:int):int = i + arg
# Error 'varFunc' can have side effect

うむ、ちゃんとエラーになる。
ちなみに上記のlet i = 0const i = 0に変更すると、コンパイルエラーにはならない。

const i = 0
func varFunc(arg:int):int = i + arg # Ok

呼出すプロシージャが副作用をもっているケース

let i = 0
proc subProc(arg:int):int = i + arg
func callProcFunc(arg:int):int = subProc(arg)
# Error: 'callProcFunc' can have side effect

ふむ、ちゃんとエラーになる。

引数を変更しちゃうケース

func varArgFunc(arg:var int):int = 
  arg = arg + 1
  return arg
var i = 1
echo varArgFunc(i) # 2

コンパイルエラーにはならず。これは前述の

そのパラメータが型var T、ref T、またはptr Tをいずれも持たない場合

に該当しないので、副作用ありと判断してくれない模様。ここまで検知はしてくれないか・・・。ちょっと残念?。ref T型を引数に渡すことは普通にありえるがこんなもんなんだろうか?まぁ、わざわざvarをつけるんだから、そりゃそうか・・・という気もしますが。

not nil制約

Kotlinのスマートキャスト機能はスバラシイ。最近のプログラミング言語はNull安全がモダンらしい。
Nimにもnot nilというキーワードを型に指定することができ、nilを許容しない型を定義できる。

not nil利用例

import strformat
# ドキュメントに記載ないが、実験的機能のためプラグマが必要
{.experimental. "notnil".} 

type
  # Nil許可型
  NilablePoint = ref object
    x,y:int
  # Nil不許可型
  Point = NilablePoint not nil

  # 以下の様に継承関係持たせたかったが、うまくいかなかった・・・
  #Point = ref object of NilablePoint not nil

proc `$`(self:NilablePoint):string = fmt"x:{self.x},y:{self.y}"

let p1:Point = nil # nil不許可なのでコンパイルエラー
let p2:NilablePoint = Point(x:1,y:1) #これはコンパイル通る。
let p3:Point = NilablePoint(x:1,y:1) #これもコンパイル通る。
echo p2
echo p3 # $(toString)はNil許可型にバインドされているが、Nil不許可型でもうごく。

まず、KotlinのようにNil不許可型 -> Nil許可型 の継承関係を作ろうとしたが、それをするとなぜかNil不許可型にnilを代入してもコンパイルエラーにならなかった。
おそらく、親の型の特性を引き継ぐ方が優先されている?(ちょっと、良くわからない挙動。。。)

なので仕方なく、Nil許可型のエイリアスとしてNil不許可型を定義して、not nil制約をつけてみると、nilを代入すると正しくコンパイルエラーになってくれた。

nilを返し得るプロシージャとの関係

次にnilを返し得るプロシージャの戻り値を、Nil不許可型で受けたとき

# nilを返しうるプロシージャ
proc nilableProc(n:int):NilablePoint=
  if n > 0 :
    NilablePoint(x:n,y:0)
  else:
    nil

# Nil不許可型にバインドされるプロシージャ
proc showX(self:Point) = echo(self.x)

let p1:Point = nilableProc(1) # Error: cannot prove 'nilableProc(1)' is not nil
let p2:NilablePoint = nilableProc(1) # これは通る
p2.showX # これは通らない #Error: cannot prove 'p2' is not nil

エラーが出てほしいところでは、期待通り以下のようなコンパイルーエラーが発生してくれるので、Nil安全な模様。

Error: cannot prove 'nilableProc(1)' is not nil
(Nilにならないと証明できない)

で、使いやすいのか?

Kotlinと比べると煩雑&中途半端な気がします。実装の仕方によってヌルポを軽減はできるけれど、完全抑止は難しそう。
基本的にはNil許可(あえてnot nilと付けないといけない)なんで、今の所オマケっぽい機能に感じました。

まだ「実験的」機能のため、今後に期待。

11
5
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
11
5