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.
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.
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
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.
Knowing that Tuple
s are already instances of Read
instance Read Position where
readsPrec s str = [(Position x, rest) | (x,rest) <- readsPrec s str]
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.