Writing a proper custom read instance

Hello fellow Haskellers,

I am learning Haskell from one month now, and I am struggling in creating a custom read instance for a personnal data type.

I followed this and the relevant chapter in Learn Yourself a Haskell, here is my code snippet.

data Position      =  Position (Absc,Ordn) deriving (Show)
instance Read (Position) where
readsPrec _ input =
    let absc = List.filter (/='(') $ takeWhile (/=',')
        ordn = List.filter (/=')') $ tail (dropWhile (/=',') )
    in (\str -> Position ( (read (absc str) :: Int)
                       , (read (ordn str) :: Int) ) ) input
type Absc = Int
type Ordn = Int

My goal is to parse an input "(1,3)" to output something like Position (1,3)

I, however, get the following error messages:

• Couldn't match expected type ‘[Char]’
              with actual type ‘[Char] -> [Char]’
• Probable cause: ‘takeWhile’ is applied to too few arguments
  In the second argument of ‘($)’, namely ‘takeWhile (/= ',')’
  In the expression: filter (/= '(') $ takeWhile (/= ',')
  In an equation for ‘absc’:
      absc = filter (/= '(') $ takeWhile (/= ',')

Same for the ordn function.

• Couldn't match expected type ‘[(Position, String)]’
              with actual type ‘Position’
• In the expression:
    (\ str
       -> Position ((read (absc str) :: Int), (read (ordn str) :: Int)))
      input
  In the expression:
    let
      absc = filter (/= '(') $ takeWhile (/= ',')
      ordn = filter (/= ')') $ tail (dropWhile (/= ','))
    in
      (\ str
         -> Position ((read (absc str) :: Int), (read (ordn str) :: Int)))
        input
  In an equation for ‘readsPrec’:
      readsPrec _ input
        = let
            absc = filter (/= '(') $ takeWhile (/= ',')
            ordn = filter (/= ')') $ tail (dropWhile (/= ','))
          in
            (\ str
               -> Position ((read (absc str) :: Int), (read (ordn str) :: Int)))
              input

It seems that my let statement does not recognize absc and ordn as functions (or at least try to apply them directly, while I only want to define them as partially applied functions to apply them later at parameter str). I am also probably messing up with my Position value constructor.

I am not familiar with Haskell coding style, and I may have used some keywords and tools that I do not fully understand. Could you hint me on how to write this to make it work?

Thank you in advance.

Upvotes: 1

Views: 839

Answers (2)

Using show "(3,4)"::Position returns Position (3,4). Thank you very much for your detailed answer. I tend to confuse $ and . notation, but after I went through the documentation, it became clear.

Upvotes: 0

epsilonhalbe
epsilonhalbe

Reputation: 15959

There are a bunch of errors in your code - I think you should read up a bit on the difference between $ and . it is an essential thing to know when handling functions, most of your errors are due to a try to apply a function to its argument ($) instead of concatenating functions (.).

This is what I modified your code to at least type check - though I think readsPrec should not keep the original input but the remaining string after the parsing is done.

Fixing the code

instance Read Position where
  readsPrec _ input =
      let absc = filter (/='(') . takeWhile (/=',')
          ordn = filter (/=')') . tail . dropWhile (/=',')
       in [(Position ( read (absc input) :: Int
                     , read (ordn input) :: Int), input)]

But seeing this makes me unhappy - isn't haskell said to be abstract, slick and really expressive. Let us give it another try

tidying up

instance Read Position where
  readsPrec _ str = let ('(':absc,',':ordn') = break (==',') str
                        (ordn,')':str') = break (==')') ordn'
                     in [(Position (read absc, read ordn), str')]

much better, we capture that there should be the parens at the beginning and the end, but still a bit cumbersome.

using existing functionality

Knowing that Tuples are already instances of Read

instance Read Position where
  readsPrec s str = [(Position x, rest) | (x,rest) <- readsPrec s str]

nice but still we can do better

Say we recently worked a bit with tuples and found the very handy module Data.Bifunctor that has functions first and second to transform the first and second component of a 2-tuple (any bifunctor in fact).

We can simplify the above to

instance Read Position where
  readsPrec s = map (first Position) . readsPrec s

clean and short.

Upvotes: 6

Related Questions