作業スペース
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    [](https://)[](https://) # Scheme言語 【データ型】 データ型の多くは階層構造になります。Scheme言語には豊富なデータ型があり、いくつかのものは分割不可能なもので、それ以外は他のデータ型を組み合せて合成したデータ型です。 **・単純データ型** Schemeの単純データ型には、真理値、数値、文字、シンボルがあります。 **・真理値** Schemeの真理値は、真は#t、偽は#fです。 Schemeには引数が真理値であるかどうかを判定するboolean?という 述語手続きがあります。 ``` (boolean? #t) => #t (boolean? "Hello, World!") => #f ``` 手続きnotは引数を真理値だと考えてこれを否定します。 ``` (not #f) => #t (not #t) => #f (not "Hello, World!") => #f ``` 最後の式はSchemeの便利なところで、真理値が必要な文脈では、 Schemeは#fではない値はすべて真の値として扱います。 **・数値** Schemeの数値は、整数(たとえば、42)、有理数(22/7)、 実数(3.1416)、複素数(2+3i)になりえます。 整数は有理数の一部であり、有理数は実数の一部であり、 実数は複素数の一部です。いろいろな数の種類について テストする述語が存在します。 ``` (number? 42) => #t (number? #t) => #f (complex? 2+3i) => #t (real? 2+3i) => #f (real? 3.1416) => #t (real? 22/7) => #t (real? 42) => #t (rational? 2+3i) => #f (rational? 3.1416) => #t (rational? 22/7) => #t (integer? 22/7) => #f (integer? 42) => #t ``` (複素数complex、実数real、有理数rational、整数integer) Schemeの整数は10進表示でなければならないわけではないので#bを接頭辞としてもちいれば、2進数表示であることを指定できます。 例:#b1100=12 数値の同値性のチェックには汎用同値性述語eqv?を使うことができます。 ``` (eqv? 42 42) => #t (eqv? 42 #f) => #f (eqv? 42 42.0) => #f ``` しかし、もし比較すべき引数が数値だとわかっているなら、数値専用同値性 述語=を使うのがよりふさわしいです。 ``` (= 42 42) => #t (= 42 #f) -->ERROR!!! (= 42 42.0) => #t ``` そのほかにも、 <、<=、>、>=の比較をすることができます。 ``` (< 3 2) => #f (>= 4.5 3) => #t ``` 算術手続き、+、-、*、/、exptの振舞いは以下の とおりです。 ``` (+ 1 2 3) => 6 (- 5.3 2) => 3.3 (- 5 2 1) => 2 (* 1 2 3) => 6 (/ 6 3) => 2 (/ 22 7) => 22/7 (expt 2 3) => 8 (expt 4 1/2) => 2.0 ``` -および/は、引数が一つの場合には、それぞれ、符号反転、 逆数を返します。 ``` (- 4) => -4 (/ 4) => 1/4 ``` 手続きmaxおよびminはそれぞれ、引数として与えられた数値の 最大、最小を返します。引数の数はいくつでもかまわない。 ``` (max 1 3 4 2 3) => 4 (min 1 3 4 2 3) => 1 ``` 手続きabsは引数であたえられた数値の絶対値を返します。 ``` (abs 3) => 3 (abs -4) => 4 ``` **・文字** Schemeの文字データは接頭辞#\を使って表わします。したがって、 #\cは文字cのことです。いくつかの非表示文字にはより 説明的な名前がついています。たとえば、#\newline、#\tabなどです。 空白文字は#\ 、あるいはより読みやすく#\spaceと 書くことができます。 文字述語はchar?です ``` (char? #\c) => #t (char? 1) => #f (char? #\;) => #t ``` 文字データ型には次の比較述語一式があります。 char=?、char<?、char<=?、char>?、char>=?です。 ``` (char=? #\a #\a) => #t (char<? #\a #\b) => #t (char>=? #\a #\b) => #f ``` 大文字、小文字の違いを無視した比較を行うには、手続き名に charではなくchar-ciのついた述語を使います。 ``` (char-ci=? #\a #\A) => #t (char-ci<? #\a #\B) => #t ``` 大文字、小文字の変換を行う手続き。 ``` (char-downcase #\A) => #\a (char-upcase #\a) => #\A ``` **・シンボル** 上でみた単純データ型はどれも自己評価的であり、これらのデータ型のうちどのオブジェクトをリスナーに入力しても リスナーによって返される評価結果は、入力したものと同じになる。 ``` #t => #t 42 => 42 #\c => #\c ``` シンボルはSchemeのプログラムでは変数の識別子として使われ、評価されて、その変数がもつ値になるからです。それでもなお、 シンボルは単純データ型です。シンボルは正当な値で、文字や数値 その他の値と変換可能なものです。 Schemeによって変数と解釈されないシンボルを指定するには シンボルをクウォートしなければなりません。 ``` (quote xyz) => xyz ``` このタイプのクウォートはSchemeでは良くつかうので、簡略記法が 用意されています。式 'E はSchemeでは以下と同じです。 (quote E) Schemeのシンボルは文字列により名前がついています。 シンボル名の唯一の制限は他のデータ(つまり、文字、真理値)と取り違えるよう なものではいけないということです。それゆえ、this-is-a-symbol、 i18n、<=>、$!#*はすべてシンボルで、16、-i (複素数!) #t、"this-is-a-string"、(barf)(リスト)はシンボルではありません。 シンボルかどうかをチェックするには述語symbol?を使います。 ``` (symbol? 'xyz) => #t (symbol? 42) => #f ``` Scheme のシンボルは通常、大文字と小文字を区別しません。 したがって、Calorieとcalorieは等しいシンボルです。 (eqv? 'Calorie 'calorie) => #t シンボルxyzをフォームdefineをつかってグローバル変数として 利用することができます。 (define xyz 9) これは、変数xyzが値9を保持するといっています。xyzを リスナーに入力すると結果はxyzの保持するその値になります。 xyz => 9 変数が保持している値を変更するには、フォームset!が使えます。 (set! xyz #\c) とすると xyz => #\c 【合成データ型】 合成データ型は構造化された方法で他のデータ型から値を合成することによって構築されます。 **・文字列** 文字列は文字の並び(名前として文字のならびをもつ単純データ、シンボル と混同してはいけません)です。一連の文字の並びを二重引用符で囲むことで 文字列を指定できます。文字列は評価すると、その文字列自身になります。 ``` "Hello, World!" => "Hello, World!" ``` 手続きstringは一文字ずつ文字をとり、その文字からなる文字列を返します。 ``` (string #\H #\e #\l #\l #\o) => "Hello" ``` ではグローバル変数greetingを定義してみる。 ``` (define greeting "Hello; Hello!") ``` 与えられた文字列中の文字は個別にアクセスしたり変更したりすることができます。手続きstring-refは一つの文字列と(0基準の)インデックス をとり、そのインデックスの文字を返します。 ``` (string-ref greeting 0) => #\H ``` 新しい文字列は別の文字列を連結することで作ることができます。 ``` (string-append "E " "Pluribus " "Unum") => "E Pluribus Unum" ``` 長さを指定して文字列を作ることができます。あとから、好きな文字を 埋めることができます。 ``` (define a-3-char-long-string (make-string 3)) ``` 文字列かどうかをチェックする述語はstring?です。 string、make-string、string-appendの結果として 得られた文字列は可変です。手続きstring-set!は与えられた インデックスの文字を置き換えます。 ``` (define hello (string #\H #\e #\l #\l #\o)) hello => "Hello" (string-set! hello 1 #\a) hello => "Hallo" ``` **・ベクタ** ベクタは文字列のような並べですが、その要素は文字以外のものでも かまわない。ベクタ自身でもかまわないので、これは多次元ベクタを 作るよい方法です。 Here’s a way to create a vector of the first five integers: 最初の5つの整数からなるベクタをこのように作ります。 ``` (vector 0 1 2 3 4) => #(0 1 2 3 4) ``` Schemeにおけるベクタの値の表現:要素を括弧でかこみ、#を頭につけます。 make-stringと同様に、プロシージャ手続きmake-vectorは指定した 長さのベクタを作ります。 ``` (define v (make-vector 5)) ``` 手続きvector-refおよびvector-set!はベクタの要素にアクセス あるいは変更します。 ベクタかどうかをチェックする述語はvector?です。 **・ドット対とリスト** ドット対は二つの任意の値を組合せて順序対にすることで 作りだされた合成値です。最初の要素はcar、二番目の要素は cdrと呼ばれ、組合せの手続きはconsです。 ``` (cons 1 #t) => (1 . #t) ``` ドット対は自己評価的ではありません。そこで、データとして 直接(すなわちcons呼び出しによって作りだすことなく)指定するには 明示的にクウォートしなければなりません。 ``` '(1 . #t) => (1 . #t) (1 . #t) -->ERROR!!! ``` アクセサ手続きはcarとcdrです。 ``` (define x (cons 1 #t)) (car x) => 1 (cdr x) => #t ``` ドット対の要素は変更子手続きset-car!およびset-cdr!によって とりかえることができます。 ``` (set-car! x 2) (set-cdr! x #f) x => (2 . #f) ``` ドット対に別のドット対を含めることができます。 ``` (define y (cons (cons 1 2) 3)) y => ((1 . 2) . 3) ``` このリストのcarのcarは1です。 そして、carのcdrは2です。すなわち、 ``` (car (car y)) => 1 (cdr (car y)) => 2 ``` Schemeはcarとcdr手続きのカスケードした組み合わせに 対応する省略形手続きを提供しています。つまり、caarは carのcarを、cdarはcarのcdrを表わします。 ``` (caar y) => 1 (cdar y) => 2 ``` c...rスタイルの省略形は4段のカスケードまでは存在することが 保証されています。つまり、cadr、cdadr、cdaddrはいつでも 使えます。 第二要素についてドットが入れ子になっているとき、その結果の式については Schemeは特別な表記を使います。 ``` (cons 1 (cons 2 (cons 3 (cons 4 5)))) => (1 2 3 4 . 5) ``` つまり、(1 2 3 4 . 5)は(1 . (2 . (3 . (4 . 5)))の 省略形です。この式の最後のcdrは5です。 Schemeは最後のcdrが空リストと呼ばれる特別なオブジェクト (これは()と表現される式)の場合の省略形を提供しています。 空リストは自己評価的ではないと考えられています。なので、 プログラム中で値として使うには、クウォートすべきです '() => () ドット対フォーム(1 . (2 . (3 . (4 . ()))))の省略形は (1 2 3 4) です。 この入れ子になった特別な種類のドット対をリストと呼びます。 件のリストは4要素長であるといいます。これは、 ``` (cons 1 (cons 2 (cons 3 (cons 4 '())))) ``` として作ることもできますが、Schemeにはlistと呼ばれる手続きがあり、 もっと、便利にリストを作ることができます。listは任意個の引数をとり、 それを含むリストを返します ``` (list 1 2 3 4) => (1 2 3 4) ``` どんな要素がリストにあるのかが全部わかっているなら、 リストを指定するのにクウォートがつかえます。 ``` '(1 2 3 4) => (1 2 3 4) ``` リストの要素はインデックスでアクセスすることができます。 ``` (define y (list 1 2 3 4)) (list-ref y 0) => 1 (list-ref y 3) => 4 (list-tail y 1) => (2 3 4) (list-tail y 3) => (4) ``` list-tailは与えられたインデックスから始まる後の 部分のリストを返します。 述語pair?、list?、null?はそれぞれ引数が、ドット対か、 リストか、空リストかを判定します。 ``` (pair? '(1 . 2)) => #t (pair? '(1 2)) => #t (pair? '()) => #f (list? '()) => #t (null? '()) => #t (list? '(1 2)) => #t (list? '(1 . 2)) => #f (null? '(1 2)) => #f (null? '(1 . 2)) => #f ``` **・データ型間の変換** Schemeではデータ型間の変換をおこなう手続きが多数提供されています。 すでに、char-downcaseおよびchar-upcaseをつかって、 大文字小文字変換をする方法を知りました。文字はchar->integer を用いて整数に変換することができます。また整数はinteger->charを 用いて文字に変換することができます(通常、文字に対応する整数というのは その文字のASCIIコードです)。 ``` (char->integer #\d) => 100 (integer->char 50) => #\2 ``` 文字列は対応する文字のリストに変換することができます。 ``` (string->list "hello") => (#\h #\e #\l #\l #\o) ``` おなじ流れで別の変換手続きがあります。 list->string、vector->listおよびlist->vectorです。 数値は文字列に変換することができます。 ``` (number->string 16) => "16" ``` 文字列は数値に変換することができます。もし対応する数字がない場合には #fが返ります。 ``` (string->number "16") => 16 (string->number "Am I a hot number?") => #f ``` string->numberの基数を示す二つ目のオプション引数をとります。 ``` (string->number "16" 8) => 14 ``` 8進数の16 は十四です。 シンボルと文字列は互いに変換できます。 ``` (symbol->string 'symbol) => "symbol" (string->symbol "string") => string ``` **・そのほかのデータ型** Schemeには、そのほかのデータ型がいくつかあります。 ひとつは手続きです。すでにいろいろな手続きを見ました。 たとえば、display、+、consです。実際、これらは 手続き値をもつ変数です。値そのものは数値や文字のように、 目に見えるものではありません。 ``` cons => <procedure ``` いままで見てきた手続きはプリミティブ手続きで、標準的な グローバル変数がこれを保持しています。 ユーザは新しい手続き値を作ることができます。 さらにもうひとつのデータ型はポートです。ポートは これを通じて入出力をおこなうコンジットです。 ポートは通常ファイルやコンソールに結びつけられています。 “Hello, World!” プログラムで、コンソールに文字列を 書くのに手続きdisplayを使いました。displayは 二つの引数をとることができて、ひとつは表示する文字列、 もうひとつは表示すべき出力ポートです。 さきほどのプログラムでは、displayの二つめの引数は 暗黙のものです。デフォルトの出力ポートが使われていて、 これは標準出力ポートです。現在の標準出力ポートは (current-output-port)という手続き呼び出しで得ることができます。 ``` (display "Hello, World!" (current-output-port)) ``` **・S式** ここらでのすべてのデータ型は、あらゆるものを 含むS式とよばれるデータ型にまとめられます (S)はシンボルを表しています)。つまり、 42、#\c、(1 . 2)、#(a b c)、"Hello"、(quote xyz)、 (string->number "16")、(begin (display "Hello, World!") (newline)) はすべてS式です。 **3【フォーム】** これまで見てきたSchemeの例題プログラムはS式である。このことは 全てのSchemeプログラムに言えることであり、プログラムはデータです。 すなわち、文字データ #\cはプログラムあるいは フォームです。プログラムというかわりに フォームというより一般的な言葉をつかいます。 こうすることにより、プログラムの断片もとりあげることが できます。 Schemeはフォーム#\cを評価して、値#\cとします。 これは、#\cは自己評価的であるからです。すべての S式が自己評価的であるわけではありません。たとえば、 シンボルのS式xyzは評価するのと変数xyzが 保持していた値になります。リストのS式(string->number "16")は 評価すると数値の16になります。 すべてのS式が正しいプログラムであるとはかぎりません。 ドット対のS式(1 . 2)をSchemeのリスナーから入力すると エラーになるでしょう。 Schemeはリストフォームを評価するとき、そのフォームの最初の要素、 先頭を検査します。先頭を評価して手続きでなれば、フォームの のこりの部分を評価して、その引数の値を得たうえで、手続きをその 引数に適用します。 もし、上のフォームの先頭がスペシャルフォームであったら、 評価はそのフォームに特有の方法で進みます。 いくつかのスペシャルフォームはすでに見てきました。 begin、define、set!です。beginはその サブフォームを順に評価し、最後のサブフォームの結果の値を 全体の結果のとして返します。defineは変数を導入し、それを 初期化します。set!は変数の束縛を変更します。 **・手続き** すでに見てきた手続きは、cons、string->listなどのような Schemeのプリミティブな手続きでした。ユーザはスペシャルフォーム lambdaをつかって、独自の手続きをつくりだすことができます。 たとえば、次のものは、引数に2を加える手続きを定義しています。 ``` (lambda (x) (+ x 2)) ``` 最初のサブフォーム、(x)はパラメータのリストです。 のこりのサブフォームは手続きの本体を構成するものです。 この手続きは一つの引数に対して呼ぶことができ、プリミティブ 手続きと同じように振舞います。 ``` ((lambda (x) (+ x 2)) 5) => 7 ``` もし、この同じ手続きを何度も呼びたければ、lambdaの複製を そのたびに呼んでもいいのですが、もっとよい方法があります。 この手続きの値を保持する変数を使うことができます。 ``` (define add2 (lambda (x) (+ x 2))) ``` こうすると、この変数add2を引数に2を足す必要があるたびに 呼びだすことができます。 ``` (add2 4) => 6 (add2 9) => 11 ``` **・手続きパラメータ** lambda手続きのパラメータは最初のサブフォーム(先頭、シンボル lambdaの直後)で指定します。add2は単一引数(単項)手続き なのでパラメータリストはシングルトンリスト(x)です。シンボルxは 手続き引数を保持する変数として振舞います。変数xは手続き本体について 局所的(ローカル)であるといいます。 2引数手続きについては2要素リストを使うことができます。 一般にn引数手続きについてはn要素リストを使えます。 次の例は長方形の面積を計算する2引数手続きです。 ふたつの引数は長方形のたてとよこです。 ``` (define area (lambda (length breadth) (* length breadth))) ``` areaはその引数をかけあわせていますので、プリミティブ手続き *と同じです。単に ``` (define area *) ``` と書いてもよかったわけです。 **・可変長の引数** 手続きのなかにはときに異った数の引数をとるものがあります。 このようにするためにはlambdaパラメータリストを単一の シンボルに置き換えます。このシンボルはその手続きが呼ばれる 引数のリストとして振舞います。 一般的には、このlambdaパラメータリストは(x ...)という フォームあるいは、単一のシンボルまたはドット対(x ... . z)という フォームです。ドット対の場合、ドットの前のすべての変数は 手続き呼び出しで、対応するすべての引数に束縛され、ドットの 後の一個の変数は、のこりのすべての引数をひとつのリストに 拾いあげます。 **・apply** Scheme手続きapplyは手続きを引数のリストに適用します。 ``` (define x '(1 2 3)) (apply + x) => 6 ``` 一般には、applyは、ひとつの手続きと、別に可変長の引数をとります。 この引数の最後はリストでなければなりません。最後の引数の前に間の引数 を付けて引数リストをつくります。そうして、この引数リストに対して、 手続きを呼んで、その結果を返します。 ``` (apply + 1 2 3 x) => 12 ``` **・直列化** スペシャルフォームbeginを使って、順番に評価する必要のある一連の サブフォームをひとつにまとめます。 ``` (define display3 (lambda (arg1 arg2 arg3) (begin (display arg1) (display " ") (display arg2) (display " ") (display arg3) (newline)))) ``` Schemeでは、lambda-本体は暗黙のbeginです。したがって、 display3の本体のbeginはなくてもかまいません。しかし、 あって困るものでもありません。display3はもっと単純になります。 ``` (define display3 (lambda (arg1 arg2 arg3) (display arg1) (display " ") (display arg2) (display " ") (display arg3) (newline))) ``` **4【条件式】** すべての言語と同様に、Schemeには条件式があります。 基本フォームはifです。 ``` (if test-expression then-branch else-branch) ``` test-expressionが評価されて真(すなわち、#f以外の値)になれば、 “then”の枝が評価されます。さもなければ、“else”の枝が評価されます。 “else”の枝はなくてもかまいません。 ``` (define p 80) (if (> p 70) 'safe 'unsafe) => safe (if (< p 90) 'low-pressure) ;no ``else'' branch => low-pressure ``` Schemeでは利便のために他にも条件式フォームが用意されています。 これらはすべて、if式に展開されるマクロ(chap 8)として 定義することができます。 **・whenとunless** whenとunlessはひとつの枝(“then”あるいは“else”の枝)しかない 基本条件式が必要なときに便利です。 ``` (when (< (pressure tube) 60) (open-valve tube) (attach floor-pump tube) (depress floor-pump 5) (detach floor-pump tube) (close-valve tube)) ``` tubeのpressureが60未満と仮定すると、この条件式は floor-pumpをtubeに取り付け、5回押します (attachとdepressはなにか適当な手続きです)。 同じプログラムをifを使うと次のようになります。 ``` (if (< (pressure tube) 60) (begin (open-valve tube) (attach floor-pump tube) (depress floor-pump 5) (detach floor-pump tube) (close-valve tube))) ``` whenの枝は暗黙のbeginであり、一方、ifでは どちらかのブランチに2つ以上フォームがあるときには 明示的なbeginが必要です。 同じ振舞いをunlessをつかって書けば以下のようになります。 ``` (unless (>= (pressure tube) 60) (open-valve tube) (attach floor-pump tube) (depress floor-pump 5) (detach floor-pump tube) (close-valve tube)) ``` すべてのSchemeがwhenとunlessを用意しているわけではありません。 おつかいのSchemeになければ、マクロ(8章参照)として定義できます。 **・cond** condフォームは入れ子になったif式を表現するのに便利です。 最後以外の“else”の枝には新しいifが導入されます。したがって、 ``` (if (char<? c #\c) -1 (if (char=? c #\c) 0 1)) ``` はcondをつかって、次のように書けます。 ``` (cond ((char<? c #\c) -1) ((char=? c #\c) 0) (else 1)) ``` このように、condはマルチブランチの条件式です。 各節にはテストとそれに結びつくアクションがあります。 最初に成功したテストがそれに結びついたアクションを起します。 最後のelse節はどのテストも成功しなかった場合に選択されます。 condのアクションは暗黙のbeginです。 **・case** condの特別な場合にはcase式で圧縮した表現にできます。 これはすべてのテストが所属テストの場合です。 ``` (case c ((#\a) 1) ((#\b) 2) ((#\c) 3) (else 4)) => 3 ``` cの値を先頭にもつ節が選択されます。 **・andとor** Schemeには論理積(“and”)と論理和(“or”)に対応するスペシャルフォームが 用意されています(2.1.1節で見た否定のnotは手続きです)。 スペシャルフォームandはすべてのサブフォームが真ならば真を返します。 実際に返ってくる値は最後のサブフォームの値です。サブフォームのひとつでも 偽であれば、andは#fを返します。 ``` (and 1 2) => 2 (and #f 1) => #f ``` スペシャルフォームorは最初に真になったサブフォームの値を返します。 もし、すべてのサブフォームが偽ならばorは#fを返します。 ``` (or 1 2) => 1 (or #f 1) => 1 ``` and、orはともにサブフォームを左から右へ評価します。 結果が決りしだい返すことができるのでandとorはのこりの サブフォームを無視することがあります。 ``` (and 1 #f expression-guaranteed-to-cause-error) => #f (or 1 #f expression-guaranteed-to-cause-error) => 1 ``` **5【レキシカル変数】** Schemeの変数はレキシカルスコープをもちます。すなわち、変数は プログラムテキストのある特定の連続した広がり内のフォームにだけ 見えています。これまで見てきたグローバル変数も例外ではありません。 グローバル変数のスコープはプログラム全体で、たしかに連続していますね。 すでにいくつかローカル変数の例も見ています。それは、 lambdaパラメータで、この手続きが呼出されるたびに束縛され、 そのスコープはこの手続きの本体です。たとえば、 ``` (define x 9) (define add2 (lambda (x) (+ x 2))) x => 9 (add2 3) => 5 (add2 x) => 11 x => 9 ``` ここでは、グローバル変数xとローカル変数xがありますが、 後者は手続きadd2でもたらされたものです。グローバルの xは常に9です。ローカルのxは最初のadd2の呼出しでは 3に束縛されます。2回目の呼び出しではグローバルのxの値、 すなわち、9に束縛されます。この手続きの呼び出しから 返ってくれば、グローバルのxはあいかわらず9です。 フォームset!は変数のレキシカル束縛を変更します。 ``` (set! x 20) ``` はxのグローバル束縛を9から20へ変更します。 それはset!から見えているxの束縛だからです。 もし、set!がadd2の本体内にあればローカルなxを 変更したでしょう。 ``` (define add2 (lambda (x) (set! x (+ x 2)) x)) ``` ここのset!はローカル変数xに2を加えます。この手続きはローカルの xの新しい値を返します(効果という意味では、この手続きは前のaddと なんら区別はつきません)。まえと同じようにグローバルのxについて add2を呼んでみましょう。 ``` (add2 x) => 22 ``` (グローバルのxは今は9ではなく、20であることを思い 出してください。) add2の内側にあるset!はadd2が使うローカル変数のxに影響を 与えるだけです。ローカル変数はグローバルxの値に束縛されますが、 後者はローカルのxにset!によってひきよせられるような ことはありません。 ``` x => 20 ``` ここでの議論のすべては、ローカル変数とグローバル変数に 同じ識別子をつかっているからこその議論です。どのような テキストであっても、識別子名xは字面上もっも近い 変数名を参照します。このことは、外側のすべてのグローバルなxを 覆い隠すということです。つまり、add2の中ではパラメータxは グローバルなxを覆い隠しています。 手続きの本体は、それを囲むスコープにある、パラメータが 覆い隠していない変数にアクセスしたり変更したりすることができます。 そうすると次のような興味深いプログラムをつくることができます。 ``` (define counter 0) (define bump-counter (lambda () (set! counter (+ counter 1)) counter)) ``` 手続きbump-counterは引数なしの手続き(サンクとも言う)です。 これは、ローカル変数をもちませんので、なにも覆い隠すものはありません。 呼ばれるたびにグローバル変数counterを変更し、1ずつカウント アップし、counterの現在の値を返します。以下は、連続で bump-counterを呼んだ例です。 ``` (bump-counter) => 1 (bump-counter) => 2 (bump-counter) => 3 ``` **・letとlet*** ローカル変数は手続きを明示的に生成しなくても導入することができます。 スペシャルフォームletはその本体で利用するローカル変数のリストを 導入します。 ``` (let ((x 1) (y 2) (z 3)) (list x y z)) => (1 2 3) ``` lambdaのときと同様、let本体内で、ローカルな x(1に束縛されている)はグローバルなx(これは20に束縛 されています)を覆い隠します。 ローカル変数の初期化(xは1に、yは2に、 zは3に束縛)はletの本体とは考えません。それゆえ、 初期化部でのxへの参照は、グローバルに対する参照であって ローカルのxへの参照ではありません。 ``` (let ((x 1) (y x)) (+ x y)) => 21 ``` xは1に束縛され、yはグローバルのx すなわち20に束縛されますので、このようになります。 letのレキシカル変数のリストが直列に導入されるほうが 便利なときがあります。つまり、あとからの変数の初期化は 前にでてきた変数のレキシカルスコープ内でおこる ということです。フォームlet*はこれを行います。 ``` (let* ((x 1) (y x)) (+ x y)) => 2 ``` yの初期化中の、xは直上のxを参照しています。 この例はletが入れ子になった以下のプログラムと完全に同等 (実際以下の場合の便利な省略形として便利)です。 ``` (let ((x 1)) (let ((y x)) (+ x y))) => 2 ``` レキシカル変数が束縛された値は手続きであってもかまいません。 ``` (let ((cons (lambda (x y) (+ x y)))) (cons 1 2)) => 3 ``` このlet本体中では、レキシカル変数consは引数を足します。 外側では、consはあいかわらずドット対を作ります。 **・fluid-let** レキシカル変数は、隠蔽されることがなければ、そのスコープの始めから 終りまで見えています。ときには、レキシカル変数を臨時にある値にするのが 便利なことがあります。これを行うにはfluid-letフォームを使います。 ``` (fluid-let ((counter 99)) (display (bump-counter)) (newline) (display (bump-counter)) (newline) (display (bump-counter)) (newline)) ``` これはletと同じように見えますが、グローバル変数counterを 隠蔽せずに、fluid-let本体へつづける前に、それを臨時に99 に設定します。したがって、本体部のdisplayは以下のような 出力をします。 ``` 100 101 102 ``` fluid-let式が評価された後は、グローバルのcounterは、 fluid-letの前の値に復帰します。 ``` counter => 3 ``` fluid-letはletとは全く違う効果があることに注意してください。 fluid-letは、letがやるように、新しいレキシカル変数を導入する、という ことはありません。既存のレキシカル変数の束縛を変更し、fluid-let が済んだとたんにその変更はなかったことになります。 これを理解するために、次のプログラムを考えてみましょう。 ``` (let ((counter 99)) (display (bump-counter)) (newline) (display (bump-counter)) (newline) (display (bump-counter)) (newline)) ``` これはひとつ前の例のfluid-letをletで置き換えたものです。 出力はこうなります。 ``` 4 5 6 ``` すなわち、グローバルのcounterは最初3でbump-counter が呼出されるごとに更新されます。新しいレキシカル変数counterは 初期化部で99になっていますがbump-counterの呼び出しにはなんら 影響力をもちません。bump-counterへの呼び出しはローカルのcounter のスコープ内にありますが、bump-counterの本体はそのスコープ内にはない からです。bump-counter本体部はグローバルのcounterを参照 しており、その最終の値は6です。 ``` counter => 6 ``` **6【再帰】** 手続き本体は他の手続きへの呼び出しを含むことができます。 特別な場合には自分自身への呼び出しになります。 ``` (define factorial (lambda (n) (if (= n 0) 1 (* n (factorial (- n 1)))))) ``` この再帰的手続きは、ある数の階乗を計算するものです。 与えられた数が0なら、答は1です。それ以外のnであれば、 この手続きは、n - 1 の階乗を自分自身をつかって計算し、その 途中の答とnをかけて、その積を結果として返します。 相互に再帰する手続きを定義することも可能です。以下の奇遇性を チェックする述語は互いに再帰しています。 ``` (define is-even? (lambda (n) (if (= n 0) #t (is-odd? (- n 1))))) (define is-odd? (lambda (n) (if (= n 0) #f (is-even? (- n 1))))) ``` ここにあげたのは極く単純な相互再帰の説明のためだけのものです。 Schemeではeven?とodd?はプリミティブ述語として既に用意されて います。 **・letrec** 上の手続きをローカル変数として手に入れたくなったら、 letフォームを使って、 ``` (let ((local-even? (lambda (n) (if (= n 0) #t (local-odd? (- n 1))))) (local-odd? (lambda (n) (if (= n 0) #f (local-even? (- n 1)))))) (list (local-even? 23) (local-odd? 23))) ``` とすることが可能かもしれません。しかし、これはうまく動きません。 初期化部のlocal-even?とlocal-odd?の出現はそれぞれのレキシカル 変数自身を参照しないからです。letをlet*に変更しても、やはり、 うまく動きません。local-odd?本体の中のlocal-even?は正しい値を 参照していますが、local-even?中のlocal-odd?はあいかわらず、 どこか他を指しているのです。 問題を解決するにはこんなふうに、Schemeの提供するフォーム letrecを利用します。 ``` (letrec ((local-even? (lambda (n) (if (= n 0) #t (local-odd? (- n 1))))) (local-odd? (lambda (n) (if (= n 0) #f (local-even? (- n 1)))))) (list (local-even? 23) (local-odd? 23))) ``` letrecによって導入されたレキシカル変数は、letrec本体から だけではなく、すべての初期化部からも見えています。というわけで、 letrecは再帰的および相互再帰的ローカル手続きの定義にはもってこいです。 **・名前つきのlet** letrecを用いて定義された再帰的手続きでループを表現することがきます。 たとえば、10からのカウントダウンを表示したいとしましょう。 ``` (letrec ((countdown (lambda (i) (if (= i 0) 'liftoff (begin (display i) (newline) (countdown (- i 1))))))) (countdown 10)) ``` これはコンソールに10から1までの数字を出力し結果として liftoffを返します。 Schemeで名前つきletというのがあって、よりコンパクトに このてのループを書くことができます。 ``` (let countdown ((i 10)) (if (= i 0) 'liftoff (begin (display i) (newline) (countdown (- i 1))))) ``` letの直後にあるループを識別する変数の存在に注意してください。 このプログラムはletrecで書いたものと同等です。名前つきletは letrecフォームに展開されるマクロ(8章参照)のひとつである と考えてさしつかえありません。 **・反復** 上で定義したcountdownは実際には再帰的手続きです。 Schemeではループは再帰を通じてのみ定義することができます。 ループや反復をあつかう特別な構成はありません。 とは言っても、上で定義したようなループは真性のループです。 まちがいなく、ほかの言語での勘定と同じになっています。 どういうことかというと、Schemeは上のようなタイプの再帰には 特別に注意していて、手続きの呼び出し/復帰のオーバヘッドが 発生しないようにしています。 Schemeではこれを末尾呼び出しの除去と呼ばれている処理に よって行います。countdown手続きをよく見ると、countdown本体中の 再帰的呼び出しがおこるときには、それが末尾呼び出しになっている あるいは、まさに終るところとなっていることに気づくでしょう。 countdownのおのおのの呼び出しが、自分自身を呼び出さないか、あるいは 呼び出すときには最後のアクションとして呼び出すかのどちらかです。 Schemeの実装にとってはこれは反復と区別がつかない再帰です。 というわけで、ループを再帰で書きましょう。安全です。 有用な末尾再帰的手続きのもうひとつの例です。 ``` (define list-position (lambda (o l) (let loop ((i 0) (l l)) (if (null? l) #f (if (eqv? (car l) o) i (loop (+ i 1) (cdr l))))))) ``` **・手続きのリスト上への写像** 特別な反復はリストの各エレメントに対して同じアクションを繰り返すというの があります。Scheme ではこうした状況用にふたつの手続きを用意しています。 mapとfor-eachです。 map手続きは与えられた手続きを与えられたリストの各要素に 適用して、結果のリストを返します。たとえば、 ``` (map add2 '(1 2 3)) => (3 4 5) ``` for-each手続きも同様にリストの各要素に手続きを適用しますが、 なにも返しません。この手続き適用は純粋に副作用だけのためにあります。 たとえば、 ``` (for-each display (list "one " "two " "buckle my shoe")) ``` はコンソールに文字列を(でてきた順に)表示する副作用があります。 mapおよびfor-eachが適用する手続きは一引数の手続きである必要は ありません。たとえば、n引数の手続きがあたえられた場合にはmapは n本のリストをとり、その手続きをそれぞれのリストから選ばれた n個の引数に適用します。たとえば、 ``` (map cons '(1 2 3) '(10 20 30)) => ((1 . 10) (2 . 20) (3 . 30)) (map + '(1 2 3) '(10 20 30)) => (11 22 33) ```

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully