2015-08-16 2 views
1

Я имел эту идею типобезопасного языка шаблонного, который будет использовать полиморфные варианты в качестве источника переменных типобезопасного, которые могут быть заменены текстом, например:Типобезопасного замена переменного шаблона

type 'a t = Var of 'a | Text of string | Join of 'a t * 'a t 

let rec render ~vars = function 
    | Text source -> source 
    | Var label -> vars label 
    | Join (left, right) -> render left ~vars^render right ~vars 

let result = render (Join (Var `Foo, Text "bar")) ~vars:(function `Foo -> "foo");; 

let() = assert (result = "foobar") 

Все в порядке: компилятор будет обеспечивать, чтобы вы не забыли переменную замещения, или что у вас нет опечатки в имени переменной - благодаря полиморфным вариантам.

Однако, я считаю, две проблемы:

  1. Вы можете случайно поставить неиспользуемую переменную.

  2. Если шаблон не содержит переменных, вы по-прежнему вынуждены поставлять ~vars функцию, и только один, который будет работать будет fun _ -> "" или fun _ -> assert false, которые compromizes типа-безопасности в случае, если шаблон когда-либо изменения.

Я ищу совет по вышеуказанным проблемам, но я также ценю любые применимые рекомендации по дизайну API.

+0

Как насчет 'Var of 'a *' a -> string'? Это выглядит некрасиво, но вам нужно связать принтер с переменной. Мое решение здесь - связать их с конструктором типа. – objmagic

+0

@nnarklrh не уверен, что я следую. Что это за '' a -> string' принтер? Можете ли вы привести пример? – Halst

+0

Извините, моя ошибка. Это должно быть 'Var of 'a * string'. Затем вы пишете «Var (' Foo, Foo) ». Но это кажется слишком громоздким. – objmagic

ответ

1

Я думаю, что это невозможно с полиморфными вариантами. Тип render функции:

вал визуализации: вар :('а -> строка) ->' в -> Строка

и частичное применение render (Join (Var `Foo, Text "var")) имеет следующий вид:

vars:([> `Foo ] -> string) -> string 

Что вы хотите сделать, так это закрыть открытый вариант типа [> `Foo ] и ограничить его [ `Foo ] -> string, чтобы исключить функции, которые могут получить большие входы, такие как [< `Foo | `Bar ] -> string.

Единственный способ ограничить тип, чтобы добавить ограничение типа: (vars : [ `Foo ] -> string) перечисляются все теги, которые вы хотите явно, но это то, что вы хотите, чтобы избежать ...

3

Ничего силы вы всегда использовать полиморфный варианты. у вас может быть тип void, который гарантированно отличается от любого полиморфного варианта.

type void 
let empty_vars : void -> string = fun _ assert false 

Когда вы примените его к пустой шаблон, вы в конечном итоге с

let result = render (Text "bar") ~vars:empty_vars 

Таким образом, если вы позже добавить переменную в шаблоне, вы сразу заметите его через ошибки типа.

Для неиспользуемых переменных, лучшее, что я могу предложить также не использовать полиморфные варианты:

type v = Foo 
let result = render (Join (Var Foo, Text "bar")) ~vars:(function Foo -> "foo");; 

Это будет только поймать неиспользованные случаи в определении функции, но, конечно, если вы удалите часть шаблона , вы ничего не заметите.

Одно из решений, которые имеют схожие свойства, но могут или не могут удовлетворить ваш вкус, - это использовать объекты.

let rec render ~vars = function 
    | Text source -> source 
    | Var label -> label vars 
    | Join (left, right) -> render left ~vars^render right ~vars 


let foo v = v#foo 
let result = render (Join (Var foo, Text "bar")) ~vars:object method foo = "foo" end 

Таким образом, вы можете сохранить тот же шаблон, когда не используются переменные:

let result = render (Text "bar") ~vars:object end 

Но до сих пор нет неиспользованной переменной проверки.

+0

Ваше решение 'empty_vars' интересно. Однако вам нужно помнить, что функция должна быть «~ vars». Я думал о том, чтобы сделать '~ vars' по умолчанию' empty_vars', но затем он выводит 'render' на' ~ vars: (void -> string) -> void t -> string', что не полезно. Какие-либо предложения? – Halst

+0

Это классическая проблема, когда требуется, чтобы параметр по умолчанию имел тип, который можно переопределить, к сожалению, в настоящее время нет общего хорошего решения для этого (у некоторых есть идеи о том, как реализовать это в компиляторе). Но в этом случае у вас есть несколько легкое решение: объявите две версии вашей функции: «render: vars :('a -> string) ->' at -> string' и' render ': void t -> string', 'render'' является тем, который был предварительно использован для' empty_vars' –

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