Pergunta

Eu fiz uma função semelhante à array de numpy. Ele converte listas para matrizes, listas de listas para matrizes 2D, etc.

Ele funciona assim:

ghci> arrFromNestedLists ["hello", "world"] :: Array (Int, (Int, ())) Char
array ((0,(0,())),(1,(4,()))) [((0,(0,())),'h'),((0,(1,())),'e'),((0,(2,())),'l'),((0,(3,())),'l'),((0,(4,())),'o'),((1,(0,())),'w'),((1,(1,())),'o'),((1,(2,())),'r'),((1,(3,())),'l'),((1,(4,())),'d')]

(Int, (Int, ())) e não (Int, Int) porque eu não sei de uma maneira programática para aumentar o comprimento de uma tupla. (Pergunta lado:? Há forma)

A codificação de que era estranho e eu tive que fazer uma "solução alternativa" (passando em torno de argumentos falsos para funções) para que ele funcione. Gostaria de saber se há uma maneira melhor.

Então, aqui está o código, interrompida com detalhes das soluções feias:

{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, TypeFamilies #-}

type family ListOfIndex i a
type instance ListOfIndex () a = a
type instance ListOfIndex (Int, i) a = [ListOfIndex i a]

class Ix i => ArrConv i where
  acBounds :: a -> ListOfIndex i a -> (i, i)
  acFlatten :: i -> ListOfIndex i a -> [a]

acBounds "deveria" ser :: ListOfIndex i a -> (i, i). E da mesma forma para acFlatten. Cada um é dada uma variável dummy (undefined é sempre o valor dado) porque senão eu não poderia obtê-lo para compilar: (

arrFromNestedLists :: forall i a. ArrConv i => ListOfIndex i a -> Array i a
arrFromNestedLists lst =
  listArray
  (acBounds (undefined :: a) lst)
  (acFlatten (undefined :: i) lst)

Acima é o argumento fictício undefined passando no trabalho. Conta a GHC qual instância ListOfIndex para uso.

instance ArrConv () where
  acBounds _ = const ((), ())
  acFlatten _ = (: [])

A seguir função deve ter sido a função acBounds em uma instância do ArrConv, e é declarada fora só porque eu preciso usar ScopedTypeVariables e eu não sei como eu posso fazer isso em uma função em uma definição instância ..

acSucBounds
  :: forall a i. ArrConv i
  => a -> [ListOfIndex i a] -> ((Int, i), (Int, i))
acSucBounds _ lst =
  ((0, inStart), (length lst - 1, inEnd))
  where
    (inStart, inEnd) = acBounds (undefined :: a) (head lst)

instance ArrConv i => ArrConv (Int, i) where
  acBounds = acSucBounds
  acFlatten _ = concatMap (acFlatten (undefined :: i))
Foi útil?

Solução

A razão que os argumentos extras para acBounds e acFlatten são necessários é que o a tipos e i não podem ser recuperados a partir ListOfIndex i a -> (i, i) e ListOfIndex i a -> [a] respectivamente. Uma solução alternativa é a de combinar os dois métodos em um método de tipo acArgs ListOfIndex i a -> ((i, i), a). Agora, o único problema é usá-lo no caso de (Int, i) de uma forma que impede a typechecker de generalizar seu tipo demasiado causando o mesmo problema de antes (por exemplo, não podemos simplesmente usar fst . acArgs).

{-# LANGUAGE TypeFamilies, FlexibleInstances #-}

import Data.Array

type family ListOfIndex i a
type instance ListOfIndex () a = a
type instance ListOfIndex (Int, i) a = [ListOfIndex i a]

class Ix i => ArrConv i where
  acArgs :: ListOfIndex i a -> ((i, i), [a])

instance ArrConv () where
  acArgs x = (((), ()), [x])

instance ArrConv i => ArrConv (Int, i) where
  acArgs lst =
    (((0, inStart), (length lst - 1, inEnd)), args >>= snd)
    where
      args = map acArgs lst
      (inStart, inEnd) = fst (head args)

arrFromNestedLists :: ArrConv i => ListOfIndex i a -> Array i a
arrFromNestedLists = uncurry listArray . acArgs

Outras dicas

Se você quiser manter acBounds e acFlatten separado, você pode adicionar um de nível tipo de argumento tag a ele, ou seja, acBounds teria tipo acBounds :: Proxy a -> ListOfIndex i a -> (i, i). Isso elimina a necessidade dos argumentos undefined, pois você pode apenas passar (Proxy :: SomeConcreteType) a ele; e acBounds não tem nenhuma maneira de extrair qualquer informação de nível de valor útil a partir dele, uma vez que é isomórfico (de forma não tipificada) para o tipo de unidade.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top