diff --git a/next/conf.py b/next/conf.py index 47ff769d..b45515c1 100644 --- a/next/conf.py +++ b/next/conf.py @@ -21,7 +21,7 @@ extensions = ['myst_parser', 'lexer'] templates_path = ['_templates'] -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', ".env", '.venv', "README"] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', ".env", '.venv', "README", 'sources'] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output diff --git a/next/locales/zh_CN/LC_MESSAGES/sources.po b/next/locales/zh_CN/LC_MESSAGES/sources.po new file mode 100644 index 00000000..11db02a2 --- /dev/null +++ b/next/locales/zh_CN/LC_MESSAGES/sources.po @@ -0,0 +1,30 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024, International Digital Economy Academy +# This file is distributed under the same license as the MoonBit Document +# package. +# FIRST AUTHOR , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: MoonBit Document \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-11-28 16:03+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language: zh_CN\n" +"Language-Team: zh_CN \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.16.0\n" + +#: ../../sources/segment-tree/README.md:1 +msgid "Segment Tree" +msgstr "" + +#: ../../sources/segment-tree/README.md:3 +msgid "Check `tutorial/example/segment-tree`." +msgstr "" + diff --git a/next/locales/zh_CN/LC_MESSAGES/toolchain.po b/next/locales/zh_CN/LC_MESSAGES/toolchain.po index 81106ce7..2aaf4b8d 100644 --- a/next/locales/zh_CN/LC_MESSAGES/toolchain.po +++ b/next/locales/zh_CN/LC_MESSAGES/toolchain.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: MoonBit Document \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-27 13:35+0800\n" +"POT-Creation-Date: 2024-11-28 16:03+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -1176,7 +1176,7 @@ msgstr "" msgid "" "{\n" " \"name\": \"example\",\n" -" ...\n" +" // ...\n" "}\n" msgstr "" @@ -1194,7 +1194,7 @@ msgstr "" msgid "" "{\n" " \"name\": \"moonbitlang/core\",\n" -" ...\n" +" // ...\n" "}\n" msgstr "" @@ -1219,7 +1219,7 @@ msgid "" "{\n" " \"name\": \"example\",\n" " \"version\": \"0.1.0\",\n" -" ...\n" +" // ...\n" "}\n" msgstr "" @@ -2687,4 +2687,3 @@ msgid "" "test username/hello/lib/fib/fib_test.mbt::0 ok\n" "Total tests: 3, passed: 3, failed: 0.\n" msgstr "" - diff --git a/next/locales/zh_CN/LC_MESSAGES/tutorial.po b/next/locales/zh_CN/LC_MESSAGES/tutorial.po index 677ee3e8..e0294c16 100644 --- a/next/locales/zh_CN/LC_MESSAGES/tutorial.po +++ b/next/locales/zh_CN/LC_MESSAGES/tutorial.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: MoonBit Document \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-27 13:35+0800\n" +"POT-Creation-Date: 2024-11-28 17:11+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -20,10 +20,5722 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.16.0\n" -#: ../../tutorial/index.md:7 -msgid "Contents:" +#: ../../tutorial/example/gmachine/gmachine-1.md:1 +msgid "G-Machine 1" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:3 +msgid "" +"Lazy evaluation stands as a foundational concept in the realm of " +"programming languages. Haskell, renowned as a purely functional " +"programming language, boasts a robust lazy evaluation mechanism. This " +"mechanism not only empowers developers to craft code that's both more " +"efficient and concise but also enhances program performance and " +"responsiveness, especially when tackling sizable datasets or intricate " +"data streams. In this article, we'll delve into the Lazy Evaluation " +"mechanism, thoroughly examining its principles and implementation " +"methods, and then explore how to implement Haskell's evaluation semantics" +" in [MoonBit](https://www.moonbitlang.com/)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:5 +msgid "Higher-Order Functions and Performance Challenges" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:7 +msgid "" +"Higher-order functions such as `map` and `filter` often serve as many " +"people's first impression of functional programming (although it goes far" +" beyond these functions). They simplify many list processing tasks, but " +"another problem emerges: using too many of these higher-order functions " +"can lead to poor performance (because it requires multiple traversals of " +"the list)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:9 +msgid "" +"To enhance code efficiency, some propose leveraging compiler " +"optimizations based on recurring patterns within higher-order functions. " +"For instance, by rewriting `map(f, map(g, list))` as:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:11 +msgid "map(fn (x) { f(g(x)) }, list)\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:15 +msgid "" +"Nice try, but it's important to recognize that such optimization " +"techniques have inherent limitations, particularly when navigating more " +"complex scenarios. Consolidating all processes into a single function " +"might circumvent the need for repeated list traversals, yet it " +"detrimentally affects code readability and complicates the process of " +"making modifications. Could there be a more equitable solution that " +"balances efficiency with maintainability?" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:17 +msgid "" +"Lazy evaluation is a technique that can reduce unnecessary costs to some " +"extent in such scenarios. This strategy can be integrated into specific " +"data structures (for example, the Stream type added in Java 8, and the " +"stream in the earlier Scheme language), or the entire language can be " +"designed to be lazy (successful examples include the Miranda language of " +"the 1980s and later by Haskell and Clean languages)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:19 +msgid "" +"Let's first explore how lazy lists (`Stream`) can avoid multiple " +"traversals in such cases." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:22 +msgid "The `List[T]` here is a `typealias` of `@immut/list.T[T]`" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:25 +msgid "Lazy List Implementation" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:27 +msgid "First, let's define its type:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:29 +msgid "" +"enum Stream[T] {\n" +" Empty\n" +" Cons(T, () -> Stream[T])\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:36 +msgid "" +"The only real difference between `Stream[T]` and `List[T]` is in the " +"`Cons`: the place holding the rest of the list is replaced with a " +"parameterless function (in jargon, called a thunk). This is a simple " +"implementation of lazy evaluation: wrapping things you don't want to " +"compute right away in a thunk." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:38 +msgid "We also need a function to convert a regular list into a lazy list:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:40 +msgid "" +"fn Stream::from_list[T](l : List[T]) -> Stream[T] {\n" +" match l {\n" +" Nil => Empty\n" +" Cons(x, xs) => Cons(x, fn () { Stream::from_list(xs) })\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:49 +msgid "" +"This function does not need to traverse the entire list to convert it " +"into `Stream`. For operations that are not urgent (here, " +"`Stream::from_list(xs)`), we wrap them directly in a thunk and return. " +"The following `map` function will adopt this approach (though here, `xs` " +"is already a thunk)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:51 +msgid "" +"fn map[X, Y](self : Stream[X], f : (X) -> Y) -> Stream[Y] {\n" +" match self {\n" +" Empty => Empty\n" +" Cons(x, xs) => Cons(f(x), fn () { xs().map(f) })\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:60 +msgid "" +"The `take` function is responsible for performing computations, and it " +"can extract n elements as needed." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:62 +msgid "" +"fn take[T](self : Stream[T], n : Int) -> List[T] {\n" +" if n == 0 {\n" +" Nil\n" +" } else {\n" +" match self {\n" +" Empty => Nil\n" +" Cons(x, xs) => Cons(x, xs().take(n - 1))\n" +" }\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:75 +msgid "" +"The implementation of lazy data structures using thunks is " +"straightforward and effectively addresses the problems mentioned above. " +"This method requires users to explicitly indicate where in the code " +"computation should be delayed, whereas the strategy of lazy languages is " +"much more aggressive: it defaults to using lazy evaluation for all user-" +"defined functions! In the following sections, we will present a minimal " +"implementation of a lazy functional language and briefly introduce its " +"underlying theoretical model." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:77 +msgid "A Lazy Evaluation Language and Its Abstract Syntax Tree" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:79 +msgid "" +"The example used in this article is a lazy evaluation language, " +"deliberately made to resemble Clojure (a Lisp dialect) and named coreF. " +"This design choice allows for the use of Clojure's syntax highlighting in" +" markdown. Don't worry, though the syntax might seem a bit complex at " +"first, it is straightforward enough." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:81 +msgid "Functions are defined using the `defn` keyword:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:83 +msgid "" +"(defn factorial[n] ;; n is the parameter, this function calculates the " +"factorial of n\n" +" (if (eq n 0) ;; The definition starts here and continues for the next " +"three lines\n" +" 1\n" +" (mul n (factorial (sub n 1)))))\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:90 +msgid "" +"Referring to it as a function in general conversation is acceptable. " +"However, when discussing lazy functional languages, we must introduce a " +"specialized term: _Super Combinator_. In the definition of a super " +"combinator, all free variables should be included in an initial pair of " +"`[]`." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:92 +msgid "" +"Execution of a coreF program begins with `main`, calling a specific super" +" combinator as if replacing it with its definition." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:94 +msgid "(defn main[] (factorial 42))\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:98 +msgid "" +"Super combinators without parameters, such as `main`, are referred to by " +"a specific term: _Constant Applicative Forms (CAF)_." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:100 +msgid "" +"coreF also possesses several language features, including custom data " +"structures, `case` expressions for dismantling structures, and `let` and " +"`letrec` for the declaration of local variables. However, the scope of " +"this article is limited to the aforementioned features (actually, even " +"less, as built-in functions like `eq`, `mul`, `sub`, etc., are planned " +"for future implementation)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:102 +msgid "" +"coreF excludes anonymous functions because anonymous functions introduce " +"extra free variables. Removing them requires an additional transformation" +" step: lambda lifting. This technique can transform a lambda expression " +"into an external Super Combinator, but this is not a main point of lazy " +"evaluation, hence its omission here." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:104 +msgid "" +"Super combinators will eventually be parsed into `ScDef[String]`, but " +"writing a parser is a tedious task. I will provide it along with the " +"final code." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:106 +msgid "" +"enum RawExpr[T] {\n" +" Var(T)\n" +" Num(Int)\n" +" Constructor(Int, Int) // tag, arity\n" +" App(RawExpr[T], RawExpr[T])\n" +" Let(Bool, List[(T, RawExpr[T])], RawExpr[T]) // isRec, Defs, Body\n" +" Case(RawExpr[T], List[(Int, List[T], RawExpr[T])])\n" +"} derive(Show)\n" +"\n" +"struct ScDef[T] {\n" +" name : String\n" +" args : List[T]\n" +" body : RawExpr[T]\n" +"} derive(Show)\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:123 +msgid "Additionally, some predefined coreF programs are required." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:125 +msgid "" +"let preludeDefs : List[ScDef[String]] = {\n" +" let id = ScDef::new(\"I\", List::of([\"x\"]), Var(\"x\")) // id x = x\n" +" let k = ScDef::new(\"K\", List::of([\"x\", \"y\"]), Var(\"x\")) // K x " +"y = x\n" +" let k1 = ScDef::new(\"K1\", List::of([\"x\", \"y\"]), Var(\"y\")) // K1" +" x y = y\n" +" let s = ScDef::new(\n" +" \"S\",\n" +" List::of([\"f\", \"g\", \"x\"]),\n" +" App(App(Var(\"f\"), Var(\"x\")), App(Var(\"g\"), Var(\"x\"))),\n" +" ) // S f g x = f x (g x)\n" +" let compose = ScDef::new(\n" +" \"compose\",\n" +" List::of([\"f\", \"g\", \"x\"]),\n" +" App(Var(\"f\"), App(Var(\"g\"), Var(\"x\"))),\n" +" ) // compose f g x = f (g x)\n" +" let twice = ScDef::new(\n" +" \"twice\",\n" +" List::of([\"f\"]),\n" +" App(App(Var(\"compose\"), Var(\"f\")), Var(\"f\")),\n" +" ) // twice f = compose f f\n" +" Cons(id, Cons(k, Cons(k1, Cons(s, Cons(compose, Cons(twice, Nil))))))\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:149 +msgid "Why Graph" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:151 +msgid "" +"In the coreF language, expressions (not `RawExpr[T]` mentioned earlier, " +"but runtime expressions) are stored in memory in the form of a graph " +"rather than a tree when being evaluated.)" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:153 +msgid "Why is this approach taken? Let's examine this through a program example:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:155 +msgid "" +"(defn square[x] (mul x x))\n" +"(defn main[] (square (square 3)))\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:160 +msgid "" +"If we evaluate according to the conventional expression tree, it would be" +" reduced to:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:162 +msgid "(mul (square 3) (square 3))\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:166 +msgid "" +"In this case, `(square 3)` would be evaluated twice, which is certainly " +"not desirable for lazy evaluation." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:168 +msgid "" +"To illustrate this more clearly, let's make a somewhat improper analogy " +"using MoonBit code:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:170 +msgid "" +"fn square(thunk : () -> Int) -> Int {\n" +" thunk() * thunk()\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:176 +msgid "" +"To represent the program using a graph is to facilitate sharing of " +"computation results and avoid redundant calculations. To achieve this " +"purpose, it's crucial to implement an in-place update algorithm when " +"reducing the graph. Regarding in-place update, let's simulate it using " +"MoonBit code:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:178 +msgid "" +"enum LazyData[T] {\n" +" Waiting(() -> T)\n" +" Done(T)\n" +"}\n" +"\n" +"struct LazyRef[T] {\n" +" mut data : LazyData[T]\n" +"}\n" +"\n" +"fn extract[T](self : LazyRef[T]) -> T {\n" +" match self.data {\n" +" Waiting(thunk) => {\n" +" let value = thunk()\n" +" self.data = Done(value) // in-place update\n" +" value\n" +" }\n" +" Done(value) => value\n" +" }\n" +"}\n" +"\n" +"fn square(x : LazyRef[Int]) -> Int {\n" +" x.extract() * x.extract()\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:204 +msgid "" +"Regardless of which side executes the `extract` method first, it will " +"update the referenced mutable field and replace its content with the " +"computed result. Therefore, there's no need to recompute it during the " +"second execution of the `extract` method." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:206 +#, fuzzy +msgid "Conventions" msgstr "目录:" +#: ../../tutorial/example/gmachine/gmachine-1.md:208 +msgid "" +"Before delving into how graph reduction works, let's establish some key " +"terms and basic facts. We'll continue using the same program as an " +"example:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:210 +msgid "" +"(defn square[x] (mul x x)) ;; multiplication\n" +"(defn main[] (square (square 3)))\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:215 +msgid "Built-in primitives like `mul` are predefined operations." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:217 +msgid "" +"Evaluating an expression (of course, lazy) and updating its corresponding" +" node in the graph in place is called reduction." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:218 +msgid "" +"`(square 3)` is a reducible expression (often abbreviated as redex), " +"consisting of `square` and its argument. It can be reduced to `(mul 3 " +"3)`. `(mul 3 3)` is also a redex, but it's a different type of redex " +"compared to `(square 3)` because `square` is a user-defined combinator " +"while `mul` is an implemented built-in primitive." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:219 +msgid "" +"The reduction result of `(mul 3 3)` is the expression `9`, which cannot " +"be further reduced. Such expressions that cannot be further reduced are " +"called Normal forms." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:220 +msgid "" +"An expression may contain multiple sub-expressions (e.g., `(mul (add 3 5)" +" (mul 7 9))`). In such cases, the order of reduction of expressions is " +"crucial – some programs only halt under specific reduction orders." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:222 +msgid "" +"There's a special reduction order that always selects the outermost redex" +" for reduction, known as _normal order reduction_. This reduction order " +"will be uniformly adopted in the following discussion." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:224 +msgid "So, the graph reduction can be described with the following pseudocode:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:226 +msgid "" +"While there exist reducible expressions in the graph {\n" +" Select the outermost reducible expression.\n" +" Reduce the expression.\n" +" Update the graph with the result of reduction.\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:234 +msgid "" +"Dizzy now? Let's find a few examples to demonstrate how to perform " +"reductions on paper." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:236 +msgid "**Step 1: Find the next redex**" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:238 +msgid "The execution of the entire program starts from the `main` function." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:240 +msgid "" +"(defn square[x] (mul x x))\n" +"(defn main[] (add 33 (square 3)))\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:245 +msgid "" +"`main` itself is a CAF - the simplest kind of redex. If we perform the " +"substitution, the current expression to be handled is:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:247 +msgid "(add 33 (square 3))\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:251 +msgid "" +"According to the principle of finding the outermost redex, it seems like " +"we've immediately found the redex formed by `add` and its two parameters " +"(let's assume it for now)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:253 +msgid "" +"But wait! Due to the presence of default currying, the abstract syntax " +"tree corresponding to this expression is actually composed of multiple " +"nested `App` nodes. It roughly looks like this (simplified for " +"readability):" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:255 +msgid "App(App(add, 33), square3)\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:259 +msgid "" +"This chain-like structure from `add` to the outermost `App` node is " +"called the \"Spine\"" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:261 +msgid "" +"Going back to check, `add` is an internally defined primitive. However, " +"since its second argument `(square 3)` is not in normal form, we cannot " +"reduce it (adding an unevaluated expression to an integer seems a bit " +"absurd). So, we can't definitively say that `(add 33 (square 3))` is a " +"redex; it's just the outermost function application. To reduce it, we " +"must first reduce `(square 3)`." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:263 +msgid "**Step 2: Reduce**" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:265 +msgid "" +"Since `square` is a user-defined super combinator, reducing `(square 3)` " +"involves only parameter substitution." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:267 +msgid "" +"If a redex has fewer arguments than required by the super combinator, " +"which is common in higher-order functions, consider the example of " +"tripling all integers in a list." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:269 +msgid "(map (mul 3) list-of-int)\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:273 +msgid "" +"Here, `(mul 3)` cannot be treated as a redex because it lacks sufficient " +"arguments, making it a `weak head normal form` (often abbreviated as " +"WHNF). In this situation, even if its sub-expressions contain redexes, no" +" action is needed." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:275 +msgid "**Step 3: Update**" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:277 +msgid "" +"This step only affects execution efficiency and can be skipped during " +"paper deductions." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:279 +msgid "" +"These operations are easy to perform on paper (when the amount of code " +"doesn't exceed half a sheet), but when we switch to computers, how do we " +"translate these steps into executable code?" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:281 +msgid "" +"To answer this question, pioneers in the world of lazy evaluation " +"programming languages have proposed various **abstract machines** for " +"modeling lazy evaluation. These include:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:283 +#: ../../tutorial/example/gmachine/index.md:1 +msgid "G-Machine" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:284 +msgid "Three Instruction Machine" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:285 +msgid "ABC Machine (used by the Clean language)" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:286 +msgid "Spineless Tagless G-Machine (abbreviated as STG, used by Haskell language)" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:288 +msgid "" +"They are execution models used to guide compiler implementations. It's " +"important to note that, unlike various popular virtual machines today " +"(such as the JVM), abstract machines are more like intermediate " +"representations (IR) for compilers. Taking Haskell's compiler GHC as an " +"example, after generating STG (Spineless Tagless G-Machine) code, it " +"doesn't directly pass it to an interpreter for execution. Instead, it " +"further transforms it into LLVM, C code, or machine code based on the " +"selected backend." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:290 +msgid "" +"To simplify implementation, this article will directly use MoonBit to " +"write an interpreter for G-Machine instructions, starting from a minimal " +"example and gradually adding more features." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:292 +msgid "G-Machine Overview" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:294 +msgid "" +"While the G-Machine is an abstract machine for lazy functional languages," +" its structure and concepts are not significantly different from what one" +" encounters when writing general imperative languages. It also features " +"structures like heap and stack, and its instructions are executed " +"sequentially. Some key differences include:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:296 +msgid "The basic unit of memory in the heap is not bytes, but graph nodes." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:297 +msgid "" +"The stack only contains pointers to addresses in the heap, not actual " +"data." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:299 +msgid "This design may not be practical, but it's relatively simple." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:301 +msgid "" +"In coreF, super combinators are compiled into a series of G-Machine " +"instructions. These instructions can be roughly categorized as follows:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:303 +msgid "" +"Access Data Instructions, For example, `PushArg` (access function " +"arguments), and `PushGlobal` (access other super combinators)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:304 +msgid "" +"Construct/update graph nodes in the heap, like `MkApp`, `PushInt`, " +"`Update`" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:305 +msgid "Clean up the `Pop` instruction of the unused addresses from the stack." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:306 +msgid "Express control flow with the `Unwind` instruction" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:308 +msgid "Dissecting the G-Machine State" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:310 +msgid "In this simple version of the G-Machine, the state includes:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:312 +msgid "" +"Heap: This is where the expression graph and the sequences of " +"instructions corresponding to super combinators are stored." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:314 +#, python-format +msgid "" +"// Use the 'type' keyword to encapsulate an address type.\n" +"type Addr Int derive(Eq, Show) \n" +"\n" +"// Describe graph nodes with an enumeration type.\n" +"enum Node { \n" +" NNum(Int)\n" +" // The application node\n" +" NApp(Addr, Addr) \n" +" // To store the number of parameters and \n" +" // the corresponding sequence of instructions for a super combinator.\n" +" NGlobal(String, Int, List[Instruction]) \n" +" // The Indirection node,The key component of implementing lazy " +"evaluation\n" +" NInd(Addr) \n" +"} derive (Eq, Show)\n" +"\n" +"struct GHeap { // The heap uses an array, and the space with None content" +" in the array is available as free memory.\n" +" mut objectCount : Int\n" +" memory : Array[Option[Node]]\n" +"}\n" +"\n" +"// Allocate heap space for nodes.\n" +"fn alloc(self : GHeap, node : Node) -> Addr {\n" +" let heap = self\n" +" // Assuming there is still available space in the heap.\n" +" fn next(n : Int) -> Int {\n" +" (n + 1) % heap.memory.length()\n" +" }\n" +" fn free(i : Int) -> Bool {\n" +" heap.memory[i].is_empty()\n" +" }\n" +" let mut i = heap.objectCount\n" +" while not(free(i)) {\n" +" i = next(i)\n" +" }\n" +" heap.memory[i] = Some(node)\n" +" return Addr(i)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:354 +msgid "" +"Stack: The stack only holds addresses pointing to the heap. A simple " +"implementation can use `List[Addr]`." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:355 +msgid "" +"Global Table: It's a mapping table that records the names of super " +"combinators (including predefined and user-defined) and their " +"corresponding addresses as `NGlobal` nodes. Here I implement it using a " +"Robin Hood hash table." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:356 +msgid "Current code sequence to be executed." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:357 +msgid "" +"Execution status statistics: A simple implementation involves calculating" +" how many instructions have been executed." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:359 +msgid "" +"type GStats Int\n" +"\n" +"let statInitial : GStats = GStats(0)\n" +"\n" +"fn statInc(self : GStats) -> GStats {\n" +" let GStats(n) = self\n" +" GStats(n + 1)\n" +"}\n" +"\n" +"fn statGet(self : GStats) -> Int {\n" +" let GStats(n) = self\n" +" return n\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:375 +msgid "The entire state is represented using the type `GState`." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:377 +msgid "" +"struct GState {\n" +" mut stack : List[Addr]\n" +" heap : GHeap\n" +" globals : RHTable[String, Addr]\n" +" mut code : List[Instruction]\n" +" stats : GStats\n" +"}\n" +"\n" +"fn putStack(self : GState, addr : Addr) -> Unit {\n" +" self.stack = Cons(addr, self.stack)\n" +"}\n" +"\n" +"fn putCode(self : GState, is : List[Instruction]) -> Unit {\n" +" self.code = append(is, self.code)\n" +"}\n" +"\n" +"fn pop1(self : GState) -> Addr {\n" +" match self.stack {\n" +" Cons(addr, reststack) => {\n" +" self.stack = reststack\n" +" addr\n" +" }\n" +" Nil => {\n" +" abort(\"pop1: stack size smaller than 1\")\n" +" }\n" +" }\n" +"}\n" +"\n" +"fn pop2(self : GState) -> (Addr, Addr) {\n" +" // Pop 2 pops the top two elements from the stack.\n" +" // Returns (the first, the second).\n" +" match self.stack {\n" +" Cons(addr1, Cons(addr2, reststack)) => {\n" +" self.stack = reststack\n" +" (addr1, addr2)\n" +" }\n" +" otherwise => {\n" +" abort(\"pop2: stack size smaller than 2\")\n" +" }\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:421 +#: ../../tutorial/example/gmachine/gmachine-1.md:427 +msgid "" +"Now, we can map each step of the graph reduction algorithm we deduced on " +"paper to this abstract machine:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:423 +#: ../../tutorial/example/gmachine/gmachine-1.md:429 +msgid "" +"At the initial state of the machine, all compiled super combinators have " +"been placed in `NGlobal` nodes on the heap. At this point, the current " +"code sequence in the G-Machine contains only two instructions. The first " +"instruction pushes the address of the `main` node onto the stack, and the" +" second instruction loads the corresponding code sequence of `main` into " +"the current code sequence." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:424 +#: ../../tutorial/example/gmachine/gmachine-1.md:430 +msgid "" +"The corresponding code sequence of `main` is instantiated on the heap, " +"where nodes are allocated and data is loaded accordingly, ultimately " +"constructing a graph in memory. This process is referred to as " +"\"instantiating\" `main`. Once instantiation is complete, the address of " +"the entry point of this graph is pushed onto the stack." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:425 +msgid "" +"After instantiation is finished, you need to update graph nodes (since " +"`main` has no parameters, there is no need to clean up residual unused " +"addresses in the stack) and find the next redex to clean up." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:431 +msgid "" +"After instantiation is finished, cleanup work is done, which involves " +"updating graph nodes (since `main` has no parameters, there is no need to" +" clean up residual unused addresses in the stack) and finding the next " +"redex." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:433 +msgid "All of these tasks have corresponding instruction implementations." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:435 +msgid "Corresponding Effect of Each Instruction" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:437 +msgid "The highly simplified G-Machine currently consists of 7 instructions." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:439 +msgid "" +"enum Instruction {\n" +" Unwind\n" +" PushGlobal(String)\n" +" PushInt(Int)\n" +" PushArg(Int)\n" +" MkApp\n" +" Update(Int)\n" +" Pop(Int)\n" +"} derive(Eq, Show)\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:451 +msgid "" +"The `PushInt` instruction is the simplest. It allocates an `NNum` node on" +" the heap and pushes its address onto the stack." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:453 +msgid "" +"fn push_int(self : GState, num : Int) -> Unit {\n" +" let addr = self.heap.alloc(NNum(num))\n" +" self.putStack(addr)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:460 +msgid "" +"The `PushGlobal` instruction retrieves the address of the specified super" +" combinator from the global table and then pushes the address onto the " +"stack." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:462 +msgid "" +"fn push_global(self : GState, name : String) -> Unit {\n" +" let sc = self.globals[name]\n" +" match sc {\n" +" None => abort(\"push_global(): cant find super combinator \\{name}\")" +"\n" +" Some(addr) => {\n" +" self.putStack(addr)\n" +" }\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:474 +msgid "" +"The `PushArg` instruction is a bit more complex. It has specific " +"requirements regarding the layout of addresses on the stack: the first " +"address should point to the super combinator node, followed by n " +"addresses pointing to N `NApp` nodes. `PushArg` retrieves the Nth " +"parameter, starting from the `offset + 1`." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:476 +msgid "" +"fn push_arg(self : GState, offset : Int) -> Unit {\n" +" // Skip the first super combinator node.\n" +" // Access the (offset + 1)th NApp node\n" +" let appaddr = @immut/list.unsafe_nth(self.stack, offset + 1)\n" +" let arg = match self.heap[appaddr] {\n" +" NApp(_, arg) => arg\n" +" otherwise => \n" +" abort(\n" +" \"push_arg: stack offset \\{offset} address \\{appaddr} node " +"\\{otherwise}, not a applicative node\"\n" +" )\n" +" }\n" +" self.putStack(arg)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:492 +msgid "" +"The `MkApp` instruction takes two addresses from the top of the stack, " +"constructs an `NApp` node, and pushes its address onto the stack." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:494 +msgid "" +"fn mkapp(self : GState) -> Unit {\n" +" let (a1, a2) = self.pop2()\n" +" let appaddr = self.heap.alloc(NApp(a1, a2))\n" +" self.putStack(appaddr)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:502 +msgid "" +"The `Update` instruction assumes that the first address on the stack " +"points to the current redex's evaluation result. It skips the addresses " +"of the immediately following super combinator nodes and replaces the Nth " +"`NApp` node with an indirect node pointing to the evaluation result. If " +"the current redex is a CAF, it directly replaces its corresponding " +"`NGlobal` node on the heap. From this, we can see why in lazy functional " +"languages, there is not much distinction between functions without " +"parameters and ordinary variables." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:504 +msgid "" +"fn update(self : GState, n : Int) -> Unit {\n" +" let addr = self.pop1()\n" +" let dst = @immut/list.unsafe_nth(self.stack, n)\n" +" self.heap[dst] = NInd(addr)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:512 +msgid "" +"The `Unwind` instruction in the G-Machine is akin to an evaluation loop. " +"It has several branching conditions based on the type of node " +"corresponding to the address at the top of the stack:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:514 +msgid "For `Nnum` nodes: Do nothing." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:515 +msgid "" +"For `NApp` nodes: Push the address of the left node onto the stack and " +"`Unwind` again." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:516 +msgid "" +"For `NGlobal` nodes: If there are enough parameters on the stack, load " +"this super combinator into the current code." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:517 +msgid "" +"For `NInd` nodes: Push the address contained within this indirect node " +"onto the stack and Unwind again." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:519 +msgid "" +"fn unwind(self : GState) -> Unit {\n" +" let addr = self.pop1()\n" +" match self.heap[addr] {\n" +" NNum(_) => self.putStack(addr)\n" +" NApp(a1, _) => {\n" +" self.putStack(addr)\n" +" self.putStack(a1)\n" +" self.putCode(Cons(Unwind, Nil))\n" +" }\n" +" NGlobal(_, n, c) => {\n" +" if self.stack.length() < n {\n" +" abort(\"Unwinding with too few arguments\")\n" +" } else {\n" +" self.putStack(addr)\n" +" self.putCode(c)\n" +" }\n" +" }\n" +" NInd(a) => {\n" +" self.putStack(a)\n" +" self.putCode(Cons(Unwind, Nil))\n" +" }\n" +" otherwise => \n" +" abort(\"unwind() : wrong kind of node \\{otherwise}, address " +"\\{addr}\")\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:547 +msgid "" +"The `Pop` instruction pops N addresses, eliminating the need for a " +"separate function implementation." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:549 +msgid "Compiling Super Combinators into Instruction Sequences" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:551 +msgid "" +"In the G-Machine Overview section, we roughly described the behavior of " +"compiled super combinators. Now we can precisely describe the compilation" +" process of super combinators." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:553 +msgid "" +"Firstly, before the instruction sequence of a compiled super combinator " +"is executed, there must be certain addresses already present in the " +"stack:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:555 +msgid "" +"The topmost address points to an `NGlobal` node (the super combinator " +"itself)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:556 +msgid "" +"Following are N addresses (N being the number of parameters for this " +"super combinator), pointing to a series of App nodes - corresponding " +"exactly to the spine of a redex. The bottommost address in the stack " +"points to the outermost App node of the expression, and the rest follow " +"suit." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:558 +msgid "" +"When compiling a super combinator, we need to maintain an environment " +"that allows us to find the relative position of parameters in the stack " +"during the compilation process by their names. Additionally, since " +"clearing the preceding N+1 addresses is necessary after completing the " +"instantiation of a super combinator, the number of parameters N needs to " +"be passed as well." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:560 +msgid "" +"Here, \"parameters\" refer to addresses pointing to App nodes on the " +"heap, and the actual parameter addresses can be accessed through the " +"pusharg instruction." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:562 +msgid "" +"fn compileSC(self : ScDef[String]) -> (String, Int, List[Instruction]) {\n" +" let name = self.name\n" +" let body = self.body\n" +" let mut arity = 0\n" +" fn gen_env(i : Int, args : List[String]) -> List[(String, Int)] {\n" +" match args {\n" +" Nil => {\n" +" arity = i\n" +" return Nil\n" +" }\n" +" Cons(s, ss) => Cons((s, i), gen_env(i + 1, ss))\n" +" }\n" +" }\n" +" let env = gen_env(0, self.args)\n" +" (name, arity, compileR(body, env, arity))\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:581 +msgid "" +"The `compileR` function generates code for instantiating super " +"combinators by calling the `compileC` function, and then appends three " +"instructions:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:583 +msgid "" +"`Update(N)`: Updates the original redex in the heap to an `NInd` node, " +"which then points to the newly instantiated super combinator." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:584 +msgid "`Pop(N)`: Clears the stack of redundant addresses." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:585 +msgid "`Unwind`: Searches for the next redex to start the next reduction." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:587 +msgid "" +"fn compileR(\n" +" self : RawExpr[String],\n" +" env : List[(String, Int)],\n" +" arity : Int\n" +") -> List[Instruction] {\n" +" if arity == 0 {\n" +" // The Pop 0 instruction does nothing in practice, \n" +" // so it is not generated when the arity is 0.\n" +" compileC(self, env).concat(@immut/list.of([Update(arity), Unwind]))\n" +" } else {\n" +" compileC(self, env).concat(\n" +" @immut/list.of([Update(arity), Pop(arity), Unwind]),\n" +" )\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:605 +msgid "" +"When compiling the definition of super combinators, a rather crude " +"approach is used: if a variable is not a parameter, it is treated as " +"another super combinator (writing it incorrectly will result in a runtime" +" error). For function application, the right-hand expression is compiled " +"first, then all offsets corresponding to parameters in the environment " +"are incremented (because an extra address pointing to the instantiated " +"right-hand expression is added to the top of the stack), then the left-" +"hand expression is compiled, and finally the `MkApp` instruction is " +"added." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:607 +msgid "" +"fn compileC(\n" +" self : RawExpr[String],\n" +" env : List[(String, Int)]\n" +") -> List[Instruction] {\n" +" match self {\n" +" Var(s) =>\n" +" match lookupENV(env, s) {\n" +" None => @immut/list.of([PushGlobal(s)])\n" +" Some(n) => @immut/list.of([PushArg(n)])\n" +" }\n" +" Num(n) => @immut/list.of([PushInt(n)])\n" +" App(e1, e2) =>\n" +" compileC(e2, env)\n" +" .concat(compileC(e1, argOffset(1, env)))\n" +" .concat(@immut/list.of([MkApp]))\n" +" _ => abort(\"not support yet\")\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:628 +msgid "Running the G-Machine" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:630 +msgid "" +"Once the super combinators are compiled, they need to be placed on the " +"heap (along with adding their addresses to the global table). This can be" +" done recursively." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:632 +msgid "" +"fn buildInitialHeap(scdefs : List[(String, Int, List[Instruction])]) -> " +"(GHeap, RHTable[String, Addr]) {\n" +" let heap = { objectCount : 0, memory : Array::make(10000, None) }\n" +" let globals = RHTable::new(50)\n" +" fn go(lst : List[(String, Int, List[Instruction])]) {\n" +" match lst {\n" +" Nil => ()\n" +" Cons((name, arity, instrs), rest) => {\n" +" let addr = heap.alloc(NGlobal(name, arity, instrs))\n" +" globals[name] = addr\n" +" go(rest)\n" +" }\n" +" }\n" +" }\n" +" go(scdefs)\n" +" return (heap, globals)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:651 +msgid "" +"Define a function \"step\" that updates the state of the G-Machine by one" +" step, returning false if the final state has been reached." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:653 +msgid "" +"fn step(self : GState) -> Bool {\n" +" match self.code {\n" +" Nil => return false\n" +" Cons(i, is) => {\n" +" self.code = is\n" +" self.statInc()\n" +" match i {\n" +" PushGlobal(f) => self.push_global(f)\n" +" PushInt(n) => self.push_int(n)\n" +" PushArg(n) => self.push_arg(n)\n" +" MkApp => self.mkapp()\n" +" Unwind => self.unwind()\n" +" Update(n) => self.update(n)\n" +" Pop(n) => self.stack = self.stack.drop(n)\n" +" } // without the need for additional functions\n" +" return true\n" +" }\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:675 +msgid "" +"Additionally, define a function \"reify\" that continuously executes the " +"\"step\" function until the final state is reached." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:677 +msgid "" +"fn reify(self : GState) -> Unit {\n" +" if self.step() {\n" +" self.reify()\n" +" } else {\n" +" let stack = self.stack\n" +" match stack {\n" +" Cons(addr, Nil) => {\n" +" let res = self.heap[addr]\n" +" println(\"\\{res}\")\n" +" }\n" +" _ => abort(\"wrong stack \\{stack}\")\n" +" }\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:694 +msgid "Combine the above components." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:696 +msgid "" +"fn run(codes : List[String]) -> Unit {\n" +" fn parse_then_compile(code : String) -> (String, Int, " +"List[Instruction]) {\n" +" let code = TokenStream::new(code)\n" +" let code = parseSC(code)\n" +" let code = compileSC(code)\n" +" return code\n" +" }\n" +"\n" +" let codes = " +"codes.map(parse_then_compile).concat(preludeDefs.map(compileSC))\n" +" let (heap, globals) = buildInitialHeap(codes)\n" +" let initialState : GState = {\n" +" heap,\n" +" stack: Nil,\n" +" code: initialCode,\n" +" globals,\n" +" stats: initialStat,\n" +" }\n" +" initialState.reify()\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:718 +#: ../../tutorial/example/gmachine/gmachine-2.md:330 +#: ../../tutorial/example/myers-diff/myers-diff2.md:186 +#: ../../tutorial/example/myers-diff/myers-diff3.md:214 +#: ../../tutorial/example/segment-tree/segment-tree.md:141 +#: ../../tutorial/example/segment-tree/segment-tree2.md:132 +#: ../../tutorial/example/sudoku/index.md:388 +msgid "Conclusion" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:720 +msgid "" +"The features of the G-Machine we've constructed so far are too limited to" +" run even a somewhat decent program. In the next article, we will " +"incrementally add features such as primitives and custom data structures." +" Towards the end, we'll introduce lazy evaluation techniques after " +"covering the G-Machine." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:722 +msgid "Reference" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-1.md:724 +msgid "" +"Peyton Jones, Simon & Lester, David. (2000). Implementing functional " +"languages: a tutorial." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:1 +msgid "G-Machine 2" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:3 +msgid "" +"This article is the second in the series on implementing lazy evaluation " +"in MoonBit. In the first part, we explored the purposes of lazy " +"evaluation and a typical abstract machine for lazy evaluation, the " +"G-Machine, and implemented some basic G-Machine instructions. In this " +"article, we will further extend the G-Machine implementation from the " +"previous article to support `let` expressions and basic arithmetic, " +"comparison, and other operations." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:5 +#, fuzzy +msgid "let Expressions" +msgstr "语言扩展" + +#: ../../tutorial/example/gmachine/gmachine-2.md:7 +msgid "" +"The `let` expression in coref differs slightly from that in MoonBit. A " +"`let` expression can create multiple variables but can only be used " +"within a limited scope. Here is an example:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:9 +msgid "" +"{\n" +" let x = n + m\n" +" let y = x + 42\n" +" x * y\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:17 +msgid "Equivalent coref expression:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:19 +msgid "" +"(let ([x (add n m)]\n" +" [y (add x 42)])\n" +" (mul x y)) ;; xy can only be used within let\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:25 +msgid "" +"It is important to note that coref's `let` expressions must follow a " +"sequential order. For example, the following is not valid:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:27 +msgid "" +"(let ([y (add x 42)]\n" +" [x (add n m)])\n" +" (mul x y))\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:33 +msgid "" +"In contrast, `letrec` is more complex as it allows the local variables " +"defined to reference each other without considering the order of their " +"definitions." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:35 +msgid "" +"Before implementing `let` (and the more complex `letrec`), we first need " +"to modify the current parameter passing method. The local variables " +"created by `let` should intuitively be accessed in the same way as " +"parameters, but the local variables defined by `let` do not correspond to" +" `NApp` nodes. Therefore, we need to adjust the stack parameters before " +"calling the supercombinator." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:37 +msgid "" +"The adjustment is done in the implementation of the `Unwind` instruction." +" If the supercombinator has no parameters, it is the same as the original" +" unwind. When there are parameters, the top address of the " +"supercombinator node is discarded, and the `rearrange` function is " +"called." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:39 +msgid "" +"fn rearrange(self : GState, n : Int) -> Unit {\n" +" let appnodes = take(self.stack, n)\n" +" let args = map(fn (addr) {\n" +" let NApp(_, arg) = self.heap[addr]\n" +" arg\n" +" }, appnodes)\n" +" self.stack = append(args, drop(appnodes, n - 1))\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:50 +msgid "" +"The `rearrange` function assumes that the first N addresses on the stack " +"point to a series of `NApp` nodes. It keeps the bottommost one (used as " +"Redex update), cleans up the top N-1 addresses, and then places N " +"addresses that directly point to the parameters." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:52 +msgid "" +"After this, both parameters and local variables can be accessed using the" +" same command by changing the `PushArg` instruction to a more general " +"`Push` instruction." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:54 +msgid "" +"fn push(self : GState, offset : Int) -> Unit {\n" +" // Copy the address at offset + 1 to the top of the stack\n" +" // Push(n) a0 : . . . : an : s\n" +" // => an : a0 : . . . : an : s\n" +" let appaddr = nth(self.stack, offset)\n" +" self.putStack(appaddr)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:64 +msgid "" +"The next issue is that we need something to clean up. Consider the " +"following expression:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:66 +msgid "" +"(let ([x1 e1]\n" +" [x2 e2])\n" +" expr)\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:72 +msgid "" +"After constructing the graph corresponding to the expression `expr`, the " +"stack still contains addresses pointing to e1 and e2 (corresponding to " +"variables x1 and x2), as shown below (the stack grows from bottom to " +"top):" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:74 +msgid "" +"
\n" +" |\n" +"
\n" +" |\n" +"
\n" +" |\n" +"...remaining stack...\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:84 +msgid "" +"Therefore, we need a new instruction to clean up these no longer needed " +"addresses. It is called `Slide`. As the name suggests, the function of " +"`Slide(n)` is to skip the first address and delete the following N " +"addresses." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:86 +msgid "" +"fn slide(self : GState, n : Int) -> Unit {\n" +" let addr = self.pop1()\n" +" self.stack = Cons(addr, drop(self.stack, n))\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:93 +msgid "" +"Now we can compile `let`. We will compile the expressions corresponding " +"to local variables using the `compileC` function. Then, traverse the list" +" of variable definitions (`defs`), compile and update the corresponding " +"offsets in order. Finally, use the passed `comp` function to compile the " +"main expression and add the `Slide` instruction to clean up the unused " +"addresses." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:95 +msgid "" +"Compiling the main expression using the passed function makes it easy to " +"reuse when adding subsequent features." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:97 +msgid "" +"fn compileLet(comp : (RawExpr[String], List[(String, Int)]) -> " +"List[Instruction], defs : List[(String, RawExpr[String])], expr : " +"RawExpr[String], env : List[(String, Int)]) -> List[Instruction] {\n" +" let (env, codes) = loop env, List::Nil, defs {\n" +" env, acc, Nil => (env, acc)\n" +" env, acc, Cons((name, expr), rest) => {\n" +" let code = compileC(expr, env)\n" +" // Update offsets and add offsets for local variables corresponding" +" to name\n" +" let env = List::Cons((name, 0), argOffset(1, env))\n" +" continue env, append(acc, code), rest\n" +" }\n" +" }\n" +" append(codes, append(comp(expr, env), List::[Slide(length(defs))]))\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:112 +msgid "" +"The semantics of `letrec` are more complex - it allows the N variables " +"within the expression to reference each other, so we need to pre-allocate" +" N addresses and place them on the stack. We need a new instruction: " +"`Alloc(N)`, which pre-allocates N `NInd` nodes and pushes the addresses " +"onto the stack sequentially. The addresses in these indirect nodes are " +"negative and only serve as placeholders." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:114 +msgid "" +"fn allocNodes(self : GState, n : Int) -> Unit {\n" +" let dummynode : Node = NInd(Addr(-1))\n" +" for i = 0; i < n; i = i + 1 {\n" +" let addr = self.heap.alloc(dummynode)\n" +" self.putStack(addr)\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:124 +msgid "The steps to compile letrec are similar to `let`:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:126 +msgid "Use `Alloc(n)` to allocate N addresses." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:127 +msgid "Use the `loop` expression to build a complete environment." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:128 +msgid "" +"Compile the local variables in `defs`, using the `Update` instruction to " +"update the results to the pre-allocated addresses after compiling each " +"one." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:129 +msgid "Compile the main expression and use the `Slide` instruction to clean up." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:131 +msgid "" +"fn compileLetrec(comp : (RawExpr[String], List[(String, Int)]) -> " +"List[Instruction], defs : List[(String, RawExpr[String])], expr : " +"RawExpr[String], env : List[(String, Int)]) -> List[Instruction] {\n" +" let env = loop env, defs {\n" +" env, Nil => env\n" +" env, Cons((name, _), rest) => {\n" +" let env = List::Cons((name, 0), argOffset(1, env))\n" +" continue env, rest\n" +" }\n" +" }\n" +" let n = length(defs)\n" +" fn compileDefs(defs : List[(String, RawExpr[String])], offset : Int) ->" +" List[Instruction] {\n" +" match defs {\n" +" Nil => append(comp(expr, env), List::[Slide(n)])\n" +" Cons((_, expr), rest) => append(compileC(expr, env), " +"Cons(Update(offset), compileDefs(rest, offset - 1)))\n" +" }\n" +" }\n" +" Cons(Alloc(n), compileDefs(defs, n - 1))\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:151 +msgid "Adding Primitives" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:153 +msgid "" +"From this step, we can finally perform basic integer operations such as " +"arithmetic, comparison, and checking if two numbers are equal. First, " +"modify the `Instruction` type to add related instructions." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:155 +msgid "" +" Add\n" +" Sub\n" +" Mul\n" +" Div\n" +" Neg\n" +" Eq // ==\n" +" Ne // !=\n" +" Lt // <\n" +" Le // <=\n" +" Gt // >\n" +" Ge // >=\n" +" Cond(List[Instruction], List[Instruction])\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:170 +msgid "" +"At first glance, implementing these instructions seems simple. Take `Add`" +" as an example: just pop two top addresses from the stack, retrieve the " +"corresponding numbers from memory, perform the operation, and push the " +"result address back onto the stack." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:172 +msgid "" +"fn add(self : GState) -> Unit {\n" +" let (a1, a2) = self.pop2() // Pop two top addresses\n" +" match (self.heap[a1], self.heap[a2]) {\n" +" (NNum(n1), NNum(n2)) => {\n" +" let newnode = Node::NNum(n1 + n2)\n" +" let addr = self.heap.alloc(newnode)\n" +" self.putStack(addr)\n" +" }\n" +" ......\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:186 +msgid "" +"However, the next problem we face is that this is a lazy evaluation " +"language. The parameters of `add` are likely not yet computed (i.e., not " +"`NNum` nodes). We also need an instruction that can force a computation " +"to give a result or never stop computing. We call it `Eval` (short for " +"Evaluation)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:188 +msgid "" +"In jargon, the result of such a computation is called Weak Head Normal " +"Form (WHNF)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:190 +msgid "" +"At the same time, we need to modify the structure of `GState` and add a " +"state called `dump`. Its type is `List[(List[Instruction], List[Addr])]`," +" used by `Eval` and `Unwind` instructions." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:192 +msgid "The implementation of the `Eval` instruction is not complicated:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:194 +msgid "Pop the top address of the stack." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:196 +msgid "" +"Save the current unexecuted instruction sequence and stack (by putting " +"them into the dump)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:198 +msgid "Clear the current stack and place the previously saved address." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:200 +msgid "Clear the current instruction sequence and place the `Unwind` instruction." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:202 +msgid "" +"This is similar to how strict evaluation languages handle saving caller " +"contexts, but practical implementations would use more efficient methods." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:204 +msgid "" +"fn eval(self : GState) -> Unit {\n" +" let addr = self.pop1()\n" +" self.putDump(self.code, self.stack)\n" +" self.stack = List::[addr]\n" +" self.code = List::[Unwind]\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:213 +msgid "" +"This simple definition requires modifying the `Unwind` instruction to " +"restore the context when `Unwind` in the `NNum` branch finds that there " +"is a recoverable context (`dump` is not empty)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:215 +msgid "" +"fn unwind(self : GState) -> Unit {\n" +" let addr = self.pop1()\n" +" match self.heap[addr] {\n" +" NNum(_) => {\n" +" match self.dump {\n" +" Nil => self.putStack(addr)\n" +" Cons((instrs, stack), restDump) => {\n" +" // Restore the stack\n" +" self.stack = stack\n" +" self.putStack(addr)\n" +" self.dump = restDump\n" +" // Return to original code execution\n" +" self.code = instrs\n" +" }\n" +" }\n" +" }\n" +" ......\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:237 +msgid "" +"Next, we need to implement arithmetic and comparison instructions. We use" +" two functions to simplify the form of binary operations. The result of " +"the comparison instruction is a boolean value, and for simplicity, we use" +" numbers to represent it: 0 for `false`, 1 for `true`." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:239 +msgid "" +"fn liftArith2(self : GState, op : (Int, Int) -> Int) -> Unit {\n" +" // Binary arithmetic operations\n" +" let (a1, a2) = self.pop2()\n" +" match (self.heap[a1], self.heap[a2]) {\n" +" (NNum(n1), NNum(n2)) => {\n" +" let newnode = Node::NNum(op(n1, n2))\n" +" let addr = self.heap.alloc(newnode)\n" +" self.putStack(addr)\n" +" }\n" +" (node1, node2) => abort(\"liftArith2: \\{a1} = \\{node1} \\{a2} = " +"\\{node2}\")\n" +" }\n" +"}\n" +"\n" +"fn liftCmp2(self : GState, op : (Int, Int) -> Bool) -> Unit {\n" +" // Binary comparison operations\n" +" let (a1, a2) = self.pop2()\n" +" match (self.heap[a1], self.heap[a2]) {\n" +" (NNum(n1), NNum(n2)) => {\n" +" let flag = op(n1, n2)\n" +" let newnode = if flag { Node::NNum(1) } else { Node::NNum(0) }\n" +" let addr = self.heap.alloc(newnode)\n" +" self.putStack(addr)\n" +" }\n" +" (node1, node2) => abort(\"liftCmp2: \\{a1} = \\{node1} \\{a2} = " +"\\{node2}\")\n" +" }\n" +"}\n" +"\n" +"// Implement negation separately\n" +"fn negate(self : GState) -> Unit {\n" +" let addr = self.pop1()\n" +" match self.heap[addr] {\n" +" NNum(n) => {\n" +" let addr = self.heap.alloc(NNum(-n))\n" +" self.putStack(addr)\n" +" }\n" +" otherwise => {\n" +" // If not NNum, throw an error\n" +" abort(\"negate: wrong kind of node \\{otherwise}, address \\{addr} " +"\")\n" +" }\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:283 +msgid "Finally, implement branching:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:285 +msgid "" +"fn condition(self : GState, i1 : List[Instruction], i2 : " +"List[Instruction]) -> Unit {\n" +" let addr = self.pop1()\n" +" match self.heap[addr] {\n" +" NNum(0) => {\n" +" // If false, jump to i2\n" +" self.code = append(i2, self.code)\n" +" }\n" +" NNum(1) => {\n" +" // If true, jump to i1\n" +" self.code = append(i1, self.code)\n" +" }\n" +" otherwise => abort(\"cond : \\{addr} = \\{otherwise}\")\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:302 +msgid "" +"No major adjustments are needed in the compilation part, just add some " +"predefined programs:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:304 +#: ../../tutorial/example/gmachine/gmachine-3.md:9 +msgid "" +"let compiledPrimitives : List[(String, Int, List[Instruction])] = List::[" +"\n" +" // Arithmetic\n" +" (\"add\", 2, List::[Push(1), Eval, Push(1), Eval, Add, Update(2), " +"Pop(2), Unwind]),\n" +" (\"sub\", 2, List::[Push(1), Eval, Push(1), Eval, Sub, Update(2), " +"Pop(2), Unwind]),\n" +" (\"mul\", 2, List::[Push(1), Eval, Push(1), Eval, Mul, Update(2), " +"Pop(2), Unwind]),\n" +" (\"div\", 2, List::[Push(1), Eval, Push(1), Eval, Div, Update(2), " +"Pop(2), Unwind]),\n" +" // Comparison\n" +" (\"eq\", 2, List::[Push(1), Eval, Push(1), Eval, Eq, Update(2), " +"Pop(2), Unwind]),\n" +" (\"neq\", 2, List::[Push(1), Eval, Push(1), Eval, Ne, Update(2), " +"Pop(2), Unwind]),\n" +" (\"ge\", 2, List::[Push(1), Eval, Push(1), Eval, Ge, Update(2), " +"Pop(2), Unwind]),\n" +" (\"gt\", 2, List::[Push(1), Eval, Push(1), Eval, Gt, Update(2), " +"Pop(2), Unwind]),\n" +" (\"le\", 2, List::[Push(1), Eval, Push(1), Eval, Le, Update(2), " +"Pop(2), Unwind]),\n" +" (\"lt\", 2, List::[Push(1), Eval, Push(1), Eval, Lt, Update(2), " +"Pop(2), Unwind]),\n" +" // Miscellaneous\n" +" (\"negate\", 1, List::[Push(0), Eval, Neg, Update(1), Pop(1), Unwind])," +"\n" +" (\"if\", 3, List::[Push(0), Eval, Cond(List::[Push(1)], " +"List::[Push(2)]), Update(3), Pop(3), Unwind])\n" +"]\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:324 +msgid "and modify the initial instruction sequence" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:326 +msgid "let initialCode : List[Instruction] = List::[PushGlobal(\"main\"), Eval]\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-2.md:332 +msgid "" +"In the next part, we will improve the code generation for primitives and " +"add support for data structures." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:1 +msgid "G-Machine 3" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:3 +msgid "" +"This article is the third in a series on implementing Haskell's lazy " +"evaluation in MoonBit. In the previous article, we learned how to compile" +" `let` expressions and how to implement basic arithmetic and comparison " +"operations. In this article, we will implement a context-based " +"optimization method and add support for data structures." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:5 +msgid "Tracking Context" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:7 +msgid "" +"Let's review how we implemented primitives in the [last " +"tutorial](https://www.moonbitlang.com/docs/examples/gmachine-2)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:29 +msgid "" +"This implementation introduces many `Eval` instructions, but they are not" +" always necessary. For example:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:31 +msgid "(add 3 (mul 4 5))\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:35 +msgid "" +"The two arguments of `add` are already in WHNF (Weak Head Normal Form) " +"before executing `Eval`. Therefore, the `Eval` instructions here are " +"redundant." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:37 +msgid "" +"One feasible optimization method is to consider the context when " +"compiling expressions. For example, `add` requires its arguments to be " +"evaluated to WHNF, so its arguments are in a strict context during " +"compilation. By doing this, we can identify some expressions that can be " +"safely compiled with strict evaluation (only a subset)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:39 +msgid "An expression in a supercombinator definition is in a strict context." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:41 +msgid "" +"If `(op e1 e2)` is in a strict context (where `op` is a primitive), then " +"`e1` and `e2` are also in a strict context." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:43 +msgid "" +"If `(let (.....) e)` is in a strict context, then `e` is also in a strict" +" context (but the expressions corresponding to the local variables are " +"not, as `e` may not need their results)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:45 +msgid "" +"We use the `compileE` function to implement compilation in a strict " +"context, ensuring that _the value at the top of the stack is always in " +"WHNF_." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:47 +msgid "" +"For the default branch, we simply add an `Eval` instruction after the " +"result of `compileC`." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:49 +msgid "append(compileC(self, env), List::[Eval])\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:53 +msgid "Constants are pushed directly." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:55 +msgid "Num(n) => List::[PushInt(n)]\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:59 +msgid "" +"For `let/letrec` expressions, the specially designed `compileLet` and " +"`compileLetrec` become useful. Compiling a `let/letrec` expression in a " +"strict context only requires using `compileE` to compile its main " +"expression." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:61 +msgid "" +"Let(rec, defs, e) => {\n" +" if rec {\n" +" compileLetrec(compileE, defs, e, env)\n" +" } else {\n" +" compileLet(compileE, defs, e, env)\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:71 +msgid "" +"The `if` and `negate` functions, with 3 and 1 arguments respectively, " +"require special handling." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:73 +msgid "" +"App(App(App(Var(\"if\"), b), e1), e2) => {\n" +" let condition = compileE(b, env)\n" +" let branch1 = compileE(e1, env)\n" +" let branch2 = compileE(e2, env)\n" +" append(condition, List::[Cond(branch1, branch2)])\n" +"}\n" +"App(Var(\"negate\"), e) => {\n" +" append(compileE(e, env), List::[Neg])\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:85 +msgid "" +"Basic binary operations can be handled uniformly through a lookup table. " +"First, construct a hash table called `builtinOpS` to query the " +"corresponding instructions by the name of the primitive." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:87 +msgid "" +"let builtinOpS : RHTable[String, Instruction] = {\n" +" let table : RHTable[String, Instruction] = RHTable::new(50)\n" +" table[\"add\"] = Add\n" +" table[\"mul\"] = Mul\n" +" table[\"sub\"] = Sub\n" +" table[\"div\"] = Div\n" +" table[\"eq\"] = Eq\n" +" table[\"neq\"] = Ne\n" +" table[\"ge\"] = Ge\n" +" table[\"gt\"] = Gt\n" +" table[\"le\"] = Le\n" +" table[\"lt\"] = Lt\n" +" table\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:104 +msgid "The rest of the handling is not much different." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:106 +msgid "" +"App(App(Var(op), e0), e1) => {\n" +" match builtinOpS[op] {\n" +" None => append(compileC(self, env), List::[Eval]) // Not a primitive " +"op, use the default branch\n" +" Some(instr) => {\n" +" let code1 = compileE(e1, env)\n" +" let code0 = compileE(e0, argOffset(1, env))\n" +" append(code1, append(code0, List::[instr]))\n" +" }\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:119 +msgid "" +"Are we done? It seems so, but there's another WHNF besides integers: " +"partially applied functions." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:121 +msgid "" +"A partial application is when the number of arguments is insufficient. " +"This situation is common in higher-order functions, for example:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:123 +msgid "(map (add 1) listofnumbers)\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:127 +msgid "Here, `(add 1)` is a partial application." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:129 +msgid "" +"To ensure that the code generated by the new compilation strategy works " +"correctly, we need to modify the implementation of the `Unwind` " +"instruction for the `NGlobal` branch. When the number of arguments is " +"insufficient and the dump has saved stacks, we should only retain the " +"original redex and restore the stack." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:131 +msgid "" +"NGlobal(_, n, c) => {\n" +" let k = length(self.stack)\n" +" if k < n {\n" +" match self.dump {\n" +" Nil => abort(\"Unwinding with too few arguments\")\n" +" Cons((i, s), rest) => {\n" +" // a1 : ...... : ak\n" +" // ||\n" +" // ak : s\n" +" // Retain the redex and restore the stack\n" +" self.stack = append(drop(self.stack, k - 1), s)\n" +" self.dump = rest\n" +" self.code = i\n" +" }\n" +" }\n" +" } else {\n" +" ......\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:153 +msgid "" +"This context-based strictness analysis technique is useful but cannot do " +"anything with supercombinator calls. Here we briefly introduce a " +"strictness analysis technique based on boolean operations, which can " +"analyze which arguments of a supercombinator call should be compiled " +"using strict mode." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:155 +msgid "" +"We first define a concept: bottom, which conceptually represents a value " +"that never terminates or causes an exception. For a supercombinator `f " +"a[1] ...... a[n]`, if one argument `a[i]` satisfies `a[i] = bottom`, then" +" `f a[1] .... a[i] .... a[n] = bottom` (other arguments are not bottom). " +"This indicates that no matter how complex the internal control flow of " +"`f` is, it **must** need the result of argument `a[i]` to get the final " +"result. Therefore, a`[i]` should be strictly evaluated." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:157 +msgid "" +"If this condition is not met, it does not necessarily mean that the " +"argument is not needed at all; it may be used only in certain branches " +"and its use is determined at runtime. Such an argument is a typical " +"example of one that should be lazily evaluated." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:159 +msgid "" +"Let's consider bottom as `false` and non-bottom values as `true`. In this" +" way, all functions in coref can be considered boolean functions. Take " +"`abs` as an example:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:161 +msgid "" +"(defn abs[n]\n" +" (if (lt n 0) (negate n) n))\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:166 +msgid "We analyze how to translate it into a boolean function from top to bottom:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:168 +msgid "" +"For an expression like `(if x y z)`, x must be evaluated, but only one of" +" `y` or `z` needs to be evaluated. This can be translated into `x and (y " +"or z)`. Taking the example of the function above, if `n` is bottom, then " +"the condition `(lt n 0)` is also bottom, and thus the result of the " +"entire expression is also bottom." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:170 +msgid "For primitive expressions, using `and` for all parts is sufficient." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:172 +msgid "" +"To determine whether a parameter needs to be compiled strictly, you can " +"convert the above condition into a Boolean function: `a[i] = false` " +"implies `f a[1] .... a[i] .... a[n] = false` (with all other parameters " +"being true)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:174 +msgid "" +"This is essentially a method of program analysis called \"abstract " +"interpretation.\"" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:176 +msgid "Custom Data Structures" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:178 +msgid "" +"The data structure type definition in Haskell is similar to the `enum` in" +" MoonBit. However, since CoreF is a simple toy language used to " +"demonstrate lazy evaluation, it does not allow custom data types. The " +"only built-in data structure is the lazy list." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:180 +msgid "" +"(defn take[n l]\n" +" (case l\n" +" [(Nil) Nil]\n" +" [(Cons x xs)\n" +" (if (le n 0)\n" +" Nil\n" +" (Cons x (take (sub n 1) xs)))]))\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:190 +msgid "" +"As shown above, you can use the `case` expression for simple pattern " +"matching on lists." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:192 +msgid "" +"The corresponding graph node for a list is `NConstr(Int, List[Addr])`, " +"which consists of two parts:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:194 +msgid "" +"A tag for different value constructors: the tag for `Nil` is 0, and the " +"tag for `Cons` is 1." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:195 +msgid "" +"A list of addresses for storing substructures, whose length corresponds " +"to the number of parameters (arity) of a value constructor." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:197 +msgid "" +"This graph node structure can be used to implement various data " +"structures, but CoreF does not have a type system. For demonstration " +"purposes, only lazy lists are implemented." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:199 +msgid "" +"We need to add two instructions, `Split` and `Pack`, to deconstruct and " +"construct lists." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:201 +msgid "" +"fn split(self : GState, n : Int) -> Unit {\n" +" let addr = self.pop1()\n" +" match self.heap[addr] {\n" +" NConstr(_, addrs) => {\n" +" // n == addrs.length()\n" +" self.stack = addrs + self.stack\n" +" }\n" +" }\n" +"}\n" +"\n" +"fn pack(self : GState, t : Int, n : Int) -> Unit {\n" +" let addrs = self.stack.take(n)\n" +" // Assume the number of arguments is sufficient\n" +" self.stack = self.stack.drop(n)\n" +" let addr = self.heap.alloc(NConstr(t, addrs))\n" +" self.putStack(addr)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:221 +msgid "" +"Additionally, a `CaseJump` instruction is needed to implement the `case` " +"expression." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:223 +msgid "" +"fn casejump(self : GState, table : List[(Int, List[Instruction])]) -> " +"Unit {\n" +" let addr = self.pop1()\n" +" match self.heap[addr] {\n" +" NConstr(t, addrs) => {\n" +" match lookupENV(table, t) {\n" +" None => abort(\"casejump\")\n" +" Some(instrs) => {\n" +" self.code = instrs + self.code\n" +" self.putStack(addr)\n" +" }\n" +" }\n" +" }\n" +" otherwise => abort(\"casejump(): addr = \\{addr} node = " +"\\{otherwise}\")\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:241 +msgid "" +"After adding the above instructions, we need to modify the `compileC` and" +" `compileE` functions. Since the object matched by the `case` expression " +"needs to be evaluated to WHNF, only the `compileE` function can compile " +"it." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:243 +msgid "" +"// compileE\n" +" Case(e, alts) => {\n" +" compileE(e, env) + List::[CaseJump(compileAlts(alts, env))]\n" +" }\n" +" Constructor(0, 0) => {\n" +" // Nil\n" +" List::[Pack(0, 0)]\n" +" }\n" +" App(App(Constructor(1, 2), x), xs) => {\n" +" // Cons(x, xs)\n" +" compileC(xs, env) + compileC(x, argOffset(1, env)) + List::[Pack(1, " +"2)]\n" +" }\n" +"\n" +"// compileC\n" +" App(App(Constructor(1, 2), x), xs) => {\n" +" // Cons(x, xs)\n" +" compileC(xs, env) + compileC(x, argOffset(1, env)) + List::[Pack(1, " +"2)]\n" +" }\n" +" Constructor(0, 0) => {\n" +" // Nil\n" +" List::[Pack(0, 0)]\n" +" }\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:268 +msgid "" +"At this point, a new problem arises. Previously, printing the evaluation " +"result only needed to handle simple `NNum` nodes, but `NConstr` nodes " +"have substructures. When the list itself is evaluated to WHNF, its " +"substructures are mostly unevaluated `NApp` nodes. We need to add a " +"`Print` instruction, which will recursively evaluate and write the result" +" into the `output` component of `GState`." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:270 +msgid "" +"struct GState {\n" +" output : Buffer\n" +" ......\n" +"}\n" +"\n" +"fn gprint(self : GState) -> Unit {\n" +" let addr = self.pop1()\n" +" match self.heap[addr] {\n" +" NNum(n) => {\n" +" self.output.write_string(n.to_string())\n" +" self.output.write_char(' ')\n" +" }\n" +" NConstr(0, Nil) => self.output.write_string(\"Nil\")\n" +" NConstr(1, Cons(addr1, Cons(addr2, Nil))) => {\n" +" // Force evaluation of addr1 and addr2 is required, so we execute " +"Eval instructions first\n" +" self.code = List::[Instruction::Eval, Print, Eval, Print] + " +"self.code\n" +" self.putStack(addr2)\n" +" self.putStack(addr1)\n" +" }\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:294 +msgid "Finally, change the initial code of the G-Machine to:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:296 +msgid "" +"let initialCode : List[Instruction] = List::[PushGlobal(\"main\"), Eval, " +"Print]\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:300 +msgid "" +"Now, we can write some classic functional programs using lazy lists, such" +" as the infinite Fibonacci sequence:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:302 +msgid "(defn fibs[] (Cons 0 (Cons 1 (zipWith add fibs (tail fibs)))))\n" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:306 +msgid "" +"After introducing data structures, strictness analysis becomes more " +"complex. For lazy lists, there are various evaluation modes:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:308 +msgid "" +"Fully strict (requires the list to be finite and all elements to be non-" +"bottom)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:309 +msgid "Fully lazy." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:310 +msgid "Head strict (the list can be infinite, but its elements cannot be bottom)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:311 +msgid "Tail strict (the list must be finite, but its elements can be bottom)." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:313 +msgid "" +"Moreover, the context in which a function is used can change the " +"evaluation mode of its parameters (it cannot be analyzed in isolation and" +" requires cross-function analysis). Such complex strictness analysis " +"usually employs projection analysis techniques. Relevant literature " +"includes:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:315 +msgid "Projections for Strictness Analysis" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:317 +msgid "Static Analysis and Code Optimizations in Glasgow Haskell Compiler" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:319 +msgid "Implementing Projection-based Strictness Analysis" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:321 +msgid "Theory and Practice of Demand Analysis in Haskell" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:323 +#: ../../tutorial/example/myers-diff/myers-diff.md:382 +msgid "Epilogue" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:325 +msgid "" +"Lazy evaluation can reduce runtime redundant calculations, but it also " +"introduces new problems, such as:" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:327 +msgid "The notorious side effect order issue." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:329 +msgid "" +"Excessive redundant nodes. Some computations that are not shared still " +"store their results on the heap, which is detrimental to utilizing the " +"CPU's caching mechanism." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:331 +msgid "" +"The representative of lazy evaluation languages, Haskell, offers a " +"controversial solution to the side effect order problem: Monads. This " +"solution has some value for eagerly evaluated languages as well, but many" +" online tutorials emphasize its mathematical background too much and fail" +" to explain how to use it effectively." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:333 +msgid "" +"Idris2, Haskell's successor (which is no longer a lazy language), retains" +" Monads and introduces another mechanism for handling side effects: " +"Algebraic Effects." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:335 +msgid "" +"The Spineless G-Machine designed by SPJ improved the problem of excessive" +" redundant nodes, and its successor, the STG, unified the data layout of " +"different types of nodes." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:337 +msgid "" +"In addition to improvements in abstract machine models, GHC's " +"optimization of Haskell programs heavily relies on inline-based " +"optimizations and projection analysis-based strictness analysis " +"techniques." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:339 +msgid "" +"In 2004, several GHC designers discovered that the previous push-enter " +"model, where parameters are pushed onto the stack and then a function is " +"called, was less effective than the eval-apply model, where the " +"responsibility is handed to the caller. They published a paper titled " +"\"Making a Fast Curry: Push/Enter vs. Eval/Apply for Higher-order " +"Languages.\"" +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:341 +msgid "" +"In 2007, Simon Marlow found that jump and execute code in the tagless " +"design significantly affected the performance of modern CPU branch " +"predictors. The paper \"_Faster laziness using dynamic pointer tagging_\"" +" described several solutions." +msgstr "" + +#: ../../tutorial/example/gmachine/gmachine-3.md:343 +msgid "" +"Lazy purely functional languages have shown many interesting " +"possibilities, but they have also faced much criticism and reflection. " +"Nevertheless, it is undoubtedly an intriguing technology!" +msgstr "" + +#: ../../tutorial/example/gmachine/index.md:3 ../../tutorial/example/index.md:5 +#: ../../tutorial/example/myers-diff/index.md:3 +#: ../../tutorial/example/segment-tree/index.md:13 ../../tutorial/index.md:8 +msgid "Contents:" +msgstr "目录:" + +#: ../../tutorial/example/index.md:1 +msgid "Examples" +msgstr "" + +#: ../../tutorial/example/index.md:3 +msgid "Here are some examples built with MoonBit." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:1 +msgid "Lambda calculus" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:3 +msgid "" +"Functional programming rises with the fall of Moore's Law. The full " +"utilization of milti-core processors has become an increasingly important" +" optimization method, while functional programming also becomes more " +"popularized with its affinity for parallel computation. The reasons " +"behind this trend can be traced back to one of its theoretical " +"ancestors—Lambda calculus." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:5 +msgid "" +"Lambda calculus originated from the 1930s. Created by Turing's mentor " +"Alonzo Church, formal systems have now evolved a vast and flourishing " +"family tree. This article will illustrate one of its most fundamental " +"forms: untyped Lambda calculus (which was also one of the earliest forms " +"proposed by Alonzo Church)." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:7 +msgid "Basic rules of untyped Lambda calculus" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:9 +msgid "" +"The only actions allowed in untyped Lambda calculus are defining Lambdas " +"(often referred to as Abstraction) and calling Lambdas (often referred to" +" as Application). These actions constitute the basic expressions in " +"Lambda calculus." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:11 +msgid "" +"Most programmers are no strange to the name \"Lambda expression\" as most" +" mainstream programming languages are hugely influenced by functional " +"language paradigm. Lambdas in untyped Lambda calculus are simpler than " +"those in mainstream programming languages. A Lambda typically looks like " +"this: `λx.x x`, where `x` is its parameter (each Lambda can only have one" +" parameter), `.` is the separator between the parameter and the " +"expression defining it, and `x x` is its definition." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:13 +msgid "" +"Some materials may omit spaces, so the above example can be rewritten as " +"`λx.xx`." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:15 +msgid "" +"If we replace `x x` with `x(x)`, it might be more in line with the " +"function calls we see in general languages. However, in the more common " +"notation of Lambda calculus, calling a Lambda only requires a space " +"between it and its parameter. Here, we call the parameter given by `x`, " +"which is `x` itself." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:17 +msgid "" +"The combination of the above two expressions and the variables introduced" +" when defining Lambdas are collectively referred to as the Lambda term. " +"In MoonBit, we use an enum type to represent it:" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:19 +msgid "" +"enum Term {\n" +" Var(String) // Variable\n" +" Abs(String, Term) // Define lambda, variables represented by strings\n" +" App(Term, Term) // Call lambda\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:27 +msgid "" +"Concepts encountered in daily programming such as boolean values, if " +"expressions, natural number arithmetic, and even recursion can all be " +"implemented using Lambda expressions. However, this is not the focus of " +"this article. Interested readers can refer to:" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:29 +msgid "" +"Programming with Nothing:[https://tomstu.art/programming-with-" +"nothing](https://tomstu.art/programming-with-nothing)" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:31 +msgid "" +"To implement an interpreter for untyped Lambda calculus, the basic things" +" we need to understand are just two rules: Alpha conversion and Beta " +"reduction." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:33 +msgid "" +"**Alpha conversion** describes the fact that the structure of Lambda is " +"crucial, and the names of variables are not that important. `λx.x` and " +"`λfoo.foo` can be interchanged. For certain nested Lambdas with repeated " +"variable names, such as `λx.(λx.x) x`, when renaming, the inner variables" +" cannot be renamed. For example, the above example can be rewritten using" +" Alpha conversion as `λy.(λx.x) y`." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:35 +msgid "" +"**Beta reduction** focuses on handling Lambda calls. Let's take an " +"example:" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:37 +msgid "(λx.(λy.x)) (λs.(λz.z))\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:41 +msgid "" +"In untyped Lambda calculus, all that needs to be done after calling a " +"Lambda is to substitute the parameter. In the example above, we need to " +"replace the variable `x` with `(λs.(λz.z))`, resulting in:" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:43 +msgid "(λy.(λs.(λz.z)))\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:47 +msgid "Free Variables and Variable Capture" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:49 +msgid "" +"If a variable in a Lambda term is not defined in its context, we call it " +"a free variable. For example, in the Lambda term `(λx.(λy.fgv h))`, the " +"variables `fgv` and `h` do not have corresponding Lambda definitions." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:51 +msgid "" +"During Beta reduction, if the Lambda term used for variable substitution " +"contains free variables, it may lead to a behavior called \"variable " +"capture\":" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:53 +msgid "(λx.(λy.x)) (λz.y)\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:57 +msgid "After substitution:" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:59 +msgid "λy.λz.y\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:63 +msgid "" +"The free variable in `λz.y` is treated as a parameter of some Lambda, " +"which is obviously not what we want." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:65 +msgid "" +"A common solution to the variable capture problem when writing " +"interpreters is to traverse the expression before substitution to obtain " +"a set of free variables. When encountering an inner Lambda during " +"substitution, check if the variable name is in this set of free " +"variables:" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:67 +msgid "" +"// (λx.E) T => E.subst(x, T)\n" +"fn subst(self : Term, var : String, term : Term) -> Term {\n" +" let freeVars : Set[String] = term.get_free_vars()\n" +" match self {\n" +" Abs(variable, body) => {\n" +" if freeVars.contains(variable) {\n" +" // The variable name is in the set of free variables \n" +" // of the current inner Lambda, indicating variable capture\n" +" abort(\"subst(): error while encountering \\{variable}\")\n" +" } else {\n" +" ...\n" +" }\n" +" }\n" +" ...\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:86 +msgid "" +"Next, I'll introduce a less popular but somewhat convenient method: de " +"Bruijn index." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:88 +msgid "De Bruijn Index" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:90 +msgid "" +"De Bruijn index is a technique for representing variables in Lambda terms" +" using integers. Specifically, it replaces specific variables with " +"Lambdas between the variable and its original imported position." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:92 +msgid "" +"λx.(λy.x (λz.z z))\n" +"\n" +"λ.(λ.1 (λ.0 0))\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:98 +msgid "" +"In the example above, there is one Lambda `λy` between the variable `x` " +"and its introduction position `λx`, so `x` is replaced with `1`. For " +"variable `z`, there are no other Lambdas between its introduction " +"position and its usage, so it is directly replaced with `0`. In a sense, " +"the value of the de Bruijn index describes the relative distance between " +"the variable and its corresponding Lambda. Here, the distance is measured" +" by the number of nested Lambdas." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:100 +msgid "" +"The same variable may be replaced with different integers in different " +"positions." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:102 +msgid "" +"We define a new type `TermDBI` to represent Lambda terms using de Bruijn " +"indices:" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:104 +msgid "" +"enum TermDBI {\n" +" Var(String, Int)\n" +" Abs(String, TermDBI)\n" +" App(TermDBI, TermDBI)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:112 +msgid "" +"However, directly writing and reading Lambda terms in de Bruijn index " +"form is painful, so we need to write a function `bruijn()` to convert " +"`Term` to `TermDBI`. This is also why there is still a `String` in the " +"definition of the `TermDBI` type, so that the original variable name can " +"be used for its `to_string` method, making it easy to print and view the " +"evaluation results with `println`." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:114 +msgid "" +"fn to_string(self : TermDBI) -> String {\n" +" match self {\n" +" Var(name, _) => name\n" +" Abs(name, body) => \"(\\\\\\{name}.\\{body})\"\n" +" App(t1, t2) => \"\\{t1} \\{t2}\"\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:124 +msgid "" +"To simplify implementation, if the input `Term` contains free variables, " +"the `bruijn()` function will report an error directly. MoonBit provides a" +" `Result[V, E]` type in the standard library, which has two constructors," +" `Ok(V)` and `Err(E)`, representing success and failure in computation, " +"respectively." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:126 +msgid "Readers familiar with Rust should find this familiar." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:128 +msgid "fn bruijn(self : Term) -> Result[TermDBI, String]\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:132 +msgid "" +"We take a clumsy approach to save variable names and their associated " +"nesting depth. First, we define the `Index` type:" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:134 +msgid "" +"struct Index {\n" +" name : String\n" +" depth : Int\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:141 +msgid "" +"Then we write a helper function to find the corresponding `depth` based " +"on a specific `name` from `List[Index]`:" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:143 +msgid "" +"// Find the first depth corresponding to varname in the environment\n" +"fn find(map : @immut/list.T[Index], varname : String) -> Result[Int, " +"String] {\n" +" match map {\n" +" Nil => Err(varname) // abort(\"variable '(varname)' not found\")\n" +" Cons(i, rest) => {\n" +" if i.name == varname {\n" +" Ok(i.depth)\n" +" } else {\n" +" find(rest, varname)\n" +" }\n" +" }\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:159 +msgid "Now we can complete the `bruijn()` function." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:161 +msgid "" +"Handling `Var` is the simplest, just look up the table to find the " +"corresponding `depth`." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:162 +msgid "" +"`Abs` is a bit more complicated. First, add one to the `depth` of all " +"`index` in the list (because the Lambda nesting depth has increased by " +"one), and then add `{ name : varname, depth : 0 }` to the beginning of " +"the list." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:163 +msgid "" +"`App` succeeds when both sub-items can be converted; otherwise, it " +"returns an `Err`." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:165 +msgid "" +"fn go(m : @immut/list.T[Index], t : Term) -> Result[TermDBI, String] {\n" +" match t {\n" +" Var(name) => {\n" +" let idx = find(m, name)\n" +" match idx {\n" +" Err(name) => Err(name)\n" +" Ok(idx) => Ok(Var(name, idx))\n" +" }\n" +" }\n" +" Abs(varname, body) => {\n" +" let m = m.map(fn (index){\n" +" { name : index.name, depth : index.depth + 1 }\n" +" }).cons({ name : varname, depth : 0 })\n" +" let res = go(m, body)\n" +" match res {\n" +" Err(name) => Err(name)\n" +" Ok(term) => Ok(Abs(varname, term))\n" +" }\n" +" }\n" +" App(e1, e2) => {\n" +" match (go(m, e1), go(m, e2)) {\n" +" (Ok(e1), Ok(e2)) => Ok(App(e1, e2))\n" +" (Err(name), _) => Err(name)\n" +" (_, Err(name)) => Err(name)\n" +" }\n" +" }\n" +" }\n" +"}\n" +"go(Nil, self)\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:197 +msgid "Reduce on TermDBI" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:199 +msgid "Reduction mainly deals with App, i.e., calls:" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:201 +msgid "" +"fn eval(self : TermDBI) -> TermDBI {\n" +" match self {\n" +" App(t1, t2) => {\n" +" match (eval(t1), eval(t2)) {\n" +" (Abs(_, t1), t2) => eval(subst(t1, t2))\n" +" (t1, t2) => App(t1, t2)\n" +" }\n" +" }\n" +" Abs(_) => self\n" +" otherwise => abort(\"eval(): \\{otherwise} \")\n" +" // eval should not encounter free variables\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:217 +msgid "" +"First, attempt reduction on both sub-items, then see if `eval(t1)` " +"results in a Lambda. If so, perform one step of variable substitution " +"(via the subst function) and then continue simplifying. For Lambdas " +"(`Abs`), simply return them as they are." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:219 +msgid "" +"The implementation of the subst function becomes much simpler when we " +"don't need to consider free variables. We just need to keep track of the " +"current depth recursively and compare it with the encountered variables. " +"If they match, it's the variable to be replaced." +msgstr "" + +#: ../../tutorial/example/lambda/index.md:221 +msgid "" +"fn subst(t1 : TermDBI, t2 : TermDBI) -> TermDBI {\n" +" fn go(t1 : TermDBI, t2 : TermDBI, depth : Int) -> TermDBI {\n" +" match t1 {\n" +" Var(name, d) => {\n" +" if d == depth {\n" +" t2\n" +" } else {\n" +" t1\n" +" }\n" +" }\n" +" Abs(name, t) => {\n" +" Abs(name, go(t, t2, depth + 1))\n" +" }\n" +" App(tl, tr) => {\n" +" App(go(tl, t2, depth), go(tr, t2, depth))\n" +" }\n" +" }\n" +" }\n" +" go(t1, t2, 0)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:244 +msgid "" +"The full code: " +"[try.moonbitlang.com/#b52b528b](https://try.moonbitlang.com/#b52b528b)" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:246 +msgid "Improvement" +msgstr "" + +#: ../../tutorial/example/lambda/index.md:248 +msgid "" +"When mapping variable names to indices, we used the " +"`@immut/list.T[Index]` type and updated the entire list every time we " +"added a new Lambda. However, this is actually quite a clumsy method. I " +"believe you can quickly realize that to store a `@immut/list.T[String]` " +"should simply suffice. If you're interested, you can try it yourself." +msgstr "" + +#: ../../tutorial/example/myers-diff/index.md:1 +msgid "Myers Diff" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:1 +msgid "Myers diff" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:3 +msgid "" +"Have you ever used the Unix tool `diff`? In short, it is a tool for " +"comparing the differences between two text files. What's more, Unix has a" +" tool called `patch`." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:5 +msgid "" +"Nowadays, few people manually apply patches to software packages, but " +"`diff` still retains its usefulness in another area: version control " +"systems. It's quite handy to have the function of being able to see what " +"changes have been made to source code files after a particular commit " +"(and highlighted with different colors). Take the most popular version " +"control system today, git, as an example:" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:7 +msgid "" +"diff --git a/main/main.mbt b/main/main.mbt\n" +"index 99f4c4c..52b1388 100644\n" +"--- a/main/main.mbt\n" +"+++ b/main/main.mbt\n" +"@@ -3,7 +3,7 @@\n" +"\n" +" fn main {\n" +" let a = lines(\"A\\nB\\nC\\nA\\nB\\nB\\nA\")\n" +"- let b = lines(\"C\\nB\\nA\\nB\\nA\\nC\")\n" +"+ let b = lines(\"C\\nB\\nA\\nB\\nA\\nA\")\n" +" let r = shortst_edit(a, b)\n" +" println(r)\n" +" }\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:23 +msgid "But how exactly do we calculate the differences between two text files?" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:25 +msgid "" +"git's default diff algorithm was proposed by _Eugene W. Myers_ in his " +"paper **An O(ND) Difference Algorithm and Its Variations**. This paper " +"mainly focuses on proving the correctness of the algorithm. In the " +"following text, we will understand the basic framework of this algorithm " +"in a less rigorous way and use MoonBit to write a simple implementation." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:27 +msgid "Defining \"Difference\" and Its Measurement Criteria" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:29 +msgid "" +"When we talk about the \"difference\" between two text files, what we are" +" actually referring to is a series of editing steps that can transform " +"text a into text b." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:31 +msgid "Assume the content of text a is" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:33 +msgid "" +"A\n" +"B\n" +"C\n" +"A\n" +"B\n" +"B\n" +"A\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:43 +msgid "Assume the content of text b is" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:45 +msgid "" +"C\n" +"B\n" +"A\n" +"B\n" +"A\n" +"C\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:54 +msgid "" +"To transform text a into text b, the simplest edit sequence is to delete " +"each character in a (indicated with a minus sign) and then insert each " +"character in b (indicated with a plus sign)." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:56 +msgid "" +"- A\n" +"- B\n" +"- C\n" +"- A\n" +"- B\n" +"- B\n" +"- A\n" +"+ C\n" +"+ B\n" +"+ A\n" +"+ B\n" +"+ A\n" +"+ C\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:72 +msgid "" +"But such a result might not be very helpful for programmers reading the " +"code. The following edit sequence is much better, at least it is shorter." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:74 +msgid "" +"- A\n" +"- B\n" +" C\n" +"+ B\n" +" A\n" +" B\n" +"- B\n" +" A\n" +"+ C\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:86 +msgid "" +"In fact, it is one of the shortest edit sequences that can transform text" +" a into text b, with 5 operations. If we only measure the length of the " +"edit sequence, this result is satisfactory. But when we look at the " +"various programming languages, we find that there are other metrics that " +"are equally important for user experience. Let's look at the following " +"examples:" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:88 +msgid "" +"// good quality\n" +" struct RHSet[T] {\n" +" set : RHTable[T, Unit]\n" +" }\n" +"+\n" +"+ fn RHSet::new[T](capacity : Int) -> RHSet[T] {\n" +"+ let set : RHTable[T, Unit]= RHTable::new(capacity)\n" +"+ { set : set }\n" +"+ }\n" +"\n" +"\n" +"// bad quality\n" +" struct RHSet[T] {\n" +" set : RHTable[T, Unit]\n" +"+ }\n" +"+\n" +"+ fn RHSet::new[T](capacity : Int) -> RHSet[T] {\n" +"+ let set : RHTable[T, Unit]= RHTable::new(capacity)\n" +"+ { set : set }\n" +" }\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:111 +msgid "" +"When we insert a new function definition at the end of a file, the " +"calculated edit sequence should ideally locate the changes at the end. In" +" similar cases, when there are both deletions and insertions, it is best " +"not to calculate an edit sequence that interleaves these two operations. " +"Here's another example." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:113 +msgid "" +"Good: - one Bad: - one\n" +" - two + four\n" +" - three - two\n" +" + four + five\n" +" + five + six\n" +" + six - three\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:122 +msgid "" +"Myers' diff algorithm can fulfill all those requirements. It is a greedy " +"algorithm that skips over matching lines whenever possible (avoiding " +"inserting text before `{`), and it also tries to place deletions before " +"insertions, avoiding the latter situation." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:124 +msgid "Algorithm Overview" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:126 +msgid "" +"The basic idea in Myers' paper is to construct a grid graph of edit " +"sequences and then search for the shortest path on this graph. Using the " +"previous example `a = ABCABBA` and `b = CBABAC`, we create an `(x, y)` " +"coordinate grid." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:128 +msgid "" +" 0 1 2 3 4 5 6 7\n" +"\n" +"0 o-----o-----o-----o-----o-----o-----o-----o\n" +" | | | \\ | | | | |\n" +" | | | \\ | | | | | C\n" +" | | | \\ | | | | |\n" +"1 o-----o-----o-----o-----o-----o-----o-----o\n" +" | | \\ | | | \\ | \\ | |\n" +" | | \\ | | | \\ | \\ | | B\n" +" | | \\ | | | \\ | \\ | |\n" +"2 o-----o-----o-----o-----o-----o-----o-----o\n" +" | \\ | | | \\ | | | \\ |\n" +" | \\ | | | \\ | | | \\ | A\n" +" | \\ | | | \\ | | | \\ |\n" +"3 o-----o-----o-----o-----o-----o-----o-----o\n" +" | | \\ | | | \\ | \\ | |\n" +" | | \\ | | | \\ | \\ | | B\n" +" | | \\ | | | \\ | \\ | |\n" +"4 o-----o-----o-----o-----o-----o-----o-----o\n" +" | \\ | | | \\ | | | \\ |\n" +" | \\ | | | \\ | | | \\ | A\n" +" | \\ | | | \\ | | | \\ |\n" +"5 o-----o-----o-----o-----o-----o-----o-----o\n" +" | | | \\ | | | | |\n" +" | | | \\ | | | | | C\n" +" | | | \\ | | | | |\n" +"6 o-----o-----o-----o-----o-----o-----o-----o\n" +"\n" +"\n" +" A B C A B B A\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:161 +msgid "" +"The upper left of this grid is the starting point `(0, 0)`, and the lower" +" right is the endpoint `(7, 6)`. Moving one step right along the x-axis " +"deletes the corresponding character in a, moving one step down along the " +"y-axis inserts the corresponding character in b, and diagonal lines " +"indicate matching characters that can be skipped without triggering any " +"edits." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:163 +msgid "" +"Before writing the actual search code, let's manually perform two rounds " +"of searching:" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:165 +msgid "" +"The first round starts at `(0, 0)` and moves one step to reach `(0,1)` " +"and `(1,0)`." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:167 +msgid "" +"The second round starts at `(0,1)` and `(1,0)`. From `(0,1)`, moving down" +" reaches `(0,2)`, but there is a diagonal line leading to `(1,3)`, so the" +" final point is `(1,3)`." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:169 +msgid "The entire Myers algorithm is based on this kind of breadth-first search." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:171 +#: ../../tutorial/example/segment-tree/segment-tree.md:33 +#: ../../tutorial/example/segment-tree/segment-tree2.md:39 +#, fuzzy +msgid "Implementation" +msgstr "实现列表" + +#: ../../tutorial/example/myers-diff/myers-diff.md:173 +msgid "" +"We have outlined the basic idea, now it's time to consider the design in " +"detail. The input to the algorithm is two strings, but the search needs " +"to be conducted on a graph. It's a waste of both memory and time to " +"construct the graph and then search it." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:175 +msgid "" +"The implementation of the Myers algorithm adopts a clever approach by " +"defining a new coordinate `k = x - y`." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:177 +msgid "Moving right increases `k` by one." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:179 +msgid "Moving left decreases `k` by one." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:181 +msgid "Moving diagonally down-left keeps `k` unchanged." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:183 +msgid "" +"Let's define another coordinate `d` to represent the depth of the search." +" Using `d` as the horizontal axis and `k` as the vertical axis, we can " +"draw a tree diagram of the search process." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:185 +msgid "" +" | 0 1 2 3 4 5\n" +"----+--------------------------------------\n" +" |\n" +" 4 | 7,3\n" +" | /\n" +" 3 | 5,2\n" +" | /\n" +" 2 | 3,1 7,5\n" +" | / \\ / \\\n" +" 1 | 1,0 5,4 7,6\n" +" | / \\ \\\n" +" 0 | 0,0 2,2 5,5\n" +" | \\ \\\n" +"-1 | 0,1 4,5 5,6\n" +" | \\ / \\\n" +"-2 | 2,4 4,6\n" +" | \\\n" +"-3 | 3,6\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:206 +msgid "" +"You can see that in each round of searching, `k` is strictly within the " +"range `[-d, d]` (because in one move, it can at most increase or decrease" +" by one from the previous round), and the `k` values between points have " +"an interval of 2. The basic idea of Myers' algorithm comes from this " +"idea: searching by iterating over `d` and `k`. Of course, it also needs " +"to save the `x` coordinates of each round for use in the next round of " +"searching." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:208 +msgid "Let's first define the `Line` struct, which represents a line in the text." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:210 +msgid "" +"struct Line {\n" +" number : Int // Line number\n" +" text : String // Does not include newline\n" +"} derive(Debug, Show)\n" +"\n" +"fn Line::new(number : Int, text : String) -> Line {\n" +" Line::{ number : number, text : text }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:221 +msgid "" +"Then, define a helper function that splits a string into `Array[Line]` " +"based on newline characters. Note that line numbers start from 1." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:223 +msgid "" +"fn lines(str : String) -> Array[Line] {\n" +" let mut line_number = 0\n" +" let buf = Buffer::make(50)\n" +" let vec = []\n" +" for i = 0; i < str.length(); i = i + 1 {\n" +" let ch = str[i]\n" +" buf.write_char(ch)\n" +" if ch == '\\n' {\n" +" let text = buf.to_string()\n" +" buf.reset()\n" +" line_number = line_number + 1\n" +" vec.push(Line::new(line_number, text))\n" +" }\n" +" } else {\n" +" // The text may not end with a newline\n" +" let text = buf.to_string()\n" +" if text != \"\" {\n" +" line_number = line_number + 1\n" +" vec.push(Line::new(line_number, text))\n" +" }\n" +" vec\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:249 +msgid "" +"Next, we need to wrap the array so that it supports negative indexing " +"because we will use the value of `k` as an index." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:251 +msgid "" +"type BPArray[T] Array[T] // BiPolar Array\n" +"\n" +"fn BPArray::make[T](capacity : Int, default : T) -> BPArray[T] {\n" +" let arr = Array::make(capacity, default)\n" +" BPArray(arr)\n" +"}\n" +"\n" +"fn op_get[T](self : BPArray[T], idx : Int) -> T {\n" +" let BPArray(arr) = self\n" +" if idx < 0 {\n" +" arr[arr.length() + idx]\n" +" } else {\n" +" arr[idx]\n" +" }\n" +"}\n" +"\n" +"fn op_set[T](self : BPArray[T], idx : Int, elem : T) -> Unit {\n" +" let BPArray(arr) = self\n" +" if idx < 0 {\n" +" arr[arr.length() + idx] = elem\n" +" } else {\n" +" arr[idx] = elem\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:278 +msgid "" +"Now we can start writing the search function. Before searching for the " +"complete path, let's start with our first goal to find the length of the " +"shortest path (equal to the search depth). Here is the basic framework:" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:280 +msgid "" +"fn shortst_edit(a : Array[Line], b : Array[Line]) -> Int {\n" +" let n = a.length()\n" +" let m = b.length()\n" +" let max = n + m\n" +" let v = BPArray::make(2 * max + 1, 0)\n" +" for d = 0; d < max + 1; d = d + 1 {\n" +" for k = -d; k < d + 1; k = k + 2 {\n" +" ......\n" +" }\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:294 +msgid "" +"In the most extreme case (the two texts have no matching lines), it can " +"be inferred that the maximum number of steps needed is `n + m`, and the " +"minimum is 0. Therefore, set the variable `max = n + m`. The array `v` " +"uses `k` as an index to store the historical record of `x` values. Since " +"`k` ranges from `[-d, d]`, the size of this array is set to `2 * max + " +"1`." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:296 +msgid "" +"But even at this point, it is still difficult to figure out what to do " +"next, so let's only consider the case `d = 0; k = 0` for now. At this " +"point, it must be at `(0, 0)`. Also, if the beginnings of the two texts " +"are the same, they can be skipped directly. We write the final " +"coordinates of this round into the array `v`." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:298 +msgid "" +"if d == 0 { // When d equals 0, k must also equal 0\n" +" x = 0\n" +" y = x - k\n" +" while x < n && y < m && a[x].text == b[y].text {\n" +" // Skip all matching lines\n" +" x = x + 1\n" +" y = y + 1\n" +" }\n" +" v[k] = x\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:311 +msgid "" +"When `d > 0`, the coordinate information stored from the previous round " +"is required. When we know the `k` value of a point and the coordinates of" +" the points from the previous round of searching, the value of `v[k]` is " +"easy to deduce. Because with each step k can only increase or decrease by" +" one, `v[k]` in the search tree must extend from either `v[k - 1]` or " +"`v[k + 1]`. The next question is: how to choose between the two paths " +"ending at `v[k - 1]` and `v[k + 1]`?" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:313 +msgid "There are two boundary cases: `k == -d` and `k == d`." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:315 +msgid "When `k == -d`, you can only choose `v[k + 1]`." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:317 +msgid "When `k == -d`, you can only choose `v[k - 1]`." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:319 +msgid "" +"Recalling the requirement mentioned earlier: arranging deletions before " +"insertions as much as possible, this essentially means choosing the " +"position with the largest `x` value from the previous position." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:321 +msgid "" +"if k == -d {\n" +" x = v[k + 1]\n" +"} else if k == d {\n" +" x = v[k - 1] + 1 // add 1 to move horizontally\n" +"} else if v[k - 1] < v[k + 1] {\n" +" x = v[k + 1]\n" +"} else {\n" +" x = v[k - 1] + 1\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:333 +msgid "Merging these four branches, we get the following code:" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:335 +msgid "" +"if k == -d || (k != d && v[k - 1] < v[k + 1]) {\n" +" x = v[k + 1]\n" +"} else {\n" +" x = v[k - 1] + 1\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:343 +msgid "Combining all the steps above, we get the following code:" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:345 +msgid "" +"fn shortst_edit(a : Array[Line], b : Array[Line]) -> Int {\n" +" let n = a.length()\n" +" let m = b.length()\n" +" let max = n + m\n" +" let v = BPArray::make(2 * max + 1, 0)\n" +" // v[1] = 0\n" +" for d = 0; d < max + 1; d = d + 1 {\n" +" for k = -d; k < d + 1; k = k + 2 {\n" +" let mut x = 0\n" +" let mut y = 0\n" +" // if d == 0 {\n" +" // x = 0\n" +" // }\n" +" if k == -d || (k != d && v[k - 1] < v[k + 1]) {\n" +" x = v[k + 1]\n" +" } else {\n" +" x = v[k - 1] + 1\n" +" }\n" +" y = x - k\n" +" while x < n && y < m && a[x].text == b[y].text {\n" +" x = x + 1\n" +" y = y + 1\n" +" }\n" +" v[k] = x\n" +" if x >= n && y >= m {\n" +" return d\n" +" }\n" +" }\n" +" } else {\n" +" abort(\"impossible\")\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:380 +msgid "" +"Since the initial value of the array is 0, we can omit the branch for `d " +"== 0`." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:384 +msgid "" +"We have implemented an incomplete version of Myers' algorithm, which " +"completes the forward path search. In the next article, we will implement" +" the backtracking to restore the complete edit path and write a function " +"to output a colored diff." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:386 +msgid "This article references:" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:388 +msgid "" +"[https://blog.jcoglan.com/2017/02/15/the-myers-diff-algorithm-" +"part-2/](https://blog.jcoglan.com/2017/02/15/the-myers-diff-algorithm-" +"part-2/)" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff.md:390 +msgid "Thanks to the author James Coglan." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:1 +msgid "Myers diff 2" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:3 +msgid "" +"This is the second post in the diff series. In the [previous " +"one](https://www.moonbitlang.com/docs/examples/myers-diff), we learned " +"how to transform the process of computing diffs into a graph search " +"problem and how to search for the shortest edit distance. In this " +"article, we will learn how to extend the search process from the previous" +" post to obtain the complete edit sequence." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:5 +msgid "Recording the Search Process" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:7 +msgid "" +"The first step to obtaining the complete edit sequence is to save the " +"entire editing process. This step is relatively simple; we just need to " +"save the current search depth `d` and the graph node with depth `d` at " +"the beginning of each search round." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:9 +msgid "" +"fn shortest_edit(a : Array[Line], b : Array[Line]) -> " +"Array[(BPArray[Int], Int)] {\n" +" let n = a.length()\n" +" let m = b.length()\n" +" let max = n + m\n" +" let v = BPArray::make(2 * max + 1, 0)\n" +" let trace = []\n" +" fn push(v : BPArray[Int], d : Int) -> Unit {\n" +" trace.push((v, d))\n" +" }\n" +" // v[1] = 0\n" +" for d = 0; d < max + 1; d = d + 1 {\n" +" push(v.copy(), d) // Save search depth and node\n" +" for k = -d; k < d + 1; k = k + 2 {\n" +" let mut x = 0\n" +" let mut y = 0\n" +" // if d == 0 {\n" +" // x = 0\n" +" // }\n" +" if k == -d || (k != d && v[k - 1] < v[k + 1]) {\n" +" x = v[k + 1]\n" +" } else {\n" +" x = v[k - 1] + 1\n" +" }\n" +" y = x - k\n" +" while x < n && y < m && a[x].text == b[y].text {\n" +" x = x + 1\n" +" y = y + 1\n" +" }\n" +"\n" +" v[k] = x\n" +" if x >= n && y >= m {\n" +" return trace\n" +" }\n" +" }\n" +" } else {\n" +" abort(\"impossible\")\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:50 +msgid "Backtracking the Edit Path" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:52 +msgid "" +"After recording the entire search process, the next step is to walk back " +"from the endpoint to find the path taken. But before we do that, let's " +"first define the `Edit` type." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:54 +msgid "" +"enum Edit {\n" +" Insert(~new : Line)\n" +" Delete(~old : Line)\n" +" Equal(~old : Line, ~new : Line) // old, new\n" +"} derive(Debug, Show)\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:62 +msgid "Next, let's perform the backtracking." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:64 +msgid "" +"fn backtrack(a : Array[Line], b : Array[Line], trace : " +"Array[(BPArray[Int], Int)]) -> Array[Edit] {\n" +" let mut x = a.length()\n" +" let mut y = b.length()\n" +" let edits = []\n" +" fn push(edit : Edit) -> Unit {\n" +" edits.push(edit)\n" +" }\n" +" ......\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:75 +msgid "" +"The method of backtracking is essentially the same as forward search, " +"just in reverse." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:77 +msgid "Calculate the current `k` value using `x` and `y`." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:79 +msgid "" +"Access the historical records and use the same judgment criteria as in " +"forward search to find the `k` value at the previous search round." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:81 +msgid "Restore the coordinates of the previous search round." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:83 +msgid "Try free movement and record the corresponding edit actions." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:85 +msgid "Determine the type of edit that caused the change in `k` value." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:87 +msgid "Continue iterating." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:89 +msgid "" +" for i = trace.length() - 1; i >= 0; i = i - 1 {\n" +" let (v, d) = trace[i]\n" +" let k = x - y\n" +" let prev_k = if k == -d || (k != d && v[k - 1] < v[k + 1]) {\n" +" k + 1\n" +" } else {\n" +" k - 1\n" +" }\n" +" let prev_x = v[prev_k]\n" +" let prev_y = prev_x - prev_k\n" +" while x > prev_x && y > prev_y {\n" +" x = x - 1\n" +" y = y - 1\n" +" push(Equal(old = a[x], new = b[y]))\n" +" }\n" +" if d > 0 {\n" +" if x == prev_x {\n" +" push(Insert(new = b[prev_y]))\n" +" } else if y == prev_y {\n" +" push(Delete(old = a[prev_x]))\n" +" }\n" +" x = prev_x\n" +" y = prev_y\n" +" }\n" +" }\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:117 +msgid "Combining the two functions, we get a complete `diff` implementation." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:119 +msgid "" +"fn diff(a : Array[Line], b : Array[Line]) -> Array[Edit] {\n" +" let trace = shortest_edit(a, b)\n" +" backtrack(a, b, trace)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:126 +msgid "Printing the Diff" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:128 +msgid "" +"To print a neat diff, we need to left-align the text. Also, due to the " +"order issue during backtracking, we need to print from back to front." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:130 +msgid "" +"let line_width = 4\n" +"\n" +"fn pad_right(s : String, width : Int) -> String {\n" +" String::make(width - s.length(), ' ') + s\n" +"}\n" +"\n" +"fn print_edit(edit : Edit) -> String {\n" +" match edit {\n" +" Insert(_) as edit => {\n" +" let tag = \"+\"\n" +" let old_line = pad_right(\"\", line_width)\n" +" let new_line = pad_right(edit.new.number.to_string(), line_width)\n" +" let text = edit.new.text\n" +" \"\\{tag} \\{old_line} \\{new_line} \\{text}\"\n" +" }\n" +" Delete(_) as edit => {\n" +" let tag = \"-\"\n" +" let old_line = pad_right(edit.old.number.to_string(), line_width)\n" +" let new_line = pad_right(\"\", line_width)\n" +" let text = edit.old.text\n" +" \"\\{tag} \\{old_line} \\{new_line} \\{text}\"\n" +" }\n" +" Equal(_) as edit => {\n" +" let tag = \" \"\n" +" let old_line = pad_right(edit.old.number.to_string(), line_width)\n" +" let new_line = pad_right(edit.new.number.to_string(), line_width)\n" +" let text = edit.old.text\n" +" \"\\{tag} \\{old_line} \\{new_line} \\{text}\"\n" +" }\n" +" }\n" +"}\n" +"\n" +"fn print_diff(diff : Array[Edit]) -> Unit {\n" +" for i = diff.length() - 1; i >= 0; i = i - 1 {\n" +" diff[i]\n" +" |> print_edit\n" +" |> println\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:172 +msgid "The result is as follows:" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:174 +msgid "" +"- 1 A\n" +"- 2 B\n" +" 3 1 C\n" +"+ 2 B\n" +" 4 3 A\n" +" 5 4 B\n" +"- 6 B\n" +" 7 5 A\n" +"+ 6 C\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff2.md:188 +msgid "" +"The Myers algorithm demonstrated above is complete, but due to the " +"frequent copying of arrays, it has a very large space overhead. " +"Therefore, most software like Git uses a linear variant of the diff " +"algorithm (found in the appendix of the original paper). This variant may" +" sometimes produce diffs of lower quality (harder for humans to read) " +"than the standard Myers algorithm but can still ensure the shortest edit " +"sequence." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:1 +msgid "Myers diff 3" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:3 +msgid "" +"This article is the third in the [diff " +"series](https://docs.moonbitlang.com/examples/myers-diff). In the " +"[previous part](https://docs.moonbitlang.com/examples/myers-diff2), we " +"explored the full Myers algorithm and its limitations. In this post, " +"we'll learn how to implement a variant of the Myers algorithm that " +"operates with linear space complexity." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:5 +msgid "Divide and Conquer" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:7 +msgid "" +"The linear variant of Myers' diff algorithm used by Git employs a concept" +" called the _Snake_ (sometimes referred to as the _Middle Snake_) to " +"break down the entire search process. A Snake in the edit graph " +"represents a diagonal movement of 0 to N steps after a single left or " +"down move. The linear Myers algorithm finds the middle Snake on the " +"optimal edit path and uses it to divide the entire edit graph into two " +"parts. The subsequent steps apply the same technique to the resulting " +"subgraphs, eventually producing a complete edit path." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:9 +msgid "" +" 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14\n" +" 0 o---o---o---o---o---o---o\n" +" | | | | | | |\n" +" 1 o---o---o---o---o---o---o\n" +" | | \\ | | | | |\n" +" 2 o---o---o---o---o---o---o\n" +" | | | | | | |\n" +" 3 o---o---o---o---o---o---o\n" +" | | | | | \\ | |\n" +" 4 o---o---o---o---o---o---o\n" +" | | | | | | |\n" +" 5 o---o---o---o---o---o---o\n" +" \\\n" +" 6 @\n" +" \\\n" +" 7 @---o---o---o---o---o---o\n" +" | | | | | |\n" +" 8 o---o---o---o---o---o\n" +" | \\ | | | | |\n" +" 9 o---o---o---o---o---o\n" +" | | | | | |\n" +"10 o---o---o---o---o---o\n" +" | | | | | |\n" +"11 o---o---o---o---o---o\n" +" | | | \\ | | |\n" +"12 o---o---o---o---o---o\n" +" | | | | | |\n" +"13 o---o---o---o---o---o\n" +" | | | | | \\ |\n" +"14 o---o---o---o---o---o\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:42 +msgid "" +"A quick recap: The optimal edit path is the one that has the shortest " +"distance to the endpoint (a diagonal distance of zero), and there can be " +"more than one such path." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:44 +msgid "" +"Attentive readers may have noticed a chicken-and-egg problem: to find a " +"Snake, you need an optimal edit path, but to get an optimal edit path, it" +" seems like you need to run the original Myers algorithm first." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:46 +msgid "" +"In fact, the idea behind the linear Myers algorithm is somewhat " +"unconventional: it alternates the original Myers algorithm from both the " +"top-left and bottom-right corners, but without storing the history. " +"Instead, it simply checks if the searches from both sides overlap. When " +"they do, the overlapping portion is returned as the Middle Snake." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:48 +msgid "" +"This approach seems straightforward, but there are still some details to " +"sort out." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:50 +msgid "" +"When searching from the bottom-right, the diagonal coordinate can no " +"longer be referred to as _k_. We need to define a new diagonal coordinate" +" **c = k - delta**. This coordinate is the mirror image of _k_, perfectly" +" suited for reverse direction search." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:52 +msgid "" +" x k\n" +" 0 1 2 3\n" +" 0 1 2 3 \\ \\ \\ \\\n" +" y 0 o-----o-----o-----o o-----o-----o-----o\n" +" | | | | -1 | | | | \\\n" +" | | | | \\ | | | | 2\n" +" 1 o-----o-----o-----o o-----o-----o-----o\n" +" | | \\ | | -2 | | \\ | | \\\n" +" | | \\ | | \\ | | \\ | | 1\n" +" 2 o-----o-----o-----o o-----o-----o-----o\n" +" \\ \\ \\ \\\n" +" -3 -2 -1 0\n" +" c\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:68 +msgid "" +"How do we determine if the searches overlap? Simply check if the position" +" on a diagonal line in the forward search has an _x_ value greater than " +"that in the reverse search. However, since the _k_ and _c_ coordinates " +"differ for the same diagonal, the conversion can be a bit tricky." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:70 +#, fuzzy +msgid "Code Implementation" +msgstr "实现列表" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:72 +msgid "" +"We'll start by defining `Snake` and `Box` types, representing the middle " +"snake and the sub-edit graphs (since they're square, we call them `Box`)." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:74 +msgid "" +"struct Box {\n" +" left : Int,\n" +" right : Int,\n" +" top : Int,\n" +" bottom : Int\n" +"} derive(Debug, Show)\n" +"\n" +"struct Snake {\n" +" start : (Int, Int),\n" +" end : (Int, Int)\n" +"} derive(Debug, Show)\n" +"\n" +"fn width(self : Box) -> Int {\n" +" self.right - self.left\n" +"}\n" +"\n" +"fn height(self : Box) -> Int {\n" +" self.bottom - self.top\n" +"}\n" +"\n" +"fn size(self : Box) -> Int {\n" +" self.width() + self.height()\n" +"}\n" +"\n" +"fn delta(self : Box) -> Int {\n" +" self.width() - self.height()\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:104 +msgid "" +"To avoid getting bogged down in details too early, let's assume we " +"already have a function `midpoint : (Box, Array[Line], Array[Line]) -> " +"Snake?` to find the middle snake. Then, we can build the function " +"`find_path` to search for the complete path." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:106 +msgid "" +"fn find_path(box : Box, a : Array[Line], b : Array[Line]) -> Iter[(Int, " +"Int)]? {\n" +" let snake = midpoint(box, a, b)?;\n" +" let start = snake.start;\n" +" let end = snake.end;\n" +" let headbox = Box { left: box.left, top: box.top, right: start.0, " +"bottom: start.1 };\n" +" let tailbox = Box { left: end.0, top: end.1, right: box.right, bottom: " +"box.bottom };\n" +" // println(\"snake = \\{snake}\")\n" +" // println(\"headbox = \\{headbox}\")\n" +" // println(\"tailbox = \\{tailbox}\")\n" +" let head = find_path(headbox, a, b).or(Iter::singleton(start));\n" +" let tail = find_path(tailbox, a, b).or(Iter::singleton(end));\n" +" Some(head.concat(tail))\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:122 +msgid "" +"The implementation of `find_path` is straightforward, but `midpoint` is a" +" bit more complex:" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:124 +msgid "For a `Box` of size 0, return `None`." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:125 +msgid "" +"Calculate the search boundaries. Since forward and backward searches each" +" cover half the distance, divide by two. However, if the size of the " +"`Box` is odd, add one more to the forward search boundary." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:126 +msgid "Store the results of the forward and backward searches in two arrays." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:127 +msgid "" +"Alternate between forward and backward searches, returning `None` if no " +"result is found." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:129 +msgid "" +"fn midpoint(self : Box, a : Array[Line], b : Array[Line]) -> Snake? {\n" +" if self.size() == 0 {\n" +" return None;\n" +" }\n" +" let max = {\n" +" let half = self.size() / 2;\n" +" if is_odd(self.size()) {\n" +" half + 1\n" +" } else {\n" +" half\n" +" }\n" +" };\n" +" let vf = BPArray::make(2 * max + 1, 0);\n" +" vf[1] = self.left;\n" +" let vb = BPArray::make(2 * max + 1, 0);\n" +" vb[1] = self.bottom;\n" +" for d in 0..max + 1 {\n" +" match forward(self, vf, vb, d, a, b) {\n" +" None =>\n" +" match backward(self, vf, vb, d, a, b) {\n" +" None => continue,\n" +" res => return res,\n" +" },\n" +" res => return res,\n" +" }\n" +" }\n" +" None\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:160 +msgid "" +"The forward and backward searches have some modifications compared to the" +" original Myers algorithm, which need a bit of explanation:" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:162 +msgid "" +"Since we need to return the snake, the search process must calculate the " +"previous coordinate (`px` stands for previous x)." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:163 +msgid "" +"The search now works within a `Box` (not the global edit graph), so " +"calculating `y` from `x` (or vice versa) requires conversion." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:164 +msgid "" +"The backward search minimizes `y` as a heuristic strategy, but minimizing" +" `x` would also work." +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:166 +msgid "" +"fn forward(self : Box, vf : BPArray, vb : BPArray, d : Int, a :" +" Array[Line], b : Array[Line]) -> Snake? {\n" +" for k in (0..=d).rev() {\n" +" let c = k - self.delta();\n" +" let (mut x, mut px) = if k == -d || (k != d && vf[k - 1] < vf[k\n" +"\n" +" + 1]) {\n" +" (vf[k + 1], vf[k + 1])\n" +" } else {\n" +" (vf[k - 1] + 1, vf[k - 1])\n" +" };\n" +" let mut y = self.top + (x - self.left) - k;\n" +" let py = if d == 0 || x != px { y } else { y - 1 };\n" +" while x < self.right && y < self.bottom && a[x].text == b[y].text {\n" +" x += 1;\n" +" y += 1;\n" +" }\n" +" vf[k] = x;\n" +" if is_odd(self.delta()) && (c >= -(d - 1) && c <= d - 1) && y >= " +"vb[c] {\n" +" return Some(Snake { start: (px, py), end: (x, y) });\n" +" }\n" +" }\n" +" None\n" +"}\n" +"\n" +"fn backward(self : Box, vf : BPArray, vb : BPArray, d : Int, a " +": Array[Line], b : Array[Line]) -> Snake? {\n" +" for c in (0..=d).rev() {\n" +" let k = c + self.delta();\n" +" let (mut y, mut py) = if c == -d || (c != d && vb[c - 1] > vb[c + 1])" +" {\n" +" (vb[c + 1], vb[c + 1])\n" +" } else {\n" +" (vb[c - 1] - 1, vb[c - 1])\n" +" };\n" +" let mut x = self.left + (y - self.top) + k;\n" +" let px = if d == 0 || y != py { x } else { x + 1 };\n" +" while x > self.left && y > self.top && a[x - 1].text == b[y - 1].text" +" {\n" +" x -= 1;\n" +" y -= 1;\n" +" }\n" +" vb[c] = y;\n" +" if is_even(self.delta()) && (k >= -d && k <= d) && x <= vf[k] {\n" +" return Some(Snake { start: (x, y), end: (px, py) });\n" +" }\n" +" }\n" +" None\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/myers-diff/myers-diff3.md:216 +msgid "" +"In addition to the default diff algorithm, Git also offers another diff " +"algorithm called patience diff. It differs significantly from Myers diff " +"in approach and sometimes produces more readable diff results." +msgstr "" + +#: ../../tutorial/example/segment-tree/index.md:1 +msgid "Segment Tree" +msgstr "线段树" + +#: ../../tutorial/example/segment-tree/index.md:3 +msgid "" +"The Segment Tree is a common data structure used to solve various range " +"modification and query problems. For instance, consider the following " +"problem:" +msgstr "先断树是一种常见的数据结构,用于一些线性区间的修改、查询问题,比如对于以下问题:" + +#: ../../tutorial/example/segment-tree/index.md:5 +msgid "" +"Given a known-length array of numbers with initial values, we need to " +"perform multiple range addition operations (adding a value to all " +"elements in a range) and range sum operations (calculating the sum of " +"elements in a range)." +msgstr "给出一个长度已知的、有初值的数字数组,接下来要进行许多区间加法操作(将一个区间的数值加到所有元素上)和区间求和操作(计算一个区间的元素和)。" + +#: ../../tutorial/example/segment-tree/index.md:7 +msgid "" +"Using a standard array, assuming the length og this array is N, each " +"modification and query would take O(N) time. However, after constructing " +"a Segment Tree in O(log N) time, both operations can be performed in " +"O(log N), highlighting the importance of Segment Trees for range queries." +msgstr "" +"如果该问题使用正常的数组方式来遍历求解,假设该数组长度为 N,每次修改和查询的操作耗时是 O(N)的;但线段树经过 O(N log N) " +"的构建之后,可以对上述两个操作做到 O(log N) 的优秀复杂度,足以体现其在区间问题上的重要性。" + +#: ../../tutorial/example/segment-tree/index.md:9 +msgid "" +"This example illustrates just one simple problem that Segment Trees can " +"address. They can handle much more complex and interesting scenarios. In " +"the upcoming articles, we will explore the concept of Segment Trees and " +"how to implement them in MoonBit, ultimately creating a tree that " +"supports range addition and multiplication, enables range sum queries, " +"and has immutable properties." +msgstr "" +"当然上面的例子只是线段树可以解决的一个简单问题,它可以做到的更复杂、更有趣的事情还有很多。在接下来的几篇文章当中我们将会学习使用线段树的概念以及如何使用" +" MoonBit 实现它,最终我们将一步步实现一棵支持区间加法与乘法、并可以查询区间和、拥有不可变特性的线段树。" + +#: ../../tutorial/example/segment-tree/index.md:11 +msgid "" +"In this section, we will learn the basic principles of Segment Trees and " +"how to write a simple Segment Tree in MoonBit that supports point " +"modifications and queries." +msgstr "本节我们将学习线段树的基本原理以及如何使用 MoonBit 编写一棵最基本的支持单点修改、查询的线段树。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:1 +msgid "Segment Trees (Part 1)" +msgstr "线段树(第一部分)" + +#: ../../tutorial/example/segment-tree/segment-tree.md:3 +msgid "What is a Segment Tree?" +msgstr "线段树是什么?" + +#: ../../tutorial/example/segment-tree/segment-tree.md:5 +msgid "" +"This section focuses on concepts and theory. If you're already familiar " +"with Segment Trees and their principles, feel free to skip to the next " +"section." +msgstr "本节是纯粹的概念、理论内容,如果读者已经了解并且熟悉线段树的构成与其原理,可以直接阅读下一节内容。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:7 +msgid "" +"As mentioned in the introduction, Segment Trees address a class of range " +"problems, but what do they look like, and what is the principle behind " +"their excellent complexity?" +msgstr "就像引言当中所说的,线段树可以解决一类区间问题,但他长什么样子,能做到如此优秀复杂度的原理又是什么呢?" + +#: ../../tutorial/example/segment-tree/segment-tree.md:9 +msgid "" +"Let's consider a linear sequence of numbers as an example. If we want to " +"build a Segment Tree from it, it will look like this:" +msgstr "我们以下图一个线性的数字序列为例,如果我们希望以它建立一棵线段树,那么它将会长这个样子:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:11 +msgid "![build segment tree](/imgs/segment-tree-build.png)" +msgstr "![构建线段树](/imgs/segment-tree-build.png)" + +#: ../../tutorial/example/segment-tree/segment-tree.md:11 +msgid "build segment tree" +msgstr "构建线段树" + +#: ../../tutorial/example/segment-tree/segment-tree.md:13 +msgid "" +"We can see that we recursively divide the linear sequence into two equal " +"parts (with one side having an extra element if the length is odd) until " +"we reach segments of length one. During this process, we compute the sum " +"of each segment (shown in parentheses), thereby creating a Segment Tree " +"that supports range sum queries from a linear sequence." +msgstr "可以看到我们把一个线性序列的区间层层分而治之,每次分割为两个对等(如果是奇数则一边多一个)的两个区间(区间范围下标在图示中),最终直到分割为长度为一的区间,并且在此过程中计算了其中每个区间元素的加和(在括号中),这样就从一个线性序列创建了一棵支持查询区间和线段树。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:15 +msgid "" +"So, how does it work when querying a range sum? Let's take the example of" +" querying the sum from index 1 to 6:" +msgstr "那么在查询区间和的时候,它如何工作呢?我们以查询区间 1-6 的和为例:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:17 +msgid "![query segment tree](/imgs/segment-tree-query.png)" +msgstr "![查询线段树](/imgs/segment-tree-query.png)" + +#: ../../tutorial/example/segment-tree/segment-tree.md:17 +msgid "query segment tree" +msgstr "查询线段树" + +#: ../../tutorial/example/segment-tree/segment-tree.md:19 +msgid "" +"The highlighted parts in the diagram sum up to the total for the range " +"1-6, and we didn't have to consider all elements; we simply selected the " +"minimum number of segments needed to obtain our result and combined them." +" We can traverse the Segment Tree from top to bottom to determine the " +"intersections and containment relationships between segments to select " +"the appropriate ranges." +msgstr "" +"可以发现图中的标红部分加起来就等于区间 1-6 " +"的区间和,而我们并没有统计到所有元素,只是选取了最少的区间来频出我们需要求解的区间,并且把我们要求的结果(此处为和)合并起来即可得到最终解。而我们只需要从上到下遍历这棵线段树来判断区间之间的交集/包含关系即可选择到符合条件的区间。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:21 +msgid "Specifically:" +msgstr "具体来说:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:23 +msgid "" +"First, we check the relationship between the ranges 1-7 and 1-6. The " +"latter is a subset of the former, so the data from 1-7 cannot be used in " +"our calculation, and we proceed to explore its two child nodes." +msgstr "首先询问区间 1-7 与 1-6 的关系,显然后者为前者的子集,当前 1-7 的数据不能用于统计,因此继续向下遍历两个子节点。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:24 +msgid "" +"Next, we check the relationship between 1-3 and 1-6. The former is a " +"subset of the latter, contributing to our result." +msgstr "再询问 1-3 与 1-6 的关系,前者为后者的子集,可以作为解的一部分,统计入当前结果中。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:25 +msgid "" +"Then, we examine the relationship between 4-7 and 1-6, which overlap, " +"requiring us to explore both child nodes further." +msgstr "接下来询问 4-7 与 1-6 的关系,二者有交集,因此要继续向下遍历两个子节点。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:26 +msgid "" +"And we examine the relationship between 4-5 and 1-6, just as the third " +"step." +msgstr "然后询问 4-5 与 1-6 的关系,此处与第三条一致。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:27 +msgid "We repeat this process..." +msgstr "以此类推..." + +#: ../../tutorial/example/segment-tree/segment-tree.md:29 +msgid "" +"Based on binary decomposition, we will query at most Log N segments for " +"any range of length N, ensuring guaranteed complexity." +msgstr "根据二进制分解的知识,我们对任何长度为 N 的区间最多只会求解 Log N 个区间,因此复杂度是可以保证的。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:31 +msgid "" +"This section only discusses the query operation; we will elaborate on the" +" principles and implementation of modification operations in the next " +"section." +msgstr "这里仅聊到查询操作,关于线段树上的修改操作的原理和实现我们将会在下一节详细说明。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:35 +#: ../../tutorial/example/segment-tree/segment-tree2.md:41 +msgid "Basic Definition" +msgstr "基础定义" + +#: ../../tutorial/example/segment-tree/segment-tree.md:37 +msgid "We use a classic approach to represent the Segment Tree:" +msgstr "我们采用一个非常经典的方法来表达线段树:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:39 +msgid "" +"enum Node {\n" +" Nil\n" +" Node(Int, Node, Node)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree.md:45 +msgid "" +"Here, `Nil` represents an empty tree, while a `Node` contains the stored " +"data (of type Int) and its left and right children." +msgstr "其中 `Nil` 代表空树,而一个 `Node` 则包含一个它所储存的数据(为 Int 类型)和左右两个节点。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:47 +#: ../../tutorial/example/segment-tree/segment-tree2.md:53 +msgid "Building the Tree" +msgstr "建树" + +#: ../../tutorial/example/segment-tree/segment-tree.md:49 +msgid "" +"Building the tree refers to the process of abstracting a linear sequence " +"into a Segment Tree, commonly referred to as `build`." +msgstr "建树是指将一个线性序列抽象为线段树的过程,一般将其称为 `build` 。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:51 +msgid "" +"To start, we should write an overloaded `op_add` function for the `Node` " +"type to assist with the tree-building process:" +msgstr "作为前置,我们应该根据需求为 `Node` 类型编写一个 `op_add` 的重载来配合下面建树的过程:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:53 +msgid "" +"fn op_add(self : Node, v : Node) -> Node {\n" +" match (self, v) {\n" +" (Node(left, _, _), Node(right, _, _)) => Node(left + right, self, v)\n" +" (Node(_), Nil) => self\n" +" (Nil, Node(_)) => v\n" +" (Nil, Nil) => Nil\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree.md:59 +msgid "" +"With this operation defined, we can easily merge two `Node` instances " +"while maintaining the segment sums, laying the foundation for building " +"the tree. In some descriptions of Segment Trees, this process is also " +"called `pushup`." +msgstr "" +"定义这一运算之后就可以轻松的向上合并两个 `Node` " +"节点,并在此过程中维护区间的和,为我们建树打下了基础,在有些线段树的叙述当中,这个过程也叫做 `pushup` 。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:61 +msgid "" +"We can leverage MoonBit's `ArrayView` feature (known as `slice` in some " +"languages) to recursively build the tree from a segment of a linear " +"structure at a low cost, achieving O(Log N) complexity:" +msgstr "" +"我们可以用 MoonBit 的 `ArrayView` 特性(某些语言当中也叫做 `slice` " +")作为参数来低成本的取出一个线性结构的一段进行递归建树,这个过程是 `O(N Log N)` 的:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:63 +msgid "" +"fn build(data : ArrayView[Int]) -> Node {\n" +" if data.length() == 1 {\n" +" Node(data[0], Nil, Nil)\n" +" } else {\n" +" let mid = (data.length() + 1) >> 1\n" +" build(data[0:mid]) + build(data[mid:])\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree.md:69 +msgid "Let’s analyze this code:" +msgstr "分析一下这段代码:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:71 +msgid "" +"If the current length is 1, the segment does not need further " +"subdivision, so we return a leaf node with empty left and right branches." +msgstr "首先如果当前长度已经为 1,就证明该区间不再需要细分,直接返回左右分支为空的叶子节点。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:72 +msgid "" +"Otherwise, we split the segment at the midpoint and recursively build the" +" left and right segments, then merge the results." +msgstr "否则就证明该区间还可被分割,则求其中间值将其分割为两个区间分而治之的建树再通过 `Node` 之间的加法合并。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:74 +msgid "" +"This code is concise, highly readable, and optimization-friendly, serving" +" as a great learning paradigm for other data structures." +msgstr "这段代码是非常简洁、可读性非常高的,而且对优化非常友好,可以作为后续其他数据结构的范式学习。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:76 +msgid "Now, let's build a tree and test it:" +msgstr "让我们来建立一棵树并测试看看:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:78 +msgid "" +"test {\n" +" let tree = build([1, 2, 3, 4, 5][:])\n" +" @json.inspect!(\n" +" tree,\n" +" content=[\n" +" 15,\n" +" [6, [3, [1, \"Nil\", \"Nil\"], [2, \"Nil\", \"Nil\"]], [3, \"Nil\"," +" \"Nil\"]],\n" +" [9, [4, \"Nil\", \"Nil\"], [5, \"Nil\", \"Nil\"]],\n" +" ],\n" +" )\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree.md:84 +msgid "Great! We've successfully built the tree!" +msgstr "漂亮,我们已经成功完成了建树的过程!" + +#: ../../tutorial/example/segment-tree/segment-tree.md:86 +msgid "Querying" +msgstr "查询" + +#: ../../tutorial/example/segment-tree/segment-tree.md:88 +msgid "" +"Next, we need to implement the query function. Since the nodes of our " +"Segment Tree maintain segment sums, we can write a `query` function to " +"retrieve these sums:" +msgstr "接下来我们要编写查询,因为这棵线段树的节点向上合并时维护的是区间和,因此我们可以编写一个 `query` 函数来查询它:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:90 +msgid "" +"let empty_node : Node = Node(0, Nil, Nil)\n" +"\n" +"fn query(self : Node, l : Int, r : Int, query_l : Int, query_r : Int) -> " +"Node {\n" +" if query_l > r || l > query_r {\n" +" empty_node\n" +" } else if query_l <= l && query_r >= r {\n" +" self\n" +" } else {\n" +" guard let Node(_, left, right) = self\n" +" let mid = (l + r) >> 1\n" +" left.query(l, mid, query_l, query_r) +\n" +" right.query(mid + 1, r, query_l, query_r)\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree.md:97 +msgid "" +"Here, `l` and `r` represent the currently queried range, while `query_l` " +"and `query_r` denote the range we need to query. Let's break down this " +"implementation:" +msgstr "" +"首先,`l` 与 `r` 是当前函数中已经查询到的区间, `query_l` 与 `query_r` " +"是需要查询的区间,让我们来尝试解析一下这段实现:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:99 +msgid "" +"If the queried range does not overlap with the current range, it " +"contributes nothing to the result. We define an `empty_node` to represent" +" a zero-contribution node and return it." +msgstr "如果需查询的区间和当前的区间状态为互不相交,则对解没有贡献,我们定义了一个 `empty_node` 来表示 0 贡献节点,将其返回则为无贡献。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:100 +msgid "" +"If the current range is a subset of the queried range, it fully " +"contributes to the result, so we return it directly." +msgstr "如果当前区间就是需查询区间的子集,那么实际上对解的贡献就是它自己,直接返回它即可。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:101 +msgid "" +"If the current range overlaps with the queried range, we continue " +"searching downwards to find the exact covering ranges, merging the " +"results of the left and right nodes." +msgstr "如果当前区间和需要查询的区间存在交集关系,那么需要继续向下搜索来确定准确的覆盖,因此继续求出中间值向下搜索并且合并两边的 `Node` 结果。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:103 +msgid "Before We Continue" +msgstr "在继续之前" + +#: ../../tutorial/example/segment-tree/segment-tree.md:105 +msgid "" +"Notice the highlighted line. When using the `let` to destructure `Node`, " +"we could be sure that the enum being destructured wasn’t `Nil`. However, " +"the compiler couldn't guarantee this, so we would have received a warning" +" for using:" +msgstr "" +"注意被高亮的行。在使用 let 解构 Node 时,我们是可以确定要解构的 enum 一定不是 " +"Nil,但编译器是不能确定这一点的的,所以如果我们尝试这样去解构它:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:107 +msgid "let Node(x, y) = z\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree.md:111 +msgid "" +"Although it didn’t affect execution, it was somewhat misleading. With " +"MoonBit’s newly introduced `guard` statement, we can now handle this " +"better using:" +msgstr "" +"会发现编译器实际上给我们了一个警告。尽管不影响运行,但有些误导。因此我们可以用,MoonBit 的 guard 语句,用 guard let " +"这种方法来更好的解决这种需求:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:113 +msgid "guard let Node(x, y) = z\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree.md:117 +msgid "Q&A" +msgstr "问与答" + +#: ../../tutorial/example/segment-tree/segment-tree.md:119 +msgid "" +"**Q:** Why use `Node` as the return value? Can't I destructure and sum " +"the values directly?" +msgstr "问:为什么要用 `Node` 作为返回值,我用相同的逻辑也可以直接把 `Node` 当中的值给解构出来相加呀?" + +#: ../../tutorial/example/segment-tree/segment-tree.md:120 +msgid "" +"**A:** We have defined an addition operation for `Node`. Consider a " +"scenario where we need to maintain not just the sum but also the minimum " +"value of a range. In that case, we can modify the `op_add` logic to " +"maintain the minimum while the `query` function remains unaffected. It " +"ultimately returns a `Node` that can contain all necessary information, " +"so let's stick with using `Node`!" +msgstr "" +"答:首先,我们为 `Node` " +"已经编写了加和运算,不妨考虑一种情况,我们不止要维护区间和,而是要同时维护区间和还有区间最小值,这时候我们只需要更改 `Node` 的 " +"`op_add` 逻辑来维护最小值即可,而 `query` 函数和我们要维护的数据没有关系,它最终返回的是一个 `Node` " +",它可以求出所有信息!所以不妨就让我们使用 `Node` !" + +#: ../../tutorial/example/segment-tree/segment-tree.md:122 +msgid "**Q:** Shouldn't the `empty_node` change in this case?" +msgstr "问:你说的这种情况 `empty_node` 是不是也要改变?" + +#: ../../tutorial/example/segment-tree/segment-tree.md:123 +msgid "" +"**A:** Yes, the `empty_node` ensures that it doesn’t affect the result " +"when added to any other `Node`. It's a zero-contribution node, akin to " +"how 0 contributes nothing in sum operations. For minimum value " +"maintenance, it can represent a value that won't affect the outcome, " +"making the process flexible!" +msgstr "" +"答:对, `empty_node` 是用来保证它和任何其他 `Node` 相加都不会产生改变的元素,是一个零贡献的`Node` " +",类比在维护区间和的时候零贡献是 0,那么其实对于维护最小值来说你的值是当前可以取到的最大值,那就是零贡献的,这个过程处理的其实很灵活!" + +#: ../../tutorial/example/segment-tree/segment-tree.md:125 +msgid "Now, let's test the query process:" +msgstr "让我们来测试一下这个查询过程:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:127 +msgid "" +"\n" +"test {\n" +" let tree = build([1, 2, 3, 4, 5][:])\n" +" let sum = match tree.query(1, 5, 1, 3) {\n" +" Node(sum, _, _) => sum\n" +" _ => fail!(\"Expected Node\")\n" +" }\n" +" inspect!(sum, content=\"6\")\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree.md:133 +msgid "The output is `6`." +msgstr "输出是:`6`。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:135 +msgid "Fantastic! We've obtained the correct output!" +msgstr "太好了,我们得到了正确的输出!" + +#: ../../tutorial/example/segment-tree/segment-tree.md:137 +msgid "Code" +msgstr "代码" + +#: ../../tutorial/example/segment-tree/segment-tree.md:139 +msgid "" +"For the complete code, please check the [GitHub " +"repository](https://github.com/moonbitlang/moonbit-" +"docs/tree/main/next/sources/segment-tree/src/part1/top.mbt)." +msgstr "" +"完整代码请查看 [GitHub 代码仓库](https://github.com/moonbitlang/moonbit-" +"docs/tree/main/next/sources/segment-tree/src/part1/top.mbt)。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:143 +msgid "" +"Today, we learned how to build and query a simple Segment Tree. In the " +"next lesson, we will explore more complex principles and implementations " +"of Segment Trees. Interested readers can solidify their knowledge and " +"expand on it by implementing the following:" +msgstr "今天我们学习了如何编写一棵简单的线段树的构建和查询操作的编写,下一节课我们将会学习更加复杂的线段树的原理和实现,感兴趣的读者可以在阅读文章之后自行实现下面内容来巩固知识和拓展更多内容:" + +#: ../../tutorial/example/segment-tree/segment-tree.md:145 +msgid "" +"Try implementing a Segment Tree that maintains multiple pieces of " +"information (e.g., range sum, maximum, and minimum)." +msgstr "尝试实现一个可以维护多个信息(如区间和、区间最大值最小值)的线段树。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:146 +msgid "" +"Understand how to implement point query/modification operations for " +"Segment Trees." +msgstr "自行了解如何实现线段树的单点查询/修改操作并实现。" + +#: ../../tutorial/example/segment-tree/segment-tree.md:147 +msgid "" +"Explore range modification operations for Segment Trees and related Lazy " +"Tags." +msgstr "自行了解线段树的区间修改操作以及 LazyTag 的相关知识。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:1 +msgid "Segment Trees (Part 2)" +msgstr "线段树(第二部分)" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:3 +msgid "Introduction" +msgstr "介绍" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:5 +msgid "" +"In the previous article, we discussed the basic implementation of a " +"segment tree. That tree only allowed range queries (single-point " +"modifications and queries were also possible), but it couldn't handle " +"range modifications, such as adding a value to all elements in a given " +"range." +msgstr "在上一篇文章当中我们讨论了最基础线段树的实现,但那棵线段树只能做到区间的查询(当然单点的修改与查询也是可以的),但做不到区间的修改(一个经典的应用是区间加法,即整个区间都加上某个值)。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:7 +msgid "" +"In this session, we will deepen the abstraction by introducing the " +"concept of **LazyTag** to handle range modifications, creating a more " +"functional segment tree." +msgstr "在本节当中我们将基于上次的线段树继续加深抽象,引入 LazyTag 的概念来解决区间修改的问题,完成一棵功能基本完备的线段树。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:9 +msgid "How to Implement Range Modifications?" +msgstr "怎么做到区间修改?" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:11 +msgid "" +"First, let's imagine what happens if we add a number to all elements in a" +" range on the segment tree. How would we do this using a straightforward " +"approach?" +msgstr "先设想如果我们在线段树上给一个区间都加上某个数会发生什么?或者换种说法,以最简单的办法来说,我们是如何完成它的?" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:13 +msgid "![add to segment tree](/imgs/segment-tree-add.png)" +msgstr "![插入线段树](/imgs/segment-tree-add.png)" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:13 +msgid "add to segment tree" +msgstr "插入线段树" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:15 +msgid "" +"Take the segment tree from the last lesson as an example. In the figure " +"below, we add 1 to the range [4, 7]. You'll notice that we need to " +"rebuild and maintain all parts of the tree that cover this range, which " +"is too costly." +msgstr "" +"以上节课的线段树为例,上面这张图中,我们对 [4, 7] 的区间都加上 " +"1。这时候我们会发现这需要把涉及到这段区间的所有树上部分都重新构建维护一次,这个时间代价我们肯定是不能接受的。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:17 +msgid "Is there a better way? Of course! We can use **LazyTag**." +msgstr "那有没有更好的方法?当然有!可以使用 LazyTag!" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:19 +msgid "![lazytag](/imgs/segment-tree-lazytag.png)" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:19 +msgid "lazytag" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:21 +msgid "" +"Consider that instead of modifying every affected part, we mark the " +"smallest covering range with a \"+1\" tag. Based on the length of the " +"range, we calculate its value and merge it upward. Following the " +"complexity of querying from the last lesson, this operation would be " +"O(log N)." +msgstr "" +"设想我们在操作时,仅把 [4, 7] 区间的最少覆盖区间(就像查询需要的区间一样)打上一个 “+1” " +"的标记,并且根据这个区间的长度计算他应该有的值,然后合并上去,根据上节课 query 的复杂度类推,这个操作的复杂度应为 O(Log N) 的。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:23 +msgid "" +"However, there's a problem. While querying ranges like [1, 7] or [4, 7] " +"works fine, what if we query [4, 6]? The minimal covering ranges are [4, " +"5] and [6, 6], not [4, 7], so our tag doesn't propagate to lower nodes." +msgstr "" +"但有个问题,现在这种处理方法查询 [1, 7] 或者 [4, 7] 这些区间都没有问题,但如果我们要查询 [4, 6] 呢?容易发现对于区间 " +"[4, 6],它的最小覆盖区间是 [4, 5] 与 [6, 6] 而不是 [4, 7],我们的 Tag 并没有对下面的节点生效。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:25 +msgid "Here’s where the **Lazy** aspect of LazyTag comes into play." +msgstr "下面我们就要用到 LazyTag 除了 Tag 外的另一个性质:Lazy。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:27 +msgid "![add using lazytag](/imgs/segment-tree-add-lazytag.png)" +msgstr "![使用lazytag插入](/imgs/segment-tree-add-lazytag.png)" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:27 +msgid "add using lazytag" +msgstr "使用lazytag插入" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:29 +msgid "" +"We define that when querying a node with a tag, the tag is distributed to" +" its child nodes. These child nodes inherit the tag and compute their " +"values based on their length. The following diagram shows the propagation" +" of the tag downward when querying [4, 6]." +msgstr "" +"我们规定在查询到某个节点时,如果当前节点上有一个加法的 Tag,就把它分发给下面的节点,下面的节点同样接收这个 Tag " +"并且根据自己的长度计算出自己应有的值。上图展示了在查询区间 [4, 6] 的结果时发生的 Tag 向下分发的操作。非常符合直觉的,叶子节点接收 " +"Tag 之后可以直接转换为自身的改变而不需要再保留 Tag。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:31 +msgid "" +"This \"lazy propagation\" ensures that each modification is completed in " +"O(log N), while ensuring correct query results." +msgstr "容易发现像这样的“懒惰下推 Tag”的方法可以保证每次修改的操作在 O(Log N) 的时间内完成,还可以保证查询时可以查询到正确的结果!" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:34 +msgid "" +"Some may wonder about overlapping tags. However, additive tags like these" +" merge seamlessly without affecting the total sum of a node." +msgstr "可能有些同学会疑问如果 Tag 重叠会怎么样,如果我们尝试一下就可以发现上文的这种加法 Tag 之间其实具有良好的合并性,不影响该节点总和的计算。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:37 +msgid "Let’s dive into the code!" +msgstr "让我们来试试代码实现吧!" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:43 +msgid "" +"In the previous code, we defined the segment tree using `enum`. However, " +"none of the elements were clearly named, which was manageable when the " +"data size was small. Now, we need to add **Tag** and **Length** " +"attributes, so it makes sense to use labeled arguments in the `enum` " +"definition:" +msgstr "" +"上节课的代码当中使用 enum 定义了线段树,但是每个 enum " +"当中的每个元素是用来干什么的其实没有名称标识,因为数据量比较小,对我们的心智负担影响不大,但目前我们需要添加 Tag 和 Length " +"属性存储,会显得匹配和定义的时候无法区分参数。所以我们可以使用 enum 的 labeled-argument 写法来完成更好的定义:" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:45 +msgid "" +"enum Data {\n" +" Data(sum~ : Int, len~ : Int)\n" +"}\n" +"\n" +"enum LazyTag {\n" +" Nil\n" +" Tag(Int)\n" +"}\n" +"\n" +"enum Node {\n" +" Nil\n" +" Node(data~ : Data, tag~ : LazyTag, left~ : Node, right~ : Node)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:51 +msgid "" +"This allows for clearer initialization and pattern matching, making the " +"code easier to follow. We've also abstracted the `Data` type, adding a " +"`len` attribute to represent the length of the current range, which is " +"useful for calculating the node's value." +msgstr "" +"这样我们就清晰地完成了对数据、LazyTag 和节点结构的定义,在下面初始化与模式匹配时将会更加清晰。另外,我们把 Data " +"类型单独抽象出来,比上节课多了一个 len 属性,用来标记当前区间的长度,以配合 Tag 计算当前节点的值。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:55 +msgid "" +"Similar to the last lesson, before building the tree, we need to define " +"the addition operations between `Node` types. However, since we’ve " +"abstracted `Data`, we must account for its addition too:" +msgstr "我们依然像上一节一样在编写建树逻辑之前需要先考虑 Node 类型之间的加法,但本节中因为我们单独抽象了 Data,所以也要考虑他们之间的加法:" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:57 +msgid "" +"fn op_add(self : Data, v : Data) -> Data {\n" +" match (self, v) {\n" +" (Data(sum=a, len=len_a), Data(sum=b, len=len_b)) =>\n" +" Data(sum=a + b, len=len_a + len_b)\n" +" }\n" +"}\n" +"\n" +"fn op_add(self : Node, v : Node) -> Node {\n" +" match (self, v) {\n" +" (Node(data=l, ..), Node(data=r, ..)) =>\n" +" Node(data=l + r, tag=Nil, left=self, right=v)\n" +" (Node(_), Nil) => self\n" +" (Nil, Node(_)) => v\n" +" (Nil, Nil) => Nil\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:63 +msgid "" +"Here, we’ve ignored merging LazyTags for now and set the resulting tag to" +" `Nil` because once a node is reached, its parent’s LazyTag no longer " +"applies." +msgstr "" +"可以发现这里暂时还没有考虑 LazyTag 的合并,而是认为他们加法的结果得到的节点的 LazyTag 均为 " +"Nil,这是很好理解的,如果已经走到一个节点,那么它的父节点当然会是没有 LazyTag 的。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:65 +msgid "Now, we can implement the tree-building function:" +msgstr "接下来就可以写出建树的代码,这与上节非常相似:" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:67 +msgid "" +"fn build(data : ArrayView[Int]) -> Node {\n" +" if data.length() == 1 {\n" +" Node(data=Data(sum=data[0], len=1), tag=Nil, left=Nil, right=Nil)\n" +" } else {\n" +" let mid = (data.length() + 1) >> 1\n" +" build(data[0:mid]) + build(data[mid:])\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:73 +msgid "LazyTag and Range Modifications" +msgstr "LazyTag 与区间修改的实现" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:75 +msgid "" +"We define a node receiving a LazyTag as `apply`. The key logic lies in " +"here: the node receiving the LazyTag may not own a LazyTag, and if it did" +" own one, how do we merge them? And how do we compute the new value of " +"the node based on the LazyTag?" +msgstr "" +"我们把一个节点接受一个 LazyTag 的行为定义为 apply,容易发现其实真正的核心逻辑就在这里,当前接受上方 LazyTag " +"的节点身上不一定是否有 LazyTag,而如果有,又应该怎么合并?怎么根据 LazyTag 计算当前节点新的值?答案都在这个操作当中。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:77 +msgid "" +"A decent implementation is to define a new addition operation to merge " +"LazyTags, and define an `apply` function for Node to receive it." +msgstr "" +"一个很好的实现方法是我们对 LazyTag 再单独定义一套加法运算来实现他们的合并,然后为 Node 类型编写一个 apply 函数来接收一个 " +"LazyTag。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:80 +msgid "" +"fn op_add(self : LazyTag, v : LazyTag) -> LazyTag {\n" +" match (self, v) {\n" +" (Tag(a), Tag(b)) => Tag(a + b)\n" +" (Nil, t) | (t, Nil) => t\n" +" }\n" +"}\n" +"\n" +"fn apply(self : Node, v : LazyTag) -> Node {\n" +" match (self, v) {\n" +" (Node(data=Data(sum=a, len=length), tag~, left~, right~), Tag(v) as " +"new_tag) =>\n" +" Node(\n" +" data=Data(sum=a + v * length, len=length),\n" +" tag=tag + new_tag,\n" +" left~,\n" +" right~,\n" +" )\n" +" (_, Nil) => self\n" +" (Nil, _) => Nil\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:86 +msgid "" +"Here is the core part of this section: compute the correct node's value " +"with the segment's length and the value of LazyTag." +msgstr "这是我们这节课最核心的地方,根据当前区间长度和 LazyTag 的值计算出了当前节点的正确数值,这样我们就有了 LazyTag 的实现。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:88 +msgid "Then how do we implement range modifications?" +msgstr "怎么做到区间修改?" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:90 +msgid "" +"fn modify(\n" +" self : Node,\n" +" l : Int,\n" +" r : Int,\n" +" modify_l : Int,\n" +" modify_r : Int,\n" +" tag : LazyTag\n" +") -> Node {\n" +" if modify_l > r || l > modify_r {\n" +" self\n" +" } else if modify_l <= l && modify_r >= r {\n" +" self.apply(tag)\n" +" } else {\n" +" guard let Node(left~, right~, ..) = self\n" +" left.apply(tag) + right.apply(tag)\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:96 +msgid "" +"The logic is similar to the query function from the previous lesson, but " +"now each relevant node applies the necessary LazyTag for the " +"modification." +msgstr "逻辑实际上与上节课编写的 query 大差不差,只是每个地方都让对应的节点 apply 了我们需要修改的值(作为 LazyTag)。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:98 +msgid "" +"When we arrive here, we find that, even with the range modification, it's" +" still a persistent, or **Immutable** segment tree. The `modify` function" +" will return the recently created segment tree, without changing the " +"original one, and the semantics of recurring and merging represent this " +"vividly." +msgstr "不过写到这里我们可以发现,这棵线段树就算加入了区间修改之后居然还是一个可持久化的," +"或者说 Immutable 的线段树!我们的 modify 函数将会返回最新的那棵线段树,并没有对原来的线段树作任何改变," +"而我们的递归与合并语义非常明显的体现了这一点。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:100 +msgid "" +"This means that using these kind of implementations (ADT(enum), " +"recursion) for meeting immutable requirements is natural and elegant. " +"With the garbage collection mechanism of MoonBit, we don't need to use " +"pointers **explicitly** for some relationships in recurring ADT(enum), " +"and we don't need to take care of the memory." +msgstr "这说明在一些 Immutable 的需求上上采用这类写法(ADT(enum)、递归)是非常优雅而且自然的。" +"而且 MoonBit 语言存在垃圾回收机制 (GC),所以在无限递归的 ADT(enum) 当中不需要**显式地**用指针来指代一些关系," +"我们并不需要关心内存里面发生了什么。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:102 +msgid "" +"Readers unfamiliar with the functional programming languages may not " +"notice this, but we actually always profit from it. For example, writing " +"a `ConsList` in Rust using ADT(enum), we usually need:" +msgstr "很多对函数式编程语言不熟悉的读者可能使用 MoonBit 时没有太关注到这个问题," +"但其实我们一直从中受益,比如如果我们需要在 Rust 当中使用 ADT(enum) 来写一个 ConsList," +"我们往往需要:" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:104 +msgid "" +"enum List {\n" +" Cons(T, Box>),\n" +" Nil,\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:111 +msgid "But in MoonBit, we only need:" +msgstr "但在 MoonBit,我们只需要:" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:113 +msgid "" +"enum List[T] {\n" +" Cons(T, List[T])\n" +" Nil\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:120 +msgid "GC is really interesting!" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:122 +msgid "Queries" +msgstr "查询" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:124 +msgid "For queries, we need to remember to push the LazyTag downwards:" +msgstr "查询部分只要记得需要下推 LazyTag 即可。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:126 +msgid "" +"let empty_node : Node = Node(\n" +" data=Data(sum=0, len=0),\n" +" tag=Nil,\n" +" left=Nil,\n" +" right=Nil,\n" +")\n" +"\n" +"fn query(self : Node, l : Int, r : Int, query_l : Int, query_r : Int) -> " +"Node {\n" +" if query_l > r || l > query_r {\n" +" empty_node\n" +" } else if query_l <= l && query_r >= r {\n" +" self\n" +" } else {\n" +" guard let Node(tag~, left~, right~, ..) = self\n" +" let mid = (l + r) >> 1\n" +" left.apply(tag).query(l, mid, query_l, query_r) +\n" +" right.apply(tag).query(mid + 1, r, query_l, query_r)\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:134 +msgid "" +"With this, we have a segment tree that supports range modifications and " +"is much more functional!" +msgstr "到这里我们就完成了一棵支持区间修改的,更加完美的线段树!" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:136 +msgid "" +"In the next lesson, we’ll add multiplication support to the segment tree " +"and explore some use cases for immutable segment trees. Stay tuned!" +msgstr "接下来,在最后一节课当中我们将会学习如何给当前这棵线段树再加入一个 “乘法操作”," +"以及探索一些 Immutable 线段树的应用场景。感兴趣的读者可以提前自行了解。" + +#: ../../tutorial/example/segment-tree/segment-tree2.md:138 +msgid "" +"Full code is available [here](https://github.com/moonbitlang/moonbit-" +"docs/tree/main/next/sources/segment-tree/src/part2/top.mbt)." +msgstr "​本篇编程实践完整代码[见此处](https://github.com/moonbitlang/moonbit-" +"docs/tree/main/next/sources/segment-tree/src/part2/top.mbt)" + +#: ../../tutorial/example/sudoku/index.md:1 +msgid "Sudoku Solver" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:3 +msgid "" +"Sudoku is a logic-based puzzle game that originated in 1979. It was well-" +"suited for print media like newspapers, and even in the digital age, many" +" Sudoku game programs are available for computers and smartphones. " +"Despite the variety of entertainment options today, Sudoku enthusiasts " +"continue to form active communities (online forum such as: " +"[enjoysudoku](http://forum.enjoysudoku.com/)). This article will " +"demonstrate how to write a suitable program to solve Sudoku using " +"MoonBit. ![sudoku example](/imgs/sudoku.jpg)" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:3 +msgid "sudoku example" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:6 +msgid "Squares, Units, and Peers" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:8 +msgid "" +"The most common form of Sudoku is played on a 9x9 grid. We label the rows" +" from top to bottom as A-I, and the columns from left to right as 1-9. " +"This gives each square in the grid a coordinate, for example, the square " +"containing the number 0 in the grid below has the coordinate C3." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:10 +msgid "" +" 1 2 3 4 5 6 7 8 9\n" +"A . . . . . . . . .\n" +"B . . . . . . . . .\n" +"C . . 0 . . . . . .\n" +"D . . . . . . . . .\n" +"E . . . . . . . . .\n" +"F . . . . . . . . .\n" +"G . . . . . . . . .\n" +"H . . . . . . . . .\n" +"I . . . . . . . . .\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:23 +msgid "" +"This 9x9 grid has a total of 9 units, and each unit contains squares that" +" must have unique digits from 1 to 9. However, in the initial state of " +"the game, most squares do not contain any digits." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:25 +msgid "" +" 4 1 7 | 3 6 9 | 8 2 5\n" +" 6 3 2 | 1 5 8 | 9 4 7\n" +" 9 5 8 | 7 2 4 | 3 1 6\n" +"---------+---------+---------\n" +" 8 2 5 | 4 3 7 | 1 6 9\n" +" 7 9 1 | 5 8 6 | 4 3 2\n" +" 3 4 6 | 9 1 2 | 7 5 8\n" +"---------+---------+---------\n" +" 2 8 9 | 6 4 3 | 5 7 1\n" +" 5 7 3 | 2 9 1 | 6 8 4\n" +" 1 6 4 | 8 7 5 | 2 9 3\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:39 +msgid "" +"Beyond the units, another important concept is peers. A square's peers " +"include other squares in the same row, column, and unit. For example, the" +" peers of C2 include these squares:" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:41 +msgid "" +"\n" +" A2 | |\n" +" B2 | |\n" +" C2 | |\n" +"---------+---------+---------\n" +" D2 | |\n" +" E2 | |\n" +" F2 | |\n" +"---------+---------+---------\n" +" G2 | |\n" +" H2 | |\n" +" I2 | |\n" +"\n" +" | |\n" +" | |\n" +" C1 C2 C3| C4 C5 C6| C7 C8 C9\n" +"---------+---------+---------\n" +" | |\n" +" | |\n" +" | |\n" +"---------+---------+---------\n" +" | |\n" +" | |\n" +" | |\n" +"\n" +" A1 A2 A3| |\n" +" B1 B2 B3| |\n" +" C1 C2 C3| |\n" +"---------+---------+---------\n" +" | |\n" +" | |\n" +" | |\n" +"---------+---------+---------\n" +" | |\n" +" | |\n" +" | |\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:80 +msgid "No two squares that are peers can contain the same digit." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:82 +msgid "" +"We need a data type, SquareMap[T], to store the 81 squares and the " +"information associated with each square. This can be implemented using a " +"hashtable, but using an array would be more compact and simple. First, we" +" write a function to convert coordinates A1-I9 to indices 0-80:" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:84 +msgid "" +"// A1 => 0, A2 => 1\n" +"fn square_to_int(s : String) -> Int {\n" +" if in(s[0], 'A', 'I') && in(s[1], '1', '9') {\n" +" let row = s[0].to_int() - 65 // 'A' <=> 0\n" +" let col = s[1].to_int() - 49 // '1' <=> 0\n" +" return row * 9 + col\n" +" } else {\n" +" abort(\"square_to_int(): \\{s} is not a square\")\n" +" }\n" +"}\n" +"\n" +"// Helper function `in` checks if a character is between `lw` and `up`\n" +"fn in(this : Char, lw : Char, up : Char) -> Bool {\n" +" this >= lw && this <= up\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:102 +msgid "" +"Then we wrap the array and provide operations for creating, accessing, " +"assigning values to specific coordinates, and copying SquareMap[T]. By " +"overloading the op_get and op_set methods, we can write convenient code " +"like table[\"A2\"] and table[\"C3\"] = Nil." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:104 +msgid "" +"struct SquareMap[T] {\n" +" contents : Array[T]\n" +"}\n" +"\n" +"fn SquareMap::new[T](val : T) -> SquareMap[T] {\n" +" { contents : Array::make(81, val) }\n" +"}\n" +"\n" +"fn copy[T](self : SquareMap[T]) -> SquareMap[T] {\n" +" let arr = Array::make(81, self.contents[0])\n" +" let mut i = 0\n" +" while i < 81 {\n" +" arr[i] = self.contents[i]\n" +" i = i + 1\n" +" }\n" +" return { contents : arr }\n" +"}\n" +"\n" +"fn op_get[T](self : SquareMap[T], square : String) -> T {\n" +" self.contents[square_to_int(square)]\n" +"}\n" +"\n" +"fn op_set[T](self : SquareMap[T], square : String, x : T) -> Unit {\n" +" self.contents[square_to_int(square)] = x\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:132 +msgid "Next, we prepare some constants:" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:134 +msgid "" +"let rows = \"ABCDEFGHI\"\n" +"let cols = \"123456789\"\n" +"\n" +"// squares contains the coordinates of each square\n" +"let squares : List[String] = ......\n" +"\n" +"// units[coord] contains the other squares in the unit of the square at " +"coord\n" +"// for example:units[\"A3\"] => [C3, C2, C1, B3, B2, B1, A2, A1]\n" +"let units : SquareMap[List[String]] = ......\n" +"\n" +"// peers[coord] contains all the peers of the square at coord\n" +"// for example:peers[\"A3\"] => [A1, A2, A4, A5, A6, A7, A8, A9, B1, B2, " +"B3, C1, C2, C3, D3, E3, F3, G3, H3, I3]\n" +"let peers : SquareMap[List[String]] = ......\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:150 +msgid "" +"The process of constructing the units and peers tables is tedious, so it " +"will not be detailed here." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:152 +msgid "Preprocessing the Grid" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:154 +msgid "" +"We use a string to represent the initial Sudoku grid. Various formats are" +" acceptable; both `.` and `0` represent empty squares, and other " +"characters like spaces and newlines are ignored." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:156 +msgid "" +"\"4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......\"" +"\n" +"\n" +"\"\n" +"400000805\n" +"030000000\n" +"000700000\n" +"020000060\n" +"000080400\n" +"000010000\n" +"000603070\n" +"500200000\n" +"104000000\"\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:171 +msgid "" +"For now, let's not consider game rules too much. If we only consider the " +"digits that can be filled in each square, then 1-9 are all possible. " +"Therefore, we initially set the content of all squares to `['1', '2', " +"'3', '4', '5', '6', '7', '8', '9']` (a List)." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:173 +msgid "" +"fn parseGrid(s : String) -> SquareMap[List[Char]] {\n" +" let digits = cols.to_list()\n" +" let values : SquareMap[List[Char]] = SquareMap::new(digits)\n" +" ......\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:181 +msgid "" +"Next, we need to assign values to the squares with known digits from the " +"input. This process can be implemented with the function `assign(values, " +"key, val)`, where `key` is a string like `A6` and `val` is a character. " +"It is easy to write such code." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:183 +msgid "" +"fn assign(values : SquareMap[List[Char]], key : String, val : Char) {\n" +" values[key] = Cons(val, Nil)\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:189 +msgid "Let's run it and see" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:191 +msgid "" +"\"4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......\"" +"\n" +"\n" +"// Using parseGrid and printGrid functions, skipping implementation " +"details for simplicity\n" +"\n" +" 4 123456789 123456789 | 123456789 123456789 123456789 | 8" +" 123456789 5\n" +" 123456789 3 123456789 | 123456789 123456789 123456789 | " +"123456789 123456789 123456789\n" +" 123456789 123456789 123456789 | 7 123456789 123456789 | " +"123456789 123456789 123456789\n" +"---------------------------------+---------------------------------+---------------------------------" +"\n" +" 123456789 2 123456789 | 123456789 123456789 123456789 | " +"123456789 6 123456789\n" +" 123456789 123456789 123456789 | 123456789 8 123456789 | 4" +" 123456789 123456789\n" +" 123456789 123456789 123456789 | 123456789 1 123456789 | " +"123456789 123456789 123456789\n" +"---------------------------------+---------------------------------+---------------------------------" +"\n" +" 123456789 123456789 123456789 | 6 123456789 3 | " +"123456789 7 123456789\n" +" 5 123456789 123456789 | 2 123456789 123456789 | " +"123456789 123456789 123456789\n" +" 1 123456789 4 | 123456789 123456789 123456789 | " +"123456789 123456789 123456789\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:209 +msgid "This implementation is simple and precise, but we can do more." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:211 +msgid "" +"Now, we can reintroduce the rules that we set aside earlier. However, the" +" rules themselves do not tell us what to do. We need heuristic strategies" +" to gain insights from the rules, similar to solving Sudoku with pen and " +"paper. Let's start with the elimination method:" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:213 +msgid "" +"**Strategy 1**: If a square `key` is assigned a value `val`, then its " +"peers (peers[key]) should not contain `val` in their lists of possible " +"values, as this would violate the rule that no two squares in the same " +"unit, row, or column can have the same digit." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:215 +msgid "" +"**Strategy 2**: If there is only one square in a unit that can hold a " +"specific digit (possibly happen after applying the above rule several " +"times), then that digit should be assigned to that square." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:217 +msgid "" +"We adjust the code by defining an `eliminate` function, which removes a " +"digit from the possible values of a square. After performing the " +"elimination task, it applies the above strategies to `key` and `val` to " +"attempt further eliminations. Note that it includes a boolean return " +"value to handle possible contradictions. If the list of possible values " +"for a square becomes empty, something went wrong, and we return `false`." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:219 +msgid "" +"fn eliminate(values : SquareMap[List[Char]], key : String, val : Char) ->" +" Bool {\n" +" if not(exist(values[key], fn (v) { v == val })) {\n" +" return true\n" +" }\n" +" values[key] = values[key].remove(val)\n" +" // If `key` has only one possible value left, remove this value from " +"its peers\n" +" match single(values[key]) {\n" +" Err(b) => {\n" +" if not(b) {\n" +" return false\n" +" }\n" +" }\n" +" Ok(val) => {\n" +" let mut result = true\n" +" peers[key].iter(fn (key) {\n" +" result = result && eliminate(values, key, val)\n" +" })\n" +" if not(result) {\n" +" return false\n" +" }\n" +" }\n" +" }\n" +" // If there is only one square in the unit of `key` that can hold " +"`val`, assign `val` to that square\n" +" let unit = units[key]\n" +" let places = unit.filter(fn (sq) {\n" +" exist(values[sq], fn (v) { v == val })\n" +" })\n" +" match single(places) {\n" +" Err(b) => {\n" +" return b\n" +" }\n" +" Ok(key) => {\n" +" return assign(values, key, val)\n" +" }\n" +" }\n" +"}\n" +"\n" +"\n" +"// Return `Err(false)` if the list is empty\n" +"// Return `Ok(x)` if the list contains only `[x]`\n" +"// Return `Err(true)` if the list contains `[x1, x2, ......]`\n" +"fn single[T](this : List[T]) -> Result[T, Bool] {\n" +" match this {\n" +" Nil => Err(false)\n" +" Cons(x, Nil) => Ok(x)\n" +" _ => Err(true)\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:270 +msgid "" +"Next, we define `assign(values, key, val)` to remove all values except " +"`val` from the possible values of `key`." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:272 +msgid "" +"fn assign(values : SquareMap[List[Char]], key : String, val : Char) -> " +"Bool {\n" +" let other_values = values[key].remove(val)\n" +" let mut result = true\n" +" other_values.iter(fn (val) {\n" +" result = result && eliminate(values, key, val)\n" +" })\n" +" return result\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:283 +msgid "" +"These two functions apply heuristic strategies to each square they " +"access. A successful heuristic application introduces new squares to " +"consider, allowing these strategies to propagate widely across the grid. " +"This is key to quickly eliminating invalid options." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:285 +msgid "Let's try the example again" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:287 +msgid "" +"\"4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......\"" +"\n" +"\n" +" 4 1679 12679 | 139 2369 269 | 8 1239" +" 5\n" +" 26789 3 1256789 | 14589 24569 245689 | 12679 1249" +" 124679\n" +" 2689 15689 125689 | 7 234569 245689 | 12369 12349" +" 123469\n" +"---------------------------+---------------------------+---------------------------" +"\n" +" 3789 2 15789 | 3459 34579 4579 | 13579 6" +" 13789\n" +" 3679 15679 15679 | 359 8 25679 | 4 12359" +" 12379\n" +" 36789 4 56789 | 359 1 25679 | 23579 23589" +" 23789\n" +"---------------------------+---------------------------+---------------------------" +"\n" +" 289 89 289 | 6 459 3 | 1259 7" +" 12489\n" +" 5 6789 3 | 2 479 1 | 69 489" +" 4689\n" +" 1 6789 4 | 589 579 5789 | 23569 23589" +" 23689\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:303 +msgid "" +"A significant improvement! In fact, this preprocessing can already solve " +"some simple Sudoku puzzles." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:305 +msgid "" +"\"003020600900305001001806400008102900700000008006708200002609500800203009005010300\"" +"\n" +"\n" +" 4 8 3 | 9 2 1 | 6 5 7\n" +" 9 6 7 | 3 4 5 | 8 2 1\n" +" 2 5 1 | 8 7 6 | 4 9 3\n" +"---------+---------+---------\n" +" 5 4 8 | 1 3 2 | 9 7 6\n" +" 7 2 9 | 5 6 4 | 1 3 8\n" +" 1 3 6 | 7 9 8 | 2 4 5\n" +"---------+---------+---------\n" +" 3 7 2 | 6 8 9 | 5 1 4\n" +" 8 1 4 | 2 5 3 | 7 6 9\n" +" 6 9 5 | 4 1 7 | 3 8 2\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:321 +msgid "" +"If you are interested in artificial intelligence, you might recognize " +"this as a Constraint Satisfaction Problem (CSP), and `assign` and " +"`eliminate` are specialized arc consistency algorithms. For more on this " +"topic, refer to Chapter 6 of _Artificial Intelligence: A Modern " +"Approach_." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:323 +msgid "Search" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:325 +msgid "" +"After preprocessing, we can boldly use brute-force enumeration to search " +"for all feasible combinations. However, we can still use the heuristic " +"strategies during the search process. When trying to assign a value to a " +"square, we still use `assign`, which allows us to apply previous " +"optimizations to eliminate many invalid branches during the search." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:327 +msgid "" +"Another point to note is that conflicts may arise during the search (when" +" a square's possible values are exhausted). Since mutable structures make" +" backtracking troublesome, we directly copy values each time we assign a " +"value." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:329 +msgid "" +"fn search(values : SquareMap[List[Char]]) -> " +"Option[SquareMap[List[Char]]] {\n" +" if values.contains(fn (digits){ not(isSingleton(digits)) }) {\n" +" // // Find the square with the smallest number of possible values " +"greater than 1, and start the search from this square\n" +" // This is just a heuristic strategy; you can try finding a smarter " +"and more effective one\n" +" let mut minsq = \"\"\n" +" let mut n = 10\n" +" squares.iter(fn (sq) {\n" +" let len = values[sq].length()\n" +" if len > 1 {\n" +" if len < n {\n" +" n = len\n" +" minsq = sq\n" +" }\n" +" }\n" +" })\n" +" // Iterate through assignments and stop if a successful search is " +"found\n" +" loop values[minsq] {\n" +" Nil => None\n" +" Cons(digit, rest) => {\n" +" let another = values.copy()\n" +" if assign(another, minsq, digit){\n" +" match search(another) {\n" +" None => continue rest\n" +" Some(_) as result => result\n" +" }\n" +" } else {\n" +" continue rest\n" +" }\n" +" }\n" +" }\n" +" } else {\n" +" return Some(values)\n" +" }\n" +"}\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:366 +msgid "" +"Let's run the same example again (the example is actually taken from " +"[magictour](http://magictour.free.fr/top95), a list of difficult Sudoku " +"puzzles, which is not easy for humans)" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:368 +msgid "" +"> " +"solve(\"4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......\")" +"\n" +"\n" +" 4 1 7 | 3 6 9 | 8 2 5\n" +" 6 3 2 | 1 5 8 | 9 4 7\n" +" 9 5 8 | 7 2 4 | 3 1 6\n" +"---------+---------+---------\n" +" 8 2 5 | 4 3 7 | 1 6 9\n" +" 7 9 1 | 5 8 6 | 4 3 2\n" +" 3 4 6 | 9 1 2 | 7 5 8\n" +"---------+---------+---------\n" +" 2 8 9 | 6 4 3 | 5 7 1\n" +" 5 7 3 | 2 9 1 | 6 8 4\n" +" 1 6 4 | 8 7 5 | 2 9 3\n" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:384 +msgid "" +"Running on [MoonBit online IDE](https://try.moonbitlang.com/), It takes " +"only about 0.11 seconds to solve this Sudoku!" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:386 +msgid "" +"Complete code here: " +"[try.moonbitlang.com/#6806c2fe](https://try.moonbitlang.com/#6806c2fe)" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:390 +msgid "" +"The purpose of games is to relieve boredom and bring joy. If playing a " +"game becomes more anxiety-inducing than exciting, it might go against the" +" game designer's original intent. The article demonstrated that simple " +"elimination methods and brute-force search can quickly solve some Sudoku " +"puzzles. This does not mean that Sudoku is not worth playing; rather, it " +"reveals that one should not be overly concerned with an unsolvable Sudoku" +" puzzle." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:392 +msgid "Let's play with MoonBit with ease!" +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:394 +msgid "" +"Visit MoonBit [Gallery](https://www.moonbitlang.com/gallery/sudoku/) to " +"play with the Sudoku solver written in MoonBit. Click [this " +"link](https://github.com/myfreess/sudoku) to view the full source code." +msgstr "" + +#: ../../tutorial/example/sudoku/index.md:396 +msgid "" +"This tutorial references Norvig's blog: " +"[http://norvig.com/sudoku.html](http://norvig.com/sudoku.html)" +msgstr "" + #: ../../tutorial/index.md:1 msgid "Tutorial" msgstr "教程" @@ -36,6 +5748,10 @@ msgstr "以下是一些可能帮助您学习编程语言的教程:" msgid "[Tour for Beginners](./tour.md)" msgstr "[新手之旅](./tour.md)" +#: ../../tutorial/index.md:6 +msgid "[Examples](./example/index.md)" +msgstr "" + #: ../../tutorial/tour.md:1 msgid "A Tour of MoonBit for Beginners" msgstr "MoonBit:新手之旅" @@ -803,5 +6519,4 @@ msgid "" msgstr "" "到目前为止,我们已经了解了 MoonBit 的基本特性和一些不那么简单的特性,但 MoonBit 是一种功能丰富的多范式编程语言。在确保您对 " "MoonBit 的基础知识感到满意后,我们建议您查看一些[有趣的示例](https://github.com/moonbitlang" -"/moonbit-docs/tree/main/legacy/examples) 以更好地掌握 MoonBit。" - +"/moonbit-docs/tree/main/legacy/examples) 以更好地掌握 MoonBit。" \ No newline at end of file diff --git a/next/tutorial/example/segment-tree/index.md b/next/tutorial/example/segment-tree/index.md index 56920e38..87f95c05 100644 --- a/next/tutorial/example/segment-tree/index.md +++ b/next/tutorial/example/segment-tree/index.md @@ -13,5 +13,6 @@ In this section, we will learn the basic principles of Segment Trees and how to ```{toctree} :maxdepth: 2 :caption: Contents: +:hidden: segment-tree segment-tree2 \ No newline at end of file diff --git a/next/tutorial/example/segment-tree/segment-tree.md b/next/tutorial/example/segment-tree/segment-tree.md index aa9e1a5d..e4948b9a 100644 --- a/next/tutorial/example/segment-tree/segment-tree.md +++ b/next/tutorial/example/segment-tree/segment-tree.md @@ -23,6 +23,7 @@ Specifically: - First, we check the relationship between the ranges 1-7 and 1-6. The latter is a subset of the former, so the data from 1-7 cannot be used in our calculation, and we proceed to explore its two child nodes. - Next, we check the relationship between 1-3 and 1-6. The former is a subset of the latter, contributing to our result. - Then, we examine the relationship between 4-7 and 1-6, which overlap, requiring us to explore both child nodes further. +- And we examine the relationship between 4-5 and 1-6, just as the third step. - We repeat this process... Based on binary decomposition, we will query at most Log N segments for any range of length N, ensuring guaranteed complexity. @@ -43,8 +44,6 @@ We use a classic approach to represent the Segment Tree: Here, `Nil` represents an empty tree, while a `Node` contains the stored data (of type Int) and its left and right children. -Additionally, we derive the Show Trait for easy debugging by outputting the tree structure when needed. - ### Building the Tree Building the tree refers to the process of abstracting a linear sequence into a Segment Tree, commonly referred to as `build`. diff --git a/next/tutorial/example/segment-tree/segment-tree2.md b/next/tutorial/example/segment-tree/segment-tree2.md index ba80f0f9..3b5ea989 100644 --- a/next/tutorial/example/segment-tree/segment-tree2.md +++ b/next/tutorial/example/segment-tree/segment-tree2.md @@ -28,15 +28,17 @@ Here’s where the **Lazy** aspect of LazyTag comes into play. We define that when querying a node with a tag, the tag is distributed to its child nodes. These child nodes inherit the tag and compute their values based on their length. The following diagram shows the propagation of the tag downward when querying [4, 6]. -This "lazy propagation" ensures that each modification is completed in \(O(\log N)\), while ensuring correct query results. +This "lazy propagation" ensures that each modification is completed in O(log N), while ensuring correct query results. -**Note:** Some may wonder about overlapping tags. However, additive tags like these merge seamlessly without affecting the total sum of a node. +```{note} +Some may wonder about overlapping tags. However, additive tags like these merge seamlessly without affecting the total sum of a node. +``` Let’s dive into the code! ## Implementation -### Basic Definitions +### Basic Definition In the previous code, we defined the segment tree using `enum`. However, none of the elements were clearly named, which was manageable when the data size was small. Now, we need to add **Tag** and **Length** attributes, so it makes sense to use labeled arguments in the `enum` definition: @@ -48,7 +50,7 @@ In the previous code, we defined the segment tree using `enum`. However, none of This allows for clearer initialization and pattern matching, making the code easier to follow. We've also abstracted the `Data` type, adding a `len` attribute to represent the length of the current range, which is useful for calculating the node's value. -### Tree Construction +### Building the Tree Similar to the last lesson, before building the tree, we need to define the addition operations between `Node` types. However, since we’ve abstracted `Data`, we must account for its addition too: @@ -70,7 +72,9 @@ Now, we can implement the tree-building function: ### LazyTag and Range Modifications -A node receiving a LazyTag is handled by the `apply` function. The key logic here is how the tag is merged and how the value is computed based on the node’s length: +We define a node receiving a LazyTag as `apply`. The key logic lies in here: the node receiving the LazyTag may not own a LazyTag, and if it did own one, how do we merge them? And how do we compute the new value of the node based on the LazyTag? + +A decent implementation is to define a new addition operation to merge LazyTags, and define an `apply` function for Node to receive it. ```{literalinclude} /sources/segment-tree/src/part2/top.mbt @@ -79,10 +83,9 @@ A node receiving a LazyTag is handled by the `apply` function. The key logic her :end-before: end lazytag definition ``` -This code allows a node to compute its value based on its range length and the applied LazyTag. It also merges existing tags correctly. - -Next, we implement range modifications: +Here is the core part of this section: compute the correct node's value with the segment's length and the value of LazyTag. +Then how do we implement range modifications? ```{literalinclude} /sources/segment-tree/src/part2/top.mbt :language: moonbit @@ -92,7 +95,29 @@ Next, we implement range modifications: The logic is similar to the query function from the previous lesson, but now each relevant node applies the necessary LazyTag for the modification. -Interestingly, even with range modifications, this segment tree remains persistent (immutable). The `modify` function returns a new tree without altering the original, reflecting the recursive and functional nature of the code. Since MoonBit uses garbage collection, there’s no need for explicit pointers, unlike in Rust. +When we arrive here, we find that, even with the range modification, it's still a persistent, or **Immutable** segment tree. The `modify` function will return the recently created segment tree, without changing the original one, and the semantics of recurring and merging represent this vividly. + +This means that using these kind of implementations (ADT(enum), recursion) for meeting immutable requirements is natural and elegant. With the garbage collection mechanism of MoonBit, we don't need to use pointers **explicitly** for some relationships in recurring ADT(enum), and we don't need to take care of the memory. + +Readers unfamiliar with the functional programming languages may not notice this, but we actually always profit from it. For example, writing a `ConsList` in Rust using ADT(enum), we usually need: + +```rust +enum List { + Cons(T, Box>), + Nil, +} +``` + +But in MoonBit, we only need: + +```moonbit +enum List[T] { + Cons(T, List[T]) + Nil +} +``` + +GC is really interesting! ### Queries