如何在Haskell中专门针对IO使用mapM

时间:2018-07-30 00:26:49

标签: haskell monads io-monad

说我有一个任务,它表示从kv的一些计算,其中一些输入必须从外部获取。

newtype Task k v = Task { run ∷ ∀ f. Monad f ⇒ (k → f v) → f v }

对于某些任务,将使用mapM,例如获取多个密钥。我想将mapM专用于某些monad。专门针对IO单子,我想使用Control.Concurrent.Async.mapConcurrently同时执行IO操作。

我的第一个直觉是引入包装类型

newtype AsyncIO a = AsyncIO { io :: IO a }

然后介绍

instance Monad AsyncIO

但是,这不起作用,因为在当前的GHC实现中,mapM是用traverse的{​​{1}}定义的。

是否有一个优雅的解决方案?

2 个答案:

答案 0 :(得分:5)

嗯,from sqlalchemy import Column, Integer, ForeignKey from sqlalchemy.orm import relationship, sessionmaker from sqlalchemy.ext.declarative import declared_attr, declarative_base from sqlalchemy.schema import PrimaryKeyConstraint from sqlalchemy import Sequence, create_engine class Base: @declared_attr def __tablename__(cls): return f"{cls.__name__}" @declared_attr def seq(cls): return Sequence("test_1", start=1, increment=1) @declared_attr def id(cls): return Column(Integer, cls.seq, unique=True, autoincrement=True, primary_key=True) Base = declarative_base(cls=Base) def relate(model, x): """Model is the original class, x is what class needs to be as an attribute for model""" attributeName = x.__tablename__ idAttributeName = "{}Id".format(attributeName) setattr(model, idAttributeName, Column(ForeignKey(x.id))) setattr(model, attributeName, relationship(x, foreign_keys=getattr(model, idAttributeName), primaryjoin=getattr( model, idAttributeName) == x.id, remote_side=x.id ) ) return model.__table__.c[idAttributeName] def possibleSolution(model): if len(model.defined): newPriCols = [] for x in model.defined: newPriCols.append(relate(model, x)) for priCol in model.__table__.primary_key: priCol.primary_key = False priCol.nullable = True model.__table__.primary_key = PrimaryKeyConstraint( *newPriCols # TODO: ADD all the columns that are in the model that are also a primary key # *[col for col in model.__table__.c if col.primary_key] ) class A_Table(Base): pass class B_Table(Base): defined = [A_Table] possibleSolution(B_Table) engine = create_engine('sqlite://') Base.metadata.create_all(bind=engine) Session = sessionmaker(bind=engine) session = Session() a = A_Table() b = B_Table(A_TableId=a.id) print(B_Table.__table__.primary_key) session.add(a) session.commit() session.add(b) session.commit() 只需要一个traverse。通过为Applicative使用备用mapM,可以使Applicative并行执行其操作(这是IO的实现方式)。但是,此mapConcurrently没有合法的Applicative实例:Monad,而其他(>>=)操作将与Monad和其他{{1 }}操作。例如。 (<*>)不等同于Applicative,因为mf >>= \f -> mx >>= \x -> return (f x)不能并行执行其参数,但是mf <*> mx可以并行执行。 (您可能可以使用(>>=)创建一个有效的(<*>)实例,但是Monad也可以。)

您可以做的一件事情是将unsafeInterleaveIO的函子unsafeInterleaveIOTask分开传递,然后提供自然转换以将前者中的所有计算注入到后者。查找功能也应该在Applicative上下文中。

Monad

如果您不涉及任何特殊的Applicative,只需使用newtype Task k v = Task { run ∷ ∀f f'. (Monad f, Applicative f') ⇒ (∀a. f' a → f a) → (k → f' v) → f v } 作为自然转换:

Applicative

对于id,特殊的runSimple ∷ Monad f ⇒ Task k v → (k → f v) → f v runSimple t = run t id 仿函数已经在IO中为您打包好了:

Applicative

您将这样写一个Control.Concurrent.Async.Concurrently

runWithParallelIO ∷ Task k v → (k → IO v) → IO v
runWithParallelIO t lookup = run t runConcurrently (Concurrently . lookup)

如果您发现自己编写的Task并不能从单独的task ∷ Task _k _v task = Task go where go exec lookup = do _ xs <- exec $ mapM lookup _ _ Task上下文中受益,则可以使用此智能构造函数

Monad

避免将Applicative中的每个taskSimple ∷ (∀f. Monad f ⇒ (k → f v) → f v) → Task k v taskSimple r = Task (\exec lookup -> r (exec . lookup)) 包装起来。 AFAICT,lookupexec是幂等的。

答案 1 :(得分:0)

尽管我不确定后者是否会与f的量化相冲突,但可以将mapMmapConcurrently作为附加参数,或者尽可能少地将其用作隐式参数。