// 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 ) } } }