Jared Loomis
Jared Loomis

Reputation: 67

How can I get a value out of a data in Haskell

I have the following data:

data LinkedList a = Node a (LinkedList a) | Empty deriving (Show)

And I would like to know how to get a single value out of it, without pattern matching.
So in a C-based language: list.value

Upvotes: 4

Views: 4983

Answers (6)

Davorak
Davorak

Reputation: 7444

Jared Loomis, it sounds like you want to get access to the different parts of your LinkedList with out having to write your own helper functions. So in that light there is an alternative technique of writing data constructors that write these helper functions for you.

data LinkedList a = Node { nodeHead :: a, rest :: LinkedList a} | Empty
  deriving (Show)

example usage:

*Main> let example = Node 1 (Node 2 (Node 3 Empty))
*Main> example
Node {nodeHead = 1, rest = Node {nodeHead = 2, rest = Node {nodeHead = 3, rest = Empty}}}
*Main> nodeHead example
1
*Main> nodeHead . rest $ example
2
*Main> nodeHead . rest . rest $ example
3

Careful though nodeHead and rest are considered partial functions and throw an exception when used on Empty:

*Main> nodeHead Empty
*** Exception: No match in record selector nodeHead
*Main> rest Empty
*** Exception: No match in record selector rest

If you want something with post fix syntax I would recommend the lens package.

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

