One place for hosting & domains

      Functions

      How To Run Multiple Functions Concurrently in Go


      The author selected the Diversity in Tech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      One of the popular features of the Go language is its first-class support for concurrency, or the ability of a program to do multiple things at once. Being able to run code concurrently is becoming a larger part of programming as computers move from running a single stream of code faster to running more streams of code simultaneously. To run programs faster, a programmer needs to design their programs to run concurrently, so that each concurrent part of the program can be run independently of the others. Two features in Go, goroutines and channels, make concurrency easier when used together. Goroutines solve the difficulty of setting up and running concurrent code in a program, and channels solve the difficulty of safely communicating between the code running concurrently.

      In this tutorial, you will explore both goroutines and channels. First, you will create a program that uses goroutines to run multiple functions at once. Then you will add channels to that program to communicate between the running goroutines. Finally, you’ll add more goroutines to the program to simulate a program running with multiple worker goroutines.

      Prerequisites

      To follow this tutorial, you will need:

      Running Functions at the Same Time with Goroutines

      In a modern computer, the processor, or CPU, is designed to run as many streams of code as possible at the same time. These processors have one or more “cores,” each capable of running one stream of code at the same time. So, the more cores a program can use simultaneously, the faster the program will run. However, in order for programs to take advantage of the speed increase that multiple cores provide, programs need to be able to be split into multiple streams of code. Splitting a program into parts can be one of the more challenging things to do in programming, but Go was designed to make this easier.

      One way Go does this is with a feature called goroutines. A goroutine is a special type of function that can run while other goroutines are also running. When a program is designed to run multiple streams of code at once, the program is designed to run concurrently. Typically, when a function is called, it will finish running completely before the code after it continues to run. This is known as running in the “foreground” because it prevents your program from doing anything else before it finishes. With a goroutine, the function call will continue running the next code right away while the goroutine runs in the “background”. Code is considered running in the background when it doesn’t prevent other code from running before it finishes.

      The power goroutines provide is that each goroutine can run on a processor core at the same time. If your computer has four processor cores and your program has four goroutines, all four goroutines can run simultaneously. When multiple streams of code are running at the same time on different cores like this, it’s called running in parallel.

      To visualize the difference between concurrency and parallelism, consider the following diagram. When a processor runs a function, it doesn’t always run it from beginning to completion all at once. Sometimes the operating system will interleave other functions, goroutines, or other programs on a CPU core when a function is waiting for something else to happen, such as reading a file. The diagram shows how a program designed for concurrency can run on a single core as well as multiple cores. It also shows how more segments of a goroutine can fit into the same timeframe (9 vertical segments, as seen in the diagram) when running in parallel than when running on a single core.

      Diagram split into two columns, labeled Concurrency and Parallelism. The Concurrency column has a single tall rectangle, labeled CPU core, divided into stacked sections of varying colors signifying different functions. The Parallelism column has two similar tall rectangles, both labeled CPU core, with each stacked section signifying different functions, except it only shows goroutine1 running on the left core and goroutine2 running on the right core.

      The left column in the diagram, labeled “Concurrency”, shows how a program designed around concurrency could run on a single CPU core by running part of goroutine1, then another function, goroutine, or program, then goroutine2, then goroutine1 again, and so on. To a user, this would seem like the program is running all the functions or goroutines at the same time, even though they’re actually being run in small parts one after the other.

      The column on the right of the diagram, labeled “Parallelism”, shows how that same program could run in parallel on a processor with two CPU cores. The first CPU core shows goroutine1 running interspersed with other functions, goroutines, or programs, while the second CPU core shows goroutine2 running with other functions or goroutines on that core. Sometimes both goroutine1 and goroutine2 are running at the same time as each other, just on different CPU cores.

      This diagram also shows another of Go’s powerful traits, scalability. A program is scalable when it can run on anything from a small computer with a few processor cores to a large server with dozens of cores, and take advantage of those additional resources. The diagram shows that by using goroutines, your concurrent program is capable of running on a single CPU core, but as more CPU cores are added, more goroutines can be run in parallel to speed up the program.

      To get started with your new concurrent program, create a multifunc directory in the location of your choosing. You may already have a directory for your projects, but in this tutorial, you’ll create a directory called projects. You can create the projects directory either through an IDE or via the command line.

      If you’re using the command line, begin by making the projects directory and navigating to it:

      • mkdir projects
      • cd projects

      From the projects directory, use the mkdir command to create the program’s directory (multifunc) and then navigate into it:

      • mkdir multifunc
      • cd multifunc

      Once you’re in the multifunc directory, open a file named main.go using nano, or your favorite editor:

      Paste or type the following code in the main.go file to get started.

      projects/multifunc/main.go

      package main
      
      import (
          "fmt"
      )
      
      func generateNumbers(total int) {
          for idx := 1; idx <= total; idx++ {
              fmt.Printf("Generating number %dn", idx)
          }
      }
      
      func printNumbers() {
          for idx := 1; idx <= 3; idx++ {
              fmt.Printf("Printing number %dn", idx)
          }
      }
      
      func main() {
          printNumbers()
          generateNumbers(3)
      }
      

      This initial program defines two functions, generateNumbers and printNumbers, then runs those functions in the main function. The generateNumbers function takes the amount of numbers to “generate” as a parameter, in this case one through three, and then prints each of those numbers to the screen. The printNumbers function doesn’t take any parameters yet, but it will also print out the numbers one through three.

      Once you’ve saved the main.go file, run it using go run to see the output:

      The output will look similar to this:

      Output

      Printing number 1 Printing number 2 Printing number 3 Generating number 1 Generating number 2 Generating number 3

      You’ll see the functions run one after the other, with printNumbers running first and generateNumbers running second.

      Now, imagine that printNumbers and generateNumbers each takes three seconds to run. When running synchronously, or one after the other like the last example, your program would take six seconds to run. First, printNumbers would run for three seconds, and then generateNumbers would run for three seconds. In your program, however, these two functions are independent of the other because they don’t rely on data from the other to run. You can take advantage of this to speed up this hypothetical program by running the functions concurrently using goroutines. When both functions are running concurrently the program could, in theory, run in half the time. If both the printNumbers and the generateNumbers functions take three seconds to run and both start at exactly the same time, the program could finish in three seconds. (The actual speed could vary due to outside factors, though, such as how many cores the computer has or how many other programs are running on the computer at the same time.)

      Running a function concurrently as a goroutine is similar to running a function synchronously. To run a function as a goroutine (as opposed to a standard synchronous function), you only need to add the go keyword before the function call.

      However, for the program to run the goroutines concurrently, you’ll need to make one additional change. You’ll need to add a way for your program to wait until both goroutines have finished running. If you don’t wait for your goroutines to finish and your main function completes, the goroutines could potentially never run, or only part of them could run and not complete running.

      To wait for the functions to finish, you’ll use a WaitGroup from Go’s sync package. The sync package contains “synchronization primitives”, such as WaitGroup, that are designed to synchronize various parts of a program. In your case, the synchronization keeps track of when both functions have finished running so you can exit the program.

      The WaitGroup primitive works by counting how many things it needs to wait for using the Add, Done, and Wait functions. The Add function increases the count by the number provided to the function, and Done decreases the count by one. The Wait function can then be used to wait until the count reaches zero, meaning that Done has been called enough times to offset the calls to Add. Once the count reaches zero, the Wait function will return and the program will continue running.

      Next, update the code in your main.go file to run both of your functions as goroutines using the go keyword, and add a sync.WaitGroup to the program:

      projects/multifunc/main.go

      package main
      
      import (
          "fmt"
          "sync"
      )
      
      func generateNumbers(total int, wg *sync.WaitGroup) {
          defer wg.Done()
      
          for idx := 1; idx <= total; idx++ {
              fmt.Printf("Generating number %dn", idx)
          }
      }
      
      func printNumbers(wg *sync.WaitGroup) {
          defer wg.Done()
      
          for idx := 1; idx <= 3; idx++ {
              fmt.Printf("Printing number %dn", idx)
          }
      }
      
      func main() {
          var wg sync.WaitGroup
      
          wg.Add(2)
          go printNumbers(&wg)
          go generateNumbers(3, &wg)
      
          fmt.Println("Waiting for goroutines to finish...")
          wg.Wait()
          fmt.Println("Done!")
      }
      

      After declaring the WaitGroup, it will need to know how many things to wait for. Including a wg.Add(2) in the main function before starting the goroutines will tell wg to wait for two Done calls before considering the group finished. If this isn’t done before the goroutines are started, it’s possible things will happen out of order or the code may panic because the wg doesn’t know it should be waiting for any Done calls.

      Each function will then use defer to call Done to decrease the count by one after the function finishes running. The main function is also updated to include a call to Wait on the WaitGroup, so the main function will wait until both functions call Done to continue running and exit the program.

      After saving your main.go file, run it using go run like you did before:

      The output will look similar to this:

      Output

      Printing number 1 Waiting for goroutines to finish... Generating number 1 Generating number 2 Generating number 3 Printing number 2 Printing number 3 Done!

      Your output may differ from what is printed here, and it’s even likely to change every time you run the program. With both functions running concurrently, the output depends on how much time Go and your operating system give for each function to run. Sometimes there is enough time to run each function completely and you’ll see both functions print their entire sequences uninterrupted. Other times, you’ll see the text interspersed like the output above.

      An experiment you can try is removing the wg.Wait() call in the main function and running the program a few times with go run again. Depending on your computer, you may see some output from the generateNumbers and printNumbers functions, but it’s also likely you won’t see any output from them at all. When you remove the call to Wait, the program will no longer wait for both functions to finish running before it continues. Since the main function ends soon after the Wait function, there’s a good chance that your program will reach the end of the main function and exit before the goroutines finish running. When this happens, you’ll see a few numbers printed out, but you won’t see all three from each function.

      In this section, you created a program that uses the go keyword to run two goroutines concurrently and print a sequence of numbers. You also used a sync.WaitGroup to make your program wait for those goroutines to finish before exiting the program.

      You may have noticed that the generateNumbers and printNumbers functions do not have return values. In Go, goroutines aren’t able to return values like a standard function would. You can still use the go keyword to call a function that returns values, but those return values will be thrown out and you won’t be able to access them. So, what do you do when you need data from one goroutine in another goroutine if you can’t return values? The solution is to use a Go feature called “channels”, which allow you to send data from one goroutine to another.

      Communicating Safely Between Goroutines with Channels

      One of the more difficult parts of concurrent programming is communicating safely between different parts of the program that are running simultaneously. If you’re not careful, you might run into problems that are only possible with concurrent programs. For example, a data race can happen when two parts of a program are running concurrently, and one part tries to update a variable while the other part is trying to read it at the same time. When this happens, the reading or writing can happen out of order, leading to one or both parts of the program using the wrong value. The name “data race” comes from both parts of the program “racing” each other to access the data.

      While it’s still possible to run into concurrency issues like data races in Go, the language is designed to make it easier to avoid them. In addition to goroutines, channels are another feature that makes concurrency safer and easier to use. A channel can be thought of like a pipe between two or more different goroutines that data can be sent through. One goroutine puts data into one end of the pipe and another goroutine gets that same data out. The difficult part of making sure the data gets from one to the other safely is handled for you.

      Creating a channel in Go is similar to how you would create a slice, using the built-in make() function. The type declaration for a channel uses the chan keyword followed by the type of data you want to send on the channel. For example, to create a channel for sending int values, you would use the type chan int. If you wanted a channel for sending []byte vaules, it would be chan []byte, like so:

      bytesChan := make(chan []byte)
      

      Once a channel is created, you can send or receive data on the channel by using the arrow-looking <- operator. The position of the <- operator in relation to the channel variable determines whether you’re reading from or writing to the channel.

      To write to a channel, begin with the channel variable, followed by the <- operator, then the value you want to write to the channel:

      intChan := make(chan int)
      intChan <- 10
      

      To read a value from a channel, begin with the variable you want to put the value into, either = or := to assign a value to the variable, followed by the <- operator, and then the channel you want to read from:

      intChan := make(chan int)
      intVar := <- intChan
      

      To keep these two operations straight, it can be helpful to remember that the <- arrow always points to the left (as opposed to ->), and the arrow points to where the value is going. In the case of writing to a channel, the arrow points the value to the channel. When reading from a channel, the arrow points the channel to the variable.

      Like a slice, a channel can also be read using the range keyword in a for loop. When a channel is read using the range keyword, each iteration of the loop will read the next value from the channel and put it into the loop variable. It will then continue reading from the channel until the channel is closed or the for loop is exited in other ways, such as a break:

      intChan := make(chan int)
      for num := range intChan {
          // Use the value of num received from the channel
          if num < 1 {
              break
          }
      }
      

      In some cases, you may want only to allow a function to read from or write to a channel, but not both. To do this, you would add the <- operator onto the chan type declaration. Similar to reading and writing from a channel, the channel type uses the <- arrow to allow variables to constrain a channel to only reading, only writing, or both reading and writing. For example, to define a read-only channel of int values, the type declaration would be <-chan int:

      func readChannel(ch <-chan int) {
          // ch is read-only
      }
      

      If you wanted the channel to be write-only, you would declare it as chan<- int:

      func writeChannel(ch chan<- int) {
          // ch is write-only
      }
      

      Notice that the arrow is pointing out of the channel for reading, and pointing into the channel for writing. If the declaration doesn’t have an arrow, as in the case of chan int, the channel can be used for both reading and writing.

      Finally, once a channel is no longer being used it can be closed using the built-in close() function. This step is essential because when channels are created and then left unused many times in a program, it can lead to what’s known as a memory leak. A memory leak is when a program creates something that uses up memory on a computer, but doesn’t release that memory back to the computer once it’s done using it. This leads to the program slowly (or sometimes not so slowly) using up more memory over time, like a water leak. When a channel is created with make(), some of the computer’s memory is used up for the channel, then when close() is called on the channel, that memory is given back to the computer to be used for something else.

      Now, update the main.go file in your program to use a chan int channel to communicate between your goroutines. The generateNumbers function will generate numbers and write them to the channel while the printNumbers function will read those numbers from the channel and print them to the screen. In the main function, you’ll create a new channel to pass as a parameter to each of the other functions, then use close() on the channel to close it because it will no longer be used. The generateNumbers function should also not be a goroutine any more because once that function is done running, the program will have finished generating all the numbers it needs to. This way, the close() function is only called on the channel before both functions have finished running.

      projects/multifunc/main.go

      package main
      
      import (
          "fmt"
          "sync"
      )
      
      func generateNumbers(total int, ch chan<- int, wg *sync.WaitGroup) {
          defer wg.Done()
      
          for idx := 1; idx <= total; idx++ {
              fmt.Printf("sending %d to channeln", idx)
              ch <- idx
          }
      }
      
      func printNumbers(ch <-chan int, wg *sync.WaitGroup) {
          defer wg.Done()
      
          for num := range ch {
              fmt.Printf("read %d from channeln", num)
          }
      }
      
      func main() {
          var wg sync.WaitGroup
          numberChan := make(chan int)
      
          wg.Add(2)
          go printNumbers(numberChan, &wg)
      
          generateNumbers(3, numberChan, &wg)
      
          close(numberChan)
      
          fmt.Println("Waiting for goroutines to finish...")
          wg.Wait()
          fmt.Println("Done!")
      }
      

      In the parameters for generateNumbers and printNumbers, you’ll see that the chan types are using the read-only and write-only types. Since generateNumbers only needs to be able to write numbers to the channel, it’s a write-only type with the <- arrow pointing into the channel. printNumbers only needs to be able to read numbers from the channel, so it’s a read-only type with the <- arrow pointing away from the channel.

      Even though these types could be a chan int, which would allow both reading and writing, it can be helpful to constrain them to only what the function needs to avoid accidentally causing your program to stop running from something known as a deadlock. A deadlock can happen when one part of a program is waiting for another part of the program to do something, but that other part of the program is also waiting for the first part of the program to finish. Since both parts of the program are waiting on each other, the program will never continue running, almost like when two gears seize.

      The deadlock can happen due to the way channel communication works in Go. When part of a program is writing to a channel, it will wait until another part of the program reads from that channel before continuing on. Similarly, if a program is reading from a channel it will wait until another part of the program writes to that channel before it continues. A part of a program waiting on something else to happen is said to be blocking because it’s blocked from continuing until something else happens. Channels block when they are being written to or read from. So if you have a function where you’re expecting to write to a channel but accidentally read from the channel instead, your program may enter a deadlock because the channel will never be written to. Ensuring this never happens is one reason to use a chan<- int or a <-chan int instead of just a chan int.

      One other important aspect of the updated code is using close() to close the channel once it’s done being written to by generateNumbers. In this program, close() causes the for ... range loop in printNumbers to exit. Since using range to read from a channel continues until the channel it’s reading from is closed, if close isn’t called on numberChan then printNumbers will never finish. If printNumbers never finishes, the WaitGroup’s Done method will never be called by the defer when printNumbers exits. If the Done method is never called from printNumbers, the program itself will never exit because the WaitGroup’s Wait method in the main function will never continue. This is another example of a deadlock because the main function is waiting on something that will never happen.

      Now, run your updated code using the go run command on main.go again.

      Your output may vary slightly from what’s shown below, but overall it should be similar:

      Output

      sending 1 to channel sending 2 to channel read 1 from channel read 2 from channel sending 3 to channel Waiting for functions to finish... read 3 from channel Done!

      The output from the program shows that the generateNumbers function is generating the numbers one through three while writing them to the channel shared with printNumbers. Once printNumbers receives the number, it then prints it to the screen. After generateNumbers has generated all three numbers it will exit, allowing the main function to close the channel and wait until printNumbers is finished. Once printNumbers finishes printing out the last number, it calls Done on the WaitGroup and the program exits. Similar to previous outputs, the exact output you see will depend on various outside factors, such as when the operating system or Go runtime choose to run specific goroutines, but it should be relatively close.

      The benefit of designing your programs using goroutines and channels is that once you’ve designed your program to be split up, you can scale it up to more goroutines. Since generateNumbers is just writing to a channel, it doesn’t matter how many other things are reading from that channel. It will just send numbers to anything that reads the channel. You can take advantage of this by running more than one printNumbers goroutine, so each of them will read from the same channel and handle the data concurrently.

      Now that your program is using channels to communicate, open the main.go file again and update your program so it starts multiple printNumbers goroutines. You will need to tweak the call to wg.Add so it adds one for every goroutine you start. You don’t need to worry about adding one to the WaitGroup for the call to generateNumbers any more because the program won’t continue without finishing the whole function, unlike when you were running it as a goroutine. To ensure it doesn’t reduce the WaitGroup count when it finishes, you should remove the defer wg.Done() line from the function. Next, adding the number of the goroutine to printNumbers makes it easier to see how the channel is read by each of them. Increasing the amount of numbers being generated is also a good idea so that it’s easier to see the numbers being spread out:

      projects/multifunc/main.go

      ...
      
      func generateNumbers(total int, ch chan<- int, wg *sync.WaitGroup) {
          for idx := 1; idx <= total; idx++ {
              fmt.Printf("sending %d to channeln", idx)
              ch <- idx
          }
      }
      
      func printNumbers(idx int, ch <-chan int, wg *sync.WaitGroup) {
          defer wg.Done()
      
          for num := range ch {
              fmt.Printf("%d: read %d from channeln", idx, num)
          }
      }
      
      func main() {
          var wg sync.WaitGroup
          numberChan := make(chan int)
      
          for idx := 1; idx <= 3; idx++ {
              wg.Add(1)
              go printNumbers(idx, numberChan, &wg)
          }
      
          generateNumbers(5, numberChan, &wg)
      
          close(numberChan)
      
          fmt.Println("Waiting for goroutines to finish...")
          wg.Wait()
          fmt.Println("Done!")
      }
      

      Once your main.go is updated, you can run your program again using go run with main.go. Your program should start three printNumbers goroutines before continuing on to generating numbers. Your program should also now generate five numbers instead of three to make it easier to see the numbers spread out among each of the three printNumbers goroutines:

      The ouput may look similar to this (although your output might vary quite a bit):

      Output

      sending 1 to channel sending 2 to channel sending 3 to channel 3: read 2 from channel 1: read 1 from channel sending 4 to channel sending 5 to channel 3: read 4 from channel 1: read 5 from channel Waiting for goroutines to finish... 2: read 3 from channel Done!

      When you look at your program output this time, there’s a good chance it will vary greatly from the output you see above. Since there are three printNumbers goroutines running, there’s an element of chance determining which one receives a specific number. When one printNumbers goroutine receives a number, it spends a small amount of time printing that number to the screen, while another goroutine reads the next number from the channel and does the same thing. When a goroutine has finished its work of printing the number and is ready to read another number, it will go back and read the channel again to print the next one. If there are no more numbers to be read from the channel, it will start to block until the next number can be read. Once generateNumbers has finished and close() is called on the channel, all three of the printNumbers goroutines will finish their range loops and exit. When all three goroutines have exited and called Done on the WaitGroup, the WaitGroup’s count will reach zero and the program will exit. You can also experiment with increasing or decreasing the amount of goroutines or numbers being generated to see how that affects the output.

      When using goroutines, avoid starting too many. In theory, a program could have hundreds or even thousands of goroutines. However, depending on the computer the program is running on, it could actually be slower to have a higher number of goroutines. With a high number of goroutines, there’s a chance it could run into resource starvation. Every time Go runs part of a goroutine, it requires a little bit of extra time to start running again, in addition to the time needed to run the code in the next function. Due to the extra time it takes, it’s possible for the computer to take longer to switch between running each goroutine than to actually run the goroutine itself. When this happens, it’s called resource starvation because the program and its goroutines aren’t getting the resources they need to run, or are getting very few. In these cases, it may be faster to lower the number of parts in the program running concurrently because it will lower the time it takes to switch between them, and give more time to running the program itself. Remembering how many cores the program is running on can be a good starting point for deciding how many goroutines you want to use.

      Using a combination of goroutines and channels makes it possible to create very powerful programs capable of scaling from running on small desktop computers up to massive servers. As you saw in this section, channels can be used to communicate between as few as a couple of goroutines to potentially thousands of goroutines with minimal changes. If you take this into consideration when writing your programs, you’ll be able to take advantage of the concurrency available in Go to provide your users a better overall experience.

      Conclusion

      In this tutorial, you created a program using the go keyword to start concurrently-running goroutines that printed out numbers as they run. Once that program was running, you created a new channel of int values using make(chan int), then used the channel to generate numbers in one goroutine and send them to another goroutine to be printed to the screen. Finally, you started multiple “printing” goroutines at the same time as an example of how channels and goroutines can be used to speed up your programs on multi-core computers.

      If you’re interested in learning more about concurrency in Go, the Effective Go document created by the Go team goes into much more detail. The Concurrency is not parallelism Go blog post is also an interesting follow-up about the relationship between concurrency and parallelism, two terms that are sometimes mistakenly conflated to mean the same thing.

      This tutorial is also part of the DigitalOcean How to Code in Go series. The series covers a number of Go topics, from installing Go for the first time to how to use the language itself.



      Source link

      How To Use Functions in TypeScript


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Creating and using functions is a fundamental aspect of any programming language, and TypeScript is no different. TypeScript fully supports the existing JavaScript syntax for functions, while also adding type information and function overloading as new features. Besides providing extra documentation to the function, type information also reduces the chances of bugs in the code because there’s a lower risk of passing invalid data types to a type-safe function.

      In this tutorial, you will start by creating the most basic functions with type information, then move on to more complex scenarios, like using rest parameters and function overloading. You will try out different code samples, which you can follow in your own TypeScript environment or the TypeScript Playground, an online environment that allows you to write TypeScript directly in the browser.

      Prerequisites

      To follow this tutorial, you will need:

      • An environment in which you can execute TypeScript programs to follow along with the examples. To set this up on your local machine, you will need the following:
      • If you do not wish to create a TypeScript environment on your local machine, you can use the official TypeScript Playground to follow along.
      • You will need sufficient knowledge of JavaScript, especially ES6+ syntax, such as destructuring, rest operators, and imports/exports. If you need more information on these topics, reading our How To Code in JavaScript series is recommended.
      • This tutorial will reference aspects of text editors that support TypeScript and show in-line errors. This is not necessary to use TypeScript, but does take more advantage of TypeScript features. To gain the benefit of these, you can use a text editor like Visual Studio Code, which has full support for TypeScript out of the box. You can also try out these benefits in the TypeScript Playground.

      All examples shown in this tutorial were created using TypeScript version 4.2.2.

      Creating Typed Functions

      In this section, you will create functions in TypeScript, and then add type information to them.

      In JavaScript, functions can be declared in a number of ways. One of the most popular is to use the function keyword, as is shown in the following:

      function sum(a, b) {
        return a + b;
      }
      

      In this example, sum is the name of the function, (a, b) are the arguments, and {return a + b;} is the function body.

      The syntax for creating functions in TypeScript is the same, except for one major addition: You can let the compiler know what types each argument or parameter should have. The following code block shows the general syntax for this, with the type declarations highlighted:

      function functionName(param1: Param1Type, param2: Param2Type): ReturnType {
        // ... body of the function
      }
      

      Using this syntax, you can then add types to the parameters of the sum function shown earlier:

      function sum(a: number, b: number) {
        return a + b;
      }
      

      This ensures that a and b are number values.

      You can also add the type of the returned value:

      function sum(a: number, b: number): number {
        return a + b;
      }
      

      Now TypeScript will expect the sum function to return a number value. If you call your function with some parameters and store the result value in a variable called result:

      const result = sum(1, 2);
      

      The result variable is going to have the type number. If you are using the TypeScript playground or are using a text editor that fully supports TypeScript, hovering over result with your cursor will show const result: number, showing that TypeScript has implied its type from the function declaration.

      If you called your function with a value that has a type other than the one expected by your function, the TypeScript Compiler (tsc) would give you the error 2345. Take the following call to the sum function:

      sum('shark', 'whale');
      

      This would give the following:

      Output

      Argument of type 'string' is not assignable to parameter of type 'number'. (2345)

      You can use any type in your functions, not just basic types. For example, imagine you have a User type that looks like this:

      type User = {
        firstName: string;
        lastName: string;
      };
      

      You could create a function that returns the full name of the user like the following:

      function getUserFullName(user: User): string {
        return `${user.firstName} ${user.lastName}`;
      }
      

      Most of the times TypeScript is smart enough to infer the return type of functions, so you can drop the return type from the function declaration in this case:

      function getUserFullName(user: User) {
        return `${user.firstName} ${user.lastName}`;
      }
      

      Notice you removed the : string part, which was the return type of your function. As you are returning a string in the body of your function, TypeScript correctly assumes your function has a string return type.

      To call your function now, you must pass an object that has the same shape as the User type:

      type User = {
        firstName: string;
        lastName: string;
      };
      
      function getUserFullName(user: User) {
        return `${user.firstName} ${user.lastName}`;
      }
      
      const user: User = {
        firstName: "Jon",
        lastName: "Doe"
      };
      
      const userFullName = getUserFullName(user);
      

      This code will successfully pass the TypeScript type-checker. If you hover over the userFullName constant in your editor, the editor will identify its type as string.

      Optional Function Parameters in TypeScript

      Having all parameters is not always required when creating functions. In this section, you will learn how to mark function parameters as optional in TypeScript.

      To turn a function parameter into an optional one, add the ? modifier right after the parameter name. Given a function parameter param1 with type T, you could make param1 an optional parameter by adding ?, as highlighted in the following:

      param1?: T
      

      For example, add an optional prefix parameter to your getUserFullName function, which is an optional string that can be added as a prefix to the user’s full name:

      type User = {
        firstName: string;
        lastName: string;
      };
      
      function getUserFullName(user: User, prefix?: string) {
        return `${prefix ?? ''}${user.firstName} ${user.lastName}`;
      }
      

      In the first highlighted part of this code block, you are adding an optional prefix parameter to your function, and in the second highlighted part you are prefixing the user’s full name with it. To do that, you are using the nullish coalescing operator ??. This way, you are only going to use the prefix value if it is defined; otherwise, the function will use an empty string.

      Now you can call your function with or without the prefix parameter, as shown in the following:

      type User = {
        firstName: string;
        lastName: string;
      };
      
      function getUserFullName(user: User, prefix?: string) {
        return `${prefix ?? ''} ${user.firstName} ${user.lastName}`;
      }
      
      const user: User = {
        firstName: "Jon",
        lastName: "Doe"
      };
      
      const userFullName = getUserFullName(user);
      const mrUserFullName = getUserFullName(user, 'Mr. ');
      

      In this case, the value of userFullName will be Jon Doe, and the value of mrUserFullName will be Mr. Jon Doe.

      Note that you cannot add an optional parameter before a required one; it must be listed last in the series, as is done with (user: User, prefix?: string). Listing it first would make the TypeScript Compiler return the error 1016:

      Output

      A required parameter cannot follow an optional parameter. (1016)

      Typed Arrow Function Expressions

      So far, this tutorial has shown how to type normal functions in TypeScript, defined with the function keyword. But in JavaScript, you can define a function in more than one way, such as with arrow functions. In this section, you will add types to arrow functions in TypeScript.

      The syntax for adding types to arrow functions is almost the same as adding types to normal functions. To illustrate this, change your getUserFullName function into an arrow function expression:

      const getUserFullName = (user: User, prefix?: string) => `${prefix ?? ''}${user.firstName} ${user.lastName}`;
      

      If you wanted to be explicit about the return type of your function, you would add it after the (), as shown in the highlighted code in the following block:

      const getUserFullName = (user: User, prefix?: string): string => `${prefix ?? ''}${user.firstName} ${user.lastName}`;
      

      Now you can use your function exactly like before:

      type User = {
        firstName: string;
        lastName: string;
      };
      
      const getUserFullName = (user: User, prefix?: string) => `${prefix ?? ''}${user.firstName} ${user.lastName}`;
      
      const user: User = {
        firstName: "Jon",
        lastName: "Doe"
      };
      
      const userFullName = getUserFullName(user);
      

      This will pass the TypeScript type-checker with no error.

      Note: Remember that everything valid for functions in JavaScript is also valid for functions in TypeScript. For a refresher on these rules, check out our How To Define Functions in JavaScript tutorial.

      Function Types

      In the previous sections, you added types to the parameters and return values for functions in TypeScript. In this section, you are going to learn how to create function types, which are types that represent a specific function signature. Creating a type that matches a specific function is especially useful when passing functions to other functions, like having a parameter that is itself a function. This is a common pattern when creating functions that accept callbacks.

      The syntax for creating your function type is similar to creating an arrow function, with two differences:

      • You remove the function body.
      • You make the function declaration return the return type itself.

      Here is how you would create a type that matches the getUserFullName function you have been using:

      type User = {
        firstName: string;
        lastName: string;
      };
      
      type PrintUserNameFunction = (user: User, prefix?: string) => string;
      

      In this example, you used the type keyword to declare a new type, then provided the type for the two parameters in the parentheses and the type for the return value after the arrow.

      For a more concrete example, imagine you are creating an event listener function called onEvent, which receives as the first parameter the event name, and as the second parameter the event callback. The event callback itself would receive as the first parameter an object with the following type:

      type EventContext = {
        value: string;
      };
      

      You can then write your onEvent function like this:

      type EventContext = {
        value: string;
      };
      
      function onEvent(eventName: string, eventCallback: (target: EventContext) => void) {
        // ... implementation
      }
      

      Notice that the type of the eventCallback parameter is a function type:

      eventCallback: (target: EventTarget) => void
      

      This means that your onEvent function expects another function to be passed in the eventCallback parameter. This function should accept a single argument of the type EventTarget. The return type of this function is ignored by your onEvent function, and so you are using void as the type.

      Using Typed Asynchronous Functions

      When working with JavaScript, it is relatively common to have asynchronous functions. TypeScript has a specific way to deal with this. In this section, you are going to create asynchronous functions in TypeScript.

      The syntax for creating asynchronous functions is the same as the one used for JavaScript, with the addition of allowing types:

      async function asyncFunction(param1: number) {
        // ... function implementation ...
      }
      

      There is one major difference between adding types to a normal function and adding types to an asynchronous function: In an asynchronous function, the return type must always be the Promise<T> generic. The Promise<T> generic represents the Promise object that is returned by an asynchronous function, where T is the type of the value the promise resolves to.

      Imagine you have a User type:

      type User = {
        id: number;
        firstName: string;
      };
      

      Imagine also that you have a few user objects in a data store. This data could be stored anywhere, like in a file, a database, or behind an API request. For simplicity, in this example you will be using an array:

      type User = {
        id: number;
        firstName: string;
      };
      
      const users: User[] = [
        { id: 1, firstName: "Jane" },
        { id: 2, firstName: "Jon" }
      ];
      

      If you wanted to create a type-safe function that retrieves a user by ID in an asynchronous way, you could do it like this:

      async function getUserById(userId: number): Promise<User | null> {
        const foundUser = users.find(user => user.id === userId);
      
        if (!foundUser) {
          return null;
        }
      
        return foundUser;
      }
      

      In this function, you are first declaring your function as asynchronous:

      async function getUserById(userId: number): Promise<User | null> {
      

      Then you are specifying that it accepts as the first parameter the user ID, which must be a number:

      async function getUserById(userId: number): Promise<User | null> {
      

      The return type of getUserById is a Promise that resolves to either User or null. You are using the union type User | null as the type parameter to the Promise generic.

      User | null is the T in Promise<T>:

      async function getUserById(userId: number): Promise<User | null> {
      

      Call your function using await and store the result in a variable called user:

      type User = {
        id: number;
        firstName: string;
      };
      
      const users: User[] = [
        { id: 1, firstName: "Jane" },
        { id: 2, firstName: "Jon" }
      ];
      
      async function getUserById(userId: number): Promise<User | null> {
        const foundUser = users.find(user => user.id === userId);
      
        if (!foundUser) {
          return null;
        }
      
        return foundUser;
      }
      
      async function runProgram() {
        const user = await getUserById(1);
      }
      

      Note: You are using a wrapper function called runProgram because you cannot use await in the top level of a file. Doing so would cause the TypeScript Compiler to emit the error 1375:

      Output

      'await' expressions are only allowed at the top level of a file when that file is a module, but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module. (1375)

      If you hover over user in your editor or in the TypeScript Playground, you’ll find that user has the type User | null, which is exactly the type the promise returned by your getUserById function resolves to.

      If you remove the await and just call the function directly, the Promise object is returned:

      async function runProgram() {
        const userPromise = getUserById(1);
      }
      

      If you hover over userPromise, you’ll find that it has the type Promise<User | null>.

      Most of the time, TypeScript can infer the return type of your async function, just like it does with non-async functions. You can therefore omit the return type of the getUserById function, as it is still correctly inferred to have the type Promise<User | null>:

      async function getUserById(userId: number) {
        const foundUser = users.find(user => user.id === userId);
      
        if (!foundUser) {
          return null;
        }
      
        return foundUser;
      }
      

      Adding Types to Rest Parameters

      Rest parameters are a feature in JavaScript that allows a function to receive many parameters as a single array. In this section, you will use rest parameters with TypeScript.

      Using rest parameters in a type-safe way is completely possible by using the rest parameter followed by the type of the resulting array. Take for example the following code, where you have a function called sum that accepts a variable amount of numbers and returns their total sum:

      function sum(...args: number[]) {
        return args.reduce((accumulator, currentValue) => {
          return accumulator + currentValue;
        }, 0);
      }
      

      This function uses the .reduce Array method to iterate over the array and add the elements together. Notice the rest parameter args highlighted here. The type is being set to an array of numbers: number[].

      Calling your function works normally:

      function sum(...args: number[]) {
        return args.reduce((accumulator, currentValue) => {
          return accumulator + currentValue;
        }, 0);
      }
      
      const sumResult = sum(2, 4, 6, 8);
      

      If you call your function using anything other than a number, like:

      const sumResult = sum(2, "b", 6, 8);
      

      The TypeScript Compiler will emit the error 2345:

      Output

      Argument of type 'string' is not assignable to parameter of type 'number'. (2345)

      Using Function Overloads

      Programmers sometime need a function to accept different parameters depending on how the function is called. In JavaScript, this is normally done by having a parameter that may assume different types of values, like a string or a number. Setting multiple implementations to the same function name is called function overloading.

      With TypeScript, you can create function overloads that explicitly describe the different cases that they address, improving the developer experience by document each implementation of the overloaded function separately. This section will go through how to use function overloading in TypeScript.

      Imagine you have a User type:

      type User = {
        id: number;
        email: string;
        fullName: string;
        age: number;
      };
      

      And you want to create a function that can look up a user using any of the following information:

      • id
      • email
      • age and fullName

      You could create such a function like this:

      function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
        // ... code
      }
      

      This function uses the | operator to compose a union of types for idOrEmailOrAge and for the return value.

      Next, add function overloads for each way you want your function to be used, as shown in the following highlighted code:

      type User = {
        id: number;
        email: string;
        fullName: string;
        age: number;
      };
      
      function getUser(id: number): User | undefined;
      function getUser(email: string): User | undefined;
      function getUser(age: number, fullName: string): User | undefined;
      
      function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
        // ... code
      }
      

      This function has three overloads, one for each way to retrieve a user. When creating function overloads, you add the function overloads before the function implementation itself. The function overloads do not have a body; they just have the list of parameters and the return type.

      Next, you implement the function itself, which should have a parameter list that is compatible with all function overloads. In the previous example, your first parameter can be either a number or a string, since it can be the id, the email, or the age:

      function getUser(id: number): User | undefined;
      function getUser(email: string): User | undefined;
      function getUser(age: number, fullName: string): User | undefined;
      
      function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
        // ... code
      }
      

      You therefore set the type of the idOrEmailorAge parameter in your function implementation to be number | string. This way, it is compatible with all the overloads of your getUser function.

      You are also adding an optional parameter to your function, for when the user is passing a fullName:

      function getUser(id: number): User | undefined;
      function getUser(email: string): User | undefined;
      function getUser(age: number, fullName: string): User | undefined;
      
      function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
        // ... code
      }
      

      Implementing your function could be like the following, where you are using a users array as the data store of your users:

      type User = {
        id: number;
        email: string;
        fullName: string;
        age: number;
      };
      
      const users: User[] = [
        { id: 1, email: "jane_doe@example.com", fullName: "Jane Doe" , age: 35 },
        { id: 2, email: "jon_do@example.com", fullName: "Jon Doe", age: 35 }
      ];
      
      function getUser(id: number): User | undefined;
      function getUser(email: string): User | undefined;
      function getUser(age: number, fullName: string): User | undefined;
      
      function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
        if (typeof idOrEmailOrAge === "string") {
          return users.find(user => user.email === idOrEmailOrAge);
        }
      
        if (typeof fullName === "string") {
          return users.find(user => user.age === idOrEmailOrAge && user.fullName === fullName);
        } else {
          return users.find(user => user.id === idOrEmailOrAge);
        }
      }
      
      const userById = getUser(1);
      const userByEmail = getUser("jane_doe@example.com");
      const userByAgeAndFullName = getUser(35, "Jon Doe");
      

      In this code, if idOrEmailOrAge is a string, then you can search for the user with the email key. The following conditional assumes idOrEmailOrAge is a number, so it is either the id or the age, depending on if fullName is defined.

      One interesting aspect of function overloads is that in most editors, including VS Code and the TypeScript Playground, as soon as you type the function name and open the first parenthesis to call the function, a pop-up will appear with all the overloads available, as shown in the following image:

      Overloads Popup

      If you add a comment to each function overload, the comment will also be in the pop-up as a source of documentation. For example, add the following highlighted comments to the example overloads:

      ...
      /**
       * Get a user by their ID.
       */
      function getUser(id: number): User | undefined;
      /**
       * Get a user by their email.
       */
      function getUser(email: string): User | undefined;
      /**
       * Get a user by their age and full name.
       */
      function getUser(age: number, fullName: string): User | undefined;
      ...
      

      Now when you hover over these functions, the comment will show up for each overload, as shown in the following animation:

      Overloads Popup with Comments

      User-Defined Type Guards

      The last feature of functions in TypeScript that this tutorial will examine is user-defined type guards, which are special functions that allow TypeScript to better infer the type of some value. These guards enforce certain types in conditional code blocks, where the type of a value may be different depending on the situation. These are especially useful when using the Array.prototype.filter function to return a filtered array of data.

      One common task when adding values conditionally to an array is to check for some conditions and then only add the value if the condition is true. If the value is not true, the code adds a false Boolean to the array. Before using that array, you can filter it using .filter(Boolean) to make sure only truthy values are returned.

      When called with a value, the Boolean constructor returns true or false, depending on if this value is a Truthy or Falsy value.

      For example, imagine you have an array of strings, and you only want to include the string production to that array if some other flag is true:

      const isProduction = false
      
      const valuesArray = ['some-string', isProduction && 'production']
      
      function processArray(array: string[]) {
        // do something with array
      }
      
      processArray(valuesArray.filter(Boolean))
      

      While this is, at runtime, perfectly valid code, the TypeScript Compiler will give you the error 2345 during compilation:

      Output

      Argument of type '(string | boolean)[]' is not assignable to parameter of type 'string[]'. Type 'string | boolean' is not assignable to type 'string'. Type 'boolean' is not assignable to type 'string'. (2345)

      This error is saying that, at compile-time, the value passed to processArray is interpreted as an array of false | string values, which is not what the processArray expected. It expects an array of strings: string[].

      This is one case where TypeScript is not smart enough to infer that by using .filter(Boolean) you are removing all falsy values from your array. However, there is one way to give this hint to TypeScript: using user-defined type guards.

      Create a user-defined type guard function called isString:

      function isString(value: any): value is string {
        return typeof value === "string"
      }
      

      Notice the return type of the isString function. The way to create user-defined type guards is by using the following syntax as the return type of a function:

      parameterName is Type
      

      Where parameterName is the name of the parameter you are testing, and Type is the expected type the value of this parameter has if this function returns true.

      In this case, you are saying that value is a string if isString returns true. You are also setting the type of your value parameter to any, so it works with any type of value.

      Now, change your .filter call to use your new function instead of passing it the Boolean constructor:

      const isProduction = false
      
      const valuesArray = ['some-string', isProduction && 'production']
      
      function processArray(array: string[]) {
        // do something with array
      }
      
      function isString(value: any): value is string {
        return typeof value === "string"
      }
      
      processArray(valuesArray.filter(isString))
      

      Now the TypeScript compiler correctly infers that the array passed to processArray only contains strings, and your code compiles correctly.

      Conclusion

      Functions are the building block of applications in TypeScript, and in this tutorial you learned how to build type-safe functions in TypeScript and how to take advantage of function overloads to better document all variants of a single function. Having this knowledge will allow for more type-safe and easy-to-maintain functions throughout your code.

      For more tutorials on TypeScript, check out our TypeScript Topic page.



      Source link

      How To Use the all, any, max, and min Functions in Python


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Python includes a number of built-in functions—these are global Python functions that can be called from any Python code without importing additional modules. For example, you can always call the print built-in function to output text.

      Several of the built-in functions (all, any, max, and min among them) take iterables of values as their argument and return a single value. An iterable is a Python object that can be “iterated over”, that is, it will return items in a sequence such that you can use it in a for loop. Built-in functions are useful when, for example, you want to determine if all or any of the elements in a list meet a certain criteria, or find the largest or smallest element in a list.

      In this tutorial, you will use the built-in functions all, any, max, and min.

      Prerequisites

      To get the most out of this tutorial, it is recommended to have:

      Using all

      The built-in function all checks if every element in an iterable is True. For example:

      all([True, True])
      

      If you run the previous code, you will receive this output:

      Output

      True

      In this first example, you call all and give it a list with two elements (two True Boolean values). Since every element in the iterable is True, the output was True.

      all will return False if one or more elements in the given iterable is False:

      all([True, False, True])
      

      If you run the previous code, you’ll receive this output:

      Output

      False

      You call all with a list containing three elements including one False Boolean value. Since one of the elements in the iterable was False, the output of the call to all was False.

      Notably, all stops iterating and immediately returns False as soon as it encounters the first False entry in an iterable. So, all can be useful if you want to check successive conditions that may build on each other, but return immediately as soon as one condition is False.

      A special case to be aware of is when all is given an empty iterable:

      all([])
      

      If you run the previous code, you’ll receive this output:

      Output

      True

      When you give all an empty iterable (for example, an empty list like all([])), its return value is True. So, all returns True if every element in the iterable is True or there are 0 elements.

      all is particularly helpful when combined with generators and custom conditions. Using all is often shorter and more concise than if you were to write a full-fledged for loop. Let’s consider an example to find out whether there are elements in a list that start with "s":

      animals = ["shark", "seal", "sea urchin"]
      all(a.startswith("s") for a in animals)
      

      If you run the previous code, you will receive this output:

      Output

      True

      You call all with a generator as its argument. The generator produces a Boolean for each element in the animals list based on whether or not the animal starts with the letter s. The final return value is True because every element in the animals list starts with s.

      Note: You can often use Generator expressions in place of list comprehensions as a way of saving memory. For example, consider all(i < 8 for i in range(3)) and all([i < 8 for i in range(3)]). Both statements return True because 0, 1, 2 are all less than 8. The second example (which uses a list comprehension), however, has the added overhead of implicitly creating a list three entries long ([True, True, True]) and then passing that list to the all function. The first example (which uses generator expressions), by contrast, passes a generator object to the all function, which the all function iterates over directly without the overhead of an intermediary list.

      Consider that the equivalent code written using a full-fledged for loop would have been significantly more verbose:

      animals = ["shark", "seal", "sea urchin"]
      
      def all_animals_start_with_s(animals):
          for a in animals:
              if not a.startswith("s"):
                  return False
          return True
      
      print(all_animals_start_with_s(animals))
      

      Without all, your code for determining if all the animals start with the letter s requires several more lines to implement.

      Next, you’ll look at the sibling function to all: any.

      Using any

      You can use the built-in function any to check if any element in an iterable is True. For example:

      any([False, True])
      

      If you run the previous code, you’ll receive this output:

      Output

      True

      You call any and give it a list with two elements (a False Boolean value and a True Boolean value). Since one or more element in the iterable was True, the output was also True.

      any will return False if, and only if, 0 of the elements in the given iterable are True:

      all([False, False, False])
      

      If you run the previous code, you’ll receive this output:

      Output

      False

      You call any with a list containing three elements (all False Boolean values). Since 0 of the elements in the iterable is True, the output of the call to any is False.

      Notably, any stops iterating and immediately returns True as soon as it encounters the first True entry in an iterable. So, any can be useful if you want to check successive conditions, but return immediately as soon as one condition is True.

      any—like its sibling method all—is particularly helpful when combined with generators and custom conditions (in place of a full for loop). Let’s consider an example to find out whether there are elements in a list that end with "urchin":

      animals = ["shark", "seal", "sea urchin"]
      any(s.endswith("urchin") for s in animals)
      

      You will receive this output:

      Output

      True

      You call any with a generator as its argument. The generator produces a Boolean for each element in the animals list based on whether or not the animal ends with urchin. The final return value is True because one element in the animals list ends with urchin.

      Note: When any is given an empty iterable (for example, an empty list like any([])), its return value is False. So, any returns False if there are 0 elements in the iterable or all the elements in the iterable are also False.

      Next, you’ll review another built-in function: max.

      Using max

      The built-in function max returns the largest element given in its arguments. For example:

      max([0, 8, 3, 1])
      

      max is given a list with four integers as its argument. The return value of max is the largest element in that list: 8.

      The output will be the following:

      Output

      8

      If given two or more positional arguments (as opposed to a single positional argument with an iterable), max returns the largest of the given arguments:

      max(1, -1, 3)
      

      If you run the previous code, you will receive this output:

      Output

      3

      max is given three individual arguments, the largest of which being 3. So, the return value of the call to max is 3.

      Just like any and all, max is particularly helpful because it requires fewer lines to use than equivalent code written as a full for loop.

      max can also deal with objects more complex than numbers. For example, you can use max with dictionaries or other custom objects in your program. max can accommodate these objects by using its keyword argument named key.

      You can use the key keyword argument to define a custom function that determines the value used in the comparisons to determine the maximum value. For example:

      entries = [{"id": 9}, {"id": 17}, {"id": 4}]
      max(entries, key=lambda x: x["id"])
      

      The output will be the following:

      Output

      {'id': 17}

      You call max with a list of dictionaries as its input. You give an anonymous lambda function as the key keyword argument. max calls the lambda function for every element in the entries list and returns the value of the "id" key of the given element. The final return value is the second element in entries: {"id": 17}. The second element in entries had the largest "id" value, and so was deemed to be the maximum element.

      Note that when max is called with an empty iterable, it refuses to operate and instead raises a ValueError:

      max([])
      

      If you run this code, you will receive the following output:

      Output

      Traceback (most recent call last): File "max.py", line 1, in <module> max([]) ValueError: max() arg is an empty sequence

      You call max with an empty list as its argument. max does not accept this as a valid input, and raises a ValueError Exception instead.

      max has a counterpart called min, which you’ll review next.

      Using min

      The built-in function min returns the smallest element given in its arguments. For example:

      min([8, 0, 3, 1])
      

      You give min a list with four integers as its argument. The return value of min is the smallest element in that list: 0.

      The output will be:

      Output

      0

      If given two or more positional arguments (as opposed to a single positional argument with an iterable), min returns the smallest of the given arguments:

      min(1, -1, 3)
      

      If you run the previous code, you will receive this output:

      Output

      -1

      You give min three individual arguments, the smallest of which being -1. So, the return value of the call to min is -1.

      Like max, min supports the keyword argument named key so that you can pass objects more complex than numbers into it. Using the key argument allows you to use the min function with any custom objects your program might define.

      You can use the key keyword argument to define a custom function that determines the value used in the comparisons to determine the minimum value. For example:

      entries = [{"id": 9}, {"id": 17}, {"id": 4}]
      min(entries, key=lambda x: x["id"])
      

      Output

      {'id': 4}

      You call min with a list of dictionaries as its input. You give an anonymous lambda function as the key keyword argument. min calls the lambda function for every element in the entries list and returns the value of the "id" key of the given element. The final return value is the third element in entries: {"id": 4}. The third element in entries had the smalled "id" value, and so was deemed to be the minimum element.

      Like max, when you call min with an empty iterable, it refuses to operate and instead raises a ValueError:

      min([])
      

      If you run the previous code, you will receive this output:

      Output

      Traceback (most recent call last): File "min.py", line 1, in <module> min([]) ValueError: min() arg is an empty sequence

      You call min with an empty list as its argument. min does not accept this as a valid input, and raises a ValueError Exception instead.

      Conclusion

      In this tutorial, you used the Python built-in functions all, any, max, and min. You can learn more about the functions all, any, max, and min and other Python built-in in the Python docs.

      For more information on other Python built-ins, you can also check out Built-in Python 3 Functions for Working with Numbers, How To Use the Python Map Function, and How To Use the Python Filter Function.



      Source link