Lispマクロが特別な理由は何ですか?


297

プログラミング言語に関するPaul Grahamのエッセイを読むと、Lispマクロが唯一の方法だと思うでしょう。他のプラットフォームで作業している多忙な開発者として、私はLispマクロを使用する特権を持っていませんでした。話題を理解したい人として、この機能が非常に強力になる理由を説明してください。

これを、Python、Java、C#、またはC開発の世界から理解できるものと関連付けてください。


2
ちなみに、LMPと呼ばれるC#用のLISPスタイルのマクロプロセッサがあります:ecsharp.net/lemp ... JavaScriptにもSweet.jsと呼ばれるものがあります:sweetjs.org
Qwertie

@Qwertie最近、sweetjsも機能しますか?
fredrik.hjarner

私はそれを使用していませんが、最新のコミットは6か月前でした...私には十分です!
Qwertie 2018年

回答:


308

簡単に言えば、マクロはCommon Lispまたはドメイン固有言語(DSL)の言語構文拡張を定義するために使用されます。これらの言語は、既存のLispコードに直接埋め込まれています。現在、DSLの構文はLisp(Peter NorvigのCommon Lisp のProlog Interpreterなど)と同じか、まったく異なる(例:ClojureのInfix Notation Math)ことができます。

以下に、より具体的な例を示します
。Pythonには、言語に組み込まれたリスト内包表記があります。これは、一般的なケースの単純な構文を提供します。この線

divisibleByTwo = [x for x in range(10) if x % 2 == 0]

で0と9.バックの間のすべての偶数を含むリスト生成のPython 1.5はそのような構文がありませんでした日。次のようなものを使用します。

divisibleByTwo = []
for x in range( 10 ):
   if x % 2 == 0:
      divisibleByTwo.append( x )

これらは両方とも機能的に同等です。不信の一時停止を呼び出して、Lispには反復を実行するだけの非常に限定されたループマクロがあり、リスト内包に相当する簡単な方法がないとしましょう。

Lispでは、次のように書くことができます。この不自然な例は、Pythonコードと同一になるように選択されており、Lispコードの良い例ではないことに注意してください。

;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
      (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))

;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)

;; loop from 0 upto and including 9
(loop for x in (range 10)
   ;; test for divisibility by two
   if (= (mod x 2) 0) 
   ;; append to the list
   do (setq divisibleByTwo (append divisibleByTwo (list x))))

先に進む前に、マクロとは何かについてよりよく説明する必要があります。これは、コード上で実行変換であることによりコード。つまり、コードを引数として取り、インタープリター(またはコンパイラー)によって読み取られたコードの一部が操作され、結果が返されて、その場で実行されます。

もちろん、それは多くのタイピングであり、プログラマーは怠惰です。したがって、リストを理解するためにDSLを定義できます。実際、すでに1つのマクロ(ループマクロ)を使用しています。

