Search code examples
rubyhaskellclojurelanguage-comparisons

How would you write this Clojure snippet in Ruby and/or Haskell?


I was working on a Rails template and was trying to write a bit of code that allows me to populate a table or multiple columns of ul tags "top-to-bottom" and "left-to-right" across however many columns I specify. I'm just getting the hang of Ruby so I couldn't figure this out. I'm also curious about an idiomatic Haskell version for this useful snippet. Improvements to Clojure version appreciated:

(defn table [xs & {:keys [cols direction]
                   :or   {cols 1 direction 'right}}]
  (into []
        (condp = direction
          'down (let [c (count xs)
                      q (int (/ c cols))
                      n (if (> (mod c q) 0) (inc q) q)]
                  (apply map vector (partition n n (repeat nil) xs)))
          'right (map vec (partition cols cols (repeat nil) xs))))) 

With this bit of code I can then do the following:

(table (range 10) :cols 3)

Printed out this would look like so:

0    1    2 
3    4    5 
6    7    8
9

And the trickier one:

(table (range 10) :cols 3 :direction 'down)

Looks like so:

0    4    8    
1    5    9    
2    6        
3    7        

Solution

  • I would probably write something like this in Haskell, using the Data.List.Split package from Hackage:

    import Data.List       (intercalate, transpose)
    import Data.List.Split (splitEvery)
    
    data Direction = Horizontal | Vertical deriving (Eq, Read, Show)
    
    table :: Direction -> Int -> [a] -> [[a]]
    table Horizontal cols xs = splitEvery cols xs
    table Vertical   cols xs = let (q,r) = length xs `divMod` cols
                                   q'    = if r == 0 then q else q+1
                               in transpose $ table Horizontal q' xs
    
    showTable :: Show a => [[a]] -> String
    showTable = intercalate "\n" . map (intercalate "\t" . map show)
    
    main :: IO ()
    main = mapM_ putStrLn [ showTable $ table Horizontal 3 [0..9]
                          , "---"
                          , showTable $ table Vertical   3 [0..9] ]
    

    Some of this, like the Direction type and the transpose trick, was derived from jkramer's answer. I wouldn't use keyword arguments for something like this in Haskell (it doesn't really have such things, but you can emulate them using records as in Edward Kmett's answer), but I put those arguments first because it's more useful with partial application (defaultTable = table Horizontal 1). The splitEvery function just chunks a list into lists of the appropriate size; the rest of the code should be straightforward. The table function returns a list of lists; to get a string, the showTable function inserts tabs and newlines. (The intercalate function concatenates a list of lists, separating them with the given list. It's analogous to Perl/Python/Ruby's join, only for lists instead of just strings.)