📜 ⬆️ ⬇️

Choosing a programming language: 3 tips from a programmer at Apple

image

From the translator: this article is a translation of the material written by the programmer Alastair Paragas from Apple. He worked with such programming languages ​​as Javascript, Python, PHP, Java, Scala, Haskell, Swift and Rust. Alastair shares his own thoughts on the topic of choosing and studying "his" language, because this question is relevant for both beginners and professionals who want to choose a new toolkit.

Do you study a programming language for the sake of employment or advanced training, or is it purely a hobby, sooner or later you will have to choose between them. How to do it? This is a difficult question, but you can answer it this way: every day thousands of programmers do it. To make things easier for yourself, you should follow a few principles.

Skillbox recommends: Practical course "Profession Web developer" .
We remind: for all readers of Habr - a discount of 10,000 rubles when writing to any Skillbox course on the promotional code "Habr"
.

Comparative performance


Levels of abstraction
')
If we generalize strongly, we can say that modern programming languages ​​are divided into three types:

  1. "Fast", which are used for the rapid creation of applications or their prototypes.
  2. “Infrastructure”, which helps to optimize or refine individual parts of an application that has already been written in order to improve its performance.
  3. The so-called system programming languages, the use of which allows you to get full control over the memory of the device.

Of course, the real division into types among programming languages ​​is less strict: there are intermediate, hybrid versions of various types.

If we talk about learning languages, then first you should try the first type - “fast” languages: they allow you to immediately see the result of the work and learn from your own mistakes. First of all, it is PHP, Javascript, Ruby and Python. The entry threshold here is minimal, and you can learn the basics in a short time. These languages ​​have standard libraries that allow you to add a large number of functions to the application, and the range of their capabilities is quite large.

from concurrent.futures import ThreadPoolExecutor from http.client import HTTPException from urllib import request from typing import Union, Dict, Any, List def get_request_task(url: str) -> Union[List[Dict[str, Any]], None]: try: contents = None with request.urlopen(url) as response: contents = response.read() return contents except HTTPException: return None with ThreadPoolExecutor() as executor: for result in executor.map(get_request_task, [ "https://jsonplaceholder.typicode.com/posts", "https://jsonplaceholder.typicode.com/comments", "https://jsonplaceholder.typicode.com/albums" ]): if result is None: print("Something terrible has happened!") else: print(result) 

