トラック
/
Julia
Julia
/
演習
/
高校の恋人
高校の恋人

高校の恋人

学習演習

はじめに

Juliaは、JITコンパイルできる関数の中に、できるだけ多くのコードを書くことを推奨しています。そして、小さな関数をたくさん作ることは、設計上、性能が良くなるようにできています。

そうすると、小さくて単純な関数がたくさん残ることになり、それらを組み合わせて、簡単ではない処理をこなす必要が出てきます。

わかりやすい方法の1つは、関数呼び出しを入れ子にすることです。次の例はかなり無理やりですが、要点は伝わるでしょう。

julia> first.(titlecase.(reverse.(["my", "test", "strings"])))
3-element Vector{Char}:
 'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
 'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)

この方法の欠点は、入れ子が深くなるほど読みやすさが急速に落ちることです。

もっと単純で、もっと柔軟な方法が必要です。

合成

これは数学者に愛用されている手法で、Juliaはその数学的な構文を取り入れています。

任意の数の関数を、∘演算子(\circと入力してからタブ)でcomposedできます。その結果は、1つの関数として使えます。

julia> compfunc = first ∘ titlecase ∘ reverse
first ∘ titlecase ∘ reverse
julia> compfunc.(["my", "test", "strings"])
3-element Vector{Char}:
 'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
 'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
# alternative syntax, giving the same result
julia> (first ∘ titlecase ∘ reverse).(["my", "test", "strings"])

注意すべき点がいくつかあります。

  • 元になる関数は、入れ子にしたときと同じ順番で並び、右から左の順に実行されます。
  • 合成するときにブロードキャストを使うのは簡単ではありませんが、合成した関数を呼び出すときには適用できます。

パイプライン

もう1つの方法は、_数学者_のアプローチというより、_プログラマー_のアプローチと言えるかもしれません。

Pipelinesは、Unixのシェルスクリプトで昔から使われており、最近では主要なプログラミング言語でも一般的になりました(その採用を先駆けたのはF#だとよく言われます)。

基本的な考え方は、何らかのデータから始めて、それを一連の関数に通して結果を得るというものです。

パイプ演算子は|>です(F#や最近のバージョンのRと同じです)。ただしJuliaには、ブロードキャスト版の.|>もあります。

julia> ["my", "test", "strings"] .|> reverse .|> titlecase .|> first
3-element Vector{Char}:
 'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
 'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)

実行は厳密に左から右になり、各関数の出力が矢印の方向に流れて、次の関数の入力になります。

制約、回避策、その他の選択肢

合成とパイプラインの説明に使った関数が、どれも_1つ_の引数しか取らないのは、偶然ではありません。

一部の純粋関数型言語では、_最初の_引数を関数にパイプしつつ、他の引数も含めることができます。

対照的に、Juliaはパイプラインの中で関数の_名前_(またはそれに相当するもの)だけを想定していて、追加の引数は取りません。

これには重要な技術的な理由があります(curryingが言語設計の標準的な一部ではないことに関係しています)。curryingを理解していない_多く_の人たちは、この制約が不注意な見落としではなく、今後のJuliaのバージョンでも変わる見込みはない、と受け入れるだけでよいでしょう。

回避策

必要なことを何でもしてくれる単一引数の関数が必要です。幸いなことに、Juliaで新しい関数を定義するのは簡単です。

最も簡単には、anonymous functionを使う方法があります。たとえば、入力が文字列1つで、それをアンダースコアで分割したいとします。

julia> "my_test_strings" |> (s -> split(s, '_'))
3-element Vector{SubString{String}}:
 "my"
 "test"
 "strings"

そのベクトルは、前と同じように、他の関数にパイプできます。

この場合、無名関数を括弧で囲むのは任意ですが、一般には曖昧さを減らすのに役立つ方法です。

同様に、プログラムのもっと前のほうで名前付きの関数を作り、必要に応じて再利用することもできます。

説明

この演習では、高校時代の恋人同士がSNSで愛を告白できるように、2人のイニシャルを使ったUnicodeのハートを作ります。

❤ J.  +  M. ❤

1. 名前を整える

cleanupnameメソッドを実装します。 名前を受け取り、その中の-をすべて取り除いて、スペースに置き換えます。 また、名前の先頭と末尾にある空白も取り除きます。

julia> cleanupname("Jane-Ann")
"Jane Ann"

2. 名前の最初の文字を取得する

firstletterメソッドを実装します。 名前を受け取り、その最初の文字を文字列として返します。 前の手順で定義したcleanupnameを再利用し、他の関数と組み合わせてみましょう。

julia> firstletter("Jane")
"J"

3. 最初の文字をイニシャルに整える

initialメソッドを実装します。 名前を受け取り、その最初の文字を大文字にし、そのあとにピリオドを付けて返します。 前の手順で定義したfirstletterを再利用しましょう。

initial("Robert")
"R."

4. イニシャルをハートの中に入れる

coupleメソッドを実装します。 2つの名前を受け取り、それぞれのイニシャルを絵文字のハートで囲んで返します。 前の手順で定義したinitialを再利用しましょう。

couple("Blake Miller", "Riley Lewis")
"❤ B.  +  R. ❤"
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Julia Exercism

高校の恋人を始める準備はできましたか?

Exercismに登録すれば、35個のコンセプト128個の演習、そして本物の人間によるメンタリングとともに、Juliaを学んでマスターできます。すべて無料です。