I've been slowly learning Haskell over the past few months. I made a small module to graph functions using Gloss, and I would like feedback on how idiomatic it is (looking for ways to simplify using standard functions and to make it more pointfree).

Graph.hs

module Graph (graph) where

import Graphics.Gloss

graph :: (Float -> Float) -> (Float, Float, Float, Float) -> Float -> Picture
graph f (l, r, b, t) dx = pictures $map Line visible where points :: [(Float, Float)] points = [(x, f x) | x <- [l,l+dx..r]] pairs :: [[(Float, Float)]] pairs = zipWith (\x y -> [x,y]) points$ tail points

visible :: [[(Float, Float)]]
visible = filter (all (\(_,y) -> b <= y && t >= y)) $pairs  Main.hs import Graphics.Gloss import Graph main :: IO () main = display FullScreen white . scale 20 20 . pictures$ [
color blue $graph f (-10, 10, -10, 10) 0.001, color black$ Line [(0, -10), (0, 10)],
color black $Line [(-10, 0), (10, 0)] ] where f :: Float -> Float f x = 1 / (x - 1)  ## 2 Answers This looks great. Honestly, there isn't anything I'd change. You could make it more pointfree in some parts, but that's not going be more readable or maintainable. # Magic numbers The only part I'd change are the static dimensions in main. Those are fine in a toy program, but a proper one will need some kind of configuration, so make sure that you use proper values from the beginning: main :: IO () main = display FullScreen white . scale width height . pictures$ [
color blue $graph f (l, r, b, t) 0.001, color black$ Line [(origin, b), (origin, t)],
color black $Line [(l, origin), (r, origin)] ] where f :: Float -> Float f x = 1 / (x - 1) -- easy configurable: (l, r, b, t) = (-10, 10, -10, 10) width = 20 height = 20 origin = 0  That way you can also exchange the values with proper command line interpretation main :: IO () main = do (l, r, b, t) <- getDimensions let width = r - l let height = t - b let origin = ... display FullScreen white . scale width height . pictures$ [
...


After all, no magic numbers is a good practice in both imperative and functional languages.

Next, I'd introduce GraphBound as a type synonym, just to make graph's type signature easier on the eye:

-- | Graph boundaries, given in (left, right, bottom, top) order
type GraphBound = (Float, Float, Float, Float)

graph :: (Float -> Float) -> GraphBound -> Float -> Picture
graph f (l, r, b, t) dx = pictures $map Line visible ...  You might even exchange GraphBound with a proper data type later which checks does not export its constructor to make sure that you don't end up with left = 20 and right = -10: makeGraph :: Float -> Float -> Float -> Float -> Maybe GraphBound  However, that's an overkill, so let's not focus on that for too long. # List comprehensions vs. point-free Now let's get back to your original query. Is it possible to make graph more point-free? Sure: graph :: (Float -> Float) -> (Float, Float, Float, Float) -> Float -> Picture graph f (l, r, b, t) = pictures . map Line . filter (all (\(_,y) -> b <= y && t >= y)) . (tail >>= flip (zipWith (\x y -> [x, y]))) . map (\x -> (x, f x)) . flip (enumFromThenTo l) r . (l+)  The point dx is completely gone from graph. However, the function is now unreadable. We went from a perfectly understandable function to a highly complex one. It gets a lot more readable if we use some helpers, but at that point we're almost back to your original function: graph :: (Float -> Float) -> (Float, Float, Float, Float) -> Float -> Picture graph f (l, r, b, t) = pictures . map Line . filter inGraph . segments . points . ranged where inGraph (_,y) = fall (\(_,y) -> b <= y && t >= y) segments ps = zipWith (\x y -> [x, y]) ps$ tail ps
...


That's not better than your original version, because your original version is already very good to begin with. The only change I could envision is a list comprehension in visible, but that's a matter of preference:

graph :: (Float -> Float) -> (Float, Float, Float, Float) -> Float -> Picture
graph f (l, r, b, t) dx = pictures lines
where
points = [(x, f x) | x <- [l,l+dx..r]]
pairs  = zipWith (\x y -> [x,y]) points $tail points lines = [Line segment | segment <- pairs, all (\(_,y) -> b <= y && t >= y)) segment]  But you're the judge on which variant you want to use. # Other remarks Thank you for using type signatures. Keep in mind that it's uncommon to use them in local bindings (where), as the outer function's signature should fix all types already. Inner type signatures can be a hassle if you change your outer type signature later, but they're sometimes necessary. You should simplify using standard functions and make it more pointfree. import Data.List.Split graph :: (Float -> Float) -> (Float, Float, Float, Float) -> Float -> Picture graph f (l, r, b, t) dx = pictures$ map Line
\$ wordsBy (\(_,y) -> b > y || t < y) [(x, f x) | x <- [l,l+dx..r]]