2012-05-14 5 views
5

В моем коде у меня есть контекст доступа к базе данных, который обеспечивает элементарные операции чтения/записи, называемые CouchDB.ctx. Различные модули в моем приложении затем расширяют этот класс с дополнительными функциями, такими как Async.ctx.OCaml: ограничения типа в подписях

Я реализую модуль Cache, который обернут вокруг модуля Source. Функции модуля Cache принимают аргумент контекста и манипулируют базой данных. Затем некоторые вызовы пересылаются в модуль Source вместе с контекстом.

мне нужно определить функтор вдоль линий этого:

module CouchDB = struct 
    class ctx = object 
    method get : string -> string option monad 
    method put : string -> string -> unit monad 
    end 
end 

module AsyncDB = struct 
    class ctx = object 
    inherit CouchDB.ctx 
    method delay : 'a. float -> (ctx -> 'a monad) -> 'a monad 
    end 
end 

module type SOURCE = sig 
    class ctx = #CouchDB.ctx (* <-- incorrect *) 
    type source 
    val get : source -> ctx -> string monad 
end 

module Cache = functor(S:SOURCE) -> struct 
    class ctx = S.ctx 
    type source = S.source 
    let get source ctx = 
    bind (ctx # get source) (function 
    | Some cache -> return cache 
    | None -> 
     bind (S.get source ctx) 
     (fun data -> bind (ctx # put source data) 
         (fun() -> return data)) 
end 

module SomeSource = struct 
    class ctx = AsyncDB.ctx 
    type source = string 
    let get s ctx = 
    ctx # async 300 (some_long_computation s) 
end 

module SomeCache = Cache(SomeSource) 

Проблема заключается в том, что я не могу выразить тот факт, что контекст, используемый Source модуль должен быть подтипом CouchDB.ctx. Приведенный выше код возвращает ошибку:

A type variable is unbound in this type declaration. 
In type #CouchDB.ctx as 'a the variable 'a is unbound 

Как выразить это ограничение типа?

+0

Мне любопытно ваша подпись 'SOURCE'. Декларация в моем сознании должна быть 'module type SOURCE = sig class ctx: object inherit CouchDB.ctx end (* ... *) end'; делает ли это не то, что вам нужно? –

ответ

5

[Устаревшие ...

Ближайший вы можете получить это определение подписи как:

module type SOURCE = sig 
    type 'a ctx = 'a constraint 'a = #CouchDB.ctx 
    type source 
    val get : source -> 'a ctx -> string 
end 

Но, конечно же, вы можете также просто написать:

module type SOURCE = sig 
    type source 
    val get : source -> #CouchDB.ctx -> string 
end 

Редактировать : Обратите внимание, что OCaml использует структурный набор текста для объектов. Это означает, что даже если вы этого захотите, вы не можете более ограничивать, чем указано выше. Он даже не ограничивает аргументы get экземплярами CouchDB.ctx или производным классом - любой объект, который имеет (по крайней мере), тот же метод будет совместим. Даже когда вы пишете

val get : source -> CouchDB.ctx -> string 

вы можете передать любой объект, который имеет те же методы. Тип CouchDB.ctx - это просто аббревиатура для определенного типа структурного объекта, которая соответствует объектам, сгенерированным классом с тем же именем. Это не ограничивается этими. И просто чтобы быть уверенным: это считается особенностью.

======]

Edit 2: С помощью расширенного примера, теперь я вижу, что вы хотите и почему. К сожалению, это невозможно сразу в OCaml. Вам понадобится частично абстрактных типов. А именно, вам нужно будет писать

module type SOURCE = sig 
    type ctx < CouchDB.ctx 
    ... 
end 

Это не доступно в OCaml. Тем не менее, вы можете получить близко, если вы готовы предоставить явное вентиляционный в подписи:

module type SOURCE = sig 
    type ctx 
    val up : ctx -> CouchDB.ctx 
    type source = string 
    val get : source -> ctx -> string monad 
end 

Затем в Cache, вы должны заменить вхождения ctx#get с (S.up ctx)#get, а также для ctx#put.

module Cache = functor (S:SOURCE) -> struct 
    type ctx = S.ctx 
    type source = S.source 
    let get source ctx = 
    bind ((S.up ctx)#get source) ... 
end 

module SomeSource = struct 
    type ctx = AsyncDB.ctx 
    let up ctx = (ctx : ctx :> CouchDB.ctx) 
    type source = string 
    let get s ctx = ... 
end 

module SomeCache = Cache (SomeSource) 

Обратите внимание, что я также сделал type source = string прозрачной в подписи SOURCE. Без этого я не вижу, как ctx#get source может набрать тип в функторе Cache.

+0

Я не понимаю, как я могу сделать первый пример подходящим для моей ситуации (мой контекстный класс имеет нулевые параметры). Вторая версия неверна, так как она ожидает, что 'Source.get' примет * любой * подкласс' CouchDB.ctx', когда на самом деле он принимает только * one * ('Async.ctx'). –

+0

Ну, почему вы * хотите * ограничивать 'Source.get' так? Насколько я могу судить, это необязательно, и он ничего не покупает (все подтипы являются чисто структурными в OCaml, даже если вы используете имена классов). Я бы порекомендовал не пытаться навязать какую-то номинальную подтипизацию на OCaml, так как это не так, как работает язык. OCaml выступает за структурную типизацию над номинальной и полиморфизм над подтипированием. Только ограничивайте типы, если это необходимо для проверки типов. –

+0

Этот тип был ограничен для * целей иллюстрации *. В моей фактической кодовой базе это ограничение было выведено из тела функции, что, очевидно, более сложно, чем то, что представлено здесь. Я пытаюсь представить свою проблему без копирования-вставки фактических тысяч строк из моего кода. Поскольку использование ограничений типа для иллюстративных целей запутывает, я изменил код выше для дополнительного разъяснения. –

1

Если я не уверен, непонимание того, что вы после этого, то это следует сделать трюк:

module type SOURCE = sig 
    class ctx : CouchDB.ctx 
    type source 
    val get : source -> ctx -> string 
end 

class ctx : CouchDB.ctx является class-specification. Документы OCaml описывают их как

This is the counterpart in signatures of class definitions. A class specification matches a class definition if they have the same type parameters and their types match.

Там же этот

module type SOURCE = sig 
    class type ctx = CouchDB.ctx 
    type source 
    val get : source -> ctx -> string 
end 

, который немного отличается. Первый требует определения реального класса в модуле, который последний принимает определение класса или определение типа класса (т. Е. Псевдоним типа класса).

+0

Я обновил свой вопрос, чтобы включить пример использования функтора, чтобы вы также могли его скомпилировать. Ваши решения не позволили 'SomeSource' совместить подпись' SOURCE', потому что они не разрешают 'ctx = Async.ctx' либо как классы или типы классов. –

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