2017-02-08 5 views
3

Есть ли лучший способ написать функцию addValues ниже? Кажется, должно быть возможно использовать сопоставление шаблонов, а не FSharp.Reflection, но я не вижу его.Создать новый футляр для соединения из существующего

open System 
open FSharp.Reflection 

type Value = 
    | Tag1 of decimal 
    | Tag2 of decimal 
    | Error of string 

let addValues v1 v2 = 
    let c1, f1 = FSharpValue.GetUnionFields(v1, v1.GetType()) 
    let c2, f2 = FSharpValue.GetUnionFields(v2, v2.GetType()) 
    let amt1 = (f1.[0]) :?> decimal 
    let amt2 = (f2.[0]) :?> decimal 

    if c1 = c2 
     then ((FSharpValue.MakeUnion(c1, [|box (amt1 + amt2)|]))) :?> Value 
     else Error "Mixed Tags" 

Это можно сделать следующим образом:

addValues (Tag1 22m) (Tag1 10m) //Value = Tag1 32M 
addValues (Tag1 22m) (Tag2 10m) //Value = Error "Mixed Tags" 
+2

Что должно произойти с 'addValues ​​(ошибка« e1 ») (ошибка« e2 »)'? – Lee

+1

Вы уверены, что вам нужен 'Error' в этом типе? –

+1

только для меня в этом случае лучше использовать ['Choice <'T1,'T2>'] (https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.choice%5B%27t1%2C%27t2%5D-union-%5Bfsharp% 5D) или, может быть, 'Option' –

ответ

5

Это не ясно, как addValues (Error "e1") (Error "e2) должны быть обработаны, но и для других случаев вы можете сделать:

let addValues v1 v2 = 
    match v1, v2 with 
    | Tag1 d1, Tag1 d2 -> Tag1 (d1 + d2) 
    | Tag2 d1, Tag2 d2 -> Tag2 (d1 + d2) 
    | Error e1, Error e2 -> //??? 
    | _ -> Error "Mixed Tags" 
+0

Я должен был указать« без перечисления всех тегов в матче ». В принципе, я хочу проверить, совпадают ли эти два случая, и если да, то возвращаем новый с суммой. В противном случае верните ошибку. Что касается того, являются ли эти два значения ошибками в этом случае, то в моем домене верна любая ошибка. – jbeeko

4

Это точно не то, что исходный вопрос был о, но, как некоторые другие комментаторы, у меня есть догадка, что ваши типы данных не очень хорошо выбраны. Наличие случая Error означает, что в момент, когда у вас больше тегов, ваша функция addValues становится действительно неудобной. Если я предполагаю, что все ваши ценности десятичные, вы можете повторно определить следующим образом:

type Tag = | Tag1 | Tag2 
type ResultOrError = | Result of Tag * decimal | Error of string 

(или использовать один из Choice типов). addValues становится:

let addValues t1 t2 = 
    match t1, t2 with 
    | Result (tag1, v1), Result (tag2, v2) when tag1 = tag2 -> Result (tag1, v1 + v2) 
    | Result _, Result _ -> Error "Tag mismatch" 
    | Result _, Error _ -> failwith "not implemented" 
    | Error _, _ -> failwith "not implemented" 

В тот момент, вы расширяете свои метки типа в type Tag = | Tag1 | Tag2 | Tag3, addValues остается по-прежнему работает.

+0

Действительно, это альтернативный дизайн, с которым я играл. Это просто немного более подробный, чем писать 'Tag1 33m' для создания значения, которое вам нужно написать' Result (Tag1, 33m) ' – jbeeko

1

Я хотел бы взорвать ту же трубу, что и Anton Schwaighofer in his answer. Критическая часть информации должна находиться внутри структуры данных, поскольку она будет использовать методы монады для выполнения the lifting; вам также потребовались бы отдельные вызовы конструкторам корпусов Tag1 и Tag2.

type Tag = Tag1 | Tag2 
type ResultOrError = 
| Result of Tag * decimal 
| Error of string 

let bind2 f = function 
| Result(tag, value) -> f tag value 
| error -> error 
let lift2EqualTag op mx my = 
    bind2 (fun tagX x -> 
     bind2 (fun tagY y -> 
      if tagX = tagY then Result(tagX, op x y) 
      else Error "Mixed tags") my) mx 
let add = lift2EqualTag (+) 

add (Result(Tag1, 22m)) (Result(Tag1, 10m)) 
// val it : ResultOrError = Result (Tag1,32M) 
add (Result(Tag1, 22m)) (Result(Tag2, 10m)) 
// val it : ResultOrError = Error "Mixed tags" 
Смежные вопросы