読者です 読者をやめる 読者になる 読者になる

yaml上の言語での戻り値について

github.com

yaml上の言語での戻り値について考える事がある。だいたい以下の2つについて考えていた。

  • Noneが戻り値の関数の扱い
  • 戻り値の解釈の仕方

Noneが戻り値の関数の扱い

通常のpythonの関数でreturnを書かなかった場合にはNoneが返る。

def f():
    pass


print(f())  # None

これをyaml上での言語はどうしたら良いかという話。はじめはpythonに倣って戻り値のない関数は戻り値を持たないものとして実装していた。内部的な実装は結構泥臭くって、変換の結果がmissingという値なら変換後のyamlの値から取り除くみたいな感じのコードで if return_value is None: return missing みたいなコードが入っていた。

ただよく考えると、JSONにはnullが存在するし(python上ではNone)、yamlでもpairのないobjectの定義はNoneになる。Noneが値として存在しているというところがむずかしい。安易にNoneとmissingを同一視してしまっていたのを止めることにした。

では、戻り値を持たない関数はどう書くかと言うと、sideeffect というデコレータを作った。これをつけると戻り値を持たない関数になる。例えば、昨日作ったimportの機能などは全部この sideeffect をつけることにした。

from zenmai.decorators import sideeffect


@sideeffect
def f(d):
    print("yay", d)

名前として適切かどうかは微妙なところだけれど。まぁ副作用ではあるし良いかみたいな感じ。

戻り値を捨てるような部分をどうするかというのがまだ決まっていない

ところで戻り値を捨てるような部分をどうするかというのがまだ決まっていない。例えばyaml上でimportする時には現在以下の様に書くのだけれど。

code:
  $import: math
n:
  $math.floor: 10.5

ここでのcodeに意味はない。ただ、codeは$による修飾もない部分なのに消えてしまうというのは混乱を招きそうな気がする。例えば、$importなどはNoneを返すままの実装にしておき、戻り値を元のyamlに加えないような機能を持つ関数でwrapするというような考えの方が良いのかもしれない。その場合には、やっぱり1階層分全て1つの関数呼び出しと言うのは不便なのかもしれない。

$code:
  - $import: math
  - $import: string

そういえば、変数の参照をどうすれば良いのかというのが明らかになっていないかも(代入も含めて)?

戻り値の解釈の仕方

戻り値の解釈の仕方もあんまり決めていなかった。$で始まるpairは関数呼び出し(action)として扱うということは決めていたけれど、その関数呼び出しの戻り値をどうするかについては考えていなかった。もう少し具体的にいうと、戻り値として返すdictが$で始まるpairを含んでいた場合にどう解釈するかを決めていなかった。

元々の実装では、関数呼び出し(action)は一度きりという風に実装していたけれど、特にそうする必要もない気がしたので、戻り値もまた評価器による評価の対象になる用にした。具体的には以下の様なコードを書いたときの結果が変わる。

def inc(n):
    return n + 1


def inc2(n):
    return {"$inc": n + 1}

を使った以下のyaml

n:
  $inc2: 10

以下の様に変わる。

n: 12

(以前は $inc: 11だった)

たぶん、まだ実装していないけれどquoteのようなものも必要になってくる気がしている。