From 0f524feda07dde9463a37233400a34e95c15a391 Mon Sep 17 00:00:00 2001 From: kleidemos Date: Tue, 6 Feb 2018 00:05:32 +0500 Subject: [PATCH 01/48] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4=20"Thinking?= =?UTF-8?q?=20functionally"=20=D0=B2=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA?= =?UTF-8?q?=D1=82.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- posts/currying.md | 198 ++++++++--- posts/defining-functions.md | 361 +++++++++++++------ posts/function-composition.md | 176 +++++++--- posts/function-signatures.md | 88 +++-- posts/function-values-and-simple-values.md | 165 ++++++--- posts/how-types-work-with-functions.md | 332 +++++++++++++----- posts/mathematical-functions.md | 183 +++++++--- posts/organizing-functions.md | 355 +++++++++++++------ posts/partial-application.md | 152 +++++--- posts/stack-based-calculator.md | 389 +++++++++++++++------ posts/thinking-functionally-intro.md | 43 ++- posts/type-extensions.md | 344 ++++++++++++------ series/thinking-functionally.md | 43 ++- 14 files changed, 2053 insertions(+), 779 deletions(-) diff --git a/.gitignore b/.gitignore index 1a366fb..cba74db 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ _book # eBook build output *.epub *.mobi -*.pdf \ No newline at end of file +*.pdf +/.vs diff --git a/posts/currying.md b/posts/currying.md index cf330fe..debf1b5 100644 --- a/posts/currying.md +++ b/posts/currying.md @@ -8,11 +8,17 @@ seriesOrder: 5 categories: [Currying] --- -After that little digression on basic types, we can turn back to functions again, and in particular the puzzle we mentioned earlier: if a mathematical function can only have one parameter, then how is it possible that an F# function can have more than one? +> After that little digression on basic types, we can turn back to functions again, and in particular the puzzle we mentioned earlier: if a mathematical function can only have one parameter, then how is it possible that an F# function can have more than one? -The answer is quite simple: a function with multiple parameters is rewritten as a series of new functions, each with only one parameter. And this is done automatically by the compiler for you. It is called "**currying**", after Haskell Curry, a mathematician who was an important influence on the development of functional programming. +После небольшого экскурса по базовым типам, можно вернуться к функциям снова, и в частности к ранее упомянутой загадке: если математическая функция может иметь лишь один параметр, то как в F# может существовать функция имеющая большее число параметров? -To see how this works in practice, let's use a very basic example that prints two numbers: +> The answer is quite simple: a function with multiple parameters is rewritten as a series of new functions, each with only one parameter. And this is done automatically by the compiler for you. It is called "**currying**", after Haskell Curry, a mathematician who was an important influence on the development of functional programming. + +Ответ довольно прост: функция с множестов параметров переписывается как серия новых функций, каждая имеющая только один параметр. Данная операция произвоидтся компилятором автоматически. Это называется "**каррированием**" (_currying_), в честь Haskell Curry, математика, который оказал очень существенное влияние на разработку функционального программирования. + +> To see how this works in practice, let's use a very basic example that prints two numbers: + +Чтобы увидеть как это работает на практике, воспользуемся очень базовым примером который выводит два числа: ```fsharp //normal version @@ -20,7 +26,9 @@ let printTwoParameters x y = printfn "x=%i y=%i" x y ``` -Internally, the compiler rewrites it as something more like: +> Internally, the compiler rewrites it as something more like: + +Внутри компилятор переписывает его приблизительно в такой форме: ```fsharp //explicitly curried version @@ -30,14 +38,23 @@ let printTwoParameters x = // only one parameter! subFunction // return the subfunction ``` -Let's examine this in more detail: +> Let's examine this in more detail: + +Рассмотрм процесс более детально: + +> 1. Construct the function called "`printTwoParameters`" but with only *one* parameter: "x" +> 2. Inside that, construct a subfunction that has only *one* parameter: "y". Note that this inner function uses the "x" parameter but x is not passed to it explicitly as a parameter. The "x" parameter is in scope, so the inner function can see it and use it without needing it to be passed in. +> 3. Finally, return the newly created subfunction. +> 4. This returned function is then later used against "y". The "x" parameter is baked into it, so the returned function only needs the y param to finish off the function logic. -1. Construct the function called "`printTwoParameters`" but with only *one* parameter: "x" -2. Inside that, construct a subfunction that has only *one* parameter: "y". Note that this inner function uses the "x" parameter but x is not passed to it explicitly as a parameter. The "x" parameter is in scope, so the inner function can see it and use it without needing it to be passed in. -3. Finally, return the newly created subfunction. -4. This returned function is then later used against "y". The "x" parameter is baked into it, so the returned function only needs the y param to finish off the function logic. +1. Объявить функцию с названием "`printTwoParameters`", но имеющую лишь _один_ параметр: "x". +2. Внутри создать подфункцию, которая также имеет лишь _один_ параметр: "y". Заметим, что внутренняя функция использует параметр "x", но x не передается внутрь нее как параметр. "x" находится в такой области видимости, что вложенная функция может видеть его и использовать без необходимости в его передаче. +3. Наконец вернуть новую созданную подфункцию. +4. Возвращенная функция затем применяется в отношении "y". "x" замыкается в ней, так что возвращаемая функция нуждается в параметре y чтобы закончить свою логику. -By rewriting it this way, the compiler has ensured that every function has only one parameter, as required. So when you use "`printTwoParameters`", you might think that you are using a two parameter function, but it is actually only a one parameter function! You can see for yourself by passing only one argument instead of two: +> By rewriting it this way, the compiler has ensured that every function has only one parameter, as required. So when you use "`printTwoParameters`", you might think that you are using a two parameter function, but it is actually only a one parameter function! You can see for yourself by passing only one argument instead of two: + +Переписав ее таким образом, комплиятор гарантирует, что каждая функция имеет только один параметр, как требуется. _При использовании "`printTwoParameters`", можно подумать, что используется функция с двумя параметрами, но на самом деле используется только функции с одним параметром._ В этом можно убедиться, передав ей лишь один аргумент вместо двух: ```fsharp // eval with one argument @@ -47,15 +64,25 @@ printTwoParameters 1 val it : (int -> unit) = ``` -If you evaluate it with one argument, you don't get an error, you get back a function. +> If you evaluate it with one argument, you don't get an error, you get back a function. + +Если вычислить ее с одним аргументом, мы не получим ошибку, вернется функция. + +> So what you are really doing when you call `printTwoParameters` with two arguments is: + +Что на самом деле происходит, когда вызывается `printTwoParameters` с двумя аргументами: + +> * You call `printTwoParameters` with the first argument (x) +> * `printTwoParameters` returns a new function that has "x" baked into it. +> * You then call the new function with the second argument (y) -So what you are really doing when you call `printTwoParameters` with two arguments is: +* Вызывается `printTwoParameters` с первым аргуметом (x) +* `printTwoParameters` возвращает новую функцию, в которой запечатан "x". +* Затем вызывается новая функция со вторым аргуметом (y) -* You call `printTwoParameters` with the first argument (x) -* `printTwoParameters` returns a new function that has "x" baked into it. -* You then call the new function with the second argument (y) +> Here is an example of the step by step version, and then the normal version again. -Here is an example of the step by step version, and then the normal version again. +Вот пример пошаговой и нормальной (снова) версий. ```fsharp // step by step version @@ -72,7 +99,9 @@ let result = (printTwoParameters x) y let result = printTwoParameters x y ``` -Here is another example: +> Here is another example: + +Вот другой пример: ```fsharp //normal version @@ -96,13 +125,21 @@ let result = intermediateFn y let result = addTwoParameters x y ``` -Again, the "two parameter function" is actually a one parameter function that returns an intermediate function. +> Again, the "two parameter function" is actually a one parameter function that returns an intermediate function. + +Опять же, "функция с двумя параметрами" на самом деле это функция с одним параметром, которая возвращает промежуточную функцию. + +> But wait a minute -- what about the "`+`" operation itself? It's a binary operation that must take two parameters, surely? No, it is curried like every other function. There is a function called "`+`" that takes one parameter and returns a new intermediate function, exactly like `addTwoParameters` above. -But wait a minute -- what about the "`+`" operation itself? It's a binary operation that must take two parameters, surely? No, it is curried like every other function. There is a function called "`+`" that takes one parameter and returns a new intermediate function, exactly like `addTwoParameters` above. +Но подождите, а что с "`+`"? Это ведь бинарная операция, которая должна принимать два параметра? Нет, она тоже каррируется как и другие функции. Функция заваемая "`+`" берет один параметр и возвращает новую промежуточную функцию, в точности как `addTwoParameters` выше. -When we write the statement `x+y`, the compiler reorders the code to remove the infix and turns it into `(+) x y`, which is the function named `+` called with two parameters. Note that the function named "+" needs to have parentheses around it to indicate that it is being used as a normal function name rather than as an infix operator. +> When we write the statement `x+y`, the compiler reorders the code to remove the infix and turns it into `(+) x y`, which is the function named `+` called with two parameters. Note that the function named "+" needs to have parentheses around it to indicate that it is being used as a normal function name rather than as an infix operator. -Finally, the two parameter function named `+` is treated as any other two parameter function would be. +Когда мы пишем выражение `x+y`, компилятор _переупорядочивает_ код, чтобы удалить инфикс и превратить его в `(+) x y`, _что является функцией с именем `+` принимающей два параметра._ Заметим, что функция с именем "+" должна быть в скобках, чтобы она используется как обычная функция, а не как инфиксный оператор. + +> Finally, the two parameter function named `+` is treated as any other two parameter function would be. + +Наконец, функция с двумя параметрами называемая `+` обрабатывается как любая другая функция с двумя параметрами. ```fsharp // using plus as a single value function @@ -118,7 +155,9 @@ let result = (+) x y let result = x + y ``` -And yes, this works for all other operators and built in functions like printf. +> And yes, this works for all other operators and built in functions like printf. + +И да, это работает на все другие операторы и встроенные функции, такие как `printf`. ```fsharp // normal version of multiply @@ -136,47 +175,67 @@ let intermediateFn = printfn "x=%i y=%i" 3 // "3" is baked in let result = intermediateFn 5 ``` -## Signatures of curried functions ## +## Signatures of curried functions | Сигнатуры каррированных функций ## -Now that we know how curried functions work, what should we expect their signatures to look like? +> Now that we know how curried functions work, what should we expect their signatures to look like? -Going back to the first example, "`printTwoParameters`", we saw that it took one argument and returned an intermediate function. The intermediate function also took one argument and returned nothing (that is, unit). So the intermediate function has type `int->unit`. In other words, the domain of `printTwoParameters` is `int` and the range is `int->unit`. Putting this together we see that the final signature is: +Теперь, когда мы знаем, как работают каррированные функции, что можно ожидать от их сигнатур? + +> Going back to the first example, "`printTwoParameters`", we saw that it took one argument and returned an intermediate function. The intermediate function also took one argument and returned nothing (that is, unit). So the intermediate function has type `int->unit`. In other words, the domain of `printTwoParameters` is `int` and the range is `int->unit`. Putting this together we see that the final signature is: + +Возращаясь к первому примеру, "`printTwoParameter`", мы видели, что функция принимала один рагумент и возвращала промежуточную функцию. Промежуточная функция также принимала один аргумент и ничего не возврашала (т.е. `unit`). Поэтому промежуточная функция имела тип `int->unit`. Другими словами, domain `printTwoParameters` - это `int`, а range - `int->unit`. Собрав все это воедино мы увидим конечную сигнатуру: ```fsharp val printTwoParameters : int -> (int -> unit) ``` -If you evaluate the explicitly curried implementation, you will see the parentheses in the signature, as written above, but if you evaluate the normal implementation, which is implicitly curried, the parentheses are left off, like so: +> If you evaluate the explicitly curried implementation, you will see the parentheses in the signature, as written above, but if you evaluate the normal implementation, which is implicitly curried, the parentheses are left off, like so: + +Если вычислить явно каррированную реализацию, можно увидеть скобки в сигнатуре, как написано выше, но если вычислить нормальную имплементацию, которая неявно каррированна, скобок не будет: ```fsharp val printTwoParameters : int -> int -> unit ``` -The parentheses are optional. If you are trying to make sense of function signatures it might be helpful to add them back in mentally. +> The parentheses are optional. If you are trying to make sense of function signatures it might be helpful to add them back in mentally. + +Скобки опциональны. Но их можно представлять в уме, чтобы упростить восприятие сигнатур функций. + +> At this point you might be wondering, what is the difference between a function that returns an intermediate function and a regular two parameter function? -At this point you might be wondering, what is the difference between a function that returns an intermediate function and a regular two parameter function? +В этом месте впасть в недоумение, из-за наличия разницы между между функцией, которая возвращает промежуточную функцию и обычной функцией с двумя параметрами? -Here's a one parameter function that returns a function: +> Here's a one parameter function that returns a function: + +Функция с одним параметром возвращающая другую функцию: ```fsharp let add1Param x = (+) x // signature is = int -> (int -> int) ``` -Here's a two parameter function that returns a simple value: +> Here's a two parameter function that returns a simple value: + +А вот функция с двумя параметрами которая возвращает простое значение: ```fsharp let add2Params x y = (+) x y // signature is = int -> int -> int ``` -The signatures are slightly different, but in practical terms, there *is* no difference*, only that the second function is automatically curried for you. +> The signatures are slightly different, but in practical terms, there *is* no difference*, only that the second function is automatically curried for you. + +Их сигнатуры немного отличаются, но в практическом плане между ними нет особой разницы, за исключением того факта, что вторая функция автоматически каррирована. -## Functions with more than two parameters ## +## Functions with more than two parameters | Функции с большим количеством параметров ## -How does currying work for functions with more than two parameters? Exactly the same way: for each parameter except the last one, the function returns an intermediate function with the previous parameters baked in. +> How does currying work for functions with more than two parameters? Exactly the same way: for each parameter except the last one, the function returns an intermediate function with the previous parameters baked in. -Consider this contrived example. I have explicitly specified the types of the parameters, but the function itself does nothing. +Как каррирование работает для функций с количеством параметров большим двух? Точно также: для каждого параметра кроме последнего функция возвращает промежуточную функцию замкающую предыдщуий параметр. + +> Consider this contrived example. I have explicitly specified the types of the parameters, but the function itself does nothing. + +Рассмотрим этот хитрый пример. У меня явно объявлены типы параметров, но функция ничего не делает. ```fsharp let multiParamFn (p1:int)(p2:bool)(p3:string)(p4:float)= @@ -194,13 +253,17 @@ let intermediateFn3 = intermediateFn2 "hello" let finalResult = intermediateFn3 3.141 ``` -The signature of the overall function is: +> The signature of the overall function is: + +Сигнатура полной функции: ```fsharp val multiParamFn : int -> bool -> string -> float -> unit ``` -and the signatures of the intermediate functions are: +> and the signatures of the intermediate functions are: + +и сигнатуры промежуточных функций: ```fsharp val intermediateFn1 : (bool -> string -> float -> unit) @@ -209,7 +272,9 @@ val intermediateFn3 : (float -> unit) val finalResult : unit = () ``` -A function signature can tell you how many parameters the function takes: just count the number of arrows outside of parentheses. If the function takes or returns other function parameters, there will be other arrows in parentheses, but these can be ignored. Here are some examples: +> A function signature can tell you how many parameters the function takes: just count the number of arrows outside of parentheses. If the function takes or returns other function parameters, there will be other arrows in parentheses, but these can be ignored. Here are some examples: + +Сигнатура функции может говорить о том, как много параметров принимает функция: достаточно подсчитать число стрелок вне скобок. Если функция принимает или возвращает другую функцию, будут еще стрелки, но они будут в скобках и их можно будет проигнорировать. Вот некоторые примеры: ```fsharp int->int->int // two int parameters and returns an int @@ -229,27 +294,37 @@ int->string->bool->unit // three params (int,string,bool) ``` -## Issues with multiple parameters ## +## Issues with multiple parameters | _Вопросы_ с несколькими параметрами ## + +> The logic behind currying can produce some unexpected results until you understand it. Remember that you will not get an error if you evaluate a function with fewer arguments than it is expecting. Instead you will get back a partially applied function. If you then go on to use this partially applied function in a context where you expect a value, you will get obscure error messages from the compiler. -The logic behind currying can produce some unexpected results until you understand it. Remember that you will not get an error if you evaluate a function with fewer arguments than it is expecting. Instead you will get back a partially applied function. If you then go on to use this partially applied function in a context where you expect a value, you will get obscure error messages from the compiler. +Логика за каррированием приводит к некоторым неожиданным результатам, пока не придет понимание. Помните, что вы не получите ошибку, если запустите функцию с меньшим количеством аргументов, чем ожидается. Вместо этого вы получите частично примененную функцию. Если затем вы воспользуетесь частично примененной функцией в контексте, где ожидается значение, можно получить малопонятную ошибку от компилятора. -Here's an innocuous looking function: +> Here's an innocuous looking function: + +С виду безобидная функция: ```fsharp // create a function let printHello() = printfn "hello" ``` -What would you expect to happen when we call it as shown below? Will it print "hello" to the console? Try to guess before evaluating it, and here's a hint: be sure to take a look at the function signature. +> What would you expect to happen when we call it as shown below? Will it print "hello" to the console? Try to guess before evaluating it, and here's a hint: be sure to take a look at the function signature. + +Как думаете, что произойдет, если вызвать ее как показано ниже? Выведится ли "hello" на консоль? Попробуйте догадаться до выполнения, подсказка: посмотрите на сигнатуру функции. ```fsharp // call it printHello ``` -It will *not* be called as expected. The original function expects a unit argument that was not supplied, so you are getting a partially applied function (in this case with no arguments). +> It will *not* be called as expected. The original function expects a unit argument that was not supplied, so you are getting a partially applied function (in this case with no arguments). -How about this? Will it compile? +Вопреки ожиданиям вызова _не_ будет. Исходная функция ожидает `unit` как аргумент, который не был передан, поэтому была получена частично примененная функция (в данном случае без аргументов). + +> How about this? Will it compile? + +А что насчет этого случая? Будет ли он скомпилирован? ```fsharp let addXY x y = @@ -257,7 +332,9 @@ let addXY x y = x + y ``` -If you evaluate it, you will see that the compiler complains about the printfn line. +> If you evaluate it, you will see that the compiler complains about the printfn line. + +Если запустить его, компилятор пожалуется на строку с `printfn`. ```fsharp printfn "x=%i y=%i" x @@ -266,9 +343,13 @@ printfn "x=%i y=%i" x //arguments. Its type is ^a -> unit. ``` -If you didn't understand currying, this message would be very cryptic! All expressions that are evaluated standalone like this (i.e. not used as a return value or bound to something with "let") *must* evaluate to the unit value. And in this case, it is does *not* evaluate to the unit value, but instead evaluates to a function. This is a long winded way of saying that `printfn` is missing an argument. +> If you didn't understand currying, this message would be very cryptic! All expressions that are evaluated standalone like this (i.e. not used as a return value or bound to something with "let") *must* evaluate to the unit value. And in this case, it is does *not* evaluate to the unit value, but instead evaluates to a function. This is a long winded way of saying that `printfn` is missing an argument. -A common case of errors like this is when interfacing with the .NET library. For example, the `ReadLine` method of a `TextReader` must take a unit parameter. It is often easy to forget this and leave off the parens, in which case you do not get a compiler error immediately, but only when you try to treat the result as a string. +Если нет понимания каррирования, данное сообщение может быть очень загадочным. Дело в том, что все выражения, которые вычисляются отдельно как это (т.е. не используются как возвращаемое значение или привязка к чему-нибудь посредством "let") _должны_ вычисляться в `unit` значение. В данном случае, оно _не_ вычисляется в `unit` значение, но вместо этого возвращает функцию. Это длинный извилистный путь сказать, что `printfn` лишен аргумента. + +> A common case of errors like this is when interfacing with the .NET library. For example, the `ReadLine` method of a `TextReader` must take a unit parameter. It is often easy to forget this and leave off the parens, in which case you do not get a compiler error immediately, but only when you try to treat the result as a string. + +В большинстве случаев ошибки подобные этой случаются при взаимодейтвии с библиотекой из мира .NET. Например, `Readline` метод `TextReader` должент принимать `unit` параметр. Часто об этом легко забыть, и не поставить скобки, в этом случае нельзя получить ошибку компилятора в момент "вызова", но она появится при попытке интерпретировать результат как строку. ```fsharp let reader = new System.IO.StringReader("hello"); @@ -283,11 +364,15 @@ let line2 = reader.ReadLine() //correct printfn "The line is %s" line2 //no compiler error ``` -In the code above, `line1` is just a pointer or delegate to the `Readline` method, not the string that we expected. The use of `()` in `reader.ReadLine()` actually executes the function. +> In the code above, `line1` is just a pointer or delegate to the `Readline` method, not the string that we expected. The use of `()` in `reader.ReadLine()` actually executes the function. + +В коде выше `line1` - просто указатель или делегат на `Readline` метод, а не строка, как можно было бы ожидать. Использование `()` в `reader.ReadLine()` действительно вызывит функцию. -## Too many parameters ## +## Too many parameters | Слишком много параметров ## -You can get similar cryptic messages when you have too many parameters as well. Here are some examples of passing too many parameters to printf. +> You can get similar cryptic messages when you have too many parameters as well. Here are some examples of passing too many parameters to printf. + +Можно получить столь же загадочные сообщения, если передать функции слишком много параметров. Несколько примеров передачи слишком большого числа параметров в `printf`: ```fsharp printfn "hello" 42 @@ -303,9 +388,14 @@ printfn "hello %i %i" 42 43 44 // but given a 'a -> 'b -> unit ``` -For example, in the last case, the compiler is saying that it expects the format argument to have three parameters (the signature `'a -> 'b -> 'c -> 'd` has three parameters) but it is given only two (the signature `'a -> 'b -> unit` has two parameters). +> For example, in the last case, the compiler is saying that it expects the format argument to have three parameters (the signature `'a -> 'b -> 'c -> 'd` has three parameters) but it is given only two (the signature `'a -> 'b -> unit` has two parameters). + +__TODO: Не могу собрать нормальную фразу.__ +Например, в последнем случае компилятор сообщит, что он ожидает форматирующий аргумент с тремя параметрами (сигнатура `'a -> 'b -> 'c -> 'd` имеет три параметра), но вместо этого получил с двумя (у сигнатуры `'a -> 'b -> unit` только два параметра)_. + +> In cases not using `printf`, passing too many parameters will often mean that you end up with a simple value that you then try to pass a parameter to. The compiler will complain that the simple value is not a function. -In cases not using `printf`, passing too many parameters will often mean that you end up with a simple value that you then try to pass a parameter to. The compiler will complain that the simple value is not a function. +В случаях отличных от использования `printf` передача большого количества параметров часто означает, что на определенном этапе вычислений было получен простое значение, которому пытаются передать параметр. Компилятор будет жаловаться, что простое значение не является функцией. ```fsharp let add1 x = x + 1 @@ -314,7 +404,9 @@ let x = add1 2 3 // and cannot be applied ``` -If you break the call into a series of explicit intermediate functions, as we did earlier, you can see exactly what is going wrong. +> If you break the call into a series of explicit intermediate functions, as we did earlier, you can see exactly what is going wrong. + +Если разбить общий вызов на серию явных промежуточных функций, как делали ранее, можно увидеть, что именно происходит не так. ```fsharp let add1 x = x + 1 @@ -322,4 +414,4 @@ let intermediateFn = add1 2 //returns a simple value let x = intermediateFn 3 //intermediateFn is not a function! // ==> error FS0003: This value is not a function // and cannot be applied -``` +``` \ No newline at end of file diff --git a/posts/defining-functions.md b/posts/defining-functions.md index 09079a4..b061ef8 100644 --- a/posts/defining-functions.md +++ b/posts/defining-functions.md @@ -9,40 +9,57 @@ categories: [Functions, Combinators] --- -We have seen how to create typical functions using the "let" syntax, below: +> We have seen how to create typical functions using the "let" syntax, below: + +Мы видели как создавать обычные функции используя синтаксис "let", ниже: ```fsharp let add x y = x + y ``` -In this section, we'll look at some other ways of creating functions, and tips for defining functions. +> In this section, we'll look at some other ways of creating functions, and tips for defining functions. + +В этой статье мы рассмотрим некоторые другие способы создания функций, а также советы по их определению. + +## Anonymous functions (a.k.a. lambdas) | Анонимные функцие (лямбды) ## -## Anonymous functions (a.k.a. lambdas) ## +> If you are familiar with lambdas in other languages, this will not be new to you. An anonymous function (or "lambda expression") is defined using the form: -If you are familiar with lambdas in other languages, this will not be new to you. An anonymous function (or "lambda expression") is defined using the form: +Если вы знакомы с лямбдами в других языках, эта темя не станет новой. Анонимные функции (или "лямбда выражения") опредеяются посредство следующей формы: ```fsharp fun parameter1 parameter2 etc -> expression ``` -If you are used to lambdas in C# there are a couple of differences: +> If you are used to lambdas in C# there are a couple of differences: + +По сравнению с лямбдами из C# есть два отличия: + +> * the lambda must have the special keyword `fun`, which is not needed in the C# version +> * the arrow symbol is a single arrow `->` rather than the double arrow (`=>`) in C#. -* the lambda must have the special keyword `fun`, which is not needed in the C# version -* the arrow symbol is a single arrow `->` rather than the double arrow (`=>`) in C#. +* лямбды должны иметь специальное ключенове слово `fun`, которое отстутствует в C# +* используется одинарная стрелка `->`, вместо двойной `=>` из C#. -Here is a lambda that defines addition: +> Here is a lambda that defines addition: + +Лямбда-определение функции сложения: ```fsharp let add = fun x y -> x + y ``` -This is exactly the same as a more conventional function definition: +> This is exactly the same as a more conventional function definition: + +Таже функция в традиционной форме: ```fsharp let add x y = x + y ``` -Lambdas are often used when you have a short expression and you don't want to define a function just for that expression. This is particularly common with list operations, as we have seen already. +> Lambdas are often used when you have a short expression and you don't want to define a function just for that expression. This is particularly common with list operations, as we have seen already. + +Лямбды часто используются, когда есть короткое выражение и нет желания определять для него отдельную функцию. Что особенно характерно для операций со списком, как было показано ранее. ```fsharp // with separately defined function @@ -53,9 +70,13 @@ let add1 i = i + 1 [1..10] |> List.map (fun i -> i + 1) ``` -Note that you must use parentheses around the lambda. +> Note that you must use parentheses around the lambda. + +Следует обратить внимание, что необходимо использовать скобки вокруг лямбд. + +> Lambdas are also used when you want to make it clear that you are returning a function from another function. For example, the "`adderGenerator`" function that we talked about earlier could be rewritten with a lambda. -Lambdas are also used when you want to make it clear that you are returning a function from another function. For example, the "`adderGenerator`" function that we talked about earlier could be rewritten with a lambda. +Лямбды также используются когда необходимо показать явно, что из функции возвращается другая функция. Например, "`adderGenerator`" который обсуждался ранее может быть переписан с помощью лямбды. ```fsharp // original definition @@ -65,15 +86,21 @@ let adderGenerator x = (+) x let adderGenerator x = fun y -> x + y ``` -The lambda version is slightly longer, but makes it clear that an intermediate function is being returned. +> The lambda version is slightly longer, but makes it clear that an intermediate function is being returned. -You can nest lambdas as well. Here is yet another definition of `adderGenerator`, this time using lambdas only. +Лямбда версия немного длиньше, но позволяет сразу понять, что будет возвращена промежуточная функция. + +> You can nest lambdas as well. Here is yet another definition of `adderGenerator`, this time using lambdas only. + +Лямбды могут быть вложенными. Еще один пример определения `adderGenerator`, в этот раз чисто на лямбдах. ```fsharp let adderGenerator = fun x -> (fun y -> x + y) ``` -Can you see that all three of the following definitions are the same thing? +> Can you see that all three of the following definitions are the same thing? + +Ясно ли вам, что все три следующих определения эквивалентны? ```fsharp let adderGenerator1 x y = x + y @@ -81,13 +108,19 @@ let adderGenerator2 x = fun y -> x + y let adderGenerator3 = fun x -> (fun y -> x + y) ``` -If you can't see it, then do reread the [post on currying](../posts/currying.md). This is important stuff to understand! +> If you can't see it, then do reread the [post on currying](../posts/currying.md). This is important stuff to understand! + +Если нет, то перечитайте [главу о каррировании](../posts/currying.md). Это очень важно для понимания! + +## Pattern matching on parameters | Сопоставление параметров с шаблоном ## -## Pattern matching on parameters ## +> When defining a function, you can pass an explicit parameter, as we have seen, but you can also pattern match directly in the parameter section. In other words, the parameter section can contain *patterns*, not just identifiers! -When defining a function, you can pass an explicit parameter, as we have seen, but you can also pattern match directly in the parameter section. In other words, the parameter section can contain *patterns*, not just identifiers! +Когда определяется функция, ей можно передать параметры явно, как было показано ранее, но также можно произвести сопоставление с шаблоном прямо в секции параметров. Другими словами, секция параметров может содержать паттерны (шаблоны сопоставления), а не только идентификаторы! -The following example demonstrates how to use patterns in a function definition: +> The following example demonstrates how to use patterns in a function definition: + +Следующий пример демонстрирует как использовать шаблоны в определении функции: ```fsharp type Name = {first:string; last:string} // define a new type @@ -107,22 +140,30 @@ f1 bob f2 bob ``` -This kind of matching can only occur when the matching is always possible. For example, you cannot match on union types or lists this way, because some cases might not be matched. +> This kind of matching can only occur when the matching is always possible. For example, you cannot match on union types or lists this way, because some cases might not be matched. + +Данный вид сопоставления может присходить только тогда, когда соответсвие всегда разрешимо. Например, нельзя подобным способом матчить типы объединения и списки, потому-что некоторые случаи не могут быть сопоставлены. ```fsharp let f3 (x::xs) = // use pattern matching on a list printfn "first element is=%A" x ``` -You will get a warning about incomplete pattern matches. +> You will get a warning about incomplete pattern matches. + +Будет получено предупреждение о неполноте шаблона. -## A common mistake: tuples vs. multiple parameters ## +## A common mistake: tuples vs. multiple parameters | Распространенная ошибка: кортежи vs. множество параметров ## + +> If you come from a C-like language, a tuple used as a single function parameter can look awfully like multiple parameters. They are not the same thing at all! As I noted earlier, if you see a comma, it is probably part of a tuple. Parameters are separated by spaces. + +Если вы пришли из C-подобного языка, кортежи использованные в качестве однопараметрической функции может выглядеть так же ужасно как и мултипараметрическая функция. Но это не одно и то же! Как я отметил ранее, если вы видете запятую, скорее всего это кортеж. Параметры же разделяются посредством запятой. -If you come from a C-like language, a tuple used as a single function parameter can look awfully like multiple parameters. They are not the same thing at all! As I noted earlier, if you see a comma, it is probably part of a tuple. Parameters are separated by spaces. +> Here is an example of the confusion: -Here is an example of the confusion: +Пример путаницы: ```fsharp // a function that takes two distinct parameters @@ -138,11 +179,17 @@ let addTuple aTuple = let addConfusingTuple (x,y) = x + y ``` -* The first definition, "`addTwoParams`", takes two parameters, separated with spaces. -* The second definition, "`addTuple`", takes a single parameter. It then binds "x" and "y" to the inside of the tuple and does the addition. -* The third definition, "`addConfusingTuple`", takes a single parameter just like "`addTuple`", but the tricky thing is that the tuple is unpacked and bound as part of the parameter definition using pattern matching. Behind the scenes, it is exactly the same as "`addTuple`". +> * The first definition, "`addTwoParams`", takes two parameters, separated with spaces. +> * The second definition, "`addTuple`", takes a single parameter. It then binds "x" and "y" to the inside of the tuple and does the addition. +> * The third definition, "`addConfusingTuple`", takes a single parameter just like "`addTuple`", but the tricky thing is that the tuple is unpacked and bound as part of the parameter definition using pattern matching. Behind the scenes, it is exactly the same as "`addTuple`". -Let's look at the signatures (it is always a good idea to look at the signatures if you are unsure) +* Первое определение, "`addTwoParams`", принимает два параметра разделенных пробелом. +* Второе определение, "`addTuple`", принимает один параметр. Этот параметр привязывает "x" и "y" из кортежа и суммирует их. +* Третье определение, "`addConfusingTuple`", принимает один параметр как и "`addTuple`", но трюк в том, что этот кортеж распаковывается и привязывается как часть определения параметра при помощи сопоставления с шаблоном. За кулисами все происходит точно так же как и в "`addTuple`". + +> Let's look at the signatures (it is always a good idea to look at the signatures if you are unsure) + +Посмотрите на сигнатуры (смотреть на сигнатуры - хорошая практика, если вы в чем то не уверены). ```fsharp val addTwoParams : int -> int -> int // two params @@ -150,7 +197,9 @@ val addTuple : int * int -> int // tuple->int val addConfusingTuple : int * int -> int // tuple->int ``` -Now let's use them: +> Now let's use them: + +А теперь сюда: ```fsharp //test @@ -160,12 +209,18 @@ addTwoParams (1,2) // error trying to pass a single tuple // int but here has type 'a * 'b ``` -Here we can see an error occur in the second case above. +> Here we can see an error occur in the second case above. -First, the compiler treats `(1,2)` as a generic tuple of type `('a * 'b)`, which it attempts to pass as the first parameter to "`addTwoParams`". +Здесь мы видим ошибку во втором случае. + +> First, the compiler treats `(1,2)` as a generic tuple of type `('a * 'b)`, which it attempts to pass as the first parameter to "`addTwoParams`". Then it complains that the first parameter of `addTwoParams` is an `int`, and we're trying to pass a tuple. -To make a tuple, use a comma! Here's how to do it correctly: +Во-первых, компилятор трактует `(1,2)` как обобщенный параметр типа `('a * 'b)`, который пробует передать в качестве первого параметра в "`addTwoParams`". После чего жалуется, что первый параметр `addTwoParams` не является `int`, и была совершена попытка передачи кортежа. + +> To make a tuple, use a comma! Here's how to do it correctly: + +Что бы сделать кортеж, используйте запятую! Т.е.: ```fsharp addTuple (1,2) // ok @@ -180,7 +235,9 @@ addTuple y // ok addConfusingTuple y // ok ``` -Conversely, if you attempt to pass multiple arguments to a function expecting a tuple, you will also get an obscure error. +> Conversely, if you attempt to pass multiple arguments to a function expecting a tuple, you will also get an obscure error. + +И наоборот, если попытаться передать множество аргументов в функцию ожидающую кортеж, можно также получить непонятную ошибку. ```fsharp addConfusingTuple 1 2 // error trying to pass two args @@ -188,11 +245,15 @@ addConfusingTuple 1 2 // error trying to pass two args // cannot be applied ``` -In this case, the compiler thinks that, since you are passing two arguments, `addConfusingTuple` must be curryable. So then "`addConfusingTuple 1`" would be a partial application that returns another intermediate function. Trying to apply that intermediate function with "2" gives an error, because there is no intermediate function! We saw this exact same error in the post on currying, when we discussed the issues that can occur from having too many parameters. +> In this case, the compiler thinks that, since you are passing two arguments, `addConfusingTuple` must be curryable. So then "`addConfusingTuple 1`" would be a partial application that returns another intermediate function. Trying to apply that intermediate function with "2" gives an error, because there is no intermediate function! We saw this exact same error in the post on currying, when we discussed the issues that can occur from having too many parameters. + +В этот раз, компилятор решил, что раз передаются два аргумента, `addConfusingTuple` должна быть каррируемой. Это значит, что "`addConfusingTuple 1`" является частичным применением и должно возвращать другую промежуточную функцию. Попытка вызвать промежуточную функцию на "2" выдает ошибку, т.к. здесь нет промежуточной функции! Мы видим ту же ошибку, что и в главе о каррировании, когда мы обсуждали проблемы связанные со слишком большим количеством параметров. -### Why not use tuples as parameters? ### +### Why not use tuples as parameters? | Почему бы не использовать кортежи в качестве параметров? ### -The discussion of the issues with tuples above shows that there's another way to define functions with more than one parameter: rather than passing them in separately, all the parameters can be combined into a single composite data structure. In the example below, the function takes a single parameter, which is a tuple containing three items. +> The discussion of the issues with tuples above shows that there's another way to define functions with more than one parameter: rather than passing them in separately, all the parameters can be combined into a single composite data structure. In the example below, the function takes a single parameter, which is a tuple containing three items. + +Обсуждение кортежей выше показывает, что существует другой способ опредления функций со множеством параметров: вместо передачи их по отдельности, все параметры могут быть собраны в виде одной структуры. В примере ниже, функция принимает один параметр, который является кортежем из трех элментов. ```fsharp let f (x,y,z) = x + y * z @@ -202,18 +263,29 @@ let f (x,y,z) = x + y * z f (1,2,3) ``` -Note that the function signature is different from a true three parameter function. There is only one arrow, so only one parameter, and the stars indicate that this is a tuple of `(int*int*int)`. +> Note that the function signature is different from a true three parameter function. There is only one arrow, so only one parameter, and the stars indicate that this is a tuple of `(int*int*int)`. + +Следует обратить внимание, что сигнатура отличается от сигнутуры функции с тремя параметрами. Здесь только одна стрелка, один параметр и звездочки указывающие на кортеж `(int*int*int)`. -When would we want to use tuple parameters instead of individual ones? +> When would we want to use tuple parameters instead of individual ones? -* When the tuples are meaningful in themselves. For example, if we are working with three dimensional coordinates, a three-tuple might well be more convenient than three separate dimensions. -* Tuples are occasionally used to bundle data together in a single structure that should be kept together. For example, the `TryParse` functions in .NET library return the result and a Boolean as a tuple. But if you have a lot of data that is kept together as a bundle, then you will probably want to define a record or class type to store it. +Когда возникает необходимость использовать кортеж параметров вместо индивидуальных значений? // TODO: переформулировать. -### A special case: tuples and .NET library functions ### +> * When the tuples are meaningful in themselves. For example, if we are working with three dimensional coordinates, a three-tuple might well be more convenient than three separate dimensions. +> * Tuples are occasionally used to bundle data together in a single structure that should be kept together. For example, the `TryParse` functions in .NET library return the result and a Boolean as a tuple. But if you have a lot of data that is kept together as a bundle, then you will probably want to define a record or class type to store it. -One area where commas are seen a lot is when calling .NET library functions! +* Когда кортежи значимы сами по себе. Например, если производятся операции над трехмерными координатами, тройные кортежи могут быть более удобными чем три отдельных измерения. +* Кортежи иногда используются, чтобы объеденить данные в единую структуру, которая должна сохраняться вместе. Например, `TryParse` методы из .NET библиотеки возвращают результат и булевую переменную в виде кортежа. Но если имеется достаточно большой объем данных передаваемых в связке, скорее всего он будет опредлен в виде записи или класса. -These all take tuple-like arguments, and so these calls look just the same as they would from C#: +### A special case: tuples and .NET library functions | Особые случай: кортежи и функции .NET библиотеки ### + +> One area where commas are seen a lot is when calling .NET library functions! + +Одна из областей, где запятые встречаются очень часто, это вызов функций .NET библиотеки! + +> These all take tuple-like arguments, and so these calls look just the same as they would from C#: + +Они все принимают кортежи и эти вызовы выглядят также как на C#: ```fsharp // correct @@ -223,9 +295,13 @@ System.String.Compare("a","b") System.String.Compare "a" "b" ``` -The reason is that .NET library functions are not curried and cannot be partially applied. *All* the parameters must *always* be passed in, and using a tuple-like approach is the obvious way to do this. +> The reason is that .NET library functions are not curried and cannot be partially applied. *All* the parameters must *always* be passed in, and using a tuple-like approach is the obvious way to do this. + +Причина этого кроется в том, что функции классического .NET не каррируемы и не могут быть частично применены. _Все_ парамерты _всегда_ должны быть передаваться сразу, и использование кортеже-подобной передачи самый очевидный способ сделать это. -But do note that although these calls look like tuples, they are actually a special case. Real tuples cannot be used, so the following code is invalid: +> But do note that although these calls look like tuples, they are actually a special case. Real tuples cannot be used, so the following code is invalid: + +Однако следует заметить, что данные вызовы лишь выглядят как передачи кортежей, на самом деле это особы случай. В действительности кортежи не могут быть использованы, и следующий код невалиден: ```fsharp let tuple = ("a","b") @@ -234,7 +310,9 @@ System.String.Compare tuple // error System.String.Compare "a","b" // error ``` -If you do want to partially apply .NET library functions, it is normally trivial to write wrapper functions for them, as we have [seen earlier](../posts/partial-application.md), and as shown below: +> If you do want to partially apply .NET library functions, it is normally trivial to write wrapper functions for them, as we have [seen earlier](../posts/partial-application.md), and as shown below: + +Если есть желание частично применить функции .NET, это можно сделать тривиально при помощи обертки над ней, как делалось [ранее](../posts/partial-application.md), или как показано ниже: ```fsharp // create a wrapper function @@ -249,20 +327,33 @@ let strCompareWithB = strCompare "B" ``` -## Guidelines for separate vs. grouped parameters ## +## Guidelines for separate vs. grouped parameters | Руководство по выбору отдельных и cгруппированных параметров ## + +> The discussion on tuples leads us to a more general topic: when should function parameters be separate and when should they be grouped? + +Обсуждение кортежей ведет к более общей теме: когда параметры должны быть отдельными, а когда cгруппированными? + +> Note that F# is different from C# in this respect. In C# *all* the parameters are *always* provided, so the question does not even arise! In F#, due to partial application, only some parameters might be provided, so you need to distinguish between those that are required to be grouped together vs. those that are independent. -The discussion on tuples leads us to a more general topic: when should function parameters be separate and when should they be grouped? +Следует обратить внимание на то, чем F# отличается от C# в этом отношении. В C# _все_ параметры _всегда_ переданы, поэтому данный вопрос там даже не возникает! В F# из-за частичного применения могут быть представлены лишь некоторые из параметров, поэтому необходимо проводить различие между случаем когда параметры должны быть объедененны и случаем, когда они независимы. -Note that F# is different from C# in this respect. In C# *all* the parameters are *always* provided, so the question does not even arise! In F#, due to partial application, only some parameters might be provided, so you need to distinguish between those that are required to be grouped together vs. those that are independent. +> Here are some general guidelines of how to structure parameters when you are designing your own functions. -Here are some general guidelines of how to structure parameters when you are designing your own functions. +Общие рекомендации о том как структурировать параметры при проектировании собственных функций. -* In general, it is always better to use separate parameters rather than passing them as a single structure such as a tuple or record. This allows for more flexible behavior such as partial application. -* But, when a group of parameters *must* all be set at once, then *do* use some sort of grouping mechanism. +> * In general, it is always better to use separate parameters rather than passing them as a single structure such as a tuple or record. This allows for more flexible behavior such as partial application. +> * But, when a group of parameters *must* all be set at once, then *do* use some sort of grouping mechanism. -In other words, when designing a function, ask yourself "could I provide this parameter in isolation?" If the answer is no, the parameters should be grouped. +* В общем случае, всегда лучше использовать раздельные параметры вместо передачи одной структуры будь то кортеж или запись. Это позволяет добиться более гибкого поведения, такого как частичное применение. +* Но, когда группа параметров _должна_ быть передана за раз, следует использовать какой-нибудь механизм группировки. -Let's look at some examples: +> In other words, when designing a function, ask yourself "could I provide this parameter in isolation?" If the answer is no, the parameters should be grouped. + +Другими словами, когда разрабатываете функцию, спросите себя "Могу ли я предоставить это параметр отдельно?". Если ответ нет, то параметры должны быть сгруппированы. + +> Let's look at some examples: + +Рассмотрим несколько примеров: ```fsharp // Pass in two numbers for addition. @@ -285,31 +376,41 @@ let setCustomerName first last = // not recommended let setCustomerName myCredentials aName = //good ``` -Finally, do be sure to order the parameters appropriately to assist with partial application (see the guidelines in the earlier [post](../posts/partial-application.md)). For example, in the last function above, why did I put the `myCredentials` parameter ahead of the `aName` parameter? +> Finally, do be sure to order the parameters appropriately to assist with partial application (see the guidelines in the earlier [post](../posts/partial-application.md)). For example, in the last function above, why did I put the `myCredentials` parameter ahead of the `aName` parameter? + +Нанонец, убедитесь, что порядок параметров поможет в частичном применении (смотрите руководство [здесь](../posts/partial-application.md)). Например, в почему я поместил `myCredentials` перед `aName` в последней функции? + +## Parameter-less functions | Функции без параметров ## -## Parameter-less functions ## +> Sometimes we may want functions that don't take any parameters at all. For example, we may want a "hello world" function that we can call repeatedly. As we saw in a previous section, the naive definition will not work. -Sometimes we may want functions that don't take any parameters at all. For example, we may want a "hello world" function that we can call repeatedly. As we saw in a previous section, the naive definition will not work. +Иногда может понадобиться функция, которая не принимает никаких параметров. Например, нужна функция "hello world" которую можно вызывать многократно. Как было показано в предыдущей секции, наивное определение не работает. ```fsharp let sayHello = printfn "Hello World!" // not what we want ``` -The fix is to add a unit parameter to the function, or use a lambda. +> The fix is to add a unit parameter to the function, or use a lambda. + +Но это можно исправить, если добавить unit параметр к функции или использовать лямбду. ```fsharp let sayHello() = printfn "Hello World!" // good let sayHello = fun () -> printfn "Hello World!" // good ``` -And then the function must always be called with a unit argument: +> And then the function must always be called with a unit argument: + +После чего функция всегда должна вызываться с `unit` аргументом: ```fsharp // call it sayHello() ``` -This is particularly common with the .NET libraries. Some examples are: +> This is particularly common with the .NET libraries. Some examples are: + +Что происходит достаточно часто при взаимодействии с .NET библиотеками: ```fsharp Console.ReadLine() @@ -317,38 +418,52 @@ System.Environment.GetCommandLineArgs() System.IO.Directory.GetCurrentDirectory() ``` -Do remember to call them with the unit parameter! +> Do remember to call them with the unit parameter! + +Запомните, вызывайте их с `unit` параметрами! + +## Defining new operators | Определение новых операторов ## -## Defining new operators ## +> You can define functions named using one or more of the operator symbols (see the [F# documentation](http://msdn.microsoft.com/en-us/library/dd233204) for the exact list of symbols that you can use): -You can define functions named using one or more of the operator symbols (see the [F# documentation](http://msdn.microsoft.com/en-us/library/dd233204) for the exact list of symbols that you can use): +Можно определять функции с использованием одного и более операторных символа (смотрите [документацию](http://msdn.microsoft.com/en-us/library/dd233204) для ознакомления со списком символов): ```fsharp // define let (.*%) x y = x + y + 1 ``` -You must use parentheses around the symbols when defining them. +> You must use parentheses around the symbols when defining them. -Note that for custom operators that begin with `*`, a space is required; otherwise the `(*` is interpreted as the start of a comment: +Необходимо использовать скобки вокруг символов для определения функции. + +> Note that for custom operators that begin with `*`, a space is required; otherwise the `(*` is interpreted as the start of a comment: + +Операторы начинающиеся с `*` требуют пробел между скобкой и `*`, т.к. в F# `(*` выполняет роль начала комментария (как `/*...*/` в C#): ```fsharp let ( *+* ) x y = x + y + 1 ``` -Once defined, the new function can be used in the normal way, again with parens around the symbols: +> Once defined, the new function can be used in the normal way, again with parens around the symbols: + +Единожды определенная, новая функция может быть использована обычным способом, если будет завернута в скобки: ```fsharp let result = (.*%) 2 3 ``` -If the function has exactly two parameters, you can use it as an infix operator without parentheses. +> If the function has exactly two parameters, you can use it as an infix operator without parentheses. + +Если фунция используется с двумя параметрами, можно использовать инфиксную операторную запись без скобок. ```fsharp let result = 2 .*% 3 ``` -You can also define prefix operators that start with `!` or `~` (with some restrictions -- see the [F# documentation on operator overloading](http://msdn.microsoft.com/en-us/library/dd233204#prefix)) +> You can also define prefix operators that start with `!` or `~` (with some restrictions -- see the [F# documentation on operator overloading](http://msdn.microsoft.com/en-us/library/dd233204#prefix)) + +Можно также определять префиксные операторы начинающиеся с `!` или `~` (с некоторыми ограничениями, смотрите [документацию](http://msdn.microsoft.com/en-us/library/dd233204#prefix)) ```fsharp let (~%%) (s:string) = s.ToCharArray() @@ -357,13 +472,19 @@ let (~%%) (s:string) = s.ToCharArray() let result = %% "hello" ``` -In F# it is quite common to create your own operators, and many libraries will export operators with names such as `>=>` and `<*>`. +> In F# it is quite common to create your own operators, and many libraries will export operators with names such as `>=>` and `<*>`. -## Point-free style ## +В F# определние операторов достаточно частая операция, и многие библиотеки будут экспортировать операторы с именами типа `>=>`и `<*>`. -We have already seen many examples of leaving off the last parameter of functions to reduce clutter. This style is referred to as **point-free style** or **tacit programming**. +## Point-free style | Point-free стиль ## -Here are some examples: +> We have already seen many examples of leaving off the last parameter of functions to reduce clutter. This style is referred to as **point-free style** or **tacit programming**. + +Мы уже видели множество примеров функций у которых отсутствовали последние параметры, чтобы снизить уровень хаоса. Этот стиль называется **point-free стилем** или **молчаливым программированием (tacit programming)**. + +> Here are some examples: + +Вот несколько примеров: ```fsharp let add x y = x + y // explicit @@ -376,23 +497,39 @@ let sum list = List.reduce (fun sum e -> sum+e) list // explicit let sum = List.reduce (+) // point free ``` -There are pros and cons to this style. +> There are pros and cons to this style. + +У данного стиля есть свои плюсы и минусы. + +> On the plus side, it focuses attention on the high level function composition rather than the low level objects. For example "`(+) 1 >> (*) 2`" is clearly an addition operation followed by a multiplication. And "`List.reduce (+)`" makes it clear that the plus operation is key, without needing to know about the list it is actually applied to. -On the plus side, it focuses attention on the high level function composition rather than the low level objects. For example "`(+) 1 >> (*) 2`" is clearly an addition operation followed by a multiplication. And "`List.reduce (+)`" makes it clear that the plus operation is key, without needing to know about the list it is actually applied to. +Одним из плюсов является то, что акцент производится на композицию функций высшего порядка вместо возни с низкоуровневыми объектами. Например, "`(+) 1 >> (*) 2`" - явное сложение с последующим умножением. А "`List.reduce (+)`" дает понять, что важна операция сложения, безотносительно информации о списке. -Point-free helps to clarify the underlying algorithm and reveal commonalities between code -- the "`reduce`" function used above is a good example of this -- it will be discussed in a planned series on list processing. +> Point-free helps to clarify the underlying algorithm and reveal commonalities between code -- the "`reduce`" function used above is a good example of this -- it will be discussed in a planned series on list processing. -On the other hand, too much point-free style can make for confusing code. Explicit parameters can act as a form of documentation, and their names (such as "list") make it clear what the function is acting on. +Безточечный стиль позволяет сосредоточиться на базовом алгоритме и выявить общие черты в коде. "`reduce`" функция использованная выше является хорошим примером. Эта тема будет обсуждаться в запланированной серии по обработке списков. -As with anything in programming, the best guideline is to use the approach that provides the most clarity. +> On the other hand, too much point-free style can make for confusing code. Explicit parameters can act as a form of documentation, and their names (such as "list") make it clear what the function is acting on. -## Combinators ## +С другой стороны, чрезмерное использование подобного стиля может сделать код малопонятным. Явные параметры действуют как документация и их имена (такие как "list") облегчают понимание того, что делает функция. -The word "**combinator**" is used to describe functions whose result depends only on their parameters. That means there is no dependency on the outside world, and in particular no other functions or global value can be accessed at all. +> As with anything in programming, the best guideline is to use the approach that provides the most clarity. -In practice, this means that a combinator function is limited to combining its parameters in various ways. +Как и все в программировании, лучшая рекомендация, предпочитайте тот подход, что обеспечивает наибольшую ясность. -We have already seen some combinators already: the "pipe" operator and the "compose" operator. If you look at their definitions, it is clear that all they do is reorder the parameters in various ways +## Combinators | Комбинаторы ## + +> The word "**combinator**" is used to describe functions whose result depends only on their parameters. That means there is no dependency on the outside world, and in particular no other functions or global value can be accessed at all. + +"**Комбинаторами**" называют функции, чей результат зависит только от их параметров. Это означает, что не существует зависимости от внешнего мира, и, в частности, никакие другие функции или глобальные значения не могут повлиять на них. + +> In practice, this means that a combinator function is limited to combining its parameters in various ways. + +На практике, это означает, что комбинаторные функции ограничены комбинацией их параметров различными способами. + +> We have already seen some combinators already: the "pipe" operator and the "compose" operator. If you look at their definitions, it is clear that all they do is reorder the parameters in various ways + +Мы уже видели несколько комбинаторов: "pipe" и оператор композиции. Если посмотреть на их определения, то понятно, что все, что они делают, это переупорядочивают параметры различными способами. ```fsharp let (|>) x f = f x // forward pipe @@ -401,13 +538,19 @@ let (>>) f g x = g (f x) // forward composition let (<<) g f x = g (f x) // reverse composition ``` -On the other hand, a function like "printf", although primitive, is not a combinator, because it has a dependency on the outside world (I/O). +> On the other hand, a function like "printf", although primitive, is not a combinator, because it has a dependency on the outside world (I/O). + +С другой стороны, функции подобные "printf", хоть и примитивны, но не являются комбинаторами, потому-что имеют зависимость от внешнего мира (I/O). + +### Combinator birds | ? Комбинаторные птички (TODO: Обсудить) ### -### Combinator birds ### +> Combinators are the basis of a whole branch of logic (naturally called "combinatory logic") that was invented many years before computers and programming languages. Combinatory logic has had a very large influence on functional programming. -Combinators are the basis of a whole branch of logic (naturally called "combinatory logic") that was invented many years before computers and programming languages. Combinatory logic has had a very large influence on functional programming. +Комбинаторы являются основой целого раздела логики (естественно называемого "комбинаторная логика"), который был изобретен за много лет до компьютеров и языков программирования. Комбинаторная логика имеет очень большое влияние на функциональное программирование. -To read more about combinators and combinatory logic, I recommend the book "To Mock a Mockingbird" by Raymond Smullyan. In it, he describes many other combinators and whimsically gives them names of birds. Here are some examples of some standard combinators and their bird names: +> To read more about combinators and combinatory logic, I recommend the book "To Mock a Mockingbird" by Raymond Smullyan. In it, he describes many other combinators and whimsically gives them names of birds. Here are some examples of some standard combinators and their bird names: + +Чтобы узнать больше о комбинаторах и комбинаторной логики, я рекомендую книгу "To Mock a Mockingbird" Raymond-а Smullyan-а. В ней он объясняет другие комбинаторы и причудливо дает им названия птиц. Вот несколько примеров стандартных комбинаторов и их птичъих имен: ```fsharp let I x = x // identity function, or the Idiot bird @@ -420,25 +563,41 @@ let S x y z = x z (y z) // The Starling let rec Y f x = f (Y f) x // Y-combinator, or Sage bird ``` -The letter names are quite standard, so if you refer to "the K combinator", everyone will be familiar with that terminology. +> The letter names are quite standard, so if you refer to "the K combinator", everyone will be familiar with that terminology. + +Буквенные имена вполне стандартные, так что можно ссылаться на K-комбинатор всякому, кто знаком с данной терминологией. -It turns out that many common programming patterns can be represented using these standard combinators. For example, the Kestrel is a common pattern in fluent interfaces where you do something but then return the original object. The Thrush is the pipe operation, the Queer bird is forward composition, and the Y-combinator is famously used to make functions recursive. +> It turns out that many common programming patterns can be represented using these standard combinators. For example, the Kestrel is a common pattern in fluent interfaces where you do something but then return the original object. The Thrush is the pipe operation, the Queer bird is forward composition, and the Y-combinator is famously used to make functions recursive. -Indeed, there is a well-known theorem that states that any computable function whatsoever can be built from just two basic combinators, the Kestrel and the Starling. +Получается, что множество распространенных шаблонов программирования могут быть представлены через данные стандартные комбинаторы. Например, Kestrel является обычным паттерном в fluent интерфейсе где вы делаете что-то, но возвращаете оригинальный объект. Thrush - пайп, Queer - прямая композиция, а Y-комбинатор отлично справляется с созданием рекурсивных функций. -### Combinator libraries ### +> Indeed, there is a well-known theorem that states that any computable function whatsoever can be built from just two basic combinators, the Kestrel and the Starling. -A combinator library is a code library that exports a set of combinator functions that are designed to work together. The user of the library can then easily combine simple functions together to make bigger and more complex functions, like building with Lego. +На самом деле, существует широко известная теорема, что любая вычислимая функция может быть построена при помощи лишь двух бызовых комбинаторов, Kestrel-а и Starling-а. -A well designed combinator library allows you to focus on the high level operations, and push the low level "noise" to the background. We've already seen some examples of this power in the examples in ["why use F#"](../series/why-use-fsharp.md) series, and the `List` module is full of them -- the "`fold`" and "`map`" functions are also combinators, if you think about it. +### Combinator libraries | Библиотеки комбинаторов ### -Another advantage of combinators is that they are the safest type of function. As they have no dependency on the outside world they cannot change if the global environment changes. A function that reads a global value or uses a library function can break or alter between calls if the context is different. This can never happen with combinators. +> A combinator library is a code library that exports a set of combinator functions that are designed to work together. The user of the library can then easily combine simple functions together to make bigger and more complex functions, like building with Lego. -In F#, combinator libraries are available for parsing (the FParsec library), HTML construction, testing frameworks, and more. We'll discuss and use combinators further in later series. +Библиотеки кобинаторов - это библиотеки которые экспортируют множество комбинаторных функций, которые раработаны с учетом их совместного использования. Пользователь подобной библиотеки может легко комбинировать функции вместе, чтобы получить еще большие и сложные функции, как кубики лего. -## Recursive functions ## +> A well designed combinator library allows you to focus on the high level operations, and push the low level "noise" to the background. We've already seen some examples of this power in the examples in ["why use F#"](../series/why-use-fsharp.md) series, and the `List` module is full of them -- the "`fold`" and "`map`" functions are also combinators, if you think about it. -Often, a function will need to refer to itself in its body. The classic example is the Fibonacci function: +Хорошо спроектированная библиотека комбинаторов позволяет сосредоточиться на высокоуровневых функциях, и скрыть низкоуровневый "шум". Мы уже видели их силу в нескольких примерах в серии ["why use F#"](../series/why-use-fsharp.md), и модуль `List` полон таких функций, "`fold`" и "`map`" также являются комбинаторами, если вы подумали об этом. + +> Another advantage of combinators is that they are the safest type of function. As they have no dependency on the outside world they cannot change if the global environment changes. A function that reads a global value or uses a library function can break or alter between calls if the context is different. This can never happen with combinators. + +Другое преимущество комбинаторов - они являются самым безопасныи типом функций. Т.к. они не имеют зависимостей от внешнего мира, они не могут изменяться, при изменении глобальной среды. Функция которая читает глобальное значение или использует библиотченые функции, может сломаться или измениться между вызовами если контекст изменится. Этого никогда не произойдет с комбинаторами. + +> In F#, combinator libraries are available for parsing (the FParsec library), HTML construction, testing frameworks, and more. We'll discuss and use combinators further in later series. + +В F# библиотеки комбинаторов доступны для парсинга (FParsec), создания HTML, тестирующих фреймворков и т.д. Мы обсудим и воспользуемся комбинаторами позднее в следующих сериях. + +## Recursive functions | Рекурсивные функции ## + +> Often, a function will need to refer to itself in its body. The classic example is the Fibonacci function: + +Часто функции необходимо ссылаться на саму себя из ее тела. Классический пример - функция Фибоначи. ```fsharp let fib i = @@ -448,11 +607,15 @@ let fib i = | n -> fib(n-1) + fib(n-2) ``` -Unfortunately, this will not compile: +> Unfortunately, this will not compile: + +К сожалению, данная функция не сможет скомпилироваться: error FS0039: The value or constructor 'fib' is not defined -You have to tell the compiler that this is a recursive function using the rec keyword. +> You have to tell the compiler that this is a recursive function using the rec keyword. + +Необходимо указать компилятору, что это рекурсивная функция используя ключевое слово `rec`. ```fsharp let rec fib i = @@ -462,4 +625,6 @@ let rec fib i = | n -> fib(n-1) + fib(n-2) ``` -Recursive functions and data structures are extremely common in functional programming, and I hope to devote a whole later series to this topic. +> Recursive functions and data structures are extremely common in functional programming, and I hope to devote a whole later series to this topic. + +Рекурсивные функции и структуры данных очень распространены в функциональном программировании, и я надеюсь посвятить этой теме целую серию позднее. \ No newline at end of file diff --git a/posts/function-composition.md b/posts/function-composition.md index 72744c4..fbacd78 100644 --- a/posts/function-composition.md +++ b/posts/function-composition.md @@ -9,31 +9,55 @@ categories: [Functions] image: "/assets/img/Functions_Composition.png" --- -## Function associativity ## +## Function associativity | Ассоциативность функций ## -If we have a chain of functions in a row, how are they combined? +> If we have a chain of functions in a row, how are they combined? -For example, what does this mean? +Пусть есть цепочка функций написанных в ряд. В каком порядке они будут скомбинированны? + +> For example, what does this mean? + +Например, что значит эта функция? ```fsharp let F x y z = x y z ``` -Does it mean apply the function y to the argument z, and then take the result and use it as an argument for x? In which case it is the same as: +> Does it mean apply the function y to the argument z, and then take the result and use it as an argument for x? In which case it is the same as: + +Значит ли это, что функция `y` должна быть применена к аргументу `z`, а затем полученный результат должен быть передан в `x`? Т.е.: ```fsharp let F x y z = x (y z) ``` -Or does it mean apply the function x to the argument y, and then take the resulting function and evaluate it with the argument z? In which case it is the same as: +> Or does it mean apply the function x to the argument y, and then take the resulting function and evaluate it with the argument z? In which case it is the same as: + +Или функция `x` применяется к аргументу `y`, после чего функция полученная в результате будет вычислена с аргументом `z`? Т.е.: ```fsharp let F x y z = (x y) z ``` -The answer is the latter. Function application is *left associative*. That is, evaluating `x y z` is the same as evaluating `(x y) z`. And evaluating `w x y z` is the same as evaluating `((w x) y) z`. This should not be a surprise. We have already seen that this is how partial application works. If you think of x as a two parameter function, then `(x y) z` is the result of partial application of the first parameter, followed by passing the z argument to the intermediate function. +> 1. The answer is the latter. +> 1. Function application is *left associative*. +> 1. That is, evaluating `x y z` is the same as evaluating `(x y) z`. +> 1. And evaluating `w x y z` is the same as evaluating `((w x) y) z`. +> 1. This should not be a surprise. +> 1. We have already seen that this is how partial application works. +> 1. If you think of x as a two parameter function, then `(x y) z` is the result of partial application of the first parameter, followed by passing the z argument to the intermediate function. + +1. Верен второй вариант. +1. Применение функций имеет _левую ассоциативность_. +1. `x y z` значит тоже самое что и `(x y) z`. +1. А `w x y z` равно `((w x) y) z`. +1. Это не должно выглядить удивительным. +1. Мы уже видели как работает частичное применение. +1. Если рассуждать о `x` как о функции с двумя параметрами, то `(x y) z` - это результат частичного применения первого параметра, за которым следует передача аргумента `z` к промежуточной функции. -If you do want to do right association, you can use explicit parentheses, or you can use a pipe. The following three forms are equivalent. +> If you do want to do right association, you can use explicit parentheses, or you can use a pipe. The following three forms are equivalent. + +Если нужна правая ассоциативность, можно использовать скобки или pipe. Следующие три записи эквивалентны: ```fsharp let F x y z = x (y z) @@ -41,24 +65,34 @@ let F x y z = y z |> x // using forward pipe let F x y z = x <| y z // using backward pipe ``` -As an exercise, work out the signatures for these functions without actually evaluating them! +> As an exercise, work out the signatures for these functions without actually evaluating them! + +В качестве упражнения, попробуйте вывести сигнатуры этих функций без реального вычисления! + +## Function composition | Композиция функций ## -## Function composition ## +> We've mentioned function composition a number of times in passing now, but what does it actually mean? It can seem quite intimidating at first, but it is actually quite simple. -We've mentioned function composition a number of times in passing now, but what does it actually mean? It can seem quite intimidating at first, but it is actually quite simple. +Мы упоминали композицию функций несколько раз, но что в действительности означает данный термин? Это кажется устрашающим на первый взгляд, но на самом деле все довольно просто. -Say that you have a function "f" that maps from type "T1" to type "T2", and say that you also have a function "g" that maps from type "T2" to type "T3". Then you can connect the output of "f" to the input of "g", creating a new function that maps from type "T1" to type "T3". +> Say that you have a function "f" that maps from type "T1" to type "T2", and say that you also have a function "g" that maps from type "T2" to type "T3". Then you can connect the output of "f" to the input of "g", creating a new function that maps from type "T1" to type "T3". + +Скажем у нас есть функция "f" которая сопоставляет тип "T1" к типу "T2", также у нас есть функция "g" которая преобразует тип "T2" в тип "T3". Тогда мы можем соединить вывод "f" и ввод "g", создав новую функцию, которая преобразует тип "T1" к типу "T3". ![](../assets/img/Functions_Composition.png) -Here's an example +> Here's an example + +Например: ```fsharp let f (x:int) = float x * 3.0 // f is int->float let g (x:float) = x > 4.0 // g is float->bool ``` -We can create a new function h that takes the output of "f" and uses it as the input for "g". +> We can create a new function h that takes the output of "f" and uses it as the input for "g". + +Мы можем создать новую функцию "h" которая берет вывод "f" и использует его в качестве ввода для "g". ```fsharp let h (x:int) = @@ -66,7 +100,9 @@ let h (x:int) = g(y) // return output of g ``` -A much more compact way is this: +> A much more compact way is this: + +Чуть более компактно: ```fsharp let h (x:int) = g ( f(x) ) // h is int->bool @@ -76,27 +112,37 @@ h 1 h 2 ``` -So far, so straightforward. What is interesting is that we can define a new function called "compose" that, given functions "f" and "g", combines them in this way without even knowing their signatures. +> So far, so straightforward. What is interesting is that we can define a new function called "compose" that, given functions "f" and "g", combines them in this way without even knowing their signatures. + +_Так далеко, так просто._ Это интересно, мы можем определить новую функцию "compose", которая принимает функции "f" и "g" и комбинирует их даже не зная их сигнатуры. ```fsharp let compose f g x = g ( f(x) ) ``` -If you evaluate this, you will see that the compiler has correctly deduced that if "`f`" is a function from generic type `'a` to generic type `'b`, then "`g`" is constrained to have generic type `'b` as an input. And the overall signature is: +> If you evaluate this, you will see that the compiler has correctly deduced that if "`f`" is a function from generic type `'a` to generic type `'b`, then "`g`" is constrained to have generic type `'b` as an input. And the overall signature is: + +После выполнения можно увидеть, что компилятор правильно решил, что "`f`" - это функция с обощенного типа `'a` к обобщенному типу `'b`, а "`g`" ограниченна вводом типа `'b`: ```fsharp val compose : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c ``` -(Note that this generic composition operation is only possible because every function has one input and one output. This approach would not be possible in a non-functional language.) +> (Note that this generic composition operation is only possible because every function has one input and one output. This approach would not be possible in a non-functional language.) + +(Обратите внимание, что обобщенная композиция операций возможна только благодаря тому, что каждая функция имеет ровно один входной параметр и один вывод. Данный подход невозможен в нефункциональных языках.) -As we have seen, the actual definition of compose uses the "`>>`" symbol. +> As we have seen, the actual definition of compose uses the "`>>`" symbol. + +Как мы видим, данное определение используется для оператора "`>>`". ```fsharp let (>>) f g x = g ( f(x) ) ``` -Given this definition, we can now use composition to build new functions from existing ones. +> Given this definition, we can now use composition to build new functions from existing ones. + +Благодаря данному определению можно строить новые функции на основе существующих при помощи композиции. ```fsharp let add1 x = x + 1 @@ -107,21 +153,29 @@ let add1Times2 x = (>>) add1 times2 x add1Times2 3 ``` -This explicit style is quite cluttered. We can do a few things to make it easier to use and understand. +> This explicit style is quite cluttered. We can do a few things to make it easier to use and understand. + +Явная запись весьма громоздка. Но можно сделать ее использование более простым для понимания. -First, we can leave off the x parameter so that the composition operator returns a partial application. +> First, we can leave off the x parameter so that the composition operator returns a partial application. + +Во первых, можно избавиться от параметра `x`, и композиция вернет частичное применение. ```fsharp let add1Times2 = (>>) add1 times2 ``` -And now we have a binary operation, so we can put the operator in the middle. +> And now we have a binary operation, so we can put the operator in the middle. + +Во вторых, т.к. `>>` является бинарным оператором, можно поместить его в центре. ```fsharp let add1Times2 = add1 >> times2 ``` -And there you have it. Using the composition operator allows code to be cleaner and more straightforward. +> And there you have it. Using the composition operator allows code to be cleaner and more straightforward. + +Применение композиции делает код чище и понятнее. ```fsharp let add1 x = x + 1 @@ -134,11 +188,15 @@ let add1Times2 x = times2(add1 x) let add1Times2 = add1 >> times2 ``` -## Using the composition operator in practice ## +## Using the composition operator in practice | Использование оператора композиции на практике ## + +> The composition operator (like all infix operators) has lower precedence than normal function application. This means that the functions used in composition can have arguments without needing to use parentheses. + +Оператор композиции (как и все инфиксные операторы) имеет более низкий приоритет чем обычные функции. Это означает, что функции использованные в композиции могут иметь аргументы без использования скобок. -The composition operator (like all infix operators) has lower precedence than normal function application. This means that the functions used in composition can have arguments without needing to use parentheses. +> For example, if the "add" and "times" functions have an extra parameter, this can be passed in during the composition. -For example, if the "add" and "times" functions have an extra parameter, this can be passed in during the composition. +Например, если у функций "add" и "times" есть параметры, они могут быть переданы во время композиции. ```fsharp let add n x = x + n @@ -150,15 +208,21 @@ let add5Times3 = add 5 >> times 3 add5Times3 1 ``` -As long as the inputs and outputs match, the functions involved can use any kind of value. For example, consider the following, which performs a function twice: +> As long as the inputs and outputs match, the functions involved can use any kind of value. For example, consider the following, which performs a function twice: + +Пока соответствующие входы и выходы функций совпадают, функции могут использовать любые значения. Например, рассмотрим следующий код, который выполняет функцию дважды: ```fsharp let twice f = f >> f //signature is ('a -> 'a) -> ('a -> 'a) ``` -Note that the compiler has deduced that the function f must use the same type for both input and output. +> Note that the compiler has deduced that the function f must use the same type for both input and output. + +Обратите внимание, что компилятор вывел, что "`f`" принимает и возвращает значения одного типа. -Now consider a function like "`+`". As we have seen earlier, the input is an `int`, but the output is actually a partially applied function `(int->int)`. The output of "`+`" can thus be used as the input of "`twice`". So we can write something like: +> Now consider a function like "`+`". As we have seen earlier, the input is an `int`, but the output is actually a partially applied function `(int->int)`. The output of "`+`" can thus be used as the input of "`twice`". So we can write something like: + +Теперь рассмотрим функцию "`+`". Как мы видели ранее, ввод является `int`-ом, но вывод в действительности - `(int->int)`. Таким образом "`+`" может быть использована в "`twice`". Поэтому можно написать: ```fsharp let add1 = (+) 1 // signature is (int -> int) @@ -168,15 +232,21 @@ let add1Twice = twice add1 // signature is also (int -> int) add1Twice 9 ``` -On the other hand, we can't write something like: +> On the other hand, we can't write something like: + +С другой стороны нельзя написать: ```fsharp let addThenMultiply = (+) >> (*) ``` -because the input to "*" must be an `int` value, not an `int->int` function (which is what the output of addition is). +> because the input to "*" must be an `int` value, not an `int->int` function (which is what the output of addition is). + +Потому-что ввод "*" должен быть `int`, а не `int->int` функцией (который является выходом сложения). -But if we tweak it so that the first function has an output of just `int` instead, then it does work: +> But if we tweak it so that the first function has an output of just `int` instead, then it does work: + +Но если подправить первую функцию так, чтобы она возвращала только `int`, все заработает: ```fsharp let add1ThenMultiply = (+) 1 >> (*) @@ -186,14 +256,18 @@ let add1ThenMultiply = (+) 1 >> (*) add1ThenMultiply 2 7 ``` -Composition can also be done backwards using the "`<<`" operator, if needed. +> Composition can also be done backwards using the "`<<`" operator, if needed. + +Композиция также может быть выполнена в обратном порядке посредством "`<<`", если это необходимо: ```fsharp let times2Add1 = add 1 << times 2 times2Add1 3 ``` -Reverse composition is mainly used to make code more English-like. For example, here is a simple example: +> Reverse composition is mainly used to make code more English-like. For example, here is a simple example: + +Обратная композиция в основном используется, чтобы сделать код более похожим на английский язык ("English-like"). Например: ```fsharp let myList = [] @@ -202,17 +276,23 @@ myList |> List.isEmpty |> not // straight pipeline myList |> (not << List.isEmpty) // using reverse composition ``` -## Composition vs. pipeline ## +## Composition vs. pipeline | Композиция vs. конвеер ## + +> At this point, you might be wondering what the difference is between the composition operator and the pipeline operator, as they can seem quite similar. + +Вы можете быть сбиты с толку небольшой разницей между композицией и конвеером, т.к. они могут выглядеть очень похожими. -At this point, you might be wondering what the difference is between the composition operator and the pipeline operator, as they can seem quite similar. +> First let's look again at the definition of the pipeline operator: -First let's look again at the definition of the pipeline operator: +Во первых, посмотрите на определение конвеера: ```fsharp let (|>) x f = f x ``` -All it does is allow you to put the function argument in front of the function rather than after. That's all. If the function has multiple parameters, then the input would be the final parameter. Here's the example used earlier. +> All it does is allow you to put the function argument in front of the function rather than after. That's all. If the function has multiple parameters, then the input would be the final parameter. Here's the example used earlier. + +Все это позволяет поставить аргументы функций перед ней, а не после. Вот и все. Если у функции есть несколько параметров, то ввод должен быть последним параметром (в текущем наборе параметров, а не вообще). Пример встречаемый ранее: ```fsharp let doSomething x y z = x+y+z @@ -220,7 +300,9 @@ doSomething 1 2 3 // all parameters after function 3 |> doSomething 1 2 // last parameter piped in ``` -Composition is not the same thing and cannot be a substitute for a pipe. In the following case the number 3 is not even a function, so its "output" cannot be fed into `doSomething`: +> Composition is not the same thing and cannot be a substitute for a pipe. In the following case the number 3 is not even a function, so its "output" cannot be fed into `doSomething`: + +Композиция не тоже самое и не может быть заменой пайпу. В следующем примере число 3 даже не функция, поэтому "вывод" не может быть передан в `doSomething`: ```fsharp 3 >> doSomething 1 2 // not allowed @@ -230,9 +312,13 @@ doSomething 1 2 ( 3(x) ) // implies 3 should be a function! // but here has type int ``` -The compiler is complaining that "3" should be some sort of function `'a->'b`. +> The compiler is complaining that "3" should be some sort of function `'a->'b`. -Compare this with the definition of composition, which takes 3 arguments, where the first two must be functions. +Компилятор жалуется, что значение "3" должно быть разновидностью функций `'a->'b`. + +> Compare this with the definition of composition, which takes 3 arguments, where the first two must be functions. + +Сравните это с определением композиции, которая берет 3 аргумента, где первые два должны быть функциями. ```fsharp let (>>) f g x = g ( f(x) ) @@ -242,7 +328,9 @@ let times n x = x * n let add1Times2 = add 1 >> times 2 ``` -Trying to use a pipe instead doesn't work. In the following example, "`add 1`" is a (partial) function of type `int->int`, and cannot be used as the second parameter of "`times 2`". +> Trying to use a pipe instead doesn't work. In the following example, "`add 1`" is a (partial) function of type `int->int`, and cannot be used as the second parameter of "`times 2`". + +Попытки использовать конвейера вместо композиции обернутся ошибкой компиляции. В следующем примере "`add 1`" - это (частичная) функция `int->int`, которая не может быть использвоана в качестве второго параметра для "`times 2`". ```fsharp let add1Times2 = add 1 |> times 2 // not allowed @@ -251,4 +339,6 @@ let add1Times2 = times 2 (add 1) // add1 should be an int // error FS0001: Type mismatch. 'int -> int' does not match 'int' ``` -The compiler is complaining that "`times 2`" should take an `int->int` parameter, that is, be of type `(int->int)->'a`. +> The compiler is complaining that "`times 2`" should take an `int->int` parameter, that is, be of type `(int->int)->'a`. + +Компилятор пожалуется, что "`times 2`" необходимо принимать параметр `int->int`, т.е. быть функцией `(int->int)->'a`. \ No newline at end of file diff --git a/posts/function-signatures.md b/posts/function-signatures.md index 8043875..90b3534 100644 --- a/posts/function-signatures.md +++ b/posts/function-signatures.md @@ -8,7 +8,9 @@ seriesOrder: 9 categories: [Functions] --- -It may not be obvious, but F# actually has two syntaxes - one for normal (value) expressions, and one for type definitions. For example: +> It may not be obvious, but F# actually has two syntaxes - one for normal (value) expressions, and one for type definitions. For example: + +Это может быть неочевидным, но в F# существует два синтаксиса, один для нормальных выражений, другой для определения типов. Например: ```fsharp [1;2;3] // a normal expression @@ -21,11 +23,17 @@ int option // a type expression int * string // a type expression ``` -Type expressions have a special syntax that is *different* from the syntax used in normal expressions. You have already seen many examples of this when you use the interactive session, because the type of each expression has been printed along with its evaluation. +> Type expressions have a special syntax that is *different* from the syntax used in normal expressions. You have already seen many examples of this when you use the interactive session, because the type of each expression has been printed along with its evaluation. + +Выражения типов имеют специальный синтаксис, который *отличается* от синтаксиса используемого в обычных выражениях. Вы уже видели множество примеров, где использовалася интерактивная сессия, потому что тип каждого выражения выводился вместе с результатом выполнения. + +> As you know, F# uses type inference to deduce types, so you don't often need to explicitly specify types in your code, especially for functions. But in order to work effectively in F#, you *do* need to understand the type syntax, so that you can build your own types, debug type errors, and understand function signatures. In this post, we'll focus on its use in function signatures. -As you know, F# uses type inference to deduce types, so you don't often need to explicitly specify types in your code, especially for functions. But in order to work effectively in F#, you *do* need to understand the type syntax, so that you can build your own types, debug type errors, and understand function signatures. In this post, we'll focus on its use in function signatures. +Как вы знаете, F# использует вывод типов для их определения, поэтому вам зачастую не надо явно прописывать типы в вашем коде специально для функций. Однако для эффективной работы в F#, необходимо понимать синтаксис типов, чтобы вы могли определять свои собственные типы, отлаживать ошибки вывода типов и понимать сигнатуры функций. В данном посте я сосредоточусь на использовании сигнатур функций. -Here are some example function signatures using the type syntax: +> Here are some example function signatures using the type syntax: + +Вот несколько примеров сигнатур функций использующих синтаксис типов: ```fsharp // expression syntax // type syntax @@ -38,89 +46,119 @@ List.filter // ('a -> bool) -> 'a list -> 'a list List.map // ('a -> 'b) -> 'a list -> 'b list ``` -## Understanding functions through their signatures ## +## Understanding functions through their signatures | Понимание функций через сигнатуры ## + +> Just by examining a function's signature, you can often get some idea of what it does. Let's look at some examples and analyze them in turn. -Just by examining a function's signature, you can often get some idea of what it does. Let's look at some examples and analyze them in turn. +Часто, даже просто изучив сигнатуру функции, можно получить некоторое представление, о том, что она делает. Рассмотрим несколько примеров и проанализируем их по очереди. ```fsharp // function signature 1 int -> int -> int ``` -This function takes two `int` parameters and returns another, so presumably it is some sort of mathematical function such as addition, subtraction, multiplication, or exponentiation. +> This function takes two `int` parameters and returns another, so presumably it is some sort of mathematical function such as addition, subtraction, multiplication, or exponentiation. + +Данная функция берет два `int` параметра и возвращает еще один `int`, скорее всего, это разновидность математических функций, таких как сложение, вычитание, умножение или возведение в степень. ```fsharp // function signature 2 int -> unit ``` -This function takes an `int` and returns a `unit`, which means that the function is doing something important as a side-effect. Since there is no useful return value, the side effect is probably something to do with writing to IO, such as logging, writing to a file or database, or something similar. +> This function takes an `int` and returns a `unit`, which means that the function is doing something important as a side-effect. Since there is no useful return value, the side effect is probably something to do with writing to IO, such as logging, writing to a file or database, or something similar. + +Данная функция принимает `int` и возвращает `unit`, что означает, что функция делает что-то важное в виде side-эффекта. Т.к. она не возвращает полезного значения, side-эффект скорее всего производит операции записи в IO, такие как логирование, запись в базу данных или что-нибудь похожее. ```fsharp // function signature 3 unit -> string ``` -This function takes no input but returns a `string`, which means that the function is conjuring up a string out of thin air! Since there is no explicit input, the function probably has something to do with reading (from a file say) or generating (a random string, say). +> This function takes no input but returns a `string`, which means that the function is conjuring up a string out of thin air! Since there is no explicit input, the function probably has something to do with reading (from a file say) or generating (a random string, say). + +Эта функция ничего не принимает, но возвращает `string`, что может означать, что функция получает строку из воздуха! Поскольку нет явного ввода, функция вероятно делает что-то с чтением (скажем из файла) или генерацией (н-р. случайная строка). ```fsharp // function signature 4 int -> (unit -> string) ``` -This function takes an `int` input and returns a function that when called, returns strings. Again, the function probably has something to do with reading or generating. The input probably initializes the returned function somehow. For example, the input could be a file handle, and the returned function something like `readline()`. Or the input could be a seed for a random string generator. We can't tell exactly, but we can make some educated guesses. +> This function takes an `int` input and returns a function that when called, returns strings. Again, the function probably has something to do with reading or generating. The input probably initializes the returned function somehow. For example, the input could be a file handle, and the returned function something like `readline()`. Or the input could be a seed for a random string generator. We can't tell exactly, but we can make some educated guesses. + +Эта функция принимает `int` и возвращает функцию, которая при вызове возвращает строку. Опять же, вероятно функция производит операцию чтения или генерации. Ввод скорее всего каким-то образом инициализирует возвращаемую функцию. Например, ввод может быть идентификатором файла, а возвращаемая функция являться чем-то подобным `readline()`. Или ввод может быть конфигурацией генератора случайных строк. Мы не можем сказать точно, однако мы можем сделать некоторые обоснованные выводы. ```fsharp // function signature 5 'a list -> 'a ``` -This function takes a list of some type, but returns only one of that type, which means that the function is merging or choosing elements from the list. Examples of functions with this signature are `List.sum`, `List.max`, `List.head` and so on. +> This function takes a list of some type, but returns only one of that type, which means that the function is merging or choosing elements from the list. Examples of functions with this signature are `List.sum`, `List.max`, `List.head` and so on. + +Функция принимает список некоторого типа, но возвращает лишь один элемент данного типа, что может означать, что функция аггрегирует список или выбирает один из его элементов. Подобную сигнатуру имеют `List.sum`, `List.max`, `List.head` и т.д. ```fsharp // function signature 6 ('a -> bool) -> 'a list -> 'a list ``` -This function takes two parameters: the first is a function that maps something to a bool (a predicate), and the second is a list. The return value is a list of the same type. Predicates are used to determine whether a value meets some sort of criteria, so it looks like the function is choosing elements from the list based on whether the predicate is true or not and then returning a subset of the original list. A typical function with this signature is `List.filter`. +> This function takes two parameters: the first is a function that maps something to a bool (a predicate), and the second is a list. The return value is a list of the same type. Predicates are used to determine whether a value meets some sort of criteria, so it looks like the function is choosing elements from the list based on whether the predicate is true or not and then returning a subset of the original list. A typical function with this signature is `List.filter`. + +Эта функция принимает два параметра: первый - функция которая преобразует что-либо в `bool` (предикат), а второй - список. Возвращаемое значения является списком того же типа. Предикаты используют, чтобы определить, соответствует ли определенный объект какому-либо критерию, похоже, что данная функция выбирает элементы из списка на основе того, принимает ли предикат значение истинны или нет, после чего возвращает подмножество исходного списка. Типичной функцией с подобной сигнатурой является `List.filter`. ```fsharp // function signature 7 ('a -> 'b) -> 'a list -> 'b list ``` -This function takes two parameters: the first maps type `'a` to type `'b`, and the second is a list of `'a`. The return value is a list of a different type `'b`. A reasonable guess is that the function takes each of the `'a`s in the list, maps them to a `'b` using the function passed in as the first parameter, and returns the new list of `'b`s. And indeed, the prototypical function with this signature is `List.map`. +> This function takes two parameters: the first maps type `'a` to type `'b`, and the second is a list of `'a`. The return value is a list of a different type `'b`. A reasonable guess is that the function takes each of the `'a`s in the list, maps them to a `'b` using the function passed in as the first parameter, and returns the new list of `'b`s. And indeed, the prototypical function with this signature is `List.map`. + +Функция принимает два парамерта: первый - преобразует тип `'a` в тип `'b`, а второй - список типа `'a`. Возвращаемое значение является списком другого типа `'b`. Разумно будет предположить, что функция берет каждый элемент из списка `'a`, и преобразует их в `'b` используя функцию переданную в качестве первого параметра, после чего возвращает список `'b`. И действительно, `List.map` является прообразом функции с такой сигнатурой. + +### Using function signatures to find a library method | Поиск библиотечных методов при помощи сигнатуры функции ### -### Using function signatures to find a library method ### +> Function signatures are an important part of searching for library functions. The F# libraries have hundreds of functions in them and they can initially be overwhelming. Unlike an object oriented language, you cannot simply "dot into" an object to find all the appropriate methods. However, if you know the signature of the function you are looking for, you can often narrow down the list of candidates quickly. -Function signatures are an important part of searching for library functions. The F# libraries have hundreds of functions in them and they can initially be overwhelming. Unlike an object oriented language, you cannot simply "dot into" an object to find all the appropriate methods. However, if you know the signature of the function you are looking for, you can often narrow down the list of candidates quickly. +Сигнатуры функций важная часть поиска библиотечных функций. Библиотеки F# содержат сотни функций, что вначале может быть ошеломляющим. В отличии от объектно ориентированных языков, вы не можете просто "войти в объект" через точку, чтобы найти все связанные методы. Однако, если вы знаете сигнатуру функции, которую желаете найти, вы зачастую можете быстро сузить список кандидатов. -For example, let's say you have two lists and you are looking for a function to combine them into one. What would the signature be for this function? It would take two list parameters and return a third, all of the same type, giving the signature: +> For example, let's say you have two lists and you are looking for a function to combine them into one. What would the signature be for this function? It would take two list parameters and return a third, all of the same type, giving the signature: + +Например, если скажем вы имеете два списка, и вы хотите найти функцию комбинирующую их в один. Какой сигнатурой обладала бы искомая функция? Она должна была бы принимать два списка в качестве параметров и возвращать третий, все одного типа: ```fsharp 'a list -> 'a list -> 'a list ``` -Now go to the [MSDN documentation for the F# List module](http://msdn.microsoft.com/en-us/library/ee353738), and scan down the list of functions, looking for something that matches. As it happens, there is only one function with that signature: +> Now go to the [MSDN documentation for the F# List module](http://msdn.microsoft.com/en-us/library/ee353738), and scan down the list of functions, looking for something that matches. As it happens, there is only one function with that signature: + +Теперь перейдем на [сайт документации MSDN для модуля List](http://msdn.microsoft.com/en-us/library/ee353738), и поищем список функций, в поисках чего-либо похожего. Оказывается, что существует лишь одна функция с такой сигнатурой: ```fsharp append : 'T list -> 'T list -> 'T list ``` -which is exactly the one we want! +> which is exactly the one we want! -## Defining your own types for function signatures ## +Это именно то, что нам нужно! -Sometimes you may want to create your own types to match a desired function signature. You can do this using the "type" keyword, and define the type in the same way that a signature is written: +## Defining your own types for function signatures | Определение своего собственного типа для сигнатур функций ## + +> Sometimes you may want to create your own types to match a desired function signature. You can do this using the "type" keyword, and define the type in the same way that a signature is written: + +Иногда может понадобиться создать функцию своего собственного типа, чтобы соответствовать требуемой сигнатуре функции. Это можно сделать при помощи ключевого слова "type", аналогично примеру ниже: ```fsharp type Adder = int -> int type AdderGenerator = int -> Adder ``` -You can then use these types to constrain function values and parameters. +> You can then use these types to constrain function values and parameters. + +Можно использовать данный тип, чтобы ограничить функции-значения и параметры. -For example, the second definition below will fail because of type constraints. If you remove the type constraint (as in the third definition) there will not be any problem. +> For example, the second definition below will fail because of type constraints. If you remove the type constraint (as in the third definition) there will not be any problem. + +Например, второе определение ниже, упадет, т.к. тип ограничен. Если мы удалим ограничение типов (как в третьем определении) проблем не возникнет. ```fsharp let a:AdderGenerator = fun x -> (fun y -> x + y) @@ -128,9 +166,11 @@ let b:AdderGenerator = fun (x:float) -> (fun y -> x + y) let c = fun (x:float) -> (fun y -> x + y) ``` -## Test your understanding of function signatures ## +## Test your understanding of function signatures | Проверка понимания сигнатур функций ## + +> How well do you understand function signatures? See if you can create simple functions that have each of these signatures. Avoid using explicit type annotations! -How well do you understand function signatures? See if you can create simple functions that have each of these signatures. Avoid using explicit type annotations! +Как хорошо вы понимаете сигнатуры функций? Посмотрите, сможете ли вы создать простые функции, которые имеют подобные сигнатуры. Избегайте явных аннотаций типов! ```fsharp val testA = int -> int diff --git a/posts/function-values-and-simple-values.md b/posts/function-values-and-simple-values.md index 24ea18b..898b7c6 100644 --- a/posts/function-values-and-simple-values.md +++ b/posts/function-values-and-simple-values.md @@ -7,20 +7,31 @@ seriesId: "Thinking functionally" seriesOrder: 3 --- -Let's look at the simple function again +> Let's look at the simple function again + +Еще раз рассмотрим эту простую функцию. ```fsharp let add1 x = x + 1 ``` -What does the "x" mean here? It means: +> What does the "x" mean here? It means: + +Что здесь означает "x"? Он означает: + +> 1. Accept some value from the input domain. +> 2. Use the name "x" to represent that value so that we can refer to it later. + +1. Принимает некоторое значение из domain (области определения). +2. Использует имя "x", чтобы представлять значение, к которому мы можем обратиться позже. + +> This process of using a name to represent a value is called "binding". The name "x" is "bound" to the input value. -1. Accept some value from the input domain. -2. Use the name "x" to represent that value so that we can refer to it later. +Процесс использования имени для представления значения называется "привязкой" (binding). Имя "x" "привязано" к входному значению. -This process of using a name to represent a value is called "binding". The name "x" is "bound" to the input value. +> So if we evaluate the function with the input 5 say, what is happening is that everywhere we see "x" in the original definition, we replace it with "5", sort of like search and replace in a word processor. -So if we evaluate the function with the input 5 say, what is happening is that everywhere we see "x" in the original definition, we replace it with "5", sort of like search and replace in a word processor. +Так что если вычислить функцию с вводом, скажем, равным 5, то произойдет следующее. Везде где стоит "x" в изначальном определении, ставится "5", аналогично функции "найти и заменить" в текстовом редакторе. ```fsharp let add1 x = x + 1 @@ -30,17 +41,27 @@ add1 5 // result is 6 ``` -It is important to understand that this is not assignment. "x" is not a "slot" or variable that is assigned to the value and can be assigned to another value later on. It is a onetime association of the name "x" with the value. The value is one of the predefined integers, and cannot change. And so, once bound, x cannot change either; once associated with a value, always associated with a value. +> It is important to understand that this is not assignment. "x" is not a "slot" or variable that is assigned to the value and can be assigned to another value later on. It is a onetime association of the name "x" with the value. The value is one of the predefined integers, and cannot change. And so, once bound, x cannot change either; once associated with a value, always associated with a value. + +Важно понимать, что это не присваивание. "x" не "слот" и не переменная с присвоеным значением, которые можно изменить позднее. Это разовая ассоциация имени "x" с неким значением. Это иначение одно из предопределенных целых чисел, оно не может быть изменено. Т.е. однажды привязанный, x _не может быть измененен_. Метка единожды ассоциированная со значением, навсегда связана с этим значением. + +> This concept is a critical part of thinking functionally: *there are no "variables", only values*. + +Данный принцип - критически важная часть функционального мышления: *нет "переменных", только значения*. + +## Function values | Функции как значения ## + +> If you think about this a bit more, you will see that the name "`add1`" itself is just a binding to "the function that adds one to its input". The function itself is independent of the name it is bound to. -This concept is a critical part of thinking functionally: *there are no "variables", only values*. +Если поразмыслить над этим чуть подольше, можно увидеть, что имя "`add`" само по себе - это просто привязка к "функция которая увеличивает ввод на еденицу.". Сама функция независима от имени которое к ней привязано. -## Function values ## +> When you type `let add1 x = x + 1` you are telling the F# compiler "every time you see the name "`add1`", replace it with the function that adds 1 to its input". "`add1`" is called a **function value**. -If you think about this a bit more, you will see that the name "`add1`" itself is just a binding to "the function that adds one to its input". The function itself is independent of the name it is bound to. +Введя `let add1 x = x + 1`, мы говорим компилятору F# "каждый раз когда ты видешь имя "`add1`", замени его на функцию, которая добавляет 1 к вводу". "`add1`" называется **функцией-значением (function value)**. -When you type `let add1 x = x + 1` you are telling the F# compiler "every time you see the name "`add1`", replace it with the function that adds 1 to its input". "`add1`" is called a **function value**. +> To see that the function is independent of its name, try: -To see that the function is independent of its name, try: +Чтобы увидеть, что функция независит от ее имени, достаточно выполнить следующий код: ```fsharp let add1 x = x + 1 @@ -49,49 +70,71 @@ add1 5 plus1 5 ``` -You can see that "`add1`" and "`plus1`" are two names that refer ("bound to") to the same function. +> You can see that "`add1`" and "`plus1`" are two names that refer ("bound to") to the same function. -You can always identify a function value because its signature has the standard form `domain -> range`. Here is a generic function value signature: +Как видно, "`add`'" и "`plus`" - это два имени ссылающихся (привязаныых к) одной и той же функции. + +> You can always identify a function value because its signature has the standard form `domain -> range`. Here is a generic function value signature: + +Идентифицировать функцию-значение всегда можно по ее сигнатуре, которая имеет страндартную форму `domain -> range`. Обобщенная сигнатура функции-значения: ```fsharp val functionName : domain -> range ``` -## Simple values ## +## Simple values | Простые значения ## + +> Imagine an operation that always returned the integer 5 and didn't have any input. -Imagine an operation that always returned the integer 5 and didn't have any input. +Представим операцию, которая ничего не принимает и всегда возвращает 5. ![](../assets/img/Functions_Const.png) -This would be a "constant" operation. +> This would be a "constant" operation. + +Это была бы "костантная" операция. + +> How would we write this in F#? We want to tell the F# compiler "every time you see the name `c`, replace it with 5". Here's how: -How would we write this in F#? We want to tell the F# compiler "every time you see the name `c`, replace it with 5". Here's how: +Как можно было бы описать это в F#? Мы хотим сказать компилятору, "каждый раз, когда ты видишь имя `c`, замени его на 5. Вот так: ```fsharp let c = 5 ``` -which when evaluated, returns: +> which when evaluated, returns: + +при вычислении вернется: ```fsharp val c : int = 5 ``` -There is no mapping arrow this time, just a single int. What's new is an equals sign with the actual value printed after it. The F# compiler knows that this binding has a known value which it will always return, namely the value 5. +> There is no mapping arrow this time, just a single int. What's new is an equals sign with the actual value printed after it. The F# compiler knows that this binding has a known value which it will always return, namely the value 5. + +В этот раз нет стрелки сопоставления, всего лишь один int. Из нового - знак равенства с реальным значением выведенным после него. F# компилятор знает, что эта привязка имеет известное значение, которое будет возвращаться всегда, а именно число 5. + +> In other words, we've just defined a constant, or in F# terms, a simple value. + +Другими словами, мы только что определили константу, или в терминах F#, простое значение. -In other words, we've just defined a constant, or in F# terms, a simple value. +> You can always tell a simple value from a function value because all simple values have a signature that looks like: -You can always tell a simple value from a function value because all simple values have a signature that looks like: +Dсегда можно отличить простое значение от функции-значения, потому все простые значения имеют подобную сигнатуру: ```fsharp val aName: type = constant // Note that there is no arrow ``` -## Simple values vs. function values ## +## Simple values vs. function values | Простые значение vs. функции-значения ## -It is important to understand that in F#, unlike languages such as C#, there is very little difference between simple values and function values. They are both values which can be bound to names (using the same keyword `let`) and then passed around. And in fact, one of the key aspects of thinking functionally is exactly that: *functions are values that can be passed around as inputs to other functions*, as we will soon see. +> It is important to understand that in F#, unlike languages such as C#, there is very little difference between simple values and function values. They are both values which can be bound to names (using the same keyword `let`) and then passed around. And in fact, one of the key aspects of thinking functionally is exactly that: *functions are values that can be passed around as inputs to other functions*, as we will soon see. -Note that there is a subtle difference between a simple value and a function value. A function always has a domain and range and must be "applied" to an argument to get a result. A simple value does not need to be evaluated after being bound. Using the example above, if we wanted to define a "constant function" that returns five we would have to use +Важно понять, что в F#, в отличии от других языков, таких как C#, существует очень небольшая разница между простыми значения и функциями-значениями. Оба типа являются значениями, которые могут быть связаны с именами (используя ключевое слово `let`), после чего они могут быть переданы везде. На самом деле, скоро мы увидим, идея о том, что *функции это значения которые могут быть переданы как входные данные другим функциям*, является одним из ключевых аспектов функционального мышления. + +> Note that there is a subtle difference between a simple value and a function value. A function always has a domain and range and must be "applied" to an argument to get a result. A simple value does not need to be evaluated after being bound. Using the example above, if we wanted to define a "constant function" that returns five we would have to use + +Следует учесть, что существует небольшая разница между простым значением и функцией-значением. Функция всегда имеет domain и range, и должна быть "применена" к аргументу, чтобы вернуь результат. Простое значение не надо вычислять после привязки. Используя пример выше, если мы захотим определить "константную функцию", которая возвращает 5 мы могли бы использовать: ```fsharp let c = fun()->5 @@ -99,47 +142,69 @@ let c = fun()->5 let c() = 5 ``` -The signature for these functions is: +> The signature for these functions is: + +Сигнатура таких функций выглядит так: ```fsharp val c : unit -> int ``` -instead of: +> instead of: + +А не так: ```fsharp val c : int = 5 ``` -More on unit, function syntax and anonymous functions later. +> More on unit, function syntax and anonymous functions later. + +Больше информации о `unit`, синтаксисе функций и анонимных функциях будет сказано позднее. -## "Values" vs. "Objects" ## +## "Values" vs. "Objects" | "Значения" vs. "Объекты" ## -In a functional programming language like F#, most things are called "values". In an object-oriented language like C#, most things are called "objects". So what is the difference between a "value" and an "object"? +> In a functional programming language like F#, most things are called "values". In an object-oriented language like C#, most things are called "objects". So what is the difference between a "value" and an "object"? -A value, as we have seen above, is just a member of a domain. The domain of ints, the domain of strings, the domain of functions that map ints to strings, and so on. In principle, values are immutable. And values do not have any behavior attached them. +В функциональных языках программирования, таких как F#, большинство вещей называется "значениями". В объектно-ориентированных языках, таких как C#, большинство вещей называются "объектами". Какова разница между "значением" и "объектом"? -An object, in a standard definition, is an encapsulation of a data structure with its associated behavior (methods). In general, objects are expected to have state (that is, be mutable), and all operations that change the internal state must be provided by the object itself (via "dot" notation). +> A value, as we have seen above, is just a member of a domain. The domain of ints, the domain of strings, the domain of functions that map ints to strings, and so on. In principle, values are immutable. And values do not have any behavior attached them. -In F#, even the primitive values have some object-like behavior. For example, you can dot into a string to get its length: +Значение, как мы видели выше, является членом domain (домен). Домен целых чисел, домен строк, домен функций которые сопоставляют целым числам строки, и так далее. В принципе, значения иммутабельны (не изменяемы). И значения не имеют поведения прикрепленного к ним. + +> An object, in a standard definition, is an encapsulation of a data structure with its associated behavior (methods). In general, objects are expected to have state (that is, be mutable), and all operations that change the internal state must be provided by the object itself (via "dot" notation). + +Объекты, в каноническом определении, являются инкапсуляцией структуры данных с ассоциированным поведением (методами). В общем случае, объекты должны иметь состояние (то есть, быть изменяемыми), и все операции которые меняют внутреннее состояние должны предоставляться самим объектом (через "dot"-нотацию). + +> In F#, even the primitive values have some object-like behavior. For example, you can dot into a string to get its length: + +В F#, даже примитивные значения обладают некоторым объем "объектного" поведения. Напимер, можно через точку получить длину строки: ```fsharp "abc".Length ``` -But, in general, we will avoid using "object" for standard values in F#, reserving it to refer to instances of true classes, or other values that expose member methods. +> But, in general, we will avoid using "object" for standard values in F#, reserving it to refer to instances of true classes, or other values that expose member methods. + +> Но в целом, мы будем избегать использования "объекта" для стандартны значений в F#, приберегая его для обращения к полноценным классам, или другим значениям, предоставляющим методы. -## Naming Values ## +## Naming Values | Именование значений ## -Standard naming rules are used for value and function names, basically, any alphanumeric string, including underscores. There are a couple of extras: +> Standard naming rules are used for value and function names, basically, any alphanumeric string, including underscores. There are a couple of extras: -You can put an apostrophe anywhere in a name, except the first character. So: +Стандартные правила именования используемые для имен значений и функций, в основном, алфавитно-цифровая строка + символы подчеркивания. Но есть пара дополнений: + +> You can put an apostrophe anywhere in a name, except the first character. So: + +Можно добавлять апостроф в любой части имени, исключая первый символ. ```fsharp A'b'c begin' // valid names ``` -The final tick is often used to signal some sort of "variant" version of a value: +> The final tick is often used to signal some sort of "variant" version of a value: + +Последний случай часто используется как метка для "различных" версий какого-либо значения: ```fsharp let f = x @@ -147,27 +212,37 @@ let f' = derivative f let f'' = derivative f' ``` -or define variants of existing keywords +> or define variants of existing keywords + +или для переменных одноименных существующим ключевым словам ```fsharp let if' b t f = if b then t else f ``` -You can also put double backticks around any string to make a valid identifier. +> You can also put double backticks around any string to make a valid identifier. + +Можно также использовать двойные обратные ковычки для любой строки, чтобы сделать ее допустимым идентификатором. ```fsharp ``this is a name`` ``123`` //valid names ``` -You might want to use the double backtick trick sometimes: +> You might want to use the double backtick trick sometimes: -* When you want to use an identifier that is the same as a keyword +Случаи, когда может понадобиться использовать трюк с двойными обратными ковычками: + +> * When you want to use an identifier that is the same as a keyword + +* Когда необходимо использовать идентификатор, который совпадает с ключевым словом ```fsharp let ``begin`` = "begin" ``` -* When trying to use natural language for business rules, unit tests, or BDD style executable specifications a la Cucumber. +> * When trying to use natural language for business rules, unit tests, or BDD style executable specifications a la Cucumber. + +Когда необходимо использовать естественные языки для бизнес правил, модульных тестов, или BBD стиля исполняемой документации типа Cucumber. ```fsharp let ``is first time customer?`` = true @@ -183,4 +258,6 @@ let [] ``I have (.*) N products in my cart`` (n:int) = // code here ``` -Unlike C#, the naming convention for F# is that functions and values start with lowercase letters rather than uppercase (`camelCase` rather than `PascalCase`) unless designed for exposure to other .NET languages. Types and modules use uppercase however. +> Unlike C#, the naming convention for F# is that functions and values start with lowercase letters rather than uppercase (`camelCase` rather than `PascalCase`) unless designed for exposure to other .NET languages. Types and modules use uppercase however. + +В отличии от C#, конвенция именования F# требует, чтобы функции и значения начинались со строчной буквы, а не прописной (`camelCase`, а не `PascalCase`), кроме тех случаев, когда они предназначены для _взаимодействия с другими_ языками .NET. Однако типы и модули использут заглавные буквы (в начале). \ No newline at end of file diff --git a/posts/how-types-work-with-functions.md b/posts/how-types-work-with-functions.md index ce63848..59ab2e8 100644 --- a/posts/how-types-work-with-functions.md +++ b/posts/how-types-work-with-functions.md @@ -8,38 +8,55 @@ seriesOrder: 4 categories: [Types, Functions] --- -Now that we have some understanding of functions, we'll look at how types work with functions, both as domains and ranges. This is just an overview; the series ["understanding F# types"](../series/understanding-fsharp-types.md) will cover types in detail. +> Now that we have some understanding of functions, we'll look at how types work with functions, both as domains and ranges. This is just an overview; the series ["understanding F# types"](../series/understanding-fsharp-types.md) will cover types in detail. -First, we need to understand the type notation a bit more. We've seen that the arrow notation "`->`" is used to show the domain and range. So that a function signature always looks like: +Теперь, когда у нас есть некоторое понимание функций, мы посмотрим как типы взаимодействуют с функциями, как domain-а, так и range. Это просто обзор, серия ["understanding F# types"](../series/understanding-fsharp-types.md) раскроет типы более подробно. + +> First, we need to understand the type notation a bit more. We've seen that the arrow notation "`->`" is used to show the domain and range. So that a function signature always looks like: + +Сперва, нам надо чуть лучше понять нотацию типов. Мы видели стрелочную нотацию "`->`" разделяющую domain и range. Так что сигнатура функций всегда выглядит вот так: ```fsharp val functionName : domain -> range ``` -Here are some example functions: +> Here are some example functions: + +Еще несколько примеров функций: ```fsharp let intToString x = sprintf "x is %i" x // format int to string let stringToInt x = System.Int32.Parse(x) ``` -If you evaluate that in the F# interactive window, you will see the signatures: +> If you evaluate that in the F# interactive window, you will see the signatures: + +> Если выполнить этот код в интерактивном окне, можно увидеть следующие сигнатуры: ```fsharp val intToString : int -> string val stringToInt : string -> int ``` -This means: +> This means: + +Они означают: -* `intToString` has a domain of `int` which it maps onto the range `string`. -* `stringToInt` has a domain of `string` which it maps onto the range `int`. +> * `intToString` has a domain of `int` which it maps onto the range `string`. +> * `stringToInt` has a domain of `string` which it maps onto the range `int`. -## Primitive types ## +* `intToString` имеет domain типа `int`, который сопоставляется с range типа `string`. +* `stringToInt` имеет domain типа `string`, который сопоставляется с range типа `int`. -The possible primitive types are what you would expect: string, int, float, bool, char, byte, etc., plus many more derived from the .NET type system. +## Primitive types | Примитивные типы ## -Here are some more examples of functions using primitive types: +> The possible primitive types are what you would expect: string, int, float, bool, char, byte, etc., plus many more derived from the .NET type system. + +Есть ожидаемые примитивные типы: string, int, float, bool, char, byte, etc., а также еще множество других производных от системы типов .NET. + +> Here are some more examples of functions using primitive types: + +Функции с примитивными типами: ```fsharp let intToFloat x = float x // "float" fn. converts ints to floats @@ -47,7 +64,9 @@ let intToBool x = (x = 2) // true if x equals 2 let stringToString x = x + " world" ``` -and their signatures are: +> and their signatures are: + +и их сигнатуры: ```fsharp val intToFloat : int -> float @@ -55,255 +74,349 @@ val intToBool : int -> bool val stringToString : string -> string ``` -## Type annotations ## +## Type annotations | Аннотация типов ## + +> In the previous examples, the F# compiler correctly determined the types of the parameters and results. But this is not always the case. If you try the following code, you will get a compiler error: -In the previous examples, the F# compiler correctly determined the types of the parameters and results. But this is not always the case. If you try the following code, you will get a compiler error: +В предыдущих примерах компилятор F# корректно определял типы параметров и результатов. Но так бывает не всегда. Если попробовать выполнить следующий код, будет получена ошибка компиляции: ```fsharp let stringLength x = x.Length => error FS0072: Lookup on object of indeterminate type ``` -The compiler does not know what type "x" is, and therefore does not know if "Length" is a valid method. In most cases, this can be fixed by giving the F# compiler a "type annotation" so that it knows which type to use. In the corrected version below, we indicate that the type of "x" is a string. +> The compiler does not know what type "x" is, and therefore does not know if "Length" is a valid method. In most cases, this can be fixed by giving the F# compiler a "type annotation" so that it knows which type to use. In the corrected version below, we indicate that the type of "x" is a string. + +Компилятор не знает тип аргумента "x", и из-за этого не знает, является ли "Length" валидным методом. В большинстве случаев, это может быть исправлено через передачу "аннотации типа" компилятору F#, тогда он будет знать, какой тип необходимо использовать. В исправленной версии ниже, мы указываем, что тип "x" - string. ```fsharp let stringLength (x:string) = x.Length ``` -The parens around the `x:string` param are important. If they are missing, the compiler thinks that the return value is a string! That is, an "open" colon is used to indicate the type of the return value, as you can see in the example below. +> The parens around the `x:string` param are important. If they are missing, the compiler thinks that the return value is a string! That is, an "open" colon is used to indicate the type of the return value, as you can see in the example below. + +Скобки вокруг параметра `x:string` важны. Если они будут пропущены, то компилятор решит, что строкой является возвращаемое значение! То есть, "открытое" двоеточие используется для обозначения типа возвращаемого значения, как видно в следующем примере. ```fsharp let stringLengthAsInt (x:string) :int = x.Length ``` -We're indicating that the x param is a string and the return value is an int. +> We're indicating that the x param is a string and the return value is an int. + +Мы указываем, что параметр `x` является строкой, а возврашаемое значение является int. + +## Function types as parameters | Типы функций как параметры ## + +> A function that takes other functions as parameters, or returns a function, is called a **higher-order function** (sometimes abbreviated as HOF). They are used as a way of abstracting out common behavior. These kinds of functions are extremely common in F#; most of the standard libraries use them. -## Function types as parameters ## +Функция которая принимает другие функции как параметры или возвращает функцию, называется **функцией высшего порядка** (**higher-order function** иногда сокращается до HOF). Их применяют в качестве абстракции от общего поведения. Данный вид функций очень распространен в F#, их использует большинство стандартных библиотек. -A function that takes other functions as parameters, or returns a function, is called a **higher-order function** (sometimes abbreviated as HOF). They are used as a way of abstracting out common behavior. These kinds of functions are extremely common in F#; most of the standard libraries use them. +> Consider a function `evalWith5ThenAdd2`, which takes a function as a parameter, then evaluates the function with the value 5, and adds 2 to the result: -Consider a function `evalWith5ThenAdd2`, which takes a function as a parameter, then evaluates the function with the value 5, and adds 2 to the result: +Рассмотрим функцию `evalWith5ThenAdd2`, которая принимает функцию в качестве параметра, затем вычисляет эту функцию от 5, и добавляет 2 к результату: ```fsharp let evalWith5ThenAdd2 fn = fn 5 + 2 // same as fn(5) + 2 ``` -The signature of this function looks like this: +> The signature of this function looks like this: + +Сигнатура данной функции выглядит так: ```fsharp val evalWith5ThenAdd2 : (int -> int) -> int ``` -You can see that the domain is `(int->int)` and the range is `int`. What does that mean? It means that the input parameter is not a simple value, but a function, and what's more is restricted only to functions that map `ints` to `ints`. The output is not a function, just an int. +> You can see that the domain is `(int->int)` and the range is `int`. What does that mean? It means that the input parameter is not a simple value, but a function, and what's more is restricted only to functions that map `ints` to `ints`. The output is not a function, just an int. + +Вы можете видеть, что domain равен `(int->int)`, а range `int`. Что это значит? Это значит, что входным параметром является не простое значение, а функция, более того, ограниченная функциями из `int` в `int`. Выходное же значение не функция, просто `int`. -Let's try it: +> Let's try it: + +> Попробуем: ```fsharp let add1 x = x + 1 // define a function of type (int -> int) evalWith5ThenAdd2 add1 // test it ``` -gives: +> gives: + +получим: ```fsharp val add1 : int -> int val it : int = 8 ``` -"`add1`" is a function that maps ints to ints, as we can see from its signature. So it is a valid parameter for the `evalWith5ThenAdd2` function. And the result is 8. +> "`add1`" is a function that maps ints to ints, as we can see from its signature. So it is a valid parameter for the `evalWith5ThenAdd2` function. And the result is 8. -By the way, the special word "`it`" is used for the last thing that was evaluated; in this case the result we want. It's not a keyword, just a convention. +"`add1`" - это функция, которая сопоставляет `int` в `int`, как мы видим из сигнатуры. Она является допустимым параметром `evalWith5ThenAdd2`, и ее результат равен 8. -Here's another one: +> By the way, the special word "`it`" is used for the last thing that was evaluated; in this case the result we want. It's not a keyword, just a convention. + +Кстати, специальное слово "`it`" используется для обозначения последнего вычисленного значения, в данном случае это результат, которого мы ждаил. Это не ключевое слово, это просто конвенция. + +> Here's another one: + +> Другой случай: ```fsharp let times3 x = x * 3 // a function of type (int -> int) evalWith5ThenAdd2 times3 // test it ``` -gives: +> gives: + +дает: ```fsharp val times3 : int -> int val it : int = 17 ``` -"`times3`" is also a function that maps ints to ints, as we can see from its signature. So it is also a valid parameter for the `evalWith5ThenAdd2` function. And the result is 17. +> "`times3`" is also a function that maps ints to ints, as we can see from its signature. So it is also a valid parameter for the `evalWith5ThenAdd2` function. And the result is 17. + +"`times3`" также является функцией, которая сопоставляет `int` в `int`, что видно из сигнатуры. Она также является валидным параметром для `evalWith5ThenAdd2`. Результат вычислений равен 17. -Note that the input is sensitive to the types. If our input function uses `floats` rather than `ints`, it will not work. For example, if we have: +> Note that the input is sensitive to the types. If our input function uses `floats` rather than `ints`, it will not work. For example, if we have: + +Следует учесть, что входные данные чувствительны к типам. Если передаваемая функция использует `float`, а не `int`, оно не сработает. Например, если у нас есть: ```fsharp let times3float x = x * 3.0 // a function of type (float->float) evalWith5ThenAdd2 times3float ``` -Evaluating this will give an error: +> Evaluating this will give an error: + +Попытка скопилировать вернет ошибку: ```fsharp error FS0001: Type mismatch. Expecting a int -> int but given a float -> float ``` -meaning that the input function should have been an `int->int` function. +> meaning that the input function should have been an `int->int` function. + +сообщающую, что входная функция должна быть функцией `int->int`. -### Functions as output ### +### Functions as output | Функции как выходные данные ### -A function value can also be the output of a function. For example, the following function will generate an "adder" function that adds using the input value. +> A function value can also be the output of a function. For example, the following function will generate an "adder" function that adds using the input value. + +Функции-значения также могут быть результатом функций. Например, следующая функция сгенерирует "adder" функцию, которая будет добавлять входное значение. ```fsharp let adderGenerator numberToAdd = (+) numberToAdd ``` -The signature is: +> The signature is: + +Сигнатура: ```fsharp val adderGenerator : int -> (int -> int) ``` -which means that the generator takes an `int`, and creates a function (the "adder") that maps `ints` to `ints`. Let's see how it works: +> which means that the generator takes an `int`, and creates a function (the "adder") that maps `ints` to `ints`. Let's see how it works: + +говорящая, что генератор принимает `int` и создает функцию ("adder"), которая сопоставляет `ints` в `ints`. Псмотрим как это работает: ```fsharp let add1 = adderGenerator 1 let add2 = adderGenerator 2 ``` -This creates two adder functions. The first generated function adds 1 to its input, and the second adds 2. Note that the signatures are just as we would expect them to be. +> This creates two adder functions. The first generated function adds 1 to its input, and the second adds 2. Note that the signatures are just as we would expect them to be. + +Создаются две функции сумматора. Первой создается функция добавляющая к вводу 1, вторая добавляет 2. Заметим, что сигнатуры именно такие, какие мы ожидали. ```fsharp val add1 : (int -> int) val add2 : (int -> int) ``` -And we can now use these generated functions in the normal way. They are indistinguishable from functions defined explicitly +> And we can now use these generated functions in the normal way. They are indistinguishable from functions defined explicitly + +Теперь можно использовать сгенерируемые функции как обычные, они неотличимы от функций определенных явно: ```fsharp add1 5 // val it : int = 6 add2 5 // val it : int = 7 ``` -### Using type annotations to constrain function types ### +### Using type annotations to constrain function types | Использование аннотаций типа для ограничения типов функции ### -In the first example, we had the function: +> In the first example, we had the function: + +В первом примере была функция: ```fsharp let evalWith5ThenAdd2 fn = fn 5 +2 => val evalWith5ThenAdd2 : (int -> int) -> int ``` -In this case F# could deduce that "`fn`" mapped `ints` to `ints`, so its signature would be `int->int` +> In this case F# could deduce that "`fn`" mapped `ints` to `ints`, so its signature would be `int->int` + +В данном примере F# может сделать вывод, что "`fn`" преобразует `int` в `int`, поэтому сигнатура будет `int->int`. -But what is the signature of "fn" in this following case? +> But what is the signature of "fn" in this following case? + +Но какова сигнатура "fn" в следующием случае? ```fsharp let evalWith5 fn = fn 5 ``` -Obviously, "`fn`" is some kind of function that takes an int, but what does it return? The compiler can't tell. If you do want to specify the type of the function, you can add a type annotation for function parameters in the same way as for a primitive type. +> Obviously, "`fn`" is some kind of function that takes an int, but what does it return? The compiler can't tell. If you do want to specify the type of the function, you can add a type annotation for function parameters in the same way as for a primitive type. + +Понятно, что "`fn`" - разновидность функции, которая принимает `int`, но что она возврашает? Компилятор не может ответить на этот вопрос. В таких случаях, если возникает необходимость указать тип функции, можно добавить тип аннотации для параметров функций также как и для примитивных типов. ```fsharp let evalWith5AsInt (fn:int->int) = fn 5 let evalWith5AsFloat (fn:int->float) = fn 5 ``` -Alternatively, you could also specify the return type instead. +> Alternatively, you could also specify the return type instead. + +Кроме того, можно определить возвращаемый тип. ```fsharp let evalWith5AsString fn :string = fn 5 ``` -Because the main function returns a string, the "`fn`" function is also constrained to return a string, so no explicit typing is required for "fn". +> Because the main function returns a string, the "`fn`" function is also constrained to return a string, so no explicit typing is required for "fn". + +Т.к. основная функция возврашает `string`, функция "`fn`" также вынуждена возвращать `string`, таким образом не требуется явно типизировать "`fn`". -## The "unit" type ## +## The "unit" type | Тип "unit" ## -When programming, we sometimes want a function to do something without returning a value. Consider the function "`printInt`", defined below. The function doesn't actually return anything. It just prints a string to the console as a side effect. +> When programming, we sometimes want a function to do something without returning a value. Consider the function "`printInt`", defined below. The function doesn't actually return anything. It just prints a string to the console as a side effect. + +В процессе программирования мы иногда хотим, чтобы функция делала что-то не возвраoая ничего. Рассмотрим функцию "`printInt`". Функция действительно ничего не возвращает. Она просто выводит строку на консоль как побочный эффект. ```fsharp let printInt x = printf "x is %i" x // print to console ``` -So what is the signature for this function? +> So what is the signature for this function? + +Какова ее сигнатура? ```fsharp val printInt : int -> unit ``` -What is this "`unit`"? +> What is this "`unit`"? + +Что такое "`unit`"? -Well, even if a function returns no output, it still needs a range. There are no "void" functions in mathematics-land. Every function must have some output, because a function is a mapping, and a mapping has to have something to map to! +> Well, even if a function returns no output, it still needs a range. There are no "void" functions in mathematics-land. Every function must have some output, because a function is a mapping, and a mapping has to have something to map to! + +Даже если функция не возврашает значений, она все еще нуждается в range. В мире математики не существует "void" функций. Каждая функция должна что-то возвращать, потому-что функция - это отображение, а отображение должно что-то отображать! ![](../assets/img/Functions_Unit.png) -So in F#, functions like this return a special range called "`unit`". This range has exactly one value in it, called "`()`". You can think of `unit` and `()` as somewhat like "void" (the type) and "null" (the value) in C#. But unlike void/null, `unit` is a real type and `()` is a real value. To see this, evaluate: +> So in F#, functions like this return a special range called "`unit`". This range has exactly one value in it, called "`()`". You can think of `unit` and `()` as somewhat like "void" (the type) and "null" (the value) in C#. But unlike void/null, `unit` is a real type and `()` is a real value. To see this, evaluate: + +И так, в F# функции подобные этой возвращают специальный range называемый "`unit`". Данный range содержит только одно значение, называемое "`()`". Можно подумать, что `unit` и `()` что-то вроде "void" и "null" из C# соответственно. Но в отличии от них, `unit` является реальным типом, а `()` реальным значением. Чтобы убедиться в этом, достаточно выполнить: ```fsharp let whatIsThis = () ``` -and you will see the signature: +> and you will see the signature: + +> будет получена следующая сигнатуру: ```fsharp val whatIsThis : unit = () ``` -Which means that the value "`whatIsThis`" is of type `unit` and has been bound to the value `()` +> Which means that the value "`whatIsThis`" is of type `unit` and has been bound to the value `()` + +Которая указывает, что метка "`whatIsThis`" принадлежит типу `unit` и было связано со значением `()`. + +> So, going back to the signature of "`printInt`", we can now understand it: -So, going back to the signature of "`printInt`", we can now understand it: +Теперь вернувшись к сигнатуре "`printInt`", можно понять значение это записи: ```fsharp val printInt : int -> unit ``` -This signature says: `printInt` has a domain of `int` which it maps onto nothing that we care about. +> This signature says: `printInt` has a domain of `int` which it maps onto nothing that we care about. + +Данная сигнатура говорит, что `printInt` имеет domain из `int`, который который преобразуется в нечто, что нас не интересует. -### Parameterless functions +### Parameterless functions | Функции без параметров + +> Now that we understand unit, can we predict its appearance in other contexts? For example, let's try to create a reusable "hello world" function. Since there is no input and no output, we would expect it to have a signature `unit -> unit`. Let's see: -Now that we understand unit, can we predict its appearance in other contexts? For example, let's try to create a reusable "hello world" function. Since there is no input and no output, we would expect it to have a signature `unit -> unit`. Let's see: +Теперь, когда мы понимаем `unit`, можем ли мы предсказать его появление в другом контексте? Например, попробуем создать многократно используемую функцию "hello world". Поскольку нет ни ввода, ни вывода, мы можем ожидать, сигнатуру `unit -> unit`. Посмотрим: ```fsharp let printHello = printf "hello world" // print to console ``` -The result is: +> The result is: + +Результат: ```fsharp hello world val printHello : unit = () ``` -Not quite what we expected. "Hello world" is printed immediately and the result is not a function, but a simple value of type unit. As we saw earlier, we can tell that this is a simple value because it has a signature of the form: +> Not quite what we expected. "Hello world" is printed immediately and the result is not a function, but a simple value of type unit. As we saw earlier, we can tell that this is a simple value because it has a signature of the form: + +_Не совсем то_, что мы ожидали. "Hello world" было выведено немедленно, а результатом стала не функция, а простое значение типа unit. Как мы видели ранее, мы можем сказать, что это простое значение, поскольку оно имеет сигнатуру вида: ```fsharp val aName: type = constant ``` -So in this case, we see that `printHello` is actually a *simple value* with the value `()`. It's not a function that we can call again. +> So in this case, we see that `printHello` is actually a *simple value* with the value `()`. It's not a function that we can call again. + +В данном примере мы видим, что `printHello` действительно является _простым значением_ `()`. Это не функция, которую мы можем вызвать позже. + +> Why the difference between `printInt` and `printHello`? In the `printInt` case, the value could not be determined until we knew the value of the x parameter, so the definition was of a function. In the `printHello` case, there were no parameters, so the right hand side could be determined immediately. Which it was, returning the `()` value, with the side effect of printing to the console. + +В чем разница между `printInt` и `printHello`? В случае `printInt`, значение не может быть определено, пока мы не узнаем значения параметра `x`, поэтому определение было функцией. В случае `printHello`, нет параметров, поэтому правая часть может быть определена на месте. И она была равна `()`, с side-эффектом в виде вывода на консоль. -Why the difference between `printInt` and `printHello`? In the `printInt` case, the value could not be determined until we knew the value of the x parameter, so the definition was of a function. In the `printHello` case, there were no parameters, so the right hand side could be determined immediately. Which it was, returning the `()` value, with the side effect of printing to the console. +> We can create a true reusable function that is parameterless by forcing the definition to have a unit argument, like this: -We can create a true reusable function that is parameterless by forcing the definition to have a unit argument, like this: +Можно создать настоящую многократно используемую функцию без параметров, заставляя определение иметь `unit` аргумент: ```fsharp let printHelloFn () = printf "hello world" // print to console ``` -The signature is now: +> The signature is now: + +Ее сигнатура равна: ```fsharp val printHelloFn : unit -> unit ``` -and to call it, we have to pass the `()` value as a parameter, like so: +> and to call it, we have to pass the `()` value as a parameter, like so: + +и чтобы вызвать ее, мы должны передать `()` в качестве парамерта: ```fsharp printHelloFn () ``` -### Forcing unit types with the ignore function ### +### Forcing unit types with the ignore function | _Усиление unit типов с помошью ignore function_### -In some cases the compiler requires a unit type and will complain. For example, both of the following will be compiler errors: +> In some cases the compiler requires a unit type and will complain. For example, both of the following will be compiler errors: + +В некоторых случаях компилятор требует `unit` тип и будет жаловаться. Для примера, оба следующих случая вызовут ошибку компилятора: ```fsharp do 1+1 // => FS0020: This expression should have type 'unit' @@ -313,7 +426,9 @@ let something = "hello" ``` -To help in these situations, there is a special function `ignore` that takes anything and returns the unit type. The correct version of this code would be: +> To help in these situations, there is a special function `ignore` that takes anything and returns the unit type. The correct version of this code would be: + +Чтобы помочь в данных ситуациях, существует специальная функция `ignore`, которая принимает что угодно и возвращает `unit`. Правильная версия данного кода могла бы быть такой: ```fsharp do (1+1 |> ignore) // ok @@ -323,25 +438,35 @@ let something = "hello" ``` -## Generic types ## +## Generic types | Обобщенные типы ## -In many cases, the type of the function parameter can be any type, so we need a way to indicate this. F# uses the .NET generic type system for this situation. +> In many cases, the type of the function parameter can be any type, so we need a way to indicate this. F# uses the .NET generic type system for this situation. -For example, the following function converts the parameter to a string and appends some text: +В большинстве случаев, если тип параметра функции может быть любым типом, нам надо как-то сказать об этом. F# использует обобщения (generic) из .NET для таких ситуаций. + +> For example, the following function converts the parameter to a string and appends some text: + +Например, следующая функция ковертирует параметр в строку добавляя немного текста: ```fsharp let onAStick x = x.ToString() + " on a stick" ``` -It doesn't matter what type the parameter is, as all objects understand `ToString()`. +> It doesn't matter what type the parameter is, as all objects understand `ToString()`. + +Не важно какого типа параметр, все объекты умеют в `ToString()`. -The signature is: +> The signature is: + +Сигнатура: ```fsharp val onAStick : 'a -> string ``` -What is this type called `'a`? That is F#'s way of indicating a generic type that is not known at compile time. The apostrophe in front of the "a" means that the type is generic. The signature for the C# equivalent of this would be: +> What is this type called `'a`? That is F#'s way of indicating a generic type that is not known at compile time. The apostrophe in front of the "a" means that the type is generic. The signature for the C# equivalent of this would be: + +Что за тип `'a`? В F# это способ индикации обобщенного типа, который неизвестен на момент компиляции. Апостроф перед "a" означает, что тип является обобщенным. Эквивалент данной сигнатуры на C#: ```csharp string onAStick(); @@ -351,9 +476,13 @@ string OnAStick(); // F#'s use of 'a is like // C#'s "TObject" convention ``` -Note that the F# function is still strongly typed with a generic type. It does *not* take a parameter of type `Object`. This strong typing is desirable so that when functions are composed together, type safety is still maintained. +> Note that the F# function is still strongly typed with a generic type. It does *not* take a parameter of type `Object`. This strong typing is desirable so that when functions are composed together, type safety is still maintained. -Here's the same function being used with an int, a float and a string +Надо понимать, что данная F# функция все еще обладает строгой типизацией даже с обобщенными типами. Она *не* принимает параметр типа `Object`. Строгая типизация желаема, ибо позволяет при композиции функций вместе сохранять типобезопасность. + +> Here's the same function being used with an int, a float and a string + +Одна и таже функция используется для `int`, `float` и `string`. ```fsharp onAStick 22 @@ -361,45 +490,63 @@ onAStick 3.14159 onAStick "hello" ``` -If there are two generic parameters, the compiler will give them different names: `'a` for the first generic, `'b` for the second generic, and so on. Here's an example: +> If there are two generic parameters, the compiler will give them different names: `'a` for the first generic, `'b` for the second generic, and so on. Here's an example: + +Если есть два обобщенных параметра, то компилятор даст им два различных имени: `'a` для первого, `'b` для второго и т.д. Например: ```fsharp let concatString x y = x.ToString() + y.ToString() ``` -The type signature for this has two generics: `'a` and `'b`: +> The type signature for this has two generics: `'a` and `'b`: + +В данной сигнатуре будет два обобщенных типа: `'a` и `'b`: ```fsharp val concatString : 'a -> 'b -> string ``` -On the other hand, the compiler will recognize when only one generic type is required. In the following example, the x and y parameters must be of the same type: +> On the other hand, the compiler will recognize when only one generic type is required. In the following example, the x and y parameters must be of the same type: + +С другой стороны, компилятор распознает, когда требуется только один универсальный тип. В следующем примере `x` и `y` должны быть одного типа: ```fsharp let isEqual x y = (x=y) ``` -So the function signature has the same generic type for both of them: +> So the function signature has the same generic type for both of them: + +Так сигнатура функции имеет одинаковый обобщенный тим для обоих параметров: ```fsharp val isEqual : 'a -> 'a -> bool ``` -Generic parameters are also very important when it comes to lists and more abstract structures, and we will be seeing them a lot in upcoming examples. +> Generic parameters are also very important when it comes to lists and more abstract structures, and we will be seeing them a lot in upcoming examples. + +Обобщенные параметры также очень важны, когда дело касается списков и других абстрактных структур, и мы будем видеть их много в последующих примерах. + +## Other types | Другие типы ## + +> The types discussed so far are just the basic types. These types can be combined in various ways to make much more complex types. A full discussion of these types will have to wait for [another series](../series/understanding-fsharp-types.md), but meanwhile, here is a brief introduction to them so that you can recognize them in function signatures. + +До сих пор обсуждались только базовые типы. Данные типы могут быть скомбинированны различными способами в более сложные типы. Полный их разбор будет позднее в [другой серии](../series/understanding-fsharp-types.md), но между тем, здесь кратко их разберем, так что бы можно было распознать их в сигнатуре функции. -## Other types ## +> * **The "tuple" types**. These are pairs, triples, etc., of other types. For example `("hello", 1)` is a tuple made from a string and an int. The comma is the distinguishing characteristic of a tuple -- if you see a comma in F#, it is almost certainly part of a tuple! -The types discussed so far are just the basic types. These types can be combined in various ways to make much more complex types. A full discussion of these types will have to wait for [another series](../series/understanding-fsharp-types.md), but meanwhile, here is a brief introduction to them so that you can recognize them in function signatures. +* **Кортежи (tuples)**. Это пара, тройка и т.д. составленная из других типов. Например, `("hello", 1)` - кортеж сделанный на основе `string` и `int`. Запятая - отличительный признак кортежей, если в F# где-то замечена запятая, это почти гарантировано часть кортежа. -* **The "tuple" types**. These are pairs, triples, etc., of other types. For example `("hello", 1)` is a tuple made from a string and an int. The comma is the distinguishing characteristic of a tuple -- if you see a comma in F#, it is almost certainly part of a tuple! +> In function signatures, tuples are written as the "multiplication" of the two types involved. So in this case, the tuple would have type: -In function signatures, tuples are written as the "multiplication" of the two types involved. So in this case, the tuple would have type: +В сигнатурах функций кортежи пишутся как "произведения" двух вовлеченных типов. В данном случае, кортеж будет иметь тип: ```fsharp string * int // ("hello", 1) ``` -* **The collection types**. The most common of these are lists, sequences, and arrays. Lists and arrays are fixed size, while sequences are potentially infinite (behind the scenes, sequences are the same as `IEnumerable`). In function signatures, they have their own keywords: "`list`", "`seq`", and "`[]`" for arrays. +> * **The collection types**. The most common of these are lists, sequences, and arrays. Lists and arrays are fixed size, while sequences are potentially infinite (behind the scenes, sequences are the same as `IEnumerable`). In function signatures, they have their own keywords: "`list`", "`seq`", and "`[]`" for arrays. + +* **Коллекции**. Наиболее распространенные из них - list (список), seq (перечисление) и массив. Списки и массивы имеют фиксированный размер, в то время как последовательности потенциально бесконечны (за кулисами последовательности - это те же самые `IEnumrable`). В сигнатурах функций они имеют свои собственные ключевые слова: "`list`", "`seq`" и "`[]`" для массивов. ```fsharp int list // List type e.g. [1;2;3] @@ -408,18 +555,25 @@ seq // Seq type e.g. seq{1..10} int [] // Array type e.g. [|1;2;3|] ``` -* **The option type**. This is a simple wrapper for objects that might be missing. There are two cases: `Some` and `None`. In function signatures, they have their own "`option`" keyword: +> * **The option type**. This is a simple wrapper for objects that might be missing. There are two cases: `Some` and `None`. In function signatures, they have their own "`option`" keyword: + +* **Option (опциональный тип)**. Это простая обертка над объектами, которые могут отстутвовать. Сушествует два варианта: `Some` и `None`. В сигнатурах функции они имеют свое свобственное ключевое слово "`option`": ```fsharp int option // Some(1) ``` -* **The discriminated union type**. These are built from a set of choices of other types. We saw some examples of this in the ["why use F#?"](../series/why-use-fsharp.md) series. In function signatures, they are referred to by the name of the type, so there is no special keyword. -* **The record type**. These are like structures or database rows, a list of named slots. We saw some examples of this in the ["why use F#?"](../series/why-use-fsharp.md) series as well. In function signatures, they are referred to by the name of the type, so again there is no special keyword. +> * **The discriminated union type**. These are built from a set of choices of other types. We saw some examples of this in the ["why use F#?"](../series/why-use-fsharp.md) series. In function signatures, they are referred to by the name of the type, so there is no special keyword. +> * **The record type**. These are like structures or database rows, a list of named slots. We saw some examples of this in the ["why use F#?"](../series/why-use-fsharp.md) series as well. In function signatures, they are referred to by the name of the type, so again there is no special keyword. + +* **Размеченное объединение (discriminated union)**. Они построены из множества вариантов других типов. Мы видели некоторые примеры в ["why use F#?"](../series/why-use-fsharp.md). В сигнатурах функций на них ссылаются по имени типа, они не имеют специального ключевого слова. +* **Record тип (записи)**. Подобные структурам или строкам баз данных, списки именнованных слотов. Мы также видели несколько примеров в ["why use F#?"](../series/why-use-fsharp.md). В сигнатурах функций они называются по имени типа, они также не имеют своего ключевого слова. + +## Test your understanding of types | Проверьте свое понимание типов ## -## Test your understanding of types ## +> How well do you understand the types yet? Here are some expressions for you -- see if you can guess their signatures. To see if you are correct, just run them in the interactive window! -How well do you understand the types yet? Here are some expressions for you -- see if you can guess their signatures. To see if you are correct, just run them in the interactive window! +Здесь представлено несколько выражений для проверки своего понимания сигнатур функций. Для проверки достаточно запустить их в интерактивном окне! ```fsharp let testA = float 2 diff --git a/posts/mathematical-functions.md b/posts/mathematical-functions.md index 9d8e442..849709a 100644 --- a/posts/mathematical-functions.md +++ b/posts/mathematical-functions.md @@ -7,55 +7,87 @@ seriesId: "Thinking functionally" seriesOrder: 2 --- -The impetus behind functional programming comes from mathematics. Mathematical functions have a number of very nice features that functional languages try to emulate in the real world. +> The impetus behind functional programming comes from mathematics. Mathematical functions have a number of very nice features that functional languages try to emulate in the real world. -So first, let's start with a mathematical function that adds 1 to a number. +Функциональное программирование вдохновлено математикой. Математический функции имеют ряд очень приятных особенностей, которые функциональные языки пытются претворить в жизнь. + +> So first, let's start with a mathematical function that adds 1 to a number. + +Итак, во-первых, начнем с математической функции, которая добавляет 1 к числу. Add1(x) = x+1 -What does this really mean? Well it seems pretty straightforward. It means that there is an operation that starts with a number, and adds one to it. +> What does this really mean? Well it seems pretty straightforward. It means that there is an operation that starts with a number, and adds one to it. + +Что в действительности означает это выражение? Выглядит довольно просто. Оно означает, что существует такая операция, котороая берет число и прибавляет к нему 1. + +> Let's introduce some terminology: + +Добавим немного терминологии: -Let's introduce some terminology: +> * The set of values that can be used as input to the function is called the *domain*. In this case, it could be the set of real numbers, but to make life simpler for now, let's restrict it to integers only. +> * The set of possible output values from the function is called the *range* (technically, the image on the codomain). In this case, it is also the set of integers. +> * The function is said to *map* the domain to the range. -* The set of values that can be used as input to the function is called the *domain*. In this case, it could be the set of real numbers, but to make life simpler for now, let's restrict it to integers only. -* The set of possible output values from the function is called the *range* (technically, the image on the codomain). In this case, it is also the set of integers. -* The function is said to *map* the domain to the range. +* Множество допустимых входных значений функции называются _domain_ (область определения). В данном пример, это могло быть множество действительных чисел, но сделаем жизнь проще и ограничимся здесь только целыми числами. +* Множество возможных результатов функции (область значения) называется _range_ (технически, изображение **codomain-а**). В данном случае также множество целых. +* Функцией называют _преобразование_ (в оригинале _map_) из domain-а в range. (Т.е. из области определения в область значений.) ![](../assets/img/Functions_Add1.png) -Here's how the definition would look in F# +> Here's how the definition would look in F# + +Вот как это определение будет выглядеть на F#. ```fsharp let add1 x = x + 1 ``` -If you type that into the F# interactive window (don't forget the double semicolons) you will see the result (the "signature" of the function): +> If you type that into the F# interactive window (don't forget the double semicolons) you will see the result (the "signature" of the function): + +Если ввести его в F# Interactive (следует не забыть про двойные точку с запятой), можно увидеть результат ("сигнатуру" функции): ```fsharp val add1 : int -> int ``` -Let's look at that output in detail: +> Let's look at that output in detail: + +Рассмотрим вывод подробно: + +> * The overall meaning is "the function `add1` maps integers (the domain) onto integers (the range)". +> * "`add1`" is defined as a "val", short for "value". Hmmm? what does that mean? We'll discuss values shortly. +> * The arrow notation "`->`" is used to show the domain and range. In this case, the domain is the `int` type, and the range is also the `int` type. -* The overall meaning is "the function `add1` maps integers (the domain) onto integers (the range)". -* "`add1`" is defined as a "val", short for "value". Hmmm? what does that mean? We'll discuss values shortly. -* The arrow notation "`->`" is used to show the domain and range. In this case, the domain is the `int` type, and the range is also the `int` type. +* Общий смысл - это "функция `add1` сопоставляющая целые числа (из предметной области) с целыми (областью значений). +* "`add1`" определена как "val", сокращение от "value" (значения). Хм? что это значит? Мы обсудим значения чуть позже. +* Стрелочная нотация "->" используется, чтобы показать domain и range. В данном случае, domain является типом 'int', как и range. -Also note that the type was not specified, yet the F# compiler guessed that the function was working with ints. (Can this be tweaked? Yes, as we'll see shortly). +> Also note that the type was not specified, yet the F# compiler guessed that the function was working with ints. (Can this be tweaked? Yes, as we'll see shortly). -## Key properties of mathematical functions ## +Примечательно, что тип не был указан, но компилятор F# вычислил, что функция работает с int-ами. (Можно ли это изменить? Да, скоро мы увидим). -Mathematical functions have some properties that are very different from the kinds of functions you are used to in procedural programming. +## Key properties of mathematical functions | Ключевые свойства математических функций ## -* A function always gives the same output value for a given input value -* A function has no side effects. +> Mathematical functions have some properties that are very different from the kinds of functions you are used to in procedural programming. -These properties provide some very powerful benefits, and so functional programming languages try to enforce these properties in their design as well. Let's look at each of them in turn. +Математические функции имееют ряд свойств, которые очень сильно отличают их от других типов функций, которые используются в процедурном программировании. -### Mathematical functions always give the same output for a given input ### +> * A function always gives the same output value for a given input value +> * A function has no side effects. -In imperative programming, we think that functions "do" something or "calculate" something. A mathematical function does not do any calculation -- it is purely a mapping from input to output. In fact, another way to think of defining a function is simply as the set of all the mappings. For example, in a very crude way we could define the "`add1`" -function (in C#) as +* Функция всегда имеет один и тот же результат для данного входного значения. +* Функция не имеет побочных эффектов. + +> These properties provide some very powerful benefits, and so functional programming languages try to enforce these properties in their design as well. Let's look at each of them in turn. + +Эти свойства дают ряд заметных преимуществ, которые функциональные языки программирования пытаются по мере сил реализовать в своем дизайне. Рассмотрим каждое из них по очереди. + +### Mathematical functions always give the same output for a given input | Математические функции всегда _возвращают одинаковый результат на заданное значение_ ### + +> In imperative programming, we think that functions "do" something or "calculate" something. A mathematical function does not do any calculation -- it is purely a mapping from input to output. In fact, another way to think of defining a function is simply as the set of all the mappings. For example, in a very crude way we could define the "`add1`" function (in C#) as + +В императивном программировании мы думаем, что функции либо что-то "делают", либо что-то "подсчитывают". Математические функции ничего не считают, это чистые сопоставления из input в output. В самом деле, другое определение функции - это простое множество всех отображений. Например, очень грубо можно определить функцию "'add1'" (в C#) как ```csharp int add1(int input) @@ -71,58 +103,105 @@ int add1(int input) } ``` -Obviously, we can't have a case for every possible integer, but the principle is the same. You can see that absolutely no calculation is being done at all, just a lookup. +> Obviously, we can't have a case for every possible integer, but the principle is the same. You can see that absolutely no calculation is being done at all, just a lookup. + +Очевидно, что невозможно иметь по case-у на каждое возможное число, но принцип тот же. При такой постановке никаких вычислений не производится, осуществляется лишь поиск. + +### Mathematical functions are free from side effects | Математические функции свободны от побочных эфектов ### + +> In a mathematical function, the input and the output are logically two different things, both of which are predefined. The function does not change the input or the output -- it just maps a pre-existing input value from the domain to a pre-existing output value in the range. -### Mathematical functions are free from side effects ### +В математической функции, входное и выходное значения логически две различные вещи, обе являющиеся предопределенными. Функция не изменяет входные или выходные данные и просто отображает предопределенное входное значение из области определения в предварительно определенное выходное значение в области значений. -In a mathematical function, the input and the output are logically two different things, both of which are predefined. The function does not change the input or the output -- it just maps a pre-existing input value from the domain to a pre-existing output value in the range. +> In other words, evaluating the function *cannot possibly have any effect on the input, or anything else for that matter*. Remember, evaluating the function is not actually calculating or manipulating anything; it is just a glorified lookup. -In other words, evaluating the function *cannot possibly have any effect on the input, or anything else for that matter*. Remember, evaluating the function is not actually calculating or manipulating anything; it is just a glorified lookup. +Другими словами, вычисление функции _не может иметь каких либо эффектов на входные данные или еще что-нибудь в подобном роде_". Следует запомнить, что вычисление функции в действительности не считает и не манипулирует чем-либо, это просто "прославленный" поиск. -This "immutability" of the values is subtle but very important. If I am doing mathematics, I do not expect the numbers to change underneath me when I add them! For example, if I have: +> This "immutability" of the values is subtle but very important. If I am doing mathematics, I do not expect the numbers to change underneath me when I add them! For example, if I have: + +Эта "иммутабельность" значений очень тонка, но в тоже время очень важная вещь. Когда я занимаюсь математикой, я не жду, что числа будут изменяться в процессе их сложения. Например, если у меня: x = 5 y = x+1 -I would not expect x to be changed by the adding of one to it. I would expect to get back a different number (y) and x would be left untouched. In the world of mathematics, the integers already exist as an unchangeable set, and the "add1" function simply defines a relationship between them. +> I would not expect x to be changed by the adding of one to it. I would expect to get back a different number (y) and x would be left untouched. In the world of mathematics, the integers already exist as an unchangeable set, and the "add1" function simply defines a relationship between them. + +Я не ожидаю, что `x` изменится при добавлении к нему 1. Я ожидаю, что получу другое число (`y`), и `x` должен остаться нетронутым. В мире математики целые числа уже сушествуют в неизменяемом множестве, и функция "add1" просто определяет отношения между ними. + +### The power of pure functions | Сила чистых функций ### + +> The kinds of functions which have repeatable results and no side effects are called "pure functions", and you can do some interesting things with them: + +Те разновидности функций, что имеют повторяемые результаты и не имеют побочных эффектов называются "чистыми / pure функциями", и с ними можно сделать некоторые интересные вещи: + +> * They are trivially parallelizable. I could take all the integers from 1 to 1000, say, and given 1000 different CPUs, I could get each CPU to execute the "`add1`" function for the corresponding integer at the same time, safe in the knowledge that there was no need for any interaction between them. No locks, mutexes, semaphores, etc., needed. +> * I can use a function lazily, only evaluating it when I need the output. I can be sure that the answer will be the same whether I evaluate it now or later. +> * I only ever need to evaluate a function once for a certain input, and I can then cache the result, because I know that the same input always gives the same output. +> * If I have a number of pure functions, I can evaluate them in any order I like. Again, it can't make any difference to the final result. + +* Из легко распараллелить. Скажем, можно бы взять целые числа в диапозоне от 1 до 1000 и раздать их 1000 различных процессоров, после чего поручить каждому CPU выполнить "'add1'" над соответствующим числом, одновременно будучи увереным, что нет необходомости в каком-либо взаимодействии между ними. Не потребуется ни блокировок, ни мьютексов, ни семафоров, ни т.п. +* Можно использовать функции лениво, вычисляя их тогда, когда это необходимо для вывода. Можно быть увереным, что ответ будет точно таким же, независимо от того, провожу вычисления сейчас или позже. +* Можно лишь один раз провести вычисления функции для конкретного ввода, после чего закешировать результат, потому-что известно, что данный ввод будет давать такой же вывод. +* Если есть множество чистых функций, их можно вычислять в любом порядке. Опять же, это не может повлиять на финальный результат. + +> So you can see that if we can create pure functions in a programming language, we immediately gain a lot of powerful techniques. And indeed you can do all these things in F#: + +Соответственно, если в языке программирования есть возможность создавать чистые функции, можно немедленно получить множество мощных приемов. И несомненно, все это можно сделать в F#: + +> * You have already seen an example of parallelism in the ["why use F#?"](../series/why-use-fsharp.md) series. +> * Evaluating functions lazily will be discussed in the ["optimization"](../series/optimization.md) series. +> * Caching the results of functions is called "memoization" and will also be discussed in the ["optimization"](../series/optimization.md) series. +> * Not caring about the order of evaluation makes concurrent programming much easier, and doesn't introduce bugs when functions are reordered or refactored. + +* Пример параллельных вычислений был в серии ["why use F#?"](../series/why-use-fsharp.md). +* Ленивое вычисление функций будет обсуждено в серии ["optimization"](../series/optimization.md). +* Кэширование результатов функций называется мемоизацией и также будет обсуждено в серии ["optimization"](../series/optimization.md). +* Отсутствие необходимости в отслеживании порядка выполнения делает параллельное программирование гораздо проще и позволяет не сталкиваться с багами вызванными сменой порядка фукций или рефакторинга. + +## "Unhelpful" properties of mathematical functions | "Бесполезные" свойства математических функций ## + +> Mathematical functions also have some properties that seem not to be very helpful when used in programming. + +Математические функции также имеют некоторые свойства кажущиеся не очень полезными при программировании. + +> * The input and output values are immutable +> * A function always has exactly one input and one output + +* Входные и выходные значения неизменяемы +* Функции всегда имеют один ввод и один вывод + +> These properties are mirrored in the design of functional programming languages too. Let's look at each of these in turn. -### The power of pure functions ### +Данные свойства отражаются в дизайне функциональных языков программирования. Стоит рассмотреть их поотдельности. -The kinds of functions which have repeatable results and no side effects are called "pure functions", and you can do some interesting things with them: +> **The input and output values are immutable** -* They are trivially parallelizable. I could take all the integers from 1 to 1000, say, and given 1000 different CPUs, I could get each CPU to execute the "`add1`" function for the corresponding integer at the same time, safe in the knowledge that there was no need for any interaction between them. No locks, mutexes, semaphores, etc., needed. -* I can use a function lazily, only evaluating it when I need the output. I can be sure that the answer will be the same whether I evaluate it now or later. -* I only ever need to evaluate a function once for a certain input, and I can then cache the result, because I know that the same input always gives the same output. -* If I have a number of pure functions, I can evaluate them in any order I like. Again, it can't make any difference to the final result. +**Входные и выходные значения неизменяемы** -So you can see that if we can create pure functions in a programming language, we immediately gain a lot of powerful techniques. And indeed you can do all these things in F#: +> Immutable values seem like a nice idea in theory, but how can you actually get any work done if you can't assign to variables in a traditional way? -* You have already seen an example of parallelism in the ["why use F#?"](../series/why-use-fsharp.md) series. -* Evaluating functions lazily will be discussed in the ["optimization"](../series/optimization.md) series. -* Caching the results of functions is called "memoization" and will also be discussed in the ["optimization"](../series/optimization.md) series. -* Not caring about the order of evaluation makes concurrent programming much easier, and doesn't introduce bugs when functions are reordered or refactored. +Иммутабельные значения в теории кажутся хорошей идеей, но как можно реально сделать какую-либо работу, если нет возможности назначить переменную традиционным способом. -## "Unhelpful" properties of mathematical functions ## +> I can assure you that this is not as much as a problem as you might think. As you work through this series, you'll see how this works in practice. -Mathematical functions also have some properties that seem not to be very helpful when used in programming. +Я могу заверить, что это не такая большая проблема как можно представить. Во ходе данной серией статей будет ясно, как это работает на практике. -* The input and output values are immutable -* A function always has exactly one input and one output +> **Mathematical functions always have exactly one input and one output** -These properties are mirrored in the design of functional programming languages too. Let's look at each of these in turn. +**Математические функции всегда имеют один ввод и один вывод** -**The input and output values are immutable** +> As you can see from the diagrams, there is always exactly one input and one output for a mathematical function. This is true for functional programming languages as well, although it may not be obvious when you first use them. -Immutable values seem like a nice idea in theory, but how can you actually get any work done if you can't assign to variables in a traditional way? +Как видно из диаграм, для математической функции всегда существует только один ввод и только один вывод. Это также верно для функциональных языков программирования, хотя может быть неочевидным при первом использовании. -I can assure you that this is not as much as a problem as you might think. As you work through this series, you'll see how this works in practice. +> That seems like a big annoyance. How can you do useful things without having functions with two (or more) parameters? -**Mathematical functions always have exactly one input and one output** +Это похоже на большое неудобство. Как можно сделать что-либо полезное без функций с двумя (или более) параметрами? -As you can see from the diagrams, there is always exactly one input and one output for a mathematical function. This is true for functional programming languages as well, although it may not be obvious when you first use them. +> Well, it turns there is a way to do it, and what's more, it is completely transparent to you in F#. It is called "currying" and it deserves its own post, which is coming up soon. -That seems like a big annoyance. How can you do useful things without having functions with two (or more) parameters? +Оказывается, существует путь сделать это, и более того он является абсолютно прозрачным на F#. Называется он "каррированием", и заслуживает отдельный пост, который появится в ближайшее время. -Well, it turns there is a way to do it, and what's more, it is completely transparent to you in F#. It is called "currying" and it deserves its own post, which is coming up soon. +> In fact, as you will later discover, these two "unhelpful" properties will turn out to be incredibly useful and a key part of what makes functional programming so powerful. -In fact, as you will later discover, these two "unhelpful" properties will turn out to be incredibly useful and a key part of what makes functional programming so powerful. +На самом деле, позже выяснится, что эти два "бесполезных" свойства станут невероятно ценными, и будут ключевой частью, которая делает функциональное программирование столь мощным. \ No newline at end of file diff --git a/posts/organizing-functions.md b/posts/organizing-functions.md index 4f1252d..22f01cc 100644 --- a/posts/organizing-functions.md +++ b/posts/organizing-functions.md @@ -8,21 +8,35 @@ seriesOrder: 10 categories: [Functions, Modules] --- -Now that you know how to define functions, how can you organize them? +> Now that you know how to define functions, how can you organize them? -In F#, there are three options: +Теперь Вы знаете как определять функции, но как организовать их? -* functions can be nested inside other functions. -* at an application level, the top level functions are grouped into "modules". -* alternatively, you can also use the object-oriented approach and attach functions to types as methods. +> In F#, there are three options: -We'll look at the first two options in this post, and the third in the next post. +В F# существуют три варианта: -## Nested Functions ## +> * functions can be nested inside other functions. +> * at an application level, the top level functions are grouped into "modules". +> * alternatively, you can also use the object-oriented approach and attach functions to types as methods. -In F#, you can define functions inside other functions. This is a great way to encapsulate "helper" functions that are needed for the main function but shouldn't be exposed outside. +* функции могут быть вложенны в другие функции. +* на уровне приложения, функции верхнего уровня группируются по "модулям". +* в качестве альтернативы, можно придерживаться ООП и прикреплять функции к типам в качестве методов. -In the example below `add` is nested inside `addThreeNumbers`: +> We'll look at the first two options in this post, and the third in the next post. + +В данной статье будут рассмотрены первые два способа, оставшийся будет разобран в следующей. + +## Nested Functions | Вложенные функции ## + +> In F#, you can define functions inside other functions. This is a great way to encapsulate "helper" functions that are needed for the main function but shouldn't be exposed outside. + +В F# можно определять функции внутри других функций. Это хороший способ инкапсулировать сопутствующие функции, которые необходимы лишь для основной функции и не должны быть представлены наружу. + +> In the example below `add` is nested inside `addThreeNumbers`: + +В примере ниже `add` вложен в `addThreeNumbers`: ```fsharp let addThreeNumbers x y z = @@ -38,8 +52,11 @@ let addThreeNumbers x y z = addThreeNumbers 2 3 4 ``` -A nested function can access its parent function parameters directly, because they are in scope. -So, in the example below, the `printError` nested function does not need to have any parameters of its own -- it can access the `n` and `max` values directly. +> A nested function can access its parent function parameters directly, because they are in scope. +> So, in the example below, the `printError` nested function does not need to have any parameters of its own -- it can access the `n` and `max` values directly. + +Вложенные функции могут получить доступ к родительским параметрам напрямую, потому-что они находятся в области видимости. +Так, в приведенном ниже примере вложенная функция`printError` не нуждается в параметрах, т.к. она может получить доступ к `n` и `max` напрямую. ```fsharp let validateSize max n = @@ -56,8 +73,11 @@ validateSize 10 9 validateSize 10 11 ``` -A very common pattern is that the main function defines a nested recursive helper function, and then calls it with the appropriate initial values. -The code below is an example of this: +> A very common pattern is that the main function defines a nested recursive helper function, and then calls it with the appropriate initial values. +> The code below is an example of this: + +Очень распространенным паттерном является основная функция определяющая вложенную рекурсивную вспомогательную функцию, которая вызывается с соответствующими начальными значениями. +Ниже приведен подобный пример: ```fsharp let sumNumbersUpTo max = @@ -76,10 +96,15 @@ sumNumbersUpTo 10 ``` -When nesting functions, do try to avoid very deeply nested functions, especially if the nested functions directly access the variables in their parent scopes rather than having parameters passed to them. -A badly nested function will be just as confusing as the worst kind of deeply nested imperative branching. +> When nesting functions, do try to avoid very deeply nested functions, especially if the nested functions directly access the variables in their parent scopes rather than having parameters passed to them. +> A badly nested function will be just as confusing as the worst kind of deeply nested imperative branching. + +Старайтесь избегать глубокой вложенности, особенно в случаях прямого доступа (не в виле параметров) к родительским переменным. +Плохо вложенные функции будут столь же сложны для понимания, как худшие виды глубоких вложенных императивных ветлений. -Here's how *not* to do it: +> Here's how *not* to do it: + +Пример того как *нельзя* делать: ```fsharp // wtf does this function do? @@ -98,14 +123,21 @@ let f x = ``` -## Modules ## +## Modules | Модули ## + +> A module is just a set of functions that are grouped together, typically because they work on the same data type or types. + +Модуль - это просто набор функций, которые сгруппированы вместе, обычно потому что они работают с одним и тем же типом или типами данных. + +> A module definition looks very like a function definition. It starts with the `module` keyword, then an `=` sign, and then the contents of the module are listed. +> The contents of the module *must* be indented, just as expressions in a function definition must be indented. -A module is just a set of functions that are grouped together, typically because they work on the same data type or types. +Определение модуля очень похоже на определение функции. Оно начинается с ключевого слова `module`, затем идет знак `=`, после чего идет содержимое модуля. +Содержимое модуля *должно* быть смещено, также как выражения в определении функций. -A module definition looks very like a function definition. It starts with the `module` keyword, then an `=` sign, and then the contents of the module are listed. -The contents of the module *must* be indented, just as expressions in a function definition must be indented. +> Here's a module that contains two functions: -Here's a module that contains two functions: +Определение модуля содержащего две функции: ```fsharp module MathStuff = @@ -114,9 +146,13 @@ module MathStuff = let subtract x y = x - y ``` -Now if you try this in Visual Studio, and you hover over the `add` function, you will see that the full name of the `add` function is actually `MathStuff.add`, just as if `MathStuff` was a class and `add` was a method. +> Now if you try this in Visual Studio, and you hover over the `add` function, you will see that the full name of the `add` function is actually `MathStuff.add`, just as if `MathStuff` was a class and `add` was a method. -Actually, that's exactly what is going on. Behind the scenes, the F# compiler creates a static class with static methods. So the C# equivalent would be: +Если попробовать этот код в Visual Studio, то при наведении на `add`, можно увидеть полное имя `add`, которое в действительности равно `MathStuff.add`, как будто `MastStuff` был классом, а `add` методом. + +> Actually, that's exactly what is going on. Behind the scenes, the F# compiler creates a static class with static methods. So the C# equivalent would be: + +В действительности, именно это и происходит. За кулисами F# компилятор создает статический класс со статическими методами. C# эквивалент выглядел бы так: ```csharp static class MathStuff @@ -133,14 +169,20 @@ static class MathStuff } ``` -If you realize that modules are just static classes, and that functions are static methods, then you will already have a head-start on understanding how modules work in F#, -as most of the rules that apply to static classes also apply to modules. +> If you realize that modules are just static classes, and that functions are static methods, then you will already have a head-start on understanding how modules work in F#, +> as most of the rules that apply to static classes also apply to modules. + +Осознание того, что модули являются всего-лишь статическими классами, а функции являются статическими методами, даст хорошее понимание того, как модули работают в F#, большинтсво правил применимых к статическим классам также применими и к модулям. + +> And, just as in C# every standalone function must be part of a class, in F# every standalone function *must* be part of a module. -And, just as in C# every standalone function must be part of a class, in F# every standalone function *must* be part of a module. +И так же, как в C# каждая отдельно стоящая функция должна быть частью класса, в F# каждая отдельно стоящая функция *должна* быть частью модуля. -### Accessing functions across module boundaries +### Accessing functions across module boundaries | Доступ к функциям за пределами модуля -If you want to access a function in another module, you can refer to it by its qualified name. +> If you want to access a function in another module, you can refer to it by its qualified name. + +Если есть необходимость получить функцию из другого модуля, можно ссылаться на нее через полное имя. ```fsharp module MathStuff = @@ -154,8 +196,10 @@ module OtherStuff = let add1 x = MathStuff.add x 1 ``` -You can also import all the functions in another module with the `open` directive, -after which you can use the short name, rather than having to specify the qualified name. +> You can also import all the functions in another module with the `open` directive, +> after which you can use the short name, rather than having to specify the qualified name. + +Так же можно импортировать все функции другого модуля посредством директивы `open`, после чего можно будет использовать короткое имя вместо полного. ```fsharp module OtherStuff = @@ -164,12 +208,16 @@ module OtherStuff = let add1 x = add x 1 ``` -The rules for using qualified names are exactly as you would expect. That is, you can always use a fully qualified name to access a function, -and you can use relative names or unqualified names based on what other modules are in scope. +> The rules for using qualified names are exactly as you would expect. That is, you can always use a fully qualified name to access a function, +> and you can use relative names or unqualified names based on what other modules are in scope. + +Правила использования имен вполне ожидаемые. Всегда можно обратиться к функции по ее полному имени, и можно использовать относительные или неполные имена в зависимости от текущей области видимости. + +### Nested modules | Вложенные модули -### Nested modules +> Just like static classes, modules can contain child modules nested within them, as shown below: -Just like static classes, modules can contain child modules nested within them, as shown below: +Как и статические классы, модули могут содержать вложенные модули: ```fsharp module MathStuff = @@ -184,7 +232,9 @@ module MathStuff = let subtract x y :float = x - y ``` -And other modules can reference functions in the nested modules using either a full name or a relative name as appropriate: +> And other modules can reference functions in the nested modules using either a full name or a relative name as appropriate: + +Другие модули могут ссылаться на функции во вложенных модулях используя полное или относительное имя в зависимости от обстоятельств: ```fsharp module OtherStuff = @@ -199,22 +249,37 @@ module OtherStuff = let sub1Float x = FloatLib.subtract x 1.0 ``` -### Top level modules +### Top level modules | Модули высшего порядка + +> So if there can be nested child modules, that implies that, going back up the chain, there must always be some *top-level* parent module. This is indeed true. + +// TODO: Разобраться с термином. +Таким образом, могут сущестовать дочерние модули, это означает, что идя назад по цепочке можно дойти до некоего родительского модуля высшего порядка. Это действительно так. + +> Top level modules are defined slightly differently than the modules we have seen so far. -So if there can be nested child modules, that implies that, going back up the chain, there must always be some *top-level* parent module. This is indeed true. +Модули верхнего уровня определяются несколько иначе в отличии от модулей, которые были показаны ранее. -Top level modules are defined slightly differently than the modules we have seen so far. +> * The `module MyModuleName` line *must* be the first declaration in the file +> * There is no `=` sign +> * The contents of the module are *not* indented -* The `module MyModuleName` line *must* be the first declaration in the file -* There is no `=` sign -* The contents of the module are *not* indented +* Строка `module MyModuleName` *должна* быть первой декларацией в файле +* Знак `=` отсутствует +* Содержимое модуля *не* должно иметь отступа -In general, there must be a top level module declaration present in every `.FS` source file. There some exceptions, but it is good practice anyway. +> In general, there must be a top level module declaration present in every `.FS` source file. There some exceptions, but it is good practice anyway. The module name does not have to be the same as the name of the file, but two files cannot share the same module name. -For `.FSX` script files, the module declaration is not needed, in which case the module name is automatically set to the filename of the script. +В обшем случае, должна существовать декларация верхнего уровня в каждом исходном `.FS` файле. Есть исключения, но это хорошая практика в любом случае. Имя модуля не обязанно совпадать с именем файла, но два файла не могут содержать модули с одинаковыми именнами. -Here is an example of `MathStuff` declared as a top level module: +> For `.FSX` script files, the module declaration is not needed, in which case the module name is automatically set to the filename of the script. + +Для `.FSX` файлов, декларация модуля не нужна, в данном случае имя модуля автоматически устанавливается из имени файла скрипта. + +> Here is an example of `MathStuff` declared as a top level module: + +Пример `MathStuff` декларированного в качестве верхнего модуля: ```fsharp // top level module @@ -230,11 +295,15 @@ module FloatLib = let subtract x y :float = x - y ``` -Note the lack of indentation for the top level code (the contents of `module MathStuff`), but that the content of a nested module like `FloatLib` does still need to be indented. +> Note the lack of indentation for the top level code (the contents of `module MathStuff`), but that the content of a nested module like `FloatLib` does still need to be indented. + +Обратите внимание на отсутствие отступа в коде высшего уровня (содержимом `module MathStuff`), но содержимое вложенного модуля `FloatLib` все еще обязано иметь отступ. -### Other module content +### Other module content | Другое содержимое модулей -A module can contain other declarations as well as functions, including type declarations, simple values and initialization code (like static constructors) +> A module can contain other declarations as well as functions, including type declarations, simple values and initialization code (like static constructors) + +Кроме функций модули могут содержать другие объявления, такие как декларации типов, простые значения и инициализирующий код (как статический конструктор) ```fsharp module MathStuff = @@ -259,11 +328,15 @@ module MathStuff = ``` -
By the way, if you are playing with these examples in the interactive window, you might want to right-click and do "Reset Session" every so often, so that the code is fresh and doesn't get contaminated with previous evaluations
+>
By the way, if you are playing with these examples in the interactive window, you might want to right-click and do "Reset Session" every so often, so that the code is fresh and doesn't get contaminated with previous evaluations
+ +
Кстати, если Вы запускаете данные примеры в интерактивном режиме, Вам может понадобиться достаточно часто перезапускать сессию, чтобы код оставался "свежим" и не заражался предыдущими вычислениями.
-### Shadowing +### Shadowing | Сокрытие -Here's our example module again. Notice that `MathStuff` has an `add` function and `FloatLib` *also* has an `add` function. +> Here's our example module again. Notice that `MathStuff` has an `add` function and `FloatLib` *also* has an `add` function. + +Вот еще один пример. Обратите внимание, что `MathStuff` содержит `add` _также_ как и `FloatLib`. ```fsharp module MathStuff = @@ -278,7 +351,9 @@ module MathStuff = let subtract x y :float = x - y ``` -Now what happens if I bring *both* of them into scope, and then use `add`? +> Now what happens if I bring *both* of them into scope, and then use `add`? + +Что случится, если открыть *оба* модуля в текущем области видимости и вызвать `add`? ```fsharp open MathStuff @@ -288,13 +363,21 @@ let result = add 1 2 // Compiler error: This expression was expected to // have type float but here has type int ``` -What happened was that the `MathStuff.FloatLib` module has masked or overridden the original `MathStuff` module, which has been "shadowed" by `FloatLib`. +> What happened was that the `MathStuff.FloatLib` module has masked or overridden the original `MathStuff` module, which has been "shadowed" by `FloatLib`. -As a result you now get a [FS0001 compiler error](../troubleshooting-fsharp/index.md#FS0001) because the first parameter `1` is expected to be a float. You would have to change `1` to `1.0` to fix this. +Случилось то, что модуль `MathStuff.FloatLib` переопределил оригинальный `MathStuff`, который был сокрыт ("shadowed") `FloatLib`. -Unfortunately, this is invisible and easy to overlook. Sometimes you can do cool tricks with this, almost like subclassing, but more often, it can be annoying if you have functions with the same name (such as the very common `map`). +> As a result you now get a [FS0001 compiler error](../troubleshooting-fsharp/index.md#FS0001) because the first parameter `1` is expected to be a float. You would have to change `1` to `1.0` to fix this. -If you don't want this to happen, there is a way to stop it by using the `RequireQualifiedAccess` attribute. Here's the same example where both modules are decorated with it. +В результате была получена [FS0001 compiler error](../troubleshooting-fsharp/index.md#FS0001), потому что первый параметр `1` ожидался как float. Потребуется изменить `1` на `1.0`, чтобы исправить это. + +> Unfortunately, this is invisible and easy to overlook. Sometimes you can do cool tricks with this, almost like subclassing, but more often, it can be annoying if you have functions with the same name (such as the very common `map`). + +К сожалению, на практике это _невидимо_ и легко упускается из виду. Иногда можно делать интересные трюки с помощью данного приема, почти как подклассы, но в большинстве своем, наличие одноименных функций раздражает (например, очень распространенная функция `map`). + +> If you don't want this to happen, there is a way to stop it by using the `RequireQualifiedAccess` attribute. Here's the same example where both modules are decorated with it. + +Если требуется избежать данного поведения, существует способ пресечь его при помощи атрибута `RequireQualifiedAccess`. Тот же пример у когорого оба модуля декорированны данными атрибутом: ```fsharp [] @@ -311,14 +394,18 @@ module MathStuff = let subtract x y :float = x - y ``` -Now the `open` isn't allowed: +> Now the `open` isn't allowed: + +Теперь директива `open` недоступна: ```fsharp open MathStuff // error open MathStuff.FloatLib // error ``` -But we can still access the functions (without any ambiguity) via their qualified name: +> But we can still access the functions (without any ambiguity) via their qualified name: + +Но все еще можно получить доступ к функциям (без какой-либо двусмысленности) через их полные имена: ```fsharp let result = MathStuff.add 1 2 @@ -326,22 +413,32 @@ let result = MathStuff.FloatLib.add 1.0 2.0 ``` -### Access Control +### Access Control | Контроль доступа + +> F# supports the use of standard .NET access control keywords such as `public`, `private`, and `internal`. +> The [MSDN documentation](http://msdn.microsoft.com/en-us/library/dd233188) has the complete details. + +F# поддерживает использование стандартных .NET операторов контроля доступа, таких как `public`, `private` и `internal`. [MSDN документация](http://msdn.microsoft.com/en-us/library/dd233188) содержит полную информацию. -F# supports the use of standard .NET access control keywords such as `public`, `private`, and `internal`. -The [MSDN documentation](http://msdn.microsoft.com/en-us/library/dd233188) has the complete details. +> * These access specifiers can be put on the top-level ("let bound") functions, values, types and other declarations in a module. They can also be specified for the modules themselves (you might want a private nested module, for example). +> * Everything is public by default (with a few exceptions) so you will need to use `private` or `internal` if you want to protect them. -* These access specifiers can be put on the top-level ("let bound") functions, values, types and other declarations in a module. They can also be specified for the modules themselves (you might want a private nested module, for example). -* Everything is public by default (with a few exceptions) so you will need to use `private` or `internal` if you want to protect them. +* Эти спецификаторы доступа могут быть применены к ("let bound") функциям верхнего уровня, значениям, типам и другим декларациям в модуле. Они таже могут быть указаны для самих модулей (например может понадобиться приватный вложенный модуль). +* По умолчанию все имеет публичный доступ (за исключением нескольких случаев), поэтому для их защиты потребуется использовать `private` или `internal`. -These access specifiers are just one way of doing access control in F#. Another completely different way is to use module "signature" files, which are a bit like C header files. They describe the content of the module in an abstract way. Signatures are very useful for doing serious encapsulation, but that discussion will have to wait for the planned series on encapsulation and capability based security. +> These access specifiers are just one way of doing access control in F#. Another completely different way is to use module "signature" files, which are a bit like C header files. They describe the content of the module in an abstract way. Signatures are very useful for doing serious encapsulation, but that discussion will have to wait for the planned series on encapsulation and capability based security. +Данные спецификаторы доступа являются лишь одним из способов управления видимостью в F#. Другим совершенно отличным способом является использование файлов "сигнатуры" модуля, которые напоминают файлы заголовков C. Они определяют содержимое модуля абстрактным способом. Сигнатуры очень полезны для серьезных инкапсуляций, но для рассмотрения их возможностей придется дождаться запланированной серии по инкапсуляции и *оcнованной на возможностях безопасности*. -## Namespaces +## Namespaces | Пространства имен -Namespaces in F# are similar to namespaces in C#. They can be used to organize modules and types to avoid name collisions. +> Namespaces in F# are similar to namespaces in C#. They can be used to organize modules and types to avoid name collisions. -A namespace is declared with a `namespace` keyword, as shown below. +Пространства имен в F# похожи на пространства из C#. Они могут быть использованы для организации модулей и типов, чтобы избежать конфликтов имен. + +> A namespace is declared with a `namespace` keyword, as shown below. + +Пространство имен декларированное с помощью ключевого слова `namespace`: ```fsharp namespace Utilities @@ -353,12 +450,18 @@ module MathStuff = let subtract x y = x - y ``` -Because of this namespace, the fully qualified name of the `MathStuff` module now becomes `Utilities.MathStuff` and -the fully qualified name of the `add` function now becomes `Utilities.MathStuff.add`. +> Because of this namespace, the fully qualified name of the `MathStuff` module now becomes `Utilities.MathStuff` and +> the fully qualified name of the `add` function now becomes `Utilities.MathStuff.add`. + +Из-за данного пространств имен полное имя модуля `MathStuff` стало `Utilities.MathStuff`, а полное имя `add` - `Utilities.MathStuff.add`. + +> With the namespace, the indentation rules apply, so that the module defined above must have its content indented, as it it were a nested module. -With the namespace, the indentation rules apply, so that the module defined above must have its content indented, as it it were a nested module. +К модулям внутри пространства имен применяются те же правила отступа, что были показаны ранее для модулей. -You can also declare a namespace implicitly by adding dots to the module name. That is, the code above could also be written as: +> You can also declare a namespace implicitly by adding dots to the module name. That is, the code above could also be written as: + +Также можно объявлять пространтсво имен явно при помощи добавления точки в имени модуля. Т.е. код выше можно переписать так: ```fsharp module Utilities.MathStuff @@ -368,20 +471,30 @@ let add x y = x + y let subtract x y = x - y ``` -The fully qualified name of the `MathStuff` module is still `Utilities.MathStuff`, but -in this case, the module is a top-level module and the contents do not need to be indented. +> The fully qualified name of the `MathStuff` module is still `Utilities.MathStuff`, but +> in this case, the module is a top-level module and the contents do not need to be indented. + +Полное имя модуля `MathStuff` все еще `Utilities.MathStuff`, но теперь это модуль верхнего уровня и его содержимому не нужен отступ. + +> Some additional things to be aware of when using namespaces: + +Некоторые дополнительные особенности использования пространств имен: -Some additional things to be aware of when using namespaces: +> * Namespaces are optional for modules. And unlike C#, there is no default namespace for an F# project, so a top level module without a namespace will be at the global level. +> If you are planning to create reusable libraries, be sure to add some sort of namespace to avoid naming collisions with code in other libraries. +> * Namespaces can directly contain type declarations, but not function declarations. As noted earlier, all function and value declarations must be part of a module. +> * Finally, be aware that namespaces don't work well in scripts. For example, if you try to to send a namespace declaration such as `namespace Utilities` below to the interactive window, you will get an error. -* Namespaces are optional for modules. And unlike C#, there is no default namespace for an F# project, so a top level module without a namespace will be at the global level. -If you are planning to create reusable libraries, be sure to add some sort of namespace to avoid naming collisions with code in other libraries. -* Namespaces can directly contain type declarations, but not function declarations. As noted earlier, all function and value declarations must be part of a module. -* Finally, be aware that namespaces don't work well in scripts. For example, if you try to to send a namespace declaration such as `namespace Utilities` below to the interactive window, you will get an error. +* Пространства имен необязательны для модулей. В отличии от C#, не существует пространства имен по умолчанию для F# проекта, так что модуль верхнего уровня без пространства имен будет иметь глобальный уровень. Если планируется создание библиотеки многоразового использования, необходимо добавить несколько пространств имен чтобы избежать коллизий имен с кодом других библиотек. +* Пространства имен непосредственно содержат декларации типов, но не декларации функции. Как было замечено ранее, все объявления функций и значений должны быть частью модуля. +* Наконец, следует иметь ввиду, что пространства имен не работают в скриптах. Например, если попробовать отправить объявление пространства имен, например `namespace Utilities` будет получена ошибка. -### Namespace hierarchies +### Namespace hierarchies | Иерархия пространств имен -You can create a namespace hierarchy by simply separating the names with periods: +> You can create a namespace hierarchy by simply separating the names with periods: + +Можно создавать иерархию пространств имен, просто разделив имена точками: ```fsharp namespace Core.Utilities @@ -390,7 +503,9 @@ module MathStuff = let add x y = x + y ``` -And if you want to put *two* namespaces in the same file, you can. Note that all namespaces *must* be fully qualified -- there is no nesting. +> And if you want to put *two* namespaces in the same file, you can. Note that all namespaces *must* be fully qualified -- there is no nesting. + +Можно объявить *два* пространтсва имен в одном файле, если есть такое желание. Следует обратить внимание, что все пространства имен *должны* быть объявлены полным именем -- они не поддержимают вложенность. ```fsharp namespace Core.Utilities @@ -404,8 +519,9 @@ module MoreMathStuff = let add x y = x + y ``` -One thing you can't do is have a naming collision between a namespace and a module. +> One thing you can't do is have a naming collision between a namespace and a module. +Конфликт имен можду пространством имен и модулем невозможнен. ```fsharp namespace Core.Utilities @@ -423,21 +539,32 @@ module Utilities = ``` -## Mixing types and functions in modules ## +## Mixing types and functions in modules | Смешивание типов и функий в модулях ## + +> We've seen that a module typically consists of a set of related functions that act on a data type. + +Как мы видели ранее, модули обычно состоят из множестов взаимозависимых функций, которые взаимодействуют с определенным типом данных. -We've seen that a module typically consists of a set of related functions that act on a data type. +> In an object oriented program, the data structure and the functions that act on it would be combined in a class. +> However in functional-style F#, a data structure and the functions that act on it are combined in a module instead. -In an object oriented program, the data structure and the functions that act on it would be combined in a class. -However in functional-style F#, a data structure and the functions that act on it are combined in a module instead. +В ООП структуры данных и функции воздействующие на них были бы объединены вместе внутри класса. Однако в функциональном стиле, структуры данных и функции на них воздействующие будут объединены внутри модуля. -There are two common patterns for mixing types and functions together: +> There are two common patterns for mixing types and functions together: -* having the type declared separately from the functions -* having the type declared in the same module as the functions +Существуют два паттерна комбинирования типов и функций вместе: -In the first approach, the type is declared *outside* any module (but in a namespace) and then the functions that work on the type +> * having the type declared separately from the functions +> * having the type declared in the same module as the functions + +* тип объявляется отдельно от функций +* тип объявляется в том же модуле, что и функции + +> In the first approach, the type is declared *outside* any module (but in a namespace) and then the functions that work on the type are put in a module with a similar name. +В первом случае тип декларируется *за пределами* какого либо модуля (но в пространстве имен), после чего функции, которые работают с этим типом, помещаются в одноименный типу модуль. + ```fsharp // top-level module namespace Example @@ -461,9 +588,11 @@ let person = Person.create "john" "doe" Person.fullName person |> printfn "Fullname=%s" ``` -In the alternative approach, the type is declared *inside* the module and given a simple name such as "`T`" or the name of the module. -So the functions are accessed with names like `MyModule.Func1` and `MyModule.Func2` while the type itself is -accessed with a name like `MyModule.T`. Here's an example: +> In the alternative approach, the type is declared *inside* the module and given a simple name such as "`T`" or the name of the module. +> So the functions are accessed with names like `MyModule.Func1` and `MyModule.Func2` while the type itself is +> accessed with a name like `MyModule.T`. Here's an example: + +В альтернативном варианте, тип декларируется *внутри* модуля и имеет простое название, типа "`T`" или имя модуля. Доступ к функциям осуществляется приблизительно так: `MyModule.Func` и `MyModule.Func2`, а доступ к типу: `MyModule.T`: ```fsharp module Customer = @@ -484,22 +613,34 @@ let customer = Customer.create 42 "bob" Customer.isValid customer |> printfn "Is valid?=%b" ``` -Note that in both cases, you should have a constructor function that creates new instances of the type (a factory method, if you will), -Doing this means that you will rarely have to explicitly name the type in your client code, and therefore, you not should not care whether it lives in the module or not! +> Note that in both cases, you should have a constructor function that creates new instances of the type (a factory method, if you will), +> Doing this means that you will rarely have to explicitly name the type in your client code, and therefore, you not should not care whether it lives in the module or not! -So which approach should you choose? +Заметьте, что в обоих случаях должна быть функция создающая новый экземпляр типа (фабрика). Это позволит не вызывать тип явным образом, и можно будет не задаваться вопросом, находится ли тип внутри модуля. -* The former approach is more .NET like, and much better if you want to share your libraries with other non-F# code, as the exported class names are what you would expect. -* The latter approach is more common for those used to other functional languages. The type inside a module compiles into nested classes, which is not so nice for interop. +> So which approach should you choose? -For yourself, you might want to experiment with both. And in a team programming situation, you should choose one style and be consistent. +Какой способ лучше? +> * The former approach is more .NET like, and much better if you want to share your libraries with other non-F# code, as the exported class names are what you would expect. +> * The latter approach is more common for those used to other functional languages. The type inside a module compiles into nested classes, which is not so nice for interop. -### Modules containing types only +* Первый подход польше похож на классический .NET, и его следует предпочесть, если планируется использовать данную библиотеку для кода за пределами F#, где ожидают отдельно существующий класс. +* Второй подход является более распространенным в других функциональных языках. Тип внутри модуля компилируется как вложенный класс, что как правило не очень удобно для ООП языков. -If you have a set of types that you need to declare without any associated functions, don't bother to use a module. You can declare types directly in a namespace and avoid nested classes. +> For yourself, you might want to experiment with both. And in a team programming situation, you should choose one style and be consistent. -For example, here is how you might think to do it: +Для себя, вы можете поэкспериментировать с обоими. В случае командной разработки надлежит выбрать один стиль. + +### Modules containing types only | Модули содержащие только типы + +> If you have a set of types that you need to declare without any associated functions, don't bother to use a module. You can declare types directly in a namespace and avoid nested classes. + +Если есть множество типов, которые необходимо объявить без каких либо функций, не стоит заморачиваться использованием модуля. Можно объявить типы прямо в пространстве имен не прибегая к вложенным классам. + +> For example, here is how you might think to do it: + +Например, вы захотите нечто вроде этого: ```fsharp // top-level module @@ -511,7 +652,9 @@ type PersonType = {First:string; Last:string} // no functions in the module, just types... ``` -And here is a alternative way to do it. The `module` keyword has simply been replaced with `namespace`. +> And here is a alternative way to do it. The `module` keyword has simply been replaced with `namespace`. + +А вот другой способ сделать тоже самое. Слово `module` просто заменяется на слово `namespace`. ```fsharp // use a namespace @@ -521,6 +664,10 @@ namespace Example type PersonType = {First:string; Last:string} ``` -In both cases, `PersonType` will have the same fully qualified name. +> In both cases, `PersonType` will have the same fully qualified name. + +В обоих случаях, `PersonType` будет иметь одно и тоже полное имя. + +> Note that this only works with types. Functions must always live in a module. -Note that this only works with types. Functions must always live in a module. +Следует обратить внимание, что данная замена работает только с типами. Функции **всегда** должны быть внутри модуля. diff --git a/posts/partial-application.md b/posts/partial-application.md index 2e3781c..6304e97 100644 --- a/posts/partial-application.md +++ b/posts/partial-application.md @@ -9,11 +9,17 @@ categories: [Currying, Partial Application] --- -In the previous post on currying, we looked at breaking multiple parameter functions into smaller one parameter functions. It is the mathematically correct way of doing it, but that is not the only reason it is done -- it also leads to a very powerful technique called **partial function application**. This is a very widely used style in functional programming, and it is important to understand it. +> In the previous post on currying, we looked at breaking multiple parameter functions into smaller one parameter functions. It is the mathematically correct way of doing it, but that is not the only reason it is done -- it also leads to a very powerful technique called **partial function application**. This is a very widely used style in functional programming, and it is important to understand it. -The idea of partial application is that if you fix the first N parameters of the function, you get a function of the remaining parameters. From the discussion on currying, you can probably see how this comes about naturally. +В предыдущем посте о каррировании мы увидели как мультипараметрических функции дробятся на функции по меньше лишь с одним параметром. Это математически корректный путь решения, однако есть и другие причины такого выбора -- это также приводит к очень сильной технике называемой **частичное применение функций**. Это очень широко используемый стиль в функциональном программировании, и очень важно его понять. -Here are some simple examples that demonstrate this: +> The idea of partial application is that if you fix the first N parameters of the function, you get a function of the remaining parameters. From the discussion on currying, you can probably see how this comes about naturally. + +Идея частичного применения заключается в том, что если зафиксировать первые N параметров функции получится новая функция с оставшимися параметрами. Из обсуждения каррирования можно было увидеть как частичное применение происходит естественным образом. + +> Here are some simple examples that demonstrate this: + +Несколько простых демостраций: ```fsharp // create an "adder" by partial application of add @@ -40,9 +46,13 @@ let printer = printfn "printing param=%i" [1;2;3] |> List.iter printer ``` -In each case, we create a partially applied function that we can then reuse in multiple contexts. +> In each case, we create a partially applied function that we can then reuse in multiple contexts. + +В каждом случае мы создаем частично примененную функцию, которую можно использовать в разных ситауациях. + +> The partial application can just as easily involve fixing function parameters, of course. Here are some examples: -The partial application can just as easily involve fixing function parameters, of course. Here are some examples: +И конечно, частичное применение может также легко фиксировать параметры-функции: ```fsharp // an example using List.map @@ -60,12 +70,19 @@ let filterEvens = filterEvens [1;2;3;4] ``` -The following more complex example shows how the same approach can be used to create "plug in" behavior that is transparent. +> The following more complex example shows how the same approach can be used to create "plug in" behavior that is transparent. -* We create a function that adds two numbers, but in addition takes a logging function that will log the two numbers and the result. -* The logging function has two parameters: (string) "name" and (generic) "value", so it has signature `string->'a->unit`. -* We then create various implementations of the logging function, such as a console logger or a popup logger. -* And finally we partially apply the main function to create new functions that have a particular logger baked into them. +Следующий более сложный пример показывает как тот же подход может быть использоватн, чтобы прозрачно создать "встраивоемое" поведение. + +> * We create a function that adds two numbers, but in addition takes a logging function that will log the two numbers and the result. +> * The logging function has two parameters: (string) "name" and (generic) "value", so it has signature `string->'a->unit`. +> * We then create various implementations of the logging function, such as a console logger or a popup logger. +> * And finally we partially apply the main function to create new functions that have a particular logger baked into them. + +* Мы создаем функцию, которая складывает два числа, но в дополнения она принимает функцию логирования, которая будет логировать два числа и результат. +* Функция логирования имеет два параметра: (string) "name" и (generic) "value", поэтому имеет сигнатуру `string->'a->unit`. +* Затем мы создаем различные реализации логирующей функции, такие как консольный логгер или логгер на основе всплывающего окна. +* И наконец мы частично применяем основную функцию для создания новой функции, с замкнутым логгером. ```fsharp // create an adder that supports a pluggable logging function @@ -98,7 +115,9 @@ addWithPopupLogger 1 2 addWithPopupLogger 42 99 ``` -These functions with the logger baked in can in turn be used like any other function. For example, we can create a partial application to add 42, and then pass that into a list function, just like we did for the simple "`add42`" function. +> These functions with the logger baked in can in turn be used like any other function. For example, we can create a partial application to add 42, and then pass that into a list function, just like we did for the simple "`add42`" function. + +Эти функции с замкнутым логгером могут быть использованы как любые другие функции. Например, мы можем создать частичное применение для прибавления 42, и затем передать его в списочную функцию, как мы делали для простой функции "`add42`". ```fsharp // create a another adder with 42 baked in @@ -107,15 +126,21 @@ let add42WithConsoleLogger = addWithConsoleLogger 42 [1;2;3] |> List.map add42 //compare without logger ``` -These partially applied functions are a very useful tool. We can create library functions which are flexible (but complicated), yet make it easy to create reusable defaults so that callers don't have to be exposed to the complexity all the time. +> These partially applied functions are a very useful tool. We can create library functions which are flexible (but complicated), yet make it easy to create reusable defaults so that callers don't have to be exposed to the complexity all the time. + +Частично примененные функции являются очень полезным инструментом. _Мы можем создать библиотечные функции, которые являются гибкими (хотя и сложными), причем легко сделать их многоразовыми по умолчанию, так что вызывающему не потребуется прибегать к сложной форме при каждом использовании._ -## Designing functions for partial application ## +## Designing functions for partial application | Проектирование функций для частичного применения ## -You can see that the order of the parameters can make a big difference in the ease of use for partial application. For example, most of the functions in the `List` library such as `List.map` and `List.filter` have a similar form, namely: +> You can see that the order of the parameters can make a big difference in the ease of use for partial application. For example, most of the functions in the `List` library such as `List.map` and `List.filter` have a similar form, namely: + +Видно, что порядок параметров может серьезно влиять на удобство использования частичного применения. Например, большинство функций в `List` таких как `List.map` и `List.filter` имеют схожую форму, а именно: List-function [function parameter(s)] [list] -The list is always the last parameter. Here are some examples of the full form: +> The list is always the last parameter. Here are some examples of the full form: + +Список всегда является последним параметром. Несколько примеров в полной форме: ```fsharp List.map (fun i -> i+1) [0;1;2;3] @@ -123,7 +148,9 @@ List.filter (fun i -> i>1) [0;1;2;3] List.sortBy (fun i -> -i ) [0;1;2;3] ``` -And the same examples using partial application: +> And the same examples using partial application: + +Те же самые примеры с использованием частичного применения: ```fsharp let eachAdd1 = List.map (fun i -> i+1) @@ -136,17 +163,29 @@ let sortDesc = List.sortBy (fun i -> -i) sortDesc [0;1;2;3] ``` -If the library functions were written with the parameters in a different order, it would be much more inconvenient to use them with partial application. +> If the library functions were written with the parameters in a different order, it would be much more inconvenient to use them with partial application. + +Если бы библиотечные функции были написаны с другим порядком аргументов, частичное применение было бы значительно осложнено. + +> As you write your own multi-parameter functions, you might wonder what the best parameter order is. As with all design questions, there is no "right" answer to this question, but here are some commonly accepted guidelines: + +При написании своей мультипараметрической функции можно задаться вопросом о наилучшем порядке параметров. Как и во всех архитектурных вопросах, здесь нет "правильного" ответа, но есть несколько общепринятых рекомендаций. + +> 1. Put earlier: parameters more likely to be static +> 2. Put last: the data structure or collection (or most varying argument) +> 3. For well-known operations such as "subtract", put in the expected order + +1. Сначала идут параметры, которые скорее всего будут статичными +2. Потом структуры данных или коллекции (или другие изменяющиеся параметры) +3. Для лучшего вопсриятия операций таких как "subtract", желательно соблюдать ожидаемый порядок -As you write your own multi-parameter functions, you might wonder what the best parameter order is. As with all design questions, there is no "right" answer to this question, but here are some commonly accepted guidelines: +> Guideline 1 is straightforward. The parameters that are most likely to be "fixed" with partial application should be first. We saw this with the logger example earlier. -1. Put earlier: parameters more likely to be static -2. Put last: the data structure or collection (or most varying argument) -3. For well-known operations such as "subtract", put in the expected order +Первое правило незамысловато. Параметры, котоыре скорее всего будут "зафиксированны" частичным применением должны идти первыми, например как в примерах с логгером ранее. -Guideline 1 is straightforward. The parameters that are most likely to be "fixed" with partial application should be first. We saw this with the logger example earlier. +> Guideline 2 makes it easier to pipe a structure or collection from function to function. We have seen this many times already with list functions. -Guideline 2 makes it easier to pipe a structure or collection from function to function. We have seen this many times already with list functions. +Второе правило облегчает использование pipe оператора и композиций, как уже было показано во множестве примеров ранее со списками функций. ```fsharp // piping using list functions @@ -156,7 +195,9 @@ let result = |> List.filter (fun i -> i>5) ``` -Similarly, partially applied list functions are easy to compose, because the list parameter itself can be easily elided: +> Similarly, partially applied list functions are easy to compose, because the list parameter itself can be easily elided: + +Аналогично, частичное примененные функции над списками легко компануется, т.к. `list`-параметр легко игнорируется: ```fsharp let compositeOp = List.map (fun i -> i+1) @@ -164,11 +205,15 @@ let compositeOp = List.map (fun i -> i+1) let result = compositeOp [1..10] ``` -### Wrapping BCL functions for partial application ### +### Wrapping BCL functions for partial application | Оборачивание BCL функций ждя частичного применения ### + +> The .NET base class library functions are easy to access in F#, but are not really designed for use with a functional language like F#. For example, most functions have the data parameter first, while with F#, as we have seen, the data parameter should normally come last. -The .NET base class library functions are easy to access in F#, but are not really designed for use with a functional language like F#. For example, most functions have the data parameter first, while with F#, as we have seen, the data parameter should normally come last. +Функции библиотеки базовых классов (base class library - BCL) .NET легко доступны из F#, но они в действительности не расчитаны на использование из функциональных языков таких как F#. Например, большинство функций требуют параметр данных вначале, в то время как в F# параметр данных в общем случае должен быть последним. -However, it is easy enough to create wrappers for them that are more idiomatic. For example, in the snippet below, the .NET string functions are rewritten to have the string target be the last parameter rather than the first: +> However, it is easy enough to create wrappers for them that are more idiomatic. For example, in the snippet below, the .NET string functions are rewritten to have the string target be the last parameter rather than the first: + +Однако, достаточно легко создать обертки над ними, которые будут более идеоматичны. В примере ниже строковые .NET функции переписаны так, чтобы целевая строка использовалась последней, а не первой: ```fsharp // create wrappers for .NET string functions @@ -179,7 +224,9 @@ let startsWith lookFor (s:string) = s.StartsWith(lookFor) ``` -Once the string becomes the last parameter, we can then use them with pipes in the expected way: +> Once the string becomes the last parameter, we can then use them with pipes in the expected way: + +После того, как строка стала последним параметром, можно использовать их в pipe-ах ожидаемым образом: ```fsharp let result = @@ -193,31 +240,44 @@ let result = or with function composition: +> или в композиции функций: + ```fsharp let compositeOp = replace "h" "j" >> startsWith "j" let result = compositeOp "hello" ``` -### Understanding the "pipe" function ### +### Understanding the "pipe" function | Понимание "pipe" функции (оператора) ### + +> Now that you have seen how partial application works, you should be able to understand how the "pipe" function works. -Now that you have seen how partial application works, you should be able to understand how the "pipe" function works. +Теперь, после демонстрации работы частичного применения, можно понять как работают "pipe" функции. -The pipe function is defined as: +> The pipe function is defined as: + +Определение pipe: ```fsharp let (|>) x f = f x ``` -All it does is allow you to put the function argument in front of the function rather than after. That's all. +> All it does is allow you to put the function argument in front of the function rather than after. That's all. + +Все это позволяет передать аргумент функции спереди, а не с конца. ```fsharp let doSomething x y z = x+y+z doSomething 1 2 3 // all parameters after function ``` -If the function has multiple parameters, then it appears that the input is the final parameter. Actually what is happening is that the function is partially applied, returning a function that has a single parameter: the input +> If the function has multiple parameters, then it appears that the input is the final parameter. Actually what is happening is that the function is partially applied, returning a function that has a single parameter: the input -Here's the same example rewritten to use partial application +_TODO: Перевести на русский._ +_Если функция имеет несколько параметров, то оказывается, что ввод - это последний параметр. В действительности частично примененная функция возвращает функцию, которая имеет один параметр - ввод._ + +> Here's the same example rewritten to use partial application + +Тот же пример переписанный с использованием частичного применения ```fsharp let doSomething x y = @@ -229,24 +289,32 @@ doSomethingPartial 3 // only one parameter after function now 3 |> doSomethingPartial // same as above - last parameter piped in ``` -As you have already seen, the pipe operator is extremely common in F#, and used all the time to preserve a natural flow. Here are some more usages that you might see: +> As you have already seen, the pipe operator is extremely common in F#, and used all the time to preserve a natural flow. Here are some more usages that you might see: + +Как было показано, pipe оператор чрезвычайно распространен в F#, и используется всякий раз, когда требуется сохранить естественную запись. Еще несколько примеров, которые можно было видеть ранее: ```fsharp "12" |> int // parses string "12" to an int 1 |> (+) 2 |> (*) 3 // chain of arithmetic ``` -### The reverse pipe function ### +### The reverse pipe function | Обратный pipe оператор ### + +> You might occasionally see the reverse pipe function "<|" being used. -You might occasionally see the reverse pipe function "<|" being used. +Иногда можно увидеть использование обратного pipe оператора "<|". ```fsharp let (<|) f x = f x ``` -It seems that this function doesn't really do anything different from normal, so why does it exist? +> It seems that this function doesn't really do anything different from normal, so why does it exist? -The reason is that, when used in the infix style as a binary operator, it reduces the need for parentheses and can make the code cleaner. +Кажется, что эта функция в действительности не делает ничего, что отличало бы ее от обычной записи, так зачем же она существует? + +> The reason is that, when used in the infix style as a binary operator, it reduces the need for parentheses and can make the code cleaner. + +Причина в том, что при использовании инфиксного стиля как бинарного оператора, исчезает необходимость в скобках, что делает код чище. ```fsharp printf "%i" 1+2 // error @@ -254,10 +322,12 @@ printf "%i" (1+2) // using parens printf "%i" <| 1+2 // using reverse pipe ``` -You can also use piping in both directions at once to get a pseudo infix notation. +> You can also use piping in both directions at once to get a pseudo infix notation. + +Можно использовать pipe-ы в обоих направлениях сразу для получения псевдо инфиксной нотации. ```fsharp let add x y = x + y (1+2) add (3+4) // error 1+2 |> add <| 3+4 // pseudo infix -``` +``` \ No newline at end of file diff --git a/posts/stack-based-calculator.md b/posts/stack-based-calculator.md index 2bf7105..f3f2d1d 100644 --- a/posts/stack-based-calculator.md +++ b/posts/stack-based-calculator.md @@ -8,43 +8,63 @@ seriesOrder: 13 categories: [Combinators, Functions, Worked Examples] --- -In this post, we'll implement a simple stack based calculator (also known as "reverse Polish" style). The implementation is almost entirely done with functions, with only one special type and no pattern matching at all, so it is a great testing ground for the concepts introduced in this series. +> In this post, we'll implement a simple stack based calculator (also known as "reverse Polish" style). The implementation is almost entirely done with functions, with only one special type and no pattern matching at all, so it is a great testing ground for the concepts introduced in this series. -If you are not familiar with a stack based calculator, it works as follows: numbers are pushed on a stack, and operations such as addition and multiplication pop numbers off the stack and push the result back on. +В данной статье мы реализуем простой основанный на стеке калькулятор (также известный как "обратный польский" стиль). Реализация почти полностью построена на функциях, лишь с одним специальным типом и без сопоставления шаблонов вообще, это превосходный полигон для концпеций затронутых в данной серии. -Here is a diagram showing a simple calculation using a stack: +> If you are not familiar with a stack based calculator, it works as follows: numbers are pushed on a stack, and operations such as addition and multiplication pop numbers off the stack and push the result back on. + +Если вы незнакомы с подобным калькулятором, то он работает следующим образом: числа помещаются в стек, а операции, такие как сложение и произведение числа забирают числа с вершины стека, после чего помещают обратно полученнный результат операции. + +> Here is a diagram showing a simple calculation using a stack: + +Схема простого вычисления на стеке: ![Stack based calculator diagram](../assets/img/stack-based-calculator.png) -The first steps to designing a system like this is to think about how it would be used. Following a Forth like syntax, we will give each action a label, so that the example above might want to be written something like: +> The first steps to designing a system like this is to think about how it would be used. Following a Forth like syntax, we will give each action a label, so that the example above might want to be written something like: + +Прежде чем проектировать подобную систему, следует порассуждать над тем, как она будет использоваться. Следуя подобному Forth синтаксису, дадим каждому действию соответсвующую метку, чтобы в приведенном выше примере можно было написать нечто вроде: EMPTY ONE THREE ADD TWO MUL SHOW -We might not be able to get this exact syntax, but let's see how close we can get. +> We might not be able to get this exact syntax, but let's see how close we can get. + +Возможно, точно такой синтаксис получить не удастся, но попробуем приблизиться к этому как можно ближе. + +## The Stack data type | Тип данных стека -## The Stack data type +> First we need to define the data structure for a stack. To keep things simple, we'll just use a list of floats. -First we need to define the data structure for a stack. To keep things simple, we'll just use a list of floats. +Во первых надо определить структуру данных для стека. Это просто, для этих целей можно использовать список float-ов. ```fsharp type Stack = float list ``` -But, hold on, let's wrap it in a [single case union type](../posts/discriminated-unions.md#single-case) to make it more descriptive, like this: +> But, hold on, let's wrap it in a [single case union type](../posts/discriminated-unions.md#single-case) to make it more descriptive, like this: + +Но лучше обернуть его в [single case union type](../posts/discriminated-unions.md#single-case) чтобы сделать его более наглядным, например так: ```fsharp type Stack = StackContents of float list ``` -For more details on why this is nicer, read the discussion of single case union types in [this post](../posts/discriminated-unions.md#single-case). +> For more details on why this is nicer, read the discussion of single case union types in [this post](../posts/discriminated-unions.md#single-case). + +Почему лучше делать именно так, можно прочитать [здесь](../posts/discriminated-unions.md#single-case). + +> Now, to create a new stack, we use `StackContents` as a constructor: -Now, to create a new stack, we use `StackContents` as a constructor: +Теперь создадим новый стек, используем `StackContents` в качестве конструктора: ```fsharp let newStack = StackContents [1.0;2.0;3.0] ``` -And to extract the contents of an existing Stack, we pattern match with `StackContents`: +> And to extract the contents of an existing Stack, we pattern match with `StackContents`: + +Для извлечения содержимого из существующего Stack-а используется сопоставление с шаблоном `StackContents`: ```fsharp let (StackContents contents) = newStack @@ -54,11 +74,15 @@ let (StackContents contents) = newStack ``` -## The Push function +## The Push function | Функция Push + +> Next we need a way to push numbers on to the stack. This will be simply be prepending the new value at the front of the list using the "`::`" operator. -Next we need a way to push numbers on to the stack. This will be simply be prepending the new value at the front of the list using the "`::`" operator. +Далее необходим способ помещать числа в данный стек. Для этого достаточно добавить новое значение спереди списка используя "`::`". -Here is our push function: +> Here is our push function: + +Пример функции: ```fsharp let push x aStack = @@ -67,37 +91,60 @@ let push x aStack = StackContents newContents ``` -This basic function has a number of things worth discussing. +> This basic function has a number of things worth discussing. + +Данная функция имеет ряд особенностей, которые стоит обсудить. + +> First, note that the list structure is immutable, so the function must accept an existing stack and return a new stack. It cannot just alter the existing stack. In fact, all of the functions in this example will have a similar format like this: + +Во первых, следует обратить внимание, что структура `list` неизменяема, значит функция должна принимать существующий стек и возвращать новый. Это не просто изменение существующего стека. По факту, все функции в данном примере будут иметь подобный формат: + +> Input: a Stack plus other parameters +> Output: a new Stack + + Input: Stack и еще какой-либо параметр + Output: новый Stack -First, note that the list structure is immutable, so the function must accept an existing stack and return a new stack. It cannot just alter the existing stack. In fact, all of the functions in this example will have a similar format like this: +> Next, what should the order of the parameters be? Should the stack parameter come first or last? If you remember the discussion of [designing functions for partial application](../posts/partial-application), you will remember that the most changeable thing should come last. You'll see shortly that this guideline will be born out. - Input: a Stack plus other parameters - Output: a new Stack +Во вторых, почему параметры идут именно в таком порядке? Почему стек должен идти первым или последним? В обсуждении [проектирование функций с частичным применением](../posts/partial-application) говорилось, что ниболее часто меняющийся параметр должен идти последним. Вскоре можно будет убедиться, что данне рекомендации соблюдаются. -Next, what should the order of the parameters be? Should the stack parameter come first or last? If you remember the discussion of [designing functions for partial application](../posts/partial-application), you will remember that the most changeable thing should come last. You'll see shortly that this guideline will be born out. +> Finally, the function can be made more concise by using pattern matching in the function parameter itself, rather than using a `let` in the body of the function. -Finally, the function can be made more concise by using pattern matching in the function parameter itself, rather than using a `let` in the body of the function. +Наконец, функцию можно сделать более краткой с помощью сопоставления шаблонов в самом параметре функции, вместо `let` в теле функции. -Here is the rewritten version: +> Here is the rewritten version: + +Переписанная версия: ```fsharp let push x (StackContents contents) = StackContents (x::contents) ``` -Much nicer! +> Much nicer! + +Намного лучше! + +> And by the way, look at the nice signature it has: -And by the way, look at the nice signature it has: +Между прочим, посмотретите на ее изящную сигнатуру: ```fsharp val push : float -> Stack -> Stack ``` -As we know from a [previous post](../posts/function-signatures), the signature tells you a lot about the function. -In this case, I could probably guess what it did from the signature alone, even without knowing that the name of the function was "push". -This is one of the reasons why it is a good idea to have explicit type names. If the stack type had just been a list of floats, it wouldn't have been as self-documenting. +> As we know from a [previous post](../posts/function-signatures), the signature tells you a lot about the function. +> In this case, I could probably guess what it did from the signature alone, even without knowing that the name of the function was "push". +> This is one of the reasons why it is a good idea to have explicit type names. If the stack type had just been a list of floats, it wouldn't have been as self-documenting. + +Как говорилось [ранее](../posts/function-signatures), сигнатура говорит нам, очень много о функции. +В данном случае, я мог бы догадаться, что делает данная функция лишь по ее сигнатуре, не зная, что она называется "push". +Это еще одна причина по которой было хорошей идеей иметь явные имена типа. Если бы стек был лишь списком float-ов, функция не была бы столь самодокументированна. + +> Anyway, now let's test it: -Anyway, now let's test it: +Так или иначе, проверим: ```fsharp let emptyStack = StackContents [] @@ -105,18 +152,24 @@ let stackWith1 = push 1.0 emptyStack let stackWith2 = push 2.0 stackWith1 ``` -Works great! +> Works great! -## Building on top of "push" +Прекрасно работает! -With this simple function in place, we can easily define an operation that pushes a particular number onto the stack. +## Building on top of "push" | Надстрока вершины стека при помощи "push" + +> With this simple function in place, we can easily define an operation that pushes a particular number onto the stack. + +С помощью это простой функции, можно легко определить операцию помещающую определенное число в стек. ```fsharp let ONE stack = push 1.0 stack let TWO stack = push 2.0 stack ``` -But wait a minute! Can you see that the `stack` parameter is used on both sides? In fact, we don't need to mention it at all. Instead we can skip the `stack` parameter and write the functions using partial application as follows: +> But wait a minute! Can you see that the `stack` parameter is used on both sides? In fact, we don't need to mention it at all. Instead we can skip the `stack` parameter and write the functions using partial application as follows: + +Но подождите минуту! Разве не видите, что параметр `stack` используется с двух сторон? В действительно, совершенно необязательно упоминать его два раза. Вместо этого можно опустить параметр и написать функцию с частичным применением: ```fsharp let ONE = push 1.0 @@ -126,15 +179,21 @@ let FOUR = push 4.0 let FIVE = push 5.0 ``` -Now you can see that if the parameters for `push` were in a different order, we wouldn't have been able to do this. +> Now you can see that if the parameters for `push` were in a different order, we wouldn't have been able to do this. -While we're at it, let's define a function that creates an empty stack as well: +Теперь очевидно, имей параметры `push` другой порядок, `stack` пришлось бы упоминать дважды. + +> While we're at it, let's define a function that creates an empty stack as well: + +Стоит так же определить функцию создающую пустой стек: ```fsharp let EMPTY = StackContents [] ``` -Let's test all of these now: +> Let's test all of these now: + +Проверим получнные функции: ```fsharp let stackWith1 = ONE EMPTY @@ -142,13 +201,17 @@ let stackWith2 = TWO stackWith1 let stackWith3 = THREE stackWith2 ``` -These intermediate stacks are annoying ? can we get rid of them? Yes! Note that these functions ONE, TWO, THREE all have the same signature: +> These intermediate stacks are annoying ? can we get rid of them? Yes! Note that these functions ONE, TWO, THREE all have the same signature: + +Эти промежуточные стеки раздражают? Можно ли избавиться от них? Конечно! Заметьте, что функции ONE, TWO и THREE имеют одинаковую сигнатуру: ```fsharp Stack -> Stack ``` -This means that they can be chained together nicely! The output of one can be fed into the input of the next, as shown below: +> This means that they can be chained together nicely! The output of one can be fed into the input of the next, as shown below: + +А значит, они прекрасно соеденяются вместе! Вывод одной функции может быть подан на вход следующей: ```fsharp let result123 = EMPTY |> ONE |> TWO |> THREE @@ -156,20 +219,32 @@ let result312 = EMPTY |> THREE |> ONE |> TWO ``` -## Popping the stack +## Popping the stack | ~~Выталкивание стека~~ // TODO: Исправить + +> That takes care of pushing onto the stack ? what about a `pop` function next? + +С помещением в стек разобрались, но что на счет функции `pop`? + +> When we pop the stack, we will return the top of the stack, obviously, but is that all? -That takes care of pushing onto the stack ? what about a `pop` function next? +При извлечении из стека, очевидно необходимо вернуть вершину стека, но только ли ее? -When we pop the stack, we will return the top of the stack, obviously, but is that all? +> In an object-oriented style, [the answer is yes](http://msdn.microsoft.com/en-us/library/system.collections.stack.pop.aspx). In an OO approach, we would *mutate* the stack itself behind the scenes, so that the top element was removed. -In an object-oriented style, [the answer is yes](http://msdn.microsoft.com/en-us/library/system.collections.stack.pop.aspx). In an OO approach, we would *mutate* the stack itself behind the scenes, so that the top element was removed. +В объектно-ориентированном стиле, [ответ да](http://msdn.microsoft.com/en-us/library/system.collections.stack.pop.aspx). Но в случае ООП, стек был бы изменен за кулисами, так что верхний элемент был бы удален. -But in a functional style, the stack is immutable. The only way to remove the top element is to create a *new stack* with the element removed. -In order for the caller to have access to this new diminished stack, it needs to be returned along with the top element itself. +> But in a functional style, the stack is immutable. The only way to remove the top element is to create a *new stack* with the element removed. +> In order for the caller to have access to this new diminished stack, it needs to be returned along with the top element itself. -In other words, the `pop` function will have to return *two* values, the top plus the new stack. The easiest way to do this in F# is just to use a tuple. +Однако в функциональном стиле стек неизменям. Есть только один способ удалить верхний элемент - создать _новый стек_ без удаленного элемента. Для того, чтобы вызывающий объект имел доступ к новому уменьшенному стеку, его необходимо вернуть вместе с верхним элементом. -Here's the implementation: +> In other words, the `pop` function will have to return *two* values, the top plus the new stack. The easiest way to do this in F# is just to use a tuple. + +Другими словами, функция `pop` должна возвращать _два_ значения, верхний элемент и новый стек. Простейший способ сделать это в F# - это просто использовать кортеж. + +> Here's the implementation: + +Реализация: ```fsharp /// Pop a value from the stack and return it @@ -181,23 +256,41 @@ let pop (StackContents contents) = (top,newStack) ``` -This function is also very straightforward. +> This function is also very straightforward. + +Полученная функция также очень проста. + +> As before, we are extracting the `contents` directly in the parameter. + +Как и прежде, `contents` извлекается прямо из параметра. + +> We then use a `match..with` expression to test the contents. + +Затем с помощью выражения `match..with` проверяется содержимое `contents`. + +> Next, we separate the top element from the rest, create a new stack from the remaining elements and finally return the pair as a tuple. + +Потом верхний элемент отделяется от остальной части списка, создается новый стек на основе оставшихся элементов, и наконец все это возвращается парой в виде кортежа. + +> Try the code above and see what happens. You will get a compiler error! +> The compiler has caught a case we have overlooked -- what happens if the stack is empty? -As before, we are extracting the `contents` directly in the parameter. +Но если попробовать выполнить коды выше, будет получена ошибка компиляции! +Компилятор обнаружил случай, который не был отработан, что произойдет, если стек пуст? -We then use a `match..with` expression to test the contents. +> So now we have to decide how to handle this. -Next, we separate the top element from the rest, create a new stack from the remaining elements and finally return the pair as a tuple. +Надо решить, как это обрабатывать. -Try the code above and see what happens. You will get a compiler error! -The compiler has caught a case we have overlooked -- what happens if the stack is empty? +> * Option 1: Return a special "Success" or "Error" state, as we did in a [post from the "why use F#?" series](../posts/correctness-exhaustive-pattern-matching.md). +> * Option 2: Throw an exception. -So now we have to decide how to handle this. +* Вариант 1: Вернуть специальное состояние "Success" или "Error", как это делалось в [посте из серии "why use F#?"](../posts/correctness-exhaustive-pattern-matching.md). +* Вариант 2: Выбросить исключение. -* Option 1: Return a special "Success" or "Error" state, as we did in a [post from the "why use F#?" series](../posts/correctness-exhaustive-pattern-matching.md). -* Option 2: Throw an exception. +> Generally, I prefer to use error cases, but in this case, we'll use an exception. So here's the `pop` code changed to handle the empty case: -Generally, I prefer to use error cases, but in this case, we'll use an exception. So here's the `pop` code changed to handle the empty case: +Обычно, я предпочитаю использовать специализированные состояния ошибки, но в данном конкретном случае я предпочел использовать исключение. Исправленная версия `pop` с обработкой пустого случая: ```fsharp /// Pop a value from the stack and return it @@ -211,7 +304,9 @@ let pop (StackContents contents) = failwith "Stack underflow" ``` -Now let's test it: +> Now let's test it: + +Проверим: ```fsharp let initialStack = EMPTY |> ONE |> TWO @@ -219,15 +314,19 @@ let popped1, poppedStack = pop initialStack let popped2, poppedStack2 = pop poppedStack ``` -and to test the underflow: +> and to test the underflow: + +и тест на исключение: ```fsharp let _ = pop EMPTY ``` -## Writing the math functions +## Writing the math functions | Математические функции + +> Now with both push and pop in place, we can work on the "add" and "multiply" functions: -Now with both push and pop in place, we can work on the "add" and "multiply" functions: +Теперь когда добавление и удаление на месте, можно начать работу с функциями "add" и "mulytiply": ```fsharp let ADD stack = @@ -243,7 +342,9 @@ let MUL stack = push result s2 //push back on the doubly-popped stack ``` -Test these interactively: +> Test these interactively: + +Проверка интерактивном режиме: ```fsharp let add1and2 = EMPTY |> ONE |> TWO |> ADD @@ -251,13 +352,19 @@ let add2and3 = EMPTY |> TWO |> THREE |> ADD let mult2and3 = EMPTY |> TWO |> THREE |> MUL ``` -It works! +> It works! + +Оно работает! + +### Time to refactor... | Время рефакторинга -### Time to refactor... +> It is obvious that there is significant duplicate code between these two functions. How can we refactor? -It is obvious that there is significant duplicate code between these two functions. How can we refactor? +Очевидно, что присутствует значительное количество повторяющегося кода между этими двумя функциями. Но как провести рефакторинг? -Both functions pop two values from the stack, apply some sort of binary function, and then push the result back on the stack. This leads us to refactor out the common code into a "binary" function that takes a two parameter math function as a parameter: +> Both functions pop two values from the stack, apply some sort of binary function, and then push the result back on the stack. This leads us to refactor out the common code into a "binary" function that takes a two parameter math function as a parameter: + +Обе функции извлекают два значения из стека, применяют к ним какую-нибудь бинарную функцию, после чего помещают результат обратно в стек. Можно вывести общий код в функцию "binary", которая принимает математическую функцию с двумя параметрами: ```fsharp let binary mathFn stack = @@ -271,31 +378,45 @@ let binary mathFn stack = push z stack'' ``` -*Note that in this implementation, I've switched to using ticks to represent changed states of the "same" object, rather than numeric suffixes. Numeric suffixes can easily get quite confusing.* +> *Note that in this implementation, I've switched to using ticks to represent changed states of the "same" object, rather than numeric suffixes. Numeric suffixes can easily get quite confusing.* + +_В данной реализации разные версии "одного" объекта помечены различным количеством одинарных ковычек. Это сделано потому, что числовые суффиксы как правило могут легко запутать._ + +> Question: why are the parameters in the order they are, instead of `mathFn` being after `stack`? + +Вопрос: почему параметры имеют именно такой порядок, вместо `mathFn` идущего после `stack`? + +> Now that we have `binary`, we can define ADD and friends more simply: -Question: why are the parameters in the order they are, instead of `mathFn` being after `stack`? +Теперь когда есть `binary`, можно гораздо проще определить ADD и другие функции: -Now that we have `binary`, we can define ADD and friends more simply: +> Here's a first attempt at ADD using the new `binary` helper: -Here's a first attempt at ADD using the new `binary` helper: +Первая попытка реализации ADD с помощью `binary`: ```fsharp let ADD aStack = binary (fun x y -> x + y) aStack ``` -But we can eliminate the lambda, as it is *exactly* the definition of the built-in `+` function! Which gives us: +> But we can eliminate the lambda, as it is *exactly* the definition of the built-in `+` function! Which gives us: + +Можно исключить лямбду, т.к. она представляет *точное* определение встроенной функции `+`: ```fsharp let ADD aStack = binary (+) aStack ``` -And again, we can use partial application to hide the stack parameter. Here's the final definition: +> And again, we can use partial application to hide the stack parameter. Here's the final definition: + +Опять же, можно использовать частичное применение, чтобы скрыть параметр стека. Финальное определение: ```fsharp let ADD = binary (+) ``` -And here's the definition of some other math functions: +> And here's the definition of some other math functions: + +Определение других математических функций: ```fsharp let SUB = binary (-) @@ -303,7 +424,9 @@ let MUL = binary (*) let DIV = binary (../) ``` -Let's test interactively again. +> Let's test interactively again. + +Попробуйте в интерактивном режиме: ```fsharp let div2by3 = EMPTY |> THREE|> TWO |> DIV @@ -311,7 +434,9 @@ let sub2from5 = EMPTY |> TWO |> FIVE |> SUB let add1and2thenSub3 = EMPTY |> ONE |> TWO |> ADD |> THREE |> SUB ``` -In a similar fashion, we can create a helper function for unary functions +> In a similar fashion, we can create a helper function for unary functions + +Схожим образом можно создать вспомогательную функцию для унарных операций ```fsharp let unary f stack = @@ -319,23 +444,29 @@ let unary f stack = push (f x) stack' //push the function value on the stack ``` -And then define some unary functions: +> And then define some unary functions: + +И определить несколько унарных функци: ```fsharp let NEG = unary (fun x -> -x) let SQUARE = unary (fun x -> x * x) ``` -Test interactively again: +> Test interactively again: + +Интерактив: ```fsharp let neg3 = EMPTY |> THREE|> NEG let square2 = EMPTY |> TWO |> SQUARE ``` -## Putting it all together +## Putting it all together | Сбор всего вместе + +> In the original requirements, we mentioned that we wanted to be able to show the results, so let's define a SHOW function. -In the original requirements, we mentioned that we wanted to be able to show the results, so let's define a SHOW function. +В изначальных требования упоминалось, что мы хотели показать результаты, поэтому стоит опредлеить функцию SHOW. ```fsharp let SHOW stack = @@ -344,19 +475,27 @@ let SHOW stack = stack // keep going with same stack ``` -Note that in this case, we pop the original stack but ignore the diminished version. The final result of the function is the original stack, as if it had never been popped. +> Note that in this case, we pop the original stack but ignore the diminished version. The final result of the function is the original stack, as if it had never been popped. + +Обратите внимание, что в данном случае новая версия стека получаемая через `pop` игнорируется. Конечным же результатом объявляется исходный стек, как будто он никогда не изменялся. -So now finally, we can write the code example from the original requirements +> So now finally, we can write the code example from the original requirements + +Наконец, можно написать следующий пример из оригинальных требований ```fsharp EMPTY |> ONE |> THREE |> ADD |> TWO |> MUL |> SHOW ``` -### Going further +### Going further | Идем дальше + +> This is fun -- what else can we do? -This is fun -- what else can we do? +Это весело, но что еще можно сделать? -Well, we can define a few more core helper functions: +> Well, we can define a few more core helper functions: + +Можно определить несколько дополнительных функций: ```fsharp /// Duplicate the top value on the stack @@ -376,7 +515,9 @@ let SWAP stack = let START = EMPTY ``` -And with these additional functions in place, we can write some nice examples: +> And with these additional functions in place, we can write some nice examples: + +С помощью этих дополнительных функций можно написать несколько изящных примеров: ```fsharp START @@ -395,19 +536,27 @@ START |> TWO |> SWAP |> DIV |> SHOW // 9 div 2 = 4.5 ``` -## Using composition instead of piping +## Using composition instead of piping | Использование композиции вместо конвейеризации + +> But that's not all. In fact, there is another very interesting way to think about these functions. -But that's not all. In fact, there is another very interesting way to think about these functions. +Но и это еще не все, на самом деле, существует другой интересный способ представления данных функций. -As I pointed out earlier, they all have an identical signature: +> As I pointed out earlier, they all have an identical signature: + +Как отмечалось ранее, все они имеют одинаковую сигнатуру: ```fsharp Stack -> Stack ``` -So, because the input and output types are the same, these functions can be composed using the composition operator `>>`, not just chained together with pipes. +> So, because the input and output types are the same, these functions can be composed using the composition operator `>>`, not just chained together with pipes. + +Т.к. ввод и вывод имеют одинаковые типы, эти функции могут быть скомпанованы еще и при помощи оператора `>>`, не только посредством pipe-ов. + +> Here are some examples: -Here are some examples: +Несколько примеров: ```fsharp // define a new function @@ -442,50 +591,74 @@ let SUM_NUMBERS_UPTO = START |> THREE |> SQUARE |> SUM_NUMBERS_UPTO |> SHOW ``` -In each of these cases, a new function is defined by composing other functions together to make a new one. This is a good example of the "combinator" approach to building up functionality. +> In each of these cases, a new function is defined by composing other functions together to make a new one. This is a good example of the "combinator" approach to building up functionality. + +В каждом из этих примеров определяется новая функция составленная из других функций. Это хороший пример "комбинаторного" подхода к построению функциональности. + +## Pipes vs composition | Конвееры против композиции + +> We have now seen two different ways that this stack based model can be used; by piping or by composition. So what is the difference? And why would we prefer one way over another? + +Были показаны два различных способа использования данной основанной на стеке модели; при помощи конвееров и композиций. Но в чем разница? И почему надо предпочесть один из способов другому? + +> The difference is that piping is, in a sense, a "realtime transformation" operation. When you use piping you are actually doing the operations right now, passing a particular stack around. -## Pipes vs composition +Разница в том, что pipe-ы в некотором смысле являются операцией "в реальном времени". В момент использования конвеера операции выполняются прямо сейчас, через передачу определнного стека. -We have now seen two different ways that this stack based model can be used; by piping or by composition. So what is the difference? And why would we prefer one way over another? +> On the other hand, composition is a kind of "plan" for what you want to do, building an overall function from a set of parts, but *not* actually running it yet. -The difference is that piping is, in a sense, a "realtime transformation" operation. When you use piping you are actually doing the operations right now, passing a particular stack around. +С другой стороны, композиция является разновидностью "плана", который мы хотим осуществить, построение функций из набора составляющих без непосредственного применения. -On the other hand, composition is a kind of "plan" for what you want to do, building an overall function from a set of parts, but *not* actually running it yet. +> So for example, I can create a "plan" for how to square a number by combining smaller operations: -So for example, I can create a "plan" for how to square a number by combining smaller operations: +Например, можно создать "план" вычисления квадрата числа через комбинацию малых операций: ```fsharp let COMPOSED_SQUARE = DUP >> MUL ``` -I cannot do the equivalent with the piping approach. +> I cannot do the equivalent with the piping approach. + +Я не могу привести эквивалент на основе конвееров. ```fsharp let PIPED_SQUARE = DUP |> MUL ``` -This causes a compilation error. I have to have some sort of concrete stack instance to make it work: +> This causes a compilation error. I have to have some sort of concrete stack instance to make it work: + +Это приведет к ошибке компиляции. Мне нужен какой-то конкретный экземпляр стека, чтобы выражение заработало: ```fsharp let stackWith2 = EMPTY |> TWO let twoSquared = stackWith2 |> DUP |> MUL ``` -And even then, I only get the answer for this particular input, not a plan for all possible inputs, as in the COMPOSED_SQUARE example. +> And even then, I only get the answer for this particular input, not a plan for all possible inputs, as in the COMPOSED_SQUARE example. + +И даже в этом случае, я смогу получить ответ только для этого конкретного ввода, а не обобщенный план вычисления на основе любого ввода, как в примере с `COMPOSED_SQUARE`. -The other way to create a "plan" is to explicitly pass in a lambda to a more primitive function, as we saw near the beginning: +> The other way to create a "plan" is to explicitly pass in a lambda to a more primitive function, as we saw near the beginning: + +Другой способ создать "план" - явно передать лямбду в более примитивные функции: ```fsharp let LAMBDA_SQUARE = unary (fun x -> x * x) ``` -This is much more explicit (and is likely to be faster) but loses all the benefits and clarity of the composition approach. +> This is much more explicit (and is likely to be faster) but loses all the benefits and clarity of the composition approach. + +Это более явный способ (и скорее всего более быстрый), но теряются все преимущества и ясность композиционного подхода. -So, in general, go for the composition approach if you can! +> So, in general, go for the composition approach if you can! -## The complete code +В общем, если это возможно, следует стремиться к композиционному подходу! -Here's the complete code for all the examples so far. +## The complete code | Полный код + +> Here's the complete code for all the examples so far. + +Полный код для всех примеров представленных ранее: ```fsharp // ============================================== @@ -603,10 +776,16 @@ let SUM_NUMBERS_UPTO = >> TWO >> SWAP >> DIV // n(n+1) / 2 ``` -## Summary +## Summary | Заключение + +> So there we have it, a simple stack based calculator. We've seen how we can start with a few primitive operations (`push`, `pop`, `binary`, `unary`) and from them, build up a whole domain specific language that is both easy to implement and easy to use. + +У нас получился простой калькулятор на основе стека. Мы увидели как начиная с нескольких примитивных операций (`push`, `pop`, `binary`, `unary`) и других, можно построить полноценную DSL, легко написанную, легко используемую. + +> As you might guess, this example is based heavily on the Forth language. I highly recommend the free book ["Thinking Forth"](http://thinking-forth.sourceforge.net/), which is not just about the Forth language, but about (*non* object-oriented!) problem decomposition techniques which are equally applicable to functional programming. -So there we have it, a simple stack based calculator. We've seen how we can start with a few primitive operations (`push`, `pop`, `binary`, `unary`) and from them, build up a whole domain specific language that is both easy to implement and easy to use. +Как можно догадаться, данный пример был в изрядной степени основан на языке Forth. Я очень рекомендую бесплатную книгу ["Thinking Forth"](http://thinking-forth.sourceforge.net/), которая повествует не только об языке Forth, но и об других (_не_ объектно ориентированных!) методах декомпозиции задач, которые одинакого применимы к функциональному программированию в целом. -As you might guess, this example is based heavily on the Forth language. I highly recommend the free book ["Thinking Forth"](http://thinking-forth.sourceforge.net/), which is not just about the Forth language, but about (*non* object-oriented!) problem decomposition techniques which are equally applicable to functional programming. +> I got the idea for this post from a great blog by [Ashley Feniello](http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx). If you want to go deeper into emulating a stack based language in F#, start there. Have fun! -I got the idea for this post from a great blog by [Ashley Feniello](http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx). If you want to go deeper into emulating a stack based language in F#, start there. Have fun! +Я почерпнул идею для данной статьи из великолепного блога [Ashley Feniello](http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx). Если есть желание погрузиться глубже в эмуляцию основанного на стеке языка на базе F#, начните с него. _Have fun!_ diff --git a/posts/thinking-functionally-intro.md b/posts/thinking-functionally-intro.md index cc0fbd8..502f172 100644 --- a/posts/thinking-functionally-intro.md +++ b/posts/thinking-functionally-intro.md @@ -7,21 +7,38 @@ seriesId: "Thinking functionally" seriesOrder: 1 --- -Now that you have seen some of the power of F# in the ["why use F#"](../series/why-use-fsharp.md) series, we're going to step back and look at the fundamentals of functional programming -- what does it really mean to "program functionally", and how this approach is different from object oriented or imperative programming. +> Now that you have seen some of the power of F# in the ["why use F#"](../series/why-use-fsharp.md) series, we're going to step back and look at the fundamentals of functional programming -- what does it really mean to "program functionally", and how this approach is different from object oriented or imperative programming. -### Changing the way you think ### +Теперь, когда мы увидели часть мощи F# в серии ["why use F#"](../series/why-use-fsharp.md), стоит сделать шаг назад и взглянуть на основы функционального программирования. Что в действительности означает "программировать функционально", и как этот подход отличается от объектно-ориентированного или императивного программирования. -It is important to understand that functional programming is not just a stylistic difference; it is a completely different way of thinking about programming, in the way that truly object-oriented programming (in Smalltalk say) is also a different way of thinking from a traditional imperative language such as C. +### Changing the way you think | Смена образа мышления ### -F# does allow non-functional styles, and it is tempting to retain the habits you already are familiar with. You could just use F# in a non-functional way without really changing your mindset, and not realize what you are missing. To get the most out of F#, and to be fluent and comfortable with functional programming in general, it is critical that you think functionally, not imperatively. -And that is the goal of this series: to help you understand functional programming in a deep way, and help to change the way you think. +> It is important to understand that functional programming is not just a stylistic difference; it is a completely different way of thinking about programming, in the way that truly object-oriented programming (in Smalltalk say) is also a different way of thinking from a traditional imperative language such as C. -This will be a quite abstract series, although I will use lots of short code examples to demonstrate the points. We will cover the following points: +Важно понимать, что функциональное программирование - это не только стилистическое различие. Это совсем другой способ мышления _(о программировании)_, подобно тому как настоящее ООП (скажем Smalltalk) отличается от традиционного императивного языка такого как C. -* **Mathematical functions**. The first post introduces the mathematical ideas behind functional languages, and the benefits that come from this approach. -* **Functions and values**. The next post introduces functions and values, showing how "values" are different from variables, and why there are similarities between function and simple values. -* **Types**. Then we move on to the basic types that work with functions: primitive types such as string and int; the unit type, function types, and generic types. -* **Functions with multiple parameters**. Next, I explain the concepts of "currying" and "partial application". This is where your brain can start to hurt, if you are coming from an imperative background! -* **Defining functions**. Then some posts devoted to the many different ways to define and combine functions. -* **Function signatures**. Then a important post on the critical topic of function signatures: what they mean and how to use them as an aid to understanding. -* **Organizing functions**. Once you know how to create functions, how can you organize them to make them available to the rest of your code? +> F# does allow non-functional styles, and it is tempting to retain the habits you already are familiar with. You could just use F# in a non-functional way without really changing your mindset, and not realize what you are missing. To get the most out of F#, and to be fluent and comfortable with functional programming in general, it is critical that you think functionally, not imperatively. +> And that is the goal of this series: to help you understand functional programming in a deep way, and help to change the way you think. + +F# позволяет использовать нефункциональные стили, что искушает сохранить существующие привычки. Можно просто использовать F# в нефункциональном стиле без реального изменения мировозрения и даже не представлять потерянные возможности. Однако, чтобы получить максимальную отдачу от F#, а также тепло и лампово программировать в функциональном стиле вообще, очень важно думать функционально, а не императивно. +Цель данной серии - помочь понять функциональное программирование _в глубокую сторону_ и изменить способ мышления читателя. + +> This will be a quite abstract series, although I will use lots of short code examples to demonstrate the points. We will cover the following points: + +Это будет довольно абстрактная серия, хотя я буду использовать множество коротких примеров кода для демонстрации некоторых моментов. Будут освещены следующие темы: + +> * **Mathematical functions**. The first post introduces the mathematical ideas behind functional languages, and the benefits that come from this approach. +> * **Functions and values**. The next post introduces functions and values, showing how "values" are different from variables, and why there are similarities between function and simple values. +> * **Types**. Then we move on to the basic types that work with functions: primitive types such as string and int; the unit type, function types, and generic types. +> * **Functions with multiple parameters**. Next, I explain the concepts of "currying" and "partial application". This is where your brain can start to hurt, if you are coming from an imperative background! +> * **Defining functions**. Then some posts devoted to the many different ways to define and combine functions. +> * **Function signatures**. Then a important post on the critical topic of function signatures: what they mean and how to use them as an aid to understanding. +> * **Organizing functions**. Once you know how to create functions, how can you organize them to make them available to the rest of your code? + +* **Математические функции**. Первая статья знакомит с математическими представлениями лежащими в основе функциональных языков и преимуществами, которые приносит данный подход. +* **Функции и значения**. Следующая знакомит с функциями и значениями, объясняет чем "значения" отличаются от переменных, и почему есть сходства между функциями и простыми значениями. +* **Типы**. Затем мы перейдем к основным типам, которые работают с функциаями: примитивные типы, такие как string и int, тип unit, функциональные типы, и типы обобщений (generic). +* **Функции с несколькими параметрами**. Далее я объясню понятия "каррирования" и "частичного применения". В этом месте чьим-то мозгам будет больно, особенно если у этих мозгов только императивное прошлое. +* **Определение функций**. Затем несколько постов будут посвящены множеству различных способов определения и комбинации функций. +* **Сигнатуры функций**. Затем будет важный пост о критическом значении сигнатур функций, что они значат и как использовать их для _постижения_ их содержимого. +* **Организация функций**. Когда станет понятно как создавать функции, возникнет вопрос как организовать их, чтобы сделать их доступными для остальной части кода? diff --git a/posts/type-extensions.md b/posts/type-extensions.md index 2af65bd..2ad7e85 100644 --- a/posts/type-extensions.md +++ b/posts/type-extensions.md @@ -7,12 +7,18 @@ seriesId: "Thinking functionally" seriesOrder: 11 --- -Although we have focused on the pure functional style so far, sometimes it is convenient to switch to an object oriented style. +> Although we have focused on the pure functional style so far, sometimes it is convenient to switch to an object oriented style. And one of the key features of the OO style is the ability to attach functions to a class and "dot into" the class to get the desired behavior. -In F#, this is done using a feature called "type extensions". And any F# type, not just classes, can have functions attached to them. +Хотя до этого повествование было сосредосточено на чисто функциональном стиле, иногда удобно переключится на объектно ориентированный стиль. И одной из ключевых особенностей ОО стиля является возможность прикреплять функции к классу и обращение к классу через точку для получение желаемого поведения. -Here's an example of attaching a function to a record type. +> In F#, this is done using a feature called "type extensions". And any F# type, not just classes, can have functions attached to them. + +В F#, это возможно с помощью особенности называемой "расширением типов" ("type extensions"). У любого F# типа, не только класса, могут быть прикрепленные функции. + +> Here's an example of attaching a function to a record type. + +Пример прикрепления функции к типу записи. ```fsharp module Person = @@ -30,14 +36,22 @@ let person = Person.create "John" "Doe" let fullname = person.FullName ``` -The key things to note are: +> The key things to note are: -* The `with` keyword indicates the start of the list of members -* The `member` keyword shows that this is a member function (i.e. a method) -* The word `this` is a placeholder for the object that is being dotted into (called a "self-identifier"). The placeholder prefixes the function name, and then the function body then uses the same placeholder when it needs to refer to the current instance. +Ключевые слова, на которые следует обратить внимание: + +> * The `with` keyword indicates the start of the list of members +> * The `member` keyword shows that this is a member function (i.e. a method) +> * The word `this` is a placeholder for the object that is being dotted into (called a "self-identifier"). The placeholder prefixes the function name, and then the function body then uses the same placeholder when it needs to refer to the current instance. There is no requirement to use a particular word, just as long as it is consistent. You could use `this` or `self` or `me` or any other word that commonly indicates a self reference. -You don't have to add a member at the same time that you declare the type, you can always add it later in the same module: +* Ключевое слово `with` обозначает начало списка членов +* Ключевое слово `member` показывает, что эта функция является членом (т.е. методом) +* Слово `this` является меткой объекта, на котором вызывается данный метод (называемая также "self-identifier"). Данное слово является префиксом имени функции, и внутри функции можно использовать ее для обращения к текущему экземляру. Не сушествует требований к словам использумым в качестве самоидентификатора, достаточно чтобы они были устойчивы. Можно использовать `this`, `self`, `me` или любое другое слово, которое обычно используется как отсылка на самого себя. + +> You don't have to add a member at the same time that you declare the type, you can always add it later in the same module: + +Нет нужды добавлять член вместе с декларацией типа, всегда можно добавить его позднее в том же модуле: ```fsharp module Person = @@ -61,17 +75,25 @@ let sortableName = person.SortableName ``` -These examples demonstrate what are called "intrinsic extensions". They are compiled into the type itself and are always available whenever the type is used. They also show up when you use reflection. +> These examples demonstrate what are called "intrinsic extensions". They are compiled into the type itself and are always available whenever the type is used. They also show up when you use reflection. + +Эти примеры демонстрируют вызов "встроенных расширений" ("intrinsic extensions"). Они компилируются в тип и будут доступны везде, где бы тип не использовался. Они также будут показаны при рефлексии. -With intrinsic extensions, it is even possible to have a type definition that divided across several files, as long as all the components use the same namespace and are all compiled into the same assembly. +> With intrinsic extensions, it is even possible to have a type definition that divided across several files, as long as all the components use the same namespace and are all compiled into the same assembly. Just as with partial classes in C#, this can be useful to separate generated code from authored code. -## Optional extensions +Внутренние расширения позволяют иметь определение типа распределенное по нескольким файлам, пока все компоненты используют одно и то же пространство имен и компилируются в одну сборку. Так же как и с partial классами в C#, это может быть полезным для разделения сгенерированного и написанного в ручную кода. + +## Optional extensions | Опциональные расширения + +> Another alternative is that you can add an extra member from a completely different module. +> These are called "optional extensions". They are not compiled into the type itself, and require some other module to be in scope for them to work (this behavior is just like C# extension methods). -Another alternative is that you can add an extra member from a completely different module. -These are called "optional extensions". They are not compiled into the type itself, and require some other module to be in scope for them to work (this behavior is just like C# extension methods). +Другой вариант заключается в том, что можно добавить дополнительный член из совершенно другого модуля. Их называют "опциональными расширениями". Они не компилируются внутрь класса, и требуют другой модуль в области видимости для работы с ними (данное поведение похоже на C# расширения). -For example, let's say we have a `Person` type defined: +> For example, let's say we have a `Person` type defined: + +Например, пусть определен тип `Person`: ```fsharp module Person = @@ -90,7 +112,9 @@ module Person = this.Last + ", " + this.First ``` -The example below demonstrates how to add an `UppercaseName` extension to it in a different module: +> The example below demonstrates how to add an `UppercaseName` extension to it in a different module: + +Пример ниже демонстрирует, как можно добавить `UppercaseName` расширение к нему в другом модуле: ```fsharp // in a different module @@ -101,17 +125,23 @@ module PersonExtensions = this.FullName.ToUpper() ``` -So now let's test this extension: +> So now let's test this extension: + +Теперь можно попробовать данное расширение: ```fsharp let person = Person.create "John" "Doe" let uppercaseName = person.UppercaseName ``` -Uh-oh, we have an error. What's wrong is that the `PersonExtensions` is not in scope. -Just as for C#, any extensions have to be brought into scope in order to be used. +> Uh-oh, we have an error. What's wrong is that the `PersonExtensions` is not in scope. +> Just as for C#, any extensions have to be brought into scope in order to be used. + +Будет получена ошибка. Она произошла потому что `PersonExtensions` не находится в области видимости. Как и в C#, любые расширения должны быть введены в область видимости для использования. + +> Once we do that, everything is fine: -Once we do that, everything is fine: +Как только мы сделаем это, все заработает: ```fsharp // bring the extension into scope first! @@ -122,18 +152,24 @@ let uppercaseName = person.UppercaseName ``` -## Extending system types +## Extending system types | Расширение системных типов -You can extend types that are in the .NET libraries as well. But be aware that when extending a type, you must use the actual type name, not a type abbreviation. +> You can extend types that are in the .NET libraries as well. But be aware that when extending a type, you must use the actual type name, not a type abbreviation. -For example, if you try to extend `int`, you will fail, because `int` is not the true name of the type: +Можно также расширять типы из .NET библиотеки. Но следует иметь ввиду, что при расширении типа надо использовать его фактическое имя, а не псевдоним. + +> For example, if you try to extend `int`, you will fail, because `int` is not the true name of the type: + +Например, если попробовать расширить `int`, будет получена ошибка, т.к. `int` не является правильным именем для типа: ```fsharp type int with member this.IsEven = this % 2 = 0 ``` -You must use `System.Int32` instead: +> You must use `System.Int32` instead: + +Необходимо использовать `System.Int32`: ```fsharp type System.Int32 with @@ -144,12 +180,17 @@ let i = 20 if i.IsEven then printfn "'%i' is even" i ``` -## Static members +## Static members | Статические члены + +> You can make the member functions static by: -You can make the member functions static by: +Можно создавать статические функции с помощью: -* adding the keyword `static` -* dropping the `this` placeholder +> * adding the keyword `static` +> * dropping the `this` placeholder + +* добавления ключевого слова `static` +* удаления `this` метки ```fsharp module Person = @@ -167,7 +208,9 @@ let person = Person.T.Create "John" "Doe" let fullname = person.FullName ``` -And you can create static members for system types as well: +> And you can create static members for system types as well: + +Можно создавать статические члены для системных типов: ```fsharp type System.Int32 with @@ -182,14 +225,21 @@ let pi = System.Double.Pi ```
-## Attaching existing functions +## Attaching existing functions | Прикрепление существующих функций + +> A very common pattern is to attach pre-existing standalone functions to a type. This has a couple of benefits: + +Очень распространенный паттерн - прикрепление уже существующих самостоятельных функций к типу. Он дает пару преимуществ: + +> * While developing, you can create standalone functions that refer to other standalone functions. This makes programming easier because type inference works much better with functional-style code than with OO-style ("dotting into") code. +> * But for certain key functions, you can attach them to the type as well. This gives clients the choice of whether to use functional or object-oriented style. -A very common pattern is to attach pre-existing standalone functions to a type. This has a couple of benefits: +* Во время разработки, можно объявдять самостоятельные функции, которые ссылаются на другие самостоятельные функции. Это упростит разработку, т.к. вывод типов работает гораздо лучше с функциональным стилем нежели с ООП. +* Но некоторые ключевые функции можно прикрепить к типу. Что позволит пользователям выбрать, какой из стилей использовать, функциональный или объекто-ориентированный. -* While developing, you can create standalone functions that refer to other standalone functions. This makes programming easier because type inference works much better with functional-style code than with OO-style ("dotting into") code. -* But for certain key functions, you can attach them to the type as well. This gives clients the choice of whether to use functional or object-oriented style. +> One example of this in the F# libraries is the function that calculates a list's length. It is available as a standalone function in the `List` module, but also as a method on a list instance. -One example of this in the F# libraries is the function that calculates a list's length. It is available as a standalone function in the `List` module, but also as a method on a list instance. +Примером подобного решения является функция из F# библиотеки, которая расчитывает длину списка. Можно использовать самостоятельную функцию из модуля `List` или вызывать ее как метод экземпляра. ```fsharp let list = [1..10] @@ -201,7 +251,9 @@ let len1 = List.length list let len2 = list.Length ``` -In the following example, we start with a type with no members initially, then define some functions, then finally attach the `fullName` function to the type. +> In the following example, we start with a type with no members initially, then define some functions, then finally attach the `fullName` function to the type. + +В следующем примере, тип изначально не имеет каких-либо членов, затем определяются несколько функций, и наконец к типу прикрепляется функция `fullName`. ```fsharp module Person = @@ -226,13 +278,19 @@ let fullname = Person.fullName person // functional style let fullname2 = person.FullName // OO style ``` -The standalone `fullName` function has one parameter, the person. In the attached member, the parameter comes from the `this` self-reference. +> The standalone `fullName` function has one parameter, the person. In the attached member, the parameter comes from the `this` self-reference. + +Самостоятельная функция `fullName` имеет один параметр, `person`. Добавленный же член, получает параметр из self-ссылки. + +### Attaching existing functions with multiple parameters | Добавление существующих функций со множеством параметров + +> One nice thing is that when the previously defined function has multiple parameters, you don't have to respecify them all when doing the attachment, as long as the `this` parameter is first. -### Attaching existing functions with multiple parameters +Еще одной приятной особенностью является то, что можно сначала определить мультипараметрическую функцию, в которой текущий тип передается в качестве первого параметра, после чего при создании прикрепления не потребуется упоминать все множество параметров, ограничевшись `this`. -One nice thing is that when the previously defined function has multiple parameters, you don't have to respecify them all when doing the attachment, as long as the `this` parameter is first. +> In the example below, the `hasSameFirstAndLastName` function has three parameters. Yet when we attach it, we only need to specify one! -In the example below, the `hasSameFirstAndLastName` function has three parameters. Yet when we attach it, we only need to specify one! +В примере ниже функция `hasSameFirstAndLastName` имеет три параметра. Однако при прикреплении достаточно упоминуть всего лишь один! ```fsharp module Person = @@ -258,23 +316,36 @@ let result2 = person.HasSameFirstAndLastName "bob" "smith" // OO style ``` -Why does this work? Hint: think about currying and partial application! +> Why does this work? Hint: think about currying and partial application! + +Почему это работает? Подсказка: подумайте о каррировании и частичном применении! -## Tuple-form methods +## Tuple-form methods | Кортежные методы + +> When we start having methods with more than one parameter, we have to make a decision: + +Когда у нас есть методы с более чем одним параметром, необходимо принять решение: + +> * we could use the standard (curried) form, where parameters are separated with spaces, and partial application is supported. +> * we could pass in *all* the parameters at once, comma-separated, in a single tuple. -When we start having methods with more than one parameter, we have to make a decision: +* мы можем использовать стандартную (каррированную) форму, где параметры разделяются пробелами, и поддерживается частичное применение. +* или можем передавать *все* параметры за один раз в виде разделенного запятыми кортежа. -* we could use the standard (curried) form, where parameters are separated with spaces, and partial application is supported. -* we could pass in *all* the parameters at once, comma-separated, in a single tuple. +> The "curried" form is more functional, and the "tuple" form is more object-oriented. -The "curried" form is more functional, and the "tuple" form is more object-oriented. +Каррированая форма более функциональна, в то время как кортежная форма более объектно-ориентированна. -The tuple form is also how F# interacts with the standard .NET libraries, so let's examine this approach in more detail. +> The tuple form is also how F# interacts with the standard .NET libraries, so let's examine this approach in more detail. -As a testbed, here is a Product type with two methods, each implemented using one of the approaches. +Кортежная форма также является формой взаимодействия F# со стандартными библиотеками .NET, поэтому стоит рассмотреть данный подход более детально. + +> As a testbed, here is a Product type with two methods, each implemented using one of the approaches. The `CurriedTotal` and `TupleTotal` methods each do the same thing: work out the total price for a given quantity and discount. +В качестве тестового кода используется тип `Product` с двумя методами, каждый из них реализован одним из вышеописанных подходов. `CurriedTotal` и `TupleTotal` методы делают одно и тоже, вычисляют итоговую цену продукта по количеству и скидке. + ```fsharp type Product = {SKU:string; Price: float} with @@ -287,7 +358,9 @@ type Product = {SKU:string; Price: float} with (this.Price * float qty) - discount ``` -And here's some test code: +> And here's some test code: + +Тестовый код: ```fsharp let product = {SKU="ABC"; Price=2.0} @@ -295,9 +368,13 @@ let total1 = product.CurriedTotal 10 1.0 let total2 = product.TupleTotal(10,1.0) ``` -No difference so far. +> No difference so far. + +Пока нет особой разницы. -We know that curried version can be partially applied: +> We know that curried version can be partially applied: + +Но мы знаем, что каррированная версия может быть частично применена: ```fsharp let totalFor10 = product.CurriedTotal 10 @@ -306,15 +383,23 @@ let totalForDifferentDiscounts = discounts |> List.map totalFor10 ``` -But the tuple approach can do a few things that that the curried one can't, namely: +> But the tuple approach can do a few things that that the curried one can't, namely: + +С другой стороны кортежная версия способна на то, что не может каррированая, а именно: + +> * Named parameters +> * Optional parameters +> * Overloading -* Named parameters -* Optional parameters -* Overloading +* Именнованные параметры +* Необязательные параметры +* Перегрузки -### Named parameters with tuple-style parameters +### Named parameters with tuple-style parameters | Именнованные параметры с параметрами в кортежном стиле -The tuple-style approach supports named parameters: +> The tuple-style approach supports named parameters: + +Кортежний подход поддерживает именнованные параметры: ```fsharp let product = {SKU="ABC"; Price=2.0} @@ -322,18 +407,29 @@ let total3 = product.TupleTotal(qty=10,discount=1.0) let total4 = product.TupleTotal(discount=1.0, qty=10) ``` -As you can see, when names are used, the parameter order can be changed. +> As you can see, when names are used, the parameter order can be changed. + +Как видите, это позволяет менять порядок параметров с помощью явного указания имен. + +> Note: if some parameters are named and some are not, the named ones must always be last. + +Внимание: если лишь у некоторой части параметров есть имена, то эти параметры всегда должна находиться в конце. + +### Optional parameters with tuple-style parameters | Необязательные параметры с параметрами в кортежном стиле + +> For tuple-style methods, you can specify an optional parameter by prefixing the parameter name with a question mark. -Note: if some parameters are named and some are not, the named ones must always be last. +У методов в стиле кортежей, можно помечать параметры как опциональные при помощи префикса в виде знака вопроса перед именем параметра. -### Optional parameters with tuple-style parameters +> * If the parameter is set, it comes through as `Some value` +> * If the parameter is not set, it comes through as `None` -For tuple-style methods, you can specify an optional parameter by prefixing the parameter name with a question mark. +* Если параметр задан, то в функцию придет `Some value` +* Иначе придет `None` -* If the parameter is set, it comes through as `Some value` -* If the parameter is not set, it comes through as `None` +> Here's an example: -Here's an example: +Пример: ```fsharp type Product = {SKU:string; Price: float} with @@ -346,7 +442,9 @@ type Product = {SKU:string; Price: float} with | Some discount -> extPrice - discount ``` -And here's a test: +> And here's a test: + +И тест: ```fsharp let product = {SKU="ABC"; Price=2.0} @@ -358,12 +456,18 @@ let total1 = product.TupleTotal2(10) let total2 = product.TupleTotal2(10,1.0) ``` -This explicit matching of the `None` and `Some` can be tedious, and there is a slightly more elegant solution for handling optional parameters. +> This explicit matching of the `None` and `Some` can be tedious, and there is a slightly more elegant solution for handling optional parameters. + +Явная проверка на `None` и `Some` может быть утомительной, для обработки опциональных параметров существует более элегантное решение. + +> There is a function `defaultArg` which takes the parameter as the first argument and a default for the second argument. If the parameter is set, the value is returned. +> And if not, the default value is returned. + +Функция `defaultArg` принимает параметр в качестве первого аргумента и значение по умолчанию в качестве второго. Если параметр установлен, будет возвращено установленное значение, иначе значение по умолчанию. -There is a function `defaultArg` which takes the parameter as the first argument and a default for the second argument. If the parameter is set, the value is returned. -And if not, the default value is returned. +> Let's see the same code rewritten to use `defaultArg` -Let's see the same code rewritten to use `defaultArg` +Тот же код с применением `defaulArg` ```fsharp type Product = {SKU:string; Price: float} with @@ -378,16 +482,24 @@ type Product = {SKU:string; Price: float} with -### Method overloading +### Method overloading | Перегрузка методов + +> In C#, you can have multiple methods with the same name that differ only in their function signature (e.g. different parameter types and/or number of parameters) + +В C# можно создать множество методов с одинаковым именем, которые отличаются своей сигнатурой (например, различные типы параметров и/или их число). + +> In the pure functional model, that does not make sense -- a function works with a particular domain type and a particular range type. +> The same function cannot work with different domains and ranges. + +В чисто функциональной модели, это не имеет смысла -- функция работает с определенным типом и определенным типом дупустимых диапазонов допустимых значений. Одна и та же функция не может взаимодействовать с другими domain-ами и range-ами. -In C#, you can have multiple methods with the same name that differ only in their function signature (e.g. different parameter types and/or number of parameters) +> However, F# *does* support method overloading, but only for methods (that is functions attached to types) and of these, only those using tuple-style parameter passing. -In the pure functional model, that does not make sense -- a function works with a particular domain type and a particular range type. -The same function cannot work with different domains and ranges. +Однако, F# поддерживает перегрузку методов, но только для методов (которые прикреплены к типам) и только тех из них, которые написаны в кортежном стиле. -However, F# *does* support method overloading, but only for methods (that is functions attached to types) and of these, only those using tuple-style parameter passing. +> Here's an example, with yet another variant on the `TupleTotal` method! -Here's an example, with yet another variant on the `TupleTotal` method! +Пример, с еще одним вариантом метода `TupleTotal`! ```fsharp type Product = {SKU:string; Price: float} with @@ -403,11 +515,14 @@ type Product = {SKU:string; Price: float} with (this.Price * float qty) - discount ``` -Normally, the F# compiler would complain that there are two methods with the same name, but in this case, because they are tuple based and because their signatures are different, it is acceptable. -(To make it obvious which one is being called, I have added a small debugging message.) +> Normally, the F# compiler would complain that there are two methods with the same name, but in this case, because they are tuple based and because their signatures are different, it is acceptable. +> (To make it obvious which one is being called, I have added a small debugging message.) -And here's a test: +Обычно, компилятор F# жалуется, что существует два метода с одинаковым именем, но в данном случае это приемлимо, т.к. они объявлены в кортежной нотации и их сигнатуры различаются. (Чтобы было понятно, какой из методов вызывается, я добавил небольшое сообщения для отладки.) +> And here's a test: + +Пример использования: ```fsharp let product = {SKU="ABC"; Price=2.0} @@ -421,21 +536,32 @@ let total2 = product.TupleTotal3(10,1.0) -## Hey! Not so fast... The downsides of using methods +## Hey! Not so fast... The downsides of using methods | Эй! Не так быстро... Недостатки использования методов + +> If you are coming from an object-oriented background, you might be tempted to use methods everywhere, because that is what you are familiar with. +> But be aware that there some major downsides to using methods as well: + +Придя из объектно-ориентированного мира, можно решить, использовать методы везде, потому что уже знакомы. Но следует быть осторожным, т.к. у них существуют ряд серьезных недостатков: + +> * Methods don't play well with type inference +> * Methods don't play well with higher order functions + +* Методы плохо работают с выводом типов +* Методы плохо работают с функциями высшего порядка -If you are coming from an object-oriented background, you might be tempted to use methods everywhere, because that is what you are familiar with. -But be aware that there some major downsides to using methods as well: +> In fact, by overusing methods you would be needlessly bypassing the most powerful and useful aspects of programming in F#. -* Methods don't play well with type inference -* Methods don't play well with higher order functions +На самом деле, чрезмерно используя методов можно пропустить самые сильные и полезные стороны программирования на F#. -In fact, by overusing methods you would be needlessly bypassing the most powerful and useful aspects of programming in F#. +> Let's see what I mean. -Let's see what I mean. +Посмотрим, что я имею ввиду. -### Methods don't play well with type inference +### Methods don't play well with type inference | Методы плохо взаимодействую с выводом типа -Let's go back to our Person example again, the one that had the same logic implemented both as a standalone function and as a method: +> Let's go back to our Person example again, the one that had the same logic implemented both as a standalone function and as a method: + +Вернемся к примеру `Person`, который имел одну и ту же логику реализованную в виде самостоятельной функции и метода: ```fsharp module Person = @@ -455,9 +581,13 @@ module Person = member this.FullName = fullName this ``` -Now let's see how well each one works with type inference. Say that I want to print the full name of a person, so I will define a function `printFullName` that takes a person as a parameter. +> Now let's see how well each one works with type inference. Say that I want to print the full name of a person, so I will define a function `printFullName` that takes a person as a parameter. + +Теперь, посмотрим как хорошо вывод типов работает с каждым из них. Пусть я хочу вывести полное имя человека, тогда я определю функцию `printFullName`, которая берет `person` в качетсве параметра. -Here's the code using the module level standalone function. +> Here's the code using the module level standalone function. + +Код использующий самостоятельную функцию из модуля. ```fsharp open Person @@ -470,9 +600,13 @@ let printFullName person = // val printFullName : Person.T -> unit ``` -This compiles without problems, and the type inference has correctly deduced that parameter was a person +> This compiles without problems, and the type inference has correctly deduced that parameter was a person + +Компилируется без пробелм, а вывод типов корректно идентифицирует параметр как `Person`. -Now let's try the "dotted" version: +> Now let's try the "dotted" version: + +Теперь попробуем версию через точку: ```fsharp open Person @@ -482,15 +616,23 @@ let printFullName2 person = printfn "Name is %s" (person.FullName) ``` -This does not compile at all, because the type inference does not have enough information to deduce the parameter. *Any* object might implement `.FullName` -- there is just not enough to go on. +> This does not compile at all, because the type inference does not have enough information to deduce the parameter. *Any* object might implement `.FullName` -- there is just not enough to go on. + +Этот код вообще не скомпилируется, т.к. вывод типов не имеет достаточной информации, чтобы вывести тип параметра. *Любой* объект может реализовывать `.FullName` -- этого недостаточно для вывода. + +> Yes, we could annotate the function with the parameter type, but that defeats the whole purpose of type inference. + +Да, мы можем аннотировать функцию с параметризованным типом, но из-за этого теряется смысл в выведении типа. -Yes, we could annotate the function with the parameter type, but that defeats the whole purpose of type inference. +### Methods don't play well with higher order functions | Методы не работают хорошо с функциями высшего порядка -### Methods don't play well with higher order functions +> A similar problem happens with higher order functions. For example, let's say that, given a list of people, we want to get all their full names. -A similar problem happens with higher order functions. For example, let's say that, given a list of people, we want to get all their full names. +С подобной проблемой можно столкнуться и в функциях высшего порядка. Например, есть список людей, и нам надо получить список их полных имен. -With a standalone function, this is trivial: +> With a standalone function, this is trivial: + +В случае самостоятельной функции решение тривиально: ```fsharp open Person @@ -504,7 +646,9 @@ let list = [ list |> List.map fullName ``` -With object methods, we have to create special lambdas everywhere: +> With object methods, we have to create special lambdas everywhere: + +В случае метода объекта, придется везде создавать специальную лямбду: ```fsharp open Person @@ -518,7 +662,11 @@ let list = [ list |> List.map (fun p -> p.FullName) ``` -And this is just a simple example. Object methods don't compose well, are hard to pipe, and so on. +> And this is just a simple example. Object methods don't compose well, are hard to pipe, and so on. + +А ведь это еще достаточно простой пример. Методы объектов довольно плохо компонуются, неудобны в конвейере и т.д. + +> So, a plea for those of you new to functionally programming. Don't use methods at all if you can, especially when you are learning. +> They are a crutch that will stop you getting the full benefit from functional programming. -So, a plea for those of you new to functionally programming. Don't use methods at all if you can, especially when you are learning. -They are a crutch that will stop you getting the full benefit from functional programming. \ No newline at end of file +Поэтому, призываю вас, если вы новичок в функциональном программированию. Если можете, не используйте методы, особенно в процессе обучения. Они будут костылем который не позволит получить от функционального программирования полную выгоду. \ No newline at end of file diff --git a/series/thinking-functionally.md b/series/thinking-functionally.md index e0260b0..f81ff12 100644 --- a/series/thinking-functionally.md +++ b/series/thinking-functionally.md @@ -4,18 +4,33 @@ title: "The 'thinking functionally' series" seriesIndexId: "Thinking functionally" --- -This series of posts will introduce you to the fundamentals of functional programming --- what does it really mean to "program functionally", and how this approach differs from object oriented or imperative programming. +> This series of posts will introduce you to the fundamentals of functional programming +> -- what does it really mean to "program functionally", and how this approach differs from object oriented or imperative programming. -* [Thinking Functionally: Introduction](../posts/thinking-functionally-intro.md). A look at the basics of functional programming. -* [Mathematical functions](../posts/mathematical-functions.md). The impetus behind functional programming. -* [Function Values and Simple Values](../posts/function-values-and-simple-values.md). Binding not assignment. -* [How types work with functions](../posts/how-types-work-with-functions.md). Understanding the type notation. -* [Currying](../posts/currying.md). Breaking multi-parameter functions into smaller one-parameter functions. -* [Partial application](../posts/partial-application.md). Baking-in some of the parameters of a function. -* [Function associativity and composition](../posts/function-composition.md). Building new functions from existing ones. -* [Defining functions](../posts/defining-functions.md). Lambdas and more. -* [Function signatures](../posts/function-signatures.md). A function signature can give you some idea of what it does. -* [Organizing functions](../posts/organizing-functions.md). Nested functions and modules. -* [Attaching functions to types](../posts/type-extensions.md). Creating methods the F# way. -* [Worked example: A stack based calculator](../posts/stack-based-calculator.md). Using combinators to build functionality. +Эта серия постов познакомит читателя с основами функционального программирования. Что в действительности значит "программировать функционально", и чем этот подход отличается от объектно-ориентированного или императивного программирования. + +> * [Thinking Functionally: Introduction](../posts/thinking-functionally-intro.md). A look at the basics of functional programming. +> * [Mathematical functions](../posts/mathematical-functions.md). The impetus behind functional programming. +> * [Function Values and Simple Values](../posts/function-values-and-simple-values.md). Binding not assignment. +> * [How types work with functions](../posts/how-types-work-with-functions.md). Understanding the type notation. +> * [Currying](../posts/currying.md). Breaking multi-parameter functions into smaller one-parameter functions. +> * [Partial application](../posts/partial-application.md). Baking-in some of the parameters of a function. +> * [Function associativity and composition](../posts/function-composition.md). Building new functions from existing ones. +> * [Defining functions](../posts/defining-functions.md). Lambdas and more. +> * [Function signatures](../posts/function-signatures.md). A function signature can give you some idea of what it does. +> * [Organizing functions](../posts/organizing-functions.md). Nested functions and modules. +> * [Attaching functions to types](../posts/type-extensions.md). Creating methods the F# way. +> * [Worked example: A stack based calculator](../posts/stack-based-calculator.md). Using combinators to build functionality. + +* [Thinking Functionally: Introduction | Мыслить функционально: введение](../posts/thinking-functionally-intro.md). Первый взгляд на основы функционального программирования. +* [Mathematical functions | Математические функции](../posts/mathematical-functions.md). Движущая сила в основе функционального программирования. +* [Function Values and Simple Values | Функции как значения и обычные значения](../posts/function-values-and-simple-values.md). Связывание <> присваивание. +* [How types work with functions | Как типы взаимодействуют с функциями](../posts/how-types-work-with-functions.md). Понимание нотации типов. +* [Currying | Каррирование](../posts/currying.md). Разбивка функций с несколькими параметрами на множество мелких функций с одним параметром. +* [Partial application | Частичное применение](../posts/partial-application.md). Запечатывание некоторых параметров внутри функции. +* [Function associativity and composition | Ассоциативность и композиция функций](../posts/function-composition.md). Построение новых функций на основе существующих. +* [Defining functions | Определение функций](../posts/defining-functions.md). Лямбды и многое другое. +* [Function signatures | Сигнатуры функций](../posts/function-signatures.md). Сигнатура функции может дать вам некоторое представление о ее содержании. +* [Organizing functions | Организация функций](../posts/organizing-functions.md). Вложенные функции и модули. +* [Attaching functions to types | Прикрепление функций к типам](../posts/type-extensions.md). Идеоматичный для F# способ создания методов. +* [Worked example: A stack based calculator | Пример: калькулятор на стеке](../posts/stack-based-calculator.md). Использование комбинаторов для построения функционала. \ No newline at end of file From a271d4df2f8fdc411db51e28c56fdf056d695494 Mon Sep 17 00:00:00 2001 From: kleidemos Date: Tue, 6 Feb 2018 08:39:46 +0500 Subject: [PATCH 02/48] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20Fog?= =?UTF-8?q?gyFinder-=D0=B0=20=D0=BA=20=D1=83=D1=82=D1=80=D1=83=2006.02.201?= =?UTF-8?q?8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/mathematical-functions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/posts/mathematical-functions.md b/posts/mathematical-functions.md index 849709a..b87d6f1 100644 --- a/posts/mathematical-functions.md +++ b/posts/mathematical-functions.md @@ -19,7 +19,7 @@ seriesOrder: 2 > What does this really mean? Well it seems pretty straightforward. It means that there is an operation that starts with a number, and adds one to it. -Что в действительности означает это выражение? Выглядит довольно просто. Оно означает, что существует такая операция, котороая берет число и прибавляет к нему 1. +Что в действительности означает это выражение? Выглядит довольно просто. Оно означает, что существует такая операция, которая берет число и прибавляет к нему 1. > Let's introduce some terminology: @@ -29,7 +29,7 @@ seriesOrder: 2 > * The set of possible output values from the function is called the *range* (technically, the image on the codomain). In this case, it is also the set of integers. > * The function is said to *map* the domain to the range. -* Множество допустимых входных значений функции называются _domain_ (область определения). В данном пример, это могло быть множество действительных чисел, но сделаем жизнь проще и ограничимся здесь только целыми числами. +* Множество допустимых входных значений функции называются _domain_ (область определения). В данном примере, это могло быть множество действительных чисел, но сделаем жизнь проще и ограничимся здесь только целыми числами. * Множество возможных результатов функции (область значения) называется _range_ (технически, изображение **codomain-а**). В данном случае также множество целых. * Функцией называют _преобразование_ (в оригинале _map_) из domain-а в range. (Т.е. из области определения в область значений.) @@ -45,7 +45,7 @@ let add1 x = x + 1 > If you type that into the F# interactive window (don't forget the double semicolons) you will see the result (the "signature" of the function): -Если ввести его в F# Interactive (следует не забыть про двойные точку с запятой), можно увидеть результат ("сигнатуру" функции): +Если ввести его в F# Interactive (не забудьте про двойные точку с запятой), то можно увидеть результат ("сигнатуру" функции): ```fsharp val add1 : int -> int @@ -65,7 +65,7 @@ val add1 : int -> int > Also note that the type was not specified, yet the F# compiler guessed that the function was working with ints. (Can this be tweaked? Yes, as we'll see shortly). -Примечательно, что тип не был указан, но компилятор F# вычислил, что функция работает с int-ами. (Можно ли это изменить? Да, скоро мы увидим). +Заметьте, что тип не был указан явно, но компилятор F# решил, что функция работает с int-ами. (Можно ли это изменить? Да, и скоро мы это увидим). ## Key properties of mathematical functions | Ключевые свойства математических функций ## From b16d48437fd20da5983103b087b7e0c6bf56e690 Mon Sep 17 00:00:00 2001 From: Anna Udovichenko Date: Mon, 12 Feb 2018 16:50:39 +0300 Subject: [PATCH 03/48] Edited till 134th line --- posts/currying.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/posts/currying.md b/posts/currying.md index debf1b5..cc4c8ba 100644 --- a/posts/currying.md +++ b/posts/currying.md @@ -10,15 +10,15 @@ categories: [Currying] > After that little digression on basic types, we can turn back to functions again, and in particular the puzzle we mentioned earlier: if a mathematical function can only have one parameter, then how is it possible that an F# function can have more than one? -После небольшого экскурса по базовым типам, можно вернуться к функциям снова, и в частности к ранее упомянутой загадке: если математическая функция может иметь лишь один параметр, то как в F# может существовать функция имеющая большее число параметров? +После небольшого экскурса в базовые типы, мы можем снова вернуться к функциям, в частности, к ранее упомянутой загадке: если математическая функция может принимать только один параметр, то как в F# может существовать функция принимающая большее число параметров? > The answer is quite simple: a function with multiple parameters is rewritten as a series of new functions, each with only one parameter. And this is done automatically by the compiler for you. It is called "**currying**", after Haskell Curry, a mathematician who was an important influence on the development of functional programming. -Ответ довольно прост: функция с множестов параметров переписывается как серия новых функций, каждая имеющая только один параметр. Данная операция произвоидтся компилятором автоматически. Это называется "**каррированием**" (_currying_), в честь Haskell Curry, математика, который оказал очень существенное влияние на разработку функционального программирования. +Ответ довольно прост: функция с несколькими параметрами переписывается как серия новых функций, каждая из которых принимает только один параметр. Эта операция производится компилятором автоматически. Это называется "**каррированием**" (_currying_), в честь Хаскела Карри, математика, который существенно повлиял на разработку функционального программирования. > To see how this works in practice, let's use a very basic example that prints two numbers: -Чтобы увидеть как это работает на практике, воспользуемся очень базовым примером который выводит два числа: +Чтобы увидеть, как это работает на практике, воспользуемся простейшим примером кода, печатающим два числа: ```fsharp //normal version @@ -28,7 +28,7 @@ let printTwoParameters x y = > Internally, the compiler rewrites it as something more like: -Внутри компилятор переписывает его приблизительно в такой форме: +На самом деле компилятор переписывает его приблизительно в такой форме: ```fsharp //explicitly curried version @@ -40,21 +40,21 @@ let printTwoParameters x = // only one parameter! > Let's examine this in more detail: -Рассмотрм процесс более детально: +Рассмотрм этот процесс подробнее: > 1. Construct the function called "`printTwoParameters`" but with only *one* parameter: "x" > 2. Inside that, construct a subfunction that has only *one* parameter: "y". Note that this inner function uses the "x" parameter but x is not passed to it explicitly as a parameter. The "x" parameter is in scope, so the inner function can see it and use it without needing it to be passed in. > 3. Finally, return the newly created subfunction. > 4. This returned function is then later used against "y". The "x" parameter is baked into it, so the returned function only needs the y param to finish off the function logic. -1. Объявить функцию с названием "`printTwoParameters`", но имеющую лишь _один_ параметр: "x". -2. Внутри создать подфункцию, которая также имеет лишь _один_ параметр: "y". Заметим, что внутренняя функция использует параметр "x", но x не передается внутрь нее как параметр. "x" находится в такой области видимости, что вложенная функция может видеть его и использовать без необходимости в его передаче. -3. Наконец вернуть новую созданную подфункцию. -4. Возвращенная функция затем применяется в отношении "y". "x" замыкается в ней, так что возвращаемая функция нуждается в параметре y чтобы закончить свою логику. +1. Объявляем функцию с названием "`printTwoParameters`", но принимаюшую только _один_ параметр: "x". +2. Внутри неё создаём локальную функцию, которая также принимает только _один_ параметр: "y". Заметим, что локальная функция использует параметр "x", но x не передается в нее как аргумент. "x" находится в такой области видимости, что вложенная функция может видеть его и использовать без необходимости в его передаче. +3. Наконец, возвращаем только что созданную локальную функцию. +4. Возвращенная функция затем применяется к аргументу "y". Параметр "x" замыкается в ней, так что возвращаемая функция нуждается только в параметре y чтобы завершить свою логику. > By rewriting it this way, the compiler has ensured that every function has only one parameter, as required. So when you use "`printTwoParameters`", you might think that you are using a two parameter function, but it is actually only a one parameter function! You can see for yourself by passing only one argument instead of two: -Переписав ее таким образом, комплиятор гарантирует, что каждая функция имеет только один параметр, как требуется. _При использовании "`printTwoParameters`", можно подумать, что используется функция с двумя параметрами, но на самом деле используется только функции с одним параметром._ В этом можно убедиться, передав ей лишь один аргумент вместо двух: +Переписывая функции таким образом, комплиятор гарантирует, что каждая функция принимает только один параметр, как и требовалось. _Таким образом, используя "`printTwoParameters`", можно подумать, что это функция с двумя параметрами, но на самом деле используется только функции с одним параметром._ В этом можно убедиться, передав ей лишь один аргумент вместо двух: ```fsharp // eval with one argument @@ -66,23 +66,23 @@ val it : (int -> unit) = > If you evaluate it with one argument, you don't get an error, you get back a function. -Если вычислить ее с одним аргументом, мы не получим ошибку, вернется функция. +Если вычислить ее с одним аргументом, мы не получим ошибку, а будет возвращена функция. > So what you are really doing when you call `printTwoParameters` with two arguments is: -Что на самом деле происходит, когда вызывается `printTwoParameters` с двумя аргументами: +Итак, вот что на самом деле происходит, когда `printTwoParameters` вызывается с двумя аргументами: > * You call `printTwoParameters` with the first argument (x) > * `printTwoParameters` returns a new function that has "x" baked into it. > * You then call the new function with the second argument (y) * Вызывается `printTwoParameters` с первым аргуметом (x) -* `printTwoParameters` возвращает новую функцию, в которой запечатан "x". +* `printTwoParameters` возвращает новую функцию, в которой замкнут "x". * Затем вызывается новая функция со вторым аргуметом (y) > Here is an example of the step by step version, and then the normal version again. -Вот пример пошаговой и нормальной (снова) версий. +Вот пример пошаговой и обыкновенной версий: ```fsharp // step by step version @@ -127,11 +127,11 @@ let result = addTwoParameters x y > Again, the "two parameter function" is actually a one parameter function that returns an intermediate function. -Опять же, "функция с двумя параметрами" на самом деле это функция с одним параметром, которая возвращает промежуточную функцию. +Опять же, "функция с двумя параметрами" на самом деле является функцией с одним параметром, которая возвращает промежуточную функцию. > But wait a minute -- what about the "`+`" operation itself? It's a binary operation that must take two parameters, surely? No, it is curried like every other function. There is a function called "`+`" that takes one parameter and returns a new intermediate function, exactly like `addTwoParameters` above. -Но подождите, а что с "`+`"? Это ведь бинарная операция, которая должна принимать два параметра? Нет, она тоже каррируется как и другие функции. Функция заваемая "`+`" берет один параметр и возвращает новую промежуточную функцию, в точности как `addTwoParameters` выше. +Но подождите, а что с операторором "`+`"? Это ведь бинарная операция, которая должна принимать два параметра? Нет, она тоже каррируется, как и другие функции. Это функция с именем "`+`", которая принимает один параметр и возвращает новую промежуточную функцию, в точности как `addTwoParameters` выше. > When we write the statement `x+y`, the compiler reorders the code to remove the infix and turns it into `(+) x y`, which is the function named `+` called with two parameters. Note that the function named "+" needs to have parentheses around it to indicate that it is being used as a normal function name rather than as an infix operator. From 700c998cf289488af38c2a1cf65b7c129ef1f716 Mon Sep 17 00:00:00 2001 From: Anna Udovichenko Date: Tue, 13 Feb 2018 14:16:18 +0300 Subject: [PATCH 04/48] Edited till the end --- posts/currying.md | 150 +++++++++++++++++++++++----------------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/posts/currying.md b/posts/currying.md index cc4c8ba..3b41eab 100644 --- a/posts/currying.md +++ b/posts/currying.md @@ -8,7 +8,7 @@ seriesOrder: 5 categories: [Currying] --- -> After that little digression on basic types, we can turn back to functions again, and in particular the puzzle we mentioned earlier: if a mathematical function can only have one parameter, then how is it possible that an F# function can have more than one? +> After that little digression on basic types, we can turn back to functions again, and in particular the puzzle we mentioned earlier: if a mathematical function can only have one parameter, then how is it possible that an F# function can have more than one? После небольшого экскурса в базовые типы, мы можем снова вернуться к функциям, в частности, к ранее упомянутой загадке: если математическая функция может принимать только один параметр, то как в F# может существовать функция принимающая большее число параметров? @@ -22,7 +22,7 @@ categories: [Currying] ```fsharp //normal version -let printTwoParameters x y = +let printTwoParameters x y = printfn "x=%i y=%i" x y ``` @@ -33,7 +33,7 @@ let printTwoParameters x y = ```fsharp //explicitly curried version let printTwoParameters x = // only one parameter! - let subFunction y = + let subFunction y = printfn "x=%i y=%i" x y // new function with one param subFunction // return the subfunction ``` @@ -42,10 +42,10 @@ let printTwoParameters x = // only one parameter! Рассмотрм этот процесс подробнее: -> 1. Construct the function called "`printTwoParameters`" but with only *one* parameter: "x" -> 2. Inside that, construct a subfunction that has only *one* parameter: "y". Note that this inner function uses the "x" parameter but x is not passed to it explicitly as a parameter. The "x" parameter is in scope, so the inner function can see it and use it without needing it to be passed in. -> 3. Finally, return the newly created subfunction. -> 4. This returned function is then later used against "y". The "x" parameter is baked into it, so the returned function only needs the y param to finish off the function logic. +> 1. Construct the function called "`printTwoParameters`" but with only *one* parameter: "x" +> 2. Inside that, construct a subfunction that has only *one* parameter: "y". Note that this inner function uses the "x" parameter but x is not passed to it explicitly as a parameter. The "x" parameter is in scope, so the inner function can see it and use it without needing it to be passed in. +> 3. Finally, return the newly created subfunction. +> 4. This returned function is then later used against "y". The "x" parameter is baked into it, so the returned function only needs the y param to finish off the function logic. 1. Объявляем функцию с названием "`printTwoParameters`", но принимаюшую только _один_ параметр: "x". 2. Внутри неё создаём локальную функцию, которая также принимает только _один_ параметр: "y". Заметим, что локальная функция использует параметр "x", но x не передается в нее как аргумент. "x" находится в такой области видимости, что вложенная функция может видеть его и использовать без необходимости в его передаче. @@ -58,13 +58,13 @@ let printTwoParameters x = // only one parameter! ```fsharp // eval with one argument -printTwoParameters 1 +printTwoParameters 1 // get back a function! val it : (int -> unit) = ``` -> If you evaluate it with one argument, you don't get an error, you get back a function. +> If you evaluate it with one argument, you don't get an error, you get back a function. Если вычислить ее с одним аргументом, мы не получим ошибку, а будет возвращена функция. @@ -88,9 +88,9 @@ val it : (int -> unit) = // step by step version let x = 6 let y = 99 -let intermediateFn = printTwoParameters x // return fn with +let intermediateFn = printTwoParameters x // return fn with // x "baked in" -let result = intermediateFn y +let result = intermediateFn y // inline version of above let result = (printTwoParameters x) y @@ -105,21 +105,21 @@ let result = printTwoParameters x y ```fsharp //normal version -let addTwoParameters x y = +let addTwoParameters x y = x + y //explicitly curried version let addTwoParameters x = // only one parameter! - let subFunction y = + let subFunction y = x + y // new function with one param subFunction // return the subfunction -// now use it step by step +// now use it step by step let x = 6 let y = 99 -let intermediateFn = addTwoParameters x // return fn with +let intermediateFn = addTwoParameters x // return fn with // x "baked in" -let result = intermediateFn y +let result = intermediateFn y // normal version let result = addTwoParameters x y @@ -129,27 +129,27 @@ let result = addTwoParameters x y Опять же, "функция с двумя параметрами" на самом деле является функцией с одним параметром, которая возвращает промежуточную функцию. -> But wait a minute -- what about the "`+`" operation itself? It's a binary operation that must take two parameters, surely? No, it is curried like every other function. There is a function called "`+`" that takes one parameter and returns a new intermediate function, exactly like `addTwoParameters` above. +> But wait a minute -- what about the "`+`" operation itself? It's a binary operation that must take two parameters, surely? No, it is curried like every other function. There is a function called "`+`" that takes one parameter and returns a new intermediate function, exactly like `addTwoParameters` above. Но подождите, а что с операторором "`+`"? Это ведь бинарная операция, которая должна принимать два параметра? Нет, она тоже каррируется, как и другие функции. Это функция с именем "`+`", которая принимает один параметр и возвращает новую промежуточную функцию, в точности как `addTwoParameters` выше. > When we write the statement `x+y`, the compiler reorders the code to remove the infix and turns it into `(+) x y`, which is the function named `+` called with two parameters. Note that the function named "+" needs to have parentheses around it to indicate that it is being used as a normal function name rather than as an infix operator. -Когда мы пишем выражение `x+y`, компилятор _переупорядочивает_ код, чтобы удалить инфикс и превратить его в `(+) x y`, _что является функцией с именем `+` принимающей два параметра._ Заметим, что функция с именем "+" должна быть в скобках, чтобы она используется как обычная функция, а не как инфиксный оператор. +Когда мы пишем выражение `x+y`, компилятор _переупорядочивает_ код таким образом, чтобы преобразовать инфикс в `(+) x y`, _что является функцией с именем `+`, принимающей два параметра._ Заметим, что функция "+" нуждается в скобках, чтобы указать на то, она используется как обычная функция, а не как инфиксный оператор. -> Finally, the two parameter function named `+` is treated as any other two parameter function would be. +> Finally, the two parameter function named `+` is treated as any other two parameter function would be. Наконец, функция с двумя параметрами называемая `+` обрабатывается как любая другая функция с двумя параметрами. ```fsharp -// using plus as a single value function +// using plus as a single value function let x = 6 let y = 99 let intermediateFn = (+) x // return add with x baked in -let result = intermediateFn y +let result = intermediateFn y // using plus as a function with two parameters -let result = (+) x y +let result = (+) x y // normal version of plus as infix operator let result = x + y @@ -168,7 +168,7 @@ let intermediateFn = (*) 3 // return multiply with "3" baked in let result = intermediateFn 5 // normal version of printfn -let result = printfn "x=%i y=%i" 3 5 +let result = printfn "x=%i y=%i" 3 5 // printfn as a one parameter function let intermediateFn = printfn "x=%i y=%i" 3 // "3" is baked in @@ -181,9 +181,9 @@ let result = intermediateFn 5 Теперь, когда мы знаем, как работают каррированные функции, что можно ожидать от их сигнатур? -> Going back to the first example, "`printTwoParameters`", we saw that it took one argument and returned an intermediate function. The intermediate function also took one argument and returned nothing (that is, unit). So the intermediate function has type `int->unit`. In other words, the domain of `printTwoParameters` is `int` and the range is `int->unit`. Putting this together we see that the final signature is: +> Going back to the first example, "`printTwoParameters`", we saw that it took one argument and returned an intermediate function. The intermediate function also took one argument and returned nothing (that is, unit). So the intermediate function has type `int->unit`. In other words, the domain of `printTwoParameters` is `int` and the range is `int->unit`. Putting this together we see that the final signature is: -Возращаясь к первому примеру, "`printTwoParameter`", мы видели, что функция принимала один рагумент и возвращала промежуточную функцию. Промежуточная функция также принимала один аргумент и ничего не возврашала (т.е. `unit`). Поэтому промежуточная функция имела тип `int->unit`. Другими словами, domain `printTwoParameters` - это `int`, а range - `int->unit`. Собрав все это воедино мы увидим конечную сигнатуру: +Возращаясь к первому примеру, "`printTwoParameter`", мы видели, что функция принимала один рагумент и возвращала промежуточную функцию. Промежуточная функция также принимала один аргумент и ничего не возврашала (т.е. `unit`). Поэтому промежуточная функция имела тип `int->unit`. Другими словами, domain `printTwoParameters` - это `int`, а range - `int->unit`. Собрав все это воедино мы увидим конечную сигнатуру: ```fsharp val printTwoParameters : int -> (int -> unit) @@ -191,7 +191,7 @@ val printTwoParameters : int -> (int -> unit) > If you evaluate the explicitly curried implementation, you will see the parentheses in the signature, as written above, but if you evaluate the normal implementation, which is implicitly curried, the parentheses are left off, like so: -Если вычислить явно каррированную реализацию, можно увидеть скобки в сигнатуре, как написано выше, но если вычислить нормальную имплементацию, которая неявно каррированна, скобок не будет: +Если вычислить явно каррированную реализацию, можно увидеть скобки в сигнатуре, как написано выше, но если вычислить обыкновенную, неявно каррированную реализацию, скобок не будет: ```fsharp val printTwoParameters : int -> int -> unit @@ -199,63 +199,63 @@ val printTwoParameters : int -> int -> unit > The parentheses are optional. If you are trying to make sense of function signatures it might be helpful to add them back in mentally. -Скобки опциональны. Но их можно представлять в уме, чтобы упростить восприятие сигнатур функций. +Скобки необязательны. Но их можно представлять в уме, чтобы упростить восприятие сигнатур функций. > At this point you might be wondering, what is the difference between a function that returns an intermediate function and a regular two parameter function? -В этом месте впасть в недоумение, из-за наличия разницы между между функцией, которая возвращает промежуточную функцию и обычной функцией с двумя параметрами? +Сейчас вам должно быть интересно, в чём разница между функцией, которая возвращает промежуточную функцию и обычной функцией с двумя параметрами. > Here's a one parameter function that returns a function: -Функция с одним параметром возвращающая другую функцию: +Вот функция с одним параметром, возвращающая другую функцию: ```fsharp -let add1Param x = (+) x +let add1Param x = (+) x // signature is = int -> (int -> int) ``` > Here's a two parameter function that returns a simple value: -А вот функция с двумя параметрами которая возвращает простое значение: +А вот функция с двумя параметрами, которая возвращает простое значение: ```fsharp -let add2Params x y = (+) x y +let add2Params x y = (+) x y // signature is = int -> int -> int ``` > The signatures are slightly different, but in practical terms, there *is* no difference*, only that the second function is automatically curried for you. -Их сигнатуры немного отличаются, но в практическом плане между ними нет особой разницы, за исключением того факта, что вторая функция автоматически каррирована. +Их сигнатуры немного отличаются, но в практическом смысле между ними нет особой разницы, за исключением того факта, что вторая функция автоматически каррирована. -## Functions with more than two parameters | Функции с большим количеством параметров ## +## Functions with more than two parameters | Функции с более чем двумя параметрами ## > How does currying work for functions with more than two parameters? Exactly the same way: for each parameter except the last one, the function returns an intermediate function with the previous parameters baked in. -Как каррирование работает для функций с количеством параметров большим двух? Точно также: для каждого параметра кроме последнего функция возвращает промежуточную функцию замкающую предыдщуий параметр. +Как работает каррирование для функций с количеством параметров, большим двух? Точно так же: для каждого параметра, кроме последнего, функция возвращает промежуточную функцию, замыкающую предыдущий параметр. > Consider this contrived example. I have explicitly specified the types of the parameters, but the function itself does nothing. -Рассмотрим этот хитрый пример. У меня явно объявлены типы параметров, но функция ничего не делает. +Рассмотрим этот затейливый пример. У меня явно объявлены типы параметров, но функция ничего не делает. ```fsharp let multiParamFn (p1:int)(p2:bool)(p3:string)(p4:float)= () //do nothing -let intermediateFn1 = multiParamFn 42 - // intermediateFn1 takes a bool +let intermediateFn1 = multiParamFn 42 + // intermediateFn1 takes a bool // and returns a new function (string -> float -> unit) -let intermediateFn2 = intermediateFn1 false - // intermediateFn2 takes a string +let intermediateFn2 = intermediateFn1 false + // intermediateFn2 takes a string // and returns a new function (float -> unit) -let intermediateFn3 = intermediateFn2 "hello" - // intermediateFn3 takes a float +let intermediateFn3 = intermediateFn2 "hello" + // intermediateFn3 takes a float // and returns a simple value (unit) let finalResult = intermediateFn3 3.141 ``` > The signature of the overall function is: -Сигнатура полной функции: +Сигнатура всей функции: ```fsharp val multiParamFn : int -> bool -> string -> float -> unit @@ -274,44 +274,44 @@ val finalResult : unit = () > A function signature can tell you how many parameters the function takes: just count the number of arrows outside of parentheses. If the function takes or returns other function parameters, there will be other arrows in parentheses, but these can be ignored. Here are some examples: -Сигнатура функции может говорить о том, как много параметров принимает функция: достаточно подсчитать число стрелок вне скобок. Если функция принимает или возвращает другую функцию, будут еще стрелки, но они будут в скобках и их можно будет проигнорировать. Вот некоторые примеры: +Сигнатура функции может сообщить о том, сколько параметров принимает функция: достаточно подсчитать число стрелок вне скобок. Если функция принимает или возвращает другую функцию, будут еще стрелки, но они будут в скобках и их можно будет проигнорировать. Вот некоторые примеры: ```fsharp int->int->int // two int parameters and returns an int -string->bool->int // first param is a string, second is a bool, +string->bool->int // first param is a string, second is a bool, // returns an int -int->string->bool->unit // three params (int,string,bool) +int->string->bool->unit // three params (int,string,bool) // returns nothing (unit) (int->string)->int // has only one parameter, a function // value (from int to string) // and returns a int -(int->string)->(int->bool) // takes a function (int to string) - // returns a function (int to bool) +(int->string)->(int->bool) // takes a function (int to string) + // returns a function (int to bool) ``` -## Issues with multiple parameters | _Вопросы_ с несколькими параметрами ## +## Issues with multiple parameters | _Трудности_ с множественными параметрами ## > The logic behind currying can produce some unexpected results until you understand it. Remember that you will not get an error if you evaluate a function with fewer arguments than it is expecting. Instead you will get back a partially applied function. If you then go on to use this partially applied function in a context where you expect a value, you will get obscure error messages from the compiler. -Логика за каррированием приводит к некоторым неожиданным результатам, пока не придет понимание. Помните, что вы не получите ошибку, если запустите функцию с меньшим количеством аргументов, чем ожидается. Вместо этого вы получите частично примененную функцию. Если затем вы воспользуетесь частично примененной функцией в контексте, где ожидается значение, можно получить малопонятную ошибку от компилятора. +Пока вы не поймёте логику, которая стоит за каррированием, она будет приводить к некоторым неожиданным результатам. Помните, что вы не получите ошибку, если запустите функцию с меньшим количеством аргументов, чем ожидается. Вместо этого вы получите частично примененную функцию. Если затем вы воспользуетесь частично примененной функцией в контексте, где ожидается значение, можно получить малопонятную ошибку от компилятора. -> Here's an innocuous looking function: +> Here's an innocuous looking function: -С виду безобидная функция: +Рассмотрим с виду безобидную функцию: ```fsharp // create a function let printHello() = printfn "hello" ``` -> What would you expect to happen when we call it as shown below? Will it print "hello" to the console? Try to guess before evaluating it, and here's a hint: be sure to take a look at the function signature. +> What would you expect to happen when we call it as shown below? Will it print "hello" to the console? Try to guess before evaluating it, and here's a hint: be sure to take a look at the function signature. -Как думаете, что произойдет, если вызвать ее как показано ниже? Выведится ли "hello" на консоль? Попробуйте догадаться до выполнения, подсказка: посмотрите на сигнатуру функции. +Как думаете, что произойдет, если вызвать ее, как показано ниже? Выведится ли "hello" на консоль? Попробуйте догадаться до выполнения. Подсказка: посмотрите на сигнатуру функции. ```fsharp // call it @@ -327,9 +327,9 @@ printHello А что насчет этого случая? Будет ли он скомпилирован? ```fsharp -let addXY x y = - printfn "x=%i y=%i" x - x + y +let addXY x y = + printfn "x=%i y=%i" x + x + y ``` > If you evaluate it, you will see that the compiler complains about the printfn line. @@ -343,30 +343,30 @@ printfn "x=%i y=%i" x //arguments. Its type is ^a -> unit. ``` -> If you didn't understand currying, this message would be very cryptic! All expressions that are evaluated standalone like this (i.e. not used as a return value or bound to something with "let") *must* evaluate to the unit value. And in this case, it is does *not* evaluate to the unit value, but instead evaluates to a function. This is a long winded way of saying that `printfn` is missing an argument. +> If you didn't understand currying, this message would be very cryptic! All expressions that are evaluated standalone like this (i.e. not used as a return value or bound to something with "let") *must* evaluate to the unit value. And in this case, it is does *not* evaluate to the unit value, but instead evaluates to a function. This is a long winded way of saying that `printfn` is missing an argument. -Если нет понимания каррирования, данное сообщение может быть очень загадочным. Дело в том, что все выражения, которые вычисляются отдельно как это (т.е. не используются как возвращаемое значение или привязка к чему-нибудь посредством "let") _должны_ вычисляться в `unit` значение. В данном случае, оно _не_ вычисляется в `unit` значение, но вместо этого возвращает функцию. Это длинный извилистный путь сказать, что `printfn` лишен аргумента. +Если нет понимания каррирования, данное сообщение может быть очень загадочным. Дело в том, что все выражения, которые вычисляются отдельно, как это (т.е. не используются как возвращаемое значение или привязка к чему-либо посредством "let") _должны_ вычисляться в `unit` значение. В данном случае, оно _не_ вычисляется в `unit` значение, но вместо этого возвращает функцию. Это длинный извилистный способ сказать, что `printfn` не хватает аргумента. > A common case of errors like this is when interfacing with the .NET library. For example, the `ReadLine` method of a `TextReader` must take a unit parameter. It is often easy to forget this and leave off the parens, in which case you do not get a compiler error immediately, but only when you try to treat the result as a string. -В большинстве случаев ошибки подобные этой случаются при взаимодейтвии с библиотекой из мира .NET. Например, `Readline` метод `TextReader` должент принимать `unit` параметр. Часто об этом легко забыть, и не поставить скобки, в этом случае нельзя получить ошибку компилятора в момент "вызова", но она появится при попытке интерпретировать результат как строку. +В большинстве случаев ошибки, подобные этой, случаются при взаимодейтвии с библиотекой из мира .NET. Например, метод `Readline` класса `TextReader` должент принимать `unit` параметр. Об этом часто можно забыть, и не поставить скобки, в этом случае нельзя получить ошибку компилятора в момент "вызова", но она появится при попытке интерпретировать результат как строку. ```fsharp let reader = new System.IO.StringReader("hello"); -let line1 = reader.ReadLine // wrong but compiler doesn't +let line1 = reader.ReadLine // wrong but compiler doesn't // complain printfn "The line is %s" line1 //compiler error here! -// ==> error FS0001: This expression was expected to have -// type string but here has type unit -> string +// ==> error FS0001: This expression was expected to have +// type string but here has type unit -> string let line2 = reader.ReadLine() //correct -printfn "The line is %s" line2 //no compiler error +printfn "The line is %s" line2 //no compiler error ``` > In the code above, `line1` is just a pointer or delegate to the `Readline` method, not the string that we expected. The use of `()` in `reader.ReadLine()` actually executes the function. -В коде выше `line1` - просто указатель или делегат на `Readline` метод, а не строка, как можно было бы ожидать. Использование `()` в `reader.ReadLine()` действительно вызывит функцию. +В коде выше `line1` - просто указатель или делегат на метод `Readline`, а не строка, как можно было бы ожидать. Использование `()` в `reader.ReadLine()` действительно вызовет функцию. ## Too many parameters | Слишком много параметров ## @@ -376,31 +376,31 @@ printfn "The line is %s" line2 //no compiler error ```fsharp printfn "hello" 42 -// ==> error FS0001: This expression was expected to have -// type 'a -> 'b but here has type unit +// ==> error FS0001: This expression was expected to have +// type 'a -> 'b but here has type unit printfn "hello %i" 42 43 -// ==> Error FS0001: Type mismatch. Expecting a 'a -> 'b -> 'c -// but given a 'a -> unit +// ==> Error FS0001: Type mismatch. Expecting a 'a -> 'b -> 'c +// but given a 'a -> unit printfn "hello %i %i" 42 43 44 -// ==> Error FS0001: Type mismatch. Expecting a 'a->'b->'c->'d -// but given a 'a -> 'b -> unit +// ==> Error FS0001: Type mismatch. Expecting a 'a->'b->'c->'d +// but given a 'a -> 'b -> unit ``` > For example, in the last case, the compiler is saying that it expects the format argument to have three parameters (the signature `'a -> 'b -> 'c -> 'd` has three parameters) but it is given only two (the signature `'a -> 'b -> unit` has two parameters). __TODO: Не могу собрать нормальную фразу.__ -Например, в последнем случае компилятор сообщит, что он ожидает форматирующий аргумент с тремя параметрами (сигнатура `'a -> 'b -> 'c -> 'd` имеет три параметра), но вместо этого получил с двумя (у сигнатуры `'a -> 'b -> unit` только два параметра)_. +Например, в последнем случае компилятор сообщает, что ожидается форматирующая строка с тремя параметрами (сигнатура `'a -> 'b -> 'c -> 'd` имеет три параметра), но вместо этого получена строка с двумя (у сигнатуры `'a -> 'b -> unit` два параметра)_. > In cases not using `printf`, passing too many parameters will often mean that you end up with a simple value that you then try to pass a parameter to. The compiler will complain that the simple value is not a function. -В случаях отличных от использования `printf` передача большого количества параметров часто означает, что на определенном этапе вычислений было получен простое значение, которому пытаются передать параметр. Компилятор будет жаловаться, что простое значение не является функцией. +В тех случаях, где не используется `printf`, передача большого количества параметров часто означает, что на определенном этапе вычислений было получено простое значение, которому пытаются передать параметр. Компилятор будет возмущаться, что простое значение не является функцией. ```fsharp let add1 x = x + 1 let x = add1 2 3 -// ==> error FS0003: This value is not a function +// ==> error FS0003: This value is not a function // and cannot be applied ``` @@ -412,6 +412,6 @@ let x = add1 2 3 let add1 x = x + 1 let intermediateFn = add1 2 //returns a simple value let x = intermediateFn 3 //intermediateFn is not a function! -// ==> error FS0003: This value is not a function +// ==> error FS0003: This value is not a function // and cannot be applied ``` \ No newline at end of file From b4121e3b4fb4689d5959b97b08e67452679628dd Mon Sep 17 00:00:00 2001 From: kleidemos Date: Wed, 14 Feb 2018 07:52:00 +0500 Subject: [PATCH 05/48] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BA=D0=B8=20FoggyFinder-=D0=B0=20=D0=BE=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=BA=20?= =?UTF-8?q?=D1=83=D1=82=D1=80=D1=83=2014.02.2018.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/thinking-functionally-intro.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/posts/thinking-functionally-intro.md b/posts/thinking-functionally-intro.md index 502f172..4eb21e1 100644 --- a/posts/thinking-functionally-intro.md +++ b/posts/thinking-functionally-intro.md @@ -20,12 +20,12 @@ seriesOrder: 1 > F# does allow non-functional styles, and it is tempting to retain the habits you already are familiar with. You could just use F# in a non-functional way without really changing your mindset, and not realize what you are missing. To get the most out of F#, and to be fluent and comfortable with functional programming in general, it is critical that you think functionally, not imperatively. > And that is the goal of this series: to help you understand functional programming in a deep way, and help to change the way you think. -F# позволяет использовать нефункциональные стили, что искушает сохранить существующие привычки. Можно просто использовать F# в нефункциональном стиле без реального изменения мировозрения и даже не представлять потерянные возможности. Однако, чтобы получить максимальную отдачу от F#, а также тепло и лампово программировать в функциональном стиле вообще, очень важно думать функционально, а не императивно. +F# позволяет использовать нефункциональные стили, что искушает сохранить существующие привычки. Вы можете просто использовать F# привычным образом без реального изменения мировоззрения и даже не представлять, что вы теряете. Однако, чтобы получить максимальную отдачу от F#, а также тепло и лампово программировать в функциональном стиле вообще, очень важно думать функционально, а не императивно. Цель данной серии - помочь понять функциональное программирование _в глубокую сторону_ и изменить способ мышления читателя. > This will be a quite abstract series, although I will use lots of short code examples to demonstrate the points. We will cover the following points: -Это будет довольно абстрактная серия, хотя я буду использовать множество коротких примеров кода для демонстрации некоторых моментов. Будут освещены следующие темы: +Это будет довольно абстрактная серия, хотя я буду использовать множество коротких примеров кода для демонстрации некоторых моментов. Мы рассмотрим следующие темы: > * **Mathematical functions**. The first post introduces the mathematical ideas behind functional languages, and the benefits that come from this approach. > * **Functions and values**. The next post introduces functions and values, showing how "values" are different from variables, and why there are similarities between function and simple values. From 13dcb4efe4f7465e38ccfd667ec4d5857dccf93e Mon Sep 17 00:00:00 2001 From: Anna Udovichenko Date: Wed, 14 Feb 2018 06:30:10 +0300 Subject: [PATCH 06/48] Minor fixes according to the comments --- posts/currying.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/posts/currying.md b/posts/currying.md index 3b41eab..73a4d24 100644 --- a/posts/currying.md +++ b/posts/currying.md @@ -47,14 +47,14 @@ let printTwoParameters x = // only one parameter! > 3. Finally, return the newly created subfunction. > 4. This returned function is then later used against "y". The "x" parameter is baked into it, so the returned function only needs the y param to finish off the function logic. -1. Объявляем функцию с названием "`printTwoParameters`", но принимаюшую только _один_ параметр: "x". -2. Внутри неё создаём локальную функцию, которая также принимает только _один_ параметр: "y". Заметим, что локальная функция использует параметр "x", но x не передается в нее как аргумент. "x" находится в такой области видимости, что вложенная функция может видеть его и использовать без необходимости в его передаче. -3. Наконец, возвращаем только что созданную локальную функцию. +1. Объявляется функция с названием "`printTwoParameters`", но принимающая только _один_ параметр: "x". +2. Внутри неё создаётся локальная функция, которая также принимает только _один_ параметр: "y". Заметим, что локальная функция использует параметр "x", но x не передается в нее как аргумент. "x" находится в такой области видимости, что вложенная функция может видеть его и использовать без необходимости в его передаче. +3. Наконец, возвращается только что созданная локальная функция. 4. Возвращенная функция затем применяется к аргументу "y". Параметр "x" замыкается в ней, так что возвращаемая функция нуждается только в параметре y чтобы завершить свою логику. > By rewriting it this way, the compiler has ensured that every function has only one parameter, as required. So when you use "`printTwoParameters`", you might think that you are using a two parameter function, but it is actually only a one parameter function! You can see for yourself by passing only one argument instead of two: -Переписывая функции таким образом, комплиятор гарантирует, что каждая функция принимает только один параметр, как и требовалось. _Таким образом, используя "`printTwoParameters`", можно подумать, что это функция с двумя параметрами, но на самом деле используется только функции с одним параметром._ В этом можно убедиться, передав ей лишь один аргумент вместо двух: +Переписывая функции таким образом, комплиятор гарантирует, что каждая функция принимает только один параметр, как и требовалось. Таким образом, используя "`printTwoParameters`", можно подумать, что это функция с двумя параметрами, но на самом деле используется функция с только одним параметром. В этом можно убедиться, передав ей лишь один аргумент вместо двух: ```fsharp // eval with one argument @@ -294,7 +294,7 @@ int->string->bool->unit // three params (int,string,bool) ``` -## Issues with multiple parameters | _Трудности_ с множественными параметрами ## +## Issues with multiple parameters | Трудности с множественными параметрами ## > The logic behind currying can produce some unexpected results until you understand it. Remember that you will not get an error if you evaluate a function with fewer arguments than it is expecting. Instead you will get back a partially applied function. If you then go on to use this partially applied function in a context where you expect a value, you will get obscure error messages from the compiler. @@ -390,7 +390,6 @@ printfn "hello %i %i" 42 43 44 > For example, in the last case, the compiler is saying that it expects the format argument to have three parameters (the signature `'a -> 'b -> 'c -> 'd` has three parameters) but it is given only two (the signature `'a -> 'b -> unit` has two parameters). -__TODO: Не могу собрать нормальную фразу.__ Например, в последнем случае компилятор сообщает, что ожидается форматирующая строка с тремя параметрами (сигнатура `'a -> 'b -> 'c -> 'd` имеет три параметра), но вместо этого получена строка с двумя (у сигнатуры `'a -> 'b -> unit` два параметра)_. > In cases not using `printf`, passing too many parameters will often mean that you end up with a simple value that you then try to pass a parameter to. The compiler will complain that the simple value is not a function. From a7554f4f3bea27af2138ad61670982e84d21041c Mon Sep 17 00:00:00 2001 From: Anna Udovichenko Date: Wed, 14 Feb 2018 20:23:10 +0300 Subject: [PATCH 07/48] Edited partial application --- posts/partial-application.md | 152 +++++++++++++++++------------------ 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/posts/partial-application.md b/posts/partial-application.md index 6304e97..2122cfc 100644 --- a/posts/partial-application.md +++ b/posts/partial-application.md @@ -11,15 +11,15 @@ categories: [Currying, Partial Application] > In the previous post on currying, we looked at breaking multiple parameter functions into smaller one parameter functions. It is the mathematically correct way of doing it, but that is not the only reason it is done -- it also leads to a very powerful technique called **partial function application**. This is a very widely used style in functional programming, and it is important to understand it. -В предыдущем посте о каррировании мы увидели как мультипараметрических функции дробятся на функции по меньше лишь с одним параметром. Это математически корректный путь решения, однако есть и другие причины такого выбора -- это также приводит к очень сильной технике называемой **частичное применение функций**. Это очень широко используемый стиль в функциональном программировании, и очень важно его понять. +В предыдущем посте о каррировании мы увидели, как функции с несколькими параметрами дробятся на функции поменьше, с одним параметром. Это математически корректное решение, однако есть и другие причины так поступать -- это также приводит к очень мощной технике, называемой **частичное применение функций**. Такой стиль очень широко используется в функциональном программировании, и очень важно его понимать. > The idea of partial application is that if you fix the first N parameters of the function, you get a function of the remaining parameters. From the discussion on currying, you can probably see how this comes about naturally. -Идея частичного применения заключается в том, что если зафиксировать первые N параметров функции получится новая функция с оставшимися параметрами. Из обсуждения каррирования можно было увидеть как частичное применение происходит естественным образом. +Идея частичного применения заключается в том, что если зафиксировать первые N параметров функции, получится новая функция с оставшимися параметрами. Из обсуждения каррирования можно было увидеть, как частичное применение происходит естественным образом. > Here are some simple examples that demonstrate this: -Несколько простых демостраций: +Несколько простых примеров для иллюстрации: ```fsharp // create an "adder" by partial application of add @@ -29,7 +29,7 @@ add42 3 // create a new list by applying the add42 function // to each element -[1;2;3] |> List.map add42 +[1;2;3] |> List.map add42 // create a "tester" by partial application of "less than" let twoIsLessThan = (<) 2 // partial application @@ -37,22 +37,22 @@ twoIsLessThan 1 twoIsLessThan 3 // filter each element with the twoIsLessThan function -[1;2;3] |> List.filter twoIsLessThan +[1;2;3] |> List.filter twoIsLessThan // create a "printer" by partial application of printfn -let printer = printfn "printing param=%i" +let printer = printfn "printing param=%i" // loop over each element and call the printer function -[1;2;3] |> List.iter printer +[1;2;3] |> List.iter printer ``` > In each case, we create a partially applied function that we can then reuse in multiple contexts. -В каждом случае мы создаем частично примененную функцию, которую можно использовать в разных ситауациях. +В каждом случае мы создаем частично примененную функцию, которую можно повторно использовать в разных ситауациях. > The partial application can just as easily involve fixing function parameters, of course. Here are some examples: -И конечно, частичное применение может также легко фиксировать параметры-функции: +И конечно, частичное применение позволяет так же легко фиксировать параметры-функции. Вот несколько примеров: ```fsharp // an example using List.map @@ -63,7 +63,7 @@ let add1ToEach = List.map add1 // fix the "add1" function add1ToEach [1;2;3;4] // an example using List.filter -let filterEvens = +let filterEvens = List.filter (fun i -> i%2 = 0) // fix the filter function // test @@ -72,46 +72,46 @@ filterEvens [1;2;3;4] > The following more complex example shows how the same approach can be used to create "plug in" behavior that is transparent. -Следующий более сложный пример показывает как тот же подход может быть использоватн, чтобы прозрачно создать "встраивоемое" поведение. +Следующий, более сложный пример иллюстрирует то, как тот же подход может использоваться для того, чтобы прозрачно создать "встраиваемое" поведение. -> * We create a function that adds two numbers, but in addition takes a logging function that will log the two numbers and the result. +> * We create a function that adds two numbers, but in addition takes a logging function that will log the two numbers and the result. > * The logging function has two parameters: (string) "name" and (generic) "value", so it has signature `string->'a->unit`. > * We then create various implementations of the logging function, such as a console logger or a popup logger. -> * And finally we partially apply the main function to create new functions that have a particular logger baked into them. +> * And finally we partially apply the main function to create new functions that have a particular logger baked into them. -* Мы создаем функцию, которая складывает два числа, но в дополнения она принимает функцию логирования, которая будет логировать два числа и результат. -* Функция логирования имеет два параметра: (string) "name" и (generic) "value", поэтому имеет сигнатуру `string->'a->unit`. +* Создаем функцию, которая складывает два числа, но в дополнение она принимает функцию логирования, которая будет логировать эти числа и результат. +* Функция логирования принимает два параметра: (string) "name" и (generic) "value", поэтому имеет сигнатуру `string->'a->unit`. * Затем мы создаем различные реализации логирующей функции, такие как консольный логгер или логгер на основе всплывающего окна. -* И наконец мы частично применяем основную функцию для создания новой функции, с замкнутым логгером. +* И наконец, мы частично применяем основную функцию для создания новой функции, с замкнутым логгером. ```fsharp // create an adder that supports a pluggable logging function -let adderWithPluggableLogger logger x y = +let adderWithPluggableLogger logger x y = logger "x" x logger "y" y let result = x + y - logger "x+y" result - result + logger "x+y" result + result // create a logging function that writes to the console -let consoleLogger argName argValue = - printfn "%s=%A" argName argValue +let consoleLogger argName argValue = + printfn "%s=%A" argName argValue //create an adder with the console logger partially applied -let addWithConsoleLogger = adderWithPluggableLogger consoleLogger -addWithConsoleLogger 1 2 +let addWithConsoleLogger = adderWithPluggableLogger consoleLogger +addWithConsoleLogger 1 2 addWithConsoleLogger 42 99 // create a logging function that creates popup windows -let popupLogger argName argValue = - let message = sprintf "%s=%A" argName argValue +let popupLogger argName argValue = + let message = sprintf "%s=%A" argName argValue System.Windows.Forms.MessageBox.Show( - text=message,caption="Logger") + text=message,caption="Logger") |> ignore //create an adder with the popup logger partially applied -let addWithPopupLogger = adderWithPluggableLogger popupLogger -addWithPopupLogger 1 2 +let addWithPopupLogger = adderWithPluggableLogger popupLogger +addWithPopupLogger 1 2 addWithPopupLogger 42 99 ``` @@ -121,20 +121,20 @@ addWithPopupLogger 42 99 ```fsharp // create a another adder with 42 baked in -let add42WithConsoleLogger = addWithConsoleLogger 42 -[1;2;3] |> List.map add42WithConsoleLogger -[1;2;3] |> List.map add42 //compare without logger +let add42WithConsoleLogger = addWithConsoleLogger 42 +[1;2;3] |> List.map add42WithConsoleLogger +[1;2;3] |> List.map add42 //compare without logger ``` > These partially applied functions are a very useful tool. We can create library functions which are flexible (but complicated), yet make it easy to create reusable defaults so that callers don't have to be exposed to the complexity all the time. -Частично примененные функции являются очень полезным инструментом. _Мы можем создать библиотечные функции, которые являются гибкими (хотя и сложными), причем легко сделать их многоразовыми по умолчанию, так что вызывающему не потребуется прибегать к сложной форме при каждом использовании._ +Частично примененные функции - это очень полезный инструмент. _Мы можем создать гибкие (хоть и сложные) библиотечные функции, причем легко сделать их по умолчанию пригодными для повторного использования, так что сложность будет сокрыта от клиентского кода._ ## Designing functions for partial application | Проектирование функций для частичного применения ## > You can see that the order of the parameters can make a big difference in the ease of use for partial application. For example, most of the functions in the `List` library such as `List.map` and `List.filter` have a similar form, namely: -Видно, что порядок параметров может серьезно влиять на удобство использования частичного применения. Например, большинство функций в `List` таких как `List.map` и `List.filter` имеют схожую форму, а именно: +Очевидно, что порядок параметров может серьезно влиять на удобство частичного применения. Например, большинство функций в `List` таких как `List.map` и `List.filter` имеют схожую форму, а именно: List-function [function parameter(s)] [list] @@ -153,43 +153,43 @@ List.sortBy (fun i -> -i ) [0;1;2;3] Те же самые примеры с использованием частичного применения: ```fsharp -let eachAdd1 = List.map (fun i -> i+1) +let eachAdd1 = List.map (fun i -> i+1) eachAdd1 [0;1;2;3] -let excludeOneOrLess = List.filter (fun i -> i>1) +let excludeOneOrLess = List.filter (fun i -> i>1) excludeOneOrLess [0;1;2;3] -let sortDesc = List.sortBy (fun i -> -i) +let sortDesc = List.sortBy (fun i -> -i) sortDesc [0;1;2;3] ``` > If the library functions were written with the parameters in a different order, it would be much more inconvenient to use them with partial application. -Если бы библиотечные функции были написаны с другим порядком аргументов, частичное применение было бы значительно осложнено. +Если бы библиотечные функции были реализованы с другим порядком аргументов, частичное применение было бы намного менее удобным. > As you write your own multi-parameter functions, you might wonder what the best parameter order is. As with all design questions, there is no "right" answer to this question, but here are some commonly accepted guidelines: -При написании своей мультипараметрической функции можно задаться вопросом о наилучшем порядке параметров. Как и во всех архитектурных вопросах, здесь нет "правильного" ответа, но есть несколько общепринятых рекомендаций. +Когда вы пишете свою функцию с многими параметрами, вы можете задуматься о наилучшем их порядке. Как и во всех вопросах проектирования, здесь нет "правильного" ответа, но есть несколько общепринятых рекомендаций. -> 1. Put earlier: parameters more likely to be static -> 2. Put last: the data structure or collection (or most varying argument) -> 3. For well-known operations such as "subtract", put in the expected order +> 1. Put earlier: parameters more likely to be static +> 2. Put last: the data structure or collection (or most varying argument) +> 3. For well-known operations such as "subtract", put in the expected order -1. Сначала идут параметры, которые скорее всего будут статичными -2. Потом структуры данных или коллекции (или другие изменяющиеся параметры) -3. Для лучшего вопсриятия операций таких как "subtract", желательно соблюдать ожидаемый порядок +1. Ставьте в начало параметры, которые скорее всего будут статичными +2. Ставьте последними структуры данных или коллекции (или другие изменяющиеся параметры) +3. Для лучшего восприятия операций, таких как вычитание, желательно соблюдать ожидаемый порядок > Guideline 1 is straightforward. The parameters that are most likely to be "fixed" with partial application should be first. We saw this with the logger example earlier. -Первое правило незамысловато. Параметры, котоыре скорее всего будут "зафиксированны" частичным применением должны идти первыми, например как в примерах с логгером ранее. +Первый совет прост. Параметры, которые скорее всего будут "зафиксированны" частичным применением должны идти первыми, как в примерах с логгером выше. > Guideline 2 makes it easier to pipe a structure or collection from function to function. We have seen this many times already with list functions. -Второе правило облегчает использование pipe оператора и композиций, как уже было показано во множестве примеров ранее со списками функций. +Следование второму совету облегчает использование оператора конвейеризации и композиции. Мы уже наблюдали это много раз в примерах с функциями над списками. ```fsharp // piping using list functions -let result = +let result = [1..10] |> List.map (fun i -> i+1) |> List.filter (fun i -> i>5) @@ -197,44 +197,44 @@ let result = > Similarly, partially applied list functions are easy to compose, because the list parameter itself can be easily elided: -Аналогично, частичное примененные функции над списками легко компануется, т.к. `list`-параметр легко игнорируется: +Аналогично, частичное примененные функции над списками легко подвергаются композиции, т.к. параметр-список может быть опущен: ```fsharp -let compositeOp = List.map (fun i -> i+1) +let compositeOp = List.map (fun i -> i+1) >> List.filter (fun i -> i>5) let result = compositeOp [1..10] ``` -### Wrapping BCL functions for partial application | Оборачивание BCL функций ждя частичного применения ### +### Wrapping BCL functions for partial application | Оборачивание BCL функций для частичного применения ### > The .NET base class library functions are easy to access in F#, but are not really designed for use with a functional language like F#. For example, most functions have the data parameter first, while with F#, as we have seen, the data parameter should normally come last. -Функции библиотеки базовых классов (base class library - BCL) .NET легко доступны из F#, но они в действительности не расчитаны на использование из функциональных языков таких как F#. Например, большинство функций требуют параметр данных вначале, в то время как в F# параметр данных в общем случае должен быть последним. +Функции библиотеки базовых классов (base class library - BCL) .NET легко доступны из F#, но они не особенно расчитаны на использование в функциональных языках, таких как F#. Например, большинство функций требует параметр данных вначале, в то время как в F# параметр данных в общем случае должен быть последним. > However, it is easy enough to create wrappers for them that are more idiomatic. For example, in the snippet below, the .NET string functions are rewritten to have the string target be the last parameter rather than the first: -Однако, достаточно легко создать обертки над ними, которые будут более идеоматичны. В примере ниже строковые .NET функции переписаны так, чтобы целевая строка использовалась последней, а не первой: +Однако, достаточно легко можно написить обертки, чтобы сделать эти функции более идеоматичными. В примере ниже строковые .NET функции переписаны так, чтобы целевая строка использовалась последней, а не первой: ```fsharp // create wrappers for .NET string functions -let replace oldStr newStr (s:string) = +let replace oldStr newStr (s:string) = s.Replace(oldValue=oldStr, newValue=newStr) -let startsWith lookFor (s:string) = +let startsWith lookFor (s:string) = s.StartsWith(lookFor) ``` > Once the string becomes the last parameter, we can then use them with pipes in the expected way: -После того, как строка стала последним параметром, можно использовать их в pipe-ах ожидаемым образом: +После того, как строка стала последним параметром, можно использовать эти функции в конвейерах, как обычно: ```fsharp -let result = - "hello" - |> replace "h" "j" +let result = + "hello" + |> replace "h" "j" |> startsWith "j" -["the"; "quick"; "brown"; "fox"] +["the"; "quick"; "brown"; "fox"] |> List.filter (startsWith "f") ``` @@ -247,40 +247,40 @@ let compositeOp = replace "h" "j" >> startsWith "j" let result = compositeOp "hello" ``` -### Understanding the "pipe" function | Понимание "pipe" функции (оператора) ### +### Understanding the "pipe" function | Понимание конвейерного оператора ### > Now that you have seen how partial application works, you should be able to understand how the "pipe" function works. -Теперь, после демонстрации работы частичного применения, можно понять как работают "pipe" функции. +После того, как вы увидели частичное применение в деле, вы можете понять, как работают конвейерные функции. > The pipe function is defined as: -Определение pipe: +Функция конвейеризации определена так: ```fsharp let (|>) x f = f x ``` -> All it does is allow you to put the function argument in front of the function rather than after. That's all. +> All it does is allow you to put the function argument in front of the function rather than after. That's all. -Все это позволяет передать аргумент функции спереди, а не с конца. +Всё, что она делает, это позволяет поставить аргумент перед функцией, а не после. ```fsharp let doSomething x y z = x+y+z doSomething 1 2 3 // all parameters after function ``` -> If the function has multiple parameters, then it appears that the input is the final parameter. Actually what is happening is that the function is partially applied, returning a function that has a single parameter: the input +> If the function has multiple parameters, then it appears that the input is the final parameter. Actually what is happening is that the function is partially applied, returning a function that has a single parameter: the input _TODO: Перевести на русский._ -_Если функция имеет несколько параметров, то оказывается, что ввод - это последний параметр. В действительности частично примененная функция возвращает функцию, которая имеет один параметр - ввод._ +???? Если функция принимает несколько параметров, то она выглядит так, будто входной параметр - последний. На самом деле функция применяется частично и возвращает функцию, которая принимает единственный параметр - input. ??? > Here's the same example rewritten to use partial application -Тот же пример переписанный с использованием частичного применения +Вот аналогичный пример, переписанный с целью частичного применения ```fsharp -let doSomething x y = +let doSomething x y = let intermediateFn z = x+y+z intermediateFn // return intermediateFn @@ -291,30 +291,30 @@ doSomethingPartial 3 // only one parameter after function now > As you have already seen, the pipe operator is extremely common in F#, and used all the time to preserve a natural flow. Here are some more usages that you might see: -Как было показано, pipe оператор чрезвычайно распространен в F#, и используется всякий раз, когда требуется сохранить естественную запись. Еще несколько примеров, которые можно было видеть ранее: +Как вы уже видели, конвейерный оператор чрезвычайно распространен в F#, и используется всякий раз, когда требуется сохранить естественный поток данных. Еще несколько примеров, которые вы возможно встречали: ```fsharp "12" |> int // parses string "12" to an int 1 |> (+) 2 |> (*) 3 // chain of arithmetic ``` -### The reverse pipe function | Обратный pipe оператор ### +### The reverse pipe function | Обратный конвейерный оператор ### -> You might occasionally see the reverse pipe function "<|" being used. +> You might occasionally see the reverse pipe function "<|" being used. -Иногда можно увидеть использование обратного pipe оператора "<|". +Время от времени можно встретить обратный конвейерный оператор "<|". ```fsharp let (<|) f x = f x ``` -> It seems that this function doesn't really do anything different from normal, so why does it exist? +> It seems that this function doesn't really do anything different from normal, so why does it exist? -Кажется, что эта функция в действительности не делает ничего, что отличало бы ее от обычной записи, так зачем же она существует? +Кажется, что эта функция в действительности не делает ничего особенного, так зачем же она нужна? > The reason is that, when used in the infix style as a binary operator, it reduces the need for parentheses and can make the code cleaner. -Причина в том, что при использовании инфиксного стиля как бинарного оператора, исчезает необходимость в скобках, что делает код чище. +Причина заключается в том, что когда обратный конвейерный оператор используется как бинарный оператор в инфиксном стиле, он снижает потребность в скобках, что делает код чище. ```fsharp printf "%i" 1+2 // error @@ -324,7 +324,7 @@ printf "%i" <| 1+2 // using reverse pipe > You can also use piping in both directions at once to get a pseudo infix notation. -Можно использовать pipe-ы в обоих направлениях сразу для получения псевдо инфиксной нотации. +Можно использовать конвейеры сразу в двух направлениях для получения псевдо инфиксной нотации. ```fsharp let add x y = x + y From 9ad7a4bb0ddc4d4b72fdec9123e81a3c681e1535 Mon Sep 17 00:00:00 2001 From: Anna Udovichenko Date: Thu, 15 Feb 2018 07:28:51 +0300 Subject: [PATCH 08/48] Edited according to comments --- posts/partial-application.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/posts/partial-application.md b/posts/partial-application.md index 2122cfc..09495eb 100644 --- a/posts/partial-application.md +++ b/posts/partial-application.md @@ -128,7 +128,7 @@ let add42WithConsoleLogger = addWithConsoleLogger 42 > These partially applied functions are a very useful tool. We can create library functions which are flexible (but complicated), yet make it easy to create reusable defaults so that callers don't have to be exposed to the complexity all the time. -Частично примененные функции - это очень полезный инструмент. _Мы можем создать гибкие (хоть и сложные) библиотечные функции, причем легко сделать их по умолчанию пригодными для повторного использования, так что сложность будет сокрыта от клиентского кода._ +Частично примененные функции - это очень полезный инструмент. Мы можем создать гибкие (хоть и сложные) библиотечные функции, причем легко сделать их по умолчанию пригодными для повторного использования, так что сложность будет сокрыта от клиентского кода. ## Designing functions for partial application | Проектирование функций для частичного применения ## @@ -209,7 +209,7 @@ let result = compositeOp [1..10] > The .NET base class library functions are easy to access in F#, but are not really designed for use with a functional language like F#. For example, most functions have the data parameter first, while with F#, as we have seen, the data parameter should normally come last. -Функции библиотеки базовых классов (base class library - BCL) .NET легко доступны из F#, но они не особенно расчитаны на использование в функциональных языках, таких как F#. Например, большинство функций требует параметр данных вначале, в то время как в F# параметр данных в общем случае должен быть последним. +Функции библиотеки базовых классов (base class library - BCL) .NET легко доступны из F#, но они не совсем расчитаны на использование в функциональных языках, таких как F#. Например, большинство функций требует параметр данных вначале, в то время как в F# параметр данных в общем случае должен быть последним. > However, it is easy enough to create wrappers for them that are more idiomatic. For example, in the snippet below, the .NET string functions are rewritten to have the string target be the last parameter rather than the first: @@ -310,7 +310,7 @@ let (<|) f x = f x > It seems that this function doesn't really do anything different from normal, so why does it exist? -Кажется, что эта функция в действительности не делает ничего особенного, так зачем же она нужна? +Кажется, что эта функция ничего особенного не делает, так зачем же она нужна? > The reason is that, when used in the infix style as a binary operator, it reduces the need for parentheses and can make the code cleaner. From 0426203e5f84f85a35eeb09dfbfb0212b2574adb Mon Sep 17 00:00:00 2001 From: Anna Udovichenko Date: Fri, 16 Feb 2018 13:29:02 +0300 Subject: [PATCH 09/48] Small edits according to comments --- posts/currying.md | 6 +++--- posts/partial-application.md | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/posts/currying.md b/posts/currying.md index 73a4d24..e5d7acf 100644 --- a/posts/currying.md +++ b/posts/currying.md @@ -10,11 +10,11 @@ categories: [Currying] > After that little digression on basic types, we can turn back to functions again, and in particular the puzzle we mentioned earlier: if a mathematical function can only have one parameter, then how is it possible that an F# function can have more than one? -После небольшого экскурса в базовые типы, мы можем снова вернуться к функциям, в частности, к ранее упомянутой загадке: если математическая функция может принимать только один параметр, то как в F# может существовать функция принимающая большее число параметров? +После небольшого экскурса в базовые типы мы можем снова вернуться к функциям, в частности, к ранее упомянутой загадке: если математическая функция может принимать только один параметр, то как в F# может существовать функция, принимающая большее число параметров? > The answer is quite simple: a function with multiple parameters is rewritten as a series of new functions, each with only one parameter. And this is done automatically by the compiler for you. It is called "**currying**", after Haskell Curry, a mathematician who was an important influence on the development of functional programming. -Ответ довольно прост: функция с несколькими параметрами переписывается как серия новых функций, каждая из которых принимает только один параметр. Эта операция производится компилятором автоматически. Это называется "**каррированием**" (_currying_), в честь Хаскела Карри, математика, который существенно повлиял на разработку функционального программирования. +Ответ довольно прост: функция с несколькими параметрами переписывается как серия новых функций, каждая из которых принимает только один параметр. Эту операцию компилятор выполняет автоматически, и называется она "**каррирование**" (_currying_), в честь Хаскела Карри, математика, который существенно повлиял на разработку функционального программирования. > To see how this works in practice, let's use a very basic example that prints two numbers: @@ -40,7 +40,7 @@ let printTwoParameters x = // only one parameter! > Let's examine this in more detail: -Рассмотрм этот процесс подробнее: +Рассмотрим этот процесс подробнее: > 1. Construct the function called "`printTwoParameters`" but with only *one* parameter: "x" > 2. Inside that, construct a subfunction that has only *one* parameter: "y". Note that this inner function uses the "x" parameter but x is not passed to it explicitly as a parameter. The "x" parameter is in scope, so the inner function can see it and use it without needing it to be passed in. diff --git a/posts/partial-application.md b/posts/partial-application.md index 09495eb..ee3bf12 100644 --- a/posts/partial-application.md +++ b/posts/partial-application.md @@ -209,7 +209,7 @@ let result = compositeOp [1..10] > The .NET base class library functions are easy to access in F#, but are not really designed for use with a functional language like F#. For example, most functions have the data parameter first, while with F#, as we have seen, the data parameter should normally come last. -Функции библиотеки базовых классов (base class library - BCL) .NET легко доступны из F#, но они не совсем расчитаны на использование в функциональных языках, таких как F#. Например, большинство функций требует параметр данных вначале, в то время как в F# параметр данных в общем случае должен быть последним. +Функции библиотеки базовых классов (base class library - BCL) .NET легко доступны из F#, но они спроектированы без расчёта на использование в функциональных языках, таких как F#. Например, большинство функций требует параметр данных вначале, в то время как в F# параметр данных в общем случае должен быть последним. > However, it is easy enough to create wrappers for them that are more idiomatic. For example, in the snippet below, the .NET string functions are rewritten to have the string target be the last parameter rather than the first: @@ -273,7 +273,9 @@ doSomething 1 2 3 // all parameters after function > If the function has multiple parameters, then it appears that the input is the final parameter. Actually what is happening is that the function is partially applied, returning a function that has a single parameter: the input _TODO: Перевести на русский._ -???? Если функция принимает несколько параметров, то она выглядит так, будто входной параметр - последний. На самом деле функция применяется частично и возвращает функцию, которая принимает единственный параметр - input. ??? +_Если функция имеет несколько параметров, то оказывается, что ввод - это последний параметр. В действительности частично примененная функция возвращает функцию, которая имеет один параметр - ввод._ + +???? _Если функция принимает несколько параметров, то она выглядит так, будто входной параметр - последний. На самом деле функция применяется частично и возвращает функцию, которая принимает единственный параметр - input._ ??? > Here's the same example rewritten to use partial application @@ -310,7 +312,7 @@ let (<|) f x = f x > It seems that this function doesn't really do anything different from normal, so why does it exist? -Кажется, что эта функция ничего особенного не делает, так зачем же она нужна? +Кажется, что эта функция ничего не делает, так зачем же она нужна? > The reason is that, when used in the infix style as a binary operator, it reduces the need for parentheses and can make the code cleaner. From 6112050bd7f7e070ccb2b16482ff8b86cd920c86 Mon Sep 17 00:00:00 2001 From: FoggyFinder Date: Fri, 16 Feb 2018 23:16:16 +0200 Subject: [PATCH 10/48] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D0=BF=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D1=82=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/stack-based-calculator.md | 34 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/posts/stack-based-calculator.md b/posts/stack-based-calculator.md index f3f2d1d..389784b 100644 --- a/posts/stack-based-calculator.md +++ b/posts/stack-based-calculator.md @@ -10,11 +10,11 @@ categories: [Combinators, Functions, Worked Examples] > In this post, we'll implement a simple stack based calculator (also known as "reverse Polish" style). The implementation is almost entirely done with functions, with only one special type and no pattern matching at all, so it is a great testing ground for the concepts introduced in this series. -В данной статье мы реализуем простой основанный на стеке калькулятор (также известный как "обратный польский" стиль). Реализация почти полностью построена на функциях, лишь с одним специальным типом и без сопоставления шаблонов вообще, это превосходный полигон для концпеций затронутых в данной серии. +В данной статье мы реализуем простой основанный на стеке калькулятор (также известный как "обратный польский" стиль). Реализация почти полностью построена на функциях, лишь с одним специальным типом и без сопоставления шаблонов вообще, это превосходный полигон для концепций затронутых в данной серии. > If you are not familiar with a stack based calculator, it works as follows: numbers are pushed on a stack, and operations such as addition and multiplication pop numbers off the stack and push the result back on. -Если вы незнакомы с подобным калькулятором, то он работает следующим образом: числа помещаются в стек, а операции, такие как сложение и произведение числа забирают числа с вершины стека, после чего помещают обратно полученнный результат операции. +Если вы незнакомы с подобным калькулятором, то он работает следующим образом: числа помещаются в стек, а операции, такие как сложение и произведение числа забирают числа с вершины стека, после чего помещают обратно полученный результат операции. > Here is a diagram showing a simple calculation using a stack: @@ -24,7 +24,7 @@ categories: [Combinators, Functions, Worked Examples] > The first steps to designing a system like this is to think about how it would be used. Following a Forth like syntax, we will give each action a label, so that the example above might want to be written something like: -Прежде чем проектировать подобную систему, следует порассуждать над тем, как она будет использоваться. Следуя подобному Forth синтаксису, дадим каждому действию соответсвующую метку, чтобы в приведенном выше примере можно было написать нечто вроде: +Прежде чем проектировать подобную систему, следует порассуждать над тем, как она будет использоваться. Следуя подобному Forth синтаксису, дадим каждому действию соответствующую метку, чтобы в приведенном выше примере можно было написать нечто вроде: EMPTY ONE THREE ADD TWO MUL SHOW @@ -36,7 +36,7 @@ categories: [Combinators, Functions, Worked Examples] > First we need to define the data structure for a stack. To keep things simple, we'll just use a list of floats. -Во первых надо определить структуру данных для стека. Это просто, для этих целей можно использовать список float-ов. +Во первых надо определить структуру данных для стека. Это просто, для этих целей можно использовать список чисел с плавающей точкой. ```fsharp type Stack = float list @@ -97,7 +97,7 @@ let push x aStack = > First, note that the list structure is immutable, so the function must accept an existing stack and return a new stack. It cannot just alter the existing stack. In fact, all of the functions in this example will have a similar format like this: -Во первых, следует обратить внимание, что структура `list` неизменяема, значит функция должна принимать существующий стек и возвращать новый. Это не просто изменение существующего стека. По факту, все функции в данном примере будут иметь подобный формат: +Во первых, следует обратить внимание, что структура `list` неизменяемая, значит функция должна принимать существующий стек и возвращать новый. Это не просто изменение существующего стека. По факту, все функции в данном примере будут иметь подобный формат: > Input: a Stack plus other parameters > Output: a new Stack @@ -107,7 +107,7 @@ let push x aStack = > Next, what should the order of the parameters be? Should the stack parameter come first or last? If you remember the discussion of [designing functions for partial application](../posts/partial-application), you will remember that the most changeable thing should come last. You'll see shortly that this guideline will be born out. -Во вторых, почему параметры идут именно в таком порядке? Почему стек должен идти первым или последним? В обсуждении [проектирование функций с частичным применением](../posts/partial-application) говорилось, что ниболее часто меняющийся параметр должен идти последним. Вскоре можно будет убедиться, что данне рекомендации соблюдаются. +Во вторых, почему параметры идут именно в таком порядке? Почему стек должен идти первым или последним? В обсуждении [проектирование функций с частичным применением](../posts/partial-application) говорилось, что наиболее часто меняющийся параметр должен идти последним. Вскоре можно будет убедиться, что данные рекомендации соблюдаются. > Finally, the function can be made more concise by using pattern matching in the function parameter itself, rather than using a `let` in the body of the function. @@ -128,7 +128,7 @@ let push x (StackContents contents) = > And by the way, look at the nice signature it has: -Между прочим, посмотретите на ее изящную сигнатуру: +Между прочим, посмотрите на ее изящную сигнатуру: ```fsharp val push : float -> Stack -> Stack @@ -140,7 +140,7 @@ val push : float -> Stack -> Stack Как говорилось [ранее](../posts/function-signatures), сигнатура говорит нам, очень много о функции. В данном случае, я мог бы догадаться, что делает данная функция лишь по ее сигнатуре, не зная, что она называется "push". -Это еще одна причина по которой было хорошей идеей иметь явные имена типа. Если бы стек был лишь списком float-ов, функция не была бы столь самодокументированна. +Это еще одна причина по которой было хорошей идеей иметь явные имена типа. Если бы стек был лишь списком чисел с плавающей точкой, то функция не была бы столь само-документированной. > Anyway, now let's test it: @@ -193,7 +193,7 @@ let EMPTY = StackContents [] > Let's test all of these now: -Проверим получнные функции: +Проверим полученные функции: ```fsharp let stackWith1 = ONE EMPTY @@ -211,7 +211,7 @@ Stack -> Stack > This means that they can be chained together nicely! The output of one can be fed into the input of the next, as shown below: -А значит, они прекрасно соеденяются вместе! Вывод одной функции может быть подан на вход следующей: +А значит, они прекрасно соединяются вместе! Вывод одной функции может быть подан на вход следующей: ```fsharp let result123 = EMPTY |> ONE |> TWO |> THREE @@ -236,7 +236,7 @@ let result312 = EMPTY |> THREE |> ONE |> TWO > But in a functional style, the stack is immutable. The only way to remove the top element is to create a *new stack* with the element removed. > In order for the caller to have access to this new diminished stack, it needs to be returned along with the top element itself. -Однако в функциональном стиле стек неизменям. Есть только один способ удалить верхний элемент - создать _новый стек_ без удаленного элемента. Для того, чтобы вызывающий объект имел доступ к новому уменьшенному стеку, его необходимо вернуть вместе с верхним элементом. +Однако в функциональном стиле стек неизменяем. Есть только один способ удалить верхний элемент - создать _новый стек_ без удаленного элемента. Для того, чтобы вызывающий объект имел доступ к новому уменьшенному стеку, его необходимо вернуть вместе с верхним элементом. > In other words, the `pop` function will have to return *two* values, the top plus the new stack. The easiest way to do this in F# is just to use a tuple. @@ -446,7 +446,7 @@ let unary f stack = > And then define some unary functions: -И определить несколько унарных функци: +И определить несколько унарных функций: ```fsharp let NEG = unary (fun x -> -x) @@ -466,7 +466,7 @@ let square2 = EMPTY |> TWO |> SQUARE > In the original requirements, we mentioned that we wanted to be able to show the results, so let's define a SHOW function. -В изначальных требования упоминалось, что мы хотели показать результаты, поэтому стоит опредлеить функцию SHOW. +В изначальных требования упоминалось, что мы хотели показать результаты, поэтому стоит определить функцию SHOW. ```fsharp let SHOW stack = @@ -552,7 +552,7 @@ Stack -> Stack > So, because the input and output types are the same, these functions can be composed using the composition operator `>>`, not just chained together with pipes. -Т.к. ввод и вывод имеют одинаковые типы, эти функции могут быть скомпанованы еще и при помощи оператора `>>`, не только посредством pipe-ов. +Т.к. ввод и вывод имеют одинаковые типы, эти функции могут быть скомпонованы еще и при помощи `>>`, а не только посредством конвейерных операторов. > Here are some examples: @@ -603,7 +603,7 @@ START |> THREE |> SQUARE |> SUM_NUMBERS_UPTO |> SHOW > The difference is that piping is, in a sense, a "realtime transformation" operation. When you use piping you are actually doing the operations right now, passing a particular stack around. -Разница в том, что pipe-ы в некотором смысле являются операцией "в реальном времени". В момент использования конвеера операции выполняются прямо сейчас, через передачу определнного стека. +Разница в том, что pipe-ы в некотором смысле являются операцией "в реальном времени". В момент использования конвейера операции выполняются прямо сейчас, через передачу определенного стека. > On the other hand, composition is a kind of "plan" for what you want to do, building an overall function from a set of parts, but *not* actually running it yet. @@ -619,7 +619,7 @@ let COMPOSED_SQUARE = DUP >> MUL > I cannot do the equivalent with the piping approach. -Я не могу привести эквивалент на основе конвееров. +Я не могу привести эквивалент на основе конвейеров. ```fsharp let PIPED_SQUARE = DUP |> MUL @@ -784,7 +784,7 @@ let SUM_NUMBERS_UPTO = > As you might guess, this example is based heavily on the Forth language. I highly recommend the free book ["Thinking Forth"](http://thinking-forth.sourceforge.net/), which is not just about the Forth language, but about (*non* object-oriented!) problem decomposition techniques which are equally applicable to functional programming. -Как можно догадаться, данный пример был в изрядной степени основан на языке Forth. Я очень рекомендую бесплатную книгу ["Thinking Forth"](http://thinking-forth.sourceforge.net/), которая повествует не только об языке Forth, но и об других (_не_ объектно ориентированных!) методах декомпозиции задач, которые одинакого применимы к функциональному программированию в целом. +Как можно догадаться, данный пример был в изрядной степени основан на языке Forth. Я очень рекомендую бесплатную книгу ["Thinking Forth"](http://thinking-forth.sourceforge.net/), которая повествует не только об языке Forth, но и об других (_не_ объектно ориентированных!) методах декомпозиции задач, которые одинаково применимы к функциональному программированию в целом. > I got the idea for this post from a great blog by [Ashley Feniello](http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx). If you want to go deeper into emulating a stack based language in F#, start there. Have fun! From 7afa3bdc3db5dfb09798b6bb7e8926cdc250bfb5 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Tue, 27 Feb 2018 23:05:25 +0700 Subject: [PATCH 11/48] Some textual fixes for thinking-functional-intro --- posts/thinking-functionally-intro.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/posts/thinking-functionally-intro.md b/posts/thinking-functionally-intro.md index 4eb21e1..dfd2dd4 100644 --- a/posts/thinking-functionally-intro.md +++ b/posts/thinking-functionally-intro.md @@ -9,36 +9,36 @@ seriesOrder: 1 > Now that you have seen some of the power of F# in the ["why use F#"](../series/why-use-fsharp.md) series, we're going to step back and look at the fundamentals of functional programming -- what does it really mean to "program functionally", and how this approach is different from object oriented or imperative programming. -Теперь, когда мы увидели часть мощи F# в серии ["why use F#"](../series/why-use-fsharp.md), стоит сделать шаг назад и взглянуть на основы функционального программирования. Что в действительности означает "программировать функционально", и как этот подход отличается от объектно-ориентированного или императивного программирования. +Теперь, когда читатель увидел некоторые из причин, по которым стоит использовать F#, в серии ["why use F#"](../series/why-use-fsharp.md), сделаем шаг назад и обсудим основы функционального программирования. Что в действительности означает "программировать функционально", и как этот подход отличается от объектно-ориентированного или императивного программирования? ### Changing the way you think | Смена образа мышления ### -> It is important to understand that functional programming is not just a stylistic difference; it is a completely different way of thinking about programming, in the way that truly object-oriented programming (in Smalltalk say) is also a different way of thinking from a traditional imperative language such as C. +> It is important to understand that functional programming is not just a stylistic difference; it is a completely different way of thinking about programming, in the way that truly object-oriented programming (in Smalltalk say) is also a different way of thinking from a traditional imperative language such as C. -Важно понимать, что функциональное программирование - это не только стилистическое различие. Это совсем другой способ мышления _(о программировании)_, подобно тому как настоящее ООП (скажем Smalltalk) отличается от традиционного императивного языка такого как C. +Важно понимать, что функциональное программирование - это не просто отдельный стиль программирования. Это совсем другой способ рассуждения о программе, который отличается от "традиционного" подхода так же значительно, как настоящее ООП (в стиле Smalltalk) отличается от традиционного императивного языка - такого, как C. > F# does allow non-functional styles, and it is tempting to retain the habits you already are familiar with. You could just use F# in a non-functional way without really changing your mindset, and not realize what you are missing. To get the most out of F#, and to be fluent and comfortable with functional programming in general, it is critical that you think functionally, not imperatively. > And that is the goal of this series: to help you understand functional programming in a deep way, and help to change the way you think. -F# позволяет использовать нефункциональные стили, что искушает сохранить существующие привычки. Вы можете просто использовать F# привычным образом без реального изменения мировоззрения и даже не представлять, что вы теряете. Однако, чтобы получить максимальную отдачу от F#, а также тепло и лампово программировать в функциональном стиле вообще, очень важно думать функционально, а не императивно. -Цель данной серии - помочь понять функциональное программирование _в глубокую сторону_ и изменить способ мышления читателя. +F# позволяет использовать нефункциональные стили кодирования, и это искушает программиста сохранить его существующие привычки. На F# вы можете программировать так, как привыкли, не меняя радикально мировоззрения, и даже не представляя, что при этом упускаете. Однако, чтобы получить от F# максимальную отдачу, а также научиться уверенно программировать в функциональном стиле вообще, очень важно научиться мыслить функционально, а не императивно. +Цель данной серии - помочь читателю понять подоплёку функционального программирования и изменить его способ мышления. > This will be a quite abstract series, although I will use lots of short code examples to demonstrate the points. We will cover the following points: Это будет довольно абстрактная серия, хотя я буду использовать множество коротких примеров кода для демонстрации некоторых моментов. Мы рассмотрим следующие темы: > * **Mathematical functions**. The first post introduces the mathematical ideas behind functional languages, and the benefits that come from this approach. -> * **Functions and values**. The next post introduces functions and values, showing how "values" are different from variables, and why there are similarities between function and simple values. +> * **Functions and values**. The next post introduces functions and values, showing how "values" are different from variables, and why there are similarities between function and simple values. > * **Types**. Then we move on to the basic types that work with functions: primitive types such as string and int; the unit type, function types, and generic types. > * **Functions with multiple parameters**. Next, I explain the concepts of "currying" and "partial application". This is where your brain can start to hurt, if you are coming from an imperative background! > * **Defining functions**. Then some posts devoted to the many different ways to define and combine functions. > * **Function signatures**. Then a important post on the critical topic of function signatures: what they mean and how to use them as an aid to understanding. > * **Organizing functions**. Once you know how to create functions, how can you organize them to make them available to the rest of your code? -* **Математические функции**. Первая статья знакомит с математическими представлениями лежащими в основе функциональных языков и преимуществами, которые приносит данный подход. -* **Функции и значения**. Следующая знакомит с функциями и значениями, объясняет чем "значения" отличаются от переменных, и почему есть сходства между функциями и простыми значениями. -* **Типы**. Затем мы перейдем к основным типам, которые работают с функциаями: примитивные типы, такие как string и int, тип unit, функциональные типы, и типы обобщений (generic). +* **Математические функции**. Первая статья знакомит с математическими представлениями, лежащими в основе функциональных языков и преимуществами, которые приносит данный подход. +* **Функции и значения**. Следующая знакомит с функциями и значениями, объясняет чем "значения" отличаются от переменных, и какие есть сходства между функциями и простыми значениями. +* **Типы**. Затем мы перейдем к основным типам, которые работают с функциаями: примитивные типы, такие как string и int, тип unit, функциональные типы, и обобщённые типы (generic). * **Функции с несколькими параметрами**. Далее я объясню понятия "каррирования" и "частичного применения". В этом месте чьим-то мозгам будет больно, особенно если у этих мозгов только императивное прошлое. -* **Определение функций**. Затем несколько постов будут посвящены множеству различных способов определения и комбинации функций. -* **Сигнатуры функций**. Затем будет важный пост о критическом значении сигнатур функций, что они значат и как использовать их для _постижения_ их содержимого. -* **Организация функций**. Когда станет понятно как создавать функции, возникнет вопрос как организовать их, чтобы сделать их доступными для остальной части кода? +* **Определение функций**. Затем несколько постов будут посвящены множеству различных способов определения и комбинирования функций. +* **Сигнатуры функций**. Далее будет важный пост о критическом значении сигнатур функций, что они значат, и как использовать сигнатуры для понимания содержимого функций. +* **Организация функций**. Когда станет понятно, как создавать функции, возникнет вопрос: как можно их организовать, чтобы сделать доступными для остальной части кода? From cb04b306fa3bb4405f2295fb62a9f63781f6de16 Mon Sep 17 00:00:00 2001 From: FoggyFinder Date: Mon, 26 Mar 2018 11:02:48 +0300 Subject: [PATCH 12/48] fix typos 1 --- posts/currying.md | 16 +++++------ posts/defining-functions.md | 42 ++++++++++++++-------------- posts/thinking-functionally-intro.md | 2 +- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/posts/currying.md b/posts/currying.md index e5d7acf..8676b15 100644 --- a/posts/currying.md +++ b/posts/currying.md @@ -54,7 +54,7 @@ let printTwoParameters x = // only one parameter! > By rewriting it this way, the compiler has ensured that every function has only one parameter, as required. So when you use "`printTwoParameters`", you might think that you are using a two parameter function, but it is actually only a one parameter function! You can see for yourself by passing only one argument instead of two: -Переписывая функции таким образом, комплиятор гарантирует, что каждая функция принимает только один параметр, как и требовалось. Таким образом, используя "`printTwoParameters`", можно подумать, что это функция с двумя параметрами, но на самом деле используется функция с только одним параметром. В этом можно убедиться, передав ей лишь один аргумент вместо двух: +Переписывая функции таким образом, компилятор гарантирует, что каждая функция принимает только один параметр, как и требовалось. Таким образом, используя "`printTwoParameters`", можно подумать, что это функция с двумя параметрами, но на самом деле используется функция с только одним параметром. В этом можно убедиться, передав ей лишь один аргумент вместо двух: ```fsharp // eval with one argument @@ -76,9 +76,9 @@ val it : (int -> unit) = > * `printTwoParameters` returns a new function that has "x" baked into it. > * You then call the new function with the second argument (y) -* Вызывается `printTwoParameters` с первым аргуметом (x) +* Вызывается `printTwoParameters` с первым аргументом (x) * `printTwoParameters` возвращает новую функцию, в которой замкнут "x". -* Затем вызывается новая функция со вторым аргуметом (y) +* Затем вызывается новая функция со вторым аргументом (y) > Here is an example of the step by step version, and then the normal version again. @@ -131,7 +131,7 @@ let result = addTwoParameters x y > But wait a minute -- what about the "`+`" operation itself? It's a binary operation that must take two parameters, surely? No, it is curried like every other function. There is a function called "`+`" that takes one parameter and returns a new intermediate function, exactly like `addTwoParameters` above. -Но подождите, а что с операторором "`+`"? Это ведь бинарная операция, которая должна принимать два параметра? Нет, она тоже каррируется, как и другие функции. Это функция с именем "`+`", которая принимает один параметр и возвращает новую промежуточную функцию, в точности как `addTwoParameters` выше. +Но подождите, а что с оператором "`+`"? Это ведь бинарная операция, которая должна принимать два параметра? Нет, она тоже каррируется, как и другие функции. Это функция с именем "`+`", которая принимает один параметр и возвращает новую промежуточную функцию, в точности как `addTwoParameters` выше. > When we write the statement `x+y`, the compiler reorders the code to remove the infix and turns it into `(+) x y`, which is the function named `+` called with two parameters. Note that the function named "+" needs to have parentheses around it to indicate that it is being used as a normal function name rather than as an infix operator. @@ -183,7 +183,7 @@ let result = intermediateFn 5 > Going back to the first example, "`printTwoParameters`", we saw that it took one argument and returned an intermediate function. The intermediate function also took one argument and returned nothing (that is, unit). So the intermediate function has type `int->unit`. In other words, the domain of `printTwoParameters` is `int` and the range is `int->unit`. Putting this together we see that the final signature is: -Возращаясь к первому примеру, "`printTwoParameter`", мы видели, что функция принимала один рагумент и возвращала промежуточную функцию. Промежуточная функция также принимала один аргумент и ничего не возврашала (т.е. `unit`). Поэтому промежуточная функция имела тип `int->unit`. Другими словами, domain `printTwoParameters` - это `int`, а range - `int->unit`. Собрав все это воедино мы увидим конечную сигнатуру: +Возвращаясь к первому примеру, "`printTwoParameter`", мы видели, что функция принимала один аргумент и возвращала промежуточную функцию. Промежуточная функция также принимала один аргумент и ничего не возвращала (т.е. `unit`). Поэтому промежуточная функция имела тип `int->unit`. Другими словами, domain `printTwoParameters` - это `int`, а range - `int->unit`. Собрав все это воедино мы увидим конечную сигнатуру: ```fsharp val printTwoParameters : int -> (int -> unit) @@ -311,7 +311,7 @@ let printHello() = printfn "hello" > What would you expect to happen when we call it as shown below? Will it print "hello" to the console? Try to guess before evaluating it, and here's a hint: be sure to take a look at the function signature. -Как думаете, что произойдет, если вызвать ее, как показано ниже? Выведится ли "hello" на консоль? Попробуйте догадаться до выполнения. Подсказка: посмотрите на сигнатуру функции. +Как думаете, что произойдет, если вызвать ее, как показано ниже? Выведется ли "hello" на консоль? Попробуйте догадаться до выполнения. Подсказка: посмотрите на сигнатуру функции. ```fsharp // call it @@ -345,11 +345,11 @@ printfn "x=%i y=%i" x > If you didn't understand currying, this message would be very cryptic! All expressions that are evaluated standalone like this (i.e. not used as a return value or bound to something with "let") *must* evaluate to the unit value. And in this case, it is does *not* evaluate to the unit value, but instead evaluates to a function. This is a long winded way of saying that `printfn` is missing an argument. -Если нет понимания каррирования, данное сообщение может быть очень загадочным. Дело в том, что все выражения, которые вычисляются отдельно, как это (т.е. не используются как возвращаемое значение или привязка к чему-либо посредством "let") _должны_ вычисляться в `unit` значение. В данном случае, оно _не_ вычисляется в `unit` значение, но вместо этого возвращает функцию. Это длинный извилистный способ сказать, что `printfn` не хватает аргумента. +Если нет понимания каррирования, данное сообщение может быть очень загадочным. Дело в том, что все выражения, которые вычисляются отдельно, как это (т.е. не используются как возвращаемое значение или привязка к чему-либо посредством "let") _должны_ вычисляться в `unit` значение. В данном случае, оно _не_ вычисляется в `unit` значение, но вместо этого возвращает функцию. Это длинный извилистый способ сказать, что `printfn` не хватает аргумента. > A common case of errors like this is when interfacing with the .NET library. For example, the `ReadLine` method of a `TextReader` must take a unit parameter. It is often easy to forget this and leave off the parens, in which case you do not get a compiler error immediately, but only when you try to treat the result as a string. -В большинстве случаев ошибки, подобные этой, случаются при взаимодейтвии с библиотекой из мира .NET. Например, метод `Readline` класса `TextReader` должент принимать `unit` параметр. Об этом часто можно забыть, и не поставить скобки, в этом случае нельзя получить ошибку компилятора в момент "вызова", но она появится при попытке интерпретировать результат как строку. +В большинстве случаев ошибки, подобные этой, случаются при взаимодействии с библиотекой из мира .NET. Например, метод `Readline` класса `TextReader` должен принимать `unit` параметр. Об этом часто можно забыть, и не поставить скобки, в этом случае нельзя получить ошибку компилятора в момент "вызова", но она появится при попытке интерпретировать результат как строку. ```fsharp let reader = new System.IO.StringReader("hello"); diff --git a/posts/defining-functions.md b/posts/defining-functions.md index b061ef8..1e5a8a3 100644 --- a/posts/defining-functions.md +++ b/posts/defining-functions.md @@ -21,11 +21,11 @@ let add x y = x + y В этой статье мы рассмотрим некоторые другие способы создания функций, а также советы по их определению. -## Anonymous functions (a.k.a. lambdas) | Анонимные функцие (лямбды) ## +## Anonymous functions (a.k.a. lambdas) | Анонимные функции (лямбды) ## > If you are familiar with lambdas in other languages, this will not be new to you. An anonymous function (or "lambda expression") is defined using the form: -Если вы знакомы с лямбдами в других языках, эта темя не станет новой. Анонимные функции (или "лямбда выражения") опредеяются посредство следующей формы: +Если вы знакомы с лямбдами в других языках, эта тема не станет новой. Анонимные функции (или "лямбда выражения") определяются посредством следующей формы: ```fsharp fun parameter1 parameter2 etc -> expression @@ -38,7 +38,7 @@ fun parameter1 parameter2 etc -> expression > * the lambda must have the special keyword `fun`, which is not needed in the C# version > * the arrow symbol is a single arrow `->` rather than the double arrow (`=>`) in C#. -* лямбды должны иметь специальное ключенове слово `fun`, которое отстутствует в C# +* лямбды должны иметь специальное ключевое слово `fun`, которое отсутствует в C# * используется одинарная стрелка `->`, вместо двойной `=>` из C#. > Here is a lambda that defines addition: @@ -51,7 +51,7 @@ let add = fun x y -> x + y > This is exactly the same as a more conventional function definition: -Таже функция в традиционной форме: +Та же функция в традиционной форме: ```fsharp let add x y = x + y @@ -88,7 +88,7 @@ let adderGenerator x = fun y -> x + y > The lambda version is slightly longer, but makes it clear that an intermediate function is being returned. -Лямбда версия немного длиньше, но позволяет сразу понять, что будет возвращена промежуточная функция. +Лямбда версия немного длиннее, но позволяет сразу понять, что будет возвращена промежуточная функция. > You can nest lambdas as well. Here is yet another definition of `adderGenerator`, this time using lambdas only. @@ -142,7 +142,7 @@ f2 bob > This kind of matching can only occur when the matching is always possible. For example, you cannot match on union types or lists this way, because some cases might not be matched. -Данный вид сопоставления может присходить только тогда, когда соответсвие всегда разрешимо. Например, нельзя подобным способом матчить типы объединения и списки, потому-что некоторые случаи не могут быть сопоставлены. +Данный вид сопоставления может происходить только тогда, когда соответствие всегда разрешимо. Например, нельзя подобным способом матчить типы объединения и списки, потому-что некоторые случаи не могут быть сопоставлены. ```fsharp let f3 (x::xs) = // use pattern matching on a list @@ -159,7 +159,7 @@ let f3 (x::xs) = // use pattern matching on a list > If you come from a C-like language, a tuple used as a single function parameter can look awfully like multiple parameters. They are not the same thing at all! As I noted earlier, if you see a comma, it is probably part of a tuple. Parameters are separated by spaces. -Если вы пришли из C-подобного языка, кортежи использованные в качестве однопараметрической функции может выглядеть так же ужасно как и мултипараметрическая функция. Но это не одно и то же! Как я отметил ранее, если вы видете запятую, скорее всего это кортеж. Параметры же разделяются посредством запятой. +Если вы пришли из C-подобного языка, кортежи использованные в качестве однопараметрической функции может выглядеть так же ужасно как и мультипараметрическая функция. Но это не одно и то же! Как я отметил ранее, если вы видите запятую, скорее всего это кортеж. Параметры же разделяются посредством запятой. > Here is an example of the confusion: @@ -253,7 +253,7 @@ addConfusingTuple 1 2 // error trying to pass two args > The discussion of the issues with tuples above shows that there's another way to define functions with more than one parameter: rather than passing them in separately, all the parameters can be combined into a single composite data structure. In the example below, the function takes a single parameter, which is a tuple containing three items. -Обсуждение кортежей выше показывает, что существует другой способ опредления функций со множеством параметров: вместо передачи их по отдельности, все параметры могут быть собраны в виде одной структуры. В примере ниже, функция принимает один параметр, который является кортежем из трех элментов. +Обсуждение кортежей выше показывает, что существует другой способ определения функций со множеством параметров: вместо передачи их по отдельности, все параметры могут быть собраны в виде одной структуры. В примере ниже, функция принимает один параметр, который является кортежем из трех элементов. ```fsharp let f (x,y,z) = x + y * z @@ -265,7 +265,7 @@ f (1,2,3) > Note that the function signature is different from a true three parameter function. There is only one arrow, so only one parameter, and the stars indicate that this is a tuple of `(int*int*int)`. -Следует обратить внимание, что сигнатура отличается от сигнутуры функции с тремя параметрами. Здесь только одна стрелка, один параметр и звездочки указывающие на кортеж `(int*int*int)`. +Следует обратить внимание, что сигнатура отличается от сигнатуры функции с тремя параметрами. Здесь только одна стрелка, один параметр и звездочки указывающие на кортеж `(int*int*int)`. > When would we want to use tuple parameters instead of individual ones? @@ -275,7 +275,7 @@ f (1,2,3) > * Tuples are occasionally used to bundle data together in a single structure that should be kept together. For example, the `TryParse` functions in .NET library return the result and a Boolean as a tuple. But if you have a lot of data that is kept together as a bundle, then you will probably want to define a record or class type to store it. * Когда кортежи значимы сами по себе. Например, если производятся операции над трехмерными координатами, тройные кортежи могут быть более удобными чем три отдельных измерения. -* Кортежи иногда используются, чтобы объеденить данные в единую структуру, которая должна сохраняться вместе. Например, `TryParse` методы из .NET библиотеки возвращают результат и булевую переменную в виде кортежа. Но если имеется достаточно большой объем данных передаваемых в связке, скорее всего он будет опредлен в виде записи или класса. +* Кортежи иногда используются, чтобы объединить данные в единую структуру, которая должна сохраняться вместе. Например, `TryParse` методы из .NET библиотеки возвращают результат и булевую переменную в виде кортежа. Но если имеется достаточно большой объем данных передаваемых в связке, скорее всего он будет определен в виде записи или класса. ### A special case: tuples and .NET library functions | Особые случай: кортежи и функции .NET библиотеки ### @@ -297,11 +297,11 @@ System.String.Compare "a" "b" > The reason is that .NET library functions are not curried and cannot be partially applied. *All* the parameters must *always* be passed in, and using a tuple-like approach is the obvious way to do this. -Причина этого кроется в том, что функции классического .NET не каррируемы и не могут быть частично применены. _Все_ парамерты _всегда_ должны быть передаваться сразу, и использование кортеже-подобной передачи самый очевидный способ сделать это. +Причина этого кроется в том, что функции классического .NET не каррируемы и не могут быть частично применены. _Все_ параметры _всегда_ должны быть передаваться сразу, и использование кортеже-подобной передачи самый очевидный способ сделать это. > But do note that although these calls look like tuples, they are actually a special case. Real tuples cannot be used, so the following code is invalid: -Однако следует заметить, что данные вызовы лишь выглядят как передачи кортежей, на самом деле это особы случай. В действительности кортежи не могут быть использованы, и следующий код невалиден: +Однако следует заметить, что данные вызовы лишь выглядят как передачи кортежей, на самом деле это особый случай. В действительности кортежи не могут быть использованы, и следующий код невалиден: ```fsharp let tuple = ("a","b") @@ -378,7 +378,7 @@ let setCustomerName myCredentials aName = //good > Finally, do be sure to order the parameters appropriately to assist with partial application (see the guidelines in the earlier [post](../posts/partial-application.md)). For example, in the last function above, why did I put the `myCredentials` parameter ahead of the `aName` parameter? -Нанонец, убедитесь, что порядок параметров поможет в частичном применении (смотрите руководство [здесь](../posts/partial-application.md)). Например, в почему я поместил `myCredentials` перед `aName` в последней функции? +Наконец, убедитесь, что порядок параметров поможет в частичном применении (смотрите руководство [здесь](../posts/partial-application.md)). Например, в почему я поместил `myCredentials` перед `aName` в последней функции? ## Parameter-less functions | Функции без параметров ## @@ -455,7 +455,7 @@ let result = (.*%) 2 3 > If the function has exactly two parameters, you can use it as an infix operator without parentheses. -Если фунция используется с двумя параметрами, можно использовать инфиксную операторную запись без скобок. +Если функция используется с двумя параметрами, можно использовать инфиксную операторную запись без скобок. ```fsharp let result = 2 .*% 3 @@ -474,7 +474,7 @@ let result = %% "hello" > In F# it is quite common to create your own operators, and many libraries will export operators with names such as `>=>` and `<*>`. -В F# определние операторов достаточно частая операция, и многие библиотеки будут экспортировать операторы с именами типа `>=>`и `<*>`. +В F# определение операторов достаточно частая операция, и многие библиотеки будут экспортировать операторы с именами типа `>=>`и `<*>`. ## Point-free style | Point-free стиль ## @@ -507,7 +507,7 @@ let sum = List.reduce (+) // point free > Point-free helps to clarify the underlying algorithm and reveal commonalities between code -- the "`reduce`" function used above is a good example of this -- it will be discussed in a planned series on list processing. -Безточечный стиль позволяет сосредоточиться на базовом алгоритме и выявить общие черты в коде. "`reduce`" функция использованная выше является хорошим примером. Эта тема будет обсуждаться в запланированной серии по обработке списков. +Бесточечный стиль позволяет сосредоточиться на базовом алгоритме и выявить общие черты в коде. "`reduce`" функция использованная выше является хорошим примером. Эта тема будет обсуждаться в запланированной серии по обработке списков. > On the other hand, too much point-free style can make for confusing code. Explicit parameters can act as a form of documentation, and their names (such as "list") make it clear what the function is acting on. @@ -550,7 +550,7 @@ let (<<) g f x = g (f x) // reverse composition > To read more about combinators and combinatory logic, I recommend the book "To Mock a Mockingbird" by Raymond Smullyan. In it, he describes many other combinators and whimsically gives them names of birds. Here are some examples of some standard combinators and their bird names: -Чтобы узнать больше о комбинаторах и комбинаторной логики, я рекомендую книгу "To Mock a Mockingbird" Raymond-а Smullyan-а. В ней он объясняет другие комбинаторы и причудливо дает им названия птиц. Вот несколько примеров стандартных комбинаторов и их птичъих имен: +Чтобы узнать больше о комбинаторах и комбинаторной логики, я рекомендую книгу "To Mock a Mockingbird" Raymond-а Smullyan-а. В ней он объясняет другие комбинаторы и причудливо дает им названия птиц. Вот несколько примеров стандартных комбинаторов и их птичьих имен: ```fsharp let I x = x // identity function, or the Idiot bird @@ -573,13 +573,13 @@ let rec Y f x = f (Y f) x // Y-combinator, or Sage bird > Indeed, there is a well-known theorem that states that any computable function whatsoever can be built from just two basic combinators, the Kestrel and the Starling. -На самом деле, существует широко известная теорема, что любая вычислимая функция может быть построена при помощи лишь двух бызовых комбинаторов, Kestrel-а и Starling-а. +На самом деле, существует широко известная теорема, что любая вычислимая функция может быть построена при помощи лишь двух базовых комбинаторов, Kestrel-а и Starling-а. ### Combinator libraries | Библиотеки комбинаторов ### > A combinator library is a code library that exports a set of combinator functions that are designed to work together. The user of the library can then easily combine simple functions together to make bigger and more complex functions, like building with Lego. -Библиотеки кобинаторов - это библиотеки которые экспортируют множество комбинаторных функций, которые раработаны с учетом их совместного использования. Пользователь подобной библиотеки может легко комбинировать функции вместе, чтобы получить еще большие и сложные функции, как кубики лего. +Библиотеки комбинаторов - это библиотеки которые экспортируют множество комбинаторных функций, которые разработаны с учетом их совместного использования. Пользователь подобной библиотеки может легко комбинировать функции вместе, чтобы получить еще большие и сложные функции, как кубики легко. > A well designed combinator library allows you to focus on the high level operations, and push the low level "noise" to the background. We've already seen some examples of this power in the examples in ["why use F#"](../series/why-use-fsharp.md) series, and the `List` module is full of them -- the "`fold`" and "`map`" functions are also combinators, if you think about it. @@ -587,7 +587,7 @@ let rec Y f x = f (Y f) x // Y-combinator, or Sage bird > Another advantage of combinators is that they are the safest type of function. As they have no dependency on the outside world they cannot change if the global environment changes. A function that reads a global value or uses a library function can break or alter between calls if the context is different. This can never happen with combinators. -Другое преимущество комбинаторов - они являются самым безопасныи типом функций. Т.к. они не имеют зависимостей от внешнего мира, они не могут изменяться, при изменении глобальной среды. Функция которая читает глобальное значение или использует библиотченые функции, может сломаться или измениться между вызовами если контекст изменится. Этого никогда не произойдет с комбинаторами. +Другое преимущество комбинаторов - они являются самым безопасным типом функций. Т.к. они не имеют зависимостей от внешнего мира, они не могут изменяться, при изменении глобальной среды. Функция которая читает глобальное значение или использует библиотечные функции, может сломаться или измениться между вызовами если контекст изменится. Этого никогда не произойдет с комбинаторами. > In F#, combinator libraries are available for parsing (the FParsec library), HTML construction, testing frameworks, and more. We'll discuss and use combinators further in later series. @@ -597,7 +597,7 @@ let rec Y f x = f (Y f) x // Y-combinator, or Sage bird > Often, a function will need to refer to itself in its body. The classic example is the Fibonacci function: -Часто функции необходимо ссылаться на саму себя из ее тела. Классический пример - функция Фибоначи. +Часто функции необходимо ссылаться на саму себя из ее тела. Классический пример - функция Фибоначчи. ```fsharp let fib i = diff --git a/posts/thinking-functionally-intro.md b/posts/thinking-functionally-intro.md index dfd2dd4..1747033 100644 --- a/posts/thinking-functionally-intro.md +++ b/posts/thinking-functionally-intro.md @@ -37,7 +37,7 @@ F# позволяет использовать нефункциональные * **Математические функции**. Первая статья знакомит с математическими представлениями, лежащими в основе функциональных языков и преимуществами, которые приносит данный подход. * **Функции и значения**. Следующая знакомит с функциями и значениями, объясняет чем "значения" отличаются от переменных, и какие есть сходства между функциями и простыми значениями. -* **Типы**. Затем мы перейдем к основным типам, которые работают с функциаями: примитивные типы, такие как string и int, тип unit, функциональные типы, и обобщённые типы (generic). +* **Типы**. Затем мы перейдем к основным типам, которые работают с функциями: примитивные типы, такие как string и int, тип unit, функциональные типы, и обобщённые типы (generic). * **Функции с несколькими параметрами**. Далее я объясню понятия "каррирования" и "частичного применения". В этом месте чьим-то мозгам будет больно, особенно если у этих мозгов только императивное прошлое. * **Определение функций**. Затем несколько постов будут посвящены множеству различных способов определения и комбинирования функций. * **Сигнатуры функций**. Далее будет важный пост о критическом значении сигнатур функций, что они значат, и как использовать сигнатуры для понимания содержимого функций. From e65bd0e375cb9830aff8b4fe199eca061cbba6fc Mon Sep 17 00:00:00 2001 From: FoggyFinder Date: Tue, 27 Mar 2018 14:33:25 +0300 Subject: [PATCH 13/48] fix typos 2 --- posts/function-composition.md | 12 +++++----- posts/function-signatures.md | 8 +++---- posts/function-values-and-simple-values.md | 28 +++++++++++----------- posts/mathematical-functions.md | 18 +++++++------- posts/partial-application.md | 6 ++--- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/posts/function-composition.md b/posts/function-composition.md index fbacd78..f9ffc32 100644 --- a/posts/function-composition.md +++ b/posts/function-composition.md @@ -51,7 +51,7 @@ let F x y z = (x y) z 1. Применение функций имеет _левую ассоциативность_. 1. `x y z` значит тоже самое что и `(x y) z`. 1. А `w x y z` равно `((w x) y) z`. -1. Это не должно выглядить удивительным. +1. Это не должно выглядеть удивительным. 1. Мы уже видели как работает частичное применение. 1. Если рассуждать о `x` как о функции с двумя параметрами, то `(x y) z` - это результат частичного применения первого параметра, за которым следует передача аргумента `z` к промежуточной функции. @@ -122,7 +122,7 @@ let compose f g x = g ( f(x) ) > If you evaluate this, you will see that the compiler has correctly deduced that if "`f`" is a function from generic type `'a` to generic type `'b`, then "`g`" is constrained to have generic type `'b` as an input. And the overall signature is: -После выполнения можно увидеть, что компилятор правильно решил, что "`f`" - это функция с обощенного типа `'a` к обобщенному типу `'b`, а "`g`" ограниченна вводом типа `'b`: +После выполнения можно увидеть, что компилятор правильно решил, что "`f`" - это функция с обобщенного типа `'a` к обобщенному типу `'b`, а "`g`" ограничена вводом типа `'b`: ```fsharp val compose : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c @@ -276,15 +276,15 @@ myList |> List.isEmpty |> not // straight pipeline myList |> (not << List.isEmpty) // using reverse composition ``` -## Composition vs. pipeline | Композиция vs. конвеер ## +## Composition vs. pipeline | Композиция vs. конвейер ## > At this point, you might be wondering what the difference is between the composition operator and the pipeline operator, as they can seem quite similar. -Вы можете быть сбиты с толку небольшой разницей между композицией и конвеером, т.к. они могут выглядеть очень похожими. +Вы можете быть сбиты с толку небольшой разницей между композицией и конвейером, т.к. они могут выглядеть очень похожими. > First let's look again at the definition of the pipeline operator: -Во первых, посмотрите на определение конвеера: +Во первых, посмотрите на определение конвейера: ```fsharp let (|>) x f = f x @@ -330,7 +330,7 @@ let add1Times2 = add 1 >> times 2 > Trying to use a pipe instead doesn't work. In the following example, "`add 1`" is a (partial) function of type `int->int`, and cannot be used as the second parameter of "`times 2`". -Попытки использовать конвейера вместо композиции обернутся ошибкой компиляции. В следующем примере "`add 1`" - это (частичная) функция `int->int`, которая не может быть использвоана в качестве второго параметра для "`times 2`". +Попытки использовать конвейер вместо композиции обернутся ошибкой компиляции. В следующем примере "`add 1`" - это (частичная) функция `int->int`, которая не может быть использована в качестве второго параметра для "`times 2`". ```fsharp let add1Times2 = add 1 |> times 2 // not allowed diff --git a/posts/function-signatures.md b/posts/function-signatures.md index 90b3534..964c6d4 100644 --- a/posts/function-signatures.md +++ b/posts/function-signatures.md @@ -25,7 +25,7 @@ int * string // a type expression > Type expressions have a special syntax that is *different* from the syntax used in normal expressions. You have already seen many examples of this when you use the interactive session, because the type of each expression has been printed along with its evaluation. -Выражения типов имеют специальный синтаксис, который *отличается* от синтаксиса используемого в обычных выражениях. Вы уже видели множество примеров, где использовалася интерактивная сессия, потому что тип каждого выражения выводился вместе с результатом выполнения. +Выражения типов имеют специальный синтаксис, который *отличается* от синтаксиса используемого в обычных выражениях. Вы уже видели множество примеров, где использовалась интерактивная сессия, потому что тип каждого выражения выводился вместе с результатом выполнения. > As you know, F# uses type inference to deduce types, so you don't often need to explicitly specify types in your code, especially for functions. But in order to work effectively in F#, you *do* need to understand the type syntax, so that you can build your own types, debug type errors, and understand function signatures. In this post, we'll focus on its use in function signatures. @@ -104,7 +104,7 @@ int -> (unit -> string) > This function takes two parameters: the first is a function that maps something to a bool (a predicate), and the second is a list. The return value is a list of the same type. Predicates are used to determine whether a value meets some sort of criteria, so it looks like the function is choosing elements from the list based on whether the predicate is true or not and then returning a subset of the original list. A typical function with this signature is `List.filter`. -Эта функция принимает два параметра: первый - функция которая преобразует что-либо в `bool` (предикат), а второй - список. Возвращаемое значения является списком того же типа. Предикаты используют, чтобы определить, соответствует ли определенный объект какому-либо критерию, похоже, что данная функция выбирает элементы из списка на основе того, принимает ли предикат значение истинны или нет, после чего возвращает подмножество исходного списка. Типичной функцией с подобной сигнатурой является `List.filter`. +Эта функция принимает два параметра: первый - функция которая преобразует что-либо в `bool` (предикат), а второй - список. Возвращаемое значение является списком того же типа. Предикаты используют, чтобы определить, соответствует ли определенный объект какому-либо критерию, похоже, что данная функция выбирает элементы из списка на основе того, принимает ли предикат значение истинны или нет, после чего возвращает подмножество исходного списка. Типичной функцией с подобной сигнатурой является `List.filter`. ```fsharp // function signature 7 @@ -113,13 +113,13 @@ int -> (unit -> string) > This function takes two parameters: the first maps type `'a` to type `'b`, and the second is a list of `'a`. The return value is a list of a different type `'b`. A reasonable guess is that the function takes each of the `'a`s in the list, maps them to a `'b` using the function passed in as the first parameter, and returns the new list of `'b`s. And indeed, the prototypical function with this signature is `List.map`. -Функция принимает два парамерта: первый - преобразует тип `'a` в тип `'b`, а второй - список типа `'a`. Возвращаемое значение является списком другого типа `'b`. Разумно будет предположить, что функция берет каждый элемент из списка `'a`, и преобразует их в `'b` используя функцию переданную в качестве первого параметра, после чего возвращает список `'b`. И действительно, `List.map` является прообразом функции с такой сигнатурой. +Функция принимает два параметра: первый - преобразует тип `'a` в тип `'b`, а второй - список типа `'a`. Возвращаемое значение является списком другого типа `'b`. Разумно будет предположить, что функция берет каждый элемент из списка `'a`, и преобразует их в `'b` используя функцию переданную в качестве первого параметра, после чего возвращает список `'b`. И действительно, `List.map` является прообразом функции с такой сигнатурой. ### Using function signatures to find a library method | Поиск библиотечных методов при помощи сигнатуры функции ### > Function signatures are an important part of searching for library functions. The F# libraries have hundreds of functions in them and they can initially be overwhelming. Unlike an object oriented language, you cannot simply "dot into" an object to find all the appropriate methods. However, if you know the signature of the function you are looking for, you can often narrow down the list of candidates quickly. -Сигнатуры функций важная часть поиска библиотечных функций. Библиотеки F# содержат сотни функций, что вначале может быть ошеломляющим. В отличии от объектно ориентированных языков, вы не можете просто "войти в объект" через точку, чтобы найти все связанные методы. Однако, если вы знаете сигнатуру функции, которую желаете найти, вы зачастую можете быстро сузить список кандидатов. +Сигнатуры функций важная часть поиска библиотечных функций. Библиотеки F# содержат сотни функций, что вначале может быть ошеломляющим. В отличие от объектно ориентированных языков, вы не можете просто "войти в объект" через точку, чтобы найти все связанные методы. Однако, если вы знаете сигнатуру функции, которую желаете найти, вы зачастую можете быстро сузить список кандидатов. > For example, let's say you have two lists and you are looking for a function to combine them into one. What would the signature be for this function? It would take two list parameters and return a third, all of the same type, giving the signature: diff --git a/posts/function-values-and-simple-values.md b/posts/function-values-and-simple-values.md index 898b7c6..60523d2 100644 --- a/posts/function-values-and-simple-values.md +++ b/posts/function-values-and-simple-values.md @@ -43,7 +43,7 @@ add1 5 > It is important to understand that this is not assignment. "x" is not a "slot" or variable that is assigned to the value and can be assigned to another value later on. It is a onetime association of the name "x" with the value. The value is one of the predefined integers, and cannot change. And so, once bound, x cannot change either; once associated with a value, always associated with a value. -Важно понимать, что это не присваивание. "x" не "слот" и не переменная с присвоеным значением, которые можно изменить позднее. Это разовая ассоциация имени "x" с неким значением. Это иначение одно из предопределенных целых чисел, оно не может быть изменено. Т.е. однажды привязанный, x _не может быть измененен_. Метка единожды ассоциированная со значением, навсегда связана с этим значением. +Важно понимать, что это не присваивание. "x" не "слот" и не переменная с присвоенным значением, которые можно изменить позднее. Это разовая ассоциация имени "x" с неким значением. Это значение одно из предопределенных целых чисел, оно не может быть изменено. Т.е. однажды привязанный, x _не может быть изменен_. Метка единожды ассоциированная со значением, навсегда связана с этим значением. > This concept is a critical part of thinking functionally: *there are no "variables", only values*. @@ -53,15 +53,15 @@ add1 5 > If you think about this a bit more, you will see that the name "`add1`" itself is just a binding to "the function that adds one to its input". The function itself is independent of the name it is bound to. -Если поразмыслить над этим чуть подольше, можно увидеть, что имя "`add`" само по себе - это просто привязка к "функция которая увеличивает ввод на еденицу.". Сама функция независима от имени которое к ней привязано. +Если поразмыслить над этим чуть подольше, можно увидеть, что имя "`add`" само по себе - это просто привязка к "функция которая увеличивает ввод на единицу.". Сама функция независима от имени которое к ней привязано. > When you type `let add1 x = x + 1` you are telling the F# compiler "every time you see the name "`add1`", replace it with the function that adds 1 to its input". "`add1`" is called a **function value**. -Введя `let add1 x = x + 1`, мы говорим компилятору F# "каждый раз когда ты видешь имя "`add1`", замени его на функцию, которая добавляет 1 к вводу". "`add1`" называется **функцией-значением (function value)**. +Введя `let add1 x = x + 1`, мы говорим компилятору F# "каждый раз когда ты видишь имя "`add1`", замени его на функцию, которая добавляет 1 к вводу". "`add1`" называется **функцией-значением (function value)**. > To see that the function is independent of its name, try: -Чтобы увидеть, что функция независит от ее имени, достаточно выполнить следующий код: +Чтобы увидеть, что функция не зависит от своего имени, достаточно выполнить следующий код: ```fsharp let add1 x = x + 1 @@ -72,11 +72,11 @@ plus1 5 > You can see that "`add1`" and "`plus1`" are two names that refer ("bound to") to the same function. -Как видно, "`add`'" и "`plus`" - это два имени ссылающихся (привязаныых к) одной и той же функции. +Как видно, "`add`'" и "`plus`" - это два имени ссылающихся (привязанных к) одной и той же функции. > You can always identify a function value because its signature has the standard form `domain -> range`. Here is a generic function value signature: -Идентифицировать функцию-значение всегда можно по ее сигнатуре, которая имеет страндартную форму `domain -> range`. Обобщенная сигнатура функции-значения: +Идентифицировать функцию-значение всегда можно по ее сигнатуре, которая имеет стандартную форму `domain -> range`. Обобщенная сигнатура функции-значения: ```fsharp val functionName : domain -> range @@ -92,7 +92,7 @@ val functionName : domain -> range > This would be a "constant" operation. -Это была бы "костантная" операция. +Это была бы "константная" операция. > How would we write this in F#? We want to tell the F# compiler "every time you see the name `c`, replace it with 5". Here's how: @@ -120,7 +120,7 @@ val c : int = 5 > You can always tell a simple value from a function value because all simple values have a signature that looks like: -Dсегда можно отличить простое значение от функции-значения, потому все простые значения имеют подобную сигнатуру: +Всегда можно отличить простое значение от функции-значения, потому все простые значения имеют подобную сигнатуру: ```fsharp val aName: type = constant // Note that there is no arrow @@ -134,7 +134,7 @@ val aName: type = constant // Note that there is no arrow > Note that there is a subtle difference between a simple value and a function value. A function always has a domain and range and must be "applied" to an argument to get a result. A simple value does not need to be evaluated after being bound. Using the example above, if we wanted to define a "constant function" that returns five we would have to use -Следует учесть, что существует небольшая разница между простым значением и функцией-значением. Функция всегда имеет domain и range, и должна быть "применена" к аргументу, чтобы вернуь результат. Простое значение не надо вычислять после привязки. Используя пример выше, если мы захотим определить "константную функцию", которая возвращает 5 мы могли бы использовать: +Следует учесть, что существует небольшая разница между простым значением и функцией-значением. Функция всегда имеет domain и range, и должна быть "применена" к аргументу, чтобы вернуть результат. Простое значение не надо вычислять после привязки. Используя пример выше, если мы захотим определить "константную функцию", которая возвращает 5 мы могли бы использовать: ```fsharp let c = fun()->5 @@ -178,7 +178,7 @@ val c : int = 5 > In F#, even the primitive values have some object-like behavior. For example, you can dot into a string to get its length: -В F#, даже примитивные значения обладают некоторым объем "объектного" поведения. Напимер, можно через точку получить длину строки: +В F#, даже примитивные значения обладают некоторым объем "объектного" поведения. Например, можно через точку получить длину строки: ```fsharp "abc".Length @@ -186,7 +186,7 @@ val c : int = 5 > But, in general, we will avoid using "object" for standard values in F#, reserving it to refer to instances of true classes, or other values that expose member methods. -> Но в целом, мы будем избегать использования "объекта" для стандартны значений в F#, приберегая его для обращения к полноценным классам, или другим значениям, предоставляющим методы. +> Но в целом, мы будем избегать использования "объекта" для стандартных значений в F#, приберегая его для обращения к полноценным классам, или другим значениям, предоставляющим методы. ## Naming Values | Именование значений ## @@ -222,7 +222,7 @@ let if' b t f = if b then t else f > You can also put double backticks around any string to make a valid identifier. -Можно также использовать двойные обратные ковычки для любой строки, чтобы сделать ее допустимым идентификатором. +Можно также использовать двойные обратные кавычки для любой строки, чтобы сделать ее допустимым идентификатором. ```fsharp ``this is a name`` ``123`` //valid names @@ -230,7 +230,7 @@ let if' b t f = if b then t else f > You might want to use the double backtick trick sometimes: -Случаи, когда может понадобиться использовать трюк с двойными обратными ковычками: +Случаи, когда может понадобиться использовать трюк с двойными обратными кавычками: > * When you want to use an identifier that is the same as a keyword @@ -260,4 +260,4 @@ let [] ``I have (.*) N products in my cart`` (n:int) = > Unlike C#, the naming convention for F# is that functions and values start with lowercase letters rather than uppercase (`camelCase` rather than `PascalCase`) unless designed for exposure to other .NET languages. Types and modules use uppercase however. -В отличии от C#, конвенция именования F# требует, чтобы функции и значения начинались со строчной буквы, а не прописной (`camelCase`, а не `PascalCase`), кроме тех случаев, когда они предназначены для _взаимодействия с другими_ языками .NET. Однако типы и модули использут заглавные буквы (в начале). \ No newline at end of file +В отличии от C#, конвенция именования F# требует, чтобы функции и значения начинались со строчной буквы, а не прописной (`camelCase`, а не `PascalCase`), кроме тех случаев, когда они предназначены для _взаимодействия с другими_ языками .NET. Однако типы и модули используют заглавные буквы (в начале). \ No newline at end of file diff --git a/posts/mathematical-functions.md b/posts/mathematical-functions.md index b87d6f1..8148a16 100644 --- a/posts/mathematical-functions.md +++ b/posts/mathematical-functions.md @@ -9,7 +9,7 @@ seriesOrder: 2 > The impetus behind functional programming comes from mathematics. Mathematical functions have a number of very nice features that functional languages try to emulate in the real world. -Функциональное программирование вдохновлено математикой. Математический функции имеют ряд очень приятных особенностей, которые функциональные языки пытются претворить в жизнь. +Функциональное программирование вдохновлено математикой. Математический функции имеют ряд очень приятных особенностей, которые функциональные языки пытаются претворить в жизнь. > So first, let's start with a mathematical function that adds 1 to a number. @@ -71,7 +71,7 @@ val add1 : int -> int > Mathematical functions have some properties that are very different from the kinds of functions you are used to in procedural programming. -Математические функции имееют ряд свойств, которые очень сильно отличают их от других типов функций, которые используются в процедурном программировании. +Математические функции имеют ряд свойств, которые очень сильно отличают их от других типов функций, которые используются в процедурном программировании. > * A function always gives the same output value for a given input value > * A function has no side effects. @@ -107,7 +107,7 @@ int add1(int input) Очевидно, что невозможно иметь по case-у на каждое возможное число, но принцип тот же. При такой постановке никаких вычислений не производится, осуществляется лишь поиск. -### Mathematical functions are free from side effects | Математические функции свободны от побочных эфектов ### +### Mathematical functions are free from side effects | Математические функции свободны от побочных эффектов ### > In a mathematical function, the input and the output are logically two different things, both of which are predefined. The function does not change the input or the output -- it just maps a pre-existing input value from the domain to a pre-existing output value in the range. @@ -126,7 +126,7 @@ int add1(int input) > I would not expect x to be changed by the adding of one to it. I would expect to get back a different number (y) and x would be left untouched. In the world of mathematics, the integers already exist as an unchangeable set, and the "add1" function simply defines a relationship between them. -Я не ожидаю, что `x` изменится при добавлении к нему 1. Я ожидаю, что получу другое число (`y`), и `x` должен остаться нетронутым. В мире математики целые числа уже сушествуют в неизменяемом множестве, и функция "add1" просто определяет отношения между ними. +Я не ожидаю, что `x` изменится при добавлении к нему 1. Я ожидаю, что получу другое число (`y`), и `x` должен остаться нетронутым. В мире математики целые числа уже существуют в неизменяемом множестве, и функция "add1" просто определяет отношения между ними. ### The power of pure functions | Сила чистых функций ### @@ -139,8 +139,8 @@ int add1(int input) > * I only ever need to evaluate a function once for a certain input, and I can then cache the result, because I know that the same input always gives the same output. > * If I have a number of pure functions, I can evaluate them in any order I like. Again, it can't make any difference to the final result. -* Из легко распараллелить. Скажем, можно бы взять целые числа в диапозоне от 1 до 1000 и раздать их 1000 различных процессоров, после чего поручить каждому CPU выполнить "'add1'" над соответствующим числом, одновременно будучи увереным, что нет необходомости в каком-либо взаимодействии между ними. Не потребуется ни блокировок, ни мьютексов, ни семафоров, ни т.п. -* Можно использовать функции лениво, вычисляя их тогда, когда это необходимо для вывода. Можно быть увереным, что ответ будет точно таким же, независимо от того, провожу вычисления сейчас или позже. +* Из легко распараллелить. Скажем, можно бы взять целые числа в диапазоне от 1 до 1000 и раздать их 1000 различных процессоров, после чего поручить каждому CPU выполнить "'add1'" над соответствующим числом, одновременно будучи уверенным, что нет необходимости в каком-либо взаимодействии между ними. Не потребуется ни блокировок, ни мьютексов, ни семафоров, ни т.п. +* Можно использовать функции лениво, вычисляя их тогда, когда это необходимо для вывода. Можно быть уверенным, что ответ будет точно таким же, независимо от того, провожу вычисления сейчас или позже. * Можно лишь один раз провести вычисления функции для конкретного ввода, после чего закешировать результат, потому-что известно, что данный ввод будет давать такой же вывод. * Если есть множество чистых функций, их можно вычислять в любом порядке. Опять же, это не может повлиять на финальный результат. @@ -156,7 +156,7 @@ int add1(int input) * Пример параллельных вычислений был в серии ["why use F#?"](../series/why-use-fsharp.md). * Ленивое вычисление функций будет обсуждено в серии ["optimization"](../series/optimization.md). * Кэширование результатов функций называется мемоизацией и также будет обсуждено в серии ["optimization"](../series/optimization.md). -* Отсутствие необходимости в отслеживании порядка выполнения делает параллельное программирование гораздо проще и позволяет не сталкиваться с багами вызванными сменой порядка фукций или рефакторинга. +* Отсутствие необходимости в отслеживании порядка выполнения делает параллельное программирование гораздо проще и позволяет не сталкиваться с багами вызванными сменой порядка функций или рефакторинга. ## "Unhelpful" properties of mathematical functions | "Бесполезные" свойства математических функций ## @@ -172,7 +172,7 @@ int add1(int input) > These properties are mirrored in the design of functional programming languages too. Let's look at each of these in turn. -Данные свойства отражаются в дизайне функциональных языков программирования. Стоит рассмотреть их поотдельности. +Данные свойства отражаются в дизайне функциональных языков программирования. Стоит рассмотреть их по отдельности. > **The input and output values are immutable** @@ -192,7 +192,7 @@ int add1(int input) > As you can see from the diagrams, there is always exactly one input and one output for a mathematical function. This is true for functional programming languages as well, although it may not be obvious when you first use them. -Как видно из диаграм, для математической функции всегда существует только один ввод и только один вывод. Это также верно для функциональных языков программирования, хотя может быть неочевидным при первом использовании. +Как видно из диаграмм, для математической функции всегда существует только один ввод и только один вывод. Это также верно для функциональных языков программирования, хотя может быть неочевидным при первом использовании. > That seems like a big annoyance. How can you do useful things without having functions with two (or more) parameters? diff --git a/posts/partial-application.md b/posts/partial-application.md index ee3bf12..4d30dfa 100644 --- a/posts/partial-application.md +++ b/posts/partial-application.md @@ -48,7 +48,7 @@ let printer = printfn "printing param=%i" > In each case, we create a partially applied function that we can then reuse in multiple contexts. -В каждом случае мы создаем частично примененную функцию, которую можно повторно использовать в разных ситауациях. +В каждом случае мы создаем частично примененную функцию, которую можно повторно использовать в разных ситуациях. > The partial application can just as easily involve fixing function parameters, of course. Here are some examples: @@ -181,7 +181,7 @@ sortDesc [0;1;2;3] > Guideline 1 is straightforward. The parameters that are most likely to be "fixed" with partial application should be first. We saw this with the logger example earlier. -Первый совет прост. Параметры, которые скорее всего будут "зафиксированны" частичным применением должны идти первыми, как в примерах с логгером выше. +Первый совет прост. Параметры, которые скорее всего будут "зафиксированы" частичным применением должны идти первыми, как в примерах с логгером выше. > Guideline 2 makes it easier to pipe a structure or collection from function to function. We have seen this many times already with list functions. @@ -213,7 +213,7 @@ let result = compositeOp [1..10] > However, it is easy enough to create wrappers for them that are more idiomatic. For example, in the snippet below, the .NET string functions are rewritten to have the string target be the last parameter rather than the first: -Однако, достаточно легко можно написить обертки, чтобы сделать эти функции более идеоматичными. В примере ниже строковые .NET функции переписаны так, чтобы целевая строка использовалась последней, а не первой: +Однако, достаточно легко можно написать обертки, чтобы сделать эти функции более идиоматичными. В примере ниже строковые .NET функции переписаны так, чтобы целевая строка использовалась последней, а не первой: ```fsharp // create wrappers for .NET string functions From 9e649f0ce716673dde21aa8f4780ddf2fe372dda Mon Sep 17 00:00:00 2001 From: FoggyFinder Date: Thu, 29 Mar 2018 10:56:14 +0300 Subject: [PATCH 14/48] fix typos 3 --- posts/how-types-work-with-functions.md | 30 +++++++++++------------ posts/organizing-functions.md | 26 ++++++++++---------- posts/stack-based-calculator.md | 12 +++++----- posts/type-extensions.md | 33 +++++++++++++------------- 4 files changed, 50 insertions(+), 51 deletions(-) diff --git a/posts/how-types-work-with-functions.md b/posts/how-types-work-with-functions.md index 59ab2e8..2ee0d27 100644 --- a/posts/how-types-work-with-functions.md +++ b/posts/how-types-work-with-functions.md @@ -103,7 +103,7 @@ let stringLengthAsInt (x:string) :int = x.Length > We're indicating that the x param is a string and the return value is an int. -Мы указываем, что параметр `x` является строкой, а возврашаемое значение является int. +Мы указываем, что параметр `x` является строкой, а возвращаемым значением является целое число. ## Function types as parameters | Типы функций как параметры ## @@ -190,7 +190,7 @@ evalWith5ThenAdd2 times3float > Evaluating this will give an error: -Попытка скопилировать вернет ошибку: +Попытка скомпилировать вернет ошибку: ```fsharp error FS0001: Type mismatch. Expecting a int -> int but @@ -221,7 +221,7 @@ val adderGenerator : int -> (int -> int) > which means that the generator takes an `int`, and creates a function (the "adder") that maps `ints` to `ints`. Let's see how it works: -говорящая, что генератор принимает `int` и создает функцию ("adder"), которая сопоставляет `ints` в `ints`. Псмотрим как это работает: +говорящая, что генератор принимает `int` и создает функцию ("adder"), которая сопоставляет `ints` в `ints`. Посмотрим как это работает: ```fsharp let add1 = adderGenerator 1 @@ -263,7 +263,7 @@ let evalWith5ThenAdd2 fn = fn 5 +2 > But what is the signature of "fn" in this following case? -Но какова сигнатура "fn" в следующием случае? +Но какова сигнатура "fn" в следующем случае? ```fsharp let evalWith5 fn = fn 5 @@ -271,7 +271,7 @@ let evalWith5 fn = fn 5 > Obviously, "`fn`" is some kind of function that takes an int, but what does it return? The compiler can't tell. If you do want to specify the type of the function, you can add a type annotation for function parameters in the same way as for a primitive type. -Понятно, что "`fn`" - разновидность функции, которая принимает `int`, но что она возврашает? Компилятор не может ответить на этот вопрос. В таких случаях, если возникает необходимость указать тип функции, можно добавить тип аннотации для параметров функций также как и для примитивных типов. +Понятно, что "`fn`" - разновидность функции, которая принимает `int`, но что она возвращает? Компилятор не может ответить на этот вопрос. В таких случаях, если возникает необходимость указать тип функции, можно добавить тип аннотации для параметров функций также как и для примитивных типов. ```fsharp let evalWith5AsInt (fn:int->int) = fn 5 @@ -288,14 +288,14 @@ let evalWith5AsString fn :string = fn 5 > Because the main function returns a string, the "`fn`" function is also constrained to return a string, so no explicit typing is required for "fn". -Т.к. основная функция возврашает `string`, функция "`fn`" также вынуждена возвращать `string`, таким образом не требуется явно типизировать "`fn`". +Т.к. основная функция возвращает `string`, функция "`fn`" также вынуждена возвращать `string`, таким образом не требуется явно типизировать "`fn`". ## The "unit" type | Тип "unit" ## > When programming, we sometimes want a function to do something without returning a value. Consider the function "`printInt`", defined below. The function doesn't actually return anything. It just prints a string to the console as a side effect. -В процессе программирования мы иногда хотим, чтобы функция делала что-то не возвраoая ничего. Рассмотрим функцию "`printInt`". Функция действительно ничего не возвращает. Она просто выводит строку на консоль как побочный эффект. +В процессе программирования мы иногда хотим, чтобы функция делала что-то не возвращая ничего. Рассмотрим функцию "`printInt`". Функция действительно ничего не возвращает. Она просто выводит строку на консоль как побочный эффект. ```fsharp let printInt x = printf "x is %i" x // print to console @@ -315,7 +315,7 @@ val printInt : int -> unit > Well, even if a function returns no output, it still needs a range. There are no "void" functions in mathematics-land. Every function must have some output, because a function is a mapping, and a mapping has to have something to map to! -Даже если функция не возврашает значений, она все еще нуждается в range. В мире математики не существует "void" функций. Каждая функция должна что-то возвращать, потому-что функция - это отображение, а отображение должно что-то отображать! +Даже если функция не возвращает значений, она все еще нуждается в range. В мире математики не существует "void" функций. Каждая функция должна что-то возвращать, потому-что функция - это отображение, а отображение должно что-то отображать! ![](../assets/img/Functions_Unit.png) @@ -406,13 +406,13 @@ val printHelloFn : unit -> unit > and to call it, we have to pass the `()` value as a parameter, like so: -и чтобы вызвать ее, мы должны передать `()` в качестве парамерта: +и чтобы вызвать ее, мы должны передать `()` в качестве параметра: ```fsharp printHelloFn () ``` -### Forcing unit types with the ignore function | _Усиление unit типов с помошью ignore function_### +### Forcing unit types with the ignore function | _Усиление unit типов с помощью функции ignore _### > In some cases the compiler requires a unit type and will complain. For example, both of the following will be compiler errors: @@ -446,7 +446,7 @@ let something = > For example, the following function converts the parameter to a string and appends some text: -Например, следующая функция ковертирует параметр в строку добавляя немного текста: +Например, следующая функция конвертирует параметр в строку добавляя немного текста: ```fsharp let onAStick x = x.ToString() + " on a stick" @@ -482,7 +482,7 @@ string OnAStick(); // F#'s use of 'a is like > Here's the same function being used with an int, a float and a string -Одна и таже функция используется для `int`, `float` и `string`. +Одна и та же функция используется для `int`, `float` и `string`. ```fsharp onAStick 22 @@ -530,7 +530,7 @@ val isEqual : 'a -> 'a -> bool > The types discussed so far are just the basic types. These types can be combined in various ways to make much more complex types. A full discussion of these types will have to wait for [another series](../series/understanding-fsharp-types.md), but meanwhile, here is a brief introduction to them so that you can recognize them in function signatures. -До сих пор обсуждались только базовые типы. Данные типы могут быть скомбинированны различными способами в более сложные типы. Полный их разбор будет позднее в [другой серии](../series/understanding-fsharp-types.md), но между тем, здесь кратко их разберем, так что бы можно было распознать их в сигнатуре функции. +До сих пор обсуждались только базовые типы. Данные типы могут быть скомбинированы различными способами в более сложные типы. Полный их разбор будет позднее в [другой серии](../series/understanding-fsharp-types.md), но между тем, здесь кратко их разберем, так что бы можно было распознать их в сигнатуре функции. > * **The "tuple" types**. These are pairs, triples, etc., of other types. For example `("hello", 1)` is a tuple made from a string and an int. The comma is the distinguishing characteristic of a tuple -- if you see a comma in F#, it is almost certainly part of a tuple! @@ -557,7 +557,7 @@ int [] // Array type e.g. [|1;2;3|] > * **The option type**. This is a simple wrapper for objects that might be missing. There are two cases: `Some` and `None`. In function signatures, they have their own "`option`" keyword: -* **Option (опциональный тип)**. Это простая обертка над объектами, которые могут отстутвовать. Сушествует два варианта: `Some` и `None`. В сигнатурах функции они имеют свое свобственное ключевое слово "`option`": +* **Option (опциональный тип)**. Это простая обертка над объектами, которые могут отсутствовать. Существует два варианта: `Some` и `None`. В сигнатурах функций они имеют свое собственное ключевое слово "`option`": ```fsharp int option // Some(1) @@ -567,7 +567,7 @@ int option // Some(1) > * **The record type**. These are like structures or database rows, a list of named slots. We saw some examples of this in the ["why use F#?"](../series/why-use-fsharp.md) series as well. In function signatures, they are referred to by the name of the type, so again there is no special keyword. * **Размеченное объединение (discriminated union)**. Они построены из множества вариантов других типов. Мы видели некоторые примеры в ["why use F#?"](../series/why-use-fsharp.md). В сигнатурах функций на них ссылаются по имени типа, они не имеют специального ключевого слова. -* **Record тип (записи)**. Подобные структурам или строкам баз данных, списки именнованных слотов. Мы также видели несколько примеров в ["why use F#?"](../series/why-use-fsharp.md). В сигнатурах функций они называются по имени типа, они также не имеют своего ключевого слова. +* **Record тип (записи)**. Подобные структурам или строкам баз данных, списки именованных слотов. Мы также видели несколько примеров в ["why use F#?"](../series/why-use-fsharp.md). В сигнатурах функций они называются по имени типа, они также не имеют своего ключевого слова. ## Test your understanding of types | Проверьте свое понимание типов ## diff --git a/posts/organizing-functions.md b/posts/organizing-functions.md index 22f01cc..7d11ecf 100644 --- a/posts/organizing-functions.md +++ b/posts/organizing-functions.md @@ -20,7 +20,7 @@ categories: [Functions, Modules] > * at an application level, the top level functions are grouped into "modules". > * alternatively, you can also use the object-oriented approach and attach functions to types as methods. -* функции могут быть вложенны в другие функции. +* функции могут быть вложены в другие функции. * на уровне приложения, функции верхнего уровня группируются по "модулям". * в качестве альтернативы, можно придерживаться ООП и прикреплять функции к типам в качестве методов. @@ -100,7 +100,7 @@ sumNumbersUpTo 10 > A badly nested function will be just as confusing as the worst kind of deeply nested imperative branching. Старайтесь избегать глубокой вложенности, особенно в случаях прямого доступа (не в виле параметров) к родительским переменным. -Плохо вложенные функции будут столь же сложны для понимания, как худшие виды глубоких вложенных императивных ветлений. +Плохо вложенные функции будут столь же сложны для понимания, как худшие виды глубоких вложенных императивных ветвлений. > Here's how *not* to do it: @@ -172,7 +172,7 @@ static class MathStuff > If you realize that modules are just static classes, and that functions are static methods, then you will already have a head-start on understanding how modules work in F#, > as most of the rules that apply to static classes also apply to modules. -Осознание того, что модули являются всего-лишь статическими классами, а функции являются статическими методами, даст хорошее понимание того, как модули работают в F#, большинтсво правил применимых к статическим классам также применими и к модулям. +Осознание того, что модули являются всего-лишь статическими классами, а функции являются статическими методами, даст хорошее понимание того, как модули работают в F#, большинство правил применимых к статическим классам также применимо и к модулям. > And, just as in C# every standalone function must be part of a class, in F# every standalone function *must* be part of a module. @@ -254,7 +254,7 @@ module OtherStuff = > So if there can be nested child modules, that implies that, going back up the chain, there must always be some *top-level* parent module. This is indeed true. // TODO: Разобраться с термином. -Таким образом, могут сущестовать дочерние модули, это означает, что идя назад по цепочке можно дойти до некоего родительского модуля высшего порядка. Это действительно так. +Таким образом, могут существовать дочерние модули, это означает, что идя назад по цепочке можно дойти до некоего родительского модуля высшего порядка. Это действительно так. > Top level modules are defined slightly differently than the modules we have seen so far. @@ -271,7 +271,7 @@ module OtherStuff = > In general, there must be a top level module declaration present in every `.FS` source file. There some exceptions, but it is good practice anyway. The module name does not have to be the same as the name of the file, but two files cannot share the same module name. -В обшем случае, должна существовать декларация верхнего уровня в каждом исходном `.FS` файле. Есть исключения, но это хорошая практика в любом случае. Имя модуля не обязанно совпадать с именем файла, но два файла не могут содержать модули с одинаковыми именнами. +В общем случае, должна существовать декларация верхнего уровня в каждом исходном `.FS` файле. Есть исключения, но это хорошая практика в любом случае. Имя модуля не обязано совпадать с именем файла, но два файла не могут содержать модули с одинаковыми именами. > For `.FSX` script files, the module declaration is not needed, in which case the module name is automatically set to the filename of the script. @@ -377,7 +377,7 @@ let result = add 1 2 // Compiler error: This expression was expected to > If you don't want this to happen, there is a way to stop it by using the `RequireQualifiedAccess` attribute. Here's the same example where both modules are decorated with it. -Если требуется избежать данного поведения, существует способ пресечь его при помощи атрибута `RequireQualifiedAccess`. Тот же пример у когорого оба модуля декорированны данными атрибутом: +Если требуется избежать данного поведения, существует способ пресечь его при помощи атрибута `RequireQualifiedAccess`. Тот же пример у которого оба модуля декорированы данными атрибутом: ```fsharp [] @@ -423,7 +423,7 @@ F# поддерживает использование стандартных .N > * These access specifiers can be put on the top-level ("let bound") functions, values, types and other declarations in a module. They can also be specified for the modules themselves (you might want a private nested module, for example). > * Everything is public by default (with a few exceptions) so you will need to use `private` or `internal` if you want to protect them. -* Эти спецификаторы доступа могут быть применены к ("let bound") функциям верхнего уровня, значениям, типам и другим декларациям в модуле. Они таже могут быть указаны для самих модулей (например может понадобиться приватный вложенный модуль). +* Эти спецификаторы доступа могут быть применены к ("let bound") функциям верхнего уровня, значениям, типам и другим декларациям в модуле. Они также могут быть указаны для самих модулей (например может понадобиться приватный вложенный модуль). * По умолчанию все имеет публичный доступ (за исключением нескольких случаев), поэтому для их защиты потребуется использовать `private` или `internal`. > These access specifiers are just one way of doing access control in F#. Another completely different way is to use module "signature" files, which are a bit like C header files. They describe the content of the module in an abstract way. Signatures are very useful for doing serious encapsulation, but that discussion will have to wait for the planned series on encapsulation and capability based security. @@ -461,7 +461,7 @@ module MathStuff = > You can also declare a namespace implicitly by adding dots to the module name. That is, the code above could also be written as: -Также можно объявлять пространтсво имен явно при помощи добавления точки в имени модуля. Т.е. код выше можно переписать так: +Также можно объявлять пространство имен явно при помощи добавления точки в имени модуля. Т.е. код выше можно переписать так: ```fsharp module Utilities.MathStuff @@ -505,7 +505,7 @@ module MathStuff = > And if you want to put *two* namespaces in the same file, you can. Note that all namespaces *must* be fully qualified -- there is no nesting. -Можно объявить *два* пространтсва имен в одном файле, если есть такое желание. Следует обратить внимание, что все пространства имен *должны* быть объявлены полным именем -- они не поддержимают вложенность. +Можно объявить *два* пространства имен в одном файле, если есть такое желание. Следует обратить внимание, что все пространства имен *должны* быть объявлены полным именем -- они не поддерживают вложенность. ```fsharp namespace Core.Utilities @@ -521,7 +521,7 @@ module MoreMathStuff = > One thing you can't do is have a naming collision between a namespace and a module. -Конфликт имен можду пространством имен и модулем невозможнен. +Конфликт имен между пространством имен и модулем невозможен. ```fsharp namespace Core.Utilities @@ -539,11 +539,11 @@ module Utilities = ``` -## Mixing types and functions in modules | Смешивание типов и функий в модулях ## +## Mixing types and functions in modules | Смешивание типов и функций в модулях ## > We've seen that a module typically consists of a set of related functions that act on a data type. -Как мы видели ранее, модули обычно состоят из множестов взаимозависимых функций, которые взаимодействуют с определенным типом данных. +Как мы видели ранее, модули обычно состоят из множества взаимозависимых функций, которые взаимодействуют с определенным типом данных. > In an object oriented program, the data structure and the functions that act on it would be combined in a class. > However in functional-style F#, a data structure and the functions that act on it are combined in a module instead. @@ -625,7 +625,7 @@ Customer.isValid customer |> printfn "Is valid?=%b" > * The former approach is more .NET like, and much better if you want to share your libraries with other non-F# code, as the exported class names are what you would expect. > * The latter approach is more common for those used to other functional languages. The type inside a module compiles into nested classes, which is not so nice for interop. -* Первый подход польше похож на классический .NET, и его следует предпочесть, если планируется использовать данную библиотеку для кода за пределами F#, где ожидают отдельно существующий класс. +* Первый подход больше похож на классический .NET, и его следует предпочесть, если планируется использовать данную библиотеку для кода за пределами F#, где ожидают отдельно существующий класс. * Второй подход является более распространенным в других функциональных языках. Тип внутри модуля компилируется как вложенный класс, что как правило не очень удобно для ООП языков. > For yourself, you might want to experiment with both. And in a team programming situation, you should choose one style and be consistent. diff --git a/posts/stack-based-calculator.md b/posts/stack-based-calculator.md index 389784b..3326305 100644 --- a/posts/stack-based-calculator.md +++ b/posts/stack-based-calculator.md @@ -160,7 +160,7 @@ let stackWith2 = push 2.0 stackWith1 > With this simple function in place, we can easily define an operation that pushes a particular number onto the stack. -С помощью это простой функции, можно легко определить операцию помещающую определенное число в стек. +С помощью этой простой функции, можно легко определить операцию помещающую определенное число в стек. ```fsharp let ONE stack = push 1.0 stack @@ -185,7 +185,7 @@ let FIVE = push 5.0 > While we're at it, let's define a function that creates an empty stack as well: -Стоит так же определить функцию создающую пустой стек: +Стоит также определить функцию создающую пустой стек: ```fsharp let EMPTY = StackContents [] @@ -223,7 +223,7 @@ let result312 = EMPTY |> THREE |> ONE |> TWO > That takes care of pushing onto the stack ? what about a `pop` function next? -С помещением в стек разобрались, но что на счет функции `pop`? +С помещением в стек разобрались, но что насчет функции `pop`? > When we pop the stack, we will return the top of the stack, obviously, but is that all? @@ -380,7 +380,7 @@ let binary mathFn stack = > *Note that in this implementation, I've switched to using ticks to represent changed states of the "same" object, rather than numeric suffixes. Numeric suffixes can easily get quite confusing.* -_В данной реализации разные версии "одного" объекта помечены различным количеством одинарных ковычек. Это сделано потому, что числовые суффиксы как правило могут легко запутать._ +_В данной реализации разные версии "одного" объекта помечены различным количеством одинарных кавычек. Это сделано потому, что числовые суффиксы как правило могут легко запутать._ > Question: why are the parameters in the order they are, instead of `mathFn` being after `stack`? @@ -595,11 +595,11 @@ START |> THREE |> SQUARE |> SUM_NUMBERS_UPTO |> SHOW В каждом из этих примеров определяется новая функция составленная из других функций. Это хороший пример "комбинаторного" подхода к построению функциональности. -## Pipes vs composition | Конвееры против композиции +## Pipes vs composition | Конвейеры против композиции > We have now seen two different ways that this stack based model can be used; by piping or by composition. So what is the difference? And why would we prefer one way over another? -Были показаны два различных способа использования данной основанной на стеке модели; при помощи конвееров и композиций. Но в чем разница? И почему надо предпочесть один из способов другому? +Были показаны два различных способа использования данной основанной на стеке модели; при помощи конвейеров и композиций. Но в чем разница? И почему надо предпочесть один из способов другому? > The difference is that piping is, in a sense, a "realtime transformation" operation. When you use piping you are actually doing the operations right now, passing a particular stack around. diff --git a/posts/type-extensions.md b/posts/type-extensions.md index 2ad7e85..6ab2257 100644 --- a/posts/type-extensions.md +++ b/posts/type-extensions.md @@ -10,7 +10,7 @@ seriesOrder: 11 > Although we have focused on the pure functional style so far, sometimes it is convenient to switch to an object oriented style. And one of the key features of the OO style is the ability to attach functions to a class and "dot into" the class to get the desired behavior. -Хотя до этого повествование было сосредосточено на чисто функциональном стиле, иногда удобно переключится на объектно ориентированный стиль. И одной из ключевых особенностей ОО стиля является возможность прикреплять функции к классу и обращение к классу через точку для получение желаемого поведения. +Хотя до этого повествование было сосредоточено на чисто функциональном стиле, иногда удобно переключится на объектно ориентированный стиль. И одной из ключевых особенностей ОО стиля является возможность прикреплять функции к классу и обращение к классу через точку для получение желаемого поведения. > In F#, this is done using a feature called "type extensions". And any F# type, not just classes, can have functions attached to them. @@ -47,9 +47,8 @@ There is no requirement to use a particular word, just as long as it is consiste * Ключевое слово `with` обозначает начало списка членов * Ключевое слово `member` показывает, что эта функция является членом (т.е. методом) -* Слово `this` является меткой объекта, на котором вызывается данный метод (называемая также "self-identifier"). Данное слово является префиксом имени функции, и внутри функции можно использовать ее для обращения к текущему экземляру. Не сушествует требований к словам использумым в качестве самоидентификатора, достаточно чтобы они были устойчивы. Можно использовать `this`, `self`, `me` или любое другое слово, которое обычно используется как отсылка на самого себя. +* Слово `this` является меткой объекта, на котором вызывается данный метод (называемая также "self-identifier"). Данное слово является префиксом имени функции, и внутри функции можно использовать ее для обращения к текущему экземпляру. Не существует требований к словам используемым в качестве самоидентификатора, достаточно чтобы они были устойчивы. Можно использовать `this`, `self`, `me` или любое другое слово, которое обычно используется как отсылка на самого себя. -> You don't have to add a member at the same time that you declare the type, you can always add it later in the same module: Нет нужды добавлять член вместе с декларацией типа, всегда можно добавить его позднее в том же модуле: @@ -82,7 +81,7 @@ let sortableName = person.SortableName > With intrinsic extensions, it is even possible to have a type definition that divided across several files, as long as all the components use the same namespace and are all compiled into the same assembly. Just as with partial classes in C#, this can be useful to separate generated code from authored code. -Внутренние расширения позволяют иметь определение типа распределенное по нескольким файлам, пока все компоненты используют одно и то же пространство имен и компилируются в одну сборку. Так же как и с partial классами в C#, это может быть полезным для разделения сгенерированного и написанного в ручную кода. +Внутренние расширения позволяют иметь определение типа распределенное по нескольким файлам, пока все компоненты используют одно и то же пространство имен и компилируются в одну сборку. Так же как и с partial классами в C#, это может быть полезным для разделения сгенерированного и написанного вручную кода. ## Optional extensions | Опциональные расширения @@ -234,12 +233,12 @@ let pi = System.Double.Pi > * While developing, you can create standalone functions that refer to other standalone functions. This makes programming easier because type inference works much better with functional-style code than with OO-style ("dotting into") code. > * But for certain key functions, you can attach them to the type as well. This gives clients the choice of whether to use functional or object-oriented style. -* Во время разработки, можно объявдять самостоятельные функции, которые ссылаются на другие самостоятельные функции. Это упростит разработку, т.к. вывод типов работает гораздо лучше с функциональным стилем нежели с ООП. -* Но некоторые ключевые функции можно прикрепить к типу. Что позволит пользователям выбрать, какой из стилей использовать, функциональный или объекто-ориентированный. +* Во время разработки, можно объявлять самостоятельные функции, которые ссылаются на другие самостоятельные функции. Это упростит разработку, т.к. вывод типов работает гораздо лучше с функциональным стилем нежели с ООП. +* Но некоторые ключевые функции можно прикрепить к типу. Что позволит пользователям выбрать, какой из стилей использовать, функциональный или объектно-ориентированный. > One example of this in the F# libraries is the function that calculates a list's length. It is available as a standalone function in the `List` module, but also as a method on a list instance. -Примером подобного решения является функция из F# библиотеки, которая расчитывает длину списка. Можно использовать самостоятельную функцию из модуля `List` или вызывать ее как метод экземпляра. +Примером подобного решения является функция из F# библиотеки, которая рассчитывает длину списка. Можно использовать самостоятельную функцию из модуля `List` или вызывать ее как метод экземпляра. ```fsharp let list = [1..10] @@ -286,11 +285,11 @@ let fullname2 = person.FullName // OO style > One nice thing is that when the previously defined function has multiple parameters, you don't have to respecify them all when doing the attachment, as long as the `this` parameter is first. -Еще одной приятной особенностью является то, что можно сначала определить мультипараметрическую функцию, в которой текущий тип передается в качестве первого параметра, после чего при создании прикрепления не потребуется упоминать все множество параметров, ограничевшись `this`. +Еще одной приятной особенностью является то, что можно сначала определить мультипараметрическую функцию, в которой текущий тип передается в качестве первого параметра, после чего при создании прикрепления не потребуется упоминать все множество параметров, ограничившись `this`. > In the example below, the `hasSameFirstAndLastName` function has three parameters. Yet when we attach it, we only need to specify one! -В примере ниже функция `hasSameFirstAndLastName` имеет три параметра. Однако при прикреплении достаточно упоминуть всего лишь один! +В примере ниже функция `hasSameFirstAndLastName` имеет три параметра. Однако при прикреплении достаточно упомянуть всего лишь один! ```fsharp module Person = @@ -335,7 +334,7 @@ let result2 = person.HasSameFirstAndLastName "bob" "smith" // OO style > The "curried" form is more functional, and the "tuple" form is more object-oriented. -Каррированая форма более функциональна, в то время как кортежная форма более объектно-ориентированна. +Каррированая форма более функциональная, в то время как кортежная форма более объектно-ориентированная. > The tuple form is also how F# interacts with the standard .NET libraries, so let's examine this approach in more detail. @@ -391,15 +390,15 @@ let totalForDifferentDiscounts > * Optional parameters > * Overloading -* Именнованные параметры +* Именованные параметры * Необязательные параметры * Перегрузки -### Named parameters with tuple-style parameters | Именнованные параметры с параметрами в кортежном стиле +### Named parameters with tuple-style parameters | Именованные параметры с параметрами в кортежном стиле > The tuple-style approach supports named parameters: -Кортежний подход поддерживает именнованные параметры: +Кортежний подход поддерживает именованные параметры: ```fsharp let product = {SKU="ABC"; Price=2.0} @@ -518,7 +517,7 @@ type Product = {SKU:string; Price: float} with > Normally, the F# compiler would complain that there are two methods with the same name, but in this case, because they are tuple based and because their signatures are different, it is acceptable. > (To make it obvious which one is being called, I have added a small debugging message.) -Обычно, компилятор F# жалуется, что существует два метода с одинаковым именем, но в данном случае это приемлимо, т.к. они объявлены в кортежной нотации и их сигнатуры различаются. (Чтобы было понятно, какой из методов вызывается, я добавил небольшое сообщения для отладки.) +Обычно, компилятор F# жалуется, что существует два метода с одинаковым именем, но в данном случае это приемлемо, т.к. они объявлены в кортежной нотации и их сигнатуры различаются. (Чтобы было понятно, какой из методов вызывается, я добавил небольшое сообщения для отладки.) > And here's a test: @@ -583,7 +582,7 @@ module Person = > Now let's see how well each one works with type inference. Say that I want to print the full name of a person, so I will define a function `printFullName` that takes a person as a parameter. -Теперь, посмотрим как хорошо вывод типов работает с каждым из них. Пусть я хочу вывести полное имя человека, тогда я определю функцию `printFullName`, которая берет `person` в качетсве параметра. +Теперь, посмотрим как хорошо вывод типов работает с каждым из них. Пусть я хочу вывести полное имя человека, тогда я определю функцию `printFullName`, которая берет `person` в качестве параметра. > Here's the code using the module level standalone function. @@ -602,7 +601,7 @@ let printFullName person = > This compiles without problems, and the type inference has correctly deduced that parameter was a person -Компилируется без пробелм, а вывод типов корректно идентифицирует параметр как `Person`. +Компилируется без проблем, а вывод типов корректно идентифицирует параметр как `Person`. > Now let's try the "dotted" version: @@ -669,4 +668,4 @@ list |> List.map (fun p -> p.FullName) > So, a plea for those of you new to functionally programming. Don't use methods at all if you can, especially when you are learning. > They are a crutch that will stop you getting the full benefit from functional programming. -Поэтому, призываю вас, если вы новичок в функциональном программированию. Если можете, не используйте методы, особенно в процессе обучения. Они будут костылем который не позволит получить от функционального программирования полную выгоду. \ No newline at end of file +Поэтому, призываю вас, если вы новичок в функциональном программировании. Если можете, не используйте методы, особенно в процессе обучения. Они будут костылем который не позволит получить от функционального программирования полную выгоду. \ No newline at end of file From fa476fcaa755b3bf557ef271b44549522b4cfb08 Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Thu, 10 May 2018 16:34:11 +0300 Subject: [PATCH 15/48] Update mathematical-functions.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Мне кажется, что так читается легче. --- posts/mathematical-functions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/posts/mathematical-functions.md b/posts/mathematical-functions.md index 8148a16..62357f2 100644 --- a/posts/mathematical-functions.md +++ b/posts/mathematical-functions.md @@ -184,7 +184,7 @@ int add1(int input) > I can assure you that this is not as much as a problem as you might think. As you work through this series, you'll see how this works in practice. -Я могу заверить, что это не такая большая проблема как можно представить. Во ходе данной серией статей будет ясно, как это работает на практике. +Я могу заверить, что это не такая большая проблема как кажется. Во ходе данной серией статей будет ясно, как это работает на практике. > **Mathematical functions always have exactly one input and one output** @@ -200,8 +200,8 @@ int add1(int input) > Well, it turns there is a way to do it, and what's more, it is completely transparent to you in F#. It is called "currying" and it deserves its own post, which is coming up soon. -Оказывается, существует путь сделать это, и более того он является абсолютно прозрачным на F#. Называется он "каррированием", и заслуживает отдельный пост, который появится в ближайшее время. +Оказывается, существует путь сделать это, и более того он является абсолютно прозрачным на F#. Называется он "каррированием", и заслуживает отдельного поста, который появится в ближайшее время. > In fact, as you will later discover, these two "unhelpful" properties will turn out to be incredibly useful and a key part of what makes functional programming so powerful. -На самом деле, позже выяснится, что эти два "бесполезных" свойства станут невероятно ценными, и будут ключевой частью, которая делает функциональное программирование столь мощным. \ No newline at end of file +На самом деле, позже выяснится, что эти два "бесполезных" свойства станут невероятно ценными, и будут ключевой частью, которая делает функциональное программирование столь мощным. From 34cb9da5cf31ceb216ea1a1e0939777b1b526d4b Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Thu, 10 May 2018 16:42:41 +0300 Subject: [PATCH 16/48] Update mathematical-functions.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Убрал лишнюю ковычку --- posts/mathematical-functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/mathematical-functions.md b/posts/mathematical-functions.md index 62357f2..4b7144a 100644 --- a/posts/mathematical-functions.md +++ b/posts/mathematical-functions.md @@ -115,7 +115,7 @@ int add1(int input) > In other words, evaluating the function *cannot possibly have any effect on the input, or anything else for that matter*. Remember, evaluating the function is not actually calculating or manipulating anything; it is just a glorified lookup. -Другими словами, вычисление функции _не может иметь каких либо эффектов на входные данные или еще что-нибудь в подобном роде_". Следует запомнить, что вычисление функции в действительности не считает и не манипулирует чем-либо, это просто "прославленный" поиск. +Другими словами, вычисление функции _не может иметь каких либо эффектов на входные данные или еще что-нибудь в подобном роде_. Следует запомнить, что вычисление функции в действительности не считает и не манипулирует чем-либо, это просто "прославленный" поиск. > This "immutability" of the values is subtle but very important. If I am doing mathematics, I do not expect the numbers to change underneath me when I add them! For example, if I have: From b99dedc7b284e58c1ac3438777ff9754c1b2a736 Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Thu, 10 May 2018 16:55:15 +0300 Subject: [PATCH 17/48] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BE=D0=BF=D0=B5=D1=87=D0=B0=D1=82=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/mathematical-functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/mathematical-functions.md b/posts/mathematical-functions.md index 4b7144a..9b44920 100644 --- a/posts/mathematical-functions.md +++ b/posts/mathematical-functions.md @@ -139,7 +139,7 @@ int add1(int input) > * I only ever need to evaluate a function once for a certain input, and I can then cache the result, because I know that the same input always gives the same output. > * If I have a number of pure functions, I can evaluate them in any order I like. Again, it can't make any difference to the final result. -* Из легко распараллелить. Скажем, можно бы взять целые числа в диапазоне от 1 до 1000 и раздать их 1000 различных процессоров, после чего поручить каждому CPU выполнить "'add1'" над соответствующим числом, одновременно будучи уверенным, что нет необходимости в каком-либо взаимодействии между ними. Не потребуется ни блокировок, ни мьютексов, ни семафоров, ни т.п. +* Их легко распараллелить. Скажем, можно бы взять целые числа в диапазоне от 1 до 1000 и раздать их 1000 различных процессоров, после чего поручить каждому CPU выполнить "'add1'" над соответствующим числом, одновременно будучи уверенным, что нет необходимости в каком-либо взаимодействии между ними. Не потребуется ни блокировок, ни мьютексов, ни семафоров, ни т.п. * Можно использовать функции лениво, вычисляя их тогда, когда это необходимо для вывода. Можно быть уверенным, что ответ будет точно таким же, независимо от того, провожу вычисления сейчас или позже. * Можно лишь один раз провести вычисления функции для конкретного ввода, после чего закешировать результат, потому-что известно, что данный ввод будет давать такой же вывод. * Если есть множество чистых функций, их можно вычислять в любом порядке. Опять же, это не может повлиять на финальный результат. From c64ddbddaa504ba27e9d5672f84e65ca14617377 Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Thu, 10 May 2018 16:56:27 +0300 Subject: [PATCH 18/48] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=81=D0=B2=D1=8F=D0=B7=D0=BD=D0=BE=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/mathematical-functions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/posts/mathematical-functions.md b/posts/mathematical-functions.md index 9b44920..0d39d4e 100644 --- a/posts/mathematical-functions.md +++ b/posts/mathematical-functions.md @@ -119,14 +119,14 @@ int add1(int input) > This "immutability" of the values is subtle but very important. If I am doing mathematics, I do not expect the numbers to change underneath me when I add them! For example, if I have: -Эта "иммутабельность" значений очень тонка, но в тоже время очень важная вещь. Когда я занимаюсь математикой, я не жду, что числа будут изменяться в процессе их сложения. Например, если у меня: +Эта "иммутабельность" значений очень тонка, но в тоже время очень важная вещь. Когда я занимаюсь математикой, я не жду, что числа будут изменяться в процессе их сложения. Например, если у меня дано: x = 5 y = x+1 > I would not expect x to be changed by the adding of one to it. I would expect to get back a different number (y) and x would be left untouched. In the world of mathematics, the integers already exist as an unchangeable set, and the "add1" function simply defines a relationship between them. -Я не ожидаю, что `x` изменится при добавлении к нему 1. Я ожидаю, что получу другое число (`y`), и `x` должен остаться нетронутым. В мире математики целые числа уже существуют в неизменяемом множестве, и функция "add1" просто определяет отношения между ними. +То я не ожидаю, что `x` изменится при добавлении к нему 1. Я ожидаю, что получу другое число (`y`), и `x` должен остаться нетронутым. В мире математики целые числа уже существуют в неизменяемом множестве, и функция "add1" просто определяет отношения между ними. ### The power of pure functions | Сила чистых функций ### From 5fec9cbff3a9ceadc5c602a62bb9c09e9c050bcb Mon Sep 17 00:00:00 2001 From: azheleznov88 <36484800+azheleznov88@users.noreply.github.com> Date: Fri, 11 May 2018 12:21:09 +0500 Subject: [PATCH 19/48] Update mathematical-functions.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Стилистические правки --- posts/mathematical-functions.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/posts/mathematical-functions.md b/posts/mathematical-functions.md index 0d39d4e..99edf16 100644 --- a/posts/mathematical-functions.md +++ b/posts/mathematical-functions.md @@ -9,17 +9,17 @@ seriesOrder: 2 > The impetus behind functional programming comes from mathematics. Mathematical functions have a number of very nice features that functional languages try to emulate in the real world. -Функциональное программирование вдохновлено математикой. Математический функции имеют ряд очень приятных особенностей, которые функциональные языки пытаются претворить в жизнь. +Функциональное программирование вдохновлено математикой. Математические функции имеют ряд очень приятных особенностей, которые функциональные языки пытаются претворить в жизнь. > So first, let's start with a mathematical function that adds 1 to a number. -Итак, во-первых, начнем с математической функции, которая добавляет 1 к числу. +Давайте начнем с математической функции, которая добавляет 1 к числу. Add1(x) = x+1 > What does this really mean? Well it seems pretty straightforward. It means that there is an operation that starts with a number, and adds one to it. -Что в действительности означает это выражение? Выглядит довольно просто. Оно означает, что существует такая операция, которая берет число и прибавляет к нему 1. +Что на самом деле означает это выражение? Выглядит довольно просто. Оно означает, что существует такая операция, которая берет число и прибавляет к нему 1. > Let's introduce some terminology: @@ -59,7 +59,7 @@ val add1 : int -> int > * "`add1`" is defined as a "val", short for "value". Hmmm? what does that mean? We'll discuss values shortly. > * The arrow notation "`->`" is used to show the domain and range. In this case, the domain is the `int` type, and the range is also the `int` type. -* Общий смысл - это "функция `add1` сопоставляющая целые числа (из предметной области) с целыми (областью значений). +* Общий смысл - это "функция `add1` сопоставляет целые числа (из области определения) с целыми числами (из области значений). * "`add1`" определена как "val", сокращение от "value" (значения). Хм? что это значит? Мы обсудим значения чуть позже. * Стрелочная нотация "->" используется, чтобы показать domain и range. В данном случае, domain является типом 'int', как и range. @@ -71,12 +71,12 @@ val add1 : int -> int > Mathematical functions have some properties that are very different from the kinds of functions you are used to in procedural programming. -Математические функции имеют ряд свойств, которые очень сильно отличают их от других типов функций, которые используются в процедурном программировании. +Математические функции имеют ряд свойств, которые очень сильно отличают их от функций, которые используются в процедурном программировании. > * A function always gives the same output value for a given input value > * A function has no side effects. -* Функция всегда имеет один и тот же результат для данного входного значения. +* Функция всегда имеет один и тот же результат для одного и того же входного значения. * Функция не имеет побочных эффектов. > These properties provide some very powerful benefits, and so functional programming languages try to enforce these properties in their design as well. Let's look at each of them in turn. @@ -119,7 +119,7 @@ int add1(int input) > This "immutability" of the values is subtle but very important. If I am doing mathematics, I do not expect the numbers to change underneath me when I add them! For example, if I have: -Эта "иммутабельность" значений очень тонка, но в тоже время очень важная вещь. Когда я занимаюсь математикой, я не жду, что числа будут изменяться в процессе их сложения. Например, если у меня дано: +Эта "иммутабельность" значений очень тонкая, но в тоже время очень важная вещь. Когда я занимаюсь математикой, я не жду, что числа будут изменяться в процессе их сложения. Например, если у меня дано: x = 5 y = x+1 From 662d7cdeaa95e676e0147691f49326c4858ad3e0 Mon Sep 17 00:00:00 2001 From: azheleznov88 <36484800+azheleznov88@users.noreply.github.com> Date: Fri, 11 May 2018 12:21:34 +0500 Subject: [PATCH 20/48] Update thinking-functionally-intro.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Стилистические правки --- posts/thinking-functionally-intro.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/posts/thinking-functionally-intro.md b/posts/thinking-functionally-intro.md index 1747033..47c96e7 100644 --- a/posts/thinking-functionally-intro.md +++ b/posts/thinking-functionally-intro.md @@ -9,13 +9,13 @@ seriesOrder: 1 > Now that you have seen some of the power of F# in the ["why use F#"](../series/why-use-fsharp.md) series, we're going to step back and look at the fundamentals of functional programming -- what does it really mean to "program functionally", and how this approach is different from object oriented or imperative programming. -Теперь, когда читатель увидел некоторые из причин, по которым стоит использовать F#, в серии ["why use F#"](../series/why-use-fsharp.md), сделаем шаг назад и обсудим основы функционального программирования. Что в действительности означает "программировать функционально", и как этот подход отличается от объектно-ориентированного или императивного программирования? +Теперь, когда читатель увидел некоторые из причин, по которым стоит использовать F#, в серии ["why use F#"](../series/why-use-fsharp.md), сделаем шаг назад и обсудим основы функционального программирования. Что в действительности означает "программировать функционально", и чем этот подход отличается от объектно-ориентированного или императивного программирования? ### Changing the way you think | Смена образа мышления ### > It is important to understand that functional programming is not just a stylistic difference; it is a completely different way of thinking about programming, in the way that truly object-oriented programming (in Smalltalk say) is also a different way of thinking from a traditional imperative language such as C. -Важно понимать, что функциональное программирование - это не просто отдельный стиль программирования. Это совсем другой способ рассуждения о программе, который отличается от "традиционного" подхода так же значительно, как настоящее ООП (в стиле Smalltalk) отличается от традиционного императивного языка - такого, как C. +Важно понимать, что функциональное программирование - это не просто отдельный стиль программирования. Это совсем другой способ мышления в программировании, который отличается от "традиционного" подхода так же значительно, как настоящее ООП (в стиле Smalltalk) отличается от традиционного императивного языка - такого, как C. > F# does allow non-functional styles, and it is tempting to retain the habits you already are familiar with. You could just use F# in a non-functional way without really changing your mindset, and not realize what you are missing. To get the most out of F#, and to be fluent and comfortable with functional programming in general, it is critical that you think functionally, not imperatively. > And that is the goal of this series: to help you understand functional programming in a deep way, and help to change the way you think. From cc2879b7f66523142c58b8c3289182c31910b1cf Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Thu, 19 Jul 2018 15:45:05 +0300 Subject: [PATCH 21/48] =?UTF-8?q?=D0=9D=D0=B5=D0=BA=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D1=8B=D0=B5=20=D1=81=D1=82=D0=B8=D0=BB=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=20=D0=B8=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/function-values-and-simple-values.md | 28 ++++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/posts/function-values-and-simple-values.md b/posts/function-values-and-simple-values.md index 60523d2..4c74e13 100644 --- a/posts/function-values-and-simple-values.md +++ b/posts/function-values-and-simple-values.md @@ -7,6 +7,8 @@ seriesId: "Thinking functionally" seriesOrder: 3 --- +# Фунции-значения и простые значения + > Let's look at the simple function again Еще раз рассмотрим эту простую функцию. @@ -22,23 +24,23 @@ let add1 x = x + 1 > 1. Accept some value from the input domain. > 2. Use the name "x" to represent that value so that we can refer to it later. -1. Принимает некоторое значение из domain (области определения). -2. Использует имя "x", чтобы представлять значение, к которому мы можем обратиться позже. +1. Возьми некоторое значение из domain (области определения). +2. Используй имя "x" для предоставления этого значение, чтоб к нему можно было обратиться позже. > This process of using a name to represent a value is called "binding". The name "x" is "bound" to the input value. -Процесс использования имени для представления значения называется "привязкой" (binding). Имя "x" "привязано" к входному значению. +Использование имени для представления значения называется "привязкой" (binding). Имя "x" "привязано" к входному значению. > So if we evaluate the function with the input 5 say, what is happening is that everywhere we see "x" in the original definition, we replace it with "5", sort of like search and replace in a word processor. -Так что если вычислить функцию с вводом, скажем, равным 5, то произойдет следующее. Везде где стоит "x" в изначальном определении, ставится "5", аналогично функции "найти и заменить" в текстовом редакторе. +Так что если вычислить функцию с вводом, скажем, равным 5, то произойдет следующее: везде где стоит "x" в изначальном определении, ставится значение 5, аналогично функции "найти и заменить" в текстовом редакторе. ```fsharp let add1 x = x + 1 add1 5 -// replace "x" with "5" +// заменяем "x" with "5" // add1 5 = 5 + 1 = 6 -// result is 6 +// результат 6 ``` > It is important to understand that this is not assignment. "x" is not a "slot" or variable that is assigned to the value and can be assigned to another value later on. It is a onetime association of the name "x" with the value. The value is one of the predefined integers, and cannot change. And so, once bound, x cannot change either; once associated with a value, always associated with a value. @@ -53,7 +55,7 @@ add1 5 > If you think about this a bit more, you will see that the name "`add1`" itself is just a binding to "the function that adds one to its input". The function itself is independent of the name it is bound to. -Если поразмыслить над этим чуть подольше, можно увидеть, что имя "`add`" само по себе - это просто привязка к "функция которая увеличивает ввод на единицу.". Сама функция независима от имени которое к ней привязано. +Если поразмыслить над этим чуть подольше, можно увидеть, что имя "`add1`" само по себе - это тоже просто привязка к "функция которая увеличивает ввод на единицу.". Сама функция независима от имени которое к ней привязано. > When you type `let add1 x = x + 1` you are telling the F# compiler "every time you see the name "`add1`", replace it with the function that adds 1 to its input". "`add1`" is called a **function value**. @@ -123,7 +125,7 @@ val c : int = 5 Всегда можно отличить простое значение от функции-значения, потому все простые значения имеют подобную сигнатуру: ```fsharp -val aName: type = constant // Note that there is no arrow +val aName: type = constant // Заметьте - стрелки отсутствуют ``` ## Simple values vs. function values | Простые значение vs. функции-значения ## @@ -186,7 +188,7 @@ val c : int = 5 > But, in general, we will avoid using "object" for standard values in F#, reserving it to refer to instances of true classes, or other values that expose member methods. -> Но в целом, мы будем избегать использования "объекта" для стандартных значений в F#, приберегая его для обращения к полноценным классам, или другим значениям, предоставляющим методы. +> Но в целом, мы будем избегать термина "объект" для стандартных значений в F#, приберегая его для обращения к полноценным классам, или другим значениям, предоставляющим методы. ## Naming Values | Именование значений ## @@ -199,7 +201,7 @@ val c : int = 5 Можно добавлять апостроф в любой части имени, исключая первый символ. ```fsharp -A'b'c begin' // valid names +A'b'c begin' // валидные имена ``` > The final tick is often used to signal some sort of "variant" version of a value: @@ -225,7 +227,7 @@ let if' b t f = if b then t else f Можно также использовать двойные обратные кавычки для любой строки, чтобы сделать ее допустимым идентификатором. ```fsharp -``this is a name`` ``123`` //valid names +``this is a name`` ``123`` // валидные имена ``` > You might want to use the double backtick trick sometimes: @@ -242,7 +244,7 @@ let ``begin`` = "begin" > * When trying to use natural language for business rules, unit tests, or BDD style executable specifications a la Cucumber. -Когда необходимо использовать естественные языки для бизнес правил, модульных тестов, или BBD стиля исполняемой документации типа Cucumber. +* Когда необходимо использовать естественные языки для бизнес правил, модульных тестов, или BBD стиля исполняемой документации типа Cucumber. ```fsharp let ``is first time customer?`` = true @@ -260,4 +262,4 @@ let [] ``I have (.*) N products in my cart`` (n:int) = > Unlike C#, the naming convention for F# is that functions and values start with lowercase letters rather than uppercase (`camelCase` rather than `PascalCase`) unless designed for exposure to other .NET languages. Types and modules use uppercase however. -В отличии от C#, конвенция именования F# требует, чтобы функции и значения начинались со строчной буквы, а не прописной (`camelCase`, а не `PascalCase`), кроме тех случаев, когда они предназначены для _взаимодействия с другими_ языками .NET. Однако типы и модули используют заглавные буквы (в начале). \ No newline at end of file +В отличии от C#, конвенция именования F# требует, чтобы функции и значения начинались со строчной буквы, а не прописной (`camelCase`, а не `PascalCase`), кроме тех случаев, когда они предназначены для _взаимодействия с другими_ языками .NET. Однако типы и модули используют заглавные буквы (в начале). From 363f970a67f96efbdb358e94cfa486e3392d4e69 Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Tue, 11 Sep 2018 01:09:39 +0300 Subject: [PATCH 22/48] Update how-types-work-with-functions.md --- posts/how-types-work-with-functions.md | 122 ++++++++++++------------- 1 file changed, 60 insertions(+), 62 deletions(-) diff --git a/posts/how-types-work-with-functions.md b/posts/how-types-work-with-functions.md index 2ee0d27..1a062d4 100644 --- a/posts/how-types-work-with-functions.md +++ b/posts/how-types-work-with-functions.md @@ -10,11 +10,11 @@ categories: [Types, Functions] > Now that we have some understanding of functions, we'll look at how types work with functions, both as domains and ranges. This is just an overview; the series ["understanding F# types"](../series/understanding-fsharp-types.md) will cover types in detail. -Теперь, когда у нас есть некоторое понимание функций, мы посмотрим как типы взаимодействуют с функциями, как domain-а, так и range. Это просто обзор, серия ["understanding F# types"](../series/understanding-fsharp-types.md) раскроет типы более подробно. +Теперь, когда у нас есть некоторое понимание функций, мы посмотрим как типы взаимодействуют с функциями, как domain-а, так и range. Данная статья просто обзор, а для более глубокого погруженеия в типы, есть серия ["understanding F# types"](../series/understanding-fsharp-types.md). > First, we need to understand the type notation a bit more. We've seen that the arrow notation "`->`" is used to show the domain and range. So that a function signature always looks like: -Сперва, нам надо чуть лучше понять нотацию типов. Мы видели стрелочную нотацию "`->`" разделяющую domain и range. Так что сигнатура функций всегда выглядит вот так: +Для начала, нам надо чуть лучше понять нотацию типов. Мы видели стрелочную нотацию "`->`" разделяющую domain и range. Так что сигнатура функций всегда выглядит вот так: ```fsharp val functionName : domain -> range @@ -22,10 +22,10 @@ val functionName : domain -> range > Here are some example functions: -Еще несколько примеров функций: +Еще пара примеров функций с примитивными типами: ```fsharp -let intToString x = sprintf "x is %i" x // format int to string +let intToString x = sprintf "x is %i" x // форматирует int в string let stringToInt x = System.Int32.Parse(x) ``` @@ -59,8 +59,8 @@ val stringToInt : string -> int Функции с примитивными типами: ```fsharp -let intToFloat x = float x // "float" fn. converts ints to floats -let intToBool x = (x = 2) // true if x equals 2 +let intToFloat x = float x // "float" ф-ция конвертирует int во float +let intToBool x = (x = 2) // true если x равен 2 let stringToString x = x + " world" ``` @@ -95,7 +95,7 @@ let stringLength (x:string) = x.Length > The parens around the `x:string` param are important. If they are missing, the compiler thinks that the return value is a string! That is, an "open" colon is used to indicate the type of the return value, as you can see in the example below. -Скобки вокруг параметра `x:string` важны. Если они будут пропущены, то компилятор решит, что строкой является возвращаемое значение! То есть, "открытое" двоеточие используется для обозначения типа возвращаемого значения, как видно в следующем примере. +Скобки вокруг параметра `x:string` важны. Если они будут пропущены, то компилятор решит, что строкой является возвращаемое значение! То есть, "открытое" двоеточие используется для обозначения типа возвращаемого значения, как показано в следующем примере. ```fsharp let stringLengthAsInt (x:string) :int = x.Length @@ -116,12 +116,12 @@ let stringLengthAsInt (x:string) :int = x.Length Рассмотрим функцию `evalWith5ThenAdd2`, которая принимает функцию в качестве параметра, затем вычисляет эту функцию от 5, и добавляет 2 к результату: ```fsharp -let evalWith5ThenAdd2 fn = fn 5 + 2 // same as fn(5) + 2 +let evalWith5ThenAdd2 fn = fn 5 + 2 // то же самое, что и fn(5) + 2 ``` > The signature of this function looks like this: -Сигнатура данной функции выглядит так: +Сигнатура этой функции выглядит так: ```fsharp val evalWith5ThenAdd2 : (int -> int) -> int @@ -129,20 +129,20 @@ val evalWith5ThenAdd2 : (int -> int) -> int > You can see that the domain is `(int->int)` and the range is `int`. What does that mean? It means that the input parameter is not a simple value, but a function, and what's more is restricted only to functions that map `ints` to `ints`. The output is not a function, just an int. -Вы можете видеть, что domain равен `(int->int)`, а range `int`. Что это значит? Это значит, что входным параметром является не простое значение, а функция, более того, ограниченная функциями из `int` в `int`. Выходное же значение не функция, просто `int`. +Можно увидеть, что domain равен `(int->int)`, а range `int`. Что это значит? Это значит, что входным параметром является не простое значение, а функция, более того, ограниченная функциями из `int` в `int`. Выходное же значение не функция, а просто `int`. > Let's try it: -> Попробуем: +Попробуем: ```fsharp -let add1 x = x + 1 // define a function of type (int -> int) -evalWith5ThenAdd2 add1 // test it +let add1 x = x + 1 // описываем ф-цию типа (int -> int) +evalWith5ThenAdd2 add1 // тестируем ее ``` > gives: -получим: +и получим: ```fsharp val add1 : int -> int @@ -155,15 +155,15 @@ val it : int = 8 > By the way, the special word "`it`" is used for the last thing that was evaluated; in this case the result we want. It's not a keyword, just a convention. -Кстати, специальное слово "`it`" используется для обозначения последнего вычисленного значения, в данном случае это результат, которого мы ждаил. Это не ключевое слово, это просто конвенция. +Кстати, специальное слово "`it`" используется для обозначения последнего вычисленного значения, в данном случае это результат, которого мы ждали. Это не ключевое слово, это просто конвенция. > Here's another one: > Другой случай: ```fsharp -let times3 x = x * 3 // a function of type (int -> int) -evalWith5ThenAdd2 times3 // test it +let times3 x = x * 3 // ф-ция типа (int -> int) +evalWith5ThenAdd2 times3 // пробуем ее ``` > gives: @@ -181,16 +181,16 @@ val it : int = 17 > Note that the input is sensitive to the types. If our input function uses `floats` rather than `ints`, it will not work. For example, if we have: -Следует учесть, что входные данные чувствительны к типам. Если передаваемая функция использует `float`, а не `int`, оно не сработает. Например, если у нас есть: +Следует учесть, что входные данные чувствительны к типам. Если передаваемая функция использует `float`, а не `int`, то ничего не получится. Например, если у нас есть: ```fsharp -let times3float x = x * 3.0 // a function of type (float->float) +let times3float x = x * 3.0 // ф-ция типа (float->float) evalWith5ThenAdd2 times3float ``` > Evaluating this will give an error: -Попытка скомпилировать вернет ошибку: +Компилятор, при попытке скомпилировать, вернет ошибку: ```fsharp error FS0001: Type mismatch. Expecting a int -> int but @@ -199,7 +199,7 @@ error FS0001: Type mismatch. Expecting a int -> int but > meaning that the input function should have been an `int->int` function. -сообщающую, что входная функция должна быть функцией `int->int`. +сообщающую, что входная функция должна быть функцией типа `int->int`. ### Functions as output | Функции как выходные данные ### @@ -213,7 +213,7 @@ let adderGenerator numberToAdd = (+) numberToAdd > The signature is: -Сигнатура: +Ее сигнатура: ```fsharp val adderGenerator : int -> (int -> int) @@ -221,7 +221,7 @@ val adderGenerator : int -> (int -> int) > which means that the generator takes an `int`, and creates a function (the "adder") that maps `ints` to `ints`. Let's see how it works: -говорящая, что генератор принимает `int` и создает функцию ("adder"), которая сопоставляет `ints` в `ints`. Посмотрим как это работает: +означает, что генератор принимает `int` и создает функцию ("adder"), которая сопоставляет `ints` в `ints`. Посмотрим как это работает: ```fsharp let add1 = adderGenerator 1 @@ -239,7 +239,7 @@ val add2 : (int -> int) > And we can now use these generated functions in the normal way. They are indistinguishable from functions defined explicitly -Теперь можно использовать сгенерируемые функции как обычные, они неотличимы от функций определенных явно: +Теперь можно использовать сгенерируемые функции как обычные, они ничем не отличаются от функций определенных явно: ```fsharp add1 5 // val it : int = 6 @@ -250,16 +250,16 @@ add2 5 // val it : int = 7 > In the first example, we had the function: -В первом примере была функция: +В первом примере мы рассмотрели функцию: ```fsharp let evalWith5ThenAdd2 fn = fn 5 +2 - => val evalWith5ThenAdd2 : (int -> int) -> int +> val evalWith5ThenAdd2 : (int -> int) -> int ``` > In this case F# could deduce that "`fn`" mapped `ints` to `ints`, so its signature would be `int->int` -В данном примере F# может сделать вывод, что "`fn`" преобразует `int` в `int`, поэтому сигнатура будет `int->int`. +В данном примере F# может сделать вывод, что "`fn`" преобразует `int` в `int`, поэтому ее сигнатура будет `int->int`. > But what is the signature of "fn" in this following case? @@ -288,22 +288,22 @@ let evalWith5AsString fn :string = fn 5 > Because the main function returns a string, the "`fn`" function is also constrained to return a string, so no explicit typing is required for "fn". -Т.к. основная функция возвращает `string`, функция "`fn`" также вынуждена возвращать `string`, таким образом не требуется явно типизировать "`fn`". +Т.к. основная функция возвращает `string`, функция "`fn`" также вынуждена возвращать `string`, таким образом не требуется явно указывать тип "`fn`". ## The "unit" type | Тип "unit" ## > When programming, we sometimes want a function to do something without returning a value. Consider the function "`printInt`", defined below. The function doesn't actually return anything. It just prints a string to the console as a side effect. -В процессе программирования мы иногда хотим, чтобы функция делала что-то не возвращая ничего. Рассмотрим функцию "`printInt`". Функция действительно ничего не возвращает. Она просто выводит строку на консоль как побочный эффект. +В процессе программирования мы иногда хотим, чтобы функция делала что-то не возвращая ничего. Рассмотрим функцию "`printInt`". Функция действительно ничего не возвращает. Она просто выводит строку на консоль как побочный эффект исполнения. ```fsharp -let printInt x = printf "x is %i" x // print to console +let printInt x = printf "x is %i" x // вывод на консоль ``` > So what is the signature for this function? -Какова ее сигнатура? +Какова же ее сигнатура? ```fsharp val printInt : int -> unit @@ -315,13 +315,13 @@ val printInt : int -> unit > Well, even if a function returns no output, it still needs a range. There are no "void" functions in mathematics-land. Every function must have some output, because a function is a mapping, and a mapping has to have something to map to! -Даже если функция не возвращает значений, она все еще нуждается в range. В мире математики не существует "void" функций. Каждая функция должна что-то возвращать, потому-что функция - это отображение, а отображение должно что-то отображать! +Даже если функция не возвращает значений, ей все еще нужен range. В мире математики не существует "void" функций. Каждая функция должна что-то возвращать, потому-что функция - это отображение, а отображение должно что-то отображать! ![](../assets/img/Functions_Unit.png) > So in F#, functions like this return a special range called "`unit`". This range has exactly one value in it, called "`()`". You can think of `unit` and `()` as somewhat like "void" (the type) and "null" (the value) in C#. But unlike void/null, `unit` is a real type and `()` is a real value. To see this, evaluate: -И так, в F# функции подобные этой возвращают специальный range называемый "`unit`". Данный range содержит только одно значение, называемое "`()`". Можно подумать, что `unit` и `()` что-то вроде "void" и "null" из C# соответственно. Но в отличии от них, `unit` является реальным типом, а `()` реальным значением. Чтобы убедиться в этом, достаточно выполнить: +И так, в F# функции подобные этой возвращают специальный range называемый "`unit`". Данный range содержит только одно значение, обозначаемое "`()`". Можно подумать, что `unit` и `()` что-то вроде "void" и "null" из C# соответственно. Но в отличии от них, `unit` является реальным типом, а `()` реальным значением. Чтобы убедиться в этом, достаточно выполнить: ```fsharp let whatIsThis = () @@ -329,7 +329,7 @@ let whatIsThis = () > and you will see the signature: -> будет получена следующая сигнатуру: +будет получена следующая сигнатура: ```fsharp val whatIsThis : unit = () @@ -337,11 +337,11 @@ val whatIsThis : unit = () > Which means that the value "`whatIsThis`" is of type `unit` and has been bound to the value `()` -Которая указывает, что метка "`whatIsThis`" принадлежит типу `unit` и было связано со значением `()`. +Которая указывает, что метка "`whatIsThis`" принадлежит типу `unit` и связано со значением `()`. > So, going back to the signature of "`printInt`", we can now understand it: -Теперь вернувшись к сигнатуре "`printInt`", можно понять значение это записи: +Теперь вернувшись к сигнатуре "`printInt`", можно понять значение этой записи: ```fsharp val printInt : int -> unit @@ -349,9 +349,7 @@ val printInt : int -> unit > This signature says: `printInt` has a domain of `int` which it maps onto nothing that we care about. -Данная сигнатура говорит, что `printInt` имеет domain из `int`, который который преобразуется в нечто, что нас не интересует. - - +Данная сигнатура говорит, что `printInt` имеет domain из `int`, который преобразуется в нечто, что нас не интересует. ### Parameterless functions | Функции без параметров @@ -360,7 +358,7 @@ val printInt : int -> unit Теперь, когда мы понимаем `unit`, можем ли мы предсказать его появление в другом контексте? Например, попробуем создать многократно используемую функцию "hello world". Поскольку нет ни ввода, ни вывода, мы можем ожидать, сигнатуру `unit -> unit`. Посмотрим: ```fsharp -let printHello = printf "hello world" // print to console +let printHello = printf "hello world" // вывод на консоль ``` > The result is: @@ -374,7 +372,7 @@ val printHello : unit = () > Not quite what we expected. "Hello world" is printed immediately and the result is not a function, but a simple value of type unit. As we saw earlier, we can tell that this is a simple value because it has a signature of the form: -_Не совсем то_, что мы ожидали. "Hello world" было выведено немедленно, а результатом стала не функция, а простое значение типа unit. Как мы видели ранее, мы можем сказать, что это простое значение, поскольку оно имеет сигнатуру вида: +_Не совсем то_, что мы ожидали. "Hello world" было выведено немедленно, а результатом стала не функция, а простое значение типа unit. Мы можем сказать, что это простое значение, поскольку, как мы видели ранее, оно имеет сигнатуру вида: ```fsharp val aName: type = constant @@ -386,19 +384,19 @@ val aName: type = constant > Why the difference between `printInt` and `printHello`? In the `printInt` case, the value could not be determined until we knew the value of the x parameter, so the definition was of a function. In the `printHello` case, there were no parameters, so the right hand side could be determined immediately. Which it was, returning the `()` value, with the side effect of printing to the console. -В чем разница между `printInt` и `printHello`? В случае `printInt`, значение не может быть определено, пока мы не узнаем значения параметра `x`, поэтому определение было функцией. В случае `printHello`, нет параметров, поэтому правая часть может быть определена на месте. И она была равна `()`, с side-эффектом в виде вывода на консоль. +В чем разница между `printInt` и `printHello`? В случае с `printInt`, значение не может быть определено, пока мы не узнаем значения параметра `x`, поэтому определение было функцией. В случае `printHello`, нет параметров, поэтому правая часть может быть определена на месте. И она была равна `()`, с побочным эффектом в виде вывода на консоль. > We can create a true reusable function that is parameterless by forcing the definition to have a unit argument, like this: Можно создать настоящую многократно используемую функцию без параметров, заставляя определение иметь `unit` аргумент: ```fsharp -let printHelloFn () = printf "hello world" // print to console +let printHelloFn () = printf "hello world" // вывод на консоль ``` > The signature is now: -Ее сигнатура равна: +Теперь ее сигнатура равна: ```fsharp val printHelloFn : unit -> unit @@ -428,7 +426,7 @@ let something = > To help in these situations, there is a special function `ignore` that takes anything and returns the unit type. The correct version of this code would be: -Чтобы помочь в данных ситуациях, существует специальная функция `ignore`, которая принимает что угодно и возвращает `unit`. Правильная версия данного кода могла бы быть такой: +Чтобы помочь в данных ситуациях, существует специальная функция `ignore`, которая принимает что угодно и возвращает `unit`. Корректная версия данного кода могла бы быть такой: ```fsharp do (1+1 |> ignore) // ok @@ -471,14 +469,14 @@ val onAStick : 'a -> string ```csharp string onAStick(); -//or more idiomatically -string OnAStick(); // F#'s use of 'a is like - // C#'s "TObject" convention +//или более идиоматично +string OnAStick(); // F#-еры используют написание 'a так же как + // C#'-еры используют написание "TObject" по конвенции ``` > Note that the F# function is still strongly typed with a generic type. It does *not* take a parameter of type `Object`. This strong typing is desirable so that when functions are composed together, type safety is still maintained. -Надо понимать, что данная F# функция все еще обладает строгой типизацией даже с обобщенными типами. Она *не* принимает параметр типа `Object`. Строгая типизация желаема, ибо позволяет при композиции функций вместе сохранять типобезопасность. +Надо понимать, что данная F# функция все еще обладает строгой типизацией даже с обобщенными типами. Она *не* принимает параметр типа `Object`. Строгая типизация хороша, ибо позволяет при композиции функций сохранять их типобезопасность. > Here's the same function being used with an int, a float and a string @@ -516,7 +514,7 @@ let isEqual x y = (x=y) > So the function signature has the same generic type for both of them: -Так сигнатура функции имеет одинаковый обобщенный тим для обоих параметров: +Так сигнатура функции имеет одинаковый обобщенный тип для обоих параметров: ```fsharp val isEqual : 'a -> 'a -> bool @@ -524,13 +522,13 @@ val isEqual : 'a -> 'a -> bool > Generic parameters are also very important when it comes to lists and more abstract structures, and we will be seeing them a lot in upcoming examples. -Обобщенные параметры также очень важны, когда дело касается списков и других абстрактных структур, и мы будем видеть их много в последующих примерах. +Обобщенные параметры также очень важны, когда дело касается списков и других абстрактных структур, и мы увидим их достаточно много в последующих примерах. ## Other types | Другие типы ## > The types discussed so far are just the basic types. These types can be combined in various ways to make much more complex types. A full discussion of these types will have to wait for [another series](../series/understanding-fsharp-types.md), but meanwhile, here is a brief introduction to them so that you can recognize them in function signatures. -До сих пор обсуждались только базовые типы. Данные типы могут быть скомбинированы различными способами в более сложные типы. Полный их разбор будет позднее в [другой серии](../series/understanding-fsharp-types.md), но между тем, здесь кратко их разберем, так что бы можно было распознать их в сигнатуре функции. +До сих пор обсуждались только базовые типы. Данные типы могут быть скомбинированы различными способами в более сложные типы. Полный их разбор будет позднее в [другой серии](../series/understanding-fsharp-types.md), но между тем, здесь кратко их разберем, так что бы можно было распознать их в сигнатурах функций. > * **The "tuple" types**. These are pairs, triples, etc., of other types. For example `("hello", 1)` is a tuple made from a string and an int. The comma is the distinguishing characteristic of a tuple -- if you see a comma in F#, it is almost certainly part of a tuple! @@ -546,18 +544,18 @@ string * int // ("hello", 1) > * **The collection types**. The most common of these are lists, sequences, and arrays. Lists and arrays are fixed size, while sequences are potentially infinite (behind the scenes, sequences are the same as `IEnumerable`). In function signatures, they have their own keywords: "`list`", "`seq`", and "`[]`" for arrays. -* **Коллекции**. Наиболее распространенные из них - list (список), seq (перечисление) и массив. Списки и массивы имеют фиксированный размер, в то время как последовательности потенциально бесконечны (за кулисами последовательности - это те же самые `IEnumrable`). В сигнатурах функций они имеют свои собственные ключевые слова: "`list`", "`seq`" и "`[]`" для массивов. +* **Коллекции**. Наиболее распространенные из них - list (список), seq (последовательность) и массив. Списки и массивы имеют фиксированный размер, в то время как последовательности потенциально бесконечны (за кулисами последовательности - это те же самые `IEnumrable`). В сигнатурах функций они имеют свои собственные ключевые слова: "`list`", "`seq`" и "`[]`" для массивов. ```fsharp -int list // List type e.g. [1;2;3] -string list // List type e.g. ["a";"b";"c"] -seq // Seq type e.g. seq{1..10} -int [] // Array type e.g. [|1;2;3|] +int list // List type например [1;2;3] +string list // List type например ["a";"b";"c"] +seq // Seq type например seq{1..10} +int [] // Array type например [|1;2;3|] ``` > * **The option type**. This is a simple wrapper for objects that might be missing. There are two cases: `Some` and `None`. In function signatures, they have their own "`option`" keyword: -* **Option (опциональный тип)**. Это простая обертка над объектами, которые могут отсутствовать. Существует два варианта: `Some` и `None`. В сигнатурах функций они имеют свое собственное ключевое слово "`option`": +* **Option (опциональный тип)**. Это простая обертка над объектами, которые могут отсутствовать. Имеется два варианта: `Some` (когда значение существует) и `None`(когда значения нет). В сигнатурах функций они имеют свое собственное ключевое слово "`option`": ```fsharp int option // Some(1) @@ -567,7 +565,7 @@ int option // Some(1) > * **The record type**. These are like structures or database rows, a list of named slots. We saw some examples of this in the ["why use F#?"](../series/why-use-fsharp.md) series as well. In function signatures, they are referred to by the name of the type, so again there is no special keyword. * **Размеченное объединение (discriminated union)**. Они построены из множества вариантов других типов. Мы видели некоторые примеры в ["why use F#?"](../series/why-use-fsharp.md). В сигнатурах функций на них ссылаются по имени типа, они не имеют специального ключевого слова. -* **Record тип (записи)**. Подобные структурам или строкам баз данных, списки именованных слотов. Мы также видели несколько примеров в ["why use F#?"](../series/why-use-fsharp.md). В сигнатурах функций они называются по имени типа, они также не имеют своего ключевого слова. +* **Record тип (записи)**. Типы подобные структурам или строкам баз данных, набор именованных значений. Мы также видели несколько примеров в ["why use F#?"](../series/why-use-fsharp.md). В сигнатурах функций они называются по имени типа, они также не имеют своего ключевого слова. ## Test your understanding of types | Проверьте свое понимание типов ## @@ -589,6 +587,6 @@ let testJ (x:int) = 2 * 2 |> ignore let testK = "hello" let testL() = "hello" let testM x = x=x -let testN x = x 1 // hint: what kind of thing is x? -let testO x:string = x 1 // hint: what does :string modify? -``` \ No newline at end of file +let testN x = x 1 // подсказка: что в данном случае x? +let testO x:string = x 1 // подсказка: что меняется при :string ? +``` From 204f205e43ec4cb72eb719dcb5489c167d5d1c1e Mon Sep 17 00:00:00 2001 From: kleidemos Date: Mon, 17 Sep 2018 00:46:34 +0500 Subject: [PATCH 23/48] =?UTF-8?q?=D0=92=D0=BD=D1=91=D1=81=20=D0=B1=D0=B5?= =?UTF-8?q?=D1=81=D1=81=D0=BF=D0=BE=D1=80=D0=BD=D1=83=D1=8E=20=D1=87=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D1=8C=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BE=D0=BA=20vsh?= =?UTF-8?q?apenko..?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/defining-functions.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/posts/defining-functions.md b/posts/defining-functions.md index 1e5a8a3..6da34de 100644 --- a/posts/defining-functions.md +++ b/posts/defining-functions.md @@ -183,7 +183,7 @@ let addConfusingTuple (x,y) = x + y > * The second definition, "`addTuple`", takes a single parameter. It then binds "x" and "y" to the inside of the tuple and does the addition. > * The third definition, "`addConfusingTuple`", takes a single parameter just like "`addTuple`", but the tricky thing is that the tuple is unpacked and bound as part of the parameter definition using pattern matching. Behind the scenes, it is exactly the same as "`addTuple`". -* Первое определение, "`addTwoParams`", принимает два параметра разделенных пробелом. +* Первое определение, "`addTwoParams`", принимает два параметра, разделенных пробелом. * Второе определение, "`addTuple`", принимает один параметр. Этот параметр привязывает "x" и "y" из кортежа и суммирует их. * Третье определение, "`addConfusingTuple`", принимает один параметр как и "`addTuple`", но трюк в том, что этот кортеж распаковывается и привязывается как часть определения параметра при помощи сопоставления с шаблоном. За кулисами все происходит точно так же как и в "`addTuple`". @@ -237,7 +237,7 @@ addConfusingTuple y // ok > Conversely, if you attempt to pass multiple arguments to a function expecting a tuple, you will also get an obscure error. -И наоборот, если попытаться передать множество аргументов в функцию ожидающую кортеж, можно также получить непонятную ошибку. +И наоборот, если попытаться передать множество аргументов в функцию, ожидающую кортеж, можно также получить непонятную ошибку. ```fsharp addConfusingTuple 1 2 // error trying to pass two args @@ -265,11 +265,11 @@ f (1,2,3) > Note that the function signature is different from a true three parameter function. There is only one arrow, so only one parameter, and the stars indicate that this is a tuple of `(int*int*int)`. -Следует обратить внимание, что сигнатура отличается от сигнатуры функции с тремя параметрами. Здесь только одна стрелка, один параметр и звездочки указывающие на кортеж `(int*int*int)`. +Следует обратить внимание, что сигнатура отличается от сигнатуры функции с тремя параметрами. Здесь только одна стрелка, один параметр и звездочки, указывающие на кортеж `(int*int*int)`. > When would we want to use tuple parameters instead of individual ones? -Когда возникает необходимость использовать кортеж параметров вместо индивидуальных значений? // TODO: переформулировать. +В каких случаях лучше использовать кортеж параметров вместо отдельных значений? > * When the tuples are meaningful in themselves. For example, if we are working with three dimensional coordinates, a three-tuple might well be more convenient than three separate dimensions. > * Tuples are occasionally used to bundle data together in a single structure that should be kept together. For example, the `TryParse` functions in .NET library return the result and a Boolean as a tuple. But if you have a lot of data that is kept together as a bundle, then you will probably want to define a record or class type to store it. @@ -339,7 +339,7 @@ let strCompareWithB = strCompare "B" > Here are some general guidelines of how to structure parameters when you are designing your own functions. -Общие рекомендации о том как структурировать параметры при проектировании собственных функций. +Общие рекомендации о том, как структурировать параметры при проектировании собственных функций. > * In general, it is always better to use separate parameters rather than passing them as a single structure such as a tuple or record. This allows for more flexible behavior such as partial application. > * But, when a group of parameters *must* all be set at once, then *do* use some sort of grouping mechanism. From 784ce2308be460e2be5f8badaa265e547cfb2add Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Sat, 10 Nov 2018 21:43:40 +0300 Subject: [PATCH 24/48] Update currying.md --- posts/currying.md | 133 +++++++++++++++++++++++----------------------- 1 file changed, 67 insertions(+), 66 deletions(-) diff --git a/posts/currying.md b/posts/currying.md index 8676b15..95dd2f0 100644 --- a/posts/currying.md +++ b/posts/currying.md @@ -18,10 +18,10 @@ categories: [Currying] > To see how this works in practice, let's use a very basic example that prints two numbers: -Чтобы увидеть, как это работает на практике, воспользуемся простейшим примером кода, печатающим два числа: +Чтобы увидеть, как каррирование работает на практике, воспользуемся простейшим примером кода, печатающим два числа: ```fsharp -//normal version +// нормальная функция let printTwoParameters x y = printfn "x=%i y=%i" x y ``` @@ -31,11 +31,11 @@ let printTwoParameters x y = На самом деле компилятор переписывает его приблизительно в такой форме: ```fsharp -//explicitly curried version -let printTwoParameters x = // only one parameter! +// каррирование описанное явно +let printTwoParameters x = // Только один параметр let subFunction y = - printfn "x=%i y=%i" x y // new function with one param - subFunction // return the subfunction + printfn "x=%i y=%i" x y // Новая подфункция, принимающая один параметр + subFunction // Возвращаем подфункцию ``` > Let's examine this in more detail: @@ -50,17 +50,17 @@ let printTwoParameters x = // only one parameter! 1. Объявляется функция с названием "`printTwoParameters`", но принимающая только _один_ параметр: "x". 2. Внутри неё создаётся локальная функция, которая также принимает только _один_ параметр: "y". Заметим, что локальная функция использует параметр "x", но x не передается в нее как аргумент. "x" находится в такой области видимости, что вложенная функция может видеть его и использовать без необходимости в его передаче. 3. Наконец, возвращается только что созданная локальная функция. -4. Возвращенная функция затем применяется к аргументу "y". Параметр "x" замыкается в ней, так что возвращаемая функция нуждается только в параметре y чтобы завершить свою логику. +4. Возвращенная функция затем применяется к аргументу "y". Параметр "x" замыкается в ней, так что возвращаемая функция нуждается только в параметре "y" чтобы завершить свою логику.. > By rewriting it this way, the compiler has ensured that every function has only one parameter, as required. So when you use "`printTwoParameters`", you might think that you are using a two parameter function, but it is actually only a one parameter function! You can see for yourself by passing only one argument instead of two: -Переписывая функции таким образом, компилятор гарантирует, что каждая функция принимает только один параметр, как и требовалось. Таким образом, используя "`printTwoParameters`", можно подумать, что это функция с двумя параметрами, но на самом деле используется функция с только одним параметром. В этом можно убедиться, передав ей лишь один аргумент вместо двух: +Переписывая функции таким образом компилятор гарантирует, что каждая функция принимает только один параметр, как и требовалось. Таким образом, используя "`printTwoParameters`", можно подумать, что это функция с двумя параметрами, но на самом деле используется функция с только одним параметром. В этом можно убедиться, передав ей лишь один аргумент вместо двух: ```fsharp -// eval with one argument +// выполним с одним аргументом printTwoParameters 1 -// get back a function! +// получим назад функцию val it : (int -> unit) = ``` @@ -82,20 +82,20 @@ val it : (int -> unit) = > Here is an example of the step by step version, and then the normal version again. -Вот пример пошаговой и обыкновенной версий: +Вот пример пошаговой и нормальной версий: ```fsharp -// step by step version +// Пошаговая версия let x = 6 let y = 99 -let intermediateFn = printTwoParameters x // return fn with - // x "baked in" +let intermediateFn = printTwoParameters x // вернем ф-цию с + // x в замыкании let result = intermediateFn y -// inline version of above +// однострочная версия пошагового исполнения let result = (printTwoParameters x) y -// normal version +// нормальная ф-ция let result = printTwoParameters x y ``` @@ -104,24 +104,24 @@ let result = printTwoParameters x y Вот другой пример: ```fsharp -//normal version +//Нормальная версия let addTwoParameters x y = x + y -//explicitly curried version -let addTwoParameters x = // only one parameter! +//явно каррированная версия +let addTwoParameters x = // только один параметр! let subFunction y = - x + y // new function with one param - subFunction // return the subfunction + x + y // новая подфункция с одним параметром + subFunction // возвращаем подфункцию -// now use it step by step +// теперь используем ее в пошаговом варианте let x = 6 let y = 99 -let intermediateFn = addTwoParameters x // return fn with - // x "baked in" +let intermediateFn = addTwoParameters x // возвращаем ф-цию с + // x в замыкании let result = intermediateFn y -// normal version +// Нормальная версия let result = addTwoParameters x y ``` @@ -142,36 +142,37 @@ let result = addTwoParameters x y Наконец, функция с двумя параметрами называемая `+` обрабатывается как любая другая функция с двумя параметрами. ```fsharp -// using plus as a single value function +// используем плюс как ф-цию вызванную с одним параметром let x = 6 let y = 99 -let intermediateFn = (+) x // return add with x baked in +let intermediateFn = (+) x // вернется ф-ция "добавить" с "х" в замыкании let result = intermediateFn y -// using plus as a function with two parameters +// используем плюс как ф-цию с двумя параметрами let result = (+) x y -// normal version of plus as infix operator +// нормальное использование плюса как инфиксного оператора let result = x + y ``` + > And yes, this works for all other operators and built in functions like printf. И да, это работает на все другие операторы и встроенные функции, такие как `printf`. ```fsharp -// normal version of multiply +// нормальная версия умножения let result = 3 * 5 -// multiply as a one parameter function -let intermediateFn = (*) 3 // return multiply with "3" baked in +// умножение как унарная ф-ция +let intermediateFn = (*) 3 // вернется "умножить" с 3 в замыкании let result = intermediateFn 5 -// normal version of printfn +// нормальнвя версия  printfn let result = printfn "x=%i y=%i" 3 5 -// printfn as a one parameter function -let intermediateFn = printfn "x=%i y=%i" 3 // "3" is baked in +// printfn как унарная ф-ция +let intermediateFn = printfn "x=%i y=%i" 3 // "3" в замыкании let result = intermediateFn 5 ``` @@ -179,7 +180,7 @@ let result = intermediateFn 5 > Now that we know how curried functions work, what should we expect their signatures to look like? -Теперь, когда мы знаем, как работают каррированные функции, что можно ожидать от их сигнатур? +Теперь, когда мы знаем, как работают каррированные функции, интересно на что будут похожт их сигнатуры? > Going back to the first example, "`printTwoParameters`", we saw that it took one argument and returned an intermediate function. The intermediate function also took one argument and returned nothing (that is, unit). So the intermediate function has type `int->unit`. In other words, the domain of `printTwoParameters` is `int` and the range is `int->unit`. Putting this together we see that the final signature is: @@ -239,17 +240,17 @@ let add2Params x y = (+) x y ```fsharp let multiParamFn (p1:int)(p2:bool)(p3:string)(p4:float)= - () //do nothing + () //ничего не делаем -let intermediateFn1 = multiParamFn 42 - // intermediateFn1 takes a bool - // and returns a new function (string -> float -> unit) +let intermediateFn1 = multiParamFn 42 // multoParamFn принимает int и возвращает (bool -> string -> float -> unit) + // intermediateFn1 принимает bool + // и возвращает (string -> float -> unit) let intermediateFn2 = intermediateFn1 false - // intermediateFn2 takes a string - // and returns a new function (float -> unit) + // intermediateFn2 принимает string + // и возвращает (float -> unit) let intermediateFn3 = intermediateFn2 "hello" - // intermediateFn3 takes a float - // and returns a simple value (unit) + // intermediateFn3 принимаетfloat + // и возвращает простое значение (unit) let finalResult = intermediateFn3 3.141 ``` @@ -277,20 +278,20 @@ val finalResult : unit = () Сигнатура функции может сообщить о том, сколько параметров принимает функция: достаточно подсчитать число стрелок вне скобок. Если функция принимает или возвращает другую функцию, будут еще стрелки, но они будут в скобках и их можно будет проигнорировать. Вот некоторые примеры: ```fsharp -int->int->int // two int parameters and returns an int +int->int->int // 2 параметра int возвращаем int -string->bool->int // first param is a string, second is a bool, - // returns an int +string->bool->int // первый параметро string, второй - bool, + // вернется int -int->string->bool->unit // three params (int,string,bool) - // returns nothing (unit) +int->string->bool->unit // три параметра (int,string,bool) + // ничего не вернется (unit) -(int->string)->int // has only one parameter, a function - // value (from int to string) - // and returns a int +(int->string)->int // только один параметр, функция + // (из int в string) + // и вернется int -(int->string)->(int->bool) // takes a function (int to string) - // returns a function (int to bool) +(int->string)->(int->bool) // принимает ф-цию (int в string) + // вернет ф-цию (int в bool) ``` @@ -305,7 +306,7 @@ int->string->bool->unit // three params (int,string,bool) Рассмотрим с виду безобидную функцию: ```fsharp -// create a function +// созданем ф-цию let printHello() = printfn "hello" ``` @@ -314,7 +315,7 @@ let printHello() = printfn "hello" Как думаете, что произойдет, если вызвать ее, как показано ниже? Выведется ли "hello" на консоль? Попробуйте догадаться до выполнения. Подсказка: посмотрите на сигнатуру функции. ```fsharp -// call it +// выхываем ее printHello ``` @@ -345,7 +346,7 @@ printfn "x=%i y=%i" x > If you didn't understand currying, this message would be very cryptic! All expressions that are evaluated standalone like this (i.e. not used as a return value or bound to something with "let") *must* evaluate to the unit value. And in this case, it is does *not* evaluate to the unit value, but instead evaluates to a function. This is a long winded way of saying that `printfn` is missing an argument. -Если нет понимания каррирования, данное сообщение может быть очень загадочным. Дело в том, что все выражения, которые вычисляются отдельно, как это (т.е. не используются как возвращаемое значение или привязка к чему-либо посредством "let") _должны_ вычисляться в `unit` значение. В данном случае, оно _не_ вычисляется в `unit` значение, но вместо этого возвращает функцию. Это длинный извилистый способ сказать, что `printfn` не хватает аргумента. +Если нет понимания каррирования, данное сообщение может быть очень загадочным. Дело в том, что все выражения, которые вычисляются отдельно, как это (т.е. не используются как возвращаемое значение или привязка к чему-либо посредством "let"), _должны_ вычисляться в `unit` значение. В данном случае, оно _не_ вычисляется в `unit` значение, но вместо этого возвращает функцию. Это длинный извилистый способ сказать, что `printfn` не хватает аргумента. > A common case of errors like this is when interfacing with the .NET library. For example, the `ReadLine` method of a `TextReader` must take a unit parameter. It is often easy to forget this and leave off the parens, in which case you do not get a compiler error immediately, but only when you try to treat the result as a string. @@ -354,14 +355,14 @@ printfn "x=%i y=%i" x ```fsharp let reader = new System.IO.StringReader("hello"); -let line1 = reader.ReadLine // wrong but compiler doesn't - // complain -printfn "The line is %s" line1 //compiler error here! +let line1 = reader.ReadLine // ошибка, но компилятор пропустит + +printfn "The line is %s" line1 //но здесь выдаст ошибку // ==> error FS0001: This expression was expected to have // type string but here has type unit -> string -let line2 = reader.ReadLine() //correct -printfn "The line is %s" line2 //no compiler error +let line2 = reader.ReadLine() //верно +printfn "The line is %s" line2 //без ошибок компиляции ``` > In the code above, `line1` is just a pointer or delegate to the `Readline` method, not the string that we expected. The use of `()` in `reader.ReadLine()` actually executes the function. @@ -390,7 +391,7 @@ printfn "hello %i %i" 42 43 44 > For example, in the last case, the compiler is saying that it expects the format argument to have three parameters (the signature `'a -> 'b -> 'c -> 'd` has three parameters) but it is given only two (the signature `'a -> 'b -> unit` has two parameters). -Например, в последнем случае компилятор сообщает, что ожидается форматирующая строка с тремя параметрами (сигнатура `'a -> 'b -> 'c -> 'd` имеет три параметра), но вместо этого получена строка с двумя (у сигнатуры `'a -> 'b -> unit` два параметра)_. +Например, в последнем случае компилятор сообщает, что ожидается форматирующая строка с тремя параметрами (сигнатура `'a -> 'b -> 'c -> 'd` имеет три параметра), но вместо этого получена строка с двумя (у сигнатуры `'a -> 'b -> unit` два параметра). > In cases not using `printf`, passing too many parameters will often mean that you end up with a simple value that you then try to pass a parameter to. The compiler will complain that the simple value is not a function. @@ -409,8 +410,8 @@ let x = add1 2 3 ```fsharp let add1 x = x + 1 -let intermediateFn = add1 2 //returns a simple value -let x = intermediateFn 3 //intermediateFn is not a function! +let intermediateFn = add1 2 //вернет простое значение +let x = intermediateFn 3 //intermediateFn не функция! // ==> error FS0003: This value is not a function // and cannot be applied -``` \ No newline at end of file +``` From 6947461624908a481dc188fca81c9e7b918a14e7 Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Sat, 10 Nov 2018 21:45:40 +0300 Subject: [PATCH 25/48] Update currying.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit удалил многоточие --- posts/currying.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/currying.md b/posts/currying.md index 95dd2f0..701e1d2 100644 --- a/posts/currying.md +++ b/posts/currying.md @@ -50,7 +50,7 @@ let printTwoParameters x = // Только один параметр 1. Объявляется функция с названием "`printTwoParameters`", но принимающая только _один_ параметр: "x". 2. Внутри неё создаётся локальная функция, которая также принимает только _один_ параметр: "y". Заметим, что локальная функция использует параметр "x", но x не передается в нее как аргумент. "x" находится в такой области видимости, что вложенная функция может видеть его и использовать без необходимости в его передаче. 3. Наконец, возвращается только что созданная локальная функция. -4. Возвращенная функция затем применяется к аргументу "y". Параметр "x" замыкается в ней, так что возвращаемая функция нуждается только в параметре "y" чтобы завершить свою логику.. +4. Возвращенная функция затем применяется к аргументу "y". Параметр "x" замыкается в ней, так что возвращаемая функция нуждается только в параметре "y" чтобы завершить свою логику. > By rewriting it this way, the compiler has ensured that every function has only one parameter, as required. So when you use "`printTwoParameters`", you might think that you are using a two parameter function, but it is actually only a one parameter function! You can see for yourself by passing only one argument instead of two: From 02e41532255839e3fbb365d0599917ffdf137ae9 Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Tue, 27 Nov 2018 21:02:21 +0300 Subject: [PATCH 26/48] =?UTF-8?q?=D0=BC=D0=B8=D0=BD=D0=BE=D1=80=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/partial-application.md | 78 ++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/posts/partial-application.md b/posts/partial-application.md index 4d30dfa..8cc9a9e 100644 --- a/posts/partial-application.md +++ b/posts/partial-application.md @@ -22,28 +22,28 @@ categories: [Currying, Partial Application] Несколько простых примеров для иллюстрации: ```fsharp -// create an "adder" by partial application of add -let add42 = (+) 42 // partial application +// Создаем "сумматор" с помощью частичного применения к функции + аргумента 42 +let add42 = (+) 42 // само частичное применение add42 1 add42 3 -// create a new list by applying the add42 function -// to each element +// создаем новый список через применение ф-ции +// к каждому элементу исходного списка [1;2;3] |> List.map add42 -// create a "tester" by partial application of "less than" -let twoIsLessThan = (<) 2 // partial application +// создаем предиккатную ф-цию с помощью частичного применения к функции "меньше" +let twoIsLessThan = (<) 2 // частичное применение twoIsLessThan 1 twoIsLessThan 3 -// filter each element with the twoIsLessThan function +// отфильтруем каждый элемент с ф-цией twoIsLessThan [1;2;3] |> List.filter twoIsLessThan -// create a "printer" by partial application of printfn +// создаем функцию "печать" с помощью частичного применения к ф-ции printfn let printer = printfn "printing param=%i" -// loop over each element and call the printer function -[1;2;3] |> List.iter printer +// итерируем список и вызываем ф-цию printer для каждого элемента +[1;2;3] |> List.iter printerprinter ``` > In each case, we create a partially applied function that we can then reuse in multiple contexts. @@ -55,18 +55,17 @@ let printer = printfn "printing param=%i" И конечно, частичное применение позволяет так же легко фиксировать параметры-функции. Вот несколько примеров: ```fsharp -// an example using List.map +// пример использования  List.map let add1 = (+) 1 -let add1ToEach = List.map add1 // fix the "add1" function - -// test +let add1ToEach = List.map add1 // фиксируем ф-цию "add1" с List.map +// тестируем add1ToEach [1;2;3;4] -// an example using List.filter +// пример с использованием List.filter let filterEvens = - List.filter (fun i -> i%2 = 0) // fix the filter function + List.filter (fun i -> i%2 = 0) // фиксируем фильтр ф-ции -// test +// тестируем filterEvens [1;2;3;4] ``` @@ -85,7 +84,7 @@ filterEvens [1;2;3;4] * И наконец, мы частично применяем основную функцию для создания новой функции, с замкнутым логгером. ```fsharp -// create an adder that supports a pluggable logging function +// создаем сумматор который поддерживает встраевыемый логгер-функцию let adderWithPluggableLogger logger x y = logger "x" x logger "y" y @@ -93,23 +92,23 @@ let adderWithPluggableLogger logger x y = logger "x+y" result result -// create a logging function that writes to the console +// создаем логгер-функцию которая выводит лог на консоль let consoleLogger argName argValue = printfn "%s=%A" argName argValue -//create an adder with the console logger partially applied +// создаем сумматор с логером на консоль через частичное применение ф-ции let addWithConsoleLogger = adderWithPluggableLogger consoleLogger addWithConsoleLogger 1 2 addWithConsoleLogger 42 99 -// create a logging function that creates popup windows +// создаем логгер-функцию кс выводом во всплывающее окно let popupLogger argName argValue = let message = sprintf "%s=%A" argName argValue System.Windows.Forms.MessageBox.Show( text=message,caption="Logger") |> ignore -//create an adder with the popup logger partially applied +// создаем сумматор с логгер-фукцией во всплыввающее окно через частичное применение let addWithPopupLogger = adderWithPluggableLogger popupLogger addWithPopupLogger 1 2 addWithPopupLogger 42 99 @@ -120,10 +119,10 @@ addWithPopupLogger 42 99 Эти функции с замкнутым логгером могут быть использованы как любые другие функции. Например, мы можем создать частичное применение для прибавления 42, и затем передать его в списочную функцию, как мы делали для простой функции "`add42`". ```fsharp -// create a another adder with 42 baked in +// создаем еще один сумматор с частично примененным парамтром 42 let add42WithConsoleLogger = addWithConsoleLogger 42 [1;2;3] |> List.map add42WithConsoleLogger -[1;2;3] |> List.map add42 //compare without logger +[1;2;3] |> List.map add42 //сраваниваем с сумматором без логгераlogger ``` > These partially applied functions are a very useful tool. We can create library functions which are flexible (but complicated), yet make it easy to create reusable defaults so that callers don't have to be exposed to the complexity all the time. @@ -188,7 +187,7 @@ sortDesc [0;1;2;3] Следование второму совету облегчает использование оператора конвейеризации и композиции. Мы уже наблюдали это много раз в примерах с функциями над списками. ```fsharp -// piping using list functions +// использование конвеерной ф-ции со списком и ф-циями обработки списков let result = [1..10] |> List.map (fun i -> i+1) @@ -216,7 +215,7 @@ let result = compositeOp [1..10] Однако, достаточно легко можно написать обертки, чтобы сделать эти функции более идиоматичными. В примере ниже строковые .NET функции переписаны так, чтобы целевая строка использовалась последней, а не первой: ```fsharp -// create wrappers for .NET string functions +// создает обертку вокруг стандартного .NET метода let replace oldStr newStr (s:string) = s.Replace(oldValue=oldStr, newValue=newStr) @@ -267,7 +266,7 @@ let (|>) x f = f x ```fsharp let doSomething x y z = x+y+z -doSomething 1 2 3 // all parameters after function +doSomething 1 2 3 // все параметры после функции ``` > If the function has multiple parameters, then it appears that the input is the final parameter. Actually what is happening is that the function is partially applied, returning a function that has a single parameter: the input @@ -277,6 +276,9 @@ _Если функция имеет несколько параметров, т ???? _Если функция принимает несколько параметров, то она выглядит так, будто входной параметр - последний. На самом деле функция применяется частично и возвращает функцию, которая принимает единственный параметр - input._ ??? +_Опубликован вариант_ +_В случае, когда функция `f` имеет несколько параметов, а в качестве входного значения `x` конвейеризации будет выступать последний параметр функции `f`. Фактически передаваемая функция `f` уже частично применена и ожидает лишь один параметр — входное значение для конвейеризации (т е `x`)._ + > Here's the same example rewritten to use partial application Вот аналогичный пример, переписанный с целью частичного применения @@ -284,11 +286,11 @@ _Если функция имеет несколько параметров, т ```fsharp let doSomething x y = let intermediateFn z = x+y+z - intermediateFn // return intermediateFn + intermediateFn // возвращаем intermediateFn let doSomethingPartial = doSomething 1 2 -doSomethingPartial 3 // only one parameter after function now -3 |> doSomethingPartial // same as above - last parameter piped in +doSomethingPartial 3 // теперь только один параметр после ф-ции +3 |> doSomethingPartial // тоже что и выше, но теперь последний параметр конвеиризован в ф-цию ``` > As you have already seen, the pipe operator is extremely common in F#, and used all the time to preserve a natural flow. Here are some more usages that you might see: @@ -296,8 +298,8 @@ doSomethingPartial 3 // only one parameter after function now Как вы уже видели, конвейерный оператор чрезвычайно распространен в F#, и используется всякий раз, когда требуется сохранить естественный поток данных. Еще несколько примеров, которые вы возможно встречали: ```fsharp -"12" |> int // parses string "12" to an int -1 |> (+) 2 |> (*) 3 // chain of arithmetic +"12" |> int // парсит строку "12" в int +1 |> (+) 2 |> (*) 3 // арифметическая цепочка ``` ### The reverse pipe function | Обратный конвейерный оператор ### @@ -319,9 +321,9 @@ let (<|) f x = f x Причина заключается в том, что когда обратный конвейерный оператор используется как бинарный оператор в инфиксном стиле, он снижает потребность в скобках, что делает код чище. ```fsharp -printf "%i" 1+2 // error -printf "%i" (1+2) // using parens -printf "%i" <| 1+2 // using reverse pipe +printf "%i" 1+2 // ошибка +printf "%i" (1+2) // использование скобок +printf "%i" <| 1+2 // использование обратного конвейера ``` > You can also use piping in both directions at once to get a pseudo infix notation. @@ -330,6 +332,6 @@ printf "%i" <| 1+2 // using reverse pipe ```fsharp let add x y = x + y -(1+2) add (3+4) // error -1+2 |> add <| 3+4 // pseudo infix -``` \ No newline at end of file +(1+2) add (3+4) // ошибка +1+2 |> add <| 3+4 // псевдоинфиксная запись +``` From 241ac4fcfec1358a3625cfd6999d2d7eace60e7f Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Wed, 5 Dec 2018 21:50:23 +0300 Subject: [PATCH 27/48] Update function-composition.md --- posts/function-composition.md | 42 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/posts/function-composition.md b/posts/function-composition.md index f9ffc32..e551f5e 100644 --- a/posts/function-composition.md +++ b/posts/function-composition.md @@ -61,8 +61,8 @@ let F x y z = (x y) z ```fsharp let F x y z = x (y z) -let F x y z = y z |> x // using forward pipe -let F x y z = x <| y z // using backward pipe +let F x y z = y z |> x // использование прямого конвейера +let F x y z = x <| y z // использование обратного конвейера ``` > As an exercise, work out the signatures for these functions without actually evaluating them! @@ -86,8 +86,8 @@ let F x y z = x <| y z // using backward pipe Например: ```fsharp -let f (x:int) = float x * 3.0 // f is int->float -let g (x:float) = x > 4.0 // g is float->bool +let f (x:int) = float x * 3.0 // f это ф-ция типа int->float +let g (x:float) = x > 4.0 // g это ф-ция типа float->bool ``` > We can create a new function h that takes the output of "f" and uses it as the input for "g". @@ -97,7 +97,7 @@ let g (x:float) = x > 4.0 // g is float->bool ```fsharp let h (x:int) = let y = f(x) - g(y) // return output of g + g(y) // возвращаем результат вызова g ``` > A much more compact way is this: @@ -105,7 +105,7 @@ let h (x:int) = Чуть более компактно: ```fsharp -let h (x:int) = g ( f(x) ) // h is int->bool +let h (x:int) = g ( f(x) ) // h это ф-ция типа int->bool //test h 1 @@ -213,7 +213,7 @@ add5Times3 1 Пока соответствующие входы и выходы функций совпадают, функции могут использовать любые значения. Например, рассмотрим следующий код, который выполняет функцию дважды: ```fsharp -let twice f = f >> f //signature is ('a -> 'a) -> ('a -> 'a) +let twice f = f >> f //сигнатура ('a -> 'a) -> ('a -> 'a) ``` > Note that the compiler has deduced that the function f must use the same type for both input and output. @@ -225,8 +225,8 @@ let twice f = f >> f //signature is ('a -> 'a) -> ('a -> 'a) Теперь рассмотрим функцию "`+`". Как мы видели ранее, ввод является `int`-ом, но вывод в действительности - `(int->int)`. Таким образом "`+`" может быть использована в "`twice`". Поэтому можно написать: ```fsharp -let add1 = (+) 1 // signature is (int -> int) -let add1Twice = twice add1 // signature is also (int -> int) +let add1 = (+) 1 // сигнатура (int -> int) +let add1Twice = twice add1 // сигнатура так же (int -> int) //test add1Twice 9 @@ -250,7 +250,7 @@ let addThenMultiply = (+) >> (*) ```fsharp let add1ThenMultiply = (+) 1 >> (*) -// (+) 1 has signature (int -> int) and output is an 'int' +// (+) 1 с сигнатурой (int -> int) и результатом 'int' //test add1ThenMultiply 2 7 @@ -271,9 +271,9 @@ times2Add1 3 ```fsharp let myList = [] -myList |> List.isEmpty |> not // straight pipeline +myList |> List.isEmpty |> not // прямой конвейер -myList |> (not << List.isEmpty) // using reverse composition +myList |> (not << List.isEmpty) // использование обратной композиции ``` ## Composition vs. pipeline | Композиция vs. конвейер ## @@ -296,8 +296,8 @@ let (|>) x f = f x ```fsharp let doSomething x y z = x+y+z -doSomething 1 2 3 // all parameters after function -3 |> doSomething 1 2 // last parameter piped in +doSomething 1 2 3 // все параметры указаны после ф-ции +3 |> doSomething 1 2 // последний параметр конвейеризован в ф-цию ``` > Composition is not the same thing and cannot be a substitute for a pipe. In the following case the number 3 is not even a function, so its "output" cannot be fed into `doSomething`: @@ -305,9 +305,9 @@ doSomething 1 2 3 // all parameters after function Композиция не тоже самое и не может быть заменой пайпу. В следующем примере число 3 даже не функция, поэтому "вывод" не может быть передан в `doSomething`: ```fsharp -3 >> doSomething 1 2 // not allowed -// f >> g is the same as g(f(x)) so rewriting it we have: -doSomething 1 2 ( 3(x) ) // implies 3 should be a function! +3 >> doSomething 1 2 // ошибка +// f >> g то же самое что и g(f(x)) так что можем переписать это: +doSomething 1 2 ( 3(x) ) // подразумевается что 3 должно быть ф-цией! // error FS0001: This expression was expected to have type 'a->'b // but here has type int ``` @@ -333,12 +333,12 @@ let add1Times2 = add 1 >> times 2 Попытки использовать конвейер вместо композиции обернутся ошибкой компиляции. В следующем примере "`add 1`" - это (частичная) функция `int->int`, которая не может быть использована в качестве второго параметра для "`times 2`". ```fsharp -let add1Times2 = add 1 |> times 2 // not allowed -// x |> f is the same as f(x) so rewriting it we have: -let add1Times2 = times 2 (add 1) // add1 should be an int +let add1Times2 = add 1 |> times 2 // ошибка +// x |> f то же самое что и f(x) так что можем переписать это: +let add1Times2 = times 2 (add 1) // add1 должно быть 'int' // error FS0001: Type mismatch. 'int -> int' does not match 'int' ``` > The compiler is complaining that "`times 2`" should take an `int->int` parameter, that is, be of type `(int->int)->'a`. -Компилятор пожалуется, что "`times 2`" необходимо принимать параметр `int->int`, т.е. быть функцией `(int->int)->'a`. \ No newline at end of file +Компилятор пожалуется, что "`times 2`" необходимо принимать параметр `int->int`, т.е. быть функцией `(int->int)->'a`. From c8e11eee03856c7df0cc62a9eb2bf8fa61c0156e Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Wed, 12 Dec 2018 13:03:20 +0300 Subject: [PATCH 28/48] Update defining-functions.md --- posts/defining-functions.md | 221 ++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 111 deletions(-) diff --git a/posts/defining-functions.md b/posts/defining-functions.md index 6da34de..dd57a1b 100644 --- a/posts/defining-functions.md +++ b/posts/defining-functions.md @@ -8,6 +8,7 @@ seriesOrder: 8 categories: [Functions, Combinators] --- +# Defining function | Опрделение функций > We have seen how to create typical functions using the "let" syntax, below: @@ -21,11 +22,11 @@ let add x y = x + y В этой статье мы рассмотрим некоторые другие способы создания функций, а также советы по их определению. -## Anonymous functions (a.k.a. lambdas) | Анонимные функции (лямбды) ## +## Anonymous functions (a.k.a. lambdas) | Анонимные функции (лямбды) > If you are familiar with lambdas in other languages, this will not be new to you. An anonymous function (or "lambda expression") is defined using the form: -Если вы знакомы с лямбдами в других языках, эта тема не станет новой. Анонимные функции (или "лямбда выражения") определяются посредством следующей формы: +Если вы знакомы с лямбдами в других языках, эта тема не станет новой. Анонимные функции (или "лямбда выражения") определяются в следующей форме: ```fsharp fun parameter1 parameter2 etc -> expression @@ -51,7 +52,7 @@ let add = fun x y -> x + y > This is exactly the same as a more conventional function definition: -Та же функция в традиционной форме: +Это точно такая же функция, которая в более традиционной форме выглядит вот так: ```fsharp let add x y = x + y @@ -59,14 +60,14 @@ let add x y = x + y > Lambdas are often used when you have a short expression and you don't want to define a function just for that expression. This is particularly common with list operations, as we have seen already. -Лямбды часто используются, когда есть короткое выражение и нет желания определять для него отдельную функцию. Что особенно характерно для операций со списком, как было показано ранее. +Лямбды часто используются, когда есть короткое выражение и нет желания определять для него отдельную функцию. Что особенно характерно для операций со списком, их мы рассмотрели ранее. ```fsharp -// with separately defined function +// отдельно описанная ф-ция let add1 i = i + 1 [1..10] |> List.map add1 -// inlined without separately defined function +// лямбда ф-ция переданная без описания отдельной ф-ции [1..10] |> List.map (fun i -> i + 1) ``` @@ -76,13 +77,13 @@ let add1 i = i + 1 > Lambdas are also used when you want to make it clear that you are returning a function from another function. For example, the "`adderGenerator`" function that we talked about earlier could be rewritten with a lambda. -Лямбды также используются когда необходимо показать явно, что из функции возвращается другая функция. Например, "`adderGenerator`" который обсуждался ранее может быть переписан с помощью лямбды. +Лямбды также используются когда необходимо показать явно, что вы из функции возвращаете другую функцию. Например, "`adderGenerator`" который мы [обсуждали ранее](https://habr.com/company/microsoft/blog/422115/) может быть переписан с помощью лямбды. ```fsharp -// original definition +// изначальное определение let adderGenerator x = (+) x -// definition using lambda +// определение через лямбда ф-цию let adderGenerator x = fun y -> x + y ``` @@ -90,9 +91,9 @@ let adderGenerator x = fun y -> x + y Лямбда версия немного длиннее, но позволяет сразу понять, что будет возвращена промежуточная функция. -> You can nest lambdas as well. Here is yet another definition of `adderGenerator`, this time using lambdas only. +> You can nest lambdas as well. Here is yet another definition of `adderGenerator`, this time using lambdas only. -Лямбды могут быть вложенными. Еще один пример определения `adderGenerator`, в этот раз чисто на лямбдах. +Лямбды могут быть вложенными. Еще один пример определения `adderGenerator`, в этот раз только на лямбдах. ```fsharp let adderGenerator = fun x -> (fun y -> x + y) @@ -100,19 +101,19 @@ let adderGenerator = fun x -> (fun y -> x + y) > Can you see that all three of the following definitions are the same thing? -Ясно ли вам, что все три следующих определения эквивалентны? +Теперь ведь очевидно, что все три следующих определения эквивалентны? ```fsharp -let adderGenerator1 x y = x + y +let adderGenerator1 x y = x + y let adderGenerator2 x = fun y -> x + y let adderGenerator3 = fun x -> (fun y -> x + y) ``` > If you can't see it, then do reread the [post on currying](../posts/currying.md). This is important stuff to understand! -Если нет, то перечитайте [главу о каррировании](../posts/currying.md). Это очень важно для понимания! +Если нет, то перечитайте [главу о каррировании](https://habr.com/company/microsoft/blog/430620/). Это очень важно для понимания! -## Pattern matching on parameters | Сопоставление параметров с шаблоном ## +## Pattern matching on parameters | Сопоставление параметров с шаблоном > When defining a function, you can pass an explicit parameter, as we have seen, but you can also pattern match directly in the parameter section. In other words, the parameter section can contain *patterns*, not just identifiers! @@ -123,29 +124,29 @@ let adderGenerator3 = fun x -> (fun y -> x + y) Следующий пример демонстрирует как использовать шаблоны в определении функции: ```fsharp -type Name = {first:string; last:string} // define a new type -let bob = {first="bob"; last="smith"} // define a value +type Name = {first:string; last:string} // описываем новый тип +let bob = {first="bob"; last="smith"} // описываем значение -// single parameter style -let f1 name = // pass in single parameter - let {first=f; last=l} = name // extract in body of function +// явно передаем один параметр +let f1 name = // передача параметра + let {first=f; last=l} = name // деконструируем параметр через шаблон printfn "first=%s; last=%s" f l -// match in the parameter itself -let f2 {first=f; last=l} = // direct pattern matching - printfn "first=%s; last=%s" f l +// использование шаблона +let f2 {first=f; last=l} = // сопоставление с образцом прямо в описании ф-ции + printfn "first=%s; last=%s" f l -// test +// тест f1 bob f2 bob ``` > This kind of matching can only occur when the matching is always possible. For example, you cannot match on union types or lists this way, because some cases might not be matched. -Данный вид сопоставления может происходить только тогда, когда соответствие всегда разрешимо. Например, нельзя подобным способом матчить типы объединения и списки, потому-что некоторые случаи не могут быть сопоставлены. +Данный вид сопоставления может происходить только тогда, когда соответствие всегда разрешимо. Например, нельзя подобным способом сопоставлять типы объединения и списки, потому-что некоторые случаи не могут быть сопоставлены. ```fsharp -let f3 (x::xs) = // use pattern matching on a list +let f3 (x::xs) = // используем сопоставление с образцом дял списка printfn "first element is=%A" x ``` @@ -153,9 +154,7 @@ let f3 (x::xs) = // use pattern matching on a list Будет получено предупреждение о неполноте шаблона. - - -## A common mistake: tuples vs. multiple parameters | Распространенная ошибка: кортежи vs. множество параметров ## +## A common mistake: tuples vs. multiple parameters | Распространенная ошибка: кортежи vs. множество параметров > If you come from a C-like language, a tuple used as a single function parameter can look awfully like multiple parameters. They are not the same thing at all! As I noted earlier, if you see a comma, it is probably part of a tuple. Parameters are separated by spaces. @@ -166,16 +165,16 @@ let f3 (x::xs) = // use pattern matching on a list Пример путаницы: ```fsharp -// a function that takes two distinct parameters +// функция которая принимает два параметра let addTwoParams x y = x + y -// a function that takes a single tuple parameter -let addTuple aTuple = +// функция которая принимает одина парамер - кортеж +let addTuple aTuple = let (x,y) = aTuple x + y -// another function that takes a single tuple parameter -// but looks like it takes two ints +// другая ф-ция которая принимает один картеж как параметр +// но выглядит так будто принимает два параметра let addConfusingTuple (x,y) = x + y ``` @@ -185,14 +184,14 @@ let addConfusingTuple (x,y) = x + y * Первое определение, "`addTwoParams`", принимает два параметра, разделенных пробелом. * Второе определение, "`addTuple`", принимает один параметр. Этот параметр привязывает "x" и "y" из кортежа и суммирует их. -* Третье определение, "`addConfusingTuple`", принимает один параметр как и "`addTuple`", но трюк в том, что этот кортеж распаковывается и привязывается как часть определения параметра при помощи сопоставления с шаблоном. За кулисами все происходит точно так же как и в "`addTuple`". +* Третье определение, "`addConfusingTuple`", принимает один параметр как и "`addTuple`", но трюк в том, что этот кортеж распаковывается(сопоставляется с образцом) и привязывается как часть определения параметра при помощи сопоставления с шаблоном. За кулисами все происходит точно так же как и в "`addTuple`". > Let's look at the signatures (it is always a good idea to look at the signatures if you are unsure) Посмотрите на сигнатуры (смотреть на сигнатуры - хорошая практика, если вы в чем то не уверены). ```fsharp -val addTwoParams : int -> int -> int // two params +val addTwoParams : int -> int -> int // два параметра val addTuple : int * int -> int // tuple->int val addConfusingTuple : int * int -> int // tuple->int ``` @@ -203,8 +202,8 @@ val addConfusingTuple : int * int -> int // tuple->int ```fsharp //test -addTwoParams 1 2 // ok - uses spaces to separate args -addTwoParams (1,2) // error trying to pass a single tuple +addTwoParams 1 2 // ok - используются пробелы для разделения параметров +addTwoParams (1,2) // error - передается только один кортеж // => error FS0001: This expression was expected to have type // int but here has type 'a * 'b ``` @@ -226,11 +225,11 @@ Then it complains that the first parameter of `addTwoParams` is an `int`, and we addTuple (1,2) // ok addConfusingTuple (1,2) // ok -let x = (1,2) +let x = (1,2) addTuple x // ok -let y = 1,2 // it's the comma you need, - // not the parentheses! +let y = 1,2 // нужна запятая, + // никаких скобо! addTuple y // ok addConfusingTuple y // ok ``` @@ -240,8 +239,8 @@ addConfusingTuple y // ok И наоборот, если попытаться передать множество аргументов в функцию, ожидающую кортеж, можно также получить непонятную ошибку. ```fsharp -addConfusingTuple 1 2 // error trying to pass two args -// => error FS0003: This value is not a function and +addConfusingTuple 1 2 // error - попытка передать два параметра в ф-цию принимающую один кортеж +// => error FS0003: This value is not a function and // cannot be applied ``` @@ -249,21 +248,21 @@ addConfusingTuple 1 2 // error trying to pass two args В этот раз, компилятор решил, что раз передаются два аргумента, `addConfusingTuple` должна быть каррируемой. Это значит, что "`addConfusingTuple 1`" является частичным применением и должно возвращать другую промежуточную функцию. Попытка вызвать промежуточную функцию на "2" выдает ошибку, т.к. здесь нет промежуточной функции! Мы видим ту же ошибку, что и в главе о каррировании, когда мы обсуждали проблемы связанные со слишком большим количеством параметров. -### Why not use tuples as parameters? | Почему бы не использовать кортежи в качестве параметров? ### +### Why not use tuples as parameters? | Почему бы не использовать кортежи в качестве параметров? -> The discussion of the issues with tuples above shows that there's another way to define functions with more than one parameter: rather than passing them in separately, all the parameters can be combined into a single composite data structure. In the example below, the function takes a single parameter, which is a tuple containing three items. +> The discussion of the issues with tuples above shows that there's another way to define functions with more than one parameter: rather than passing them in separately, all the parameters can be combined into a single composite data structure. In the example below, the function takes a single parameter, which is a tuple containing three items. Обсуждение кортежей выше показывает, что существует другой способ определения функций со множеством параметров: вместо передачи их по отдельности, все параметры могут быть собраны в виде одной структуры. В примере ниже, функция принимает один параметр, который является кортежем из трех элементов. ```fsharp let f (x,y,z) = x + y * z -// type is int * int * int -> int +// тип ф-ции int * int * int -> int -// test +// тест f (1,2,3) ``` -> Note that the function signature is different from a true three parameter function. There is only one arrow, so only one parameter, and the stars indicate that this is a tuple of `(int*int*int)`. +> Note that the function signature is different from a true three parameter function. There is only one arrow, so only one parameter, and the stars indicate that this is a tuple of `(int*int*int)`. Следует обратить внимание, что сигнатура отличается от сигнатуры функции с тремя параметрами. Здесь только одна стрелка, один параметр и звездочки, указывающие на кортеж `(int*int*int)`. @@ -275,23 +274,23 @@ f (1,2,3) > * Tuples are occasionally used to bundle data together in a single structure that should be kept together. For example, the `TryParse` functions in .NET library return the result and a Boolean as a tuple. But if you have a lot of data that is kept together as a bundle, then you will probably want to define a record or class type to store it. * Когда кортежи значимы сами по себе. Например, если производятся операции над трехмерными координатами, тройные кортежи могут быть более удобными чем три отдельных измерения. -* Кортежи иногда используются, чтобы объединить данные в единую структуру, которая должна сохраняться вместе. Например, `TryParse` методы из .NET библиотеки возвращают результат и булевую переменную в виде кортежа. Но если имеется достаточно большой объем данных передаваемых в связке, скорее всего он будет определен в виде записи или класса. +* Кортежи иногда используются, чтобы объединить данные в единую структуру, которая должна сохраняться вместе. Например, `TryParse` методы из .NET библиотеки возвращают результат и булевую переменную в виде кортежа. Но если имеется достаточно большой объем данных передаваемых в связке, скорее всего он будет определен в виде записи ([record](https://habr.com/company/microsoft/blog/422115/)) или класса. -### A special case: tuples and .NET library functions | Особые случай: кортежи и функции .NET библиотеки ### +### A special case: tuples and .NET library functions | Особые случай: кортежи и функции .NET библиотеки -> One area where commas are seen a lot is when calling .NET library functions! +> One area where commas are seen a lot is when calling .NET library functions! Одна из областей, где запятые встречаются очень часто, это вызов функций .NET библиотеки! -> These all take tuple-like arguments, and so these calls look just the same as they would from C#: +> These all take tuple-like arguments, and so these calls look just the same as they would from C#: Они все принимают кортежи и эти вызовы выглядят также как на C#: ```fsharp -// correct +// верно System.String.Compare("a","b") -// incorrect +// не верно System.String.Compare "a" "b" ``` @@ -312,22 +311,22 @@ System.String.Compare "a","b" // error > If you do want to partially apply .NET library functions, it is normally trivial to write wrapper functions for them, as we have [seen earlier](../posts/partial-application.md), and as shown below: -Если есть желание частично применить функции .NET, это можно сделать тривиально при помощи обертки над ней, как делалось [ранее](../posts/partial-application.md), или как показано ниже: +Если есть желание частично применить функции .NET, это можно сделать тривиально при помощи обертки над ней, как делалось [ранее](https://habr.com/company/microsoft/blog/430622/), или как показано ниже: ```fsharp -// create a wrapper function +// создаем ф-цию опбертку let strCompare x y = System.String.Compare(x,y) -// partially apply it +// частично применяем ее let strCompareWithB = strCompare "B" -// use it with a higher order function +// испольузем с ф-цией высшего порядка ["A";"B";"C"] -|> List.map strCompareWithB +|> List.map strCompareWithB ``` -## Guidelines for separate vs. grouped parameters | Руководство по выбору отдельных и cгруппированных параметров ## +## Guidelines for separate vs. grouped parameters | Руководство по выбору отдельных и cгруппированных параметров > The discussion on tuples leads us to a more general topic: when should function parameters be separate and when should they be grouped? @@ -356,38 +355,38 @@ let strCompareWithB = strCompare "B" Рассмотрим несколько примеров: ```fsharp -// Pass in two numbers for addition. -// The numbers are independent, so use two parameters +// Передача двух параметров дял сложения. +// Числе не завсит друг от друга, поэтому передаем их как два параметра let add x y = x + y -// Pass in two numbers as a geographical co-ordinate. -// The numbers are dependent, so group them into a tuple or record -let locateOnMap (xCoord,yCoord) = // do something +// Передаем в ф-цию два числа как географические координаты +// Числа тут зависят друг от дргуа, поэтому используем кортежи +let locateOnMap (xCoord,yCoord) = // код -// Set first and last name for a customer. -// The values are dependent, so group them into a record. +// Задаем имя и фамилию клиента +// Значения зависят друг от дргуа - группируем их в запись type CustomerName = {First:string; Last:string} -let setCustomerName aCustomerName = // good -let setCustomerName first last = // not recommended +let setCustomerName aCustomerName = // хорошо +let setCustomerName first last = // не рекомендуется -// Set first and last name and and pass the -// authorizing credentials as well. -// The name and credentials are independent, keep them separate -let setCustomerName myCredentials aName = //good +// Задааем имя и фамилию +// вместе с правами пользователя +// имя и права независимы, можем передавать их раздельно +let setCustomerName myCredentials aName = //хорошо ``` > Finally, do be sure to order the parameters appropriately to assist with partial application (see the guidelines in the earlier [post](../posts/partial-application.md)). For example, in the last function above, why did I put the `myCredentials` parameter ahead of the `aName` parameter? -Наконец, убедитесь, что порядок параметров поможет в частичном применении (смотрите руководство [здесь](../posts/partial-application.md)). Например, в почему я поместил `myCredentials` перед `aName` в последней функции? +Наконец, убедитесь, что порядок параметров поможет в частичном применении (смотрите руководство [здесь](https://habr.com/company/microsoft/blog/430622/)). Например, в почему я поместил `myCredentials` перед `aName` в последней функции? -## Parameter-less functions | Функции без параметров ## +## Parameter-less functions | Функции без параметров > Sometimes we may want functions that don't take any parameters at all. For example, we may want a "hello world" function that we can call repeatedly. As we saw in a previous section, the naive definition will not work. Иногда может понадобиться функция, которая не принимает никаких параметров. Например, нужна функция "hello world" которую можно вызывать многократно. Как было показано в предыдущей секции, наивное определение не работает. ```fsharp -let sayHello = printfn "Hello World!" // not what we want +let sayHello = printfn "Hello World!" // не то что я хотел ``` > The fix is to add a unit parameter to the function, or use a lambda. @@ -395,8 +394,8 @@ let sayHello = printfn "Hello World!" // not what we want Но это можно исправить, если добавить unit параметр к функции или использовать лямбду. ```fsharp -let sayHello() = printfn "Hello World!" // good -let sayHello = fun () -> printfn "Hello World!" // good +let sayHello() = printfn "Hello World!" // хорошо +let sayHello = fun () -> printfn "Hello World!" // хорошо ``` > And then the function must always be called with a unit argument: @@ -404,7 +403,7 @@ let sayHello = fun () -> printfn "Hello World!" // good После чего функция всегда должна вызываться с `unit` аргументом: ```fsharp -// call it +// вызов sayHello() ``` @@ -422,22 +421,22 @@ System.IO.Directory.GetCurrentDirectory() Запомните, вызывайте их с `unit` параметрами! -## Defining new operators | Определение новых операторов ## +## Defining new operators | Определение новых операторов > You can define functions named using one or more of the operator symbols (see the [F# documentation](http://msdn.microsoft.com/en-us/library/dd233204) for the exact list of symbols that you can use): Можно определять функции с использованием одного и более операторных символа (смотрите [документацию](http://msdn.microsoft.com/en-us/library/dd233204) для ознакомления со списком символов): ```fsharp -// define +// описываем let (.*%) x y = x + y + 1 ``` -> You must use parentheses around the symbols when defining them. +> You must use parentheses around the symbols when defining them. Необходимо использовать скобки вокруг символов для определения функции. -> Note that for custom operators that begin with `*`, a space is required; otherwise the `(*` is interpreted as the start of a comment: +> Note that for custom operators that begin with `*`, a space is required; otherwise the `(*` is interpreted as the start of a comment: Операторы начинающиеся с `*` требуют пробел между скобкой и `*`, т.к. в F# `(*` выполняет роль начала комментария (как `/*...*/` в C#): @@ -468,7 +467,7 @@ let result = 2 .*% 3 ```fsharp let (~%%) (s:string) = s.ToCharArray() -//use +//используем let result = %% "hello" ``` @@ -476,7 +475,7 @@ let result = %% "hello" В F# определение операторов достаточно частая операция, и многие библиотеки будут экспортировать операторы с именами типа `>=>`и `<*>`. -## Point-free style | Point-free стиль ## +## Point-free style | Point-free стиль > We have already seen many examples of leaving off the last parameter of functions to reduce clutter. This style is referred to as **point-free style** or **tacit programming**. @@ -487,17 +486,17 @@ let result = %% "hello" Вот несколько примеров: ```fsharp -let add x y = x + y // explicit +let add x y = x + y // явно let add x = (+) x // point free -let add1Times2 x = (x + 1) * 2 // explicit +let add1Times2 x = (x + 1) * 2 // явно let add1Times2 = (+) 1 >> (*) 2 // point free -let sum list = List.reduce (fun sum e -> sum+e) list // explicit +let sum list = List.reduce (fun sum e -> sum+e) list // явно let sum = List.reduce (+) // point free ``` -> There are pros and cons to this style. +> There are pros and cons to this style. У данного стиля есть свои плюсы и минусы. @@ -509,7 +508,7 @@ let sum = List.reduce (+) // point free Бесточечный стиль позволяет сосредоточиться на базовом алгоритме и выявить общие черты в коде. "`reduce`" функция использованная выше является хорошим примером. Эта тема будет обсуждаться в запланированной серии по обработке списков. -> On the other hand, too much point-free style can make for confusing code. Explicit parameters can act as a form of documentation, and their names (such as "list") make it clear what the function is acting on. +> On the other hand, too much point-free style can make for confusing code. Explicit parameters can act as a form of documentation, and their names (such as "list") make it clear what the function is acting on. С другой стороны, чрезмерное использование подобного стиля может сделать код малопонятным. Явные параметры действуют как документация и их имена (такие как "list") облегчают понимание того, что делает функция. @@ -517,7 +516,7 @@ let sum = List.reduce (+) // point free Как и все в программировании, лучшая рекомендация, предпочитайте тот подход, что обеспечивает наибольшую ясность. -## Combinators | Комбинаторы ## +## Combinators | Комбинаторы > The word "**combinator**" is used to describe functions whose result depends only on their parameters. That means there is no dependency on the outside world, and in particular no other functions or global value can be accessed at all. @@ -529,20 +528,20 @@ let sum = List.reduce (+) // point free > We have already seen some combinators already: the "pipe" operator and the "compose" operator. If you look at their definitions, it is clear that all they do is reorder the parameters in various ways -Мы уже видели несколько комбинаторов: "pipe" и оператор композиции. Если посмотреть на их определения, то понятно, что все, что они делают, это переупорядочивают параметры различными способами. +Мы уже видели несколько комбинаторов: "pipe"(конвейер) и оператор композиции. Если посмотреть на их определения, то понятно, что все, что они делают, это переупорядочивают параметры различными способами. ```fsharp -let (|>) x f = f x // forward pipe -let (<|) f x = f x // reverse pipe -let (>>) f g x = g (f x) // forward composition -let (<<) g f x = g (f x) // reverse composition +let (|>) x f = f x // прямой pipe +let (<|) f x = f x // обратный pipe +let (>>) f g x = g (f x) // прямая композиция +let (<<) g f x = g (f x) // обратная композиция ``` > On the other hand, a function like "printf", although primitive, is not a combinator, because it has a dependency on the outside world (I/O). С другой стороны, функции подобные "printf", хоть и примитивны, но не являются комбинаторами, потому-что имеют зависимость от внешнего мира (I/O). -### Combinator birds | ? Комбинаторные птички (TODO: Обсудить) ### +### Combinator birds | ? Комбинаторные птички (TODO: Обсудить) > Combinators are the basis of a whole branch of logic (naturally called "combinatory logic") that was invented many years before computers and programming languages. Combinatory logic has had a very large influence on functional programming. @@ -553,14 +552,14 @@ let (<<) g f x = g (f x) // reverse composition Чтобы узнать больше о комбинаторах и комбинаторной логики, я рекомендую книгу "To Mock a Mockingbird" Raymond-а Smullyan-а. В ней он объясняет другие комбинаторы и причудливо дает им названия птиц. Вот несколько примеров стандартных комбинаторов и их птичьих имен: ```fsharp -let I x = x // identity function, or the Idiot bird +let I x = x // тождественная ф-ция, или Idiot bird let K x y = x // the Kestrel let M x = x >> x // the Mockingbird -let T x y = y x // the Thrush (this looks familiar!) -let Q x y z = y (x z) // the Queer bird (also familiar!) +let T x y = y x // the Thrush (выглядит знакомо!) +let Q x y z = y (x z) // the Queer bird (тоже знакомо!) let S x y z = x z (y z) // The Starling -// and the infamous... -let rec Y f x = f (Y f) x // Y-combinator, or Sage bird +// и печально известный... +let rec Y f x = f (Y f) x // Y-комбинатор, или Sage bird ``` > The letter names are quite standard, so if you refer to "the K combinator", everyone will be familiar with that terminology. @@ -571,11 +570,11 @@ let rec Y f x = f (Y f) x // Y-combinator, or Sage bird Получается, что множество распространенных шаблонов программирования могут быть представлены через данные стандартные комбинаторы. Например, Kestrel является обычным паттерном в fluent интерфейсе где вы делаете что-то, но возвращаете оригинальный объект. Thrush - пайп, Queer - прямая композиция, а Y-комбинатор отлично справляется с созданием рекурсивных функций. -> Indeed, there is a well-known theorem that states that any computable function whatsoever can be built from just two basic combinators, the Kestrel and the Starling. +> Indeed, there is a well-known theorem that states that any computable function whatsoever can be built from just two basic combinators, the Kestrel and the Starling. -На самом деле, существует широко известная теорема, что любая вычислимая функция может быть построена при помощи лишь двух базовых комбинаторов, Kestrel-а и Starling-а. +На самом деле, существует широко [известная теорема](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BC%D0%B1%D0%B8%D0%BD%D0%B0%D1%82%D0%BE%D1%80%D0%BD%D0%B0%D1%8F_%D0%BB%D0%BE%D0%B3%D0%B8%D0%BA%D0%B0?oldformat=true#%D0%9E%D1%81%D0%BD%D0%BE%D0%B2%D0%BD%D1%8B%D0%B5_%D0%BF%D0%BE%D0%BD%D1%8F%D1%82%D0%B8%D1%8F), что любая вычислимая функция может быть построена при помощи лишь двух базовых комбинаторов, Kestrel-а и Starling-а. -### Combinator libraries | Библиотеки комбинаторов ### +### Combinator libraries | Библиотеки комбинаторов > A combinator library is a code library that exports a set of combinator functions that are designed to work together. The user of the library can then easily combine simple functions together to make bigger and more complex functions, like building with Lego. @@ -583,7 +582,7 @@ let rec Y f x = f (Y f) x // Y-combinator, or Sage bird > A well designed combinator library allows you to focus on the high level operations, and push the low level "noise" to the background. We've already seen some examples of this power in the examples in ["why use F#"](../series/why-use-fsharp.md) series, and the `List` module is full of them -- the "`fold`" and "`map`" functions are also combinators, if you think about it. -Хорошо спроектированная библиотека комбинаторов позволяет сосредоточиться на высокоуровневых функциях, и скрыть низкоуровневый "шум". Мы уже видели их силу в нескольких примерах в серии ["why use F#"](../series/why-use-fsharp.md), и модуль `List` полон таких функций, "`fold`" и "`map`" также являются комбинаторами, если вы подумали об этом. +Хорошо спроектированная библиотека комбинаторов позволяет сосредоточиться на высокоуровневых функциях, и скрыть низкоуровневый "шум". Мы уже видели их силу в нескольких примерах в серии ["why use F#"](../series/why-use-fsharp.md), и модуль `List` полон таких функций, "`fold`" и "`map`" также являются комбинаторами, если вы подумаете над этим. > Another advantage of combinators is that they are the safest type of function. As they have no dependency on the outside world they cannot change if the global environment changes. A function that reads a global value or uses a library function can break or alter between calls if the context is different. This can never happen with combinators. @@ -593,21 +592,21 @@ let rec Y f x = f (Y f) x // Y-combinator, or Sage bird В F# библиотеки комбинаторов доступны для парсинга (FParsec), создания HTML, тестирующих фреймворков и т.д. Мы обсудим и воспользуемся комбинаторами позднее в следующих сериях. -## Recursive functions | Рекурсивные функции ## +## Recursive functions | Рекурсивные функции > Often, a function will need to refer to itself in its body. The classic example is the Fibonacci function: Часто функции необходимо ссылаться на саму себя из ее тела. Классический пример - функция Фибоначчи. ```fsharp -let fib i = +let fib i = match i with | 1 -> 1 | 2 -> 1 | n -> fib(n-1) + fib(n-2) ``` -> Unfortunately, this will not compile: +> Unfortunately, this will not compile: К сожалению, данная функция не сможет скомпилироваться: @@ -618,7 +617,7 @@ let fib i = Необходимо указать компилятору, что это рекурсивная функция используя ключевое слово `rec`. ```fsharp -let rec fib i = +let rec fib i = match i with | 1 -> 1 | 2 -> 1 @@ -627,4 +626,4 @@ let rec fib i = > Recursive functions and data structures are extremely common in functional programming, and I hope to devote a whole later series to this topic. -Рекурсивные функции и структуры данных очень распространены в функциональном программировании, и я надеюсь посвятить этой теме целую серию позднее. \ No newline at end of file +Рекурсивные функции и структуры данных очень распространены в функциональном программировании, и я надеюсь посвятить этой теме целую серию позднее. From dbb732bea20e9e6dfdc950493c5050e0293afb4f Mon Sep 17 00:00:00 2001 From: Szer Date: Mon, 21 Jan 2019 16:43:52 +0300 Subject: [PATCH 29/48] fixes --- posts/function-signatures.md | 58 ++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/posts/function-signatures.md b/posts/function-signatures.md index 964c6d4..43d2085 100644 --- a/posts/function-signatures.md +++ b/posts/function-signatures.md @@ -10,34 +10,34 @@ categories: [Functions] > It may not be obvious, but F# actually has two syntaxes - one for normal (value) expressions, and one for type definitions. For example: -Это может быть неочевидным, но в F# существует два синтаксиса, один для нормальных выражений, другой для определения типов. Например: +Не очевидно, но в F# два синтаксиса: для обычных (значимых) выражений и для определения типов. Например: ```fsharp [1;2;3] // a normal expression -int list // a type expression +int list // a type expression Some 1 // a normal expression -int option // a type expression +int option // a type expression (1,"a") // a normal expression -int * string // a type expression +int * string // a type expression ``` -> Type expressions have a special syntax that is *different* from the syntax used in normal expressions. You have already seen many examples of this when you use the interactive session, because the type of each expression has been printed along with its evaluation. +> Type expressions have a special syntax that is *different* from the syntax used in normal expressions. You have already seen many examples of this when you use the interactive session, because the type of each expression has been printed along with its evaluation. -Выражения типов имеют специальный синтаксис, который *отличается* от синтаксиса используемого в обычных выражениях. Вы уже видели множество примеров, где использовалась интерактивная сессия, потому что тип каждого выражения выводился вместе с результатом выполнения. +Выражения для типов имеют особый синтаксис, который *отличается* от синтаксиса обычных выражений. Вы могли заметить множественные примеры этого синтаксиса во время работы с FSI, т.к. типы каждого выражения выводятся вместе с результатами его выполнения. > As you know, F# uses type inference to deduce types, so you don't often need to explicitly specify types in your code, especially for functions. But in order to work effectively in F#, you *do* need to understand the type syntax, so that you can build your own types, debug type errors, and understand function signatures. In this post, we'll focus on its use in function signatures. -Как вы знаете, F# использует вывод типов для их определения, поэтому вам зачастую не надо явно прописывать типы в вашем коде специально для функций. Однако для эффективной работы в F#, необходимо понимать синтаксис типов, чтобы вы могли определять свои собственные типы, отлаживать ошибки вывода типов и понимать сигнатуры функций. В данном посте я сосредоточусь на использовании сигнатур функций. +Как вы знаете, F# использует алгоритм вывода типов, поэтому зачастую вам не придётся явно прописывать типы в коде, особенно в функциях. Но для эффективной работы с F#, необходимо понимать синтаксис типов, таким образом вы сможете определять свои собственные типы, отлаживать ошибки приведения типов и читать сигнатуры функций. В этой статье я сосредоточусь на использовании типов в сигнатурах функций. > Here are some example function signatures using the type syntax: -Вот несколько примеров сигнатур функций использующих синтаксис типов: +Вот несколько примеров сигнатур с синтаксисом типов: ```fsharp // expression syntax // type syntax -let add1 x = x + 1 // int -> int +let add1 x = x + 1 // int -> int let add x y = x + y // int -> int -> int let print x = printf "%A" x // 'a -> unit System.Console.ReadLine // unit -> string @@ -86,25 +86,25 @@ int -> (unit -> string) > This function takes an `int` input and returns a function that when called, returns strings. Again, the function probably has something to do with reading or generating. The input probably initializes the returned function somehow. For example, the input could be a file handle, and the returned function something like `readline()`. Or the input could be a seed for a random string generator. We can't tell exactly, but we can make some educated guesses. -Эта функция принимает `int` и возвращает функцию, которая при вызове возвращает строку. Опять же, вероятно функция производит операцию чтения или генерации. Ввод скорее всего каким-то образом инициализирует возвращаемую функцию. Например, ввод может быть идентификатором файла, а возвращаемая функция являться чем-то подобным `readline()`. Или ввод может быть конфигурацией генератора случайных строк. Мы не можем сказать точно, однако мы можем сделать некоторые обоснованные выводы. +Эта функция принимает `int` и возвращает другую функцию, которая при вызове вернет строку. Опять же, вероятно, функция производит операцию чтения или генерации. Ввод, скорее всего, каким-то образом инициализирует возвращаемую функцию. Например, ввод может быть идентификатором файла, а возвращаемая функция являться чем-то похожим на `readline()`. Или ввод может быть начальным значением для генератора случайных строк. Мы не можем сказать точно, но мы можем сделать какие-то выводы. ```fsharp // function signature 5 -'a list -> 'a +'a list -> 'a ``` > This function takes a list of some type, but returns only one of that type, which means that the function is merging or choosing elements from the list. Examples of functions with this signature are `List.sum`, `List.max`, `List.head` and so on. -Функция принимает список некоторого типа, но возвращает лишь один элемент данного типа, что может означать, что функция аггрегирует список или выбирает один из его элементов. Подобную сигнатуру имеют `List.sum`, `List.max`, `List.head` и т.д. +Функция принимает список любого типа, но возвращает лишь одно значение этого типа, это может говорить о том что функция аггрегирует список или выбирает один из его элементов. Подобную сигнатуру имеют `List.sum`, `List.max`, `List.head` и т.д. ```fsharp // function signature 6 -('a -> bool) -> 'a list -> 'a list +('a -> bool) -> 'a list -> 'a list ``` > This function takes two parameters: the first is a function that maps something to a bool (a predicate), and the second is a list. The return value is a list of the same type. Predicates are used to determine whether a value meets some sort of criteria, so it looks like the function is choosing elements from the list based on whether the predicate is true or not and then returning a subset of the original list. A typical function with this signature is `List.filter`. -Эта функция принимает два параметра: первый - функция которая преобразует что-либо в `bool` (предикат), а второй - список. Возвращаемое значение является списком того же типа. Предикаты используют, чтобы определить, соответствует ли определенный объект какому-либо критерию, похоже, что данная функция выбирает элементы из списка на основе того, принимает ли предикат значение истинны или нет, после чего возвращает подмножество исходного списка. Типичной функцией с подобной сигнатурой является `List.filter`. +Эта функция принимает два параметра: первый - функция которая преобразует что-либо в `bool` (предикат), а второй - список. Возвращаемое значение является списком того же типа. Предикаты используют, чтобы определить, соответствует ли определенный объект некому критерию, и похоже, что данная функция выбирает элементы из списка на основе того, возвращает ли предикат истинное значение или нет, после чего возвращает подмножество исходного списка. Примером функции с такой сигнатурой является `List.filter`. ```fsharp // function signature 7 @@ -113,17 +113,17 @@ int -> (unit -> string) > This function takes two parameters: the first maps type `'a` to type `'b`, and the second is a list of `'a`. The return value is a list of a different type `'b`. A reasonable guess is that the function takes each of the `'a`s in the list, maps them to a `'b` using the function passed in as the first parameter, and returns the new list of `'b`s. And indeed, the prototypical function with this signature is `List.map`. -Функция принимает два параметра: первый - преобразует тип `'a` в тип `'b`, а второй - список типа `'a`. Возвращаемое значение является списком другого типа `'b`. Разумно будет предположить, что функция берет каждый элемент из списка `'a`, и преобразует их в `'b` используя функцию переданную в качестве первого параметра, после чего возвращает список `'b`. И действительно, `List.map` является прообразом функции с такой сигнатурой. +Функция принимает два параметра: первый - преобразует тип `'a` в тип `'b`, а второй - список типа `'a`. Возвращаемое значение является списком типа `'b`. Разумно предположить, что функция берет каждый элемент из списка `'a`, и преобразует их в `'b` используя переданную в качестве первого параметра функцию, после чего возвращает список `'b`. И действительно, `List.map` является прообразом функции с такой сигнатурой. -### Using function signatures to find a library method | Поиск библиотечных методов при помощи сигнатуры функции ### +### Using function signatures to find a library method | Поиск библиотечных методов при помощи сигнатур ### > Function signatures are an important part of searching for library functions. The F# libraries have hundreds of functions in them and they can initially be overwhelming. Unlike an object oriented language, you cannot simply "dot into" an object to find all the appropriate methods. However, if you know the signature of the function you are looking for, you can often narrow down the list of candidates quickly. -Сигнатуры функций важная часть поиска библиотечных функций. Библиотеки F# содержат сотни функций, что вначале может быть ошеломляющим. В отличие от объектно ориентированных языков, вы не можете просто "войти в объект" через точку, чтобы найти все связанные методы. Однако, если вы знаете сигнатуру функции, которую желаете найти, вы зачастую можете быстро сузить список кандидатов. +Сигнатуры функций очень важны в поиске библиотечных функций. Библиотеки F# содержат сотни функций, что может сбивать с толку по началу. В отличие от объектно-ориентированных языков, вы не можете просто "войти в объект" через точку, чтобы найти все связанные методы. Но если вы знаете сигнатуру желаемой функции, вы быстро сможете сузить круг поисков. > For example, let's say you have two lists and you are looking for a function to combine them into one. What would the signature be for this function? It would take two list parameters and return a third, all of the same type, giving the signature: -Например, если скажем вы имеете два списка, и вы хотите найти функцию комбинирующую их в один. Какой сигнатурой обладала бы искомая функция? Она должна была бы принимать два списка в качестве параметров и возвращать третий, все одного типа: +Например, у вас два списка, и вы хотите найти функцию комбинирующую их в один. Какой сигнатурой обладала бы искомая функция? Она должна была бы принимать два списка в качестве параметров и возвращать третий, все одного типа: ```fsharp 'a list -> 'a list -> 'a list @@ -131,34 +131,34 @@ int -> (unit -> string) > Now go to the [MSDN documentation for the F# List module](http://msdn.microsoft.com/en-us/library/ee353738), and scan down the list of functions, looking for something that matches. As it happens, there is only one function with that signature: -Теперь перейдем на [сайт документации MSDN для модуля List](http://msdn.microsoft.com/en-us/library/ee353738), и поищем список функций, в поисках чего-либо похожего. Оказывается, что существует лишь одна функция с такой сигнатурой: +Теперь перейдем на [сайт документации MSDN для модуля List](http://msdn.microsoft.com/en-us/library/ee353738), и посмотрим на список функций, в поисках чего-либо похожего. Оказывается, что существует лишь одна функция с такой сигнатурой: ```fsharp -append : 'T list -> 'T list -> 'T list +append : 'T list -> 'T list -> 'T list ``` > which is exactly the one we want! -Это именно то, что нам нужно! +То что нужно! -## Defining your own types for function signatures | Определение своего собственного типа для сигнатур функций ## +## Defining your own types for function signatures | Определение собственных типов для сигнатур функций ## > Sometimes you may want to create your own types to match a desired function signature. You can do this using the "type" keyword, and define the type in the same way that a signature is written: -Иногда может понадобиться создать функцию своего собственного типа, чтобы соответствовать требуемой сигнатуре функции. Это можно сделать при помощи ключевого слова "type", аналогично примеру ниже: +Когда-нибудь вы захотите определить свои собственные типы для желаемой функции. Это можно сделать при помощи ключевого слова "type": ```fsharp type Adder = int -> int type AdderGenerator = int -> Adder ``` -> You can then use these types to constrain function values and parameters. +> You can then use these types to constrain function values and parameters. -Можно использовать данный тип, чтобы ограничить функции-значения и параметры. +В дальнейшем вы можете использовать эти типы для ограничений значений параметров функций. > For example, the second definition below will fail because of type constraints. If you remove the type constraint (as in the third definition) there will not be any problem. -Например, второе определение ниже, упадет, т.к. тип ограничен. Если мы удалим ограничение типов (как в третьем определении) проблем не возникнет. +Например, второе объявление из-за наложенного ограничения упадет с ошибкой приведения типов. Если мы его уберём (как в третьем объявлении), ошибка исчезнет. ```fsharp let a:AdderGenerator = fun x -> (fun y -> x + y) @@ -168,14 +168,14 @@ let c = fun (x:float) -> (fun y -> x + y) ## Test your understanding of function signatures | Проверка понимания сигнатур функций ## -> How well do you understand function signatures? See if you can create simple functions that have each of these signatures. Avoid using explicit type annotations! +> How well do you understand function signatures? See if you can create simple functions that have each of these signatures. Avoid using explicit type annotations! -Как хорошо вы понимаете сигнатуры функций? Посмотрите, сможете ли вы создать простые функции, которые имеют подобные сигнатуры. Избегайте явных аннотаций типов! +Хорошо ли вы понимаете сигнатуры функций? Проверьте себя, сможете ли вы создать простые функции, с сигнатурами ниже. Избегайте явного указания типов! ```fsharp val testA = int -> int val testB = int -> int -> int -val testC = int -> (int -> int) +val testC = int -> (int -> int) val testD = (int -> int) -> int val testE = int -> int -> int -> int val testF = (int -> int) -> (int -> int) From 99ba56b4bacfd715662aee27bfc51f2d6e9abe43 Mon Sep 17 00:00:00 2001 From: Szer Date: Mon, 21 Jan 2019 16:55:37 +0300 Subject: [PATCH 30/48] for a few fixes more --- posts/function-signatures.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/posts/function-signatures.md b/posts/function-signatures.md index 43d2085..4392336 100644 --- a/posts/function-signatures.md +++ b/posts/function-signatures.md @@ -29,7 +29,7 @@ int * string // a type expression > As you know, F# uses type inference to deduce types, so you don't often need to explicitly specify types in your code, especially for functions. But in order to work effectively in F#, you *do* need to understand the type syntax, so that you can build your own types, debug type errors, and understand function signatures. In this post, we'll focus on its use in function signatures. -Как вы знаете, F# использует алгоритм вывода типов, поэтому зачастую вам не придётся явно прописывать типы в коде, особенно в функциях. Но для эффективной работы с F#, необходимо понимать синтаксис типов, таким образом вы сможете определять свои собственные типы, отлаживать ошибки приведения типов и читать сигнатуры функций. В этой статье я сосредоточусь на использовании типов в сигнатурах функций. +Как вы знаете, F# использует алгоритм вывода типов, поэтому зачастую вам не надо явно прописывать типы в коде, особенно в функциях. Но для эффективной работы с F#, необходимо понимать синтаксис типов, что бы вы смогли определять свои собственные типы, отлаживать ошибки приведения типов и читать сигнатуры функций. В этой статье я сосредоточусь на использовании типов в сигнатурах функций. > Here are some example function signatures using the type syntax: @@ -95,7 +95,7 @@ int -> (unit -> string) > This function takes a list of some type, but returns only one of that type, which means that the function is merging or choosing elements from the list. Examples of functions with this signature are `List.sum`, `List.max`, `List.head` and so on. -Функция принимает список любого типа, но возвращает лишь одно значение этого типа, это может говорить о том что функция аггрегирует список или выбирает один из его элементов. Подобную сигнатуру имеют `List.sum`, `List.max`, `List.head` и т.д. +Функция принимает список любого типа, но возвращает лишь одно значение этого типа, это может говорить о том, что функция аггрегирует список или выбирает один из его элементов. Подобную сигнатуру имеют `List.sum`, `List.max`, `List.head` и т.д. ```fsharp // function signature 6 @@ -104,7 +104,7 @@ int -> (unit -> string) > This function takes two parameters: the first is a function that maps something to a bool (a predicate), and the second is a list. The return value is a list of the same type. Predicates are used to determine whether a value meets some sort of criteria, so it looks like the function is choosing elements from the list based on whether the predicate is true or not and then returning a subset of the original list. A typical function with this signature is `List.filter`. -Эта функция принимает два параметра: первый - функция которая преобразует что-либо в `bool` (предикат), а второй - список. Возвращаемое значение является списком того же типа. Предикаты используют, чтобы определить, соответствует ли определенный объект некому критерию, и похоже, что данная функция выбирает элементы из списка на основе того, возвращает ли предикат истинное значение или нет, после чего возвращает подмножество исходного списка. Примером функции с такой сигнатурой является `List.filter`. +Эта функция принимает два параметра: первый - функция которая преобразует что-либо в `bool` (предикат), а второй - список. Возвращаемое значение является списком того же типа. Предикаты используют, чтобы определить, соответствует ли определенный объект некому критерию, и похоже, что данная функция выбирает элементы из списка на основе значения, возвращаемого предикатом - истина или ложь. После чего возвращает подмножество исходного списка. Примером функции с такой сигнатурой является `List.filter`. ```fsharp // function signature 7 @@ -131,7 +131,7 @@ int -> (unit -> string) > Now go to the [MSDN documentation for the F# List module](http://msdn.microsoft.com/en-us/library/ee353738), and scan down the list of functions, looking for something that matches. As it happens, there is only one function with that signature: -Теперь перейдем на [сайт документации MSDN для модуля List](http://msdn.microsoft.com/en-us/library/ee353738), и посмотрим на список функций, в поисках чего-либо похожего. Оказывается, что существует лишь одна функция с такой сигнатурой: +Теперь перейдем на [сайт документации MSDN для модуля List](http://msdn.microsoft.com/en-us/library/ee353738), и поищем похожую функцию. Оказывается, существует лишь одна функция с такой сигнатурой: ```fsharp append : 'T list -> 'T list -> 'T list @@ -154,7 +154,7 @@ type AdderGenerator = int -> Adder > You can then use these types to constrain function values and parameters. -В дальнейшем вы можете использовать эти типы для ограничений значений параметров функций. +В дальнейшем вы можете использовать эти типы для ограничения значений параметров функций. > For example, the second definition below will fail because of type constraints. If you remove the type constraint (as in the third definition) there will not be any problem. From 47b1a430aa44d18a9efc6ba9acd8095f1bc99b56 Mon Sep 17 00:00:00 2001 From: kleidemos <19260292+kleidemos@users.noreply.github.com> Date: Fri, 1 Feb 2019 00:18:45 +0500 Subject: [PATCH 31/48] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B5?= =?UTF-8?q?=D0=B2=20=D0=B8=20=D0=BF=D0=B0=D1=80=D1=83=20=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BE=D0=BA.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/function-signatures.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/posts/function-signatures.md b/posts/function-signatures.md index 4392336..56fd982 100644 --- a/posts/function-signatures.md +++ b/posts/function-signatures.md @@ -13,19 +13,19 @@ categories: [Functions] Не очевидно, но в F# два синтаксиса: для обычных (значимых) выражений и для определения типов. Например: ```fsharp -[1;2;3] // a normal expression -int list // a type expression +[1;2;3] // обычное выражение +int list // выражение типов -Some 1 // a normal expression -int option // a type expression +Some 1 // обычное выражение +int option // выражение типов -(1,"a") // a normal expression -int * string // a type expression +(1,"a") // обычное выражение +int * string // выражение типов ``` > Type expressions have a special syntax that is *different* from the syntax used in normal expressions. You have already seen many examples of this when you use the interactive session, because the type of each expression has been printed along with its evaluation. -Выражения для типов имеют особый синтаксис, который *отличается* от синтаксиса обычных выражений. Вы могли заметить множественные примеры этого синтаксиса во время работы с FSI, т.к. типы каждого выражения выводятся вместе с результатами его выполнения. +Выражения для типов имеют особый синтаксис, который *отличается* от синтаксиса обычных выражений. Вы могли заметить множество примеров этого синтаксиса во время работы с FSI (FSharp Interactive), т.к. типы каждого выражения выводятся вместе с результатами его выполнения. > As you know, F# uses type inference to deduce types, so you don't often need to explicitly specify types in your code, especially for functions. But in order to work effectively in F#, you *do* need to understand the type syntax, so that you can build your own types, debug type errors, and understand function signatures. In this post, we'll focus on its use in function signatures. @@ -36,7 +36,7 @@ int * string // a type expression Вот несколько примеров сигнатур с синтаксисом типов: ```fsharp -// expression syntax // type syntax +// синтаксис выражений // синтаксис типов let add1 x = x + 1 // int -> int let add x y = x + y // int -> int -> int let print x = printf "%A" x // 'a -> unit @@ -53,7 +53,7 @@ List.map // ('a -> 'b) -> 'a list -> 'b list Часто, даже просто изучив сигнатуру функции, можно получить некоторое представление, о том, что она делает. Рассмотрим несколько примеров и проанализируем их по очереди. ```fsharp -// function signature 1 +// сигнатура 1 int -> int -> int ``` @@ -62,7 +62,7 @@ int -> int -> int Данная функция берет два `int` параметра и возвращает еще один `int`, скорее всего, это разновидность математических функций, таких как сложение, вычитание, умножение или возведение в степень. ```fsharp -// function signature 2 +// сигнатура 2 int -> unit ``` @@ -71,7 +71,7 @@ int -> unit Данная функция принимает `int` и возвращает `unit`, что означает, что функция делает что-то важное в виде side-эффекта. Т.к. она не возвращает полезного значения, side-эффект скорее всего производит операции записи в IO, такие как логирование, запись в базу данных или что-нибудь похожее. ```fsharp -// function signature 3 +// сигнатура 3 unit -> string ``` @@ -80,7 +80,7 @@ unit -> string Эта функция ничего не принимает, но возвращает `string`, что может означать, что функция получает строку из воздуха! Поскольку нет явного ввода, функция вероятно делает что-то с чтением (скажем из файла) или генерацией (н-р. случайная строка). ```fsharp -// function signature 4 +// сигнатура 4 int -> (unit -> string) ``` @@ -89,7 +89,7 @@ int -> (unit -> string) Эта функция принимает `int` и возвращает другую функцию, которая при вызове вернет строку. Опять же, вероятно, функция производит операцию чтения или генерации. Ввод, скорее всего, каким-то образом инициализирует возвращаемую функцию. Например, ввод может быть идентификатором файла, а возвращаемая функция являться чем-то похожим на `readline()`. Или ввод может быть начальным значением для генератора случайных строк. Мы не можем сказать точно, но мы можем сделать какие-то выводы. ```fsharp -// function signature 5 +// сигнатура 5 'a list -> 'a ``` @@ -98,7 +98,7 @@ int -> (unit -> string) Функция принимает список любого типа, но возвращает лишь одно значение этого типа, это может говорить о том, что функция аггрегирует список или выбирает один из его элементов. Подобную сигнатуру имеют `List.sum`, `List.max`, `List.head` и т.д. ```fsharp -// function signature 6 +// сигнатура 6 ('a -> bool) -> 'a list -> 'a list ``` @@ -107,7 +107,7 @@ int -> (unit -> string) Эта функция принимает два параметра: первый - функция которая преобразует что-либо в `bool` (предикат), а второй - список. Возвращаемое значение является списком того же типа. Предикаты используют, чтобы определить, соответствует ли определенный объект некому критерию, и похоже, что данная функция выбирает элементы из списка на основе значения, возвращаемого предикатом - истина или ложь. После чего возвращает подмножество исходного списка. Примером функции с такой сигнатурой является `List.filter`. ```fsharp -// function signature 7 +// сигнатура 7 ('a -> 'b) -> 'a list -> 'b list ``` From c0b04868b29cd3218aa4a80104ed1539d2c5d5a2 Mon Sep 17 00:00:00 2001 From: kleidemos <19260292+kleidemos@users.noreply.github.com> Date: Fri, 1 Feb 2019 00:31:10 +0500 Subject: [PATCH 32/48] =?UTF-8?q?=D0=92=D1=8B=D0=BF=D0=B8=D0=BB=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BC=D0=B5=D1=80=D0=B7=D0=BA=D0=B8=D0=B5=20=D1=81?= =?UTF-8?q?=D0=B8=D0=B3=D0=BD=D0=B0=D1=82=D1=83=D1=80=D1=81=D1=81=D1=81?= =?UTF-8?q?=D1=8B=20%i.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/function-signatures.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/posts/function-signatures.md b/posts/function-signatures.md index 56fd982..81a07af 100644 --- a/posts/function-signatures.md +++ b/posts/function-signatures.md @@ -53,7 +53,6 @@ List.map // ('a -> 'b) -> 'a list -> 'b list Часто, даже просто изучив сигнатуру функции, можно получить некоторое представление, о том, что она делает. Рассмотрим несколько примеров и проанализируем их по очереди. ```fsharp -// сигнатура 1 int -> int -> int ``` @@ -62,7 +61,6 @@ int -> int -> int Данная функция берет два `int` параметра и возвращает еще один `int`, скорее всего, это разновидность математических функций, таких как сложение, вычитание, умножение или возведение в степень. ```fsharp -// сигнатура 2 int -> unit ``` @@ -71,7 +69,6 @@ int -> unit Данная функция принимает `int` и возвращает `unit`, что означает, что функция делает что-то важное в виде side-эффекта. Т.к. она не возвращает полезного значения, side-эффект скорее всего производит операции записи в IO, такие как логирование, запись в базу данных или что-нибудь похожее. ```fsharp -// сигнатура 3 unit -> string ``` @@ -80,7 +77,6 @@ unit -> string Эта функция ничего не принимает, но возвращает `string`, что может означать, что функция получает строку из воздуха! Поскольку нет явного ввода, функция вероятно делает что-то с чтением (скажем из файла) или генерацией (н-р. случайная строка). ```fsharp -// сигнатура 4 int -> (unit -> string) ``` @@ -89,7 +85,6 @@ int -> (unit -> string) Эта функция принимает `int` и возвращает другую функцию, которая при вызове вернет строку. Опять же, вероятно, функция производит операцию чтения или генерации. Ввод, скорее всего, каким-то образом инициализирует возвращаемую функцию. Например, ввод может быть идентификатором файла, а возвращаемая функция являться чем-то похожим на `readline()`. Или ввод может быть начальным значением для генератора случайных строк. Мы не можем сказать точно, но мы можем сделать какие-то выводы. ```fsharp -// сигнатура 5 'a list -> 'a ``` @@ -98,7 +93,6 @@ int -> (unit -> string) Функция принимает список любого типа, но возвращает лишь одно значение этого типа, это может говорить о том, что функция аггрегирует список или выбирает один из его элементов. Подобную сигнатуру имеют `List.sum`, `List.max`, `List.head` и т.д. ```fsharp -// сигнатура 6 ('a -> bool) -> 'a list -> 'a list ``` @@ -107,7 +101,6 @@ int -> (unit -> string) Эта функция принимает два параметра: первый - функция которая преобразует что-либо в `bool` (предикат), а второй - список. Возвращаемое значение является списком того же типа. Предикаты используют, чтобы определить, соответствует ли определенный объект некому критерию, и похоже, что данная функция выбирает элементы из списка на основе значения, возвращаемого предикатом - истина или ложь. После чего возвращает подмножество исходного списка. Примером функции с такой сигнатурой является `List.filter`. ```fsharp -// сигнатура 7 ('a -> 'b) -> 'a list -> 'b list ``` From 762a5a2ca97f8f8d1162bf63d007cedfe1223310 Mon Sep 17 00:00:00 2001 From: Szer Date: Thu, 31 Jan 2019 19:48:52 +0000 Subject: [PATCH 33/48] =?UTF-8?q?=D0=9F=D0=B0=D1=80=D0=B0=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/function-signatures.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/posts/function-signatures.md b/posts/function-signatures.md index 81a07af..021760d 100644 --- a/posts/function-signatures.md +++ b/posts/function-signatures.md @@ -29,7 +29,7 @@ int * string // выражение типов > As you know, F# uses type inference to deduce types, so you don't often need to explicitly specify types in your code, especially for functions. But in order to work effectively in F#, you *do* need to understand the type syntax, so that you can build your own types, debug type errors, and understand function signatures. In this post, we'll focus on its use in function signatures. -Как вы знаете, F# использует алгоритм вывода типов, поэтому зачастую вам не надо явно прописывать типы в коде, особенно в функциях. Но для эффективной работы с F#, необходимо понимать синтаксис типов, что бы вы смогли определять свои собственные типы, отлаживать ошибки приведения типов и читать сигнатуры функций. В этой статье я сосредоточусь на использовании типов в сигнатурах функций. +Как вы знаете, F# использует алгоритм вывода типов, поэтому зачастую вам не надо явно прописывать типы в коде, особенно в функциях. Но для эффективной работы с F# необходимо понимать синтаксис типов, что бы вы смогли определять свои собственные типы, отлаживать ошибки приведения типов и читать сигнатуры функций. В этой статье я сосредоточусь на использовании типов в сигнатурах функций. > Here are some example function signatures using the type syntax: @@ -74,7 +74,7 @@ unit -> string > This function takes no input but returns a `string`, which means that the function is conjuring up a string out of thin air! Since there is no explicit input, the function probably has something to do with reading (from a file say) or generating (a random string, say). -Эта функция ничего не принимает, но возвращает `string`, что может означать, что функция получает строку из воздуха! Поскольку нет явного ввода, функция вероятно делает что-то с чтением (скажем из файла) или генерацией (н-р. случайная строка). +Эта функция ничего не принимает, но возвращает `string`, что может означать, что функция получает строку из воздуха! Поскольку нет явного ввода, функция вероятно делает что-то с чтением (скажем из файла) или генерацией (например случайная строка). ```fsharp int -> (unit -> string) @@ -82,7 +82,7 @@ int -> (unit -> string) > This function takes an `int` input and returns a function that when called, returns strings. Again, the function probably has something to do with reading or generating. The input probably initializes the returned function somehow. For example, the input could be a file handle, and the returned function something like `readline()`. Or the input could be a seed for a random string generator. We can't tell exactly, but we can make some educated guesses. -Эта функция принимает `int` и возвращает другую функцию, которая при вызове вернет строку. Опять же, вероятно, функция производит операцию чтения или генерации. Ввод, скорее всего, каким-то образом инициализирует возвращаемую функцию. Например, ввод может быть идентификатором файла, а возвращаемая функция являться чем-то похожим на `readline()`. Или ввод может быть начальным значением для генератора случайных строк. Мы не можем сказать точно, но мы можем сделать какие-то выводы. +Эта функция принимает `int` и возвращает другую функцию, которая при вызове вернет строку. Опять же, вероятно, функция производит операцию чтения или генерации. Ввод, скорее всего, каким-то образом инициализирует возвращаемую функцию. Например, ввод может быть идентификатором файла, а возвращаемая функция похожа на `readline()`. Или же ввод может быть начальным значением для генератора случайных строк. Мы не можем сказать точно, но какие-то выводы можем сделать. ```fsharp 'a list -> 'a @@ -98,7 +98,7 @@ int -> (unit -> string) > This function takes two parameters: the first is a function that maps something to a bool (a predicate), and the second is a list. The return value is a list of the same type. Predicates are used to determine whether a value meets some sort of criteria, so it looks like the function is choosing elements from the list based on whether the predicate is true or not and then returning a subset of the original list. A typical function with this signature is `List.filter`. -Эта функция принимает два параметра: первый - функция которая преобразует что-либо в `bool` (предикат), а второй - список. Возвращаемое значение является списком того же типа. Предикаты используют, чтобы определить, соответствует ли определенный объект некому критерию, и похоже, что данная функция выбирает элементы из списка на основе значения, возвращаемого предикатом - истина или ложь. После чего возвращает подмножество исходного списка. Примером функции с такой сигнатурой является `List.filter`. +Эта функция принимает два параметра: первый - функция, преобразующая что-либо в `bool` (предикат), и список. Возвращаемое значение является списком того же типа. Предикаты используют, чтобы определить, соответствует ли объект некому критерию, и похоже, что данная функция выбирает элементы из списка согласно предикату - истина или ложь. После чего возвращает подмножество исходного списка. Примером функции с такой сигнатурой является `List.filter`. ```fsharp ('a -> 'b) -> 'a list -> 'b list @@ -106,13 +106,13 @@ int -> (unit -> string) > This function takes two parameters: the first maps type `'a` to type `'b`, and the second is a list of `'a`. The return value is a list of a different type `'b`. A reasonable guess is that the function takes each of the `'a`s in the list, maps them to a `'b` using the function passed in as the first parameter, and returns the new list of `'b`s. And indeed, the prototypical function with this signature is `List.map`. -Функция принимает два параметра: первый - преобразует тип `'a` в тип `'b`, а второй - список типа `'a`. Возвращаемое значение является списком типа `'b`. Разумно предположить, что функция берет каждый элемент из списка `'a`, и преобразует их в `'b` используя переданную в качестве первого параметра функцию, после чего возвращает список `'b`. И действительно, `List.map` является прообразом функции с такой сигнатурой. +Функция принимает два параметра: преобразование из типа `'a` в тип `'b` и список типа `'a`. Возвращаемое значение является списком типа `'b`. Разумно предположить, что функция берет каждый элемент из списка `'a`, и преобразует их в `'b` используя переданную в качестве первого параметра функцию, после чего возвращает список `'b`. И действительно, `List.map` является прообразом функции с такой сигнатурой. ### Using function signatures to find a library method | Поиск библиотечных методов при помощи сигнатур ### > Function signatures are an important part of searching for library functions. The F# libraries have hundreds of functions in them and they can initially be overwhelming. Unlike an object oriented language, you cannot simply "dot into" an object to find all the appropriate methods. However, if you know the signature of the function you are looking for, you can often narrow down the list of candidates quickly. -Сигнатуры функций очень важны в поиске библиотечных функций. Библиотеки F# содержат сотни функций, что может сбивать с толку по началу. В отличие от объектно-ориентированных языков, вы не можете просто "войти в объект" через точку, чтобы найти все связанные методы. Но если вы знаете сигнатуру желаемой функции, вы быстро сможете сузить круг поисков. +Сигнатуры функций очень важны в поиске библиотечных функций. Библиотеки F# содержат сотни функций, что по началу может сбивать с толку. В отличие от объектно-ориентированных языков, вы не можете просто "войти в объект" через точку, чтобы найти все связанные методы. Но если вы знаете сигнатуру желаемой функции, вы быстро сможете сузить круг поисков. > For example, let's say you have two lists and you are looking for a function to combine them into one. What would the signature be for this function? It would take two list parameters and return a third, all of the same type, giving the signature: From 3a0fa87a072556f7b76eed153667b96b8bca07a9 Mon Sep 17 00:00:00 2001 From: Szer Date: Thu, 31 Jan 2019 19:57:29 +0000 Subject: [PATCH 34/48] =?UTF-8?q?=D0=BE=D1=80=D1=84=D0=BE=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=84=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/function-signatures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/function-signatures.md b/posts/function-signatures.md index 021760d..bf879cb 100644 --- a/posts/function-signatures.md +++ b/posts/function-signatures.md @@ -112,7 +112,7 @@ int -> (unit -> string) > Function signatures are an important part of searching for library functions. The F# libraries have hundreds of functions in them and they can initially be overwhelming. Unlike an object oriented language, you cannot simply "dot into" an object to find all the appropriate methods. However, if you know the signature of the function you are looking for, you can often narrow down the list of candidates quickly. -Сигнатуры функций очень важны в поиске библиотечных функций. Библиотеки F# содержат сотни функций, что по началу может сбивать с толку. В отличие от объектно-ориентированных языков, вы не можете просто "войти в объект" через точку, чтобы найти все связанные методы. Но если вы знаете сигнатуру желаемой функции, вы быстро сможете сузить круг поисков. +Сигнатуры функций очень важны в поиске библиотечных функций. Библиотеки F# содержат сотни функций, что поначалу может сбивать с толку. В отличие от объектно-ориентированных языков, вы не можете просто "войти в объект" через точку, чтобы найти все связанные методы. Но если вы знаете сигнатуру желаемой функции, вы быстро сможете сузить круг поисков. > For example, let's say you have two lists and you are looking for a function to combine them into one. What would the signature be for this function? It would take two list parameters and return a third, all of the same type, giving the signature: From 6aa31e2ec4d9238f58b778ec88ba14b8f86318ab Mon Sep 17 00:00:00 2001 From: Anna Udovichenko Date: Mon, 11 Feb 2019 22:57:11 +0300 Subject: [PATCH 35/48] Edited until line 283 --- posts/organizing-functions.md | 48 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/posts/organizing-functions.md b/posts/organizing-functions.md index 7d11ecf..9a5b82a 100644 --- a/posts/organizing-functions.md +++ b/posts/organizing-functions.md @@ -14,25 +14,25 @@ categories: [Functions, Modules] > In F#, there are three options: -В F# существуют три варианта: +В F# возможны три варианта: > * functions can be nested inside other functions. > * at an application level, the top level functions are grouped into "modules". > * alternatively, you can also use the object-oriented approach and attach functions to types as methods. * функции могут быть вложены в другие функции. -* на уровне приложения, функции верхнего уровня группируются по "модулям". -* в качестве альтернативы, можно придерживаться ООП и прикреплять функции к типам в качестве методов. +* на уровне приложения функции верхнего уровня группируются по "модулям". +* или же можно придерживаться объектно-ориентированного подхода и прикреплять функции к типам в качестве методов. > We'll look at the first two options in this post, and the third in the next post. -В данной статье будут рассмотрены первые два способа, оставшийся будет разобран в следующей. +В этой статье рассмотрим первые два способа, а оставшийся - в следующей. ## Nested Functions | Вложенные функции ## > In F#, you can define functions inside other functions. This is a great way to encapsulate "helper" functions that are needed for the main function but shouldn't be exposed outside. -В F# можно определять функции внутри других функций. Это хороший способ инкапсулировать сопутствующие функции, которые необходимы лишь для основной функции и не должны быть представлены наружу. +В F# можно определять функции внутри других функций. Это хороший способ инкапсулировать вспомогательные функции, которые необходимы лишь для основной функции и не должны быть видны снаружи. > In the example below `add` is nested inside `addThreeNumbers`: @@ -55,7 +55,7 @@ addThreeNumbers 2 3 4 > A nested function can access its parent function parameters directly, because they are in scope. > So, in the example below, the `printError` nested function does not need to have any parameters of its own -- it can access the `n` and `max` values directly. -Вложенные функции могут получить доступ к родительским параметрам напрямую, потому-что они находятся в области видимости. +Вложенные функции могут получить доступ к родительским параметрам напрямую, потому-что они находятся её в области видимости. Так, в приведенном ниже примере вложенная функция`printError` не нуждается в параметрах, т.к. она может получить доступ к `n` и `max` напрямую. ```fsharp @@ -76,8 +76,8 @@ validateSize 10 11 > A very common pattern is that the main function defines a nested recursive helper function, and then calls it with the appropriate initial values. > The code below is an example of this: -Очень распространенным паттерном является основная функция определяющая вложенную рекурсивную вспомогательную функцию, которая вызывается с соответствующими начальными значениями. -Ниже приведен подобный пример: +Очень распространенным паттерном является основная функция, определяющая вложенную рекурсивную вспомогательную функцию, которая вызывается с соответствующими начальными значениями. +Ниже приведен пример такого кода: ```fsharp let sumNumbersUpTo max = @@ -99,12 +99,12 @@ sumNumbersUpTo 10 > When nesting functions, do try to avoid very deeply nested functions, especially if the nested functions directly access the variables in their parent scopes rather than having parameters passed to them. > A badly nested function will be just as confusing as the worst kind of deeply nested imperative branching. -Старайтесь избегать глубокой вложенности, особенно в случаях прямого доступа (не в виле параметров) к родительским переменным. -Плохо вложенные функции будут столь же сложны для понимания, как худшие виды глубоких вложенных императивных ветвлений. +Старайтесь избегать глубокой вложенности, особенно в случаях прямого доступа (не в виде параметров) к родительским переменным. +Чрезмерно глубоко вложенные функции будут столь же сложны для понимания, как худшие из многократно вложенных императивных ветвлений. > Here's how *not* to do it: -Пример того как *нельзя* делать: +Пример того как *не надо* делать: ```fsharp // wtf does this function do? @@ -132,8 +132,8 @@ let f x = > A module definition looks very like a function definition. It starts with the `module` keyword, then an `=` sign, and then the contents of the module are listed. > The contents of the module *must* be indented, just as expressions in a function definition must be indented. -Определение модуля очень похоже на определение функции. Оно начинается с ключевого слова `module`, затем идет знак `=`, после чего идет содержимое модуля. -Содержимое модуля *должно* быть смещено, также как выражения в определении функций. +Определение модуля очень похоже на определение функции. Оно начинается с ключевого слова `module`, затем идет знак `=`, после чего следует содержимое модуля. +Содержимое модуля *должно* быть отформатировано со смещением, также как выражения в определении функций. > Here's a module that contains two functions: @@ -148,11 +148,11 @@ module MathStuff = > Now if you try this in Visual Studio, and you hover over the `add` function, you will see that the full name of the `add` function is actually `MathStuff.add`, just as if `MathStuff` was a class and `add` was a method. -Если попробовать этот код в Visual Studio, то при наведении на `add`, можно увидеть полное имя `add`, которое в действительности равно `MathStuff.add`, как будто `MastStuff` был классом, а `add` методом. +Если открыть этот код в Visual Studio, то при наведении на `add` можно увидеть полное имя `add`, которое в действительности равно `MathStuff.add`, как будто `MastStuff` был классом, а `add` - методом. > Actually, that's exactly what is going on. Behind the scenes, the F# compiler creates a static class with static methods. So the C# equivalent would be: -В действительности, именно это и происходит. За кулисами F# компилятор создает статический класс со статическими методами. C# эквивалент выглядел бы так: +В действительности именно это и происходит. За кулисами F# компилятор создает статический класс со статическими методами. C# эквивалент выглядел бы так: ```csharp static class MathStuff @@ -172,7 +172,7 @@ static class MathStuff > If you realize that modules are just static classes, and that functions are static methods, then you will already have a head-start on understanding how modules work in F#, > as most of the rules that apply to static classes also apply to modules. -Осознание того, что модули являются всего-лишь статическими классами, а функции являются статическими методами, даст хорошее понимание того, как модули работают в F#, большинство правил применимых к статическим классам также применимо и к модулям. +Осознание того, что модули - это всего всего лишь статические классы, а функции - статические методы, даст хорошее понимание того, как модули работают в F#, поскольку большинство правил, применимых к статическим классам, применимо также и к модулям. > And, just as in C# every standalone function must be part of a class, in F# every standalone function *must* be part of a module. @@ -182,7 +182,7 @@ static class MathStuff > If you want to access a function in another module, you can refer to it by its qualified name. -Если есть необходимость получить функцию из другого модуля, можно ссылаться на нее через полное имя. +Если есть необходимость обратиться к функции из другого модуля, можно ссылаться на нее через полное имя. ```fsharp module MathStuff = @@ -211,7 +211,7 @@ module OtherStuff = > The rules for using qualified names are exactly as you would expect. That is, you can always use a fully qualified name to access a function, > and you can use relative names or unqualified names based on what other modules are in scope. -Правила использования имен вполне ожидаемые. Всегда можно обратиться к функции по ее полному имени, и можно использовать относительные или неполные имена в зависимости от текущей области видимости. +Правила использования имен вполне ожидаемые. Всегда можно обратиться к функции по ее полному имени, а можно использовать относительные или неполные имена в зависимости от текущей области видимости. ### Nested modules | Вложенные модули @@ -234,7 +234,7 @@ module MathStuff = > And other modules can reference functions in the nested modules using either a full name or a relative name as appropriate: -Другие модули могут ссылаться на функции во вложенных модулях используя полное или относительное имя в зависимости от обстоятельств: +Другие модули могут ссылаться на функции во вложенных модулях, используя полное или относительное имя в зависимости от обстоятельств: ```fsharp module OtherStuff = @@ -254,11 +254,11 @@ module OtherStuff = > So if there can be nested child modules, that implies that, going back up the chain, there must always be some *top-level* parent module. This is indeed true. // TODO: Разобраться с термином. -Таким образом, могут существовать дочерние модули, это означает, что идя назад по цепочке можно дойти до некоего родительского модуля высшего порядка. Это действительно так. +Таким образом, раз модули могут быть вложенными, следовательно, идя вверх по цепочке, можно дойти до некоего родительского модуля верхнего уровня. Это действительно так. > Top level modules are defined slightly differently than the modules we have seen so far. -Модули верхнего уровня определяются несколько иначе в отличии от модулей, которые были показаны ранее. +Модули верхнего уровня определяются несколько иначе, в отличие от модулей, которые были показаны ранее. > * The `module MyModuleName` line *must* be the first declaration in the file > * There is no `=` sign @@ -271,15 +271,15 @@ module OtherStuff = > In general, there must be a top level module declaration present in every `.FS` source file. There some exceptions, but it is good practice anyway. The module name does not have to be the same as the name of the file, but two files cannot share the same module name. -В общем случае, должна существовать декларация верхнего уровня в каждом исходном `.FS` файле. Есть исключения, но это хорошая практика в любом случае. Имя модуля не обязано совпадать с именем файла, но два файла не могут содержать модули с одинаковыми именами. +В общем случае в каждом исходном `.FS` файле должна существовать декларация верхнего уровня. Есть кое-какие исключения, но всё равно это хорошая практика. Имя модуля не обязано совпадать с именем файла, но два файла не могут содержать модули с одинаковыми именами. > For `.FSX` script files, the module declaration is not needed, in which case the module name is automatically set to the filename of the script. -Для `.FSX` файлов, декларация модуля не нужна, в данном случае имя модуля автоматически устанавливается из имени файла скрипта. +Для `.FSX` файлов декларация модуля не нужна, в данном случае имя файла скрипта автоматически становится именем модуля. > Here is an example of `MathStuff` declared as a top level module: -Пример `MathStuff` декларированного в качестве верхнего модуля: +Пример `MathStuff`, декларированного в качестве модуля верхнего модуля: ```fsharp // top level module From 5ce1feede1c1a87a36901f2f406584dcab459c51 Mon Sep 17 00:00:00 2001 From: Anna Udovichenko Date: Wed, 13 Feb 2019 22:17:57 +0300 Subject: [PATCH 36/48] edited to the end --- posts/organizing-functions.md | 225 +++++++++++++++++----------------- 1 file changed, 111 insertions(+), 114 deletions(-) diff --git a/posts/organizing-functions.md b/posts/organizing-functions.md index 9a5b82a..6fa0910 100644 --- a/posts/organizing-functions.md +++ b/posts/organizing-functions.md @@ -41,34 +41,34 @@ categories: [Functions, Modules] ```fsharp let addThreeNumbers x y z = - //create a nested helper function + //создаём вложенную вспомогательную функцию let add n = fun x -> x + n - // use the helper function + // используем вспомогательную функцию x |> add y |> add z -// test +// тестируем addThreeNumbers 2 3 4 ``` > A nested function can access its parent function parameters directly, because they are in scope. > So, in the example below, the `printError` nested function does not need to have any parameters of its own -- it can access the `n` and `max` values directly. -Вложенные функции могут получить доступ к родительским параметрам напрямую, потому-что они находятся её в области видимости. +Вложенные функции могут получить доступ к родительским параметрам напрямую, потому-что они находятся в её области видимости. Так, в приведенном ниже примере вложенная функция`printError` не нуждается в параметрах, т.к. она может получить доступ к `n` и `max` напрямую. ```fsharp let validateSize max n = - //create a nested helper function with no params + // создаём вложенную вспомогательную функцию без параметров let printError() = printfn "Oops: '%i' is bigger than max: '%i'" n max - // use the helper function + // используем вспомогательную функцию if n > max then printError() -// test +// проверяем validateSize 10 9 validateSize 10 11 ``` @@ -76,22 +76,22 @@ validateSize 10 11 > A very common pattern is that the main function defines a nested recursive helper function, and then calls it with the appropriate initial values. > The code below is an example of this: -Очень распространенным паттерном является основная функция, определяющая вложенную рекурсивную вспомогательную функцию, которая вызывается с соответствующими начальными значениями. -Ниже приведен пример такого кода: +Очень распространённым паттерном является основная функция, определяющая вложенную рекурсивную вспомогательную функцию, которая вызывается с соответствующими начальными значениями. +Ниже приведён пример такого кода: ```fsharp let sumNumbersUpTo max = - // recursive helper function with accumulator + // рекурсивная вспомогательная функция с аккумулятором let rec recursiveSum n sumSoFar = match n with | 0 -> sumSoFar | _ -> recursiveSum (n-1) (n+sumSoFar) - // call helper function with initial values + // вызываем вспомогательную функцию с начальными значениями recursiveSum max 0 -// test +// проверяем sumNumbersUpTo 10 ``` @@ -107,7 +107,7 @@ sumNumbersUpTo 10 Пример того как *не надо* делать: ```fsharp -// wtf does this function do? +// кошмар, что делает эта функция? let f x = let f2 y = let f3 z = @@ -182,7 +182,7 @@ static class MathStuff > If you want to access a function in another module, you can refer to it by its qualified name. -Если есть необходимость обратиться к функции из другого модуля, можно ссылаться на нее через полное имя. +Если есть необходимость обратиться к функции из другого модуля, можно ссылаться на неё через полное имя. ```fsharp module MathStuff = @@ -192,7 +192,7 @@ module MathStuff = module OtherStuff = - // use a function from the MathStuff module + // используем функцию из модуля MathStuff let add1 x = MathStuff.add x 1 ``` @@ -203,7 +203,7 @@ module OtherStuff = ```fsharp module OtherStuff = - open MathStuff // make all functions accessible + open MathStuff // делаем доступными все функции модуля let add1 x = add x 1 ``` @@ -211,7 +211,7 @@ module OtherStuff = > The rules for using qualified names are exactly as you would expect. That is, you can always use a fully qualified name to access a function, > and you can use relative names or unqualified names based on what other modules are in scope. -Правила использования имен вполне ожидаемые. Всегда можно обратиться к функции по ее полному имени, а можно использовать относительные или неполные имена в зависимости от текущей области видимости. +Правила использования имён вполне ожидаемые. Всегда можно обратиться к функции по её полному имени, а можно использовать относительные или неполные имена в зависимости от текущей области видимости. ### Nested modules | Вложенные модули @@ -225,8 +225,8 @@ module MathStuff = let add x y = x + y let subtract x y = x - y - // nested module - module FloatLib = + // вложенный модуль + module FloatLib = let add x y :float = x + y let subtract x y :float = x - y @@ -242,10 +242,10 @@ module OtherStuff = let add1 x = add x 1 - // fully qualified + // полное имя let add1Float x = MathStuff.FloatLib.add x 1.0 - //with a relative path + // относительное имя let sub1Float x = FloatLib.subtract x 1.0 ``` @@ -279,16 +279,16 @@ The module name does not have to be the same as the name of the file, but two fi > Here is an example of `MathStuff` declared as a top level module: -Пример `MathStuff`, декларированного в качестве модуля верхнего модуля: +Пример `MathStuff`, объявленного в качестве модуля верхнего модуля: ```fsharp -// top level module +// модуль верхнего уровня module MathStuff let add x y = x + y let subtract x y = x - y -// nested module +// вложенный модуль module FloatLib = let add x y :float = x + y @@ -297,46 +297,46 @@ module FloatLib = > Note the lack of indentation for the top level code (the contents of `module MathStuff`), but that the content of a nested module like `FloatLib` does still need to be indented. -Обратите внимание на отсутствие отступа в коде высшего уровня (содержимом `module MathStuff`), но содержимое вложенного модуля `FloatLib` все еще обязано иметь отступ. +Обратите внимание на отсутствие отступа в коде высшего уровня (содержимом `module MathStuff`), в то время как содержимое вложенного модуля `FloatLib` всё ещё обязано иметь отступ. ### Other module content | Другое содержимое модулей > A module can contain other declarations as well as functions, including type declarations, simple values and initialization code (like static constructors) -Кроме функций модули могут содержать другие объявления, такие как декларации типов, простые значения и инициализирующий код (как статический конструктор) +Помимо функций модули могут содержать другие объявления, такие как декларации типов, простые значения и инициализирующий код (например, статическиие конструкторы) ```fsharp module MathStuff = - // functions + // функции let add x y = x + y let subtract x y = x - y - // type definitions + // объявления типов type Complex = {r:float; i:float} type IntegerFunction = int -> int -> int type DegreesOrRadians = Deg | Rad - // "constant" + // "константы" let PI = 3.141 - // "variable" + // "переменные" let mutable TrigType = Deg - // initialization / static constructor + // инициализация / статический конструктор do printfn "module initialized" ``` >
By the way, if you are playing with these examples in the interactive window, you might want to right-click and do "Reset Session" every so often, so that the code is fresh and doesn't get contaminated with previous evaluations
-
Кстати, если Вы запускаете данные примеры в интерактивном режиме, Вам может понадобиться достаточно часто перезапускать сессию, чтобы код оставался "свежим" и не заражался предыдущими вычислениями.
+
Кстати, если вы запускаете данные примеры в интерактивном режиме, вам может понадобиться достаточно часто перезапускать сессию, чтобы код оставался "свежим" и не заражался предыдущими вычислениями.
-### Shadowing | Сокрытие +### Shadowing | Перекрытие > Here's our example module again. Notice that `MathStuff` has an `add` function and `FloatLib` *also* has an `add` function. -Вот еще один пример. Обратите внимание, что `MathStuff` содержит `add` _также_ как и `FloatLib`. +Это снова наш пример модуля. Обратите внимание, что `MathStuff` содержит функцию `add` _также_ как и `FloatLib`. ```fsharp module MathStuff = @@ -344,7 +344,7 @@ module MathStuff = let add x y = x + y let subtract x y = x - y - // nested module + // вложенный модуль module FloatLib = let add x y :float = x + y @@ -353,31 +353,31 @@ module MathStuff = > Now what happens if I bring *both* of them into scope, and then use `add`? -Что случится, если открыть *оба* модуля в текущем области видимости и вызвать `add`? +Что произойдёт, если открыть *оба* модуля в текущей области видимости и вызвать `add`? ```fsharp open MathStuff open MathStuff.FloatLib -let result = add 1 2 // Compiler error: This expression was expected to - // have type float but here has type int +let result = add 1 2 // Ошибка компиляции: ожидалось, что выражение имеет тип float, + // но оно имеет тип int ``` > What happened was that the `MathStuff.FloatLib` module has masked or overridden the original `MathStuff` module, which has been "shadowed" by `FloatLib`. -Случилось то, что модуль `MathStuff.FloatLib` переопределил оригинальный `MathStuff`, который был сокрыт ("shadowed") `FloatLib`. +А произошло то, что модуль `MathStuff.FloatLib` переопределил оригинальный `MathStuff`, который был перекрыт ("shadowed") модулем `FloatLib`. > As a result you now get a [FS0001 compiler error](../troubleshooting-fsharp/index.md#FS0001) because the first parameter `1` is expected to be a float. You would have to change `1` to `1.0` to fix this. -В результате была получена [FS0001 compiler error](../troubleshooting-fsharp/index.md#FS0001), потому что первый параметр `1` ожидался как float. Потребуется изменить `1` на `1.0`, чтобы исправить это. +В результате получаем [FS0001 compiler error](../troubleshooting-fsharp/index.md#FS0001), потому что первый параметр `1` ожидался как float. Чтобы это исправить, придётся изменить `1` на `1.0`. > Unfortunately, this is invisible and easy to overlook. Sometimes you can do cool tricks with this, almost like subclassing, but more often, it can be annoying if you have functions with the same name (such as the very common `map`). -К сожалению, на практике это _невидимо_ и легко упускается из виду. Иногда можно делать интересные трюки с помощью данного приема, почти как подклассы, но в большинстве своем, наличие одноименных функций раздражает (например, очень распространенная функция `map`). +К сожалению, на практике это _незаметно_ и легко упускается из виду. Иногда с помощью такого приёма можно делать интересные трюки, почти как подклассы, но чаще всего наличие одноимённых функций раздражает (например, очень распространенная функция `map`). > If you don't want this to happen, there is a way to stop it by using the `RequireQualifiedAccess` attribute. Here's the same example where both modules are decorated with it. -Если требуется избежать данного поведения, существует способ пресечь его при помощи атрибута `RequireQualifiedAccess`. Тот же пример у которого оба модуля декорированы данными атрибутом: +Если вы хотите избежать данного поведения, существует способ пресечь его при помощи атрибута `RequireQualifiedAccess`. Тот же пример, у которого оба модуля декорированы этим атрибутом: ```fsharp [] @@ -386,8 +386,8 @@ module MathStuff = let add x y = x + y let subtract x y = x - y - // nested module - [] + // вложенный модуль + [] module FloatLib = let add x y :float = x + y @@ -399,53 +399,52 @@ module MathStuff = Теперь директива `open` недоступна: ```fsharp -open MathStuff // error -open MathStuff.FloatLib // error +open MathStuff // ошибка +open MathStuff.FloatLib // ошибка ``` > But we can still access the functions (without any ambiguity) via their qualified name: -Но все еще можно получить доступ к функциям (без какой-либо двусмысленности) через их полные имена: +Но по прежнему можно получить доступ к функциям (без какой-либо двусмысленности) через их полные имена: ```fsharp let result = MathStuff.add 1 2 let result = MathStuff.FloatLib.add 1.0 2.0 ``` - ### Access Control | Контроль доступа > F# supports the use of standard .NET access control keywords such as `public`, `private`, and `internal`. > The [MSDN documentation](http://msdn.microsoft.com/en-us/library/dd233188) has the complete details. -F# поддерживает использование стандартных .NET операторов контроля доступа, таких как `public`, `private` и `internal`. [MSDN документация](http://msdn.microsoft.com/en-us/library/dd233188) содержит полную информацию. +F# поддерживает использование стандартных операторов контроля доступа из .NET, таких как `public`, `private` и `internal`. [Статья MSDN](http://msdn.microsoft.com/en-us/library/dd233188) содержит полную информацию. > * These access specifiers can be put on the top-level ("let bound") functions, values, types and other declarations in a module. They can also be specified for the modules themselves (you might want a private nested module, for example). > * Everything is public by default (with a few exceptions) so you will need to use `private` or `internal` if you want to protect them. -* Эти спецификаторы доступа могут быть применены к ("let bound") функциям верхнего уровня, значениям, типам и другим декларациям в модуле. Они также могут быть указаны для самих модулей (например может понадобиться приватный вложенный модуль). -* По умолчанию все имеет публичный доступ (за исключением нескольких случаев), поэтому для их защиты потребуется использовать `private` или `internal`. +* Эти спецификаторы доступа могут быть применены к ("let bound") функциям верхнего уровня, значениям, типам и другим объявлениям в модуле. Они также могут быть указаны для самих модулей (например может понадобиться приватный вложенный модуль). +* По умолчанию всё имеет публичный доступ (за исключением нескольких случаев), поэтому для их защиты потребуется использовать `private` или `internal`. > These access specifiers are just one way of doing access control in F#. Another completely different way is to use module "signature" files, which are a bit like C header files. They describe the content of the module in an abstract way. Signatures are very useful for doing serious encapsulation, but that discussion will have to wait for the planned series on encapsulation and capability based security. -Данные спецификаторы доступа являются лишь одним из способов управления видимостью в F#. Другим совершенно отличным способом является использование файлов "сигнатуры" модуля, которые напоминают файлы заголовков C. Они определяют содержимое модуля абстрактным способом. Сигнатуры очень полезны для серьезных инкапсуляций, но для рассмотрения их возможностей придется дождаться запланированной серии по инкапсуляции и *оcнованной на возможностях безопасности*. +Данные спецификаторы доступа являются лишь одним из способов управления видимостью в F#. Совершенно другой способ - использование файлов "сигнатур", которые напоминают заголовочные файлы языка C. Они абстрактно описывают содержимое модуля. Сигнатуры очень полезны для серьёзной инкапсуляции, но для рассмотрения их возможностей придётся дождаться запланированной серии по инкапсуляции и *безопасности на основе возможностей*. -## Namespaces | Пространства имен +## Namespaces | Пространства имён > Namespaces in F# are similar to namespaces in C#. They can be used to organize modules and types to avoid name collisions. -Пространства имен в F# похожи на пространства из C#. Они могут быть использованы для организации модулей и типов, чтобы избежать конфликтов имен. +Пространства имён в F# похожи на пространства имён из C#. Они могут быть использованы для организации модулей и типов, чтобы избежать конфликтов имён. > A namespace is declared with a `namespace` keyword, as shown below. -Пространство имен декларированное с помощью ключевого слова `namespace`: +Пространство имён, объявленное с помощью ключевого слова `namespace`: ```fsharp namespace Utilities -module MathStuff = +module MathStuff = - // functions + // функции let add x y = x + y let subtract x y = x - y ``` @@ -453,20 +452,20 @@ module MathStuff = > Because of this namespace, the fully qualified name of the `MathStuff` module now becomes `Utilities.MathStuff` and > the fully qualified name of the `add` function now becomes `Utilities.MathStuff.add`. -Из-за данного пространств имен полное имя модуля `MathStuff` стало `Utilities.MathStuff`, а полное имя `add` - `Utilities.MathStuff.add`. +Из-за этого пространства имён полное имя модуля `MathStuff` стало `Utilities.MathStuff`, а полное имя `add` - `Utilities.MathStuff.add`. > With the namespace, the indentation rules apply, so that the module defined above must have its content indented, as it it were a nested module. -К модулям внутри пространства имен применяются те же правила отступа, что были показаны ранее для модулей. +К модулям внутри пространства имён применяются те же правила отступа, что были показаны выше для модулей. > You can also declare a namespace implicitly by adding dots to the module name. That is, the code above could also be written as: -Также можно объявлять пространство имен явно при помощи добавления точки в имени модуля. Т.е. код выше можно переписать так: +Также можно объявлять пространство имён явно при помощи добавления точки в имени модуля. Т.е. код выше можно переписать так: ```fsharp module Utilities.MathStuff -// functions +// функции let add x y = x + y let subtract x y = x - y ``` @@ -474,27 +473,26 @@ let subtract x y = x - y > The fully qualified name of the `MathStuff` module is still `Utilities.MathStuff`, but > in this case, the module is a top-level module and the contents do not need to be indented. -Полное имя модуля `MathStuff` все еще `Utilities.MathStuff`, но теперь это модуль верхнего уровня и его содержимому не нужен отступ. +Полное имя модуля `MathStuff` всё ещё `Utilities.MathStuff`, но теперь это модуль верхнего уровня и его содержимому не нужен отступ. > Some additional things to be aware of when using namespaces: -Некоторые дополнительные особенности использования пространств имен: +Некоторые дополнительные особенности использования пространств имён: > * Namespaces are optional for modules. And unlike C#, there is no default namespace for an F# project, so a top level module without a namespace will be at the global level. > If you are planning to create reusable libraries, be sure to add some sort of namespace to avoid naming collisions with code in other libraries. > * Namespaces can directly contain type declarations, but not function declarations. As noted earlier, all function and value declarations must be part of a module. > * Finally, be aware that namespaces don't work well in scripts. For example, if you try to to send a namespace declaration such as `namespace Utilities` below to the interactive window, you will get an error. -* Пространства имен необязательны для модулей. В отличии от C#, не существует пространства имен по умолчанию для F# проекта, так что модуль верхнего уровня без пространства имен будет иметь глобальный уровень. Если планируется создание библиотеки многоразового использования, необходимо добавить несколько пространств имен чтобы избежать коллизий имен с кодом других библиотек. -* Пространства имен непосредственно содержат декларации типов, но не декларации функции. Как было замечено ранее, все объявления функций и значений должны быть частью модуля. -* Наконец, следует иметь ввиду, что пространства имен не работают в скриптах. Например, если попробовать отправить объявление пространства имен, например `namespace Utilities` будет получена ошибка. +* Пространства имён необязательны для модулей. В отличии от C#, для F# проектов не существует пространства имён по умолчанию, так что модуль верхнего уровня без пространства имён будет глобальным. Если планируется создание многократно используемых библиотек, необходимо добавить несколько пространств имён, чтобы избежать коллизий имён с кодом других библиотек. +* Пространства имён могут непосредственно содержать объявления типов, но не объявления функций. Как было замечено ранее, все объявления функций и значений должны быть частью модуля. +* Наконец, следует иметь ввиду, что пространства имён не работают в скриптах. Например, если попробовать отправить объявление пространства имён, такое как `namespace Utilities`, в интерактивное окошко, будет получена ошибка. - -### Namespace hierarchies | Иерархия пространств имен +### Namespace hierarchies | Иерархия пространств имён > You can create a namespace hierarchy by simply separating the names with periods: -Можно создавать иерархию пространств имен, просто разделив имена точками: +Можно выстраивать иерархию пространств имён, просто разделив имена точками: ```fsharp namespace Core.Utilities @@ -505,35 +503,34 @@ module MathStuff = > And if you want to put *two* namespaces in the same file, you can. Note that all namespaces *must* be fully qualified -- there is no nesting. -Можно объявить *два* пространства имен в одном файле, если есть такое желание. Следует обратить внимание, что все пространства имен *должны* быть объявлены полным именем -- они не поддерживают вложенность. +Так же вы можете объявить *два* пространства имён в одном файле, если хотите. Следует обратить внимание, что все пространства имён *должны* быть объявлены полным именем -- они не поддерживают вложенность. ```fsharp namespace Core.Utilities -module MathStuff = +module MathStuff = let add x y = x + y - + namespace Core.Extra -module MoreMathStuff = +module MoreMathStuff = let add x y = x + y ``` > One thing you can't do is have a naming collision between a namespace and a module. -Конфликт имен между пространством имен и модулем невозможен. +Конфликт имён между пространством имён и модулем невозможен. ```fsharp namespace Core.Utilities -module MathStuff = +module MathStuff = let add x y = x + y - + namespace Core -// fully qualified name of module -// is Core.Utilities -// Collision with namespace above! +// полное имя модуля - Core.Utilities +// коллизия с пространством имён выше! module Utilities = let add x y = x + y ``` @@ -543,12 +540,12 @@ module Utilities = > We've seen that a module typically consists of a set of related functions that act on a data type. -Как мы видели ранее, модули обычно состоят из множества взаимозависимых функций, которые взаимодействуют с определенным типом данных. +Как мы уже видели, модули обычно состоят из множества взаимозависимых функций, которые взаимодействуют с определённым типом данных. > In an object oriented program, the data structure and the functions that act on it would be combined in a class. > However in functional-style F#, a data structure and the functions that act on it are combined in a module instead. -В ООП структуры данных и функции воздействующие на них были бы объединены вместе внутри класса. Однако в функциональном стиле, структуры данных и функции на них воздействующие будут объединены внутри модуля. +В объектно-ориентированной программе структуры данных и функции, которые с ними работают, были бы объединены вместе внутри класса. А в функциональном F# структуры данных и функции, на них воздействующие, будут объединены внутри модуля. > There are two common patterns for mixing types and functions together: @@ -563,27 +560,27 @@ module Utilities = > In the first approach, the type is declared *outside* any module (but in a namespace) and then the functions that work on the type are put in a module with a similar name. -В первом случае тип декларируется *за пределами* какого либо модуля (но в пространстве имен), после чего функции, которые работают с этим типом, помещаются в одноименный типу модуль. +В первом случае тип декларируется *за пределами* какого либо модуля (но в пространстве имён), после чего функции, которые работают с этим типом, помещаются в одноимённый типу модуль. ```fsharp -// top-level module +// модуль верхнего уровня namespace Example -// declare the type outside the module +// объявляем тип за пределами модуля type PersonType = {First:string; Last:string} -// declare a module for functions that work on the type -module Person = +// объявляем модуль для функций, которые работают с типом +module Person = - // constructor - let create first last = + // конструктор + let create first last = {First=first; Last=last} - // method that works on the type - let fullName {First=first; Last=last} = + // метод, который работает с типом + let fullName {First=first; Last=last} = first + " " + last -// test +// проверяем let person = Person.create "john" "doe" Person.fullName person |> printfn "Fullname=%s" ``` @@ -592,23 +589,23 @@ Person.fullName person |> printfn "Fullname=%s" > So the functions are accessed with names like `MyModule.Func1` and `MyModule.Func2` while the type itself is > accessed with a name like `MyModule.T`. Here's an example: -В альтернативном варианте, тип декларируется *внутри* модуля и имеет простое название, типа "`T`" или имя модуля. Доступ к функциям осуществляется приблизительно так: `MyModule.Func` и `MyModule.Func2`, а доступ к типу: `MyModule.T`: +В альтернативном варианте тип декларируется *внутри* модуля и имеет простое название, типа "`T`" или имя модуля. Доступ к функциям осуществляется приблизительно так: `MyModule.Func` и `MyModule.Func2`, а доступ к типу: `MyModule.T`: ```fsharp -module Customer = +module Customer = - // Customer.T is the primary type for this module + // Customer.T - это основной тип для модуля type T = {AccountId:int; Name:string} - // constructor - let create id name = + // конструктор + let create id name = {T.AccountId=id; T.Name=name} - // method that works on the type - let isValid {T.AccountId=id; } = + // метод, который работает с типом + let isValid {T.AccountId=id; } = id > 0 -// test +// проверяем let customer = Customer.create 42 "bob" Customer.isValid customer |> printfn "Is valid?=%b" ``` @@ -616,40 +613,40 @@ Customer.isValid customer |> printfn "Is valid?=%b" > Note that in both cases, you should have a constructor function that creates new instances of the type (a factory method, if you will), > Doing this means that you will rarely have to explicitly name the type in your client code, and therefore, you not should not care whether it lives in the module or not! -Заметьте, что в обоих случаях должна быть функция создающая новый экземпляр типа (фабрика). Это позволит не вызывать тип явным образом, и можно будет не задаваться вопросом, находится ли тип внутри модуля. +Обратите внимание, что в обоих случаях должна быть функция-конструктор, создающая новый экземпляр типа (фабрика). Тогда в клиентском коде почти не придётся обращаться к имени типа явно, и можно будет не задаваться вопросом, находится тип внутри модуля или нет. > So which approach should you choose? -Какой способ лучше? +Итак, какой способ выбрать? > * The former approach is more .NET like, and much better if you want to share your libraries with other non-F# code, as the exported class names are what you would expect. > * The latter approach is more common for those used to other functional languages. The type inside a module compiles into nested classes, which is not so nice for interop. -* Первый подход больше похож на классический .NET, и его следует предпочесть, если планируется использовать данную библиотеку для кода за пределами F#, где ожидают отдельно существующий класс. -* Второй подход является более распространенным в других функциональных языках. Тип внутри модуля компилируется как вложенный класс, что как правило не очень удобно для ООП языков. +* Первый подход больше похож на классический .NET, и его следует предпочесть, если планируется использовать данную библиотеку для кода за пределами F#, где ожидается отдельно существующий класс. +* Второй подход является более распространённым в других функциональных языках. Тип внутри модуля компилируется как вложенный класс, что как правило не очень удобно для ООП языков. > For yourself, you might want to experiment with both. And in a team programming situation, you should choose one style and be consistent. -Для себя, вы можете поэкспериментировать с обоими. В случае командной разработки надлежит выбрать один стиль. +Для себя вы можете поэкспериментировать с обоими способами. В случае командной разработки надлежит выбрать один стиль. -### Modules containing types only | Модули содержащие только типы +### Modules containing types only | Модули, содержащие только типы > If you have a set of types that you need to declare without any associated functions, don't bother to use a module. You can declare types directly in a namespace and avoid nested classes. -Если есть множество типов, которые необходимо объявить без каких либо функций, не стоит заморачиваться использованием модуля. Можно объявить типы прямо в пространстве имен не прибегая к вложенным классам. +Если есть множество типов, которые необходимо объявить без каких либо функций, не стоит заморачиваться использованием модуля. Можно объявить типы прямо в пространстве имён, не прибегая к вложенным классам. > For example, here is how you might think to do it: -Например, вы захотите нечто вроде этого: +Например, вы можете захотеть сделать так: ```fsharp -// top-level module +// модуль верхнего уровня module Example -// declare the type inside a module +// объявляем тип внутри модуля type PersonType = {First:string; Last:string} -// no functions in the module, just types... +// никаких функций в модуле, только типы... ``` > And here is a alternative way to do it. The `module` keyword has simply been replaced with `namespace`. @@ -657,17 +654,17 @@ type PersonType = {First:string; Last:string} А вот другой способ сделать тоже самое. Слово `module` просто заменяется на слово `namespace`. ```fsharp -// use a namespace +// используем пространство имён namespace Example -// declare the type outside any module +// объявляем тип вне модуля type PersonType = {First:string; Last:string} ``` > In both cases, `PersonType` will have the same fully qualified name. -В обоих случаях, `PersonType` будет иметь одно и тоже полное имя. +В обоих случаях `PersonType` будет иметь одно и тоже полное имя. > Note that this only works with types. Functions must always live in a module. -Следует обратить внимание, что данная замена работает только с типами. Функции **всегда** должны быть внутри модуля. +Обратите внимание, что данная замена работает только с типами. Функции **всегда** должны быть объявлены внутри модуля. From 9ba15060df2a6ea2b6b4207b4c6b5c4442077605 Mon Sep 17 00:00:00 2001 From: Anna Udovichenko Date: Thu, 14 Feb 2019 21:43:10 +0300 Subject: [PATCH 37/48] Fixed according to the review --- posts/organizing-functions.md | 91 ++++++++++++++++------------------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/posts/organizing-functions.md b/posts/organizing-functions.md index 6fa0910..b867a4a 100644 --- a/posts/organizing-functions.md +++ b/posts/organizing-functions.md @@ -10,7 +10,7 @@ categories: [Functions, Modules] > Now that you know how to define functions, how can you organize them? -Теперь Вы знаете как определять функции, но как организовать их? +Теперь Вы знаете как определять функции, но как их организовать? > In F#, there are three options: @@ -39,16 +39,15 @@ categories: [Functions, Modules] В примере ниже `add` вложен в `addThreeNumbers`: ```fsharp -let addThreeNumbers x y z = +let addThreeNumbers x y z = - //создаём вложенную вспомогательную функцию - let add n = + // создаём вложенную вспомогательную функцию + let add n = fun x -> x + n - + // используем вспомогательную функцию x |> add y |> add z -// тестируем addThreeNumbers 2 3 4 ``` @@ -59,16 +58,15 @@ addThreeNumbers 2 3 4 Так, в приведенном ниже примере вложенная функция`printError` не нуждается в параметрах, т.к. она может получить доступ к `n` и `max` напрямую. ```fsharp -let validateSize max n = +let validateSize max n = // создаём вложенную вспомогательную функцию без параметров - let printError() = + let printError() = printfn "Oops: '%i' is bigger than max: '%i'" n max // используем вспомогательную функцию if n > max then printError() -// проверяем validateSize 10 9 validateSize 10 11 ``` @@ -80,22 +78,20 @@ validateSize 10 11 Ниже приведён пример такого кода: ```fsharp -let sumNumbersUpTo max = +let sumNumbersUpTo max = - // рекурсивная вспомогательная функция с аккумулятором - let rec recursiveSum n sumSoFar = + // рекурсивная вспомогательная функция с аккумулятором + let rec recursiveSum n sumSoFar = match n with | 0 -> sumSoFar | _ -> recursiveSum (n-1) (n+sumSoFar) // вызываем вспомогательную функцию с начальными значениями recursiveSum max 0 - -// проверяем + sumNumbersUpTo 10 ``` - > When nesting functions, do try to avoid very deeply nested functions, especially if the nested functions directly access the variables in their parent scopes rather than having parameters passed to them. > A badly nested function will be just as confusing as the worst kind of deeply nested imperative branching. @@ -108,21 +104,20 @@ sumNumbersUpTo 10 ```fsharp // кошмар, что делает эта функция? -let f x = - let f2 y = - let f3 z = +let f x = + let f2 y = + let f3 z = x * z - let f4 z = - let f5 z = + let f4 z = + let f5 z = y * z - let f6 () = + let f6 () = y * x f6() f4 y x * f2 x ``` - ## Modules | Модули ## > A module is just a set of functions that are grouped together, typically because they work on the same data type or types. @@ -140,7 +135,7 @@ let f x = Определение модуля содержащего две функции: ```fsharp -module MathStuff = +module MathStuff = let add x y = x + y let subtract x y = x - y @@ -185,12 +180,12 @@ static class MathStuff Если есть необходимость обратиться к функции из другого модуля, можно ссылаться на неё через полное имя. ```fsharp -module MathStuff = +module MathStuff = let add x y = x + y let subtract x y = x - y -module OtherStuff = +module OtherStuff = // используем функцию из модуля MathStuff let add1 x = MathStuff.add x 1 @@ -202,7 +197,7 @@ module OtherStuff = Так же можно импортировать все функции другого модуля посредством директивы `open`, после чего можно будет использовать короткое имя вместо полного. ```fsharp -module OtherStuff = +module OtherStuff = open MathStuff // делаем доступными все функции модуля let add1 x = add x 1 @@ -220,7 +215,7 @@ module OtherStuff = Как и статические классы, модули могут содержать вложенные модули: ```fsharp -module MathStuff = +module MathStuff = let add x y = x + y let subtract x y = x - y @@ -231,20 +226,20 @@ module MathStuff = let add x y :float = x + y let subtract x y :float = x - y ``` - + > And other modules can reference functions in the nested modules using either a full name or a relative name as appropriate: Другие модули могут ссылаться на функции во вложенных модулях, используя полное или относительное имя в зависимости от обстоятельств: ```fsharp -module OtherStuff = +module OtherStuff = open MathStuff let add1 x = add x 1 // полное имя let add1Float x = MathStuff.FloatLib.add x 1.0 - + // относительное имя let sub1Float x = FloatLib.subtract x 1.0 ``` @@ -258,7 +253,7 @@ module OtherStuff = > Top level modules are defined slightly differently than the modules we have seen so far. -Модули верхнего уровня определяются несколько иначе, в отличие от модулей, которые были показаны ранее. +Модули "верхнего уровня" определяются несколько иначе, в отличие от модулей, которые были показаны ранее. > * The `module MyModuleName` line *must* be the first declaration in the file > * There is no `=` sign @@ -271,7 +266,7 @@ module OtherStuff = > In general, there must be a top level module declaration present in every `.FS` source file. There some exceptions, but it is good practice anyway. The module name does not have to be the same as the name of the file, but two files cannot share the same module name. -В общем случае в каждом исходном `.FS` файле должна существовать декларация верхнего уровня. Есть кое-какие исключения, но всё равно это хорошая практика. Имя модуля не обязано совпадать с именем файла, но два файла не могут содержать модули с одинаковыми именами. +В общем случае в каждом исходном `.FS` файле должна существовать декларация "верхнего уровня". Есть кое-какие исключения, но всё равно это хорошая практика. Имя модуля не обязано совпадать с именем файла, но два файла не могут содержать модули с одинаковыми именами. > For `.FSX` script files, the module declaration is not needed, in which case the module name is automatically set to the filename of the script. @@ -279,7 +274,7 @@ The module name does not have to be the same as the name of the file, but two fi > Here is an example of `MathStuff` declared as a top level module: -Пример `MathStuff`, объявленного в качестве модуля верхнего модуля: +Пример `MathStuff`, объявленного в качестве модуля "верхнего модуля": ```fsharp // модуль верхнего уровня @@ -289,7 +284,7 @@ let add x y = x + y let subtract x y = x - y // вложенный модуль -module FloatLib = +module FloatLib = let add x y :float = x + y let subtract x y :float = x - y @@ -297,7 +292,7 @@ module FloatLib = > Note the lack of indentation for the top level code (the contents of `module MathStuff`), but that the content of a nested module like `FloatLib` does still need to be indented. -Обратите внимание на отсутствие отступа в коде высшего уровня (содержимом `module MathStuff`), в то время как содержимое вложенного модуля `FloatLib` всё ещё обязано иметь отступ. +Обратите внимание на отсутствие отступа в коде "верхнего уровня" (содержимом `module MathStuff`), в то время как содержимое вложенного модуля `FloatLib` всё ещё обязано иметь отступ. ### Other module content | Другое содержимое модулей @@ -306,7 +301,7 @@ module FloatLib = Помимо функций модули могут содержать другие объявления, такие как декларации типов, простые значения и инициализирующий код (например, статическиие конструкторы) ```fsharp -module MathStuff = +module MathStuff = // функции let add x y = x + y @@ -339,13 +334,13 @@ module MathStuff = Это снова наш пример модуля. Обратите внимание, что `MathStuff` содержит функцию `add` _также_ как и `FloatLib`. ```fsharp -module MathStuff = +module MathStuff = let add x y = x + y let subtract x y = x - y // вложенный модуль - module FloatLib = + module FloatLib = let add x y :float = x + y let subtract x y :float = x - y @@ -359,8 +354,8 @@ module MathStuff = open MathStuff open MathStuff.FloatLib -let result = add 1 2 // Ошибка компиляции: ожидалось, что выражение имеет тип float, - // но оно имеет тип int +let result = add 1 2 // Compiler error: This expression was expected to + // have type float but here has type int ``` > What happened was that the `MathStuff.FloatLib` module has masked or overridden the original `MathStuff` module, which has been "shadowed" by `FloatLib`. @@ -381,14 +376,14 @@ let result = add 1 2 // Ошибка компиляции: ожидалось, ```fsharp [] -module MathStuff = +module MathStuff = let add x y = x + y let subtract x y = x - y // вложенный модуль [] - module FloatLib = + module FloatLib = let add x y :float = x + y let subtract x y :float = x - y @@ -397,7 +392,7 @@ module MathStuff = > Now the `open` isn't allowed: Теперь директива `open` недоступна: - + ```fsharp open MathStuff // ошибка open MathStuff.FloatLib // ошибка @@ -484,9 +479,9 @@ let subtract x y = x - y > * Namespaces can directly contain type declarations, but not function declarations. As noted earlier, all function and value declarations must be part of a module. > * Finally, be aware that namespaces don't work well in scripts. For example, if you try to to send a namespace declaration such as `namespace Utilities` below to the interactive window, you will get an error. -* Пространства имён необязательны для модулей. В отличии от C#, для F# проектов не существует пространства имён по умолчанию, так что модуль верхнего уровня без пространства имён будет глобальным. Если планируется создание многократно используемых библиотек, необходимо добавить несколько пространств имён, чтобы избежать коллизий имён с кодом других библиотек. +* Пространства имён необязательны для модулей. В отличии от C#, для F# проектов не существует пространства имён по умолчанию, так что модуль верхнего уровня без пространства имён будет глобальным. Если планируется создание многократно используемых библиотек, необходимо добавить несколько пространств имён, чтобы избежать коллизий с кодом других библиотек. * Пространства имён могут непосредственно содержать объявления типов, но не объявления функций. Как было замечено ранее, все объявления функций и значений должны быть частью модуля. -* Наконец, следует иметь ввиду, что пространства имён не работают в скриптах. Например, если попробовать отправить объявление пространства имён, такое как `namespace Utilities`, в интерактивное окошко, будет получена ошибка. +* Наконец, следует иметь ввиду, что пространства имён не работают в скриптах. Например, если попробовать отправить объявление пространства имён, такое как `namespace Utilities`, в интерактивное окно, будет получена ошибка. ### Namespace hierarchies | Иерархия пространств имён @@ -531,11 +526,10 @@ namespace Core // полное имя модуля - Core.Utilities // коллизия с пространством имён выше! -module Utilities = +module Utilities = let add x y = x + y ``` - ## Mixing types and functions in modules | Смешивание типов и функций в модулях ## > We've seen that a module typically consists of a set of related functions that act on a data type. @@ -605,8 +599,7 @@ module Customer = let isValid {T.AccountId=id; } = id > 0 -// проверяем -let customer = Customer.create 42 "bob" +let customer = Customer.create 42 "bob" Customer.isValid customer |> printfn "Is valid?=%b" ``` From 1b2bb2bbc1df7a7fe5367229322d85243e324f3f Mon Sep 17 00:00:00 2001 From: kleidemos <19260292+kleidemos@users.noreply.github.com> Date: Sat, 16 Feb 2019 02:14:52 +0500 Subject: [PATCH 38/48] =?UTF-8?q?=D0=A0=D0=B0=D0=B7=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=BB=D1=81=D1=8F=20=D1=81=D0=BE=20=D1=81=D0=BF=D0=BE?= =?UTF-8?q?=D1=80=D0=BD=D1=8B=D0=BC=D0=B8=20=D0=BC=D0=BE=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D0=BC=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/organizing-functions.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/posts/organizing-functions.md b/posts/organizing-functions.md index b867a4a..5636d01 100644 --- a/posts/organizing-functions.md +++ b/posts/organizing-functions.md @@ -103,7 +103,7 @@ sumNumbersUpTo 10 Пример того как *не надо* делать: ```fsharp -// кошмар, что делает эта функция? +// wtf, что делает эта функция? let f x = let f2 y = let f3 z = @@ -244,11 +244,10 @@ module OtherStuff = let sub1Float x = FloatLib.subtract x 1.0 ``` -### Top level modules | Модули высшего порядка +### Top level modules | Модули верхнего уровня > So if there can be nested child modules, that implies that, going back up the chain, there must always be some *top-level* parent module. This is indeed true. -// TODO: Разобраться с термином. Таким образом, раз модули могут быть вложенными, следовательно, идя вверх по цепочке, можно дойти до некоего родительского модуля верхнего уровня. Это действительно так. > Top level modules are defined slightly differently than the modules we have seen so far. @@ -327,7 +326,7 @@ module MathStuff =
Кстати, если вы запускаете данные примеры в интерактивном режиме, вам может понадобиться достаточно часто перезапускать сессию, чтобы код оставался "свежим" и не заражался предыдущими вычислениями.
-### Shadowing | Перекрытие +### Shadowing | Сокрытие (Перекрытие, Shadowing) > Here's our example module again. Notice that `MathStuff` has an `add` function and `FloatLib` *also* has an `add` function. @@ -360,7 +359,7 @@ let result = add 1 2 // Compiler error: This expression was expected to > What happened was that the `MathStuff.FloatLib` module has masked or overridden the original `MathStuff` module, which has been "shadowed" by `FloatLib`. -А произошло то, что модуль `MathStuff.FloatLib` переопределил оригинальный `MathStuff`, который был перекрыт ("shadowed") модулем `FloatLib`. +А произошло то, что модуль `MathStuff.FloatLib` переопределил оригинальный `MathStuff`, который был перекрыт (сокрыт, "shadowed") модулем `FloatLib`. > As a result you now get a [FS0001 compiler error](../troubleshooting-fsharp/index.md#FS0001) because the first parameter `1` is expected to be a float. You would have to change `1` to `1.0` to fix this. @@ -368,7 +367,7 @@ let result = add 1 2 // Compiler error: This expression was expected to > Unfortunately, this is invisible and easy to overlook. Sometimes you can do cool tricks with this, almost like subclassing, but more often, it can be annoying if you have functions with the same name (such as the very common `map`). -К сожалению, на практике это _незаметно_ и легко упускается из виду. Иногда с помощью такого приёма можно делать интересные трюки, почти как подклассы, но чаще всего наличие одноимённых функций раздражает (например, очень распространенная функция `map`). +К сожалению, на практике это _незаметно_ и легко упускается из виду. Иногда с помощью такого приёма можно делать интересные трюки, почти как подклассы, но чаще всего наличие одноимённых функций раздражает (например, в случае крайне распространенной функции `map`). > If you don't want this to happen, there is a way to stop it by using the `RequireQualifiedAccess` attribute. Here's the same example where both modules are decorated with it. @@ -574,7 +573,6 @@ module Person = let fullName {First=first; Last=last} = first + " " + last -// проверяем let person = Person.create "john" "doe" Person.fullName person |> printfn "Fullname=%s" ``` From 0290fcb78fd4c71702263c2f3c2ed165b0a7912f Mon Sep 17 00:00:00 2001 From: kleidemos <19260292+kleidemos@users.noreply.github.com> Date: Sat, 16 Feb 2019 02:16:56 +0500 Subject: [PATCH 39/48] =?UTF-8?q?=D0=95=D1=89=D0=B5=20=D0=BE=D0=B4=D0=B8?= =?UTF-8?q?=D0=BD=20=D1=81=D0=BB=D0=BE=D0=B6=D0=BD=D1=8B=D0=B9=20=D1=81?= =?UTF-8?q?=D0=BB=D1=83=D1=87=D0=B0=D0=B9=20(=D0=B7=D0=B0=D0=B1=D1=8B?= =?UTF-8?q?=D0=BB).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/organizing-functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/organizing-functions.md b/posts/organizing-functions.md index 5636d01..2026236 100644 --- a/posts/organizing-functions.md +++ b/posts/organizing-functions.md @@ -538,7 +538,7 @@ module Utilities = > In an object oriented program, the data structure and the functions that act on it would be combined in a class. > However in functional-style F#, a data structure and the functions that act on it are combined in a module instead. -В объектно-ориентированной программе структуры данных и функции, которые с ними работают, были бы объединены вместе внутри класса. А в функциональном F# структуры данных и функции, на них воздействующие, будут объединены внутри модуля. +В ООП структуры данных и функции на ними, были бы объединены вместе в рамках класса. А в функциональном F# структуры данных и функции над ними объединяются в модуль. > There are two common patterns for mixing types and functions together: From 5f41f1e07d0f213cb028993931fbf5cad1013b5f Mon Sep 17 00:00:00 2001 From: kleidemos <19260292+kleidemos@users.noreply.github.com> Date: Sat, 16 Feb 2019 23:07:32 +0500 Subject: [PATCH 40/48] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BE=D0=BF=D0=B5=D1=87=D0=B0=D1=82=D0=BA=D1=83?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posts/organizing-functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posts/organizing-functions.md b/posts/organizing-functions.md index 2026236..29a3217 100644 --- a/posts/organizing-functions.md +++ b/posts/organizing-functions.md @@ -538,7 +538,7 @@ module Utilities = > In an object oriented program, the data structure and the functions that act on it would be combined in a class. > However in functional-style F#, a data structure and the functions that act on it are combined in a module instead. -В ООП структуры данных и функции на ними, были бы объединены вместе в рамках класса. А в функциональном F# структуры данных и функции над ними объединяются в модуль. +В ООП структуры данных и функции над ними, были бы объединены вместе в рамках класса. А в функциональном F# структуры данных и функции над ними объединяются в модуль. > There are two common patterns for mixing types and functions together: From 0e453fce5c130e4ddb4f06d900d9dbc21e9aafa4 Mon Sep 17 00:00:00 2001 From: Anna Udovichenko Date: Mon, 18 Feb 2019 23:13:12 +0300 Subject: [PATCH 41/48] edit type-extensions.md until line 313 --- posts/type-extensions.md | 195 +++++++++++++++++++-------------------- 1 file changed, 94 insertions(+), 101 deletions(-) diff --git a/posts/type-extensions.md b/posts/type-extensions.md index 6ab2257..52776d8 100644 --- a/posts/type-extensions.md +++ b/posts/type-extensions.md @@ -10,35 +10,34 @@ seriesOrder: 11 > Although we have focused on the pure functional style so far, sometimes it is convenient to switch to an object oriented style. And one of the key features of the OO style is the ability to attach functions to a class and "dot into" the class to get the desired behavior. -Хотя до этого повествование было сосредоточено на чисто функциональном стиле, иногда удобно переключится на объектно ориентированный стиль. И одной из ключевых особенностей ОО стиля является возможность прикреплять функции к классу и обращение к классу через точку для получение желаемого поведения. +Хотя до этого повествование было сфокусировано на чисто функциональном стиле, иногда удобно переключиться на объектно-ориентированный стиль. А одной из ключевых особенностей объектно-ориентированного стиля является возможность прикреплять функции к классу и обращение к классу через точку для получение желаемого поведения. > In F#, this is done using a feature called "type extensions". And any F# type, not just classes, can have functions attached to them. -В F#, это возможно с помощью особенности называемой "расширением типов" ("type extensions"). У любого F# типа, не только класса, могут быть прикрепленные функции. +В F# это возможно с помощью фичи, которая называется "расширение типов" ("type extensions"). У любого F# типа, не только класса, могут быть прикрепленные функции. > Here's an example of attaching a function to a record type. -Пример прикрепления функции к типу записи. +Вот пример прикрепления функции к типу записи. ```fsharp -module Person = +module Person = type T = {First:string; Last:string} with - // member defined with type declaration - member this.FullName = + // функция-член, объявленная вместе с типом + member this.FullName = this.First + " " + this.Last - // constructor - let create first last = + // конструктор + let create first last = {First=first; Last=last} - -// test + let person = Person.create "John" "Doe" let fullname = person.FullName ``` > The key things to note are: -Ключевые слова, на которые следует обратить внимание: +Ключевые моменты, на которые следует обратить внимание: > * The `with` keyword indicates the start of the list of members > * The `member` keyword shows that this is a member function (i.e. a method) @@ -46,120 +45,117 @@ let fullname = person.FullName There is no requirement to use a particular word, just as long as it is consistent. You could use `this` or `self` or `me` or any other word that commonly indicates a self reference. * Ключевое слово `with` обозначает начало списка членов -* Ключевое слово `member` показывает, что эта функция является членом (т.е. методом) -* Слово `this` является меткой объекта, на котором вызывается данный метод (называемая также "self-identifier"). Данное слово является префиксом имени функции, и внутри функции можно использовать ее для обращения к текущему экземпляру. Не существует требований к словам используемым в качестве самоидентификатора, достаточно чтобы они были устойчивы. Можно использовать `this`, `self`, `me` или любое другое слово, которое обычно используется как отсылка на самого себя. - +* Ключевое слово `member` показывает, что функция является членом (т.е. методом) +* Слово `this` является меткой объекта, на котором вызывается данный метод (также называемая "self-identifier"). Это слово является префиксом имени функции, и внутри функции можно использовать его для обращения к текущему экземпляру. Не существует требований к словам, используемым в качестве самоидентификатора, достаточно чтобы они были устойчивы. Можно использовать `this`, `self`, `me` или любое другое слово, которое обычно используется как отсылка на самого себя. -Нет нужды добавлять член вместе с декларацией типа, всегда можно добавить его позднее в том же модуле: +Нет нужды добавлять член вместе с объявлением типа, всегда можно добавить его позднее в том же модуле: ```fsharp -module Person = +module Person = type T = {First:string; Last:string} with - // member defined with type declaration - member this.FullName = + // член, объявленный вместе с типом + member this.FullName = this.First + " " + this.Last - // constructor - let create first last = + // конструктор + let create first last = {First=first; Last=last} - // another member added later - type T with - member this.SortableName = - this.Last + ", " + this.First -// test + // другой член, объявленный позже + type T with + member this.SortableName = + this.Last + ", " + this.First + let person = Person.create "John" "Doe" let fullname = person.FullName let sortableName = person.SortableName ``` - > These examples demonstrate what are called "intrinsic extensions". They are compiled into the type itself and are always available whenever the type is used. They also show up when you use reflection. -Эти примеры демонстрируют вызов "встроенных расширений" ("intrinsic extensions"). Они компилируются в тип и будут доступны везде, где бы тип не использовался. Они также будут показаны при рефлексии. +Эти примеры демонстрируют вызов "встроенных расширений" ("intrinsic extensions"). Они компилируются в тип и будут доступны везде, где бы тип ни использовался. Они также будут показаны при использовании рефлексии. > With intrinsic extensions, it is even possible to have a type definition that divided across several files, as long as all the components use the same namespace and are all compiled into the same assembly. Just as with partial classes in C#, this can be useful to separate generated code from authored code. -Внутренние расширения позволяют иметь определение типа распределенное по нескольким файлам, пока все компоненты используют одно и то же пространство имен и компилируются в одну сборку. Так же как и с partial классами в C#, это может быть полезным для разделения сгенерированного и написанного вручную кода. +Внутренние расширения позволяют даже распределять определение типа по нескольким файлам, пока все компоненты используют одно и то же пространство имен и компилируются в одну сборку. Так же как и с partial классами в C#, это может быть полезным для разделения сгенерированного и написанного вручную кода. ## Optional extensions | Опциональные расширения > Another alternative is that you can add an extra member from a completely different module. > These are called "optional extensions". They are not compiled into the type itself, and require some other module to be in scope for them to work (this behavior is just like C# extension methods). -Другой вариант заключается в том, что можно добавить дополнительный член из совершенно другого модуля. Их называют "опциональными расширениями". Они не компилируются внутрь класса, и требуют другой модуль в области видимости для работы с ними (данное поведение похоже на C# расширения). +Альтернативный вариант заключается в том, что можно добавить дополнительный член из совершенно другого модуля. Их называют "опциональными расширениями". Они не компилируются внутрь класса, и требуют другой модуль в области видимости для работы с ними (данное поведение напоминает методы-расширения (?) из C#). > For example, let's say we have a `Person` type defined: Например, пусть определен тип `Person`: ```fsharp -module Person = +module Person = type T = {First:string; Last:string} with - // member defined with type declaration - member this.FullName = + // член, объявленный вместе с типом + member this.FullName = this.First + " " + this.Last - // constructor - let create first last = + // конструктор + let create first last = {First=first; Last=last} - // another member added later - type T with - member this.SortableName = - this.Last + ", " + this.First + // другой член, объявленный позже + type T with + member this.SortableName = + this.Last + ", " + this.First ``` > The example below demonstrates how to add an `UppercaseName` extension to it in a different module: -Пример ниже демонстрирует, как можно добавить `UppercaseName` расширение к нему в другом модуле: - +Пример ниже демонстрирует, как можно добавить расширение `UppercaseName` к нему в другом модуле: + ```fsharp -// in a different module -module PersonExtensions = +// в другом модуле +module PersonExtensions = - type Person.T with - member this.UppercaseName = + type Person.T with + member this.UppercaseName = this.FullName.ToUpper() ``` > So now let's test this extension: -Теперь можно попробовать данное расширение: +Теперь можно попробовать это расширение: ```fsharp let person = Person.create "John" "Doe" -let uppercaseName = person.UppercaseName +let uppercaseName = person.UppercaseName ``` > Uh-oh, we have an error. What's wrong is that the `PersonExtensions` is not in scope. > Just as for C#, any extensions have to be brought into scope in order to be used. -Будет получена ошибка. Она произошла потому что `PersonExtensions` не находится в области видимости. Как и в C#, любые расширения должны быть введены в область видимости для использования. +Упс, получаем ошибку. Она произошла потому, что `PersonExtensions` не находится в области видимости. Как и в C#, чтобы использовать любые расширения, их нужно ввести в область видимости. > Once we do that, everything is fine: Как только мы сделаем это, все заработает: ```fsharp -// bring the extension into scope first! +// Сначала сделаем расширение доступным! open PersonExtensions let person = Person.create "John" "Doe" -let uppercaseName = person.UppercaseName +let uppercaseName = person.UppercaseName ``` - ## Extending system types | Расширение системных типов > You can extend types that are in the .NET libraries as well. But be aware that when extending a type, you must use the actual type name, not a type abbreviation. -Можно также расширять типы из .NET библиотеки. Но следует иметь ввиду, что при расширении типа надо использовать его фактическое имя, а не псевдоним. +Можно также расширять типы из .NET библиотек. Но следует иметь ввиду, что при расширении типа надо использовать его фактическое имя, а не псевдоним. > For example, if you try to extend `int`, you will fail, because `int` is not the true name of the type: -Например, если попробовать расширить `int`, будет получена ошибка, т.к. `int` не является правильным именем для типа: +Например, если попробовать расширить `int`, ничего не получится, т.к. `int` не является правильным именем для типа: ```fsharp type int with @@ -168,13 +164,12 @@ type int with > You must use `System.Int32` instead: -Необходимо использовать `System.Int32`: +Вместо этого нужно использовать `System.Int32`: ```fsharp type System.Int32 with member this.IsEven = this % 2 = 0 -//test let i = 20 if i.IsEven then printfn "'%i' is even" i ``` @@ -183,26 +178,25 @@ if i.IsEven then printfn "'%i' is even" i > You can make the member functions static by: -Можно создавать статические функции с помощью: +Можно создавать статические функции-члены с помощью: > * adding the keyword `static` > * dropping the `this` placeholder * добавления ключевого слова `static` -* удаления `this` метки +* удаления метки `this` ```fsharp -module Person = +module Person = type T = {First:string; Last:string} with - // member defined with type declaration - member this.FullName = + // член, определённый вместе с типом + member this.FullName = this.First + " " + this.Last - // static constructor - static member Create first last = + // статический конструктор + static member Create first last = {First=first; Last=last} - -// test + let person = Person.T.Create "John" "Doe" let fullname = person.FullName ``` @@ -214,12 +208,11 @@ let fullname = person.FullName ```fsharp type System.Int32 with static member IsOdd x = x % 2 = 1 - + type System.Double with static member Pi = 3.141 -//test -let result = System.Int32.IsOdd 20 +let result = System.Int32.IsOdd 20 let pi = System.Double.Pi ``` @@ -228,93 +221,93 @@ let pi = System.Double.Pi > A very common pattern is to attach pre-existing standalone functions to a type. This has a couple of benefits: -Очень распространенный паттерн - прикрепление уже существующих самостоятельных функций к типу. Он дает пару преимуществ: +Очень распространённый паттерн - прикрепление уже существующих самостоятельных функций к типу. Он даёт несколько преимуществ: > * While developing, you can create standalone functions that refer to other standalone functions. This makes programming easier because type inference works much better with functional-style code than with OO-style ("dotting into") code. > * But for certain key functions, you can attach them to the type as well. This gives clients the choice of whether to use functional or object-oriented style. -* Во время разработки, можно объявлять самостоятельные функции, которые ссылаются на другие самостоятельные функции. Это упростит разработку, т.к. вывод типов работает гораздо лучше с функциональным стилем нежели с ООП. +* Во время разработки можно объявлять самостоятельные функции, которые ссылаются на другие самостоятельные функции. Это упростит разработку, поскольку вывод типов гораздо лучше работает с функциональным стилем, нежели с объектно-ориентированным ("через точку"). * Но некоторые ключевые функции можно прикрепить к типу. Что позволит пользователям выбрать, какой из стилей использовать, функциональный или объектно-ориентированный. > One example of this in the F# libraries is the function that calculates a list's length. It is available as a standalone function in the `List` module, but also as a method on a list instance. -Примером подобного решения является функция из F# библиотеки, которая рассчитывает длину списка. Можно использовать самостоятельную функцию из модуля `List` или вызывать ее как метод экземпляра. +Примером подобного решения является функция из F# библиотеки, которая вычисляет длину списка. Можно использовать самостоятельную функцию из модуля `List` или вызывать ее как метод экземпляра. ```fsharp let list = [1..10] -// functional style +// функциональный стиль let len1 = List.length list -// OO style +// объектно-ориентированный стиль let len2 = list.Length ``` > In the following example, we start with a type with no members initially, then define some functions, then finally attach the `fullName` function to the type. -В следующем примере, тип изначально не имеет каких-либо членов, затем определяются несколько функций, и наконец к типу прикрепляется функция `fullName`. +В следующем примере тип изначально не имеет каких-либо членов, затем определяются несколько функций, и наконец к типу прикрепляется функция `fullName`. ```fsharp -module Person = - // type with no members initially - type T = {First:string; Last:string} +module Person = + // тип, изначально не имеющий членов + type T = {First:string; Last:string} - // constructor + // конструктор let create first last = {First=first; Last=last} - // standalone function - let fullName {First=first; Last=last} = + // самостоятельная функция + let fullName {First=first; Last=last} = first + " " + last - // attach preexisting function as a member - type T with + // присоединение существующей функции в качестве члена + type T with member this.FullName = fullName this - -// test + let person = Person.create "John" "Doe" -let fullname = Person.fullName person // functional style -let fullname2 = person.FullName // OO style +let fullname = Person.fullName person // ФП +let fullname2 = person.FullName // ООП ``` > The standalone `fullName` function has one parameter, the person. In the attached member, the parameter comes from the `this` self-reference. -Самостоятельная функция `fullName` имеет один параметр, `person`. Добавленный же член, получает параметр из self-ссылки. +Самостоятельная функция `fullName` имеет один параметр, `person`. Присоединённый же член получает параметр из self-ссылки. -### Attaching existing functions with multiple parameters | Добавление существующих функций со множеством параметров +### Attaching existing functions with multiple parameters | Добавление существующих функций с несколькими параметрами > One nice thing is that when the previously defined function has multiple parameters, you don't have to respecify them all when doing the attachment, as long as the `this` parameter is first. -Еще одной приятной особенностью является то, что можно сначала определить мультипараметрическую функцию, в которой текущий тип передается в качестве первого параметра, после чего при создании прикрепления не потребуется упоминать все множество параметров, ограничившись `this`. +Еще одной приятной особенностью является то, что когда определённая ранее функция принимает несколько параметров, вам не придётся перечислять их снова, присоединяя её к типу, пока параметр `this` указан первым. + +~~Я не знаю, как это красиво перевести +(можно сначала определить мультипараметрическую функцию, в которой текущий тип передается в качестве первого параметра, после чего при создании прикрепления не потребуется упоминать все множество параметров, ограничившись `this`)~~ > In the example below, the `hasSameFirstAndLastName` function has three parameters. Yet when we attach it, we only need to specify one! В примере ниже функция `hasSameFirstAndLastName` имеет три параметра. Однако при прикреплении достаточно упомянуть всего лишь один! ```fsharp -module Person = - // type with no members initially - type T = {First:string; Last:string} +module Person = + // Тип без членов + type T = {First:string; Last:string} - // constructor - let create first last = + // конструктор + let create first last = {First=first; Last=last} - // standalone function - let hasSameFirstAndLastName (person:T) otherFirst otherLast = + // самостоятельная функция + let hasSameFirstAndLastName (person:T) otherFirst otherLast = person.First = otherFirst && person.Last = otherLast - // attach preexisting function as a member - type T with + // присоединение функции в качестве члена + type T with member this.HasSameFirstAndLastName = hasSameFirstAndLastName this - -// test + let person = Person.create "John" "Doe" -let result1 = Person.hasSameFirstAndLastName person "bob" "smith" // functional style -let result2 = person.HasSameFirstAndLastName "bob" "smith" // OO style +let result1 = Person.hasSameFirstAndLastName person "bob" "smith" // ФП +let result2 = person.HasSameFirstAndLastName "bob" "smith" // ООП ``` - > Why does this work? Hint: think about currying and partial application! Почему это работает? Подсказка: подумайте о каррировании и частичном применении! From f149d05ed6c14652c01e466603710091f37450b6 Mon Sep 17 00:00:00 2001 From: Anna Udovichenko Date: Thu, 21 Feb 2019 23:22:49 +0300 Subject: [PATCH 42/48] Edited type-extensions.md untill line 486 --- posts/type-extensions.md | 57 ++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/posts/type-extensions.md b/posts/type-extensions.md index 52776d8..3743ecd 100644 --- a/posts/type-extensions.md +++ b/posts/type-extensions.md @@ -317,7 +317,7 @@ let result2 = person.HasSameFirstAndLastName "bob" "smith" // ООП > When we start having methods with more than one parameter, we have to make a decision: -Когда у нас есть методы с более чем одним параметром, необходимо принять решение: +Когда у нас появляются методы с более чем одним параметром, необходимо принять решение: > * we could use the standard (curried) form, where parameters are separated with spaces, and partial application is supported. > * we could pass in *all* the parameters at once, comma-separated, in a single tuple. @@ -331,22 +331,22 @@ let result2 = person.HasSameFirstAndLastName "bob" "smith" // ООП > The tuple form is also how F# interacts with the standard .NET libraries, so let's examine this approach in more detail. -Кортежная форма также является формой взаимодействия F# со стандартными библиотеками .NET, поэтому стоит рассмотреть данный подход более детально. +Кортежная форма также используется для взаимодействия F# со стандартными библиотеками .NET, поэтому стоит рассмотреть данный подход более детально. > As a testbed, here is a Product type with two methods, each implemented using one of the approaches. The `CurriedTotal` and `TupleTotal` methods each do the same thing: work out the total price for a given quantity and discount. -В качестве тестового кода используется тип `Product` с двумя методами, каждый из них реализован одним из вышеописанных подходов. `CurriedTotal` и `TupleTotal` методы делают одно и тоже, вычисляют итоговую цену продукта по количеству и скидке. +Нашим испытательным полигоном будет тип `Product` с двумя методами, каждый из которых реализован одним из способов, описанных выше. Методы `CurriedTotal` и `TupleTotal` делают одно и то же: вычисляют итоговую стоимость товара по заданным количеству и скидке. ```fsharp type Product = {SKU:string; Price: float} with - // curried style - member this.CurriedTotal qty discount = + // каррированная форма + member this.CurriedTotal qty discount = (this.Price * float qty) - discount - // tuple style - member this.TupleTotal(qty,discount) = + // кортежная форма + member this.TupleTotal(qty,discount) = (this.Price * float qty) - discount ``` @@ -356,7 +356,7 @@ type Product = {SKU:string; Price: float} with ```fsharp let product = {SKU="ABC"; Price=2.0} -let total1 = product.CurriedTotal 10 1.0 +let total1 = product.CurriedTotal 10 1.0 let total2 = product.TupleTotal(10,1.0) ``` @@ -370,14 +370,14 @@ let total2 = product.TupleTotal(10,1.0) ```fsharp let totalFor10 = product.CurriedTotal 10 -let discounts = [1.0..5.0] -let totalForDifferentDiscounts - = discounts |> List.map totalFor10 +let discounts = [1.0..5.0] +let totalForDifferentDiscounts + = discounts |> List.map totalFor10 ``` > But the tuple approach can do a few things that that the curried one can't, namely: -С другой стороны кортежная версия способна на то, что не может каррированая, а именно: +С другой стороны, кортежная версия способна на то, что не может каррированая, а именно: > * Named parameters > * Optional parameters @@ -387,7 +387,7 @@ let totalForDifferentDiscounts * Необязательные параметры * Перегрузки -### Named parameters with tuple-style parameters | Именованные параметры с параметрами в кортежном стиле +### Named parameters with tuple-style parameters | Именованные параметры с параметрами в форме кортежа > The tuple-style approach supports named parameters: @@ -401,22 +401,22 @@ let total4 = product.TupleTotal(discount=1.0, qty=10) > As you can see, when names are used, the parameter order can be changed. -Как видите, это позволяет менять порядок параметров с помощью явного указания имен. +Как видите, это позволяет менять порядок аргументов с помощью явного указания имен. > Note: if some parameters are named and some are not, the named ones must always be last. Внимание: если лишь у некоторой части параметров есть имена, то эти параметры всегда должна находиться в конце. -### Optional parameters with tuple-style parameters | Необязательные параметры с параметрами в кортежном стиле +### Optional parameters with tuple-style parameters | Необязательные параметры с параметрами в форме кортежа > For tuple-style methods, you can specify an optional parameter by prefixing the parameter name with a question mark. -У методов в стиле кортежей, можно помечать параметры как опциональные при помощи префикса в виде знака вопроса перед именем параметра. +Для методов с параметрами в форме кортежа можно помечать параметры как опциональные при помощи префикса в виде знака вопроса перед именем параметра. > * If the parameter is set, it comes through as `Some value` > * If the parameter is not set, it comes through as `None` -* Если параметр задан, то в функцию придет `Some value` +* Если параметр задан, то в функцию будет передано `Some value` * Иначе придет `None` > Here's an example: @@ -426,8 +426,8 @@ let total4 = product.TupleTotal(discount=1.0, qty=10) ```fsharp type Product = {SKU:string; Price: float} with - // optional discount - member this.TupleTotal2(qty,?discount) = + // Опциональная скидка + member this.TupleTotal2(qty,?discount) = let extPrice = this.Price * float qty match discount with | None -> extPrice @@ -441,34 +441,33 @@ type Product = {SKU:string; Price: float} with ```fsharp let product = {SKU="ABC"; Price=2.0} -// discount not specified +// скидка не передана let total1 = product.TupleTotal2(10) -// discount specified -let total2 = product.TupleTotal2(10,1.0) +// скидка передана +let total2 = product.TupleTotal2(10,1.0) ``` > This explicit matching of the `None` and `Some` can be tedious, and there is a slightly more elegant solution for handling optional parameters. -Явная проверка на `None` и `Some` может быть утомительной, для обработки опциональных параметров существует более элегантное решение. +Явная проверка на `None` и `Some` может быть утомительной, но для обработки опциональных параметров существует более элегантное решение. > There is a function `defaultArg` which takes the parameter as the first argument and a default for the second argument. If the parameter is set, the value is returned. > And if not, the default value is returned. -Функция `defaultArg` принимает параметр в качестве первого аргумента и значение по умолчанию в качестве второго. Если параметр установлен, будет возвращено установленное значение, иначе значение по умолчанию. +Существует функция `defaultArg`, которая принимает имя параметра в качестве первого аргумента и значение по умолчанию в качестве второго. Если параметр установлен, будет возвращено соответствующее значение, иначе - значение по умолчанию. > Let's see the same code rewritten to use `defaultArg` -Тот же код с применением `defaulArg` +Тот же код с применением `defaulArg`: ```fsharp type Product = {SKU:string; Price: float} with - // optional discount + // опциональная скидка member this.TupleTotal2(qty,?discount) = let extPrice = this.Price * float qty let discount = defaultArg discount 0.0 - //return extPrice - discount ``` @@ -478,12 +477,12 @@ type Product = {SKU:string; Price: float} with > In C#, you can have multiple methods with the same name that differ only in their function signature (e.g. different parameter types and/or number of parameters) -В C# можно создать множество методов с одинаковым именем, которые отличаются своей сигнатурой (например, различные типы параметров и/или их число). +В C# можно создать несколько методов с одинаковым именем, которые отличаются своей сигнатурой (например, различные типы параметров и/или их количество). > In the pure functional model, that does not make sense -- a function works with a particular domain type and a particular range type. > The same function cannot work with different domains and ranges. -В чисто функциональной модели, это не имеет смысла -- функция работает с определенным типом и определенным типом дупустимых диапазонов допустимых значений. Одна и та же функция не может взаимодействовать с другими domain-ами и range-ами. +В чисто функциональной модели это не имеет смысла -- функция работает с конкретным типом аргумента (область определения) и конкретным типом возвращаемого значения (область значения). Одна и та же функция не может взаимодействовать с другими областями определения и значения. > However, F# *does* support method overloading, but only for methods (that is functions attached to types) and of these, only those using tuple-style parameter passing. From 75dcba4cd49beb5860a51e53f12e42c7eaa11597 Mon Sep 17 00:00:00 2001 From: Anna Udovichenko Date: Mon, 25 Feb 2019 23:11:00 +0300 Subject: [PATCH 43/48] edited type-extensions.md till the end --- posts/type-extensions.md | 96 ++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/posts/type-extensions.md b/posts/type-extensions.md index 3743ecd..12f8394 100644 --- a/posts/type-extensions.md +++ b/posts/type-extensions.md @@ -14,7 +14,7 @@ And one of the key features of the OO style is the ability to attach functions t > In F#, this is done using a feature called "type extensions". And any F# type, not just classes, can have functions attached to them. -В F# это возможно с помощью фичи, которая называется "расширение типов" ("type extensions"). У любого F# типа, не только класса, могут быть прикрепленные функции. +В F# это возможно с помощью фичи, которая называется "расширение типов" ("type extensions"). У любого F# типа, не только класса, могут быть прикреплённые функции. > Here's an example of attaching a function to a record type. @@ -78,7 +78,7 @@ let sortableName = person.SortableName > With intrinsic extensions, it is even possible to have a type definition that divided across several files, as long as all the components use the same namespace and are all compiled into the same assembly. Just as with partial classes in C#, this can be useful to separate generated code from authored code. -Внутренние расширения позволяют даже распределять определение типа по нескольким файлам, пока все компоненты используют одно и то же пространство имен и компилируются в одну сборку. Так же как и с partial классами в C#, это может быть полезным для разделения сгенерированного и написанного вручную кода. +Внутренние расширения позволяют даже разделять определение типа на несоклько файлов, пока все компоненты используют одно и то же пространство имён и компилируются в одну сборку. Так же как и с partial классами в C#, это может быть полезным для разделения сгенерированного и написанного вручную кода. ## Optional extensions | Опциональные расширения @@ -253,7 +253,7 @@ module Person = type T = {First:string; Last:string} // конструктор - let create first last = + let create first last = {First=first; Last=last} // самостоятельная функция @@ -323,7 +323,7 @@ let result2 = person.HasSameFirstAndLastName "bob" "smith" // ООП > * we could pass in *all* the parameters at once, comma-separated, in a single tuple. * мы можем использовать стандартную (каррированную) форму, где параметры разделяются пробелами, и поддерживается частичное применение. -* или можем передавать *все* параметры за один раз в виде разделенного запятыми кортежа. +* или можем передавать *все* параметры за один раз в виде разделённого запятыми кортежа. > The "curried" form is more functional, and the "tuple" form is more object-oriented. @@ -490,18 +490,18 @@ type Product = {SKU:string; Price: float} with > Here's an example, with yet another variant on the `TupleTotal` method! -Пример, с еще одним вариантом метода `TupleTotal`! +Вот пример с еще одним вариантом метода `TupleTotal`! ```fsharp type Product = {SKU:string; Price: float} with - // no discount - member this.TupleTotal3(qty) = + // без скидки + member this.TupleTotal3(qty) = printfn "using non-discount method" this.Price * float qty - // with discount - member this.TupleTotal3(qty, discount) = + // со скидкой + member this.TupleTotal3(qty, discount) = printfn "using discount method" (this.Price * float qty) - discount ``` @@ -509,7 +509,7 @@ type Product = {SKU:string; Price: float} with > Normally, the F# compiler would complain that there are two methods with the same name, but in this case, because they are tuple based and because their signatures are different, it is acceptable. > (To make it obvious which one is being called, I have added a small debugging message.) -Обычно, компилятор F# жалуется, что существует два метода с одинаковым именем, но в данном случае это приемлемо, т.к. они объявлены в кортежной нотации и их сигнатуры различаются. (Чтобы было понятно, какой из методов вызывается, я добавил небольшое сообщения для отладки.) +Как правило компилятор F# ругается на то, что существует два метода с одинаковым именем, но в данном случае это приемлемо, т.к. они объявлены в кортежной нотации и их сигнатуры различаются. (Чтобы было понятно, какой из методов вызывается, я добавил небольшие сообщения для отладки) > And here's a test: @@ -518,11 +518,11 @@ type Product = {SKU:string; Price: float} with ```fsharp let product = {SKU="ABC"; Price=2.0} -// discount not specified -let total1 = product.TupleTotal3(10) +// скидка не указана +let total1 = product.TupleTotal3(10) -// discount specified -let total2 = product.TupleTotal3(10,1.0) +// скидка указана +let total2 = product.TupleTotal3(10,1.0) ``` @@ -532,7 +532,7 @@ let total2 = product.TupleTotal3(10,1.0) > If you are coming from an object-oriented background, you might be tempted to use methods everywhere, because that is what you are familiar with. > But be aware that there some major downsides to using methods as well: -Придя из объектно-ориентированного мира, можно решить, использовать методы везде, потому что уже знакомы. Но следует быть осторожным, т.к. у них существуют ряд серьезных недостатков: +Придя из объектно-ориентированного мира, можно поддаться соблазну использовать методы везде, потому что это что-то привычное. Но следует быть осторожным, т.к. у них существует ряд серьезных недостатков: > * Methods don't play well with type inference > * Methods don't play well with higher order functions @@ -542,53 +542,53 @@ let total2 = product.TupleTotal3(10,1.0) > In fact, by overusing methods you would be needlessly bypassing the most powerful and useful aspects of programming in F#. -На самом деле, чрезмерно используя методов можно пропустить самые сильные и полезные стороны программирования на F#. +На самом деле, злоупотребляя методами, можно упустить самые сильные и полезные стороны программирования на F#. > Let's see what I mean. Посмотрим, что я имею ввиду. -### Methods don't play well with type inference | Методы плохо взаимодействую с выводом типа +### Methods don't play well with type inference | Методы плохо взаимодействуют с выводом типов > Let's go back to our Person example again, the one that had the same logic implemented both as a standalone function and as a method: -Вернемся к примеру `Person`, который имел одну и ту же логику реализованную в виде самостоятельной функции и метода: +Вернемся к примеру с `Person`, в котором одна и та же логика была реализована в самостоятельной функции и в методе: ```fsharp -module Person = - // type with no members initially - type T = {First:string; Last:string} +module Person = + // тип без методов + type T = {First:string; Last:string} - // constructor - let create first last = + // конструктор + let create first last = {First=first; Last=last} - // standalone function - let fullName {First=first; Last=last} = + // самостоятельная функция + let fullName {First=first; Last=last} = first + " " + last - // function as a member - type T with + // функция-член + type T with member this.FullName = fullName this ``` > Now let's see how well each one works with type inference. Say that I want to print the full name of a person, so I will define a function `printFullName` that takes a person as a parameter. -Теперь, посмотрим как хорошо вывод типов работает с каждым из них. Пусть я хочу вывести полное имя человека, тогда я определю функцию `printFullName`, которая берет `person` в качестве параметра. +Теперь посмотрим, насколько хорошо вывод типов работает с каждым из способов. Допустим, я хочу вывести полное имя человека, тогда я определю функцию `printFullName`, которая принимает `person` в качестве параметра. > Here's the code using the module level standalone function. -Код использующий самостоятельную функцию из модуля. +Код, использующий самостоятельную функцию из модуля: ```fsharp open Person -// using standalone function -let printFullName person = - printfn "Name is %s" (fullName person) - -// type inference worked: -// val printFullName : Person.T -> unit +// использование самостоятельной функции +let printFullName person = + printfn "Name is %s" (fullName person) + +// Сработал вывод типов +// val printFullName : Person.T -> unit ``` > This compiles without problems, and the type inference has correctly deduced that parameter was a person @@ -602,24 +602,24 @@ let printFullName person = ```fsharp open Person -// using method with "dotting into" -let printFullName2 person = - printfn "Name is %s" (person.FullName) +// обращение к методу "через точку" +let printFullName2 person = + printfn "Name is %s" (person.FullName) ``` > This does not compile at all, because the type inference does not have enough information to deduce the parameter. *Any* object might implement `.FullName` -- there is just not enough to go on. -Этот код вообще не скомпилируется, т.к. вывод типов не имеет достаточной информации, чтобы вывести тип параметра. *Любой* объект может реализовывать `.FullName` -- этого недостаточно для вывода. +Этот код вообще не скомпилируется, т.к. вывод типов не имеет достаточной информации, чтобы определить тип параметра. *Любой* объект может реализовывать `.FullName` -- этого недостаточно для вывода. > Yes, we could annotate the function with the parameter type, but that defeats the whole purpose of type inference. -Да, мы можем аннотировать функцию с параметризованным типом, но из-за этого теряется смысл в выведении типа. +Да, мы можем аннотировать функцию типом параметра, но из-за этого теряется весь смысл автоматического вывода типов. -### Methods don't play well with higher order functions | Методы не работают хорошо с функциями высшего порядка +### Methods don't play well with higher order functions | Методы плохо сочетаются с функциями высшего порядка > A similar problem happens with higher order functions. For example, let's say that, given a list of people, we want to get all their full names. -С подобной проблемой можно столкнуться и в функциях высшего порядка. Например, есть список людей, и нам надо получить список их полных имен. +Подобная проблема возникает и в функциях высшего порядка. Например, есть список людей, и нам надо получить список их полных имен. > With a standalone function, this is trivial: @@ -630,10 +630,10 @@ open Person let list = [ Person.create "Andy" "Anderson"; - Person.create "John" "Johnson"; + Person.create "John" "Johnson"; Person.create "Jack" "Jackson"] -//get all the full names at once +// получение всех полных имён list |> List.map fullName ``` @@ -646,18 +646,18 @@ open Person let list = [ Person.create "Andy" "Anderson"; - Person.create "John" "Johnson"; + Person.create "John" "Johnson"; Person.create "Jack" "Jackson"] -//get all the full names at once +// получение всех имён list |> List.map (fun p -> p.FullName) ``` > And this is just a simple example. Object methods don't compose well, are hard to pipe, and so on. -А ведь это еще достаточно простой пример. Методы объектов довольно плохо компонуются, неудобны в конвейере и т.д. +А ведь это еще достаточно простой пример. Методы объектов довольно поддаются композиции, неудобны в конвейере и т.д. > So, a plea for those of you new to functionally programming. Don't use methods at all if you can, especially when you are learning. > They are a crutch that will stop you getting the full benefit from functional programming. -Поэтому, призываю вас, если вы новичок в функциональном программировании. Если можете, не используйте методы, особенно в процессе обучения. Они будут костылем который не позволит получить от функционального программирования полную выгоду. \ No newline at end of file +Поэтому, если вы новичок в функциональном программировании, то призываю вас: если можете, не используйте методы, особенно в процессе обучения. Они будут костылем, который не позволит получить извлечь из функционального программирования максимальную выгоду. \ No newline at end of file From b188556ae880feace977b484ed00853e283197a9 Mon Sep 17 00:00:00 2001 From: DonnieKiel <48046385+DonnieKiel@users.noreply.github.com> Date: Wed, 27 Feb 2019 12:35:13 +0300 Subject: [PATCH 44/48] Update posts/type-extensions.md Co-Authored-By: AnnaUdovichenko --- posts/type-extensions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/posts/type-extensions.md b/posts/type-extensions.md index 12f8394..a81bef3 100644 --- a/posts/type-extensions.md +++ b/posts/type-extensions.md @@ -227,7 +227,7 @@ let pi = System.Double.Pi > * But for certain key functions, you can attach them to the type as well. This gives clients the choice of whether to use functional or object-oriented style. * Во время разработки можно объявлять самостоятельные функции, которые ссылаются на другие самостоятельные функции. Это упростит разработку, поскольку вывод типов гораздо лучше работает с функциональным стилем, нежели с объектно-ориентированным ("через точку"). -* Но некоторые ключевые функции можно прикрепить к типу. Что позволит пользователям выбрать, какой из стилей использовать, функциональный или объектно-ориентированный. +* Но некоторые ключевые функции можно прикрепить к типу. Это позволяет пользователям выбирать, какой из стилей использовать - функциональный или объектно-ориентированный. > One example of this in the F# libraries is the function that calculates a list's length. It is available as a standalone function in the `List` module, but also as a method on a list instance. @@ -660,4 +660,4 @@ list |> List.map (fun p -> p.FullName) > So, a plea for those of you new to functionally programming. Don't use methods at all if you can, especially when you are learning. > They are a crutch that will stop you getting the full benefit from functional programming. -Поэтому, если вы новичок в функциональном программировании, то призываю вас: если можете, не используйте методы, особенно в процессе обучения. Они будут костылем, который не позволит получить извлечь из функционального программирования максимальную выгоду. \ No newline at end of file +Поэтому, если вы новичок в функциональном программировании, то призываю вас: если можете, не используйте методы, особенно в процессе обучения. Они будут костылем, который не позволит получить извлечь из функционального программирования максимальную выгоду. From 185630430a7726ed61cce224bdf4a3079e267be5 Mon Sep 17 00:00:00 2001 From: Anna Udovichenko Date: Thu, 28 Feb 2019 21:12:18 +0300 Subject: [PATCH 45/48] edited type-extensions.md according to the review --- posts/type-extensions.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/posts/type-extensions.md b/posts/type-extensions.md index 12f8394..5a682c8 100644 --- a/posts/type-extensions.md +++ b/posts/type-extensions.md @@ -78,14 +78,14 @@ let sortableName = person.SortableName > With intrinsic extensions, it is even possible to have a type definition that divided across several files, as long as all the components use the same namespace and are all compiled into the same assembly. Just as with partial classes in C#, this can be useful to separate generated code from authored code. -Внутренние расширения позволяют даже разделять определение типа на несоклько файлов, пока все компоненты используют одно и то же пространство имён и компилируются в одну сборку. Так же как и с partial классами в C#, это может быть полезным для разделения сгенерированного и написанного вручную кода. +Внутренние расширения позволяют даже разделять определение типа на несколько файлов, пока все компоненты используют одно и то же пространство имён и компилируются в одну сборку. Так же как и с partial классами в C#, это может быть полезным для разделения сгенерированного и написанного вручную кода. ## Optional extensions | Опциональные расширения > Another alternative is that you can add an extra member from a completely different module. > These are called "optional extensions". They are not compiled into the type itself, and require some other module to be in scope for them to work (this behavior is just like C# extension methods). -Альтернативный вариант заключается в том, что можно добавить дополнительный член из совершенно другого модуля. Их называют "опциональными расширениями". Они не компилируются внутрь класса, и требуют другой модуль в области видимости для работы с ними (данное поведение напоминает методы-расширения (?) из C#). +Альтернативный вариант заключается в том, что можно добавить дополнительный член из совершенно другого модуля. Их называют "опциональными расширениями". Они не компилируются внутрь класса, и требуют другой модуль в области видимости для работы с ними (данное поведение напоминает методы-расширения из C#). > For example, let's say we have a `Person` type defined: @@ -102,7 +102,7 @@ module Person = let create first last = {First=first; Last=last} - // другой член, объявленный позже + // ещё один член, объявленный позже type T with member this.SortableName = this.Last + ", " + this.First @@ -277,10 +277,7 @@ let fullname2 = person.FullName // ООП > One nice thing is that when the previously defined function has multiple parameters, you don't have to respecify them all when doing the attachment, as long as the `this` parameter is first. -Еще одной приятной особенностью является то, что когда определённая ранее функция принимает несколько параметров, вам не придётся перечислять их снова, присоединяя её к типу, пока параметр `this` указан первым. - -~~Я не знаю, как это красиво перевести -(можно сначала определить мультипараметрическую функцию, в которой текущий тип передается в качестве первого параметра, после чего при создании прикрепления не потребуется упоминать все множество параметров, ограничившись `this`)~~ +Есть ещё одна приятная особенность. Если определённая ранее функция принимает несколько параметров, то когда вы будете прикреплять её к типу, вам не придётся перечислять все эти параметры снова. Достаточно указать параметр `this` первым. > In the example below, the `hasSameFirstAndLastName` function has three parameters. Yet when we attach it, we only need to specify one! @@ -482,7 +479,7 @@ type Product = {SKU:string; Price: float} with > In the pure functional model, that does not make sense -- a function works with a particular domain type and a particular range type. > The same function cannot work with different domains and ranges. -В чисто функциональной модели это не имеет смысла -- функция работает с конкретным типом аргумента (область определения) и конкретным типом возвращаемого значения (область значения). Одна и та же функция не может взаимодействовать с другими областями определения и значения. +В чисто функциональной модели это не имеет смысла -- функция работает с конкретным типом аргумента (domain) и конкретным типом возвращаемого значения (range). Одна и та же функция не может взаимодействовать с другими domain и range. > However, F# *does* support method overloading, but only for methods (that is functions attached to types) and of these, only those using tuple-style parameter passing. @@ -660,4 +657,4 @@ list |> List.map (fun p -> p.FullName) > So, a plea for those of you new to functionally programming. Don't use methods at all if you can, especially when you are learning. > They are a crutch that will stop you getting the full benefit from functional programming. -Поэтому, если вы новичок в функциональном программировании, то призываю вас: если можете, не используйте методы, особенно в процессе обучения. Они будут костылем, который не позволит получить извлечь из функционального программирования максимальную выгоду. \ No newline at end of file +Поэтому, если вы новичок в функциональном программировании, то призываю вас: если можете, не используйте методы, особенно в процессе обучения. Они будут костылем, который не позволит извлечь из функционального программирования максимальную выгоду. \ No newline at end of file From 0f511256bb53abb482d6e7db6ba3c8524035a613 Mon Sep 17 00:00:00 2001 From: Anna Date: Wed, 13 Mar 2019 15:06:18 +0300 Subject: [PATCH 46/48] Edited stack-based-calculator.md until line 357 --- posts/stack-based-calculator.md | 88 ++++++++++++++++----------------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/posts/stack-based-calculator.md b/posts/stack-based-calculator.md index 3326305..41498c9 100644 --- a/posts/stack-based-calculator.md +++ b/posts/stack-based-calculator.md @@ -10,11 +10,11 @@ categories: [Combinators, Functions, Worked Examples] > In this post, we'll implement a simple stack based calculator (also known as "reverse Polish" style). The implementation is almost entirely done with functions, with only one special type and no pattern matching at all, so it is a great testing ground for the concepts introduced in this series. -В данной статье мы реализуем простой основанный на стеке калькулятор (также известный как "обратный польский" стиль). Реализация почти полностью построена на функциях, лишь с одним специальным типом и без сопоставления шаблонов вообще, это превосходный полигон для концепций затронутых в данной серии. +В этой статье мы реализуем простой стековый калькулятор (также известный как "обратная Польская нотация"). Реализация практически полностью построена на функциях, лишь с одним специальным типом, и вообще без сопоставления с образцом, так что это превосходный полигон для концепций, затронутых в нашей серии. > If you are not familiar with a stack based calculator, it works as follows: numbers are pushed on a stack, and operations such as addition and multiplication pop numbers off the stack and push the result back on. -Если вы незнакомы с подобным калькулятором, то он работает следующим образом: числа помещаются в стек, а операции, такие как сложение и произведение числа забирают числа с вершины стека, после чего помещают обратно полученный результат операции. +Если вы не знакомы с подобным калькулятором, то он работает следующим образом: числа помещаются в стек, а операции, такие как сложение и умножение, забирают числа с вершины стека, после чего помещают обратно полученный результат операции. > Here is a diagram showing a simple calculation using a stack: @@ -24,7 +24,7 @@ categories: [Combinators, Functions, Worked Examples] > The first steps to designing a system like this is to think about how it would be used. Following a Forth like syntax, we will give each action a label, so that the example above might want to be written something like: -Прежде чем проектировать подобную систему, следует порассуждать над тем, как она будет использоваться. Следуя подобному Forth синтаксису, дадим каждому действию соответствующую метку, чтобы в приведенном выше примере можно было написать нечто вроде: +Прежде чем проектировать подобную систему, следует порассуждать над тем, как она будет использоваться. Следуя Forth-подобному синтаксису, дадим каждому действию соответствующую метку, чтобы в приведенном выше примере можно было написать нечто вроде: EMPTY ONE THREE ADD TWO MUL SHOW @@ -36,7 +36,7 @@ categories: [Combinators, Functions, Worked Examples] > First we need to define the data structure for a stack. To keep things simple, we'll just use a list of floats. -Во первых надо определить структуру данных для стека. Это просто, для этих целей можно использовать список чисел с плавающей точкой. +Во первых, нужно определить структуру данных для стека. Для простоты можно использовать список чисел с плавающей точкой. ```fsharp type Stack = float list @@ -44,7 +44,7 @@ type Stack = float list > But, hold on, let's wrap it in a [single case union type](../posts/discriminated-unions.md#single-case) to make it more descriptive, like this: -Но лучше обернуть его в [single case union type](../posts/discriminated-unions.md#single-case) чтобы сделать его более наглядным, например так: +Но лучше обернуть его в [single case union type](../posts/discriminated-unions.md#single-case), чтобы сделать тип более наглядным, например так: ```fsharp type Stack = StackContents of float list @@ -56,7 +56,7 @@ type Stack = StackContents of float list > Now, to create a new stack, we use `StackContents` as a constructor: -Теперь создадим новый стек, используем `StackContents` в качестве конструктора: +Теперь создадим новый стек, используя `StackContents` в качестве конструктора: ```fsharp let newStack = StackContents [1.0;2.0;3.0] @@ -64,12 +64,12 @@ let newStack = StackContents [1.0;2.0;3.0] > And to extract the contents of an existing Stack, we pattern match with `StackContents`: -Для извлечения содержимого из существующего Stack-а используется сопоставление с шаблоном `StackContents`: +Для извлечения содержимого из существующего Stack-а используется сопоставление с образцом `StackContents`: ```fsharp let (StackContents contents) = newStack -// "contents" value set to +// Значение "contents" будет равно // float list = [1.0; 2.0; 3.0] ``` @@ -78,7 +78,7 @@ let (StackContents contents) = newStack > Next we need a way to push numbers on to the stack. This will be simply be prepending the new value at the front of the list using the "`::`" operator. -Далее необходим способ помещать числа в данный стек. Для этого достаточно добавить новое значение спереди списка используя "`::`". +Далее нам потребуется способ помещать числа в этот стек. Для этого достаточно добавить новое значение в начало списка, используя "`::`". > Here is our push function: @@ -93,11 +93,11 @@ let push x aStack = > This basic function has a number of things worth discussing. -Данная функция имеет ряд особенностей, которые стоит обсудить. +Эта функция имеет ряд особенностей, которые стоит обсудить. > First, note that the list structure is immutable, so the function must accept an existing stack and return a new stack. It cannot just alter the existing stack. In fact, all of the functions in this example will have a similar format like this: -Во первых, следует обратить внимание, что структура `list` неизменяемая, значит функция должна принимать существующий стек и возвращать новый. Это не просто изменение существующего стека. По факту, все функции в данном примере будут иметь подобный формат: +Во первых, следует обратить внимание на то, что структура `list` неизменяемая, значит функция должна принимать существующий стек и возвращать новый. Это не просто изменение существующего стека. По факту, все функции в данном примере будут иметь подобный формат: > Input: a Stack plus other parameters > Output: a new Stack @@ -107,11 +107,11 @@ let push x aStack = > Next, what should the order of the parameters be? Should the stack parameter come first or last? If you remember the discussion of [designing functions for partial application](../posts/partial-application), you will remember that the most changeable thing should come last. You'll see shortly that this guideline will be born out. -Во вторых, почему параметры идут именно в таком порядке? Почему стек должен идти первым или последним? В обсуждении [проектирование функций с частичным применением](../posts/partial-application) говорилось, что наиболее часто меняющийся параметр должен идти последним. Вскоре можно будет убедиться, что данные рекомендации соблюдаются. +Во вторых, почему параметры идут именно в таком порядке? Почему стек должен идти первым или последним? В разделе[проектирование функций с частичным применением](../posts/partial-application) говорилось, что наиболее часто меняющийся параметр должен идти последним. Вскоре можно будет убедиться, что эти рекомендации соблюдаются. > Finally, the function can be made more concise by using pattern matching in the function parameter itself, rather than using a `let` in the body of the function. -Наконец, функцию можно сделать более краткой с помощью сопоставления шаблонов в самом параметре функции, вместо `let` в теле функции. +Наконец, функцию можно сделать более краткой с помощью сопоставления с образцом в самом параметре функции, вместо `let` в теле функции. > Here is the rewritten version: @@ -128,7 +128,7 @@ let push x (StackContents contents) = > And by the way, look at the nice signature it has: -Между прочим, посмотрите на ее изящную сигнатуру: +Между прочим, посмотрите на её изящную сигнатуру: ```fsharp val push : float -> Stack -> Stack @@ -138,9 +138,9 @@ val push : float -> Stack -> Stack > In this case, I could probably guess what it did from the signature alone, even without knowing that the name of the function was "push". > This is one of the reasons why it is a good idea to have explicit type names. If the stack type had just been a list of floats, it wouldn't have been as self-documenting. -Как говорилось [ранее](../posts/function-signatures), сигнатура говорит нам, очень много о функции. -В данном случае, я мог бы догадаться, что делает данная функция лишь по ее сигнатуре, не зная, что она называется "push". -Это еще одна причина по которой было хорошей идеей иметь явные имена типа. Если бы стек был лишь списком чисел с плавающей точкой, то функция не была бы столь само-документированной. +Как говорилось [ранее](../posts/function-signatures), сигнатура сообщает нам очень многое. +В данном случае я мог бы догадаться, что делает данная функция, лишь по ее сигнатуре, даже не зная, что она называется "push". +Это еще одна причина по которой было хорошей идеей иметь явные имена типа. Если бы стек был лишь списком чисел с плавающей точкой, то функция не была бы столь самодокументированной. > Anyway, now let's test it: @@ -160,7 +160,7 @@ let stackWith2 = push 2.0 stackWith1 > With this simple function in place, we can easily define an operation that pushes a particular number onto the stack. -С помощью этой простой функции, можно легко определить операцию помещающую определенное число в стек. +С помощью этой простой функции можно легко определить операцию, помещающую определенное число в стек. ```fsharp let ONE stack = push 1.0 stack @@ -169,7 +169,7 @@ let TWO stack = push 2.0 stack > But wait a minute! Can you see that the `stack` parameter is used on both sides? In fact, we don't need to mention it at all. Instead we can skip the `stack` parameter and write the functions using partial application as follows: -Но подождите минуту! Разве не видите, что параметр `stack` используется с двух сторон? В действительно, совершенно необязательно упоминать его два раза. Вместо этого можно опустить параметр и написать функцию с частичным применением: +Но подождите минуту! Вы же видите, что параметр `stack` упоминается с обеих сторон выражения? В действительно, совершенно необязательно упоминать его два раза. Вместо этого можно опустить параметр и написать функцию с частичным применением: ```fsharp let ONE = push 1.0 @@ -181,11 +181,11 @@ let FIVE = push 5.0 > Now you can see that if the parameters for `push` were in a different order, we wouldn't have been able to do this. -Теперь очевидно, имей параметры `push` другой порядок, `stack` пришлось бы упоминать дважды. +Теперь очевидно, что если бы функция `push`имела другой порядок параметров, `stack` пришлось бы упоминать дважды. > While we're at it, let's define a function that creates an empty stack as well: -Стоит также определить функцию создающую пустой стек: +Стоит также определить функцию, создающую пустой стек: ```fsharp let EMPTY = StackContents [] @@ -219,24 +219,24 @@ let result312 = EMPTY |> THREE |> ONE |> TWO ``` -## Popping the stack | ~~Выталкивание стека~~ // TODO: Исправить +## Popping the stack | Выталкивание из стека // TODO: Исправить > That takes care of pushing onto the stack ? what about a `pop` function next? -С помещением в стек разобрались, но что насчет функции `pop`? +С добавлением в стек разобрались, но что насчет функции `pop`? > When we pop the stack, we will return the top of the stack, obviously, but is that all? -При извлечении из стека, очевидно необходимо вернуть вершину стека, но только ли ее? +При извлечении из стека, очевидно, необходимо вернуть вершину стека, но только ли ее? > In an object-oriented style, [the answer is yes](http://msdn.microsoft.com/en-us/library/system.collections.stack.pop.aspx). In an OO approach, we would *mutate* the stack itself behind the scenes, so that the top element was removed. -В объектно-ориентированном стиле, [ответ да](http://msdn.microsoft.com/en-us/library/system.collections.stack.pop.aspx). Но в случае ООП, стек был бы изменен за кулисами, так что верхний элемент был бы удален. +В объектно-ориентированном стиле [ответом будет "да"](http://msdn.microsoft.com/en-us/library/system.collections.stack.pop.aspx). Но в случае ООП, стек был бы изменен за кулисами, так что верхний элемент был бы удален. > But in a functional style, the stack is immutable. The only way to remove the top element is to create a *new stack* with the element removed. > In order for the caller to have access to this new diminished stack, it needs to be returned along with the top element itself. -Однако в функциональном стиле стек неизменяем. Есть только один способ удалить верхний элемент - создать _новый стек_ без удаленного элемента. Для того, чтобы вызывающий объект имел доступ к новому уменьшенному стеку, его необходимо вернуть вместе с верхним элементом. +Однако в функциональном стиле стек неизменяем. Есть только один способ удалить верхний элемент - создать _новый стек_ без этого элемента. Для того, чтобы вызывающий объект имел доступ к новому уменьшенному стеку, его необходимо вернуть вместе с верхним элементом. > In other words, the `pop` function will have to return *two* values, the top plus the new stack. The easiest way to do this in F# is just to use a tuple. @@ -247,8 +247,8 @@ let result312 = EMPTY |> THREE |> ONE |> TWO Реализация: ```fsharp -/// Pop a value from the stack and return it -/// and the new stack as a tuple +/// Вытолкнуть значение из стека +/// и вернуть его и новый стек в виде пары let pop (StackContents contents) = match contents with | top::rest -> @@ -275,8 +275,8 @@ let pop (StackContents contents) = > Try the code above and see what happens. You will get a compiler error! > The compiler has caught a case we have overlooked -- what happens if the stack is empty? -Но если попробовать выполнить коды выше, будет получена ошибка компиляции! -Компилятор обнаружил случай, который не был отработан, что произойдет, если стек пуст? +Попробуйте запустить этот код и посмотрите, что произойдёт. Вы получите ошибку компиляции! +Компилятор обнаружил случай, который не был отработан -- что произойдет, если стек пуст? > So now we have to decide how to handle this. @@ -290,11 +290,9 @@ let pop (StackContents contents) = > Generally, I prefer to use error cases, but in this case, we'll use an exception. So here's the `pop` code changed to handle the empty case: -Обычно, я предпочитаю использовать специализированные состояния ошибки, но в данном конкретном случае я предпочел использовать исключение. Исправленная версия `pop` с обработкой пустого случая: +Обычно я предпочитаю использовать специальное состояние для ошибки, но в данном конкретном случае я предпочел выбросить исключение. Исправленная версия `pop` с обработкой пустого случая: ```fsharp -/// Pop a value from the stack and return it -/// and the new stack as a tuple let pop (StackContents contents) = match contents with | top::rest -> @@ -322,29 +320,29 @@ let popped2, poppedStack2 = pop poppedStack let _ = pop EMPTY ``` -## Writing the math functions | Математические функции +## Writing the math functions | Арифметические функции > Now with both push and pop in place, we can work on the "add" and "multiply" functions: -Теперь когда добавление и удаление на месте, можно начать работу с функциями "add" и "mulytiply": +Теперь когда добавление и удаление на месте, можно начать работу с функциями "add" и "multiply": ```fsharp let ADD stack = - let x,s = pop stack //pop the top of the stack - let y,s2 = pop s //pop the result stack - let result = x + y //do the math - push result s2 //push back on the doubly-popped stack + let x,s = pop stack //извлечь вершину стека + let y,s2 = pop s //извлечь вершину полученного стека + let result = x + y //вычислить арифметическое выражение + push result s2 //добавить его обратно в стек let MUL stack = - let x,s = pop stack //pop the top of the stack - let y,s2 = pop s //pop the result stack - let result = x * y //do the math - push result s2 //push back on the doubly-popped stack + let x,s = pop stack //извлечь вершину стека + let y,s2 = pop s //извлечь вершину полученного стека + let result = x * y //вычислить арифметическое выражение + push result s2 //добавить его обратно в стек ``` > Test these interactively: -Проверка интерактивном режиме: +Проверка в интерактивном режиме: ```fsharp let add1and2 = EMPTY |> ONE |> TWO |> ADD @@ -354,7 +352,7 @@ let mult2and3 = EMPTY |> TWO |> THREE |> MUL > It works! -Оно работает! +Работает! ### Time to refactor... | Время рефакторинга From 54240c2198cc852630266b50e4cdcf53ade9ae47 Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 14 Mar 2019 11:49:24 +0300 Subject: [PATCH 47/48] Editet stack-based-calculator.md till the end --- posts/stack-based-calculator.md | 126 ++++++++++++++++---------------- 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/posts/stack-based-calculator.md b/posts/stack-based-calculator.md index 41498c9..540d7dc 100644 --- a/posts/stack-based-calculator.md +++ b/posts/stack-based-calculator.md @@ -358,27 +358,27 @@ let mult2and3 = EMPTY |> TWO |> THREE |> MUL > It is obvious that there is significant duplicate code between these two functions. How can we refactor? -Очевидно, что присутствует значительное количество повторяющегося кода между этими двумя функциями. Но как провести рефакторинг? +Очевидно, что в этих двух функциях значительное количество кода дублируется. Как мы можем это исправить? > Both functions pop two values from the stack, apply some sort of binary function, and then push the result back on the stack. This leads us to refactor out the common code into a "binary" function that takes a two parameter math function as a parameter: -Обе функции извлекают два значения из стека, применяют к ним какую-нибудь бинарную функцию, после чего помещают результат обратно в стек. Можно вывести общий код в функцию "binary", которая принимает математическую функцию с двумя параметрами: +Обе функции извлекают два значения из стека, применяют к ним некую бинарную функцию, после чего помещают результат обратно в стек. Можно вывести общий код в функцию "binary", которая принимает математическую функцию с двумя параметрами: ```fsharp let binary mathFn stack = - // pop the top of the stack + // вытолкнуть вершину стека let y,stack' = pop stack - // pop the top of the stack again + // снова вытолкнуть вершину стека let x,stack'' = pop stack' - // do the math + // вычислить let z = mathFn x y - // push the result value back on the doubly-popped stack + // положить результат обратно в стек push z stack'' ``` > *Note that in this implementation, I've switched to using ticks to represent changed states of the "same" object, rather than numeric suffixes. Numeric suffixes can easily get quite confusing.* -_В данной реализации разные версии "одного" объекта помечены различным количеством одинарных кавычек. Это сделано потому, что числовые суффиксы как правило могут легко запутать._ +_Обратите внимание, что в этой реализации разные версии "одного" объекта помечены различным количеством штрихов-кавычек. Это сделано потому, что числовые суффиксы могут легко привести к путанице._ > Question: why are the parameters in the order they are, instead of `mathFn` being after `stack`? @@ -386,7 +386,7 @@ _В данной реализации разные версии "одного" > Now that we have `binary`, we can define ADD and friends more simply: -Теперь когда есть `binary`, можно гораздо проще определить ADD и другие функции: +Теперь, когда есть функция `binary`, можно гораздо проще определить ADD и другие функции: > Here's a first attempt at ADD using the new `binary` helper: @@ -398,7 +398,7 @@ let ADD aStack = binary (fun x y -> x + y) aStack > But we can eliminate the lambda, as it is *exactly* the definition of the built-in `+` function! Which gives us: -Можно исключить лямбду, т.к. она представляет *точное* определение встроенной функции `+`: +Но можно избавиться от лямбды, т.к. она представляет *точное* определение встроенной функции `+`: ```fsharp let ADD aStack = binary (+) aStack @@ -406,7 +406,7 @@ let ADD aStack = binary (+) aStack > And again, we can use partial application to hide the stack parameter. Here's the final definition: -Опять же, можно использовать частичное применение, чтобы скрыть параметр стека. Финальное определение: +Опять же, можно использовать частичное применение, чтобы скрыть параметр "стек". Итоговое определение: ```fsharp let ADD = binary (+) @@ -438,8 +438,8 @@ let add1and2thenSub3 = EMPTY |> ONE |> TWO |> ADD |> THREE |> SUB ```fsharp let unary f stack = - let x,stack' = pop stack //pop the top of the stack - push (f x) stack' //push the function value on the stack + let x,stack' = pop stack + push (f x) stack' ``` > And then define some unary functions: @@ -453,29 +453,29 @@ let SQUARE = unary (fun x -> x * x) > Test interactively again: -Интерактив: +Интерактивный режим: ```fsharp let neg3 = EMPTY |> THREE|> NEG let square2 = EMPTY |> TWO |> SQUARE ``` -## Putting it all together | Сбор всего вместе +## Putting it all together | Собираем всё вместе > In the original requirements, we mentioned that we wanted to be able to show the results, so let's define a SHOW function. -В изначальных требования упоминалось, что мы хотели показать результаты, поэтому стоит определить функцию SHOW. +В изначальных требованиях упоминалось, что мы хотели бы иметь возможность показать результаты, поэтому стоит определить функцию SHOW. ```fsharp let SHOW stack = let x,_ = pop stack printfn "The answer is %f" x - stack // keep going with same stack + stack // продолжаем работать с тем же стеком ``` > Note that in this case, we pop the original stack but ignore the diminished version. The final result of the function is the original stack, as if it had never been popped. -Обратите внимание, что в данном случае новая версия стека получаемая через `pop` игнорируется. Конечным же результатом объявляется исходный стек, как будто он никогда не изменялся. +Обратите внимание, что в данном случае новая версия стека, полученная через `pop`, игнорируется. Конечным же результатом объявляется исходный стек, как будто он никогда не изменялся. > So now finally, we can write the code example from the original requirements @@ -485,7 +485,7 @@ let SHOW stack = EMPTY |> ONE |> THREE |> ADD |> TWO |> MUL |> SHOW ``` -### Going further | Идем дальше +### Going further | Идём дальше > This is fun -- what else can we do? @@ -496,20 +496,20 @@ EMPTY |> ONE |> THREE |> ADD |> TWO |> MUL |> SHOW Можно определить несколько дополнительных функций: ```fsharp -/// Duplicate the top value on the stack +/// Дублирование значения на вершине стека let DUP stack = - // get the top of the stack + // получение вершины стека let x,_ = pop stack - // push it onto the stack again + // добавление её обратно в стек push x stack -/// Swap the top two values +/// Обменять местами два верхних значения let SWAP stack = let x,s = pop stack let y,s' = pop s push y (push x s') -/// Make an obvious starting point +/// Создать начальную точку let START = EMPTY ``` @@ -538,7 +538,7 @@ START > But that's not all. In fact, there is another very interesting way to think about these functions. -Но и это еще не все, на самом деле, существует другой интересный способ представления данных функций. +Но и это ещё не всё. На самом деле, существует другой интересный способ представления этих функций. > As I pointed out earlier, they all have an identical signature: @@ -550,62 +550,58 @@ Stack -> Stack > So, because the input and output types are the same, these functions can be composed using the composition operator `>>`, not just chained together with pipes. -Т.к. ввод и вывод имеют одинаковые типы, эти функции могут быть скомпонованы еще и при помощи `>>`, а не только посредством конвейерных операторов. +Поскольку ввод и вывод имеют одинаковые типы, эти функции могут быть скомпонованы еще и при помощи оператора композиции `>>`, а не только посредством конвейерных операторов. > Here are some examples: Несколько примеров: ```fsharp -// define a new function +// определение новой функции let ONE_TWO_ADD = ONE >> TWO >> ADD -// test it START |> ONE_TWO_ADD |> SHOW -// define a new function +// определяем ещё одну let SQUARE = DUP >> MUL -// test it START |> TWO |> SQUARE |> SHOW -// define a new function +// и ещё одна функция let CUBE = DUP >> DUP >> MUL >> MUL -// test it START |> THREE |> CUBE |> SHOW -// define a new function +// и ещё let SUM_NUMBERS_UPTO = DUP // n >> ONE >> ADD // n+1 >> MUL // n(n+1) >> TWO >> SWAP >> DIV // n(n+1) / 2 -// test it START |> THREE |> SQUARE |> SUM_NUMBERS_UPTO |> SHOW ``` > In each of these cases, a new function is defined by composing other functions together to make a new one. This is a good example of the "combinator" approach to building up functionality. -В каждом из этих примеров определяется новая функция составленная из других функций. Это хороший пример "комбинаторного" подхода к построению функциональности. +В каждом из этих примеров новая функция определяется с помощью композиции других функций. Это хороший пример "комбинаторного" подхода к построению функциональности. ## Pipes vs composition | Конвейеры против композиции > We have now seen two different ways that this stack based model can be used; by piping or by composition. So what is the difference? And why would we prefer one way over another? -Были показаны два различных способа использования данной основанной на стеке модели; при помощи конвейеров и композиций. Но в чем разница? И почему надо предпочесть один из способов другому? +Мы видели два различных способа использования нашей модели; при помощи конвейеров и композиции. Но в чем разница? И почему надо предпочесть один из способов другому? > The difference is that piping is, in a sense, a "realtime transformation" operation. When you use piping you are actually doing the operations right now, passing a particular stack around. -Разница в том, что pipe-ы в некотором смысле являются операцией "в реальном времени". В момент использования конвейера операции выполняются прямо сейчас, через передачу определенного стека. +Разница заключается в том, что конвейеры в некотором смысле являются операцией "трансформации в реальном времени". В момент использования конвейера операции выполняются сразу, через передачу определенного стека. > On the other hand, composition is a kind of "plan" for what you want to do, building an overall function from a set of parts, but *not* actually running it yet. -С другой стороны, композиция является разновидностью "плана", который мы хотим осуществить, построение функций из набора составляющих без непосредственного применения. +С другой стороны, композиция - это нечто вроде "плана", который мы хотим осуществить, построение функций из набора составляющих без непосредственного применения. > So for example, I can create a "plan" for how to square a number by combining smaller operations: @@ -656,25 +652,25 @@ let LAMBDA_SQUARE = unary (fun x -> x * x) > Here's the complete code for all the examples so far. -Полный код для всех примеров представленных ранее: +Полный код для всех примеров, представленных выше: ```fsharp // ============================================== -// Types +// Типы // ============================================== type Stack = StackContents of float list // ============================================== -// Stack primitives +// Стековые примитивы // ============================================== -/// Push a value on the stack +/// Поместить значение в стек let push x (StackContents contents) = StackContents (x::contents) -/// Pop a value from the stack and return it -/// and the new stack as a tuple +/// Вытолкнуть значение из стека и вернуть его +/// и новый стек в виде пары let pop (StackContents contents) = match contents with | top::rest -> @@ -684,62 +680,62 @@ let pop (StackContents contents) = failwith "Stack underflow" // ============================================== -// Operator core +// Ядро (операторы) // ============================================== -// pop the top two elements -// do a binary operation on them -// push the result +// вытолкнуть два верхних элемента +// применить к ним бинарную операцию +// положить результат в стек let binary mathFn stack = let y,stack' = pop stack let x,stack'' = pop stack' let z = mathFn x y push z stack'' -// pop the top element -// do a unary operation on it -// push the result +// вытолкнуть вершину стека +// применить к ней унарную операцию +// положить результат в стек let unary f stack = let x,stack' = pop stack push (f x) stack' // ============================================== -// Other core +// Ядро (остальное) // ============================================== -/// Pop and show the top value on the stack +/// Вытолкнуть и напечатать вершину стека let SHOW stack = let x,_ = pop stack printfn "The answer is %f" x - stack // keep going with same stack + stack // продолжаем с тем же стеком -/// Duplicate the top value on the stack +/// Дублировать вершину стека let DUP stack = let x,s = pop stack push x (push x s) -/// Swap the top two values +/// Обменять два верхних значения местами let SWAP stack = let x,s = pop stack let y,s' = pop s push y (push x s') -/// Drop the top value on the stack +/// Удалить вершину стека let DROP stack = - let _,s = pop stack //pop the top of the stack - s //return the rest + let _,s = pop stack //вытолкнуть вершину стека + s //вернуть всё остальное // ============================================== -// Words based on primitives +// Слова, построенные на примитивах // ============================================== -// Constants +// Костанты // ------------------------------- let EMPTY = StackContents [] let START = EMPTY -// Numbers +// Числа // ------------------------------- let ONE = push 1.0 let TWO = push 2.0 @@ -747,7 +743,7 @@ let THREE = push 3.0 let FOUR = push 4.0 let FIVE = push 5.0 -// Math functions +// MАрифметические функции // ------------------------------- let ADD = binary (+) let SUB = binary (-) @@ -758,7 +754,7 @@ let NEG = unary (fun x -> -x) // ============================================== -// Words based on composition +// Слова, построенные с помощью композиции // ============================================== let SQUARE = @@ -778,11 +774,11 @@ let SUM_NUMBERS_UPTO = > So there we have it, a simple stack based calculator. We've seen how we can start with a few primitive operations (`push`, `pop`, `binary`, `unary`) and from them, build up a whole domain specific language that is both easy to implement and easy to use. -У нас получился простой калькулятор на основе стека. Мы увидели как начиная с нескольких примитивных операций (`push`, `pop`, `binary`, `unary`) и других, можно построить полноценную DSL, легко написанную, легко используемую. +У нас получился простой калькулятор на основе стека. Мы увидели, как, начиная с нескольких примитивных операций (`push`, `pop`, `binary`, `unary`) и других, можно построить полноценный DSL, простой в реализации и использовании. > As you might guess, this example is based heavily on the Forth language. I highly recommend the free book ["Thinking Forth"](http://thinking-forth.sourceforge.net/), which is not just about the Forth language, but about (*non* object-oriented!) problem decomposition techniques which are equally applicable to functional programming. -Как можно догадаться, данный пример был в изрядной степени основан на языке Forth. Я очень рекомендую бесплатную книгу ["Thinking Forth"](http://thinking-forth.sourceforge.net/), которая повествует не только об языке Forth, но и об других (_не_ объектно ориентированных!) методах декомпозиции задач, которые одинаково применимы к функциональному программированию в целом. +Как можно догадаться, данный пример был в изрядной степени основан на языке Forth. Я очень рекомендую бесплатную книгу ["Thinking Forth"](http://thinking-forth.sourceforge.net/), которая повествует не только о языке Forth, но и о других (_не_ объектно-ориентированных!) методах декомпозиции задач, которые одинаково применимы к функциональному программированию в целом. > I got the idea for this post from a great blog by [Ashley Feniello](http://blogs.msdn.com/b/ashleyf/archive/2011/04/21/programming-is-pointless.aspx). If you want to go deeper into emulating a stack based language in F#, start there. Have fun! From b75fb1eda5486aaaacb8c7885045b7b387f81354 Mon Sep 17 00:00:00 2001 From: Roman Melnikov Date: Fri, 5 Apr 2019 06:27:34 +0300 Subject: [PATCH 48/48] Update stack-based-calculator.md --- posts/stack-based-calculator.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/posts/stack-based-calculator.md b/posts/stack-based-calculator.md index 540d7dc..176d2e3 100644 --- a/posts/stack-based-calculator.md +++ b/posts/stack-based-calculator.md @@ -44,7 +44,7 @@ type Stack = float list > But, hold on, let's wrap it in a [single case union type](../posts/discriminated-unions.md#single-case) to make it more descriptive, like this: -Но лучше обернуть его в [single case union type](../posts/discriminated-unions.md#single-case), чтобы сделать тип более наглядным, например так: +Но лучше обернуть его в [single case union type](https://fsharpforfunandprofit.com/posts/discriminated-unions/#single-cases), чтобы сделать тип более наглядным, например так: ```fsharp type Stack = StackContents of float list @@ -52,7 +52,7 @@ type Stack = StackContents of float list > For more details on why this is nicer, read the discussion of single case union types in [this post](../posts/discriminated-unions.md#single-case). -Почему лучше делать именно так, можно прочитать [здесь](../posts/discriminated-unions.md#single-case). +Почему лучше делать именно так, можно прочитать [здесь](https://fsharpforfunandprofit.com/posts/discriminated-unions/#single-cases). > Now, to create a new stack, we use `StackContents` as a constructor: @@ -107,7 +107,7 @@ let push x aStack = > Next, what should the order of the parameters be? Should the stack parameter come first or last? If you remember the discussion of [designing functions for partial application](../posts/partial-application), you will remember that the most changeable thing should come last. You'll see shortly that this guideline will be born out. -Во вторых, почему параметры идут именно в таком порядке? Почему стек должен идти первым или последним? В разделе[проектирование функций с частичным применением](../posts/partial-application) говорилось, что наиболее часто меняющийся параметр должен идти последним. Вскоре можно будет убедиться, что эти рекомендации соблюдаются. +Во вторых, почему параметры идут именно в таком порядке? Почему стек должен идти первым или последним? В разделе[проектирование функций с частичным применением](https://habr.com/ru/company/microsoft/blog/430622/) говорилось, что наиболее часто меняющийся параметр должен идти последним. Вскоре можно будет убедиться, что эти рекомендации соблюдаются. > Finally, the function can be made more concise by using pattern matching in the function parameter itself, rather than using a `let` in the body of the function. @@ -138,7 +138,7 @@ val push : float -> Stack -> Stack > In this case, I could probably guess what it did from the signature alone, even without knowing that the name of the function was "push". > This is one of the reasons why it is a good idea to have explicit type names. If the stack type had just been a list of floats, it wouldn't have been as self-documenting. -Как говорилось [ранее](../posts/function-signatures), сигнатура сообщает нам очень многое. +Как говорилось [ранее](https://habr.com/ru/company/microsoft/blog/433402/), сигнатура сообщает нам очень многое. В данном случае я мог бы догадаться, что делает данная функция, лишь по ее сигнатуре, даже не зная, что она называется "push". Это еще одна причина по которой было хорошей идеей иметь явные имена типа. Если бы стек был лишь списком чисел с плавающей точкой, то функция не была бы столь самодокументированной. @@ -285,7 +285,7 @@ let pop (StackContents contents) = > * Option 1: Return a special "Success" or "Error" state, as we did in a [post from the "why use F#?" series](../posts/correctness-exhaustive-pattern-matching.md). > * Option 2: Throw an exception. -* Вариант 1: Вернуть специальное состояние "Success" или "Error", как это делалось в [посте из серии "why use F#?"](../posts/correctness-exhaustive-pattern-matching.md). +* Вариант 1: Вернуть специальное состояние "Success" или "Error", как это делалось в [посте из серии "why use F#?"](https://fsharpforfunandprofit.com/posts/correctness-exhaustive-pattern-matching/). * Вариант 2: Выбросить исключение. > Generally, I prefer to use error cases, but in this case, we'll use an exception. So here's the `pop` code changed to handle the empty case: @@ -743,7 +743,7 @@ let THREE = push 3.0 let FOUR = push 4.0 let FIVE = push 5.0 -// MАрифметические функции +// Арифметические функции // ------------------------------- let ADD = binary (+) let SUB = binary (-)