Чтобы сгладить вложенные конструкции match
, вам нужно использовать вложенные шаблоны. Это работает лучше всего для дискриминируемых союзов (как указал Брайан - и я согласен, что разработка кода F # для использования в первую очередь дискриминированных союзов - это лучшее, что вы можете сделать).
В противном случае вам понадобятся некоторые активные шаблоны, если вы хотите написать код лаконично, используя match
(ssp опубликовал один пример, который показывает активные шаблоны специально для вашей проблемы). Однако вы можете сделать это, используя следующие два многократно используемых активных шаблона:
let (|TryCast|_|) a : 'res option =
match (box a) with
| :? 'res as r -> Some(r)
| _ -> None
let (|Value|) (l:Lazy<_>) = l.Value
Первый похож на :?
, но он позволяет вам вкладывать другие шаблоны в соответствии со значением (что невозможно при as
). Вторая заставляет вычислять ленивое значение (я полагаю, что они оба могут быть объявлены в библиотеках F #, поскольку они весьма полезны). Теперь вы можете написать:
match lazy cond.EvalBool(), lazy body.Eval() with
| Value(true), Value(TryCast((Break(scope) : ControlFlowModifier)) as e) ->
e :> obj //Break is a DU element of ControlFlowModifier
| Value(true), _ ->
next() //all other values should call next()
| _, _ -> null
РЕДАКТИРОВАТЬ: Как указал Роджер в комментарии, эта версия кода может быть не очень читабельным. Я думаю, что лучшим вариантом было бы использовать только TryCast
и немного отформатировать исходный код (хотя это не совсем стандартное отступление, это правильно, и компилятор F # прекрасно с этим справляется):
match cond.EvalBool() with
| false -> null
| true ->
match body.Eval() with
| TryCast(Break(scope) as e) -> e :> obj
| _ -> next()
Вероятно, это наиболее читаемый вариант, основанный на сопоставлении с образцом, но вы также можете использовать if
вместо первого match
, как в версии от kvb, и комбинировать его с TryCast
(это действительно зависит от личных предпочтений) ):
if cond.EvalBool() then
match body.Eval() with
| TryCast(Break(scope) as e) -> e :> obj
| _ -> next()
else null
В любом случае, я считаю, что TryCast
делает код более читабельным, поскольку вы избегаете одного вложения (что в противном случае требуется из-за :? .. as ..
).