Implementing multithreaded HTTP requests in Python with static typing. Multithreading provides for the possibility of alternating three tasks (let's call them tasks A, B and C). While one task (say, task A) performs some operation with I / O binding (and, therefore, does not do any computational work), other tasks are performed simultaneously with it.

As for the “infrastructure” languages, these are Java, Kotlin, Scala, Clojure, as well as GoLang, Swift and Haskell. You can call them comfortable with a stretch, but they allow you to create productive applications. Among the difficulties is a smaller number of elements "out of the box", the exact syntax, etc. These languages ​​are good because they allow you to fine-tune the application. If you need speed, try writing an application on one of them.

 import Foundation import Dispatch func getRequestTask(url: String, dispatchGroup: DispatchGroup) { dispatchGroup.enter() let request = URLRequest(url: URL(string: url)!) let task = URLSession(configuration: URLSessionConfiguration.default).dataTask( with: request, completionHandler: { (data, response, error) in if let data = data { if let dataAsString = String(data: data, encoding: .utf8) { print(dataAsString) dispatchGroup.leave() return } } print("Something terrible has happened!") dispatchGroup.leave() } ) task.resume() } let requestDispatchGroup = DispatchGroup() for url in [ "https://jsonplaceholder.typicode.com/posts", "https://jsonplaceholder.typicode.com/comments", "https://jsonplaceholder.typicode.com/albums" ] { getRequestTask(url: url, dispatchGroup: requestDispatchGroup) } requestDispatchGroup.wait() 

A similar problem has already been solved above using Python. Now in business - Swift.

System programming languages ​​- C, C ++, Rust. They give maximum control over the application, including memory management. Also, these languages ​​are great for programming microcontrollers, computers with non-standard processor architecture and other systems. Low-level languages ​​are still important and most likely will remain relevant in the near future.

Functionality

As you know, languages ​​serve as a means of “communication” between a computer and a programmer. In order for this communication to go smoothly, it is worth studying the syntax of the language in detail. In particular, the specialist must know the most frequently used data structures and understand how you can modify certain elements of your application.

 module Main where import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.Resource (runResourceT) import Data.Conduit (($$+-), ($=+), runConduit) import Data.Conduit.List (mapM_, map, filter, catMaybes) import Data.Text (unpack) import Data.Maybe (fromJust) import Web.Twitter.Types (StreamingAPI(SStatus, SRetweetedStatus) , Status(Status), statusText, statusLang , RetweetedStatus(RetweetedStatus), rsRetweetedStatus ) import Web.Twitter.Conduit.Stream (stream) -- Filters Twitter tweets that are written only in English filterEnglishTweets :: StreamingAPI -> Bool filterEnglishTweets tweet = let langIsEnglish (Status {statusLang=language}) = case language of Just "en" -> True _ -> False in case tweet of SStatus statusObj -> langIsEnglish statusObj SRetweetedStatus (RetweetedStatus {rsRetweetedStatus=statusObj}) -> langIsEnglish statusObj _ -> False -- Filters Twitter tweets that are original posts tweetParser :: StreamingAPI -> Maybe String tweetParser tweet = case tweet of SStatus (Status {statusText=status}) -> Just $ unpack status SRetweetedStatus (RetweetedStatus {rsRetweetedStatus=rstatus}) -> Just $ unpack $ statusText rstatus _ -> Nothing main :: IO () main = do -- a bunch of connection setup details to Twitter {- Imagine a stream/production line of continually incoming Twitter tweets out of this stream, non-English tweets are removed each remaining tweet then gets packaged into one of two forms - one for original tweets - one for non-original tweets (retweets and whatnot) We then only grab packaged forms of original tweets and display them! -} in runResourceT $ do stream <- stream twitterInfo connectionManager apiRequest stream $=+ Data.Conduit.List.filter filterEnglishTweets $=+ Data.Conduit.List.map tweetParser $=+ Data.Conduit.List.catMaybes $$+- Data.Conduit.List.mapM_ (liftIO . putStrLn) 

Haskell is a rigorous functional programming language. It can check incoming data structures and work with them if they meet certain requirements.

Runtime - you need to know how your application will work on different systems. Do I need a language interpreter (for example, Python, NodeJS, PHP)? Is a system-dependent binary generated (for example, Swift and GoLang)? Does the selected language use a combination of the first and second options, for example, the application is compiled and run on some virtual machine (Java, Scala, Clojure)?
By the way, on the way to perfection, it is highly recommended to study and start using Docker plus be sure to understand the principles of Linux administration.

Libraries - each language works well in certain situations. For example, Java meets many orchestration and network logistics requirements, including database support through JDBC interface standardization and projects similar to those supported by the Apache Foundation. The same goes for Python — it’s ideal for data analysis and statistical calculations — as well as Haskell with its grammars, regular expressions, and compilers. The popularity of the language and the size of its community are two more arguments that speak in favor of using certain software in their project. If the community is small, then you should not count on the quick assistance of its participants. Conversely, the larger the community and the more popular the programming language, the faster you can solve a complex task or get advice from colleagues.

Garbage collection

"Garbage collection" is one of the forms of automatic memory management. A special process, called the garbage collector, periodically frees up memory, removing objects that are no longer required by applications. Each programming language does this in its own way.

Python implements reference counting using the stop-the-world algorithm. It pauses the execution of the program, starts and performs garbage collection, then resumes the execution of the main process. In the course of the “cleaning”, there are 3 separate “generations” - a set of “garbage heaps”. Zero contains the most "fresh" objects, then there are generations 1 and 2.

 import gc import ctypes gc.set_debug(gc.DEBUG_SAVEALL) class PyObject(ctypes.Structure): _fields_ = [("refcnt", ctypes.c_long)] object1 = {} object2 = {} object3 = {} object1['reference_to_2'] = object2 object2['reference_to_1'] = object1 object3['some_key'] = 1 object1_memory_address = id(object1) object2_memory_address = id(object2) object3_memory_address = id(object3) print "Before garbage collection --->" print "Refcount for object1: {count}".format( count=PyObject.from_address(object1_memory_address).refcnt ) print "Refcount for object2: {count}".format( count=PyObject.from_address(object2_memory_address).refcnt ) print "Refcount for object3: {count}".format( count=PyObject.from_address(object3_memory_address).refcnt ) del object1, object2, object3 gc.collect() print "After garbage collection --->" print "Refcount for object1: {count}".format( count=PyObject.from_address(object1_memory_address).refcnt ) print "Refcount for object2: {count}".format( count=PyObject.from_address(object2_memory_address).refcnt ) print "Refcount for object3: {count}".format( count=PyObject.from_address(object3_memory_address).refcnt ) print "Objects that cannot be cleaned up by reference counting: --->" for x in gc.garbage: print x 

Implementing a python garbage collector


The results of the program code above

In PHP (starting with version PHP5.3), another garbage collection option is used along with reference counting. Here, this process is performed with the program if necessary. Subgraphs that cannot be reached from the root are eliminated.

Swift also uses reference counting; there are no other ways to collect garbage. The case is shown below, when the “strong” counter of the entire object reaches 0 and is cleared by Person (since it correlates poorly with Apartment).

 class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { print("\(name) is being deinitialized") } } class Apartment { let unit: String init(unit: String) { self.unit = unit } weak var tenant: Person? deinit { print("Apartment \(unit) is being deinitialized") } 



Examples of garbage collection mechanisms implemented in other languages ​​are numerous. They can affect the overall performance of the application, and work without affecting the performance of the main task.

Duplicate concepts


Package creation and management

Familiarize yourself with the mechanisms for storing and tracking dependencies, as well as ways to maintain information about the "assembly" (package description, how to run unit tests, configure and prepare the environment, etc.).

Python uses pip in tandem with a requirements.txt file to manage dependencies and setup.py to manage environment settings , Haskell works with Cabal for both tasks, Java has Maven and Gradle , in the case of Scala, SBT works, PHP uses Composer , NodeJS - npm , etc.

Be sure to determine the localization of the development environment - you may want to run different versions of programming languages ​​depending on the project. Phpbrew for PHP, pyenv for Python and nvm for NodeJS allow you to do this.


Using pyenv, you can work with different versions of Python.

In particular cases it happens that the library used in one project is automatically installed in the others. This is true, in particular, for languages ​​like Python and Haskell. To avoid this problem, you should use virtualenv / venv for Python, virtphp for PHP and Cabal Sandboxes for Haskell.



Asynchronous I / O

This is an opportunity to increase application data I / O performance. In addition, each thread works with its own set of registers and information about the stack.



 const https = require("https"); const urlList = [ "https://reqres.in/api/users?page=1", "https://reqres.in/api/users?page=2", "https://reqres.in/api/users?page=3" ]; function getSiteContents(url) { return new Promise(function (resolve, reject) { https.get(url, function (res) { var bodyData = ""; res.on("data", function (chunk) { bodyData += chunk; }); res.on("end", function () { resolve(bodyData); }); res.on("error", function (error) { reject(error.message); }); }); }); } // One way we can proceed with execution // Make one Promise out of a list of Promises Promise.all(urlList.map(getSiteContents)) .then(function (siteContents) { console.log("Promise based execution --->"); console.log(siteContents); }); // Another way we can proceed with execution // "async" is an ES7 feature that makes our Promise/async I/O code look // more synchronous async function main () { const siteContents = await Promise.all(urlList.map(getSiteContents)) console.log("Main() based execution --->"); console.log(siteContents); } main(); // As Promises will happen in some future time, this will happen first console.log("This console.log will most likely happen first"); 

Implement Asynchronous I / O with Javascript

Functional programming

Functional programming allows you to “tell” the computer at a high level what you want from it. Most languages ​​today have the most basic possibilities for implementing this: through map , filter , reduce for lists , etc. But it is still worth using. Below is an example of functional programming in a language that does not seem to imply such an opportunity.

 <?php // Accumulator gets passed around for reuse - function as a value $accumulator = function ( string $accumulated_string, string $mapped_list_element ) { return $accumulated_string . $mapped_list_element . "\n"; }; // Notice how array_map, array_filter and array_reduce // accept functions as parameters - they are higher order functions $mapped_array = array_reduce( array_map( function (int $list_element): string { return "A list element: " . $list_element; }, [1, 2, 3, 4] ), $accumulator, "" ); echo "Mapped Array: \n"; echo $mapped_array; $filtered_array = array_reduce( array_filter( [1, 2, 3, 4], function (int $list_element): bool { return $list_element > 2; } ), $accumulator, "" ); echo "Filtered Array: \n"; echo $filtered_array; // Closures "enclose" over their surrounding state // The $closure_incrementer function here returns a function // making it a higher order function. echo "Closure Incrementer: \n"; $closure_incrementer = function () { $internal_variable = 0; return function () use (&$internal_variable) { return $internal_variable += 1; }; }; $instance = $closure_incrementer(); echo $instance() . " is equal to 1\n"; echo $instance() . " is equal to 2\n"; 

Training


The first stage is the search for the necessary information on specialized resources and the creation of a small project after the basic training has been completed. In most cases, you can use articles like “Learn X in Y days”, many of them are quite good. In many cases, there are interactive examples for learning: A Tour of GoLang and GoLang by example (for GoLang), NodeSchool Command Line exercises (for Javascript, namely NodeJS), Scala Exercises (for Scala), Python Koans (for Python) and t .P.

Starting with something difficult is not worth it. Creating small applications and scripts is what a newbie needs. The total number of lines of code in such experiments does not exceed 300-400. The main thing that is necessary at this stage is to get basic information, learn how to program at any normal speed, and the most important thing is to understand what you are doing.

 func containedClosureIncrementer() -> (() -> Int) { var anInt = 0 func incrementer() -> Int { anInt = anInt + 1 return anInt } return incrementer } func containedClosureIncrementer2() -> () -> Int { var anInt = 0 return { anInt = anInt + 1 return anInt } } let closureIncrementer = containedClosureIncrementer() print("containedClosureIncrementer call - should be 1: \(closureIncrementer() == 1)") print("containedClosureIncrementer call - should be 2: \(closureIncrementer() == 2)") var someOptionalValue: Optional<String> = nil; print("Optional - someOptionalValue is null: \(someOptionalValue == nil)") someOptionalValue = "real value" print("Optional - someOptionalValue is 'real value' \(someOptionalValue == "real value")") (["real value", nil] as Array<Optional<String>>).forEach({ someOptionalValue in if let someValue = someOptionalValue { if someValue.hasPrefix("real") { print("someValue: has real") } else { print("someValue: doesn't have real") } } else { print("someValue: has nil") } }) if (someOptionalValue ?? "").hasPrefix("real") { print("Has real 2") } else { print("Doesn't have real") } let numbersList: [Int] = Array(1...10) print("List of numbers 1 to 10: \(numbersList)") let numbersListTimes2 = numbersList.map({ (someNumber: Int) -> Int in let multiplicand = 2 return someNumber * multiplicand }) let numbersListTimes2V2 = numbersList.map({ number in number * 2 }) let numbersListTimes2V3 = numbersList.map { $0 * 2 } print("List of numbers * 2: \(numbersListTimes2)") print("V1, V2 Map operations do the same thing: \(numbersListTimes2 == numbersListTimes2V2)") print("V1, V3 Map operations do the same thing: \(numbersListTimes2 == numbersListTimes2V3)") func testGuard() { let someOptionalValue: Optional<String> = nil; guard let someOptionalValueUnwrapped = someOptionalValue else { print("testGuard: Thrown exception - nil value") return } print("testGuard: no exception - non-nil value: \(someOptionalValueUnwrapped)") } testGuard() class RuntimeError: Error {} [{throw RuntimeError()}, {1} as () throws -> Int].forEach { let returnValue = try? $0() if let returnValueUnwrapped = returnValue { print("List of closures: A normal value was returned \(returnValueUnwrapped)") } else { print("List of closures: An error was thrown") } } 

An example of an initial script that gives an idea to a novice programmer about how its code works.

The second stage is a deeper learning of the language, the creation of a full-fledged project that can no longer be called "childish." In many cases it is necessary to get acquainted with the official documentation. For Javascript this is Mozilla Developer Docs , for Swift - Swift Official Docs , for Java - Java Learning Trails , for Python - Python Official Docs t. Special attention should be paid to online courses with good teachers.

In addition , it is worth exploring other open source projects. Resources like Annotated jQuery source or Annotated BackboneJS source give an idea of ​​how a specific programming language and additional libraries are used in professional projects.

All this will help to create your own serious project, for example, desktop application, web application, mobile program. Try to use external libraries when you need additional tools and functions.



Do not forget about the performance of your application, always try to get information from the latest sources. Learning opportunities are endless, you can improve forever. But in the end you can feel how from a beginner you have become a professional - and there is simply no better feeling in the world.
Skillbox recommends:

Source: https://habr.com/ru/post/421533/


All Articles