diff --git a/_overviews/contribute/add-guides.md b/_overviews/contribute/add-guides.md index f52764736e..ea2de8d132 100644 --- a/_overviews/contribute/add-guides.md +++ b/_overviews/contribute/add-guides.md @@ -111,7 +111,7 @@ can generate the same tabs in markdown with the `tabs` directive and class `tabs ~~~liquid {% tabs hello-world-demo class=tabs-scala-version %} -{% tab 'Scala 2' for=hello-world-demo %} +{% tab 'Scala 2' %} ```scala object hello extends App { println("Hello, World!") @@ -119,7 +119,7 @@ object hello extends App { ``` {% endtab %} -{% tab 'Scala 3' for=hello-world-demo %} +{% tab 'Scala 3' %} ```scala @main def hello() = println("Hello, World!") ``` @@ -134,12 +134,12 @@ It is crucial that you use the `tabs-scala-version` class to benefit from some c - the tab picked will be remembered across the site, and when the user returns to the page after some time. For code snippets that are valid in both Scala 2 and Scala 3, please use a single tab labelled -“Scala 2 and 3” (please note that the `tabs-scala-version` class is also dropped): +`'Scala 2 and 3'` (please note that the `tabs-scala-version` class is also dropped): ~~~liquid {% tabs scala-2-and-3-demo %} -{% tab 'Scala 2 and 3' for=scala-2-and-3-demo %} +{% tab 'Scala 2 and 3' %} ```scala List(1, 2, 3).map(x => x + 1).sum ``` @@ -148,6 +148,19 @@ List(1, 2, 3).map(x => x + 1).sum ~~~ +For examples that only apply to either one of Scala 2 or 3, use the tabs `'Scala 2 Only'` and `'Scala 3 Only'`. + +If you have a particularly long tab, for readability you can indicate which tab group it belongs to with +a parameter `for=tab-group` as in this example: + +~~~liquid +{% tabs my-tab-group class=tabs-scala-version %} +... +{% tab 'Scala 3' for=my-tab-group %} +... +~~~ + + ### Typechecked Examples The site build process uses [mdoc](https://scalameta.org/mdoc/) to typecheck diff --git a/_overviews/scala3-book/taste-vars-data-types.md b/_overviews/scala3-book/taste-vars-data-types.md index 41f4fd5d07..750264bcd2 100644 --- a/_overviews/scala3-book/taste-vars-data-types.md +++ b/_overviews/scala3-book/taste-vars-data-types.md @@ -36,8 +36,8 @@ When you create a new variable in Scala, you declare whether the variable is imm These examples show how to create `val` and `var` variables: -{% tabs var-express-1 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-1 %} +{% tabs var-express-1 %} +{% tab 'Scala 2 and 3' %} ```scala // immutable @@ -52,8 +52,8 @@ var b = 1 In an application, a `val` can’t be reassigned. You’ll cause a compiler error if you try to reassign one: -{% tabs var-express-2 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-2 %} +{% tabs var-express-2 %} +{% tab 'Scala 2 and 3' %} ```scala val msg = "Hello, world" @@ -64,8 +64,8 @@ msg = "Aloha" // "reassignment to val" error; this won’t compile Conversely, a `var` can be reassigned: -{% tabs var-express-3 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-3 %} +{% tabs var-express-3 %} +{% tab 'Scala 2 and 3' %} ```scala var msg = "Hello, world" @@ -78,8 +78,8 @@ msg = "Aloha" // this compiles because a var can be reassigned When you create a variable you can explicitly declare its type, or let the compiler infer the type: -{% tabs var-express-4 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-4 %} +{% tabs var-express-4 %} +{% tab 'Scala 2 and 3' %} ```scala val x: Int = 1 // explicit @@ -91,8 +91,8 @@ val x = 1 // implicit; the compiler infers the type The second form is known as _type inference_, and it’s a great way to help keep this type of code concise. The Scala compiler can usually infer the data type for you, as shown in the output of these REPL examples: -{% tabs var-express-5 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-5 %} +{% tabs var-express-5 %} +{% tab 'Scala 2 and 3' %} ```scala scala> val x = 1 @@ -109,8 +109,8 @@ val nums: List[Int] = List(1, 2, 3) You can always explicitly declare a variable’s type if you prefer, but in simple assignments like these it isn’t necessary: -{% tabs var-express-6 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-6 %} +{% tabs var-express-6 %} +{% tab 'Scala 2 and 3' %} ```scala val x: Int = 1 @@ -133,8 +133,8 @@ In Scala, everything is an object. These examples show how to declare variables of the numeric types: -{% tabs var-express-7 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-7 %} +{% tabs var-express-7 %} +{% tab 'Scala 2 and 3' %} ```scala val b: Byte = 1 @@ -149,8 +149,8 @@ val f: Float = 3.0 Because `Int` and `Double` are the default numeric types, you typically create them without explicitly declaring the data type: -{% tabs var-express-8 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-8 %} +{% tabs var-express-8 %} +{% tab 'Scala 2 and 3' %} ```scala val i = 123 // defaults to Int @@ -161,8 +161,8 @@ val j = 1.0 // defaults to Double In your code you can also append the characters `L`, `D`, and `F` (and their lowercase equivalents) to numbers to specify that they are `Long`, `Double`, or `Float` values: -{% tabs var-express-9 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-9 %} +{% tabs var-express-9 %} +{% tab 'Scala 2 and 3' %} ```scala val x = 1_000L // val x: Long = 1000 @@ -174,8 +174,8 @@ val z = 3.3F // val z: Float = 3.3 When you need really large numbers, use the `BigInt` and `BigDecimal` types: -{% tabs var-express-10 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-10 %} +{% tabs var-express-10 %} +{% tab 'Scala 2 and 3' %} ```scala var a = BigInt(1_234_567_890_987_654_321L) @@ -188,8 +188,8 @@ Where `Double` and `Float` are approximate decimal numbers, `BigDecimal` is used Scala also has `String` and `Char` data types: -{% tabs var-express-11 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-11 %} +{% tabs var-express-11 %} +{% tab 'Scala 2 and 3' %} ```scala val name = "Bill" // String @@ -210,8 +210,8 @@ Scala strings are similar to Java strings, but they have two great additional fe String interpolation provides a very readable way to use variables inside strings. For instance, given these three variables: -{% tabs var-express-12 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-12 %} +{% tabs var-express-12 %} +{% tab 'Scala 2 and 3' %} ```scala val firstName = "John" @@ -223,8 +223,8 @@ val lastName = "Doe" You can combine those variables in a string like this: -{% tabs var-express-13 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-13 %} +{% tabs var-express-13 %} +{% tab 'Scala 2 and 3' %} ```scala println(s"Name: $firstName $mi $lastName") // "Name: John C Doe" @@ -236,8 +236,8 @@ Just precede the string with the letter `s`, and then put a `$` symbol before yo To embed arbitrary expressions inside a string, enclose them in curly braces: -{% tabs var-express-14 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-14 %} +{% tabs var-express-14 %} +{% tab 'Scala 2 and 3' %} ``` scala println(s"2 + 2 = ${2 + 2}") // prints "2 + 2 = 4" @@ -257,8 +257,8 @@ For instance, some database libraries define the very powerful `sql` interpolato Multiline strings are created by including the string inside three double-quotes: -{% tabs var-express-15 class=tabs-scala-version %} -{% tab 'Scala 2 and 3' for=var-express-15 %} +{% tabs var-express-15 %} +{% tab 'Scala 2 and 3' %} ```scala val quote = """The essence of Scala: diff --git a/_plugins/jekyll-tabs-lib/jekyll-tabs-4scala.rb b/_plugins/jekyll-tabs-lib/jekyll-tabs-4scala.rb index 3772c36525..b580d2f912 100644 --- a/_plugins/jekyll-tabs-lib/jekyll-tabs-4scala.rb +++ b/_plugins/jekyll-tabs-lib/jekyll-tabs-4scala.rb @@ -3,6 +3,8 @@ module Jekyll module Tabs + ScalaVersions = Set.new ['Scala 2', 'Scala 3'] + def self.unquote(string) string.gsub(/^['"]|['"]$/, '') end @@ -23,9 +25,15 @@ def initialize(block_name, markup, tokens) if markup =~ SYNTAX @name = Tabs::unquote($1) @css_classes = "" + @is_scala_tabs = false if $2 + css_class = Tabs::unquote($2) + css_class.strip! + if css_class == "tabs-scala-version" + @is_scala_tabs = true + end # append $2 to @css_classes - @css_classes = "#{@css_classes} #{Tabs::unquote($2)}" + @css_classes = " #{css_class}" end else raise SyntaxError.new("Block #{block_name} requires 1 attribute") @@ -35,13 +43,24 @@ def initialize(block_name, markup, tokens) def render(context) environment = context.environments.first environment["tabs-#{@name}"] = [] # reset every time (so page translations can use the same name) - super - + if environment["CURRENT_TABS_ENV"].nil? + environment["CURRENT_TABS_ENV"] = @name + else + raise SyntaxError.new("Nested tabs are not supported") + end + super # super call renders the internal content + environment["CURRENT_TABS_ENV"] = nil # reset after rendering foundDefault = false allTabs = environment["tabs-#{@name}"] + seenTabs = Set.new + allTabs.each do | tab | + if seenTabs.include? tab.label + raise SyntaxError.new("Duplicate tab label '#{tab.label}' in tabs '#{@name}'") + end + seenTabs.add tab.label if tab.defaultTab foundDefault = true end @@ -52,6 +71,17 @@ def render(context) allTabs[-1].defaultTab = true end + if @is_scala_tabs + allTabs.each do | tab | + if !Tabs::ScalaVersions.include?(tab.label) + joined_versions = Tabs::ScalaVersions.to_a.map{|item| "'#{item}'"}.join(", ") + raise SyntaxError.new( + "Scala version tab label '#{tab.label}' is not valid for tabs '#{@name}' with " + + "class=tabs-scala-version. Valid tab labels are: #{joined_versions}") + end + end + end + currentDirectory = File.dirname(__FILE__) templateFile = File.read(currentDirectory + '/template.erb') template = ERB.new(templateFile) @@ -62,7 +92,7 @@ def render(context) class TabBlock < Liquid::Block alias_method :render_block, :render - SYNTAX = /^\s*(#{Liquid::QuotedFragment})\s+(?:for=(#{Liquid::QuotedFragment}))(?:\s+(defaultTab))?/o + SYNTAX = /^\s*(#{Liquid::QuotedFragment})\s+(?:for=(#{Liquid::QuotedFragment}))?(?:\s+(defaultTab))?/o Syntax = SYNTAX def initialize(block_name, markup, tokens) @@ -70,7 +100,9 @@ def initialize(block_name, markup, tokens) if markup =~ SYNTAX @tab = Tabs::unquote($1) - @name = Tabs::unquote($2) + if $2 + @name = Tabs::unquote($2) + end @anchor = Tabs::asAnchor(@tab) if $3 @defaultTab = true @@ -88,8 +120,16 @@ def render(context) content = converter.convert(pre_content) tabcontent = TabDetails.new(label: @tab, anchor: @anchor, defaultTab: @defaultTab, content: content) environment = context.environments.first - environment["tabs-#{@name}"] ||= [] - environment["tabs-#{@name}"] << tabcontent + tab_env = environment["CURRENT_TABS_ENV"] + if tab_env.nil? + raise SyntaxError.new("Tab block '#{tabcontent.label}' must be inside a tabs block") + end + if !@name.nil? && tab_env != @name + raise SyntaxError.new( + "Tab block '#{@tab}' for=#{@name} does not match its enclosing tabs block #{tab_env}") + end + environment["tabs-#{tab_env}"] ||= [] + environment["tabs-#{tab_env}"] << tabcontent end end end diff --git a/_tour/higher-order-functions.md b/_tour/higher-order-functions.md index 3071c3ccf0..9863069b66 100644 --- a/_tour/higher-order-functions.md +++ b/_tour/higher-order-functions.md @@ -16,12 +16,12 @@ The terminology can get a bit confusing at this point, and we use the phrase "higher order function" for both methods and functions that take functions as parameters or that return a function. -In a pure Object Oriented world a good practice is to avoid exposing methods parameterized with functions that might leak object's internal state. Leaking internal state might break the invariants of the object itself thus violating encapsulation. +In a pure Object Oriented world a good practice is to avoid exposing methods parameterized with functions that might leak object's internal state. Leaking internal state might break the invariants of the object itself thus violating encapsulation. One of the most common examples is the higher-order function `map` which is available for collections in Scala. -{% tabs map_example_1 class=tabs-scala-version %} +{% tabs map_example_1 %} {% tab 'Scala 2 and 3' for=map_example_1 %} ```scala mdoc:nest @@ -39,7 +39,7 @@ list of salaries. To shrink the code, we could make the function anonymous and pass it directly as an argument to map: -{% tabs map_example_2 class=tabs-scala-version %} +{% tabs map_example_2 %} {% tab 'Scala 2 and 3' for=map_example_2 %} ```scala mdoc:nest @@ -53,7 +53,7 @@ val newSalaries = salaries.map(x => x * 2) // List(40000, 140000, 80000) Notice how `x` is not declared as an Int in the above example. That's because the compiler can infer the type based on the type of function map expects (see [Currying](/tour/multiple-parameter-lists.html)). An even more idiomatic way to write the same piece of code would be: -{% tabs map_example_3 class=tabs-scala-version %} +{% tabs map_example_3 %} {% tab 'Scala 2 and 3' for=map_example_3 %} ```scala mdoc:nest diff --git a/_zh-cn/overviews/scala3-book/why-scala-3.md b/_zh-cn/overviews/scala3-book/why-scala-3.md index e80bf387b4..ce9eba7084 100644 --- a/_zh-cn/overviews/scala3-book/why-scala-3.md +++ b/_zh-cn/overviews/scala3-book/why-scala-3.md @@ -43,7 +43,7 @@ Scala 比任何其他语言都更支持 FP 和 OOP 范式的融合。 例如,`List` 被定义为一个类---从技术上讲,它是一个抽象类---并且像这样创建了一个新实例: {% tabs list %} -{% tab 'Scala 2 and 3' for=list %} +{% tab 'Scala 2 and 3' %} ```scala val x = List(1, 2, 3) ``` @@ -55,8 +55,8 @@ val x = List(1, 2, 3) 除了从一系列模块化 traits 构建/cases像 `List` 这样的类型之外,`List` API还包含数十种其他方法,其中许多是高阶函数: -{% tabs list %} -{% tab 'Scala 2 and 3' for=list-methods %} +{% tabs list-methods %} +{% tab 'Scala 2 and 3' %} ```scala val xs = List(1, 2, 3, 4, 5) @@ -76,8 +76,8 @@ xs.takeWhile(_ < 3) // List(1, 2) Scala的 _类型推断_ 经常使语言感觉是动态类型的,即使它是静态类型的。 对于变量声明,情况确实如此: -{% tabs list %} -{% tab 'Scala 2 and 3' for=dynamic %} +{% tabs dynamic %} +{% tab 'Scala 2 and 3' %} ```scala val a = 1 val b = "Hello, world" @@ -89,8 +89,8 @@ val stuff = ("fish", 42, 1_234.5) 当把匿名函数传递给高阶函数时,情况也是如此: -{% tabs list %} -{% tab 'Scala 2 and 3' for=dynamic-hof %} +{% tabs dynamic-hof %} +{% tab 'Scala 2 and 3' %} ```scala list.filter(_ < 4) list.map(_ * 2) @@ -102,8 +102,8 @@ list.filter(_ < 4) 还有定义方法的时候: -{% tabs list %} -{% tab 'Scala 2 and 3' for=list-method %} +{% tabs list-method %} +{% tab 'Scala 2 and 3' %} ```scala def add(a: Int, b: Int) = a + b ``` @@ -113,7 +113,7 @@ def add(a: Int, b: Int) = a + b 这在Scala 3中比以往任何时候都更加真实,例如在使用[union types][union-types] 时: {% tabs union %} -{% tab 'Scala 3 Only' for=union %} +{% tab 'Scala 3 Only' %} ```scala // union type parameter def help(id: Username | Password) = @@ -132,8 +132,8 @@ val b: Password | Username = if (true) name else password Scala是一种 low ceremony,“简洁但仍然可读”的语言。例如,变量声明是简洁的: -{% tabs list %} -{% tab 'Scala 2 and 3' for=concise %} +{% tabs concise %} +{% tab 'Scala 2 and 3' %} ```scala val a = 1 val b = "Hello, world" @@ -145,7 +145,7 @@ val c = List(1,2,3) 创建类型如traits, 类和枚举都很简洁: {% tabs enum %} -{% tab 'Scala 3 Only' for=enum %} +{% tab 'Scala 3 Only' %} ```scala trait Tail: def wagTail(): Unit @@ -168,7 +168,7 @@ case class Person( 简洁的高阶函数: {% tabs list-hof %} -{% tab 'Scala 2 and 3' for=list-hof %} +{% tab 'Scala 2 and 3' %} ```scala list.filter(_ < 4) list.map(_ * 2) @@ -251,8 +251,8 @@ Scala.js 生态系统 [有几十个库](https://www.scala-js.org/libraries) 让 这里有些例子: -{% tabs list %} -{% tab 'Scala 2 and 3' for=list-more %} +{% tabs list-more %} +{% tab 'Scala 2 and 3' %} ```scala List.range(1, 3) // List(1, 2) List.range(start = 1, end = 6, step = 2) // List(1, 3, 5) @@ -299,8 +299,8 @@ nums.sortWith(_ > _) // List(10, 8, 7, 5, 1) Scala 习语以多种方式鼓励最佳实践。 对于不可变性,我们鼓励您创建不可变的 `val` 声明: -{% tabs list %} -{% tab 'Scala 2 and 3' for=val %} +{% tabs val %} +{% tab 'Scala 2 and 3' %} ```scala val a = 1 // 不可变变量 ``` @@ -309,8 +309,8 @@ val a = 1 // 不可变变量 还鼓励您使用不可变集合类,例如 `List` 和 `Map`: -{% tabs list %} -{% tab 'Scala 2 and 3' for=list-map %} +{% tabs list-map %} +{% tab 'Scala 2 and 3' %} ```scala val b = List(1,2,3) // List 是不可变的 val c = Map(1 -> "one") // Map 是不可变的 @@ -320,8 +320,8 @@ val c = Map(1 -> "one") // Map 是不可变的 样例类主要用于 [领域建模]({% link _zh-cn/overviews/scala3-book/domain-modeling-intro.md %}),它们的参数是不可变的: -{% tabs list %} -{% tab 'Scala 2 and 3' for=case-class %} +{% tabs case-class %} +{% tab 'Scala 2 and 3' %} ```scala case class Person(name: String) val p = Person("Michael Scott") @@ -333,8 +333,8 @@ p.name = "Joe" // 编译器错误(重新分配给 val 名称) 如上一节所示,Scala 集合类支持高阶函数,您可以将方法(未显示)和匿名函数传递给它们: -{% tabs list %} -{% tab 'Scala 2 and 3' for=higher-order %} +{% tabs higher-order %} +{% tab 'Scala 2 and 3' %} ```scala a.dropWhile(_ < 25) a.filter(_ < 25) @@ -349,7 +349,7 @@ nums.sortWith(_ > _) `match` 表达式让您可以使用模式匹配,它们确实是返回值的 _表达式_: {% tabs match class=tabs-scala-version %} -{% tab 'Scala 2' for=match %} +{% tab 'Scala 2' %} ```scala val numAsString = i match { case 1 | 3 | 5 | 7 | 9 => "odd" @@ -359,7 +359,7 @@ val numAsString = i match { ``` {% endtab %} -{% tab 'Scala 3' for=match %} +{% tab 'Scala 3' %} ```scala val numAsString = i match case 1 | 3 | 5 | 7 | 9 => "odd" @@ -372,7 +372,7 @@ val numAsString = i match 因为它们可以返回值,所以它们经常被用作方法的主体: {% tabs match-body class=tabs-scala-version %} -{% tab 'Scala 2' for=match-body %} +{% tab 'Scala 2' %} ```scala def isTruthy(a: Matchable) = a match { case 0 | "" => false @@ -381,7 +381,7 @@ def isTruthy(a: Matchable) = a match { ``` {% endtab %} -{% tab 'Scala 3' for=match-body %} +{% tab 'Scala 3' %} ```scala def isTruthy(a: Matchable) = a match case 0 | "" => false @@ -457,7 +457,7 @@ _安全_ 与几个新的和改变的特性有关: _人体工程学_ 的好例子是枚举和扩展方法,它们以非常易读的方式添加到 Scala 3 中: {% tabs extension %} -{% tab 'Scala 3 Only' for=extension %} +{% tab 'Scala 3 Only' %} ```scala // 枚举 enum Color: