Types 如何在F#中自动执行地图查找链?

Types 如何在F#中自动执行地图查找链?,types,map,f#,monads,Types,Map,F#,Monads,我经常有理由在一个映射中查找某个键,然后在另一个映射中查找结果,依此类推,在任意的链中,我试图推广这个过程。我还希望能够为此目的预先计算地图列表,以表示AI项目中可重用的关联链 显而易见的解决方案似乎是在我称之为“maybe_map”的列表上对map函数进行简单的扩展 let rec maybe_map operation initial_value list = match list with | [] -> Some(initial_value) | fi

我经常有理由在一个映射中查找某个键,然后在另一个映射中查找结果,依此类推,在任意的链中,我试图推广这个过程。我还希望能够为此目的预先计算地图列表,以表示AI项目中可重用的关联链

显而易见的解决方案似乎是在我称之为“maybe_map”的列表上对map函数进行简单的扩展

let rec maybe_map operation initial_value list =
  match list with
  | []          -> Some(initial_value)
  | first::rest ->
    match operation initial_value first with
    | None                   -> None
    | Some(subsequent_value) -> maybe_map operation subsequent_value rest 
以下是其使用示例

let employee_department = Map.ofList([("John", "Sales" ); ("Bob",    "IT"    )])
let department_country  = Map.ofList([("IT",   "USA"   ); ("Sales",  "France")])
let country_currency    = Map.ofList([("USA",  "Dollar"); ("France", "Euro"  )])

let result = maybe_map Map.tryFind "John" [employee_department; department_country; country_currency]
这样做效果很好,产生了一些结果(“欧元”)。当我需要使用地图的域和范围类型不同的数据时,问题就出现了。例如,如果我将汇率映射添加到上面的示例中,类型检查器会投诉,即使所涉及的每个操作都不会失败

let exchange_rate = Map.ofList([("Euro", 1.08); ("Dollar", 1.2)])

let result1 = maybe_map Map.tryFind "John" [employee_department; department_country; country_currency; exchange_rate]
类型检查器抱怨说,尽管Map.tryFind的以下应用程序均有效,但汇率预期为Map类型

let result2 = Map.tryFind "John" employee_department
let result3 = Map.tryFind "Euro" exchange_rate
如果我在C语言中这样做,那么在类型字典的数据上定义maybe#u映射似乎相对简单,显然任何“Duck-Typed”语言都不会有问题,但我看不出如何在F语言中这样做

解决这类问题的参考文献似乎经常出现,它遵循一种基于单子的方法。事实上,这里示例中使用的数据取自本文。然而,尽管使用单子来做这么简单的事情看起来像是使用大锤来敲开螺母,但当映射的域和范围类型不同时,本文给出的解决方案也会出现问题

有人知道我如何在不给概念上非常简单的东西带来额外复杂性的情况下取得进展吗

Epilog

在考虑了由于这个问题而收到的非常有用的意见后,我们决定采用这一点

class chainable
{
  protected Dictionary<IComparable, IComparable> items = new Dictionary<IComparable,IComparable>();

  public chainable() {}

  public chainable(params pair[] pairs) { foreach (pair p in pairs) items.Add(p.key, p.value); }

  public void add(IComparable key, IComparable value) { items.Add(key, value); }

  public IComparable lookup(IComparable key) { if (items.ContainsKey(key)) return items[key]; else return null; }

  public static List<chainable> chain(params chainable[] chainables) { return new List<chainable>(chainables); }

  public static IComparable lookup(IComparable key, List<chainable> chain)
  {
    IComparable value = key;
    foreach (chainable link in chain) { value = link.lookup(value); if (value == null) return null; }
    return value;
  }
}
类可链接
{
受保护的字典项=新建字典();
公共可链接(){}
公共可链接(params pair[]pairs){foreach(pairp in pairs)项。添加(p.key,p.value);}
public void add(IComparable key,IComparable value){items.add(key,value);}
公共IComparable查找(IComparable键){if(items.ContainsKey(key))返回items[key];else返回null;}
公共静态列表链(params chainetable[]chainetables){返回新列表(chainetables);}
公共静态IComparable查找(IComparable键、列表链)
{
i可比较值=键;
foreach(链中的可链接链接){value=link.lookup(value);if(value==null)返回null;}
返回值;
}
}
这个类的第一行实际上是对所需内容的说明,其余的操作直接遵循它

