【问题标题】:ML polymorphic function机器学习多态函数
【发布时间】:2023-04-18 23:47:01
【问题描述】:

我正在尝试在类型 T 上定义多态函数 sum,其中类型 T 可以是 int、real 或类型 T 的列表。 int 和 real 的情况的总和应该按预期工作。对于T的列表,它应该返回两个列表的对应元素之和(列表的长度应该相同)。

例子:

sum (INT 2, INT 3) = INT 5

sum (REAL 2.3, REAL 3.4) = REAL 5.7

sum(L [2, 3, 4], L [3, 4, 5]) = L [5, 7, 9]

sum(L L([2, 3, 4], [2, 3, 4]), L ([3, 4, 5], [3, 4, 5]) = L ([5, 7, 9], [3, 4, 5])   

我写的函数如下:

datatype T = INT of int | REAL of real | L of T list;


fun sum (x:T, x':T) = case (x, x') of

  (INT n, INT n') => INT (n + n')

| (REAL n, REAL n') => REAL (n + n')

| (L (x :: xs), L (y :: ys)) => L ((sum (x, y)) :: (sum (L xs, L 
                                                           ys))

| (_,_) => REAL (0.0);

但是对于上面的函数,我得到了错误:

构造函数应用于不正确的参数。

expects: _ * [??? list]

but got: _ * [???]

in: :: (sum (x, y), sum (L xs, L ys))

unhandled exception: Fail: compilation aborted: parseAndElaborate reported errors

因此我通过添加 nil 来更改我的代码,如下所示。据我所知,错误的原因是 cons 运算符最终试图将 T(INT 或 REAL)连接到 T(INT 或 REAL)为 (sum (x, y), sum (L xs , L ys)) 最终将通过递归调用 INT 或 REAL 来评估。因此,我通过在最后添加 nil(空列表)来更改我的代码

fun sum (x:T, x':T) = case (x, x') of

   (INT n, INT n') => INT (n + n')

 | (REAL n, REAL n') => REAL (n + n')

 | (L (x :: xs), L (y :: ys)) => L ((sum (x, y)) :: (sum (L xs, 
                                                 L ys)) :: nil)

 | (_,_) => REAL (0.0);

但是对于这种情况,它对于 INT 和 REAL 行为正确,但对于多态列表则不正确。 它对 INT 和 REAL 表现正确(因为它们更易于实现)。对于列表部分,我想 cons 运算符存在一些问题,无法找出解决方案。 我执行的测试用例及其输出如下:

sum (L([INT(1)]), L([INT(3)]));
val it = L [INT 4,L []] : T

sum (L([INT(1),INT(2)]), L([INT(3),INT(4)]));
val it = L [INT 4,L [INT #,L #]] : T

P.S: 请忽略最后一个大小写 (,) => REAL (0.0),因为我会在稍后处理类型不匹配的情况。

【问题讨论】:

  • 那些不是测试用例。测试用例将预期值与实际值进行比较。您还没有说明它们如何或为什么不起作用,因此只能通过猜测您认为错误是什么来帮助您。
  • 我编辑了问题并使其更加清晰。
  • 变得不那么清楚了:毫无疑问。您的测试不起作用(L L([2, 3, 4] 可能应该是 L [L [2, 3, 4]] 并且您无法比较实数是否相等;您是否真的阅读了我的答案?)。而且由于您编辑了主要代码,因此您没有关于 L 模式的递归的基本案例,这使得它比您编辑它之前更漂亮但更破碎。 * 不太适合逐渐消除错误消息的来回过程。你可能需要导师或耐心。

标签: sml ml


【解决方案1】:
  • 这似乎是相互递归函数的一个很好的用例:

    datatype T = INT of int | REAL of real | L of T list
    
    fun sum (x, x') = case (x, x') of
       (INT  n, INT  n') => INT (n + n')
     | (REAL n, REAL n') => REAL (n + n')
     | (L   ns, L   ns') => L (sumLists (ns, ns'))
     | (_, _)            => ? (* mismatching types *)
    
    and sumLists (x::xs, y::ys) = sum (x, y) :: sumLists (xs, ys)
      | sumLists ([], []) = []
      | sumLists (_, _) = ? (* mismatching lengths *)
    
  • 由于类型不匹配而导致REAL 0.0 似乎是个问题。

    例如,为什么sum (INT 2, L [INT 3]) 应该是REAL 0.0

    为什么sum (INT 2, REAL 3.0) 应该是REAL 0.0

    考虑在INTREAL 中添加一个“没有价值”的替代项,如果这对您的域有意义,或者可能更好,考虑将sum 函数更改为也许 如果可以在树的所有级别上有意义地计算它,则返回一个总和,即val sum : T * T -> T option。归结为错误处理。

  • 编写描述极端案例预期行为的测试。特别是在对不同类型的值求和以及对长度不匹配的列表求和时。

    您的示例将如下所示作为测试:

    val test1 = sum (L [INT 1], L [INT 3])               = L [INT 4]
    val test2 = sum (L [INT 1, INT 2], L [INT 3, INT 4]) = L [INT 4, INT 6]
    

    除了 T 不是一个相等类型,因为它包含一个real,所以你需要编写自己的相等运算符,当你遇到real 时使用epsilon test (nearlyEqual) ,例如:

    fun eqT (INT x, INT y) = x = y
      | eqT (REAL x, REAL y) = nearlyEqual(x, y, someEps)
      | eqT (L (x::xs), L (y::ys)) = eqT (x, y) andalso eqT (L ys, L xs)
      | eqT (L [], L []) = true
      | eqT (_, _) = false
    

    你的一些极端案例可能看起来像

    val case1 = sum (INT 2, REAL 3.0)
    val case2 = sum (INT 2, L [])
    val case3 = sum (INT 2, L [INT 3])
    val case4 = sum (L [INT 1], L [INT 1, INT 2])
    val case5 = sum (L [INT 1], L [INT 1, REAL 2.0])
    val case6 = sum (L [], L [L []])
    

【讨论】: