Я создаю ленивый, функциональный DSL, который позволяет пользователям определять непеременные структуры с помощью методов (что-то вроде классов из OO языки, но они не изменяемы). Я компилирую код этого языка в код Haskell.
Недавно у меня возникла проблема с этим документооборотом. Я не хочу заставлять пользователя писать явные типы, поэтому я хочу сильно использовать Hencell type inferencer. Проблема возникает, когда я переводю функцию, которая многократно вызывает полиморфный метод "объекта", передавая каждый раз разные типы аргументов, например здесь:
(псевдокод):
class X {
def method1(a, b) {
(a, b) // return
}
}
def f(x) {
print (x.method1(1,2)) // call method1 using Ints
print (x.method1("hello", "world")) // call method1 using Strings
}
def main() {
x = X() // constructor
f(x)
}
-
Каков наилучший способ генерации "эквивалентного" кода Haskell псевдокода OO, который я предоставил? Я хочу:
- чтобы иметь возможность переводить непеременные классы с помощью методов (которые могут иметь аргументы по умолчанию) для кода Haskell. (сохраняя лень, поэтому я не хочу использовать уродливые
IORefs
и имитировать изменяемые структуры данных). - не, чтобы заставить пользователя явно писать любые типы, поэтому я могу использовать все доступные механизмы Haskell для автоматического вывода типа - например, используя Template Haskell, чтобы автоматически генерировать экземпляры typeclass для данных методов (и т.д.).
- чтобы иметь возможность генерировать такой код с моим компилятором, без необходимости реализации моего собственного типа inferencer (или с моим собственным методом inferencer, если нет другого решения)
- код результата для создания быстрых двоичных файлов (хорошо оптимизируется при компиляции).
- чтобы иметь возможность переводить непеременные классы с помощью методов (которые могут иметь аргументы по умолчанию) для кода Haskell. (сохраняя лень, поэтому я не хочу использовать уродливые
-
Если предложенный ниже рабочий процесс является наилучшим, как мы можем исправить предложенный код Haskell таким образом, чтобы работали как
f con_X
, так иf con_Y
? (см. ниже)
Текущее состояние работы
Псевдокод может легко переводиться в следующий код Haskell (он написан вручную, а не сгенерирован, чтобы его было проще читать):
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
-- class and its constructor definition
data X a = X { _methodx1 :: a } deriving(Show)
con_X = X { _methodx1 = (\a b -> (a,b)) }
-- There can be other classes with "method1"
class F_method1 cls sig where
method1 :: cls sig -> sig
instance F_method1 X a where
method1 = _methodx1
f x = do
print $ (method1 x) (1::Int) (2::Int)
print $ (method1 x) ("Hello ") ("World")
main = do
let x = con_X
f x
Вышеприведенный код не работает, потому что Haskell не может выводить неявные типы rank выше 1, например тип f
. После небольшого обсуждения #haskell irc было найдено частичное решение, а именно, мы можем перевести следующий псевдокод:
class X {
def method1(a, b) {
(a, b) // return
}
}
class Y {
def method1(a, b) {
a // return
}
}
def f(x) {
print(x.method1(1, 2))
print(x.method1("hello", "world"))
}
def main() {
x = X()
y = Y()
f(x)
f(y)
}
для кода Haskell:
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleContexts #-}
data Y a = Y { _methody1 :: a } deriving(Show)
data X a = X { _methodx1 :: a } deriving(Show)
con_X = X { _methodx1 = (\a b -> (a,b)) }
con_Y = Y { _methody1 = (\a b -> a) }
class F_method1 cls sig where
method1 :: cls sig -> sig
instance F_method1 X a where
method1 = _methodx1
instance F_method1 Y a where
method1 = _methody1
f :: (F_method1 m (Int -> Int -> (Int, Int)),
F_method1 m (String -> String -> (String, String)))
=> (forall a. (Show a, F_method1 m (a -> a -> (a,a))) => m (a -> a -> (a, a))) -> IO ()
f x = do
print $ (method1 x) (1::Int) (2::Int)
print $ (method1 x) ("Hello ") ("World")
main = do
f con_X
-- f con_Y
Этот код действительно работает, но только для типа данных X
(поскольку он жестко закодировал возвращаемый тип method1
в сигнатуре f
. Строка f con_Y
не работает.
Кроме того, есть ли способ автоматически сгенерировать подпись f
или мне нужно написать свой собственный инкрементный тип для этого?
UPDATE
Решение, предоставляемое Crazy FIZRUK, действительно работает для этого конкретного случая, но используя existential data types
, например data Printable = forall a. Show a => Printable a
, принудительно применяет все методы с определенным именем (т.е. метод method1 "), чтобы иметь одинаковый тип результата во всех возможных классах, чего я не хочу достичь.
В следующем примере ясно показано, что я имею в виду:
(псевдокод):
class X {
def method1(a, b) {
(a, b) // return
}
}
class Y {
def method1(a, b) {
a // return
}
}
def f(x) {
print(x.method1(1, 2))
x.method1("hello", "world") // return
}
def main() {
x = X()
y = Y()
print (f(x).fst()) // fst returns first tuple emenet and is not defined for string
print (f(y).length()) // length returns length of String and is not defined for tuples
}
Можно ли перевести такой код в Haskell, разрешив f
вернуть результат определенного типа в зависимости от типа его аргумента?