chainable employee_department = new chainable(new pair("John", "Sales" ), new pair("Bob",    "IT"    ));
chainable department_country  = new chainable(new pair("IT",   "USA"   ), new pair("Sales",  "France"));
chainable country_currency    = new chainable(new pair("USA",  "Dollar"), new pair("France", "Euro"  ));
chainable exchange_rate       = new chainable(new pair("Euro", 1.08    ), new pair("Dollar", 1.2     ));
List<chainable> chain = chainable.chain(employee_department, department_country, country_currency, exchange_rate);
IComparable result    = chainable.lookup("John", chain);
chaineable employee\u department=新的可链接(新的一对(“约翰”、“销售”)、新的一对(“鲍勃”、“IT”);
可链接部门\国家=新可链接(新对(“IT”、“美国”)、新对(“销售”、“法国”);
可链接国家/地区\货币=可链接的新货币(新货币对(“美国”、“美元”)、新货币对(“法国”、“欧元”);
可链接汇率=新的可链接汇率(新货币对(“欧元”,1.08),新货币对(“美元”,1.2));
列表链=可链接的.chain(员工\部门、部门\国家、国家\货币、汇率);
IComparable result=chaineable.lookup(“John”,chain);

诚然,chaineable类可以用F#构造,但那只是用F#语法编写C#,所以我们决定去掉中间人。似乎F#并不总是能够从所需操作的简单描述中推断出您真正需要操作的数据类型。再次感谢您的帮助(抱歉,使用Haskell不是一个选项)。

使用此帮助函数

let search map = Option.bind (fun k -> Map.tryFind k map)
您可以使用函数组合将正在搜索的地图链接在一起:

let searchAll = 
  search employee_department
  >> search department_country
  >> search country_currency
  >> search exchange_rate

searchAll (Some "John") //Some 1.08
这确保map
N
的值的类型与map
N+1
键的类型匹配。我看不到一种搜索
列表
而不借助某些界面(如
IDictionary
)和插入对
/
取消框
的调用来避开类型系统的方法

顺便说一下,您的
也许可以编写映射
函数

let maybeMap = List.fold (fun v m -> Option.bind (fun k -> Map.tryFind k m) v)
(对于不同类型的
Map
s仍然不起作用)

编辑

由于映射不是在编译时定义的,并且类型args不同,因此可以在运行时构建查找函数的集合

let maybeMap = Seq.fold (fun v f -> Option.bind f v)
let tryFind m (v: obj) = Map.tryFind (unbox v) m |> Option.map box

let lookups = ResizeArray() //using ResizeArray to emphasize that this is built dynamically
lookups.Add(tryFind employee_department)
lookups.Add(tryFind department_country)
lookups.Add(tryFind country_currency)
lookups.Add(tryFind exchange_rate)

maybeMap (Some (box "John")) lookups

它没有那么整洁,但符合您的要求。

@Daniel给出了一个绝妙的解决方案。我想评论一下泛型可以做什么,不能做什么:原始程序显然是类型安全的,那么为什么编译器没有意识到这一点呢

以下是关键位(意译):

但这也是不允许的:用于定义
类型链的所有类型参数必须在
中声明。对象和类型转换,这意味着F#类型的值只能由固定数量的其他类型组成。但我们不知道需要链接多少种不同类型的地图


假设F#确实允许
链接
独立于
成为通用的。然后模式匹配允许类型参数
'R
转义:

match c : Chain<string, float> with 
| Link (c1, c2) -> ... // c1 : Map<string, 'R>,  c2 : Map<'R, float> ? 
我们不能在F#中这样做,但是。让我们看看哈斯克尔的整个故事:

{-# LANGUAGE ExistentialQuantification #-}

import Data.Map (Map)
import qualified Data.Map as Map

department_country = Map.fromList [("IT",   "USA"   ), ("Sales",  "France")]
country_currency   = Map.fromList [("USA",  "Dollar"), ("France", "Euro")]
exchange_rate      = Map.fromList [("Euro", 1.08),     ("Dollar", 1.2)]

data Chain t u =
    forall r. (Ord r) =>   -- Any type r can be inside the chain
       Link (Chain t r) (Chain r u)
  | Node (Map t u)

lookup :: (Ord t, Ord u) => t -> Chain t u -> Maybe u
lookup k (Node m) = Map.lookup k m
lookup k (Link c1 c2) = case lookup k c1 of
                           Just v -> lookup v c2
                           Nothing -> Nothing

chain = Link (Node department_country) 
             (Link (Node country_currency) (Node exchange_rate))
并尝试一下:

*Main> maybe_map "IT" chain
Just 1.2

你的设计必须如此注重字符串和字典吗?department不能是引用country对象的对象吗?@svick不幸的是,在系统运行之前,映射中涉及的任意“事物”都是未知的。我不希望您移动到Haskell:)谢谢。是的,这可以完成任务,但正如您所说,不幸的是,它仍然不支持地图列表,这是关键。我使用List.fold从一个版本中“解开”了我的maybe_map函数
match c : Chain<string, float> with 
| Link (c1, c2) -> ... // c1 : Map<string, 'R>,  c2 : Map<'R, float> ? 
// maybe_map<'T,'U> : 'T -> Chain<'T,'U> -> Option<'U> 
let rec maybe_map k = function
   | Node m -> Map.tryFind k m
   | Link (c1, c2) ->            // some 'R. c1: Map<'T,'R>, c2: Map<'R,'U>
        let o = maybe_map k c1   // o : ?'R option 
        match o with                
        | None -> None 
        | Some v -> maybe_map v c2   // : Option<'U>
{-# LANGUAGE ExistentialQuantification #-}

import Data.Map (Map)
import qualified Data.Map as Map

department_country = Map.fromList [("IT",   "USA"   ), ("Sales",  "France")]
country_currency   = Map.fromList [("USA",  "Dollar"), ("France", "Euro")]
exchange_rate      = Map.fromList [("Euro", 1.08),     ("Dollar", 1.2)]

data Chain t u =
    forall r. (Ord r) =>   -- Any type r can be inside the chain
       Link (Chain t r) (Chain r u)
  | Node (Map t u)

lookup :: (Ord t, Ord u) => t -> Chain t u -> Maybe u
lookup k (Node m) = Map.lookup k m
lookup k (Link c1 c2) = case lookup k c1 of
                           Just v -> lookup v c2
                           Nothing -> Nothing

chain = Link (Node department_country) 
             (Link (Node country_currency) (Node exchange_rate))
*Main> maybe_map "IT" chain
Just 1.2