// This is a comparison between Haskell and Groovy based on some simple problems found in the following
// Haskell Introduction:
// http://learnyouahaskell.com/starting-out#ready-set-go
// Ex 1. If we have two lists, [2,5,10] and [8,10,11] and we want to get the products of all the possible
// combinations between numbers in those lists, here's what we'd do.
/* HASKELL */
[ x*y | x <- [2,5,10], y <- [8,10,11]]
/* Groovy */
{ [2,5,10].collect { x -> [8,10,11].collect { y -> x * y } }.flatten() }
// Ex 2. What if we wanted all possible products that are more than 50?
/* HASKELL */
[ x*y | x <- [2,5,10], y <- [8,10,11], x*y > 50]
/* Groovy */
{ [2,5,10].collect { x -> [8,10,11].collect { y -> x * y } }.flatten().findAll{ it > 50 } }
// Ex 3. How about a list comprehension that combines a list of adjectives and a list of nouns … for epic hilarity.
/* HASKELL */
[adjective ++ " " ++ noun | adjective <- adjectives, noun <- nouns]
/* Groovy */
{ nouns, adjectives -> nouns.collect { noun -> adjectives.collect { adjective -> "$noun $adjective" } }.flatten() }
// Ex 4. Let's write our own version of length! We'll call it length'.
/* HASKELL */
length' xs = sum [1 | _ <- xs]
/* Groovy */
length = { xs -> xs.inject { acc, _ -> acc += 1 } }
// Ex 5. Here's a function that takes a string and removes everything except uppercase letters from it.
/* HASKELL */
removeNonUppercase st = [ c | c <- st, c `elem` ['A'..'Z']]
/* Groovy */
removeNonUppercase = { st -> st.inject { acc, c -> acc + ( ('A'..'Z').contains( c ) ? c : '' ) } }
// Ex 6. A list contains several lists of numbers. Let's remove all odd numbers without flattening the list.
/* HASKELL */
[ [ x | x <- xs, even x ] | xs <- xxs]
/* Groovy */
{ xxs -> xxs.inject([]) { res, xs -> res << xs.findAll { x -> x % 2 == 0 } } }
// Ex 7. ... let's try generating all triangles with sides equal to or smaller than 10:
/* HASKELL */
let triangles = [ (a,b,c) | c <- [1..10], b <- [1..10], a <- [1..10] ]
/* Groovy */
def triangles = { def res = []
(1..10).each{ a -> (1..10).each{ b -> (1..10).each{ c -> res << [a,b,c] } } }
res
}
// Ex 8. ... we'll add a condition that they all have to be right triangles.
// We'll also modify this function by taking into consideration that side b isn't larger
// than the hypothenuse and that side a isn't larger than side b.
/* HASKELL */
let rightTriangles = [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2]
/* Groovy */
def rightTriangles = { def res = []
(1..10).each{ c -> (1..c).each{ b -> (1..b).each{ a -> if ( a**2 + b**2 == c**2 ) res << [a,b,c] } } }
res
}
//############### Doing some I/O ###############//
// Ex 9. Read file and print its contents
/* HASKELL */
import System.IO
main = do
contents <- readFile "girlfriend.txt"
putStr contents
/* Groovy */
new File("girlfriend.txt").eachLine{ println it }
// Ex 10. Convert a file's text to ALL CAPS text and save it in another file
/* HASKELL */
import System.IO
import Data.Char
main = do
contents <- readFile "girlfriend.txt"
writeFile "girlfriendcaps.txt" (map toUpper contents)
/* Groovy */
def input = new File("girlfriend.txt")
new File("girlfriendcaps.txt").withWriter { w -> input.eachLine{ w.writeLine it.toUpperCase() } }
// Ex 11. Given a text file containing TO-DO tasks, remove a line chosen by the user from the file
/* HASKELL */
import System.IO
import System.Directory
import Data.List
main = do
handle <- openFile "todo.txt" ReadMode
(tempName, tempHandle) <- openTempFile "." "temp"
contents <- hGetContents handle
let todoTasks = lines contents
numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks
putStrLn "These are your TO-DO items:"
putStr $ unlines numberedTasks
putStrLn "Which one do you want to delete?"
numberString <- getLine
let number = read numberString
newTodoItems = delete (todoTasks !! number) todoTasks
hPutStr tempHandle $ unlines newTodoItems
hClose handle
hClose tempHandle
removeFile "todo.txt"
renameFile tempName "todo.txt"
/* Groovy */
def todoTasks = []
def todoFile = new File('/tmp/todo.txt')
todoFile.eachLine { todoTasks << it }
println 'These are your TO-DO items:'
todoTasks.eachWithIndex { todo, index ->
println "${index + 1} - $todo"
}
def numberString = System.console().readLine 'Which one do you want to delete?'
todoTasks.remove( numberString.toInteger() - 1 )
todoFile.withWriter { out ->
todoTasks.each { out.writeLine it }
}
// Ex 12. Create a SQL database with a single table, add data to it, then print the contents of the table
// Based on http://book.realworldhaskell.org/read/using-databases.html
/* HASKELL */
// Using Sqlite database for convenience: http://www.sqlite.org/
:module Database.HDBC Database.HDBC.Sqlite3
conn <- connectSqlite3 "test1.db"
run conn "CREATE TABLE test (id INTEGER NOT NULL, desc VARCHAR(80))" []
run conn "INSERT INTO test (id, desc) VALUES (?, ?)" [toSql 123, toSql "DB stuff"]
commit conn
res <- quickQuery conn "SELECT * FROM test"
let stringRows = map convRow res
mapM_ putStrLn stringRows
disconnect conn
where convRow :: [SqlValue] -> String
convRow [sqlId, sqlDesc] =
show intid ++ ": " ++ desc
where intid = (fromSql sqlId)::Integer
desc = case fromSql sqlDesc of
Just x -> x
Nothing -> "NULL"
convRow x = fail $ "Unexpected result: " ++ show x
/* Groovy */
// Much more convenient to use H2 in Java/Groovy: http://www.h2database.com
import static groovy.sql.Sql.newInstance
def sql = newInstance 'jdbc:h2:~/testdb', 'username', 'password', 'org.h2.Driver'
sql.execute 'CREATE TABLE test (id INTEGER NOT NULL, desc VARCHAR(80))'
sql.execute 'INSERT INTO test (id, desc) VALUES (?, ?)', [123, 'DB stuff']
sql.eachRow( 'SELECT * FROM test' ) { println "${it.id}: ${it.desc}" }
//############### GUI Stuff ###############//
// Ex 13. Create a window with a menu-bar containing a File menu with 2 items - About.. and Quit
// At the bottom of the window, there should be a status bar displaying some help for the currently
// selected menu item.
/* HASKELL */
{- demonstrates the use of a simple menu, statusbar, and dialog -}
module Main where
import Graphics.UI.WX
main :: IO ()
main
= start hello
hello :: IO ()
hello
= do -- the application frame
f <- frame [text := "Hello world!", clientSize := sz 300 200]
-- create file menu
file <- menuPane [text := "&File"]
quit <- menuQuit file [help := "Quit the demo", on command := close f]
-- create Help menu
hlp <- menuHelp []
about <- menuAbout hlp [help := "About wxHaskell"]
-- create statusbar field
status <- statusField [text := "Welcome to wxHaskell"]
-- set the statusbar and menubar
set f [ statusBar := [status]
, menuBar := [file,hlp]
-- as an example, put the menu event handler for an about box on the frame.
,on (menu about) := infoDialog f "About wxHaskell" "This is a wxHaskell demo"
]
/* Groovy */
import groovy.swing.SwingBuilder
import java.awt.BorderLayout as BL
import static javax.swing.JOptionPane.showMessageDialog as showDialog
import static javax.swing.JOptionPane.INFORMATION_MESSAGE as INFO
def about = { showDialog null, "This is a Groovy demo", "About Groovy", INFO }
new SwingBuilder().edt {
def status
def defaultStatus = "Welcome to Groovy SwingBuilder"
frame( title:'Hello world!', size: [ 300, 200 ], show: true) {
borderLayout()
menuBar() {
menu( text: "File" ) {
menuItem( text: "About..",
actionPerformed: about,
mouseEntered: { status.text = "About Groovy" },
mouseExited: { status.text = defaultStatus } )
menuItem( text: "Quit",
actionPerformed: { dispose() },
mouseEntered: { status.text = "Quit the demo" },
mouseExited: { status.text = defaultStatus } )
}
}
panel( constraints: BL.SOUTH ) {
loweredBevelBorder( parent: true )
status = label( text: defaultStatus )
}
}
}