data LinkedList' a = Node' { _nodeHead' :: a, _rest' :: LinkedList' a} | Empty'
  deriving (Show)
makeLenses ''LinkedList'

*Main> example ^? rest' 
Just (Node' {_nodeHead' = 2, _rest' = Node' {_nodeHead' = 3, _rest' = Empty'}})
*Main> example ^? rest' . nodeHead'
Just 2

Upvotes: 6

phipsgabler
phipsgabler

Reputation: 20960

For the sake of completeness let me mention something I've heard the name "dot hack" for:

Prelude> data LinkedList a = Node { nodeHead :: a, nodeRest :: LinkedList a} | Empty deriving (Show)
Prelude> let example = Node 1 (Node 2 (Node 3 Empty)) :: LinkedList Int
Prelude> let (.) = flip ($)
Prelude> example.nodeRest.nodeHead
2

It's simply realizing that the C-style accessing . is the same as applying accessor functions to the object mentioned before it, which in Haskell means to turn around the arguments of the application operator ($).

Of course, one probably wouldn't use this in real code, since it will cause confusion among other people and the loss of the composition operator.

Upvotes: 1

wit
wit

Reputation: 1622

As a rule you don't need to extract all values. If you really wish to extract, use Comonad extract function:

class Functor w => Comonad w where
    extract :: w a -> a
    ...

Often Foldable, Traversable, Monoids, Monad, Zippers are more useful

Upvotes: 1

AndrewC
AndrewC

Reputation: 32455

Let's use monads to do what you'd like. Monads are great because when defining them, you get to redefine what ; and = mean for you. (This being Haskell, we use newlines and indentation to indicate where ; goes and <- to differentiate from the permanent definition =.)

I'll have to use pattern matching to make the instances, because I've got nothing else to go on yet:

instance Monad LinkedList where
   Empty >>= f = Empty
   (Node a as) >>= f = f a `andthen` (as >>= f)
   return a = Node a Empty

The binding operator >>= is the configurable plumbing behind the <- operator. Here we've chosen ; to mean next element, using a helper function addthen in the works:

andthen :: LinkedList a -> LinkedList a -> LinkedList a
Empty `andthen` list = list
(Node a list) `andthen` therest = Node a (list `andthen` therest)

Now we can use monad notation to grab a value at a time. For example, let's apply a function to the elements in a linked list:

applyToElements :: (a -> b) -> LinkedList a -> LinkedList b
applyToElements f list = do
   val <- list
   return (f val)
ghci> applyToElements ( ++ ", yeah" )  (Node "Hello" (Node "there" Empty))
Node "Hello, yeah" (Node "there, yeah" Empty)

A simpler way

I simply wouldn't have defined that that way. I'd have used pattern matching directly:

applyToElements :: (a -> b) -> LinkedList a -> LinkedList b
applyToElements f Empty = Empty
applyToElements f (Node a list) = Node (f a) (applyToElements f list) 

and then declared

instance Functor LinkedList where
   fmap = applyToElements

because the usual name for a function that applies another function elementwise is fmap.

More complicated

Monads can be good for other things though, and sometimes it's the best way of expressing something:

combinationsWith :: (a -> b -> c) -> LinkedList a -> LinkedList b -> LinkedList c
combinationsWith f list otherlist = do  -- do automatically traverses the structure 
    val <- list                    -- works like   val = list.value 
    otherval <- otherlist          --              otherval = otherlist.value
    return (f val otherval)        -- once for each value/othervalue

Because we chose to use andthen when we defined <- for LinkedList, if we use two lists, it'll use the first list and then the second in a sort of nested subloop kind of way, so the otherval values change more frequently than the first vals, so we get:

ghci> combinationsWith (+) (Node 3 (Node 4 Empty)) (Node 10 (Node 100 Empty))
Node 13 (Node 103 (Node 14 (Node 104 Empty)))

Upvotes: 2

Levi Pearson
Levi Pearson

Reputation: 4994

Rather than present a Haskell solution to your question, I will present a more realistic comparison with C and suggest that you don't really want what you seem to be asking for:

struct list {
    int value;
    struct list *next;
};

int main(void) {
    struct list *list = NULL;
    int val;

    /* Goodbye, cruel world! */
    val = list->value;

    /* If I had "pattern-matched"... */
    if (list == NULL) {
        val = 0;
    } else {
        val = list->value;
    }
    return 0;
}

When you don't check for the NULL case in C (which corresponds to pattern matching in Haskell) you crash with a SEGFAULT at some point in the execution of your program instead of getting a compile error.

In other words, you can't get a value out of a 'possibly empty' recursive data type without doing case analysis in C either! At least not if you value the stability of your program. Haskell not only insists that you do the right thing, but provides convenient syntax to help you to do so!

As mentioned in other answers, record definition syntax provides you with convenient projections (i.e. accessor functions) automatically, which have a bit of a trade-off in comparison to accessing struct members in C: The projections are first-class, and can thus be used as parameters and return values; but they are in the same namespace as all other functions, which can lead to unfortunate name clashes.

So, in the case of straightforward data types (i.e. non-recursive) the syntax for accessing members is roughly at the same level of convenience: whole.part for C-like languages vs. part whole for Haskell.

For recursive types (like the one in your example) where one or more members reference possibly-empty instances of the same type, case analysis is necessary in either language before a value can be extracted. Here, you will either need to wrap the field access in your C-like language with a case analysis or possibly an exception handler. In Haskell, you have various forms of syntactic sugar for pattern matching that tend to be much more concise.

Furthermore, see the answer regarding Monads for ways of providing even more convenience for working with 'possibly empty' types in Haskell, hiding most of the intermediate pattern matching for multi-step computations inside library functions.

To wrap this up: My point is that as you take the time to learn the patterns and idioms of Haskell, you will likely find yourself missing the ways of doing things in C-like languages less and less.

Upvotes: 2

jtobin
jtobin

Reputation: 3273

I'd just pattern match.

llHead :: LinkedList a -> a
llHead Empty      = error "kaboom"
llHead (Node x _) = x

If you want the element at a specific index, try something like this (which also uses pattern matching):

llIdx :: LinkedList a -> Int -> a
llIdx l i = go l i
  where go Empty       _ = error "out of bounds"
        go (Node x _)  0 = x
        go (Node _ xs) j = go xs (j - 1)

Some assurance that this works:

import Test.QuickCheck

fromList []     = Empty
fromList (x:xs) = Node x (fromList xs)

allIsGood xs i  = llIdx (fromList xs) i == xs !! i

llIdxWorksLikeItShould (NonEmpty xs) =
  let reasonableIndices = choose (0, length xs - 1) :: Gen Int
  in  forAll reasonableIndices (allIsGood xs)

-- > quickCheck llIdxWorksLikeItShould
-- +++ OK, passed 100 tests.

Upvotes: 0

Related Questions