我最近考试不及格,主要是因为一个电子EDSL问题。我没有理解这些概念,所以我想这就是我失败的原因。我想我的老师的解释是为了让我理解抽象,所以我想知道是否有人能更清楚地解释它。
我想知道是否有人能简要解释一下EDSL的组件是什么,以及它们的特点是什么。在我们的过程中,我们经历了DSL的浅埋和深嵌入,并查看了DSL的以下构建块:
我认为构造函数和运行函数是更不言自明的,所以我更感兴趣的是,是什么使组合子派生或原语。如果有人会解释其他的概念,那也没什么坏处。以下是我们讲座中的一个例子,供大家参考。它是用于创建信号的DSL的一种浅表实现:
module Signal.Shallow
( Time
-- | the 'Signal' type is abstract
, Signal
-- * Smart constructors
, constS, timeS
-- * Combinators
, ($$), mapT
-- * Derived operation
, mapS
-- * Run function
, sample
) where
-- * Smart constructors
constS :: a -> Signal a
timeS :: Signal Time
-- * Combinators
($$) :: Signal (a -> b) -> Signal a -> Signal b
mapT :: (Time -> Time) -> Signal a -> Signal a
-- * Derived operation
mapS :: (a -> b) -> Signal a -> Signal b
-- * Run function
sample :: Signal a -> Time -> a
type Time = Double
newtype Signal a = Sig {unSig :: Time -> a}
-- | The constant signal.
constS x = Sig (const x)
-- | The time signal
timeS = Sig id
-- | Function application lifted to signals.
fs $$ xs = Sig (\t -> unSig fs t (unSig xs t))发布于 2014-08-31 17:02:38
原语组合器是在DSL中内置的,用基本语言(即Haskell)定义的。DSL通常是围绕抽象类型-a类型构建的,其实现对最终用户是隐藏的。完全不透明。由语言表示的原始组合器需要知道抽象是如何实现的。
另一方面,派生组合子可以在DSL中的其他组合子中实现。它不需要知道任何关于抽象类型的信息。换句话说,派生组合器是您可以自己编写的。
这与Haskell本身中的原始类型的概念非常相似。例如,您不能自己实现Int或Int操作(如+ )。这些都需要内置到编译器中的东西才能工作,因为数字是特殊处理的。另一方面,Bool并不是原始的;您可以将它写成一个库。
data Bool = True | False -- ... you can't do this for Int!除了编译器实际上是您的Haskell库之外,DSLs的“原语”和“派生”都是相同的概念。
在您的示例中,Signal是一种抽象类型。它是作为一个函数Time -> a实现的,但是该信息并不是从模块导出的。将来,您(作为DSL的作者)可以自由地更改Signal的实现方式。(事实上,您可能真的想:这不是一个有效的表示,而且使用Double的时间是非常困难的。)
像$$这样的函数是原始的,因为它依赖于知道Signal是Time -> a。当您更改Signal的表示时,您将不得不重写$$。此外,库的用户将无法实现$$本身。
另一方面,mapS是一个派生的操作,因为它可以完全按照导出的其他内容编写。它不需要了解任何关于Signal的特殊信息,甚至可以由库的一个用户编写。该实现可以类似于:
mapS f signal = constS f $$ signal请注意它是如何使用constS和$$的,但永远不要打开signal。如何解包信号的知识完全隐藏在这两个函数中。mapS是“派生的”,因为它只是在DSL中编写的,而不需要任何低于抽象级别的东西。当您更改Signal的实现时,mapS仍然可以正常工作:您只需要正确地更新constS和$$,就可以免费获得mapS。
因此:原语组合器是直接用您的语言构建的,需要知道它的内部实现细节。派生组合子纯粹是用您的语言编写的,不依赖于任何这些内部细节。它们只是一些方便的函数,可以很容易地由库的最终用户编写。
https://stackoverflow.com/questions/25593992
复制相似问题