コンセプト演習は、特定の(プログラミングの)概念を教えるために設計された演習です。 コンセプト演習で教える概念は、_シラバス_を構成します。 シラバスの設計方法の詳細は、シラバスのドキュメントを参照してください。
トラックのルートディレクトリで次のコマンドを実行すると、新しいコンセプト演習の雛形をすばやく作成できます:
bin/fetch-configlet
bin/configlet create --concept-exercise <slug>
詳細は、configlet createのドキュメントを参照してください。
コンセプト演習のメタデータは、config.jsonファイルのexercises.conceptキーで定義します。このメタデータでは、演習のUUIDやスラグなどが定義されます。
{
"exercises": {
"concept": [
{
"uuid": "93fbc7cf-3a7e-4450-ad22-e30129c36bb9",
"slug": "cars-assemble",
"name": "Cars, Assemble!",
"concepts": ["if-statements", "numbers"],
"prerequisites": ["basics"]
}
]
}
}
各コンセプト演習は、トラックのexercises/conceptディレクトリの中にそれぞれ専用のディレクトリを持ちます。コンセプト演習のディレクトリ名は、config.jsonファイルで定義するコンセプト演習のslugプロパティと一致している必要があります。
コンセプト演習のファイルには、4つの種類があります。
これらのファイルは、演習の説明のために学習者に表示されます。
.docs/introduction.md:演習で学習者に教える概念を紹介します(必須).docs/instructions.md:演習の指示を提供します(必須).docs/hints.md:学習者が演習で行き詰まったときに、自力で抜け出せるようにヒントを提供します(必須)これらのファイルは学習者に表示されることは_ありません_が、演習のメタデータを定義するために使われます。
.meta/config.json:演習に関するメタ情報を含みます(必須).meta/design.md:演習の設計を説明します(必須)これらのファイルは、演習のアプローチを説明します。
.approaches/introduction.md:演習でよく使われるアプローチのイントロダクションです(任意).approaches/config.json:アプローチのメタデータです(任意).approaches/<approach-slug>/content.md:アプローチの説明です(任意).approaches/<approach-slug>/snippet.txt:アプローチを紹介するスニペットです(任意)これらのファイルは、演習の記事を説明します。
.articles/config.json:記事のメタデータです(任意).articles/<article-slug>/content.md:記事の説明です(任意).articles/<article-slug>/snippet.md:記事を紹介するスニペットです(任意)実装ファイルやテストファイルなど、言語固有のファイルです。これらのファイル名はトラックごとに異なります。
exercises
└── concept
└── cars-assemble
├── .approaches
| ├── for-loop
| | ├── content.md
| | └── snippet.txt
| ├── config.json
| └── introduction.md
├── .articles
| ├── performance
| | ├── content.md
| | └── snippet.md
| └── config.json
├── .docs
| ├── introduction.md
| ├── instructions.md
| └── hints.md
├── .meta
| ├── config.json
| ├── design.md
| └── Exemplar.cs (exemplar implementation)
├── CarsAssemble.cs (stub implementation)
└── CarsAssemblyTests.cs (tests)
新しい演習には「楽観的マージ」という方針を採っており、トラックは「作業中」の状態で演習を開発できます。configletを通過し、マージできる最小の有効な状態は次のとおりです。
config.jsonに有効なエントリーがあり、statusがwipに設定されていること。.meta/config.jsonファイルがあること。.docs/introduction.md.docs/instructions.md.docs/hints.md.docs/introduction.md
目的: 演習で学習者に教える概念を紹介します。
有無: 必須
about.mdドキュメントに譲ります。例として、「strings」演習のイントロダクションでは、文字列を『Unicode文字の並び』や『バイトの並び』とだけ説明し、文字列の作り方を伝え、文字列にはそれを操作するためのメソッドがあることを説明するかもしれません。学習者が演習を解くために、より細かいニュアンスのある詳細を理解する必要がない限り、この程度の簡潔な説明(構文の例を添えたもの)で、演習を解くのに十分な情報になります。
# Introduction
There are two primary ways to assign objects to names in Ruby - using variables or constants. Variables are always written in snake case. A variable can reference different objects over its lifetime. For example, `my_first_variable` can be defined and redefined many times using the `=` operator:
```ruby
my_first_variable = 1
my_first_variable = "Some string"
my_first_variable = SomeComplexObject.new
```
.docs/introduction.md.tpl
目的: introduction.mdファイルを生成するためのテンプレートです。
有無: 任意
introduction.mdドキュメントは、演習の概念を学習者に紹介します。各概念には、それぞれ_独自の_introduction.mdドキュメントもあり、これは演習の文脈の外では表示されません。
概念のイントロダクションを、そのまま演習のイントロダクションに含めたい場合は、introduction.md.tplファイルを使えます。このファイルでは、プレースホルダーを使って概念のイントロダクションを参照できます:%{concept:<concept-slug>}
configletは、テンプレートファイルからintroduction.mdファイルを生成できます。生成されたファイルでは、概念のプレースホルダーがその概念のintroductionの内容に置き換わります。
Exercismのウェブサイトが認識するのはintroduction.mdドキュメントだけです。テンプレートファイルを使う場合にintroduction.mdを生成するのは、トラックの側の責任です。
テンプレートを使うかどうかは、トラックが演習ごとに決められます。概念のイントロダクションをそのまま使うのが最適とは限らない場合もあります。常に、学習者にとって最高の学習体験になるものを選びましょう。
# Introduction
%{concept:variables}
.docs/instructions.md
目的: 演習の指示を提供します。
有無: 必須
このファイルは2つの部分に分かれます。
各タスクは、次の基準に従う必要があります。
## 1. Do X、## 2. Do Y)。## 1. Check if an appointment has already passed)。Implement method X(...) that takes an A and returns a Z)。Exercismのコンテンツを誰にとっても安全なものにすることは、とても大切にしています。そのため、ストーリーが適切かどうかを判断するときは、慎重すぎるくらいがちょうどよいと考えています。マージする内容には気を配っていますが、何が問題だと受け取られるかを把握するのは難しいものです。ですから、皆さんが善意で行動していることは常に前提とし、レビューで問題を見つけられるよう、対立的にならない形で最善を尽くします。ストーリーについて確認したい場合は、@exercism/leadershipにメンションしてください。一緒に確認します。判断の手がかりをいくつか挙げます。
# Instructions
In this exercise you're going to write some code to help you cook a brilliant lasagna from your favorite cooking book.
## 1. Calculate the remaining oven time in minutes
Define the `Lasagna#remaining_minutes_in_oven` method that takes the actual minutes the lasagna has been in the oven as a parameter and returns how many minutes the lasagna still has to remain in the oven, based on the expected oven time in minutes from the previous task.
```ruby
lasagna = Lasagna.new
lasagna.remaining_minutes_in_oven(30)
# => 10
```
.docs/hints.md
目的: 学習者が演習で行き詰まったときに、自力で抜け出せるようにヒントを提供します。
有無: 必須
## Generalという見出しの下にMarkdownのリストとして書けます。instructions.mdのタスク見出しと一致する見出しの下に、Markdownのリストとして書きます(例:## 2. Do Y)。## 2. Check if a book can be borrowed)。Implement the 'canBorrowBook' function to check if a book can be borrowed. The function takes a book as its parameter and returns `true` if the book has not already been borrowed; otherwise, return `false`)。ヒントの表示は「推奨される」道ではありません。学習者がそれなしでは先に進めない場合を除き、(やんわりと)使わないよう促します。そのため、ヒントを読む学習者は少し戸惑っていたり、圧倒されていたり、もしかするといらだっているかもしれない、という点も考慮しておく価値があります。
# Hints
## General
- You need to define a [constant][constant] which should contain the [integer][integers] value specified in the recipe.
## 1. Calculate the remaining oven time in minutes
- You need to define a [method][methods] with a single parameter for the actual time so far.
[constants]: https://www.rubyguides.com/2017/07/ruby-constants/
[integers]: https://ruby-doc.org/core-2.7.0/Integer.html
[methods]: https://launchschool.com/books/ruby/read/methods
目的: 演習の設計を説明します。
有無: 必須
このファイルには、演習の設計に関する情報が入ります。目的、指導目標、教えないことなどが含まれます。これらの情報は、その演習に対応するGitHub issueから取り出せます。
これは、将来のメンテナーやコントリビューターに演習の範囲と限界を伝え、時間の経過とともに演習が複雑になっていく自然な流れを避けるために存在します。
# Design
## Goal
The goal of this exercise is to teach the student the basics of programming in Ruby.
## Learning objectives
- Know what a variable is.
- Know how to define a variable.
- Know how to update a variable.
## Out of scope
- Memory and performance characteristics.
- Method overloads.
## Concepts
The Concepts this exercise unlocks are:
- `basics`: know what a variable is; know how to define a variable; know how to update a variable.
## Prerequisites
There are no prerequisites.
.meta/config.json
目的: 演習に関するメタ情報を含みます。
有無: 必須
このファイルには、演習に関するメタ情報が入ります。
authors:演習の作者のGitHubユーザー名(必須)
contributors:演習のコントリビューターのGitHubユーザー名(任意)
forked_from:どの演習からフォークしたか(フォークした演習の場合は必須)files:この演習で使うファイルの場所。演習のディレクトリからの相対パスで指定します(必須)
solution:スタブ実装のファイル(必須)test:テストファイル(必須)exemplar:模範実装のファイル(必須)editor:エディターで読み取り専用として表示されるその他のファイル(任意)invalidator:変更されると解答が古くなるファイル(任意)language_versions:言語のバージョン要件(任意)blurb:この演習の簡単な説明。長さは350文字以下でなければなりません。Markdownは_サポートされていません_(必須)source:この演習の基になった出典(任意)source_url:この演習の基になった出典のURL(任意)representer:representerがこのファイルをどのように処理するかに関するメタ情報(任意)
version:この演習で使うrepresenterのバージョンを表す整数(親キーがある場合は必須)icon:アイコンのスラグ(アイコンの全リストを参照)。指定しない場合は、演習のスラグが使われます(任意)custom:演習固有の非標準データ。トラックのツールの動作を演習ごとにカスタマイズするために使えます(任意)ある人が作者_かつ_コントリビューターである場合は、作者としてだけ記載します。
{
"authors": ["FSharpForever"],
"files": {
"solution": ["Lasagna.fs"],
"test": ["LasagnaTests.fs"],
"exemplar": [".meta/Exemplar.fs"]
},
"blurb": "Learn the basics of F# by cooking Lucian's Luscious Lasagna"
}
ユーザーFSharpForeverがF#トラック向けにlog-levelsという演習を書いたとします。PythonProfessorがその演習をPythonトラック向けに適応させます。その後、ユーザーGladToHelpが演習を改善します。
{
"authors": ["PythonProfessor"],
"contributors": ["GladToHelp"],
"files": {
"solution": ["log_levels.py"],
"test": ["log_levels_test.py"],
"exemplar": [".meta/exemplar.py"],
"editor": ["test_helper.py"]
},
"forked_from": ["fsharp/log-levels"],
"language_versions": ">=3.7",
"blurb": "Learn how to work with strings by processing log lines.",
"source": "Wikipedia",
"source_url": "https://en.wikipedia.org/wiki/Log_file",
"representer": {
"version": 2
},
"icon": "logs",
"custom": {
"parallel": true
}
}
次の点に注意してください。
forked_fromが正しいことだけを確認します。language_versionsは自由形式の文字列で、トラックが自由に使い、好きなように解釈できます。.approaches/introduction.md
目的: 演習でよく使われるアプローチのイントロダクションです。
有無: 任意
このファイルは、演習でよく使われるアプローチを説明します。 このファイルに何を書くべきかの詳細は、ドキュメントを参照してください。
# Introduction
The key to this exercise is to deal with C# strings being immutable, which means that a `string`'s value cannot be changed.
Therefore, to reverse a string you'll need to create a _new_ `string`.
## Using LINQ
```csharp
public static string Reverse(string input)
{
return new string(input.Reverse().ToArray());
}
```
For more information, check the [LINQ approach][approach-linq].
## Which approach to use?
If readability is your primary concern (and it usually should be), the LINQ-based approach is hard to beat.
.approaches/config.json
目的: アプローチのメタデータです。
有無: 任意(アプローチのイントロダクションまたはアプローチがある場合は必須)
このファイルには、演習のアプローチに関するメタ情報が入ります。
introduction:演習のアプローチのイントロダクションの作者のGitHubユーザー名(任意)
authors:演習のアプローチのイントロダクションの作者のGitHubユーザー名(必須)
contributors:演習のアプローチのイントロダクションのコントリビューターのGitHubユーザー名(任意)
approaches:詳しいアプローチを列挙する配列(任意)
uuid:アプローチを一意に識別するV4 UUID。UUIDはトラック内でも全トラックを通じても一意でなければならず、決して変更してはいけません。slug:アプローチのスラグ。小文字のケバブケースの文字列です。スラグはトラック内のすべてのアプローチのスラグの中で一意でなければなりません。長さは255文字以下でなければなりません。title:アプローチのタイトル。長さは255文字以下でなければなりません。blurb:このアプローチの簡単な説明。長さは350文字以下でなければなりません。Markdownは_サポートされていません_(必須)authors:演習のアプローチの作者のGitHubユーザー名(必須)
contributors:演習のアプローチのコントリビューターのGitHubユーザー名(任意)
tags:提出物がどのような条件でアプローチに紐づけられるかを指定します(任意)。
all:提出物にすべて含まれていなければならないタグの配列(任意。ただしanyに要素がない場合を除く)any:少なくとも1つが提出物に含まれていなければならないタグの配列(任意。ただしallに要素がない場合を除く)not:どのタグも提出物に含まれていてはならない(任意){
"introduction": {
"authors": ["erikschierboom"]
},
"approaches": [
{
"uuid": "448fb2b4-18ab-4e55-aa54-ad4ed6d5f7f6",
"slug": "span",
"title": "Use Span<T>",
"blurb": "Use Span<T> to efficiently reverse a string.",
"authors": ["erikschierboom"]
}
]
}
.approaches/<approach-slug>/content.md
目的: アプローチの詳しい説明です。
有無: 任意(アプローチには必須)
このファイルには、アプローチの詳しい説明が入ります。 このファイルに何を書くべきかの詳細は、ドキュメントを参照してください。
# Span
```csharp
Span<char> chars = stackalloc char[input.Length];
for (var i = 0; i < input.Length; i++)
{
chars[input.Length - 1 - i] = input[i];
}
return new string(chars);
```
This `Span<T>` approach uses a `for` loop.
.approaches/<approach-slug>/snippet.txt
目的: アプローチを紹介するスニペットです。
有無: 任意(アプローチには必須)
このファイルには、アプローチを紹介する小さなスニペットが入ります。 スニペットは、演習のDig Deeperページに表示されます。
行数は8以下でなければなりません。
このファイルに何を書くべきかの詳細は、ドキュメントを参照してください。
Span<char> chars = stackalloc char[input.Length];
for (var i = 0; i < input.Length; i++)
{
chars[input.Length - 1 - i] = input[i];
}
return new string(chars);
.article/config.json
目的: 記事のメタデータです。
有無: 任意(記事がある場合は必須)
このファイルには、演習の記事に関するメタ情報が入ります。
articles:詳しい記事を列挙する配列(任意)
uuid:記事を一意に識別するV4 UUID。UUIDはトラック内でも全トラックを通じても一意でなければならず、決して変更してはいけません。slug:記事のスラグ。小文字のケバブケースの文字列です。スラグはトラック内のすべての記事のスラグの中で一意でなければなりません。長さは255文字以下でなければなりません。title:記事のタイトル。長さは255文字以下でなければなりません。blurb:この記事の簡単な説明。長さは350文字以下でなければなりません。Markdownは_サポートされていません_(必須)authors:演習の記事の作者のGitHubユーザー名(必須)
contributors:演習の記事のコントリビューターのGitHubユーザー名(任意)
{
"articles": [
{
"uuid": "6db71962-62d5-448b-a980-c20ae41013ed",
"slug": "performance",
"title": "Optimizing performance",
"blurb": "Explore how to most efficiently reverse a string and what the trade-offs are.",
"authors": ["erikschierboom"]
}
]
}
.articles/<article-slug>/content.md
目的: アプローチの詳しい説明です。
有無: 任意(アプローチには必須)
このファイルには、アプローチの詳しい説明が入ります。 このファイルに何を書くべきかの詳細は、ドキュメントを参照してください。
# Performance
In this document, we'll find out which approach is the most performant one.
## Benchmark results
| Method | Mean | Error | StdDev | Median | Allocated |
| -----: | --------: | --------: | --------: | --------: | --------: |
| Linq | 29.133 ns | 0.5865 ns | 0.5486 ns | 28.984 ns | 80 B |
| Array | 4.806 ns | 0.4999 ns | 1.4739 ns | 3.967 ns | - |
.articles/<article-slug>/snippet.txt
目的: アプローチを紹介するスニペットです。
有無: 任意(記事には必須)
このファイルには、記事を紹介する小さなスニペットが入ります。 スニペットは、演習のDig Deeperページに表示されます。
行数は8以下でなければなりません。
このファイルに何を書くべきかの詳細は、ドキュメントを参照してください。
| Method | Mean | Allocated |
| -----: | --------: | --------: |
| Linq | 29.133 ns | 80 B |
| Array | 4.806 ns | - |
目的: 学習者に開始点を提供します。
有無: 必須
.meta/config.jsonファイルの"files.solution"キーで指定しなければなりません。class Lasagna
def remaining_minutes_in_oven(actual_minutes_in_oven)
raise NotImplementedError, 'Please implement the Lasagna#remaining_minutes_in_oven method'
end
def preparation_time_in_minutes(layers)
raise NotImplementedError, 'Please implement the Lasagna#preparation_time_in_minutes method'
end
end
目的: 解答が正しいかを検証します。
有無: 必須
instructions.mdファイルにある例を使わないようにします。.meta/config.jsonファイルの"files.test"キーで指定しなければなりません。require 'minitest/autorun'
require_relative 'lasagna'
class LasagnaTest < Minitest::Test
def test_remaining_minutes_in_oven
assert_equal 15, Lasagna.new.remaining_minutes_in_oven(25)
end
def test_preparation_time_in_minutes_with_one_layer
assert_equal 2, Lasagna.new.preparation_time_in_minutes(1)
end
def test_preparation_time_in_minutes_with_multiple_layers
assert_equal 8, Lasagna.new.preparation_time_in_minutes(4)
end
end
目的: 学習者が目指すべき目標の実装を提供します。
有無: 必須
.meta/config.jsonファイルの"files.exemplar"キーで指定しなければなりません。class Lasagna
EXPECTED_MINUTES_IN_OVEN = 40
PREPARATION_MINUTES_PER_LAYER = 2
def remaining_minutes_in_oven(actual_minutes_in_oven)
EXPECTED_MINUTES_IN_OVEN - actual_minutes_in_oven
end
def preparation_time_in_minutes(layers)
layers * PREPARATION_MINUTES_PER_LAYER
end
end
目的: テストを実行できるようにします。
有無: デフォルトのファイルだけではテストを実行できない場合は必須
言語によっては、テストを実行するために追加のファイルが必要です。たとえば、C#のプロジェクトファイルやNodeのpackage.jsonファイルで、これらがないとテストを実行できません。
中には、個々の演習に固有のものではなく、_すべて_の演習に当てはまるファイルもあります。詳細は、ドキュメントを参照してください。
コンセプト演習には、その概念では_なく_、ストーリーやテーマにちなんだ名前を付けます。
よい名前の例:
Tim from MarketingLucian's Luscious LasagnaCalculator Conundrum不適切な名前の例:
Booleans:ストーリー名ではなく概念名を使っているExercise #1:演習はストーリーやテーマではない大きな変更を加えずに演習をフォークする場合は、できれば元の名前を使います。
各演習には_スラグ_もあり、これは次のルールで演習名を正規化したものです。
[a-z0-9-]+)2-ferよりtwo-fer)よいスラグの例:
tim-from-marketinglucians-luscious-lasagnacalculator-conundrum不適切なスラグの例:
TIM-FROM-MARKETING:小文字を使っていない(すなわちtim-from-marketing)TimFromMarketing:kebab-caseを使っていない(すなわちtim-from-marketing)floating-point-numbers:ストーリー名ではなく概念名を使っている演習のドキュメントは、ブラウザー内エディターを使う場合とCLIを使う場合で、学習者への表示のされ方が異なります。詳細は、こちらのドキュメントを参照してください。
各演習には、対応するアイコンがあります。
デフォルトでは、名前が演習のスラグと一致するアイコンが表示されます。
これは、演習の.meta/config.jsonファイルでiconプロパティを指定することで上書きできます。
既存の演習をフォークする場合、その演習用のアイコンがすでにあることがほとんどです。 ない場合は、website-iconsリポジトリでissueを作成してください。