eddiec
eddiec

Reputation: 7646

Pass a lens into a function

I'd like to perform a sort based on a specific value within a record. As such I was thinking of passing a lens into a lensSort function but I've been unable to make it work.

Ideally I could do something like this

lensSort :: HasLens a => Lens' a b -> a -> a -> -> Ordering
lensSort lens x y | x ^. lens > y ^. lens = GT
                    | x ^. lens < y ^. lens = LT
                    | otherwise = GT

And be able to call it with something like

data Rectangle = Rectangle { _height :: Int, _width :: Int }
makeLenses'' Rectangle

let foo = [Rectangle 1 2, Rectangle 2 1]
sortBy (lensSort height) foo

I'm failing to get this to work, and concerned I might be barking up the wrong tree completely, I'm still new to Haskell.

Upvotes: 4

Views: 143

Answers (2)

leftaroundabout
leftaroundabout

Reputation: 120711

Aside from typos, your code actually works pretty much as it is – the only things that obviously needed is that b be in fact comparable. The following works:

{-# LANGUAGE TemplateHaskell, RankNTypes #-}
import Control.Lens
import Control.Lens.TH
import Data.List

data Rectangle = Rectangle { _height :: Int, _width :: Int }
  deriving (Show)
makeLenses ''Rectangle

lensSort :: Ord b => Lens' a b -> a -> a -> Ordering
lensSort lens x y | x ^. lens > y ^. lens = GT
                    | x ^. lens < y ^. lens = LT
                    | otherwise = GT

foo :: [Rectangle]
foo = [Rectangle 1 2, Rectangle 2 1]

main = print $ sortBy (lensSort height) foo
-- [Rectangle {_height = 1, _width = 2},Rectangle {_height = 2, _width = 1}]

Note that there is not really a need to pass around an actual lens, because you only use it as a getter (≅function) anyway. So you can just do

import Data.Ord (comparing)
main = print $ sortBy (comparing (^.height))

...without any extra definitions.

Upvotes: 7

epsilonhalbe
epsilonhalbe

Reputation: 15967

You can implement your sortByLens - or rather sortByGetting function. Starting with your definitions

{-# LANGUAGE TemplateHaskell #-}
module Test where

import Control.Lens
import Data.Function (on)
import Data.List (compare, sortBy)

data Rectangle = Rectangle { _height :: Int, _width :: Int }
$(makeLenses ''Rectangle)

we will start to create your desired function in ghci:

stack ghci test.hs --package lens

> import Control.Lens
> import Data.Function
> import Data.List
> :t sortBy
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
> :t on
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
> :t compare
compare :: Ord a => a -> a -> Ordering

putting these together with your approach

>:t sortBy (compare `on` (view height))
sortBy (compare `on` (view height)) :: [Rectangle] -> [Rectangle]

to get this a bit more general we do the following

>:t \lens -> sortBy (compare `on` (view lens))
\lens -> sortBy (compare `on` (view lens)) :: Ord a => Getting a s a -> [s] -> [s]

and so we can define

sortByGetting :: Ord a => Getting a s a -> [s] -> [s]
sortByGetting g = sortBy (compare `on` view g)

Upvotes: 4

Related Questions