我正在将History.js中的绑定(bind)写到PureScript中,但仍在努力了解Eff monad,一行效果是什么以及它们为什么有值(value)的原因。现在,我用EasyFFI
编写了以下内容
type Title = String
type Url = String
type State = forall a. {title :: Title, url :: Url | a}
type Data = forall a. { | a}
type StateUpdater = Data -> Title -> Url -> Unit
-- this function is a work around for 'data' as a reserved word.
foreign import getData
"function getData(state){ return state['data']; }"
:: forall a b. { | a} -> b
unwrapState :: StateUpdater -> State -> Unit
unwrapState f s = f (getData s) s.title s.url
replaceState' :: StateUpdater
replaceState' = unsafeForeignProcedure ["data","title","url"] "History.replaceState(data,title,url)"
replaceState :: State -> Unit
replaceState = unwrapState replaceState'
foreign import data BeforeEach :: !
beforeEach :: forall e a. Eff e a -> Eff (beforeEach :: BeforeEach | e) Unit
beforeEach = unsafeForeignProcedure ["fn",""] "window.beforeEach(fn);"
在代码的后面,我有以下内容:
beforeEach $ do
replaceState {title = "wowzers!", url = "/foos"}
并得到以下错误
Cannot unify Prelude.Unit with Control.Monad.Eff.Eff u2518 u2517.
我试图以各种方式操纵类型签名以使其全部对齐,但是我真的不明白出了什么问题。因此,它只是在这一点上的猜测。
最佳答案
我对代码的修改后的版本位于本文的结尾,但是为了使其能够编译,我必须进行一些修改:
我以为您的StateUpdater
会对浏览器历史记录产生影响,因此我更改了它的类型,以将Eff
monad与新的History
效果类型一起使用。这是主要问题,因为您的最后一行使用了replaceState
,其结果类型不在Eff
monad中。这导致您看到类型错误。
我将类型同义词中的一些通用量化的类型变量移到了函数类型中。我还删除了Data
类型的同义词,并将数据内容移到data
类型的新State
字段中。
这很重要,因为您以前的Data
类型没有居民。有一个常见的误解(出于我不明白的原因),forall a. { | a}
是一种记录类型,“我不在乎字段”。这是不正确的-这种类型表示记录的类型,其中包含可能存在的所有字段,并且这种类型显然是无人居住的。从调用者的角度来看,forall a. {| a} -> r
和(forall a. {| a}) -> r
之间存在重要区别。
在回答您最初的问题时:“什么是行效应,它们为什么有用?” -最初将行添加到PureScript以处理记录类型上的多态性,而不必求助于子类型化。通过行,我们可以为使用记录的函数提供多态类型,这样我们就可以将“记录的其余部分”捕获为类型系统中的概念。
在处理效果时,行也被证明是一个有用的概念。就像我们不在乎记录的其余部分是什么一样,只要所有影响在类型系统中正确传播,我们通常就不在乎一组效果的其余部分是什么样。
举个例子,修改后的代码涉及两个效果:History
和您的BeforeEach
。 Action beforeEach
和replaceState
每个仅使用这些效果之一,但是它们的返回类型是多态的。这使得main
中的两个功能组合在一起具有两种效果,并且可以正确键入。 main
的类型为forall eff. Eff (history :: History, beforeEach :: BeforeEach | eff) {}
,这是最通用的类型,由类型检查器推断。
简而言之,效果系统中的行提供了一种巧妙的方式来处理各种“ native ”效果的交织,因此开发人员不必担心效果的顺序或lift
编码计算àmtl
之类的事情。
module Main where
import EasyFFI
import Control.Monad.Eff
type Title = String
type Url = String
type State d = { title :: Title, url :: Url, "data" :: { | d } }
type StateUpdater d = forall eff. { | d } -> Title -> Url -> Eff (history :: History | eff) {}
foreign import data History :: !
unwrapState :: forall eff d. StateUpdater d -> State d -> Eff (history :: History | eff) {}
unwrapState f s = f s."data" s.title s.url
replaceState' :: forall d. StateUpdater d
replaceState' = unsafeForeignProcedure ["d","title","url"] "History.replaceState(d,title,url)"
replaceState :: forall eff d. State d -> Eff (history :: History | eff) {}
replaceState = unwrapState replaceState'
foreign import data BeforeEach :: !
beforeEach :: forall e a. Eff e a -> Eff (beforeEach :: BeforeEach | e) {}
beforeEach = unsafeForeignProcedure ["fn",""] "window.beforeEach(fn);"
main = beforeEach $ do
replaceState { title: "wowzers!", url: "/foos", "data": {} }