Home > Net >  Ideomatic way to represent fixed size grid in Haskell
Ideomatic way to represent fixed size grid in Haskell

Time:05-31

Something I've found a little odd, is that it seems tricky to represent a fixed size array / grid without resorting to libraries (fixed-length / fixed-vector.) And those libraries, from a glance, seem relatively clunky.

I want to do something similar to this Rust code (for a chess board):

const N: usize = 8;

struct Piece {}

struct Board {
    data: [[Piece; N]; N]
}

What is the idiomatic way to do this in Haskell? And why does it seem so difficult to represent fixed sized arrays?

The ideal code would look something like:

data Board (n :: Natural) = Board (Array Piece n)

But I guess the problem there is that it would need n additional parameters for the constructor, which I'd have to type out by hand?

CodePudding user response:

The grids package looked pretty good, but it's dependencies didn't seem to be set up correctly.

I'd forgotten about the vector-sized package, which seems to be exactly what I want. Unfortunately it stores the size at runtime but that's not a real issue.

This code seems to work nicely:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeApplications #-}

module Main where

import Data.Vector.Sized
import GHC.TypeLits
import Prelude hiding (replicate)

data Piece = Piece
    deriving (Show)

newtype Board (n :: Natural) = Board (Vector n (Vector n Piece))
    deriving (Show)

newBoard :: Board 8
newBoard = Board $ replicate @8 $ replicate @8 Piece

main :: IO ()
main = print $ newBoard

CodePudding user response:

If your N is fixed at compile time and small (here, it sounds like it's fixed to 8), and you don't mind a little bit of extra typing, you can simply define a new type that has exactly the number of fields you want, all of the same type. I expect this approach does not give good random-access time, or good update time, but it is very simple, obviously correct, and easy to implement yourself. Whether those are good tradeoffs for your situation, I couldn't say.

data Eight a = Eight a a a a a a a a
  deriving (Show, Functor, Foldable, Traversable)
instance Applicative Eight where
  pure x = Eight x x x x x x x x
  Eight fa fb fc fd fe ff fg fh <*> Eight a b c d e f g h =
    Eight (fa a) (fb b) (fc c) (fd d) (fe e) (ff f) (fg g) (fh f)
newtype SixtyFour a = SixtyFour (Eight (Eight a))
  deriving (Show, Functor, Foldable, Traversable)

As you can imagine, it is easy, although again tedious, to provide accessors and updaters for particular indices.

  • Related