6

Is it possible to use list style pattern matching on vectors?

ie

import qualified Data.Vector as V

f :: V.Vector a -> a
f (x:xs) = x 

gives an error

Cactus
  • 27,075
  • 9
  • 69
  • 149
matt
  • 1,817
  • 14
  • 35

3 Answers3

9

-XViewPatterns can let you do this:

{-# LANGUAGE ViewPatterns #-}
module VecViewPats where

import Data.Vector (Vector)
import qualified Data.Vector as V

uncons :: Vector a -> Maybe (a, Vector a)
uncons v = if V.null v
  then Nothing
  else Just (V.unsafeHead v, V.unsafeTail v)

vsum :: Num a => Vector a -> a
vsum (uncons -> Just (a,av)) = a + vsum av
vsum (uncons -> Nothing) = 0

Or -XLambdaCase

import Control.Category ((>>>))
-- ...
vsum :: Num a => Vector a -> a
vsum = uncons >>> \case
  Just (a,av) -> a + vsum av
  Nothing     -> 0

But honestly, that's seems like a bit of a code smell as you're using one data structure (Vector) as another ([]) which suggests that maybe your choice of data structure is off.

If you really just want to treat it like a list for the purposes of some algorithm, why not use toList?

rampion
  • 87,131
  • 49
  • 199
  • 315
  • 2
    Or you can use a pattern synonym: `pattern x ::: xs <- (uncons -> Just (x, xs))` – Cactus May 03 '16 at 01:42
  • Is view pattern still needed no we have guard pattern ? – mb14 May 03 '16 at 06:10
  • I am using functions from the `Statistics` library, which work on `Vectors`. So I apparently would have to `toList` to work over my list then `fromList` to use the functions? which seems to defeat the point? – matt May 03 '16 at 09:17
  • matthias: it depends. Do you want to convert a vector to another vector of the same length? Use `fmap`. A single value? Use `fold`. A selection of the elements? Use `filter`. With both lists and vectors, specialized higher-order functions exist to make various tasks both more efficient and easier for others to read. – rampion May 03 '16 at 09:47
5

@Cactus has pointed out -XPatternSynonym (introduced in 7.8) combined with -XViewPattern can be used to pattern match on vectors. I am here to extend his comment a bit further.

pattern Empty <- (V.null -> True) 

The above defines a pattern synonym Empty for the empty vector. The Empty pattern matches against an empty vector using view pattern (V.null -> True). However, it cannot be used as an expression elsewhere, i.e. a uni-directional synonym, since the system doesn't really know what Empty is as a Vector (we only know that null v is True but there could be other vectors giving True as well).

To remedy this, a where clause can be added specifying that Empty actually is a empty vector, i.e. a bi-directional synonym, as well as a type signature:

pattern Empty :: Vector a
pattern Empty <- (V.null -> True) where Empty = V.empty 

This pattern synonym can be used to define uncons without an if expression:

uncons :: Vector a -> Maybe (a, Vector a)
uncons Empty = Nothing
uncons v     = Just (unsafeHead v, unsafeTail v)

We use uncons to define uni-directional synonym. Note I don't make it bi-directional since cons is costly for vector:

pattern (:<|)  :: a -> Vector a -> Vector a
pattern x :<| xs <- (uncons -> Just (x, xs))

Anyway, we are finally able to pattern match on vectors just like lists:

vsum :: Num a => Vector a -> a 
vsum Empty = 0
vsum (x :<| xs) = x + vsum xs

A complete code is here.

L.-T. Chen
  • 381
  • 4
  • 4
1

Vectors aren't intended for that kind of pattern matching--they were created to give Haskell O(1) lists, or lists that can be accessed from any point efficiently.

The closest thing to what you wrote would be something like this:

f v = V.head v

Or, if recursion is what you are looking for, the tail function will get the rest of the list.

But if you are trying to do something that moves along a list like that, there are Vector equivalents of functions such as foldl, find, map, and the like. It depends on what you intend to do.

  • I am trying to run functions which `f :: Vector a -> a` on a vector, but I want to move across the vector and recalculate (while keeping the other values) as each new item is added. ie on `[1,2,3,4,5,6,7,8,10]`, i want to `[f [1], f [1,2], f [1,2,3]..]` and so on. – matt May 03 '16 at 09:22
  • @matthias that's a [scan](https://hackage.haskell.org/package/vector-0.11.0.0/candidate/docs/Data-Vector.html#g:34) – rampion May 03 '16 at 09:53
  • @rampion sorry, I am bit confused. Isn't a scan a cumulative function that passes a result on to the next iteration? How would I write a scan function for what I have above? I appreciate your help. – matt May 03 '16 at 10:23
  • It depends on the definition of `f`. If there exists `g` s.t. `f [a_0...a_i] = g (f [a_0...a_{i-1}]) a_i` forall `i` and `b` s.t. `f [] = b`, then `scanl g b [a_0 .. a_n]` will yield `[ f [], f [a_0], f [a_0,a_1], f [a_0, a_1, a_2] ..., f [a_0, a_1, ... a_n ] ]`. Such implementations are often more efficient, as they only need to do `O(n)` work, rather than `O(n^2)`. – rampion May 03 '16 at 20:18
  • If not, then what you need is a version of `inits` for Data.Vector, which wouldn't be too hard to implement using [`init`](https://hackage.haskell.org/package/vector-0.11.0.0/candidate/docs/Data-Vector.html#v:init). `inits [ a_0, a_1 ... a_n ] = [ [], [a_0], [a_0, a_1], [a_0, a_1, a_2], ..., [a_0, a_1, ..., a_n] ]`, so you could do `fmap f . inits`. – rampion May 03 '16 at 20:20