【问题标题】:Haskell: Applicative over tuplesHaskell:应用于元组
【发布时间】:2016-04-28 22:54:59
【问题描述】:

我有问题。我有一个数据类型 Thing,它本质上是 Maybe Doubles 或 Maybe Texts 的大记录。我想创建一个数据类型,即使某些字段可能是 Nothing,也可以捕捉在其上运行计算的想法。

data Computation a b = Computation
    { getInputs :: Thing -> a
      computation :: a -> b
      modifyThing :: b -> Thing -> Thing
    }

基于输出 b,它很可能对应于事物中的某些字段,我想创建一个新事物。

modifyThing :: b -> Thing -> Thing

从 Thing 到 Maybe b 的计算可以分为两部分,加载变量和只接受数字或文本的计算。

getInputs :: Thing -> a

computation :: a -> b

以上几乎就是我想要的。在这种情况下,a 将是 (Maybe a1, Maybe a2..) 等等。这意味着在“计算”和 getInputs 我必须做类似的事情

getInputs t = \t -> ( getProp1 t , getProp2 t)

computation = \(m_a1, m_a2) -> do
    a1 <- m_a1
    a2 <- m_a2
    return $ a1 + a2

我宁愿让计算看起来像

exampleComp a1 a2 = a1 + a2

然后做类似的事情

runComputation :: Thing -> Computation -> b
runComputation thing comp = magic (computation comp) ((getInputs comp) thing)
  where magic = ???

问题是我不知道该怎么做

(Maybe a1, Maybe a2, ... , Maybe a_n)

a1 -> a2 -> ... -> a_n

如果任何 Maybe 是 Nothing,则返回 Nothing。我可以的

pure computation <*> m_a1 <*> m_a2 <*> m_a3

但是我怎样才能为任何类型的元组编写魔法呢?

附言

我正在考虑将计算写成

computation :: Thing -> b

并取消 getInputs,但它似乎会更加笨拙 供我测试和玩耍。这就是为什么我试图采用我上面描述的方法。你认为这是一个好主意吗?

已编辑

虽然不是针对我提出的特定问题的解决方案,但出于我想要做的事情的意图,我决定这样做

data Computation = Computation
    { getInputs :: Thing -> Maybe a
    , computation :: a -> b
    , modifyThing :: b -> Thing -> Thing
    }

将是前进的最佳方式。这样我就不用担心元组了。

【问题讨论】:

  • 你有什么理由不能使用可能的列表吗?
  • @Guvante 列表的一个缺点是它们是同质的——例如无法同时选择Double-y 类型字段和Int-y 类型字段。
  • @user3550758 如果您找到解决方案,StackOverflow 的方法是将其写为答案,而不是将其包含在问题的编辑中。我鼓励你将你的“编辑”拆分成它自己的实体——如果它是你得到的最佳答案,甚至接受它。 (这在这里被认为是非常有礼貌甚至是可取的。)

标签: haskell


【解决方案1】:

问题是我不知道该怎么做

(Maybe a1, Maybe a2, ... , Maybe a_n)

a1 -> a2 -> ... -> a_n

看起来类似于uncurry,它接受一个普通函数并创建一个接受元组而不是多个参数的版本。

uncurry :: (a -> b -> c) -> (a, b) -> c
uncurry f (a, b) = f a b

这个特殊的例子:

computation = \(m_a1, m_a2) -> do
    a1 <- m_a1
    a2 <- m_a2
    return $ a1 + a2

可以像这样使用 Applicative 来完成

computation (m_a1, m_a2) = (+) <$> m_a1 <*> m_a2

但您可以将其抽象为 uncurry 之类的东西:

uncurryA :: Applicative f => (a -> b -> c) -> (f a, f b) -> f c
uncurryA f (a, b) = f <$> a <*> b

允许像这样定义computation

computation a_b = uncurryA (+) a_b

【讨论】:

  • uncurry 仅适用于 2 参数函数。您可能会执行某种递归取消柯里化(例如,因为 uncurry . uncurry 可以将 a -&gt; a -&gt; a -&gt; b 转换为 ((a, a), a) -&gt; b),但您仍然需要弄清楚如何展平生成的嵌套元组。
  • (或者更准确地说,uncurry 一次只处理前两个参数,如果连续多次使用uncurry,则会导致嵌套元组。)
【解决方案2】:

我不完全确定使用Computation 而不是Thing -&gt; Thing 有什么好处,但您应该能够相当容易地定义相关函数。首先,您的示例函数computation 可以重写为更方便的形式。 (您实际上应该将其称为其他名称,因为名称 computation 已被您的记录访问者占用)。

exampleComp :: Num a => (Maybe a, Maybe a) -> Maybe a
exampleComp (a1, a2) = liftA2 (+) a1 a2

它仍然不如简单的添加那么方便,但它已经接近了。您的 runComputation 函数也非常简单,因为它实际上只是组合了您的 Computation 类型的三个组件。

runComputation :: Thing -> Computation -> Thing
runComputation thing (Computation ins comp modify) = modify (comp $ ins thing) thing

或者,用记录访问器编写,它看起来像这样。

runComputation thing comp = modifyThing comp 
                            (computation comp $ getInputs comp thing) 
                            thing

基本上,此函数使用getInputs 提取适当的数据,将该数据传递给computation 以获取一组新数据,然后将that 传递给modifyThing 以实际修改对象.

如果没有更多关于您计划对数据类型做什么的详细信息,很难说您应该使用哪种方法。就个人而言,我看不出有任何理由将computationmodifyThing 分开,但我也看不出有任何理由首先使用Computation。无论如何,这对于 CodeReview 来说更像是一个问题,而不是 StackOverflow。

【讨论】:

    猜你喜欢
    • 2014-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多