每个重复嵌套的monad有用吗?

时间:2020-05-19 12:17:50

标签: haskell io monads

按标题,我的意思是像Monad m => m (m a)这样的类型。

当monad的结构很简单时,我很容易想到这种用法:

  • [[a]],这是一个多维列表

  • Maybe (Maybe a),这是一个伴随两个错误状态的类型

  • Either e (Either e a),与上面类似,但带有消息

  • Monoid m => (m,(m,a)),这是一个作家monad,有两件事要重写

  • r -> r -> a,这是一个读者monad,有两件事可供阅读

  • Identity (Identity a),它仍然是monad身份

  • Complex (Complex a),它是2 x 2矩阵

但是,如果我想到以下几种类型,这在我脑海中是一团糟:

  • ReadP (ReadP a)?当ReadP不是Read的实例时为什么有用?

  • ReadPrec (ReadPrec a)?像上面一样?

  • Monad m => Kleisli m a (Kleisli m a b)

  • IO (IO a) !? 必须有用。很难考虑。

  • forall s. ST s (ST s a) !?就像上面一样。

这类类型是否有实际用途?尤其是对于IO人?

第二个想法,我可能需要随机选择一个IO动作。那是IO (IO a)的例子,它专注于输入。那么专注于产出呢?

2 个答案:

答案 0 :(得分:2)

从某种意义上说,单子可以看作是函子,其中的层可以折叠。

如果const highchartsExporter = require('highcharts-export-server'); let promiseId = 0; exports.generateAllCharts = (chartData, callback) => { let allPromises = []; let chartsLen = chartData.length; highchartsExporter.logLevel(4); highchartsExporter.initPool({ maxWorkers: 100, initialWorkers: 50, workLimit: 100, queueSize: 50, timeoutThreshold: 10000 }); if (!chartData || !chartsLen) { highchartsExporter.killPool(); return callback({ code: '4', msg: 'Please send chartdata' }); } for (let i = 0; i < chartsLen; i++) { allPromises.push( new Promise((resolve, reject) => { exports.getPieChartImg(chartData[i], false, results => { if (results.code !== '0') { return reject(results); } return resolve(results); }); }) ); } Promise.all(allPromises) .then(data => { highchartsExporter.killPool(); let imagesObject = { code: '0', custImg: {} }; data.forEach((image, index) => { imagesObject.custImg['pc' + (index + 1)] = image.data; imagesObject.custImg.promiseId = image.promiseId; }); return callback(imagesObject); }) .catch(err => callback({ code: '5', msg: 'Error generating charts', err })); }; exports.getPieChartImg = (seriesData, xOrLength, cb) => { let chartOpts = { colors: ['#7380D4', '#749FD4', '#74BFD4', '#74D4B6', '#99EBA8', '#FEE08B', '#FDAE61', '#F07346', '#E65433', '#C92D22'], chart: { plotBackgroundColor: null, plotBorderWidth: null, plotShadow: false, renderTo: 'container', style: { fontSize: '20px', background: '#fffdcc' }, width: 650, }, credits: { enabled: false }, title: { text: null, }, tooltip: { pointFormat: '{series.name}: {point.percentage:.1f}%' }, legend: { itemStyle: { font: 'sans-serif', fontWeight: 'bold', fontSize: '13px' }, useHTML: true, layout: 'vertical', align: 'right', verticalAlign: 'middle', labelFormatter: () => { if (this.name[xOrLength] > 9) { let words = this.name.split(/[\s]+/); let numWordsPerLine = 1; let str = []; for (let word in words) { if (parseInt(word) > 0 && parseInt(word) % numWordsPerLine == 0) str.push('<br>'); str.push(words[word]); } let label = str.join(' '); // Make legend text bold and red if most recent value is less than prior if (this.name[1] > this.name[2]) { return '<span style="font-weight:bold">' + label + '</span>'; } else { return label; } } else { return this.name; } } }, plotOptions: { pie: { size: '85%', allowPointSelect: true, cursor: 'pointer', showInLegend: true, dataLabels: { enabled: true, allowOverlap: false, distance: 10, formatter: () => { return undefined; // if (parseFloat(this.percentage.toFixed(2)) > 0.35) { // return '' + parseFloat(this.percentage).toFixed(2) + '%'; // } }, padding: 5, style: { fontFamily: '\'Lato\', sans-serif', // lineHeight: '18px', fontWeight: 'normal', fontSize: '18px' } } }, series: { stacking: 'normal', dataLabels: { enabled: true, color: '#6f6f6f', style: { fontFamily: '\'Lato\', sans-serif', // lineHeight: '18px', fontWeight: 'normal', fontSize: '18px' }, format: '{point.percentage:.2f}' }, pointWidth: 30, cursor: 'pointer' } }, series: [{ name: "Value", type: 'pie', data: seriesData }], navigation: { buttonOptions: { enabled: false } }, }; let exportSettings = generateExportSettings(chartOpts, 'Stock'); return generateBase64Chart(exportSettings, 3, cb); }; function generateExportSettings(chartOpts, constr) { return { type: 'png', constr, b64: true, // async: false, noDownload: true, scale: 2, options: chartOpts, globalOptions: { colors: ['#7380D4', '#749FD4', '#74BFD4', '#74D4B6', '#99EBA8', '#FEE08B', '#FDAE61', '#F07346', '#E65433', '#C92D22'], lang: { thousandsSep: ',' } } }; } function generateBase64Chart(exportSettings, number, cb) { // Perform an export highchartsExporter.export(exportSettings, function(err, res) { // The export result is now in res. // If the output is not PDF or SVG, it will be base64 encoded (res.data). // If the output is a PDF or SVG, it will contain a filename (res.filename). if (err) { return cb({ code: '1', msg: 'Error in stock chart', err, exportSettings }); } promiseId++; return cb({ code: '0', msg: 'Success', promiseId: promiseId, data: 'data:image/png;base64,' + res.data, }); // Kill the pool when we're done with it, and exit the application // highchartsExporter.killPool(); // process.exit(1); }); } 类的定义更像是类别理论的定义,则看起来像

Monad

class Applicative m => Monad m where return :: a -> m a join :: m (m a) -> m a 与类型为fmap的函数一起使用会导致类型为a -> m b的函数。 m a -> m (m b)用于从结果中消除一层monad。由于这是很常见的事情,因此可以定义一个函数来执行此操作。

join

如果仔细看,您会发现foo :: Monad m => (a -> m b) -> m a -> m b foo f ma = join (fmap f ma) foo,并且其参数已翻转。

>>=

由于foo = flip (>>=) 的使用量超过了>>=的使用,因此类型类定义为

join

class Applicative m => Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b 被定义为单独的函数

join

答案 1 :(得分:1)

没关系。

Monad之所以是Monad,是因为对于每个Monad ( Monad a ),我们总能得到Monad a。这种操作称为“连接”,它是“绑定”的替代操作,可以形成Monad的定义。 Haskell之所以使用“绑定”,是因为它在编写单子代码时非常有用:) (join可以使用bind来实现,并可以通过join进行绑定-它们是等效的)

没关系

实际上是个小谎言,因为形成Monad ( Monad a )的能力实际上也是使Monads成为monads的一部分。在某些操作中,Monad (Monad a)是过渡表示。

完整答案是:是的,因为这样可以启用Monads。尽管Monad ( Monad a )在列出某些Monad时可以具有额外的“域”含义;)