4

I got the idea of defining an operator that takes a (possibly) multidimensional list, and a list of indices, and returns the element. My proto attempt was:

(!!!) xs [i]            = xs !! i
(!!!) xs (cI : restI)   = (xs !! cI) !!! restI

In retrospect, this obviously has a lot of problems. I couldn't figure out a type signature first off, then I realized that in line 2, the return type of (xs !! cI) will constantly change, and may not always even be a list (on the last "iteration")

I realized that to access a multi-dimensional array using the standard subscript operator, you can simply chain it like:

[[1,2,3],[4,5,6],[7,8,9]] !! 1 !! 1 = 5

And realized that that looks a lot like a fold, so I tried:

(!!!) xxs inds = foldl (!!) xxs inds
or simply (!!!) = foldl (!!) 

But I get the same error as my first attempt; that I'm trying to construct an infinite type.

Is this type of function even possible (through a hack or otherwise)? I'm starting to think that its type is just too up in the air to work.

Just as an example, I was aiming for the following:

[[1,2,3],[4,5,6],[7,8,9]] !!! [1,1] = 5
Carcigenicate
  • 43,494
  • 9
  • 68
  • 117
  • Are you trying to make a function that works with 2-dimensions, or n-dimensions? –  Aug 31 '14 at 01:58
  • @Balthamos Arbitrary dimensions. The depth of the array would be defined by the length of the list of given indices. Like in the last example I gave, I supplied 2 indices ([1,1]), so the given array would need to be 2-dimensional. If I had given the indices [2,1,1], the list would need to be 3-dimensional. – Carcigenicate Aug 31 '14 at 02:05
  • You would want to do something similar to this question: http://stackoverflow.com/questions/5994051/is-there-a-function-to-flatten-a-nested-list-of-elements –  Aug 31 '14 at 02:12
  • I'd want to retain the original structure though. And this question is more out of curiosity then anything. It started out as needing a 2D subscript operator, but that was easy. This seemed do-able originally, so I thought I'd take a stab at it. Now that it's issues are clear though, I'm curious if there is a way to make it possible. – Carcigenicate Aug 31 '14 at 02:19
  • @Carcigenicate In order to elegantly solve this sort of problem in a Haskell-like type system, you really need dependent types. Dependent types are those that depend on values at runtime. You're wanting to index an n-dimensional list using an n-element list, so the type of the function depends on the length of the indexing list passed to it. While it's possible to get some of those features in Haskell, it isn't something the type system supports as first-class, and requires extensions, a lot of boilerplate, and some tricks. If you're interested, look at the Agda or Idris languages. – bheklilr Aug 31 '14 at 03:02
  • @bheklilr Thanks. Like I said, this isn't something that I actually require; the question was purely out of curiosity. In reality, I can't see a need for this function anyway, unless you have a program that has many different depths of arrays being used at once, and you wanted a generic function to access them. I just needed a 2D subscript operator and decided to try and make it generic. If it requires a complicated hack, I'm not really interested in implementing it, but if someone wants the challenge, I'd like to see a solution (is this the kind of thing that's posted in Codes golf?) – Carcigenicate Aug 31 '14 at 03:16

1 Answers1

8

As long as you are not bound to using a list to store your indices, you can do this without much effort. The indices must be passed as a datatype which encodes how many indices there are in the type. The canonical length indexed list type looks something like this:

data Nat = Z | S Nat

infixr 5 :>
data Vector (n :: Nat) a where 
  Nil :: Vector Z a 
  (:>) :: a -> Vector n a -> Vector (S n) a 

Then your function is

(!!!) a Nil = a 
(!!!) a (i :> is) = (a !! i) !!! is 

You'll notice this doesn't compile. This is because the types of a in the first and second lines are different. The type of a must depend on the type of the indices, and you must tell the compiler exactly how they depend on it. The dependence is quite straightforward; when there are n indices, there must be a list of n dimensions:

type family Dimension (n :: Nat) (v :: * -> *) (x :: *) :: * where 
  Dimension     Z v x = x 
  Dimension (S n) v x = v (Dimension n v x)

Then the type of the above is quite simply

(!!!) :: Dimension n [] a -> Vector n Int -> a

I don't know how familiar you are with the more advanced features of the Haskell type system, but the above requires type families and data kinds.

user2407038
  • 14,400
  • 3
  • 29
  • 42