5
\$\begingroup\$

I'm building an RSS feed parser so, later, I can retrieve recent blog posts on my word press blog and display recent posts on my other website. This code isn't intended to implement the full RSS spec. It just needs to handle the feed that Wordpress provides.

I wasn't able to leverage System.Xml.Linq because I wasn't able to get a reference to it on .Net Core.

Sample Input:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
    xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
    xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
    >

<channel>
    <title>Christopher J. McClellan</title>
    <atom:link href="https://christopherjmcclellan.wordpress.com/feed/" rel="self" type="application/rss+xml" />
    <link>https://christopherjmcclellan.wordpress.com</link>
    <description>Visual Basic, C#, and the Art of Being a Modern Man</description>
    <lastBuildDate>Thu, 27 Jul 2017 22:12:44 +0000</lastBuildDate>
    <language>en</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <generator>http://wordpress.com/</generator>
    <cloud domain='christopherjmcclellan.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
    <image>
        <url>https://s2.wp.com/i/buttonw-com.png</url>
        <title>Christopher J. McClellan</title>
        <link>https://christopherjmcclellan.wordpress.com</link>
    </image>
    <atom:link rel="search" type="application/opensearchdescription+xml" href="https://christopherjmcclellan.wordpress.com/osd.xml" title="Christopher J. McClellan" />
    <atom:link rel='hub' href='https://christopherjmcclellan.wordpress.com/?pushpress=hub'/>
    <item>
        <title>How to See Memory and CPU Usage for All Your Docker Containers on CentOS 6</title>
        <link>https://christopherjmcclellan.wordpress.com/2017/07/22/docker-container-memory-usage/</link>
        <comments>https://christopherjmcclellan.wordpress.com/2017/07/22/docker-container-memory-usage/#comments</comments>
        <pubDate>Sat, 22 Jul 2017 11:40:25 +0000</pubDate>
        <dc:creator><![CDATA[Christopher J. McClellan]]></dc:creator>
        <category><![CDATA[DevOps]]></category>
        <category><![CDATA[Programming]]></category>
        <category><![CDATA[Docker]]></category>

        <guid isPermaLink="false">http://christopherjmcclellan.wordpress.com/?p=2075</guid>
        <description><![CDATA[I run a bunch of Docker containers on a single CentOS 6 server with a limited amount of memory. (I only recently bumped it from 0.5 to 1 whole whopping gig!) Before I bring another container online, I like to check to see how much room I&#8217;ve got. Being the newest versions of Docker aren&#8217;t [&#8230;]<img alt="" border="0" src="https://pixel.wp.com/b.gif?host=christopherjmcclellan.wordpress.com&#038;blog=59673389&#038;post=2075&#038;subd=christopherjmcclellan&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
        <wfw:commentRss>https://christopherjmcclellan.wordpress.com/2017/07/22/docker-container-memory-usage/feed/</wfw:commentRss>
        <slash:comments>3</slash:comments>

        <media:content url="http://0.gravatar.com/avatar/0b43c27212304872e59ec6c73e1e30e7?s=96&#38;d=identicon&#38;r=G" medium="image">
            <media:title type="html">ckuhn203</media:title>
        </media:content>
    </item>
        <item>
        <title>Go away. It will all be waiting for you when you return. </title>
        <link>https://christopherjmcclellan.wordpress.com/2017/06/10/go-away-it-will-all-be-waiting-for-you-when-you-return/</link>
        <comments>https://christopherjmcclellan.wordpress.com/2017/06/10/go-away-it-will-all-be-waiting-for-you-when-you-return/#respond</comments>
        <pubDate>Sat, 10 Jun 2017 12:41:57 +0000</pubDate>
        <dc:creator><![CDATA[Christopher J. McClellan]]></dc:creator>
                <category><![CDATA[Life]]></category>
        <category><![CDATA[Programming]]></category>
        <category><![CDATA[work life balance]]></category>

        <guid isPermaLink="false">http://christopherjmcclellan.wordpress.com/2017/06/10/go-away-it-will-all-be-waiting-for-you-when-you-return/</guid>
        <description><![CDATA[I realize that my posts here have become a bit infrequent and erratic. If you&#8217;re a frequent reader, you know that I changed jobs early this year. I didn&#8217;t just change jobs though; I completely swapped stacks. Learning a dozen technologies &#8220;overnight&#8221; has taken up a lot of the time that I previously dedicated to [&#8230;]<img alt="" border="0" src="https://pixel.wp.com/b.gif?host=christopherjmcclellan.wordpress.com&#038;blog=59673389&#038;post=2059&#038;subd=christopherjmcclellan&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
        <wfw:commentRss>https://christopherjmcclellan.wordpress.com/2017/06/10/go-away-it-will-all-be-waiting-for-you-when-you-return/feed/</wfw:commentRss>
        <slash:comments>0</slash:comments>

        <media:content url="http://0.gravatar.com/avatar/0b43c27212304872e59ec6c73e1e30e7?s=96&#38;d=identicon&#38;r=G" medium="image">
            <media:title type="html">ckuhn203</media:title>
        </media:content>
    </item>
        <item>
        <title>Pretend Your Users Sat Next to You Everyday</title>
        <link>https://christopherjmcclellan.wordpress.com/2017/05/13/pretend-your-users-sat-next-to-you-everyday/</link>
        <comments>https://christopherjmcclellan.wordpress.com/2017/05/13/pretend-your-users-sat-next-to-you-everyday/#respond</comments>
        <pubDate>Sat, 13 May 2017 00:01:40 +0000</pubDate>
        <dc:creator><![CDATA[Christopher J. McClellan]]></dc:creator>
                <category><![CDATA[Programming]]></category>

        <guid isPermaLink="false">http://christopherjmcclellan.wordpress.com/2017/05/13/pretend-your-users-sat-next-to-you-everyday/</guid>
        <description><![CDATA[I&#8217;m very much a &#8220;The tests pass. Ship it.&#8221; kind of guy. I like to fail fast and have long believed that the best way to find places where we missed a requirement or made a mistake is to get it into the hands of real users as quickly as possible. Recently however, the users [&#8230;]<img alt="" border="0" src="https://pixel.wp.com/b.gif?host=christopherjmcclellan.wordpress.com&#038;blog=59673389&#038;post=2058&#038;subd=christopherjmcclellan&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
        <wfw:commentRss>https://christopherjmcclellan.wordpress.com/2017/05/13/pretend-your-users-sat-next-to-you-everyday/feed/</wfw:commentRss>
        <slash:comments>0</slash:comments>

        <media:content url="http://0.gravatar.com/avatar/0b43c27212304872e59ec6c73e1e30e7?s=96&#38;d=identicon&#38;r=G" medium="image">
            <media:title type="html">ckuhn203</media:title>
        </media:content>
    </item>
        <item>
        <title>Two Quick Tips for a Better Code Review Experience</title>
        <link>https://christopherjmcclellan.wordpress.com/2017/04/30/two-quick-tips-for-a-better-code-review-experience/</link>
        <comments>https://christopherjmcclellan.wordpress.com/2017/04/30/two-quick-tips-for-a-better-code-review-experience/#respond</comments>
        <pubDate>Sun, 30 Apr 2017 11:18:46 +0000</pubDate>
        <dc:creator><![CDATA[Christopher J. McClellan]]></dc:creator>
                <category><![CDATA[Programming]]></category>
        <category><![CDATA[code review]]></category>

        <guid isPermaLink="false">http://christopherjmcclellan.wordpress.com/?p=2056</guid>
        <description><![CDATA[Let&#8217;s talk about Code Reviews for a minute. They&#8217;re important; they spread knowledge around and catch bugs [citation needed], but they can be frustrating some times. They don&#8217;t need to be though. There are a few things we can do to keep the code review a pleasant experience.&#160; Review the code, not the coder.&#160; Keep [&#8230;]<img alt="" border="0" src="https://pixel.wp.com/b.gif?host=christopherjmcclellan.wordpress.com&#038;blog=59673389&#038;post=2056&#038;subd=christopherjmcclellan&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
        <wfw:commentRss>https://christopherjmcclellan.wordpress.com/2017/04/30/two-quick-tips-for-a-better-code-review-experience/feed/</wfw:commentRss>
        <slash:comments>0</slash:comments>

        <media:content url="http://0.gravatar.com/avatar/0b43c27212304872e59ec6c73e1e30e7?s=96&#38;d=identicon&#38;r=G" medium="image">
            <media:title type="html">ckuhn203</media:title>
        </media:content>
    </item>
        <item>
        <title>Tests tell stories, listen to what they say</title>
        <link>https://christopherjmcclellan.wordpress.com/2017/03/20/tests-tell-stories-listen-to-what-they-say/</link>
        <comments>https://christopherjmcclellan.wordpress.com/2017/03/20/tests-tell-stories-listen-to-what-they-say/#respond</comments>
        <pubDate>Mon, 20 Mar 2017 09:13:34 +0000</pubDate>
        <dc:creator><![CDATA[Christopher J. McClellan]]></dc:creator>
                <category><![CDATA[Programming]]></category>
        <category><![CDATA[clean architecture]]></category>
        <category><![CDATA[integration tests]]></category>
        <category><![CDATA[testing]]></category>

        <guid isPermaLink="false">http://christopherjmcclellan.wordpress.com/?p=2052</guid>
        <description><![CDATA[I&#8217;m not a big fan of integration tests. They&#8217;re often unreliable and &#8220;flaky&#8221; due to their very nature of being integration&#160;tests that rely on file systems, networks, and databases. These kinds of tests are hard to get right. At least, it&#8217;s hard to get them stable enough to be valuable and I often wonder if [&#8230;]<img alt="" border="0" src="https://pixel.wp.com/b.gif?host=christopherjmcclellan.wordpress.com&#038;blog=59673389&#038;post=2052&#038;subd=christopherjmcclellan&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
        <wfw:commentRss>https://christopherjmcclellan.wordpress.com/2017/03/20/tests-tell-stories-listen-to-what-they-say/feed/</wfw:commentRss>
        <slash:comments>0</slash:comments>

        <media:content url="http://0.gravatar.com/avatar/0b43c27212304872e59ec6c73e1e30e7?s=96&#38;d=identicon&#38;r=G" medium="image">
            <media:title type="html">ckuhn203</media:title>
        </media:content>
    </item>
        <item>
        <title>Teaching an Old Dog New Tricks</title>
        <link>https://christopherjmcclellan.wordpress.com/2017/03/04/teaching-an-old-dog-new-tricks/</link>
        <comments>https://christopherjmcclellan.wordpress.com/2017/03/04/teaching-an-old-dog-new-tricks/#respond</comments>
        <pubDate>Sat, 04 Mar 2017 13:12:41 +0000</pubDate>
        <dc:creator><![CDATA[Christopher J. McClellan]]></dc:creator>
                <category><![CDATA[agile]]></category>
        <category><![CDATA[Programming]]></category>
        <category><![CDATA[tdd]]></category>
        <category><![CDATA[pair programming]]></category>
        <category><![CDATA[productivity]]></category>
        <category><![CDATA[software development]]></category>
        <category><![CDATA[test driven development]]></category>
        <category><![CDATA[unit testing]]></category>

        <guid isPermaLink="false">http://christopherjmcclellan.wordpress.com/?p=1948</guid>
        <description><![CDATA[I had a really amazing experience this week. I had an opportunity to pair with a gentleman who has been programming since I was in diapers. He had only ever test driven code once before, but is really excited about learning. I think that in and of itself is amazing, but I learned a few [&#8230;]<img alt="" border="0" src="https://pixel.wp.com/b.gif?host=christopherjmcclellan.wordpress.com&#038;blog=59673389&#038;post=1948&#038;subd=christopherjmcclellan&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
        <wfw:commentRss>https://christopherjmcclellan.wordpress.com/2017/03/04/teaching-an-old-dog-new-tricks/feed/</wfw:commentRss>
        <slash:comments>0</slash:comments>

        <media:content url="http://0.gravatar.com/avatar/0b43c27212304872e59ec6c73e1e30e7?s=96&#38;d=identicon&#38;r=G" medium="image">
            <media:title type="html">ckuhn203</media:title>
        </media:content>
    </item>
        <item>
        <title>The Bottleneck</title>
        <link>https://christopherjmcclellan.wordpress.com/2017/01/22/the-bottleneck/</link>
        <comments>https://christopherjmcclellan.wordpress.com/2017/01/22/the-bottleneck/#respond</comments>
        <pubDate>Sun, 22 Jan 2017 14:39:09 +0000</pubDate>
        <dc:creator><![CDATA[Christopher J. McClellan]]></dc:creator>
                <category><![CDATA[Programming]]></category>

        <guid isPermaLink="false">http://christopherjmcclellan.wordpress.com/?p=1891</guid>
        <description><![CDATA[The monk Ahiru was pairing with the bright young apprentice, Taipisuto. The apprentice was quite impressed with his customized editor and the speed at which he could type. Ahiru was not nearly as impressed. The screen was flickering in a way that made him more than just a bit nauseous. &#8220;And what shall you do when [&#8230;]<img alt="" border="0" src="https://pixel.wp.com/b.gif?host=christopherjmcclellan.wordpress.com&#038;blog=59673389&#038;post=1891&#038;subd=christopherjmcclellan&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
        <wfw:commentRss>https://christopherjmcclellan.wordpress.com/2017/01/22/the-bottleneck/feed/</wfw:commentRss>
        <slash:comments>0</slash:comments>

        <media:content url="http://0.gravatar.com/avatar/0b43c27212304872e59ec6c73e1e30e7?s=96&#38;d=identicon&#38;r=G" medium="image">
            <media:title type="html">ckuhn203</media:title>
        </media:content>
    </item>
        <item>
        <title>Doing what&#8217;s best, even when it sucks</title>
        <link>https://christopherjmcclellan.wordpress.com/2017/01/09/doing-whats-best-even-when-it-sucks/</link>
        <comments>https://christopherjmcclellan.wordpress.com/2017/01/09/doing-whats-best-even-when-it-sucks/#comments</comments>
        <pubDate>Mon, 09 Jan 2017 14:01:55 +0000</pubDate>
        <dc:creator><![CDATA[Christopher J. McClellan]]></dc:creator>
                <category><![CDATA[agile]]></category>
        <category><![CDATA[Programming]]></category>

        <guid isPermaLink="false">http://christopherjmcclellan.wordpress.com/?p=1795</guid>
        <description><![CDATA[About 8 months ago, I promised you that I&#8217;d follow up and let you know how our transition to Agile software development was going. This is that long overdue follow up and I&#8217;m sad to say that six weeks ago I submitted my resignation. Tomorrow I start with my new company. I&#8217;m sitting in an airport [&#8230;]<img alt="" border="0" src="https://pixel.wp.com/b.gif?host=christopherjmcclellan.wordpress.com&#038;blog=59673389&#038;post=1795&#038;subd=christopherjmcclellan&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
        <wfw:commentRss>https://christopherjmcclellan.wordpress.com/2017/01/09/doing-whats-best-even-when-it-sucks/feed/</wfw:commentRss>
        <slash:comments>4</slash:comments>

        <media:thumbnail url="https://christopherjmcclellan.files.wordpress.com/2017/01/humanity.jpeg" />
        <media:content url="http://christopherjmcclellan.files.wordpress.com/2017/01/humanity.jpeg" medium="image">
            <media:title type="html">humanity</media:title>
        </media:content>

        <media:content url="http://0.gravatar.com/avatar/0b43c27212304872e59ec6c73e1e30e7?s=96&#38;d=identicon&#38;r=G" medium="image">
            <media:title type="html">ckuhn203</media:title>
        </media:content>

        <media:content url="http://christopherjmcclellan.files.wordpress.com/2017/01/humanity.jpeg?w=1536" medium="image">
            <media:title type="html">humanity</media:title>
        </media:content>
    </item>
        <item>
        <title>In Depth on my first NodeJs / Express Stack</title>
        <link>https://christopherjmcclellan.wordpress.com/2016/12/04/in-depth-on-my-first-nodejs-express-stack/</link>
        <comments>https://christopherjmcclellan.wordpress.com/2016/12/04/in-depth-on-my-first-nodejs-express-stack/#respond</comments>
        <pubDate>Sun, 04 Dec 2016 15:39:54 +0000</pubDate>
        <dc:creator><![CDATA[Christopher J. McClellan]]></dc:creator>
                <category><![CDATA[NodeJs]]></category>
        <category><![CDATA[Programming]]></category>
        <category><![CDATA[chai]]></category>
        <category><![CDATA[express]]></category>
        <category><![CDATA[handlebars]]></category>
        <category><![CDATA[javascript]]></category>
        <category><![CDATA[mocha]]></category>
        <category><![CDATA[node]]></category>
        <category><![CDATA[unit testing]]></category>
        <category><![CDATA[unittesting]]></category>

        <guid isPermaLink="false">http://christopherjmcclellan.wordpress.com/?p=1127</guid>
        <description><![CDATA[The whole goal of my hobby site is to iteratively create the simplest website that could possibly work using all &#8220;new to me&#8221; stacks. Getting a static site up and running on my own Linux CentOS with Lighttpd was definitely simple. Getting a NodeJs web service up and running with Express was equally simple, but [&#8230;]<img alt="" border="0" src="https://pixel.wp.com/b.gif?host=christopherjmcclellan.wordpress.com&#038;blog=59673389&#038;post=1127&#038;subd=christopherjmcclellan&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
        <wfw:commentRss>https://christopherjmcclellan.wordpress.com/2016/12/04/in-depth-on-my-first-nodejs-express-stack/feed/</wfw:commentRss>
        <slash:comments>0</slash:comments>

        <media:content url="http://0.gravatar.com/avatar/0b43c27212304872e59ec6c73e1e30e7?s=96&#38;d=identicon&#38;r=G" medium="image">
            <media:title type="html">ckuhn203</media:title>
        </media:content>

        <media:content url="http://christopherjmcclellan.files.wordpress.com/2016/11/capture.png" medium="image">
            <media:title type="html">capture</media:title>
        </media:content>

        <media:content url="http://i.imgur.com/SAx29qc.jpg" medium="image">
            <media:title type="html">Buzz LightYear and Woody. Callbacks Everywhere</media:title>
        </media:content>
    </item>
        <item>
        <title>Initial Thoughts on Node.js</title>
        <link>https://christopherjmcclellan.wordpress.com/2016/11/14/initial-thoughts-on-node-js/</link>
        <comments>https://christopherjmcclellan.wordpress.com/2016/11/14/initial-thoughts-on-node-js/#comments</comments>
        <pubDate>Mon, 14 Nov 2016 11:44:52 +0000</pubDate>
        <dc:creator><![CDATA[Christopher J. McClellan]]></dc:creator>
                <category><![CDATA[NodeJs]]></category>
        <category><![CDATA[Programming]]></category>
        <category><![CDATA[node]]></category>

        <guid isPermaLink="false">http://christopherjmcclellan.wordpress.com/?p=1124</guid>
        <description><![CDATA[I&#8217;ve been building a hobby site so I can learn about non-Microsoft stacks and I spent the weekend learning Node.js. At first I was completely amazed at how awesome it was. It took about 5 minutes to spin up a webserver and start returning HTML from an endpoint! It was easy and fun! That 5 [&#8230;]<img alt="" border="0" src="https://pixel.wp.com/b.gif?host=christopherjmcclellan.wordpress.com&#038;blog=59673389&#038;post=1124&#038;subd=christopherjmcclellan&#038;ref=&#038;feed=1" width="1" height="1" />]]></description>
        <wfw:commentRss>https://christopherjmcclellan.wordpress.com/2016/11/14/initial-thoughts-on-node-js/feed/</wfw:commentRss>
        <slash:comments>2</slash:comments>

        <media:content url="http://0.gravatar.com/avatar/0b43c27212304872e59ec6c73e1e30e7?s=96&#38;d=identicon&#38;r=G" medium="image">
            <media:title type="html">ckuhn203</media:title>
        </media:content>
    </item>
    </channel>
</rss>

Rss.fs

And the parsing code. The Rss module contains all the record types and a single Parse method that returns a RssFeed record.

module Rss =
    open System

    type Item = {
        Title: string
        Link: Uri
        Comments: Uri
        PublishDate: DateTimeOffset
        Description: string
    }

    type Channel = {
        Title: string
        Link: Uri
        Description: string
        LastBuildDate: DateTimeOffset
        Items: seq<Item>
    }

    type RssFeed = {
        Version: double
        Channel: Channel
    }

    open System
    open System.Xml
    let Parse input = 
        let document = XmlDocument()
        document.LoadXml input
        let rss = document.DocumentElement
        let version = rss.Attributes.ItemOf "version"

        let channelNode = rss.FirstChild

        let innerText (node: XmlNode) (name: string) = 
            let item = node.Item name
            item.InnerText

        let channelElementValue name =
            innerText channelNode name

        let itemNodes = seq {
            for element in channelNode.ChildNodes do
                if element.Name.Equals "item" then
                    yield element
        }

        let itemNodeToItem node = {
            Title = innerText node "title"
            Link = Uri(innerText node "link")
            Comments = Uri(innerText node "comments")
            PublishDate = DateTimeOffset.Parse(innerText node "pubDate")
            Description = innerText node "description"
         }

        let channel = {
            Title = channelElementValue "title"
            Link = Uri(channelElementValue "link")
            Description = channelElementValue "description"
            LastBuildDate = DateTimeOffset.Parse(channelElementValue "lastBuildDate")
            Items = itemNodes |> Seq.map(itemNodeToItem)
        }

        {Version = Double.Parse version.Value; Channel = channel}
\$\endgroup\$
4
\$\begingroup\$

I like how your code looks for the most part, though I want to touch on a couple of mostly minor things.

In F# we try to write things as compositions for the most part, including generics. One of the nicer things about F# (in my opinion) is that it allows for very expressive code and typing. For example:

type Channel = {
    Title: string
    Link: Uri
    Description: string
    LastBuildDate: DateTimeOffset
    Items: seq<Item>
}

Your Items should be an Item seq instead, as Item becomes a composition of the seq. The net-effect is the same: it's a sequence of items, but the expression is slightly different, we express the composition rather than the generic.

I know it was mentioned in chat that we prefer recursion or mapping over for loops:

let itemNodes = seq {
    for element in channelNode.ChildNodes do
        if element.Name.Equals "item" then
            yield element
}

So I won't touch on that in detail, except to mention that we do it to allow us to express what's happening as a series of steps, rather than an iteration.

I would reorder the parameters in the following function:

let innerText (node: XmlNode) (name: string) = 
    let item = node.Item name
    item.InnerText

Why? Because it leaves us open to piping later, but even moreso: it allows us to build a meaningful composition. You know how to use the |> (pipe-right) operator, but do you know that it always pipes the left argument to the last parameter of the function on the right? That means that if we ever want to pipe a node to innerText, we need node to be the last parameter. It let's us be more expressive, especially here:

let itemNodeToItem node = {
    Title = innerText node "title"
    Link = Uri(innerText node "link")
    Comments = Uri(innerText node "comments")
    PublishDate = DateTimeOffset.Parse(innerText node "pubDate")
    Description = innerText node "description"
 }

Now we can write Title = node |> innerText "title", which usually reads a bit cleaner in F#. We could even declare a nodeTitle node = node |> innerText "title", which is composed to allow us to pipe channelNode or node into it. (Think: Title = node |> nodeTitle.)

I would also remove the :string type-hint to the compiler from that as well, since it knows you're looking at XmlNode.Item, it should infer that the name must be a string. (I don't have a compiler sitting in front of me to verify, but that should be the case. Let me know if not and I'll remove this bit.)

The only thing I don't like about your code is this function:

let channelElementValue name =
    innerText channelNode name

The name parameter is not self-explanatory, I would personally call it elementName, element, or elName. (Abbreviations are acceptable in F#.) Also, once we've rewritten our innerText I think it can read more expressively: channelNode |> innerText name.

Finally, I see:

Items = itemNodes |> Seq.map(itemNodeToItem)

We should really remove those parenthesis: Seq.map is an F# function, and we usually distinguish between calling an F# function and a .NET method by the parenthesis, they're unnecessary here: itemNodes |> Seq.map itemNodeToItem, and I assume you probably have them because you were using a fun ... before, which requires parenthesis, but once you've extracted that to it's own function the parenthesis are obsolete.

In the end, this is very good F# code. You did a wonderful job concisely and clearly expressing what you wish to achieve: the steps are logical, flow, and make sense. Very good job. :)

\$\endgroup\$
  • \$\begingroup\$ It's funny. I was actually thinking that having the name key last would make it easier to retrieve a bunch of things at once. ["title", "item"] |> innerText node, but it turned out that wasn't very useful for this particular purpose. ++ Thanks for the review. \$\endgroup\$ – RubberDuck Aug 13 '17 at 13:09
  • 1
    \$\begingroup\$ @RubberDuck Nah, you want the node last in this case because it allows you to compose partial functions to get certain parameters, and it allows you to pipe it as an argument for mapping/parsing/what-have-you. You could even take each property, iterate it, and pipe the node to it if you were using some sort of reflection to build a serializer. (value = node |> innerText prop.Name) \$\endgroup\$ – 410_Gone Aug 13 '17 at 13:12
  • \$\begingroup\$ Quick question. My return at the end felt weird. Is there a clearer way to construct the final return value? \$\endgroup\$ – RubberDuck Aug 13 '17 at 13:14
  • \$\begingroup\$ @RubberDuck That depends: do you plan to call this from C# code? If so, I'd leave it as is. If not, I would probably ditch the types altogether and use tuples instead. You could consider a factory that returns the type from the values (let finalResult = {Version = Double.parse version.Value; Channel = channel}) and then return the finalResult, if that feels better to you (though we usually don't do that with F#, it's totally reasonable). I will agree that, at-first, the returns in F# feel a bit clunky in longer methods, but once you get used to them they begin to feel a bit more natural. \$\endgroup\$ – 410_Gone Aug 13 '17 at 13:17
  • \$\begingroup\$ Not C# code, but I do need the types to bind to my Razor view. I think maybe I'll use the intermediate. Thanks again. \$\endgroup\$ – RubberDuck Aug 13 '17 at 13:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.