Lispはいくつかの特別な構文形式を定義しています。引用符(')は、次のトークンがリテラルであることを示します。準引用符またはバックティック(`)は、次のトークンがエスケープ付きのリテラルであることを示します。エスケープはカンマ演算子で示されます。リテラル'(1 2 3)はPythonのと同等です[1, 2, 3]。別の変数に割り当てるか、そのまま使用できます。`(1 2 ,x)Pythonの[1, 2, x]where xが以前に定義された変数であると同等と考えることができます。このリスト表記は、マクロに含まれる魔法の一部です。2番目の部分は、コードをマクロにインテリジェントに置き換えるLispリーダーですが、以下にその例を示します。

したがって、lcomp(リスト内包表記の略)というマクロを定義できます。構文は、例で使用したpythonとまったく同じです[x for x in range(10) if x % 2 == 0] -(lcomp x for x in (range 10) if (= (% x 2) 0))

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them 
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
            ;; conditional is if or unless
            ;; conditional-test is (= (mod x 2) 0) in our examples
            ,conditional ,conditional-test
            ;; and this is the action from the earlier lisp example
            ;; result = result + [x] in python
            do (setq ,result (append ,result (list ,expression))))
           ;; return the result 
       ,result)))

これでコマンドラインで実行できます:

CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)

かなりきちんとね?今はそれだけではありません。あなたは、あなたが好きなら、メカニズム、または絵筆を持っています。必要に応じて任意の構文を使用できます。PythonやC#のwith構文と同じです。または.NETのLINQ構文。結局、これは人々をLispに引き付けるものです-究極の柔軟性。


51
Lispでリスト内包表記を実装するための+1、なぜそうしないのですか?
ckb

9
:@ckb実際にはLISPすでに標準ライブラリのリスト内包マクロあり(loop for x from 0 below 10 when (evenp x) collect x)ここではより多くの例を。しかし、実際には、ループは「単なるマクロ」です(実際には、少し前にゼロから再実装しました
スザンヌデュペロン

8
私はそれがかなり無関係であることを知っていますが、構文と構文解析が実際にどのように機能するのか疑問に思っています... lcompをそのように呼び出します(thirs項目を「for」から「azertyuiop」に変更します):(lcomp x azertyuiop x in(範囲10)if(=(%x 2)0))マクロは引き続き期待どおりに機能しますか?または、「for」パラメータがループで使用されているため、呼び出されたときに文字列「for」である必要がありますか?
dader 2013年

2
@daderつまり、ループは実際には別のマクロであり、forはそのマクロで使用される特別な記号です。ループマクロがなくても、マクロを簡単に定義できました。しかし、私が持っていれば、投稿はずっと長くなっていたでしょう。ループマクロとそのすべての構文はCLtLで定義されています
gte525u 14年

3
私が他の言語のマクロと混同している1つのことは、それらのマクロがホスト言語の構文によって制限されていることです。LispyマクロはLispy以外の構文を解釈できますか?つまり、構文のようなハスケル(パランテーゼなし)を作成し、Lispマクロを使用して解釈することを想像するようなものです。これは可能ですか?レクサーとパーサーを直接使用する場合と比較して、マクロを使用する場合の長所と短所は何ですか?
CMCDragonkai 2014年

105

ここではlispマクロに関する包括的な議論が行われます

その記事の興味深いサブセット:

ほとんどのプログラミング言語では、構文は複雑です。マクロは、プログラムの構文を分解して分析し、再構成する必要があります。彼らはプログラムのパーサーにアクセスできないため、ヒューリスティックスと最良の推測に依存する必要があります。時々彼らのカットレート分析は間違っている、そしてそれから彼らは壊れる。

しかし、Lispは異なります。Lispマクロパーサーにアクセスできますが、これは本当にシンプルなパーサーです。 Lispプログラムのソースは文字列ではないため、Lispマクロは文字列ではなく、リストの形式で事前に解析されたソースコードの一部です。リストです。そして、Lispプログラムはリストを分解してそれらを元に戻すのに本当に優れています。彼らはこれを毎日確実に行っています。

以下は拡張された例です。Lispには、割り当てを実行する「setf」と呼ばれるマクロがあります。setfの最も単純な形式は次のとおりです。

  (setf x whatever)

シンボル「x」の値を式「whatever」の値に設定します。

Lispにはリストもあります。「car」関数と「cdr」関数を使用して、それぞれリストの最初の要素またはリストの残りの要素を取得できます。

次に、リストの最初の要素を新しい値に置き換えたい場合はどうでしょうか。それを行うための標準機能があり、信じられないことに、その名前は「車」よりもさらに悪いです。「rplaca」です。ただし、「rplaca」を覚えておく必要はありません。

  (setf (car somelist) whatever)

somelistの車を設定します。

ここで実際に起こっていることは、「setf」がマクロであることです。コンパイル時に、引数を調べ、最初の引数が(car SOMETHING)という形式であることを確認します。それはそれ自体に「ああ、プログラマーは何かの車をセットしようとしています。そのために使用する関数は「rplaca」です。」そして、静かにコードを書き直して次のようにします。

  (rplaca somelist whatever)

5
setfは、マクロの力を示す素晴らしいイラストです。
Joel

Lispプログラムのソースは文字列ではないので、私はハイライトが好きです。リストです。!括弧が原因で、LISPマクロが他のマクロよりも優れている主な理由は何ですか?
学生

@学生私はそう思います:books.google.fr/…はあなたが正しいことを示唆しています。
VonC

55

Common Lispマクロは基本的に、コードの「構文プリミティブ」を拡張します。

たとえば、Cでは、switch / caseコンストラクトは整数型でのみ機能し、floatまたはstringに使用する場合は、ネストされたifステートメントと明示的な比較が残ります。また、Cマクロを記述してその仕事を行う方法もありません。

しかし、lispマクロは(本質的に)コードのスニペットを入力として受け取り、マクロの「呼び出し」を置き換えるコードを返すlispプログラムであるため、「プリミティブ」レパートリーを必要なだけ拡張して、通常はより読みやすいプログラムで。

Cで同じことを行うには、最初の(完全なCではない)ソースを食べ、Cコンパイラが理解できるものを吐き出すカスタムプリプロセッサを作成する必要があります。これを実行するのは間違った方法ではありませんが、必ずしも最も簡単であるとは限りません。


41

Lispマクロを使用すると、(もしあれば)どの部分または式を評価するかを決定できます。簡単な例として、Cについて考えてみましょう。

expr1 && expr2 && expr3 ...

これが言うことは:評価expr1、そして、それが本当なら、評価expr2、などです。

これ&&を関数にしてみてください...そうです、できません。次のようなものを呼び出す:

and(expr1, expr2, expr3)

間違っていたexprsかどうかexpr1に関係なく、答えを出す前に3つすべてを評価します!

lispマクロを使用すると、次のようなコードを記述できます。

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

&&これで、関数のように呼び出すことができるがあり、それらがすべてtrueでない限り、渡されたフォームを評価しません。

これがどのように役立つかを見るために、対照的に:

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

そして:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

マクロを使用して実行できる他のことは、新しいキーワードやミニ言語((loop ...)例についてはマクロを確認してください)の作成、他の言語のlispへの統合などです。たとえば、次のようなことを言うマクロを作成できます。

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

そして、それはリーダーマクロにさえ入りません。

お役に立てれば。


「(適用&&、@ exprs);これは間違いかもしれません!」
Svante

1
@svante-2つの点で:最初に&&は関数ではなくマクロです。applyは関数に対してのみ機能します。次に、applyは渡す引数のリストを取って渡すため、「(funcall fn、@ exprs)」、「(apply fn(list、@ exprs)」または「(apply fn、@ exprs nil)」のいずれかが必要です。 "(apply fn、@ exprs)"。–
アーロン

(and ...は、1つがfalseと評価されるまで式を評価します。false評価によって生成される副作用が発生し、後続の式のみがスキップされることに注意してください。
ocodo 2013年

31

Lispマクロがこのフェローよりよく説明されているのを見たことがないと思います:http : //www.defmacro.org/ramblings/lisp.html


3
特にJava / XMLのバックグラウンドがある場合。
sunsations

1
土曜日の午後にソファに横になっているこれを読むのはなんとうれしいことでしょう。非常に明確に書かれ、整理されています。
コリン

10

マクロとテンプレートを使用してCまたはC ++で実行できることを考えてください。これらは反復的なコードを管理するための非常に便利なツールですが、非常に厳しい方法で制限されています。

  • 制限されたマクロ/テンプレート構文はそれらの使用を制限します。たとえば、クラスや関数以外に展開するテンプレートを作成することはできません。マクロとテンプレートは、内部データを簡単に維持できません。
  • CおよびC ++の複雑で非常に不規則な構文は、非常に一般的なマクロを書くことを困難にします。

LispおよびLispマクロはこれらの問題を解決します。

  • LispマクロはLispで書かれています。あなたはマクロを書くためのLispの全力を持っています。
  • Lispの構文は非常に規則的です。

C ++をマスターしている人と話し、テンプレートメタプログラミングを行うために必要なすべてのテンプレートの手間をどれだけの時間学習したかを尋ねます。あるいは、Modern C ++ Designのような(優れた)本のすべてのクレイジーなトリックは、デバッグが難しく、言語は10年間標準化されていますが、実際のコンパイラ間では(実際には)移植できません。メタプログラミングに使用する言語がプログラミングに使用する言語と同じである場合、それらすべてが解消されます。


11
まあ、公平に言うと、C ++テンプレートメタプログラミングの問題は、メタプログラミング言語が異なるということではなく、恐ろしいものです-非常に単純なテンプレート機能を意図したもので発見されたほど、それほど設計されていませんでした。
Brooks Moses

@ブルックス確かに。創発的な機能は必ずしも悪いわけではありません。残念ながら、委員会主導の動きの遅い言語では、修正されたときに修正することは困難です。C ++の最新の便利な新機能の多くは、ほとんど読むことのできない言語で記述されているため、平均的なプログラマと「大祭司」との間には大きなギャップがあります。
Matt Curtis

2
@downvoter:私の回答に何か問題がある場合は、コメントを残してください。そうすれば、全員が知識を共有できます。
Matt Curtis

10

lispマクロは、プログラムフラグメントを入力として受け取ります。このプログラムフラグメントは、任意の方法で操作および変換できるデータ構造を表しています。最後に、マクロは別のプログラムフラグメントを出力し、このフラグメントは実行時に実行されます。

C#にはマクロ機能はありませんが、コンパイラーがコードを解析してCodeDOMツリーに変換し、それをメソッドに渡して別のCodeDOMに変換し、その後ILにコンパイルした場合も同等です。

これは、for each-statement using-clause、linq -expressions などの「糖」構文selectを、基礎となるコードに変換するマクロとして実装するために使用できます。

Javaにマクロがある場合、Sunが基本言語を変更する必要なく、JavaにLinq構文を実装できます。

以下は、実装用のC#のlispスタイルのマクロusingがどのように見えるかを示す疑似コードです。

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }

C#用のLispスタイルのマクロプロセッサが実際にあるので、のマクロusingは次のようになると指摘します;)
Qwertie

9

既存の回答は、マクロが何をどのように達成するかを説明する具体的な例を提供しているため、おそらく、マクロ機能が他の言語に比べて大幅に向上している理由についていくつかの考えをまとめることが役立つでしょう。最初にこれらの答えから、次に他の場所からの素晴らしい答え:

... Cでは、カスタムプリプロセッサを作成する必要があります[これは、おそらく 十分に複雑なCプログラムと見なされます] ...

Vatine

C ++をマスターしている人と話し、テンプレートメタプログラミング(これほど強力ではない)を行うために必要なすべてのテンプレートの手間をどれだけ学習したかを尋ねます。

マット・カーティス

... Javaではバイトコードウィービングでハッキングする必要がありますが、AspectJなどの一部のフレームワークでは別のアプローチを使用してこれを行うことができますが、それは基本的にハックです。

ミゲル・ピン

DOLISTは、PerlのforeachまたはPythonのforeachに似ています。Javaは、JSR-201の一部として、Java 1.5の「拡張」forループを使用して、同様の種類のループ構成を追加しました。マクロの違いに注意してください。コードに共通のパターンがあることに気付いたLispプログラマは、そのパターンのソースレベルの抽象化をマクロに書くことができます。同じパターンに気づいたJavaプログラマーは、この特定の抽象化が言語に追加する価値があることをSunに納得させる必要があります。次に、SunはJSRを公開し、業界全体の「専門家グループ」を招集して、すべてをハッシュ化する必要があります。Sunによると、このプロセスには平均18か月かかります。その後、コンパイラー作成者はすべて、コンパイラーをアップグレードして新機能をサポートする必要があります。そして、Javaプログラマーのお気に入りのコンパイラーが新しいバージョンのJavaをサポートしたとしても、古いバージョンのJavaとのソース互換性を壊すことが許可されるまで、彼らはおそらく「まだ」新しい機能を使用できません。したがって、Common Lispプログラマーが5分以内に自分で解決できるという煩わしさは、Javaプログラマーを何年も悩ませています。

Peter Pei Seibel、「Practical Common Lisp」


8

みんなの(優れた)投稿に洞察を追加できるかどうかはわかりませんが...

Lispマクロは、Lisp構文の性質上、優れた働きをします。

Lispは非常に一般的な言語です(すべてがリストであると考えてください)。マクロを使用すると、データとコードを同じものとして扱うことができます(lisp式を変更するために、文字列の解析やその他のハックは必要ありません)。これら2つの機能を組み合わせて、コードを変更するための非常にクリーンな方法があります。

編集:私が言おうとしていたのは、Lispがホモニコ性であることです。つまり、lispプログラムのデータ構造はlisp自体で記述されています。

したがって、言語自体を最大限に活用して、言語の上に独自のコードジェネレーターを作成する方法になってしまいます(たとえば、Javaではバイトコードウィービングでハックする必要がありますが、AspectJなどの一部のフレームワークでは、別のアプローチを使用してこれを行います、それは基本的にハックです)。

実際には、マクロを使用すると、追加の言語やツールを学習する必要なく、言語自体の能力を最大限に活用して、lispの上に独自のミニ言語を構築することになります。


1
洞察に満ちたコメントですが、「すべてがリストである」というこの考え方は、新規参入者を怖がらせる可能性があります。リストを理解するには、短所、自動車、CDR、セルを理解する必要があります。より正確には、LispはS-expressionsリストではなくで構成されています。
リバマー2018年

6

Lispマクロは、ほとんどすべての大規模なプログラミングプロジェクトで発生するパターンを表します。最終的に大規模なプログラムでは、コードの特定のセクションがあり、ソースコードをテキストとして出力するプログラムを作成して貼り付けるだけの方が簡単でエラーが発生しにくいことがわかります。

Pythonオブジェクトには2つのメソッド__repr__とがあり__str__ます。 __str__単に人間が読める表現です。 __repr__有効なPythonコードである表現を返します。つまり、有効なPythonとしてインタープリターに入力できるものです。このようにして、実際のソースに貼り付けることができる有効なコードを生成するPythonの小さなスニペットを作成できます。

Lispでは、このプロセス全体がマクロシステムによって形式化されています。確かにそれはあなたが構文への拡張を作成してあらゆる種類の空想的なことをすることを可能にします、しかしそれは実際の有用性は上記によって要約されます。もちろん、Lispマクロシステムがこれらの「スニペット」を言語全体の能力をフルに活用して操作できるようにするのに役立ちます。


1
最初の段落は、Lispの部外者にとって非常に明確です。これは重要です。
ワイルドカード

5

つまり、マクロはコードの変換です。それらは多くの新しい構文構造を導入することを可能にします。たとえば、C#でLINQを検討します。lispには、マクロによって実装される同様の言語拡張機能があります(たとえば、組み込みのループ構造、反復)。マクロはコードの重複を大幅に減らします。マクロを使用すると、«小さな言語»を埋め込むことができます(たとえば、c#/ javaではxmlを使用して構成しますが、lispではマクロで同じことを実現できます)。マクロは、ライブラリの使用の難しさを隠すことがあります。

たとえば、lispでは次のように書くことができます

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

これにより、すべてのデータベース(トランザクション、適切な接続のクローズ、データのフェッチなど)が非表示になりますが、C#では、SqlConnections、SqlCommandsを作成し、SqlParametersをSqlCommandsに追加し、SqlDataReadersでループして、適切にクローズする必要があります。


3

上記はすべてマクロが何であるかを説明していて、クールな例さえありますが、マクロと通常の関数の主な違いは、LISPが関数を呼び出す前にすべてのパラメータを最初に評価することです。マクロの場合はその逆です。LISPは未評価のパラメーターをマクロに渡します。たとえば、関数に(+ 1 2)を渡すと、関数は値3を受け取ります。これをマクロに渡すと、List(+ 1 2)を受け取ります。これはあらゆる種類の信じられないほど有用なことを行うために使用できます。

  • ループやリストの分解などの新しい制御構造を追加する
  • 渡された関数の実行にかかる時間を測定します。関数では、制御が関数に渡される前にパラメーターが評価されます。マクロを使用すると、ストップウォッチの開始と停止の間にコードを接続できます。以下は、マクロと関数にまったく同じコードがあり、出力が大きく異なります。注:これは不自然な例であり、違いをより明確に示すために同一となるように実装が選択されました。

    (defmacro working-timer (b) 
      (let (
            (start (get-universal-time))
            (result (eval b))) ;; not splicing here to keep stuff simple
        ((- (get-universal-time) start))))
    
    (defun my-broken-timer (b)
      (let (
            (start (get-universal-time))
            (result (eval b)))    ;; doesn't even need eval
        ((- (get-universal-time) start))))
    
    (working-timer (sleep 10)) => 10
    
    (broken-timer (sleep 10)) => 0

ところで、Scalaは言語にマクロを追加しました。Lispマクロの美しさは欠けていますが、言語がホモニコ性ではないため、それらを調べる価値は間違いなくあり、最終的には、それらが提供する抽象構文ツリーの方が使いやすいかもしれません。どちらのマクロシステムを好むかを言うのは時期尚早です。
Joerg Schmuecker 2017

2
「LISPは評価されていないパラメーターをマクロに渡します」最後に、それを明白に言う答え。しかし、あなたはその後半を忘れました:マクロの結果は、最初の場所にあったかのように、元の場所の代わりにシステムによって全体が評価される変換されたコードです(それ自体が再び呼び出しでない限り)今回もそのマクロによって変換されるマクロ)。
Will Ness

0

これは一般的なlispクックブックから入手しましたが、lispマクロが優れている理由が説明されていると思います。

「マクロは、Lispコードの通常の部分であり、推定Lispコードの別の部分を操作して、実行可能なLisp(に近いバージョン)に変換します。少し複雑に聞こえるかもしれません。簡単な例を挙げてみましょう。 2つの変数を同じ値に設定するsetqのバージョンです。

(setq2 x y (+ z 3))

z=8xとyの両方が11に設定されている場合(これの使用法は考えられませんが、これは単なる例です。)

setq2を関数として定義できないことは明らかです。x=50およびの場合y=-5、この関数は値50、-5、および11を受け取ります。どの変数が設定されることになっていたかについての知識はありません。私たちが本当に言いたいのは、あなた(Lispシステム)がを見るとき(setq2 v1 v2 e)、それをと同等に扱うこと(progn (setq v1 e) (setq v2 e))です。実際、これは正しくありませんが、今のところは問題ありません。マクロを使用すると、入力パターンを(setq2 v1 v2 e)「出力パターン」に変換するプログラムを指定することで、これを正確に実行できます(progn ...)

これが良かったと思うなら、ここを読み続けることができます:http : //cl-cookbook.sourceforge.net/macros.html


1
定義することが可能であるsetq2場合、関数としてxおよびy参照によって渡されます。CLでそれが可能かどうかはわかりません。したがって、LispやCLを特に知らない人にとっては、これは非常に例証的な例ではありませんIMO
新生児の場合、2015年

@neoascetic CL引数の受け渡しは値のみによるものです(そのため、そもそもマクロが必要です)。ただし、一部の値は(リストのように)ポインターです。
Will Ness

-5

Pythonにはデコレーターがあり、基本的には別の関数を入力として受け取る関数があります。あなたは好きなことをすることができます:関数を呼び出す、何か他のことをする、リソース取得リリースで関数呼び出しをラップするなどですが、その関数の内部を覗くことはできません。より強力にしたいとします。たとえば、デコレータが関数のコードをリストとして受け取ったとすると、関数をそのまま実行できるだけでなく、その一部を実行したり、関数の行を並べ替えたりできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.