Skip to content

Instantly share code, notes, and snippets.

@friedbrice
Last active July 28, 2023 10:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save friedbrice/ef852a5c61e80686d659024aad3cbd70 to your computer and use it in GitHub Desktop.
Save friedbrice/ef852a5c61e80686d659024aad3cbd70 to your computer and use it in GitHub Desktop.
Haskell Command-line Arguments (scroll down for prose).
import System.Environment (getArgs)
myApp :: [String] -> IO ()
myApp args =
case args of
[name] -> putStrLn ("Top of the morning to you, " ++ name)
_ -> putStrLn "I only know how to use one argument D:"
main :: IO ()
main = do
args <- getArgs
myApp args
class BeginnerArgs {
public static void main(String[] args) {
if (args.length == 1) {
System.out.println("Top of the morning to you, " + args[0]);
}
else {
System.out.println("I only know how to use one argument D:");
}
}
}
import sys
def myApp():
# in Python, sys.argv[0] is the name of the program
if len(sys.argv) == 2:
print("Top of the morning to you, " + sys.argv[1])
else:
print("I only know how to use one argument D:")
if __name__ == "__main__": myApp()

Haskell Command-line Arguments

TL/DR

import System.Environment (getArgs)

myApp :: [String] -> IO ()
myApp args = ...

main :: IO ()
main = do
    args <- getArgs
    myApp args

Hot Links

  1. Command Line Arguments in Haskell, on Dev Dungeon.

  2. How to use command line arguments in Haskell, by Alvin Alexander.

Did Read

Haskell's approach to command-line arguments is somewhat unique and deserves a short tutorial.

We'll make a little demo program, but let's start by defining a dummy spec, just so we're on the same page about what the program is supposed to do.

  1. When the program is invoked with one command-line argument, <arg>, it prints Top of the morning to you, <arg>.

  2. When the program is invoked with any other number of command-line arguments, it prints I only know how to use one argument D:.

Let's compare some other points in the programming language design space to try to find something you might already be familiar with. Java and Python will work for our purposes. Here's Java.

class BeginnerArgs {

  public static void main(String[] args) {
    if (args.length == 1) {
      System.out.println("Top of the morning to you, " + args[0]);
    }
    else {
      System.out.println("I only know how to use one argument D:");
    }
  }
}

This program will satisfy our spec:

$ javac BeginnerArgs.java
$ java BeginnerArgs Daniel
Top of the morning to you, Daniel
$ java BeginnerArgs foo bar
I only know how to use one argument D:
$ java BeginnerArgs
I only know how to use one argument D:

A few crucial points:

  1. main is a special name in Java, and we are meant to override it with our program.

  2. main gets passed an array of strings, which the Java runtime populates with the command-line args.

  3. Since main gets passed the arguments, they're in local scope in your main method, but nowhere else in your program, so your main method needs to explicitly pass around the arguments to methods it calls. This is slightly annoying, but it's actually a good thing: the behaviors of our main method and the other methods it passes args to depend on their inputs rather than on some global runtime variable.

Here's a similar program in Python.

import sys

def myApp():
    # in Python, sys.argv[0] is the name of the program
    if len(sys.argv) == 2:
        print("Top of the morning to you, " + sys.argv[1])
    else:
        print("I only know how to use one argument D:")

if __name__ == "__main__": myApp()

This program also meets our spec:

$ python BeginnerArgs.py Daniel
Top of the morning to you, Daniel
$ python BeginnerArgs.py foo bar
I only know how to use one argument D:
$ python BeginnerArgs.py
I only know how to use one argument D:

Points where Python differs from Java:

  1. __name__ is a special string variable in Python, but we are not meant to override it. It's populated by the Python runtime and tells us whether we're running interactively, running as a standalone program, or being loaded by another file.

  2. Since __name__ is just a string, it can't take any arguments (unlike main in Java).

  3. sys.argv is a global variable that the runtime populates once with the command line arguments, making them accessible in all parts of our program. This is convenient, but at the cost that our program now depends on a global runtime variable.

Both the Java approach and the Python approach are fine design choices, and each has its trade-offs. Haskell's approach is a third distinct design choice.

import System.Environment (getArgs)

myApp :: [String] -> IO ()
myApp args =
    case args of
        [name] -> putStrLn ("Top of the morning to you, " ++ name)
        _ -> putStrLn "I only know how to use one argument D:"

main :: IO ()
main = do
    args <- getArgs
    myApp args

Our Haskell program meets our spec:

$ runhaskell BeginnerArgs.hs Daniel
Top of the morning to you, Daniel
$ runhaskell BeginnerArgs.hs foo bar
I only know how to use one argument D:
$ runhaskell BeginnerArgs.hs
I only know how to use one argument D:

In Haskell:

  1. main is a special name, and (like Java) we are meant to override it with our program, though (unlike Java) main is not a function (or method). It is an I/O action that, when run by the Haskell runtime, will produce no usable result.

  2. getArgs is a global (like Python), but it does not represent the program arguments (unlike Python). It is an I/O action that, when run, will produce a list containing the program arguments.

  3. We can represent the act of running an I/O action, and gain locally-scoped access to the I/O action's result, using do notation. It's worth noting that the use of do notation does not run the I/O action, it merely represents running the action. Running I/O actions is best left to the Haskell runtime (by overriding main).

Haskell's approach combines some of the benefits of Python's and Java's. Similar to Python's approach, getArgs acts as a global handle for the program arguments, but since it's an I/O action (rather than an actual list of strings) we also get the strict scoping discipline of Java's approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment