2010-01-11 2 views
5

Я сделал функцию, похожую на numpy's array. Он преобразует списки в массивы, списки списков для 2d массивов и т.д.Типы типов Haskell и фиктивные аргументы

Это работает так:

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,())) и не (Int, Int), потому что я не знаю о programatic способ увеличить длину кортеж. (вопрос стороны: есть ли такой способ?)

Кодирование этого было неудобным, и мне пришлось сделать «обходной путь» (проходящий вокруг фиктивных аргументов функций), чтобы он работал. Интересно, есть ли лучший способ.

Так вот код, прерываемый с подробной информацией о некрасивых обходные:

{-# 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 "должен" быть :: ListOfIndex i a -> (i, i). И аналогично для acFlatten. Каждому дается фиктивная переменная (undefined всегда значение дано), потому что в противном случае я не мог заставить его компилировать :(

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

Выше фиктивная undefined аргумент прохождения на работе. Он рассказывает GHC, какой экземпляр ListOfIndex использовать.

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

ниже функция должна была acBounds функции в экземпляре ArrConv и объявлена ​​вне только потому, что мне нужно использовать ScopedTypeVariables, и я не знаю, как я могу сделать это в функции в определении экземпляра ..

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)) 
+3

«Я не знаю программного способа увеличить длину кортежа». Я не думаю, что ты можешь. Это прекрасный пример функции, тип которой зависит от значения. Это было бы легко сделать на навязчивом языке, таком как «Агда», но невозможным в Haskell. Может быть, вы могли бы каким-то образом использовать «GADTs», чтобы дать вам какое-то зависимое поведение, но с моей точки зрения, я не знаю, как это сделать. –

+0

Может быть, Шаблон Haskell может быть полезен: http://www.haskell.org/bz/thdoc.htm http://www.haskell.org/haskellwiki/Template_Haskell – primodemus

+0

@primodemus: С TH я могу сделать экземпляры для 'ArrConv' для массивов до 10 измерений, и они будут использовать обычные кортежи для индексов, что является улучшением.Но я бы чувствовал, что предел произволен, и код, вероятно, будет гораздо менее удобочитаемым. – yairchu

ответ

4

Причина, по которой дополнительные аргументы acBounds и acFlatten необходимы в том, что типы a и i не могут быть восстановлены из ListOfIndex i a -> (i, i) и ListOfIndex i a -> [a] соответственно. Одним из способов является объединение двух методов в один метод: acArgs типа ListOfIndex i a -> ((i, i), a). Теперь единственная проблема заключается в том, чтобы использовать его в экземпляре (Int, i) таким образом, который мешает тому, чтобы typechecker слишком обобщал свой тип, вызывая ту же проблему, что и раньше (например, мы не можем просто использовать 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 
+0

@Reid Barton: Awesome! Спасибо :) Я немного отредактировал ваш ответ (надеюсь, вы не против): вместо передачи 'acArgs' к функции делают ее мономорфной, теперь она использует ее только в одном месте. – yairchu

+0

А, я вижу. Ницца. –

0

Если вы хотите сохранить acBounds и acFlatten по отдельности, вы можете добавить type-level tag argument к нему, т.е. acBounds будет иметь тип acBounds :: Proxy a -> ListOfIndex i a -> (i, i). Это устраняет необходимость в аргументах undefined, поскольку вы можете просто передать (Proxy :: SomeConcreteType); и acBounds не имеет способа извлечь из него какую-либо полезную информацию уровня значения, так как она изоморфна (в нетипизированном виде) для типа единицы.

Смежные вопросы