首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在Haskell中实现动态数据类型转换

在Haskell中实现动态数据类型转换
EN

Stack Overflow用户
提问于 2021-01-01 12:04:51
回答 1查看 85关注 0票数 1

我试图在Haskell中实现一种动态类型的编程语言,它支持三种数据类型,我们将它们称为ABC,为了说明起见,我将允许A = IntegerB = [Integer]C = (Integer, Integer) (但您可以忽略这些类型的语义,这不是我所关心的)。

为了在算术表达式中可互换地使用任意类型的值,我实现了一个代数数据类型Value

代码语言:javascript
复制
data Value = A A
           | B B
           | C C

而且,由于我希望能够添加和乘值,所以我实现了类型OP

代码语言:javascript
复制
class Op a where
  add :: a -> a -> a
  mul :: a -> a -> a

现在,我还希望我的类型可以相互隐式转换(当两个不同类型出现在算术表达式中时),根据以下规则:

如果两种类型都是A

  • Otherwise,,则如果其中一种类型为A,则不进行转换,如果其中一种类型为A,则将另一种类型转换为A --这两种类型都转换为B

为了实现这一点,我实现了另一个类型类型,ImplicitlyConvertible

代码语言:javascript
复制
class ImplicitlyConvertible a where
  toA :: a -> A
  toB :: a -> B

然后,完整的示例如下所示:

代码语言:javascript
复制
{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}

module Value where

type A = Integer

type B = [Integer]

type C = (Integer,Integer)

data Value = A A
           | B B
           | C C

class ImplicitlyConvertible a where
  toA :: a -> A
  toB :: a -> B

instance ImplicitlyConvertible A where
  toA = id
  toB = error "can't convert A to B"

instance ImplicitlyConvertible B where
  toA = sum
  toB = id

instance ImplicitlyConvertible C where
  toA   = sum
  toB c = [fst c, snd c]

instance ImplicitlyConvertible Value where
  toA v = case v of
    A a -> toA a
    B b -> toA b
    C c -> toA c
  toB v = case v of
    A a -> toB a
    B b -> toB b
    C c -> toB c

class Op a where
  add :: a -> a -> a
  mul :: a -> a -> a

instance Op A where
  add = (+)
  mul = (*)

instance Op B where
  add = zipWith (+)
  mul = zipWith (*)

valueOp :: (Value -> Value -> Value) -> (Value -> Value -> Value)
valueOp op (A v) v' = op (A v) (A $ toA v')
valueOp op v (A v') = op (A $ toA v) (A v')
valueOp op v v'     = op (B $ toB v) (B $ toB v')

instance Op Value where
  add = valueOp add
  mul = valueOp mul

我对此有三个问题:

  • 实际上并没有为A实现toB这一事实似乎是不干净的。即使永远不应该调用它,我也希望完全避免实现它。

  • instance ImplicitlyConvertible Value只是一堆样板代码,我想去掉.

  • 我不确定我对instance Op Value的实现是否合理。

我一开始是不是走错路了?我如何更干净地实现所有这些呢?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-01-01 15:24:06

这实际上是最简单的工作,通过你的问题,所以我将从最后开始。

  • 我不确定我对instance Op Value的实现是否合理。

不,您的instance Op Value实现是不明智的。您试过在示例输入上评估它吗?你可能已经注意到它永远不会产生任何结果。问题的根源在于你用什么来称呼valueOp。看起来,您使用多态函数add (或mult)调用了add,但实际上并非如此。由于valueOp总是将Value上的函数作为其第一个参数,所以Op Value实例中的add定义总是使用由实例本身定义的函数add调用valueOp。这就产生了无限的递归。

如何将多态函数add传递给valueOp?请考虑这样一种类型:

代码语言:javascript
复制
valueOp :: (forall a. Op a => a -> a -> a) -> (Value -> Value -> Value)

(请注意,您需要为此启用RankNTypes。)这种类型以a上的二进制函数作为输入,该函数适用于任何具有Op实例的a。因此,对于第一种情况,您可以写:

代码语言:javascript
复制
valueOp op (A v) v' = A $ op v (toA v')

输入和输出仍然是Value类型,但是当我们调用op时,我们使用的是A类型的值,这正是我们想要的。另外两种情况自然会发生:

代码语言:javascript
复制
valueOp op v (A v') = A $ op (toA v) v'
valueOp op v v'     = B $ op (toB v) (toB v')

  • instance ImplicitlyConvertible Value只是一堆样板代码,我想去掉.

您实际需要用于ABCA实例吗?如果您从未独立使用它们,那么您可以将它们合并到Value实例中,这肯定会减少样板。此时,如果您只有一个实例( Value实例),您可以考虑完全摆脱class结构,只需定义toA :: Value -> ValuetoB :: Value -> Value函数。

如果您需要保留所有这些实例,那么我看不出有什么方法可以绕过样板。

  • 实际上并没有为A实现toB这一事实似乎是不干净的。即使永远不应该调用它,我也希望完全避免实现它。

这应该会向你提出一个关于你的总体战略的问题。在许多方面,你已经使事情令人愉快地通用,但还不清楚是什么买下了你。毕竟,如果ImplicitlyConvertible的唯一用例是在valueOp中,那么您真的需要为一个函数创建一个新的类吗?如果不是,也许您应该将实例折叠到valueOp本身的定义中?您可能在定义中仍然有一个error "can't convert A to B",但实际上您将能够证明它从未被调用,不像在当前代码中,任何人都可以使用A值来调用toB

代码语言:javascript
复制
valueOp :: (forall a. Op a => a -> a -> a) -> (Value -> Value -> Value)
valueOp op x y = case (x,y) of
  (A v, v') -> A $ op v (toA v')
  (v, A v') -> A $ op (toA v) v'
  (v, v')   -> B $ op (toB v) (toB v')
  where
    toA (A a) = a
    toA (B b) = sum b
    toA (C c) = sum c
    toB (A a) = error "can't convert A to B"
    toB (B b) = b
    toB (C c) = [fst c, snd c]

或者,如果这种转换是必要的,那么只有在显式可能的情况下才能定义它:

代码语言:javascript
复制
class Convert x y where
  convert :: x -> y

instance Convert Value A where
  convert (A a) = convert a
  convert (B b) = convert b
  convert (C c) = convert c

instance Convert A A where
  convert = id

instance Convert B A where
  convert = sum

instance Convert C A where
  convert = sum

instance Convert B B where
  convert = id

instance Convert C B where
  convert c = [fst c, snd c]


valueOp :: (forall a. Op a => a -> a -> a) -> (Value -> Value -> Value)
valueOp op (A v) v' = A $ op v (convert v')
valueOp op v (A v') = A $ op (convert v) v'
valueOp op (B v) (B v') = B $ op v v'
valueOp op (B v) (C v') = B $ op v (convert v')
valueOp op (C v) (B v') = B $ op (convert v) v'
valueOp op (C v) (C v') = B $ op (convert v) (convert v')

这需要枚举BC的所有可能选项作为valueOp的输入,这是令人沮丧的冗长,但您可以轻松地知道您的所有函数都是总计的。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65529511

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档