If you haven’t installed stack, visit:
… and install the stack build tool. If you use the brew approach and brew tries to build ghc from source then pick the manual download instead.
Then run these commands:
$ stack setup
...
$ stack ghci turtle
...
Prelude> :set -XOverloadedStrings
Prelude> import Turtle
Prelude Turtle> echo "Hello, world!"
Hello, world!"ghci as a shellI’m hosting slides on Github so that people can follow along locally
Haskell is a purely functional language with strong and static types
Purely functional means side effect order is not tied to evaluation order
Strong types are fine-grained (i.e. FilePath/Time/Name vs String)
Static types catch errors at compile time
Haskell is NOT OBJECT ORIENTED!
Haskell can be both interpreted (~ 1 s startup time) or compiled to a native binary (~ 10 ms startup time)
Haskell is a managed language, providing:
ghc-8.0)Similarities
Differences:
-fdefer-type-errors)Haskell scripts are light-weight and easy to grow
Save this to: example.hs:
#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle
-- #!/bin/bash
{-# LANGUAGE OverloadedStrings #-} --
--
import Turtle --
--
main = echo "Hello, world!" -- echo Hello, world!… then run the example script:
$ chmod u+x example.hs
$ ./example.hs
Hello, world!$ stack ghc -- -O2 example.hs
$ ./example
Hello, world!$ stack ghci
Prelude> :set -XOverloadedStrings
Prelude> import Turtle
Prelude Turtle> echo "Hello, world!"
Hello, world!
Prelude Turtle> 2 + 2
4
Prelude Turtle> let f x = x + x
Prelude Turtle> f 2
4
Prelude Turtle> :quit$ stack ghci
Prelude> :load example.hs
*Main> main
Hello, world!
*Main> :quitWhat do you think this code does?
#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle
{-# LANGUAGE OverloadedStrings #-}
import Turtle
say = echo
main = say "Hello, world!"ghci as a shell#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle
-- #!/bin/bash
{-# LANGUAGE OverloadedStrings #-} --
--
import Turtle --
--
str = "Hello, world!" -- STR='Hello, world!'
--
main = echo str -- echo $STR$ ./example.hs
Hello, world!str is immutable (analogous to Scala’s val)
Why do you think Haskell defaults to immutability?
#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle
{-# LANGUAGE OverloadedStrings #-}
import Turtle
main = echo str
str = "Hello, world!"mainModify your program to to eliminate main:
#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle
{-# LANGUAGE OverloadedStrings #-}
import Turtle
echo "Hello, world!"You will get this error message if you run the program:
example.hs:7:1: Parse error: naked expression at top levelThe top level of a Haskell program is declarative and only allows definitions
You cannot execute code at the top level
The runtime only executes main!
Use do to create a subroutine that runs more than one command:
Using significant whitespace:
#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle
-- #!/bin/bash
{-# LANGUAGE OverloadedStrings #-} --
--
import Turtle --
--
main = do --
echo "Line 1" -- echo Line 1
echo "Line 2" -- echo Line 2$ ./example.hs
Line 1
Line 2main = do
{ echo "Line 1"
; echo "Line 2"
}main = do {
echo "Line 1";
echo "Line 2";
}main = do { echo "Line1"; echo "Line2" }#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle
-- #!/bin/bash
import Turtle --
--
main = do --
dir <- pwd -- DIR=$(pwd)
time <- datefile dir -- TIME=$(date -r $DIR)
print time -- echo $TIME$ ./example.hs
2015-09-01 23:56:03.245 UTCWhy not this?
main = print(datetime(pwd))(=) and (<-)(<-) is overloaded; in this context it means “store the subroutine’s result”(=) is not overloaded; equating two things means they are interchangeableExample of overloading (<-):
Prelude> do { x <- [1, 2]; y <- [3, 4]; return (x, y) }
[(1,3),(1,4),(2,3),(2,4)]do/(<-)/return is analogous to for/(<-)/yield in Scala:
scala> for { x <- Seq(1, 2); y <- Seq(3, 4) } yield (x, y)
res0: Seq[(Int, Int)] = List((1,3), (1,4), (2,3), (2,4))… or LINQ/from/select in C#:
List<int> xs = new List<int> { 1, 2 }
List<int> ys = new List<int> { 3, 4 }
var result =
from x in xs
from y in ys
select Tuple<int, int>(x, y)#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle
-- #!/bin/bash
import Turtle --
--
datePwd = do -- datePwd() {
dir <- pwd -- DIR=$(pwd)
result <- datefile dir -- RESULT=$(date -r $DIR)
return result -- echo $RESULT
-- }
main = do --
time <- datePwd -- TIME=$(datePwd)
print time -- echo $TIMESame result:
$ ./example.hs
2015-09-01 23:56:03.245 UTCreturnYou can simplify this:
datePwd = do -- datePwd() {
dir <- pwd -- DIR=$(pwd)
result <- datefile dir -- RESULT=$(date -r $DIR)
return result -- echo $RESULT
-- }… to this:
datePwd = do -- datePwd() {
dir <- pwd -- DIR=$(pwd)
datefile dir -- date -r $DIR
-- }The return value of a subroutine is the return value of its last command
returnreturn does not break from the surrounding subroutine
return is just a command whose return value is its argument
do x <- return expr -- X=EXPR
command x -- command $X
-- Same as:
do let x = expr -- X=EXPR
command x -- command $X
-- Same as:
command expr -- command EXPRreturn is the only case where (<-) and (=) behave the same way
main = do echo "Hello, world!"
-- Same as:
main = echo "Hello, world!"do is only necessary if you want to chain multiple commands together
What do you think this code does?
main = do
let x = print 1
print 2ghci as a shellWhat happens if we use print instead of echo?
#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle
import Turtle
main = do
dir <- pwd
time <- datefile dir
echo time -- This used to be: print time$ ./example.hs
example.hs:8:10:
Couldn't match expected type `Text' with actual type `UTCTime'
In the first argument of `echo', namely `time'
In a stmt of a 'do' block: echo time
In the expression:
do { dir <- pwd;
time <- datefile dir;
echo time }
main = do
dir <- pwd
time <- datefile dir
echo time -- This line used to be: print time$ stack ghci
Prelude> import TurtlePrelude Turtle> :type pwd
pwd :: IO Turtle.FilePathPrelude Turtle> :type datefile
datefile :: Turtle.FilePath -> IO UTCTimePrelude Turtle> :type echo
echo :: Text -> IO ()Prelude Turtle> :type print
print :: Show a => a -> IO ()Visit:
https://hackage.haskell.org/package/turtle
reprUse repr to render a human-readable representation of a value as Text:
-- This behaves like Python's `repr` function
repr :: Show a => a -> Textprint is (conceptually) the same as echo + repr:
print x = echo (repr x)IntDoubleText(a, b)[a]a -> bIO aFilePathExitCodeUTCTimeWhat are the types of x, y, and z?
(Assume all string literals are Text and all numeric literals are Ints)
x = ("123", 4)
y = [2, 3]
z a = 1 + ax :: (Text, Int)
x = ("123", 4)
y :: [Int]
y = [2, 3]
z :: Int -> Int
z a = 1 + aghci as a shellghciCreate a .ghci file in your current directory that looks like this:
:set -XOverloadedStrings
import Turtle
This automatically runs the above two commands every time you run ghci
ghci searches the current directory and your home directory for a .ghci file
ghci like a shell$ stack ghci
Prelude Turtle> view (ls ".")
FilePath "/Users/ggonzalez/.bash_history"
FilePath "/Users/ggonzalez/.bash_profile"
FilePath "/Users/ggonzalez/.bashrc"
...
FilePath "/Users/ggonzalez/workspace"
Prelude Turtle> cd "/tmp"
Prelude Turtle> pwd
FilePath "/private/tmp"
Prelude Turtle> touch "foo.txt"
Prelude Turtle> testfile "foo.txt"
True
Prelude Turtle> rm "foo.txt"
Prelude Turtle> testfile "foo.txt"
False
Prelude Turtle> test<TAB>
testdir testfile
Prelude Turtle> testdir "/tmp/<TAB>
.vbox-ggonzalez-ipc
KSOutOfProcessFetcher.0.r55jifrBu08ZlGAfPLYXKgYad4c=
launch-0kuyez
...
sync-dottools.stdout.logghci auto-printghci implicitly prints any value that is not a subroutine
Prelude Turtle> 2 + 2
4
Prelude Turtle> "123" <> "456" -- (<>) concatenates strings
"123456"The behavior is the same as if we had explicitly called print:
Prelude Turtle> print (2 + 2)
4
Prelude Turtle> print ("123" <> "456")
"123456"Prelude Turtle> shell "true" empty
ExitSuccess
Prelude Turtle> shell "false" empty
ExitFailure 1
Prelude Turtle> shell "ls | wc -l" empty
5
ExitSuccessUse proc if you want safer command templating:
Prelude Turtle> -- ls /tmp /usr
Prelude Turtle> proc "ls" ["/tmp", "/usr"] empty
/tmp:
KSOutOfProcessFetcher.0.r55jifrBu08ZlGAfPLYXKgYad4c=
...
/usr:
X11 bin lib local share
X11R6 include libexec sbin standalone
ExitSuccessWithin ghci:
dir1dir1 to dir2dir2Prelude Turtle> mkdir "dir1"
Prelude Turtle> mv "dir1" "dir2"
Prelude Turtle> rmdir "dir2"ghci as a shell#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle
import Turtle
-- +----- A subroutine ...
-- |
-- | +-- ... that returns `UTCTime`
-- | |
-- v v
datePwd :: IO UTCTime
datePwd = do
dir <- pwd
datefile dir
-- +----- A subroutine ...
-- |
-- | +-- ... that returns an empty value (i.e. `()`)
-- | |
-- v v
main :: IO ()
main = do
time <- datePwd
print timestr :: Int -- Oops!
str = "Hello!"
main :: IO ()
main = echo str$ ./example.hs
example.hs:8:7:
No instance for (IsString Int)
arising from the literal `"Hello, world!"'
Possible fix: add an instance declaration for (IsString Int)
In the expression: "Hello, world!"
In an equation for `str': str = "Hello, world!"
example.hs:11:13:
Couldn't match expected type `Text' with actual type `Int'
In the first argument of `echo', namely `str'
In the expression: echo str
In an equation for `main': main = echo strOverloadedStringsAnything that implements IsString can be represented by a string literal
Examples we’ve seen so far:
FilePathTextstr :: Text
str = 4
main :: IO ()
main = echo str$ ./example.hs
example.hs:8:7:
No instance for (Num Text)
arising from the literal `4'
Possible fix: add an instance declaration for (Num Text)
In the expression: 4
In an equation for `str': str = 4NumAnything that implements Num can be represented by a numeric literal
Examples we’ve seen so far:
IntDoubleshell
:: Text -- Command line
-> Shell Text -- Standard input (as lines of `Text`)
-> IO ExitCode -- Exit code of the shell commandproc
:: Text -- Program
-> [Text] -- Arguments
-> Shell Text -- Standard input (as lines of `Text`)
-> IO ExitCode -- Exit code of the shell commandHaskell (almost always) does not require type annotations
Type signatures are for the benefit of the programmer, not the compiler
Example:
Prelude Turtle> let addAsText x y = repr (x + y)
Prelude Turtle> :type addAsText
addAsText :: (Show a, Num a) => a -> a -> Text
Prelude Turtle> addAsText 2 3
"5"No need to annotate argument types
No need to specify interfaces
No need to specify generic type parameters
ghci as a shell#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle
{-# LANGUAGE OverloadedStrings #-}
import Turtle
main = do
let cmd = "false"
x <- shell cmd empty
case x of
ExitSuccess -> return ()
ExitFailure n -> die (cmd <> " failed with exit code: " <> repr n)This always prints an error message since false always fails:
$ ./example.hs
example.hs: user error (false failed with exit code: 1)
We can replace this:
cmd <> " failed with exit code: " <> repr n… with printf-style formatting:
format (s%" failed with exit code: "%d) cmd nThe compiler infers the number and types of arguments from the format string:
Prelude Turtle> :type format (s%" failed with exit code: "%d)
format (s%" failed with exit code: "%d) :: Text -> Int -> TextWhat do you think this prints out?
Prelude Turtle> format ("A "%s%" string that takes "%d%" arguments") "format" 2Format typeA format string is not Text!
Prelude Turtle> :type format
format :: Format Text r -> rSo what is going on here?
Prelude Turtle> format "I take 0 arguments"
"I take 0 arguments"Format implements IsString(%) :: Format b c -> Format a b -> Format a c
"A " :: Format a a
s :: Format a (Text -> a)
" string that takes " :: Format a a
d :: Format a (Int -> a)
" arguments" :: Format a a
"A "%s%" string that takes "%d%" arguments" :: Format a (Text -> Int -> a)
format ("A "%s%" string that takes "%d%" arguments") :: Text -> Int -> TextYou can build your own format specifiers!
OverloadedStringsExamples we’ve seen so far:
FilePathTextFormatghci as a shellYou can parse text using match:
Prelude Turtle> match "42" "42"
["42"]
Prelude Turtle> match decimal "42"
[42]
Prelude Turtle> match (decimal `sepBy` ",") "42,5,101"
[[42,5,101]]match
:: Pattern a -- The parser
-> Text -- The text to match
-> [a] -- A list of all possible solutionsWhy can we pass a string as the first argument to match?
OverloadedStringsExamples we’ve seen so far:
FilePathTextFormatPatternHere is how to translate regular expression idioms to patterns:
Regex Pattern
========= =========
"string" "string"
. dot
e1 e2 e1 <> e2
e1 | e2 e1 <|> e2
e* star e
e+ plus e
e*? selfless (star e)
e+? selfless (plus e)
e{n} count n e
e? option e
[xyz] oneOf "xyz"
[^xyz] noneOf "xyz"Prelude Turtle> match ("can" <|> "cat") "cat"
["cat"]
Prelude Turtle> match ("can" <|> "cat") "dog"
[]
Prelude Turtle> match (option "a" <> "b") "ab"
["ab"]
Prelude Turtle> match (option "a" <> "b") "b"
["b"]
Prelude Turtle> match (option "a" <> "b") "a"
[]To match the interior of the string, use has:
Prelude Turtle> match (has "B") "ABC"
["B"]prefix and suffix match the beginning or end of a string, respectively:
Prelude Turtle> match (prefix "A") "ABC"
["A"]
Prelude Turtle> match (suffix "C") "ABC"
["C"]Prelude Turtle> match (prefix (decimal `sepBy` ",")) "1,2,3"
[[1,2,3],[1,2],[1],[]]do notation works with Patterns!
bit :: Pattern Bool
bit = (do { "0"; return False }) <|> (do { "1"; return True })
portableBitMap :: Pattern [[Bool]]
portableBitMap = do
"P1"
spaces1
width <- decimal
spaces1
height <- decimal
count height (count width (do { spaces1; bit }))Prelude Turtle> match (prefix portableBitMap) "P1\n2 2\n0 0\n1 0\n"
[[[False,False],[True,False]]]
P1
2 2
0 0
1 0
{-# LANGUAGE OverloadedStrings #-}
import Turtle
import Data.Time
entry :: Text
entry = "2015-03-27 10:25:40+0000 [-] 10.45.209.121 ..."
pattern = do
year <- decimal
"-"
month <- decimal
"-"
day <- decimal
" "
hour <- decimal
":"
minute <- decimal
":"
second <- decimal
let d = fromGregorian year month day
let t = TimeOfDay hour minute second
return (d, t)$ stack ghci
Prelude Turtle> :load pattern.hs
*Main Turtle> :type pattern
pattern :: Pattern (Day, TimeOfDay)
*Main Turtle> match (prefix pattern) entry
[(2015-03-27,10:25:40),(2015-03-27,10:25:04)]Create a pattern that parses two integers stored in a string representation of a tuple:
tuple :: Pattern (Int, Int)
tuple = ???Such that you get this result when you use it:
>>> match tuple "(3,4)"
[(3,4)]tuple :: Pattern (Int, Int)
tuple = do
"("
x <- decimal
","
y <- decimal
")"
return (x, y)ghci as a shellYou’ve already encountered at least one stream: the ls command
Prelude Turtle> :type ls
ls :: Turtle.FilePath -> Shell Turtle.FilePathA “Shell a” is a stream of “a”s
Streams are not subroutines, so you can’t run them directly within ghci:
Prelude Turtle> ls "/tmp"
<interactive>:2:1:
No instance for (Show (Shell Turtle.FilePath))
arising from a use of `print'
Possible fix:
add an instance declaration for (Show (Shell Turtle.FilePath))
In a stmt of an interactive GHCi command: print itghci tries to print the Shell stream, but fails because Shell does not implement Show
viewThe view command is the simplest way to display a Shell stream:
view :: Show a => Shell a -> IO ()view prints every element of the stream:
Prelude Turtle> view (ls "/tmp")
FilePath "/tmp/.X11-unix"
FilePath "/tmp/.X0-lock"
FilePath "/tmp/pulse-PKdhtXMmr18n"
FilePath "/tmp/pulse-xHYcZ3zmN3Fv"
FilePath "/tmp/tracker-gabriel"
FilePath "/tmp/pulse-PYi1hSlWgNj2"
FilePath "/tmp/orbit-gabriel"
FilePath "/tmp/ssh-vREYGbWGpiCa"
FilePath "/tmp/.ICE-unixempty :: Shell aThe empty stream emits nothing:
Prelude Turtle> view empty -- Outputs nothing
Prelude Turtle>In other words:
view empty = return ()return :: a -> Shell areturn builds a singleton stream that emits exactly one element:
1 :: Int
return 1 :: Shell IntPrelude Turtle> view (return 1)
1In other words:
view (return x) = print xliftIO :: IO a -> Shell aliftIO transforms a subroutine into a singleton stream:
pwd :: IO Turtle.FilePath
liftIO pwd :: Shell Turtle.FilePathPrelude Turtle> view (liftIO pwd)
FilePath "/tmp"In other words:
view (liftIO io) = do x <- io
print x(<|>) :: Shell a -> Shell a -> Shell a(<|>) concatenates two streams together to build a new stream:
Prelude Turtle> view (return 1 <|> return 2)
1
2In other words:
view (xs <|> ys) = do view xs
view ysShell streamPrelude Turtle> view (ls "/tmp" <|> liftIO home <|> ls "/usr" <|> return "/lib")
FilePath "/tmp/.X11-unix"
FilePath "/tmp/.X0-lock"
FilePath "/tmp/pulse-PKdhtXMmr18n"
FilePath "/tmp/pulse-xHYcZ3zmN3Fv"
FilePath "/tmp/tracker-gabriel"
FilePath "/tmp/pulse-PYi1hSlWgNj2"
FilePath "/tmp/orbit-gabriel"
FilePath "/tmp/ssh-vREYGbWGpiCa"
FilePath "/tmp/.ICE-unix"
FilePath "/Users/ggonzalez"
FilePath "/usr/lib"
FilePath "/usr/src"
FilePath "/usr/sbin"
FilePath "/usr/include"
FilePath "/usr/share"
FilePath "/usr/games"
FilePath "/usr/local"
FilePath "/usr/bin"
FilePath "/lib"view (ls "/tmp" <|> liftIO home <|> ls "/usr" <|> return "/lib")… is the same as:
do view (ls "/tmp")
dir <- home
print dir
view (ls "/usr")
print "/lib"Shell implements IsStringPrelude Turtle> view "123"
"123"
Prelude Turtle> view (return "123") -- Same thing
"123"
Prelude Turtle> view ("123" <|> "456")
"123"
"456"
Prelude Turtle> view (return "123" <|> return "456") -- Same thing
"123"
"456"OverloadedStringsExamples seen so far:
FilePathTextFormatShellselectYou can build a Shell stream from a list:
select :: [a] -> Shell aExample:
Prelude Turtle> view (select [1, 2, 3])
1
2
3We can use select to loop within a Shell:
#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle
-- #!/bin/bash
{-# LANGUAGE OverloadedStrings #-} --
--
import Turtle --
--
example :: Shell () --
example = do --
x <- select [1, 2] -- for x in 1 2; do
y <- select [3, 4] -- for y in 3 4; do
liftIO (print (x, y)) -- echo \(${x},${y}\);
-- done;
main = sh example -- doneThis prints every permutation of x and y:
$ ./example
(1,3)
(1,4)
(2,3)
(2,4)sh utilitysh is like view, except that it doesn’t print any elements:
view :: Show a => Shell a -> IO ()
sh :: Shell a -> IO ()ShellsYou can loop over things other than select:
Prelude Turtle> -- for file in /tmp/*; do echo $file; done
Prelude Turtle> sh (do file <- ls "/tmp"; liftIO (print file))
FilePath "/tmp/.X11-unix"
FilePath "/tmp/.X0-lock"
FilePath "/tmp/pulse-PKdhtXMmr18n"
FilePath "/tmp/pulse-xHYcZ3zmN3Fv"
FilePath "/tmp/tracker-gabriel"
FilePath "/tmp/pulse-PYi1hSlWgNj2"
FilePath "/tmp/orbit-gabriel"
FilePath "/tmp/ssh-vREYGbWGpiCa"
FilePath "/tmp/.ICE-unix"In fact, that is how view is implemented:
view :: Show a => Shell a -> IO ()
view s = sh (do { x <- s; liftIO (print x) })ghci as a shellstdoutstdout :: Shell Text -> IO ()
stdout s = sh (do
txt <- s
liftIO (echo txt) )Standard out writes each Text element of the stream to a separate line:
Prelude Turtle> stdout "Line 1"
Line 1
Prelude Turtle> stdout ("Line 1" <|> "Line 2")
Line 1
Line 2stdinstdin :: Shell Textstdin streams lines from standard input:
#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle
-- #!/bin/bash
{-# LANGUAGE OverloadedStrings #-} --
--
import Turtle --
--
main = stdout stdin -- catstdin keeps producing lines until hitting EOF:
$ ./example.hs
ABC<Enter>
ABC
Test<Enter>
Test
42<Enter>
42
<Ctrl-D>(&)If you prefer to read left-to-right, you can use the infix (&) operator:
(&) :: a -> (a -> b) -> b
x & f = f x
main = stdin & stdoutinput and outputinput :: FilePath -> Shell Text
output :: FilePath -> Shell Text -> IO ()Run these examples:
Prelude Turtle> output "file.txt" ("Test" <|> "ABC" <|> "42")
Prelude Turtle> stdout (input "file.txt")
Test
ABC
42Or left-to-right:
Prelude Turtle> "Test" <|> "ABC" <|> "42" & output "file.txt"
Prelude Turtle> input "file.txt" & stdout
Test
ABC
42inshellinshell
:: Text -- Command line
-> Shell Text -- Standard input to feed to program
-> Shell Text -- Standard output produced by programPrelude Turtle> output "ls.txt" (inshell "ls" empty)
Prelude Turtle> stdout (input "ls.txt")
.X11-unix
.X0-lock
...
.ICE-unixTurtle Prelude> output "awk.txt" (inshell "awk '{ print $1 }'" "123 456")
Turtle Prelude> stdout (input "awk.txt")
123inshell (Left-to-right)Turtle Prelude> "123 456" & inshell "awk '{ print $1 }'" & output "awk.txt"
Turtle Prelude> input "awk.txt" & stdout
123inprocinproc
:: Text -- Program
-> [Text] -- Arguments
-> Shell Text -- Standard input to feed to program
-> Shell Text -- Standard output produced by programTurtle Prelude> stdout (inproc "awk" ["{ print $1 }"] "123 456")
123Translate this Bash command to Haskell:
$ nl < example.hs > numbered.txtPrelude Turtle> input "example.hs" & inproc "nl" [] & output "numbered.txt"ghci as a shellUse a Fold to reduce the stream to a single value:
Prelude Turtle> import qualified Control.Foldl as Fold
Prelude Turtle Fold> fold (ls "/tmp") Fold.length
9Prelude Turtle Fold> fold (ls "/tmp") Fold.head
Just (FilePath "/tmp/.X11-unix")You can combine folds:
Prelude Turtle Fold> let minMax = (,) <$> Fold.minimum <*> Fold.maximum
Prelude Turtle Fold> fold (select [1..10]) minMax
(Just 1,Just 10)What are the types of:
foldFold.lengthFold.headfold :: Shell a -> Fold a b -> IO b
Fold.length :: Fold a Int
Fold.head :: Fold a (Maybe a)ls "/tmp" :: Shell Turtle.FilePath
fold :: Shell a -> Fold a b -> IO b
fold (ls "/tmp") :: Fold Turtle.FilePath b -> IO b
fold (ls "/tmp") Fold.length :: IO IntFold implements Num>>> fold (select [1..10]) Fold.sum
55
>>> fold (select [1..10]) (1 + 2 * Fold.sum)
111
>>> fold (select [1..10]) (Fold.length + Fold.sum)
65
>>> fold (select [1..10]) 5
5Examples so far:
{-# LANGUAGE OverloadedStrings #-}
import Turtle
import Prelude hiding (FilePath)
parser = (,) <$> argPath "src" "Source directory"
<*> argPath "dst" "Destination directory"
backup file = do
exists <- testfile file
when exists (do
let backupFile = file <.> "bak"
backup backupFile
mv file backupFile )
main = do
(src, dest) <- options "Backup a directory" parser
sh (do
inFile <- lstree src
Just suffix <- return (stripPrefix src inFile)
let outFile = dest </> suffix
backup outFile
echo (format ("Copying "%fp%" to "%fp) inFile outFile)
cp inFile outFile )
echo "Done!"$ ./backup --help
Backup a directory
Usage: backup SRC DST
Available options:
-h,--help Show this help text
SRC Source directory
DST Destination directory$ ./backup a/ b/
Copying a/1 to b/1
Copying a/2 to b/2
$ ls b/
1 2
$ ./backup a/ b/
Copying a/1 to b/1
Copying a/2 to b/2
$ ls b/
1 1.bak 2 2.bakYou can use Haskell as a “better Bash”, getting types for free without slow startup times or heavyweight syntax.
Visit https://hackage.haskell.org/package/turtle for more extensive documentation on the shell scripting library we used today