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

    image

    From the translator: this article is a translation of material written by 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 a pure 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 figures


    Levels of abstraction

    If you strongly generalize, 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 certain 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 you should first try the first type - “fast” languages: they allow you to immediately see the result of work and learn from your own mistakes. The first 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 
    defget_request_task(url: str) -> Union[List[Dict[str, Any]], None]:try:
        contents = Nonewith request.urlopen(url) as response:
          contents = response.read()
        return contents
      except HTTPException:
        returnNonewith 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 isNone:
          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 perform any calculation 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 to write an application on one of them.

    import Foundation
    import Dispatch
    funcgetRequestTask(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) iniflet data = data {
            iflet 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 exploring 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 whereimport 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 EnglishfilterEnglishTweets :: StreamingAPI -> BoolfilterEnglishTweets tweet =
      let
        langIsEnglish (Status {statusLang=language}) = case language ofJust"en" -> True
          _ -> Falseincase tweet ofSStatus statusObj -> langIsEnglish statusObj
        SRetweetedStatus (RetweetedStatus {rsRetweetedStatus=statusObj}) ->
          langIsEnglish statusObj
        _ -> False-- Filters Twitter tweets that are original poststweetParser :: StreamingAPI -> MaybeStringtweetParser tweet = case tweet ofSStatus (Status {statusText=status}) -> Just $ unpack status
      SRetweetedStatus (RetweetedStatus {rsRetweetedStatus=rstatus}) ->
        Just $ unpack $ statusText rstatus
      _ -> Nothingmain :: 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 variants, 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 is well suited in certain situations. For example, Java meets many orchestration and network logistics requirements, including database support through standardization of the JDBC interface and projects similar to those supported by the Apache Foundation. The same goes for Python — it’s ideal for data analysis and statistical computation — 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 " - 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)
    classPyObject(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

    Python implementation of the garbage collector.


    Results of the work of the program code mentioned above

    In PHP (starting with version PHP5.3), another version of garbage collection 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 below is shown when the “strong” counter of the entire object reaches 0 and is cleared by Person (since it correlates poorly with Apartment).

    classPerson{
        let name: Stringinit(name: String) { self.name = name }
        var apartment: Apartment?deinit { print("\(name) is being deinitialized") }
    }
    classApartment{
        let unit: Stringinit(unit: String) { self.unit = unit }
        weakvar 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


    Creating and managing packages

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

    Python uses pip in tandem with a requirements.txt file for dependency management 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 decide on 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"
    ];
    functiongetSiteContents(url) {
      returnnewPromise(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 PromisesPromise.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 synchronousasyncfunctionmain () {
      const siteContents = awaitPromise.all(urlList.map(getSiteContents))
      console.log("Main() based execution --->");
      console.log(siteContents);
    }
    main();
    // As Promises will happen in some future time, this will happen firstconsole.log("This console.log will most likely happen first");

    Implementing Asynchronous I / O Using 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;
      returnfunction()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 very 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.

    funccontainedClosureIncrementer() -> (() -> Int) {
        var anInt = 0funcincrementer() -> Int {
            anInt = anInt + 1return anInt
        }
        return incrementer
    }
    funccontainedClosureIncrementer2() -> () -> Int {
        var anInt = 0return {
            anInt = anInt + 1return 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] asArray<Optional<String>>).forEach({
        someOptionalValue iniflet 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) -> Intinlet multiplicand = 2return 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)")
    functestGuard() {
        let someOptionalValue: Optional<String> = nil;
        guardlet someOptionalValueUnwrapped = someOptionalValue else {
            print("testGuard: Thrown exception - nil value")
            return
        }
        print("testGuard: no exception - non-nil value: \(someOptionalValueUnwrapped)")
    }
    testGuard()
    classRuntimeError: Error{}
    [{throwRuntimeError()}, {1} as () throws -> Int].forEach {
        let returnValue = try? $0()
        iflet 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 sourceThey give an idea of ​​how a specific programming language and additional libraries are used in professional projects.

    All this will help 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:


    Also popular now: