Я создаю ленивый, функциональный DSL, который позволяет пользователям определять неизменяемые структуры с методами (что-то вроде классов из ОО-языков, но они не изменяемы). Я компилирую код этого языка в код Haskell.
Недавно я столкнулся с проблемой этого рабочего процесса. Я не хочу заставлять пользователя писать явные типы, поэтому я хочу активно использовать механизм вывода типов Haskell. Проблема возникает, когда я перевожу функцию, которая несколько раз вызывает полиморфный метод «объекта», передавая каждый раз разные типы аргументов, как здесь:
(псевдокод):
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, который я предоставил? Я хочу:
- to be able to translate non-mutable classes with methods (which can have default arguments) to Haskell's code. (preserving laziness, so I do not want to use ugly
IORefs
and mimic mutable data structures) - не заставлять пользователя явно писать какие-либо типы, поэтому я могу использовать все доступные механизмы Haskell, чтобы разрешить автоматический вывод типов — например, с помощью Шаблон Haskell для автоматического создания экземпляров класса типов для заданных методов (и т. д.).
- чтобы иметь возможность генерировать такой код с помощью моего компилятора без необходимости реализации моего собственного устройства вывода типов (или с помощью моего собственного устройства вывода типов, если нет другого решения)
- код результата для создания быстрых двоичных файлов (хорошо оптимизированный при компиляции).
- to be able to translate non-mutable classes with methods (which can have default arguments) to Haskell's code. (preserving laziness, so I do not want to use ugly
Если предложенный ниже рабочий процесс является наилучшим из возможных, как мы можем исправить предложенный код 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 не может вывести неявные типы ранга выше 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
или у меня есть написать для этого свой собственный механизм вывода типов?
ОБНОВЛЕНИЕ
Решение, предоставленное 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
возвращать результат определенного типа на основе типа его аргумента?
X
для каждого вызова метода? например скомпилироватьf
вf x1 x2 = print (method1 x1 (1 :: Int) (2 :: Int)) >> print (method1 x2 "Hello" "World")
иmain
вmain = f cons_X cons_X
или дажеmain = let x = cons_X in f x x
. - person Daniel Wagner   schedule 21.10.2013def test(x): x.f()
. Я не знаю, какого типаx
, когда набираю эту функцию, поэтому я не могу определить искаженное имяf
. - person Wojciech Danilo   schedule 21.10.2013f
сигнатуру какf x1 x2
, то, конечно, я могу вызватьf x x
из телаmain
, но это очень частный случай, когдаx
был определен в main. Идея не работает, когдаf
вызывается из другой функции. Напримерf_wrapper x = f x x
, который дает ту же ошибку. Конечно, мы можем попробовать отслеживать переменные, чтобы каждая функция, использующая такую функцию, продвигала эти аргументы в свою сигнатуру, но я не понимаю, как мы можем это сделать :( - person Wojciech Danilo   schedule 21.10.2013