PageSourceSearch

https://www.pepperoni.blog/journey-into-f/

html pepperoni.blog collected 2026-10-04 08:04:01 UTC 31,319 bytes, 512 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en-au">
3<head>
4
5    <meta charset="utf-8" />
6    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
7
8    <title>F#LASHCARDS</title>
9    <meta name="HandheldFriendly" content="True" />
10    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
11
12    <link rel="stylesheet" type="text/css" href="https://www.pepperoni.blog/assets/main/css/main.css?v=r8psiBP9eVY8WP1I" />
13
14    <meta name="description" content="Learn F#, and a language by implementing a flash-card program, step-by-step.">
15    <link rel="canonical" href="https://www.pepperoni.blog/journey-into-f/">
16    <meta name="referrer" content="no-referrer-when-downgrade">
17    
18    <meta property="og:site_name" content="◵ Pepperoni">
19    <meta property="og:type" content="article">
20    <meta property="og:title" content="F#LASHCARDS">
21    <meta property="og:description" content="Learn F#, and a language by implementing a flash-card program, step-by-step.">
22    <meta property="og:url" content="https://www.pepperoni.blog/journey-into-f/">
23    <meta property="og:image" content="https://images.unsplash.com/photo-1540247722900-f91e38e1f36c?crop&#x3D;entropy&amp;cs&#x3D;tinysrgb&amp;fit&#x3D;max&amp;fm&#x3D;jpg&amp;ixid&#x3D;MnwxMTc3M3wwfDF8c2VhcmNofDIzfHxjYXJkJTIwc3RhY2t8ZW58MHx8fHwxNjM5NTQ5MDYw&amp;ixlib&#x3D;rb-1.2.1&amp;q&#x3D;80&amp;w&#x3D;2000">
24    <meta property="article:published_time" content="2021-12-12T04:15:42.000Z">
25    <meta property="article:modified_time" content="2026-04-20T02:42:27.000Z">
26    <meta name="twitter:card" content="summary_large_image">
27    <meta name="twitter:title" content="F#LASHCARDS">
28    <meta name="twitter:description" content="Learn F#, and a language by implementing a flash-card program, step-by-step.">
29    <meta name="twitter:url" content="https://www.pepperoni.blog/journey-into-f/">
30    <meta name="twitter:image" content="https://images.unsplash.com/photo-1540247722900-f91e38e1f36c?crop&#x3D;entropy&amp;cs&#x3D;tinysrgb&amp;fit&#x3D;max&amp;fm&#x3D;jpg&amp;ixid&#x3D;MnwxMTc3M3wwfDF8c2VhcmNofDIzfHxjYXJkJTIwc3RhY2t8ZW58MHx8fHwxNjM5NTQ5MDYw&amp;ixlib&#x3D;rb-1.2.1&amp;q&#x3D;80&amp;w&#x3D;2000">
31    <meta name="twitter:label1" content="Written by">
32    <meta name="twitter:data1" content="Luke Hannam">
33    <meta name="twitter:creator" content="@luke_pasquazzi">
34    <meta property="og:image:width" content="1200">
35    <meta property="og:image:height" content="800">
36    
37    
37<script type="application/ld+json">
38{
39    "@context": "https://schema.org",
40    "@type": "Article",
41    "publisher": {
42        "@type": "Organization",
43        "name": "◵ Pepperoni",
44        "url": "https://www.pepperoni.blog/",
45        "logo": {
46            "@type": "ImageObject",
47            "url": "https://www.pepperoni.blog/favicon.ico",
48            "width": 48,
49            "height": 48
50        }
51    },
52    "author": {
53        "@type": "Person",
54        "name": "Luke Hannam",
55        "url": "https://www.pepperoni.blog/author/luke/",
56        "sameAs": [
57            "https://x.com/luke_pasquazzi"
58        ]
59    },
60    "headline": "F#LASHCARDS",
61    "url": "https://www.pepperoni.blog/journey-into-f/",
62    "datePublished": "2021-12-12T04:15:42.000Z",
63    "dateModified": "2026-04-20T02:42:27.000Z",
64    "image": {
65        "@type": "ImageObject",
66        "url": "https://images.unsplash.com/photo-1540247722900-f91e38e1f36c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDIzfHxjYXJkJTIwc3RhY2t8ZW58MHx8fHwxNjM5NTQ5MDYw&ixlib=rb-1.2.1&q=80&w=2000",
67        "width": 1200,
68        "height": 800
69    },
70    "description": "Learn F#, and a language by implementing a flash-card program, step-by-step.",
71    "mainEntityOfPage": "https://www.pepperoni.blog/journey-into-f/"
72}
73    </script>
73
74
75    <meta name="generator" content="Ghost 6.68">
76    <link rel="alternate" type="application/rss+xml" title="◵ Pepperoni" href="https://www.pepperoni.blog/rss/">
77    
77<script defer src="https://cdn.jsdelivr.net/ghost/portal@~2.71/umd/portal.min.js" data-i18n="true" data-ghost="https://www.pepperoni.blog/" data-key="b9a99f50d55e7a5635e19112c6" data-api="https://pepperoni.ghost.io/ghost/api/content/" data-locale="en-au" crossorigin="anonymous"></script>
77<style id="gh-members-styles">.gh-post-upgrade-cta-content,
78.gh-post-upgrade-cta {
79    display: flex;
80    flex-direction: column;
81    align-items: center;
82    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
83    text-align: center;
84    width: 100%;
85    color: #ffffff;
86    font-size: 16px;
87}
88
89.gh-post-upgrade-cta-content {
90    border-radius: 8px;
91    padding: 40px 4vw;
92}
93
94.gh-post-upgrade-cta h2 {
95    color: #ffffff;
96    font-size: 28px;
97    letter-spacing: -0.2px;
98    margin: 0;
99    padding: 0;
100}
101
102.gh-post-upgrade-cta p {
103    margin: 20px 0 0;
104    padding: 0;
105}
106
107.gh-post-upgrade-cta small {
108    font-size: 16px;
109    letter-spacing: -0.2px;
110}
111
112.gh-post-upgrade-cta a {
113    color: #ffffff;
114    cursor: pointer;
115    font-weight: 500;
116    box-shadow: none;
117    text-decoration: underline;
118}
119
120.gh-post-upgrade-cta a:hover {
121    color: #ffffff;
122    opacity: 0.8;
123    box-shadow: none;
124    text-decoration: underline;
125}
126
127.gh-post-upgrade-cta a.gh-btn {
128    display: block;
129    background: #ffffff;
130    text-decoration: none;
131    margin: 28px 0 0;
132    padding: 8px 18px;
133    border-radius: 4px;
134    font-size: 16px;
135    font-weight: 600;
136}
137
138.gh-post-upgrade-cta a.gh-btn:hover {
139    opacity: 0.92;
140}</style>
141    
141<script defer src="https://cdn.jsdelivr.net/ghost/sodo-search@~1.8/umd/sodo-search.min.js" data-key="b9a99f50d55e7a5635e19112c6" data-styles="https://cdn.jsdelivr.net/ghost/sodo-search@~1.8/umd/main.css" data-sodo-search="https://pepperoni.ghost.io/" data-locale="en-au" crossorigin="anonymous"></script>
141
142    
143    <link href="https://www.pepperoni.blog/webmentions/receive/" rel="webmention">
144    
144<script defer src="/public/cards.min.js?v=ShRHxgy4po8zN-Wf"></script>
144
145    <link rel="stylesheet" type="text/css" href="/public/cards.min.css?v=WwnU9jw5ancNC8Gc">
146    
146<script defer src="/public/member-attribution.min.js?v=AKG4hWena9j3yX3I"></script>
146
147    
147<script defer src="/public/ghost-stats.min.js?v=vFcCUf6ZQ0Hyhc8h" data-stringify-payload="false" data-datasource="analytics_events" data-storage="localStorage" data-host="https://www.pepperoni.blog/.ghost/analytics/api/v1/page_hit"  tb_site_uuid="b386c6de-a080-440c-bc0c-2348e66cdad1" tb_post_uuid="b7105ea2-a331-4e92-938c-9a5f41538348" tb_post_type="post" tb_member_uuid="undefined" tb_member_status="undefined" tb_gift_link=""></script>
147<style>:root {--ghost-accent-color: #835012;}</style>
148    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/themes/prism.min.css">
149<style>
150    code[class*=language-], pre[class*=language-] {
151        font-size: 0.9em;
152    }
153</style>
154
155</head>
156<body class="is-preload post-template">
157
158    <div id="wrapper">
159        <main id="main">
160            <div class="inner">
161
162                <header id="header">
163                        <a href="https://www.pepperoni.blog" class="logo">◵ Pepperoni</a>
164                    <ul class="icons">
165                        <li><a href="https://feedly.com/i/subscription/feed/https://www.pepperoni.blog/rss/" class="icon fa-rss" target="_blank" rel="noopener"><span class="label" title="RSS">RSS</span></a></li>
166                    </ul>
167                </header>
168
169                
170<section>
171
172    <header class="main content">
173        <span class="date">December 12, 2021</span>
174        <h1>F#LASHCARDS</h1>
175            <p class="content">Learn F#, and a language by implementing a flash-card program, step-by-step.</p>
176    </header>
177
178    <div class="image main"><img src="https://images.unsplash.com/photo-1540247722900-f91e38e1f36c?crop&#x3D;entropy&amp;cs&#x3D;tinysrgb&amp;fit&#x3D;max&amp;fm&#x3D;jpg&amp;ixid&#x3D;MnwxMTc3M3wwfDF8c2VhcmNofDIzfHxjYXJkJTIwc3RhY2t8ZW58MHx8fHwxNjM5NTQ5MDYw&amp;ixlib&#x3D;rb-1.2.1&amp;q&#x3D;80&amp;w&#x3D;2000" alt="F#LASHCARDS" /></div>
179
180    <div class="content">
181        <p>This is actually my first F# program, so I'll be learning along too! I'm not going to explain everything, but some interesting things as we go, and I'm going to be making mistakes.</p><p>Firstly, go and create a console app. I'm using Visual Studio 2019 as my editor and for the default .NET 5.0 template.</p><p>This is what my VS F# Console App template gave me inside a file called Program.fs:</p><pre><code class="language-fsharp">// Learn more about F# at http://fsharp.org
182
183open System
184
185// Define a function to construct a message to print
186let from whom =
187    sprintf "from %s" whom
188
189[&lt;EntryPoint&gt;]
190let main argv =
191    let message = from "F#" //Call the function
192    printfn "Hello World %s" message
193    0 // return an integer exit code
194</code></pre><p>Pretty straightforward, but let's go over what we have.<br>We have a "from" function, which takes a parameter "whom". It's purpose seems to be to format the string "from %s" and replace "%s" with the string passed as whom.  Then we have a main function which defines a message by calling "from" function and then printing out Hello World and then the message. Finally it has an exit code. I think that by convention (and principle), the last line must be the return type?</p><p>In F#, you know something is a function because it's definition will have following parts:</p><ol><li>let keyword</li><li>a name</li><li>one or more arguments separated by spaces</li><li>=, followed by newline (optional) and a function body.</li></ol><hr><h3 id="first-weirdness-">First weirdness.</h3><p>When I heard that a function has one or more arguments, my first immediate thought was - "hang on, what about a function with no arguments?". You can't do that in F#.</p><p>By convention when this comes up, you can use the Unit as an argument. Research tells me that the Unit type is LITERALLY a type to indicate the absence of a value to satisfy syntax. If the from function took the unit argument, it might look like this: </p><pre><code class="language-fsharp">let from () = "from F#"</code></pre><p>I thought initially this was a bit of a design flaw, but it does have a cool little consequence over using something like "void". While you would normally call a function like this:</p><pre><code class="language-fsharp">let result = myFunctionWithParams param</code></pre><p>For functions with no arguments (or the unit argument) it is called like this:</p><p>which looks pretty familiar!</p><pre><code class="language-fsharp">let fromFsharpString = from ()</code></pre><p>Also, it is necessary to make this distinction as otherwise result in below line would be the function itself, not the result of the function:</p><pre><code class="language-fsharp">let fromFshapStringFunction = from</code></pre><hr><p>We are going to start by defining a "Card" type. A Card has two "Sides". One Side is "English" and the other side is a "Latin". "English" and "Latin" are both types of Sides. In OO (Object Oriented) land, we might say they're sub-classes, but I heard that Functional programming has no inheritance in it as a rule. Let's go ahead and define the general idea of a Side first after the main function - It is a simple "record" type with a "text" property.</p><pre><code class="language-fsharp">// Learn more about F# at http://docs.microsoft.com/dotnet/fsharp
195
196open System
197
198// Define a function to construct a message to print
199let from () = "from %s"
200
201[&lt;EntryPoint&gt;]
202let main argv =
203    let message = from "F#" // Call the function
204    printfn "Hello world %s" message
205    0 // return an integer exit code
206
207type Side = { text: string }</code></pre><p><strong>Oh, we can't</strong>. Try to compile, and it breaks. The EntryPoint function must be the <strong>last </strong>to be defined in the <strong>last </strong>file to be compiled. So... let's make a new file for our definitions.</p><p>
207I'm calling mine CardTypes.fs, moving Side definition to that.</p><pre><code class="language-fsharp">module CardTypes
208
209type Side = { text:string }</code></pre><p>And now my Program.fs, I'll just add a reference to CardTypes by adding:</p><pre><code class="language-fsharp">open System
210open CardTypes //added this</code></pre><h2 id="the-first-mistake-how-not-to-define-a-type">The first mistake - how NOT to define a type</h2><p>Let's keep going by adding the Language types. Now - I could say language is a property of a Side, and that would be valid - but I have a feeling that we may want different properties based on what language we're looking at later on, so I'm defining them as types for now:</p><pre><code class="language-fsharp">module CardTypes
211
212type Side = { text: string }
213type English = Side
214type Latin = Side</code></pre><p>The syntax seems logical, if a bit different.</p><p>Now, let's describe a Card. It is made of two sides - one English and one Latin:</p><pre><code class="language-fsharp">type Card = English * Latin</code></pre><p>Let's test out that type.</p><p>To Create a Side, we define the data matching the structure of the type. F# makes a judgement call and infers that I created data matching the type.</p><pre><code class="language-fsharp">let mySide = {text="hello"}</code></pre><p>Now - in actuality, we know this is English. If we needed to specify this, we can:</p><pre><code class="language-fsharp">let mySide = {text="hello"}:English</code></pre><p>To create a Card, we just have to declare the two sides together.</p><pre><code class="language-fsharp">let myCard = ({text="hello"} , {text="salve"})</code></pre><p>Or do we? In fact, taking a closer look, what we have created is a "Side*Side" tuple. We need to explicitly state the classes while there is no way to distinguish between them.</p><pre><code class="language-fsharp">let myCard = (({text="hello"}:English) , ({text="salve"}:Latin))</code></pre><p>But we are still not done! F# is super confused. It's defined myCard as a tuple of English and Latin, but it's not saying it's a Card. Guess I'd better help:</p><pre><code class="language-fsharp">let myCard = (({text="hello"}:English) , ({text="salve"}:Latin)):Card</code></pre><p>OK. That worked, but it's super ugly. The reason it is ugly is:</p><p>1) The two sides of the card are identical, so there is no real need to have a Latin or English class distinction at this stage of the program, and we're trying to kinda force one in there</p><p>2) The card class itself is not doing storing anything that interesting that couldn't be represented as a Tuple anyway.</p><p>3) Probably most importantly; Inheritance is a pattern to avoid in functional programming.</p><p>Now, there is a slightly better way to do this - if we just say the whole thing is a Card, it is much happier:</p><pre><code class="language-fsharp">let myCard = ({text="hello"} , {text="salve"}):Card</code></pre><p>However, I think we can do better.</p><h2 id="the-second-mistake-still-defining-types-wrong">The second mistake: still defining types wrong</h2><p>We're going to make a small adjustment to the structure. Instead of defining the generalised concept of a "slide", we're going to do the reverse and start with the specific types:</p><pre><code class="language-fsharp">type MaleFemaleOrNeuter = Male | Female | Neuter
215type EnglishSide = {text:string}
216type LatinSide = {text:string; gender:MaleFemaleOrNeuter option}
217
218type Card = EnglishSide * LatinSide</code></pre><p>Note, we are creating more specific types "EnglishSide" and "LatinSide" that have slightly different data structures. The Latin language has a concept of nouns and adjectives having some kind of "gender". F# provides the "option" keyword to make nullable-like type parameters - however these parameters are not defaulted to be Nothing/null (or None in F#), and when we populate the property, we have to specifically say we have "Some" of that optional keyword.</p><pre><code class="language-fsharp">let myCard = Card({text="hello"}, {text="salve"; gender = Some Male})</code></pre><p>Note, the Card constructor is a convenient default that looks a little nicer. You can name the constructor something else if you wish:</p><pre><code class="language-fsharp">type Card = C of EnglishSide * LatinSide	//define type with constructor C
219
220let myCard = C({text="hello"}, {text="salve"; gender = Some Male})	//call constructor.</code></pre><h3 id="expanding-capabilities">Expanding Capabilities</h3><p>Let's add more languages!</p><p>As we are now expanding the concept of a Card to be a little more generic, I think we can re-introduce the concept of a Side, alongside other properties unique to the language:</p><pre><code class="language-fsharp">module CardTypes
221
222type MaleOrFemale = Male | Female
223type MaleFemaleOrNeuter = Male | Female | Neuter
224
225type EnglishSide = {text:string}
226
227type LatinSide = {
228    text:string ;
229    gender:MaleFemaleOrNeuter option;   //Latin nouns may come in three genders
230}
231
232type ItalianSide = {
233    text:string;
234    gender: MaleOrFemale option;    //Italian nouns may be male or female (no neuter)
235}
236
237type Side =
238    | E of EnglishSide
239    | L of LatinSide
240    | I of ItalianSide
241
242type Card = Side * Side
243
244
245let myCard =
246    Card(
247        E({text="hello"}),
248        L({text="salve"; gender=None}))
249let myCard2 =
250    Card(
251        L({text="mensa"; gender=Some Female}),
252        I({text="tavolo"; gender=Some MaleOrFemale.Male}))</code></pre><p>One cool thing I found out is that if I define the "E" constructor for the EnglishSide at the declaration of EnglishSide type, then E will return an EnglishSide type, but if I define the E constructor on the Side type declaration (as per above), then E returns a Side, rather than an EnglishSide. This makes the Card declaration a bit neater.</p><p>On the less favourable side, despite being somewhat obvious to me, F# was unable to distinguish that I was not passing a MaleFemaleOrNeuter option through to the gender for Italian on it's own, so I had to explicitly state this was the case.</p><p>The solution was to define constructors for the specific types against the Side type, so that it knows I'm intending it to be a Side:</p><pre><code class="language-fsharp">type Side =
253    | E of EnglishSide
254    | L of LatinSide
255    | I of ItalianSide
256
257type Card = Side * Side
258
259
260let myCard =
261	Card(
262    	E({english="hello"}),
263        L({latin="salve"; gender=None}))
264let myCard2 =
265    Card(
266        I({italian="tavolo"; gender=Some MaleOrFemale.Male}),
267        L({latin="mensa"; gender=Some MaleFemaleOrNeuter.Female}))</code></pre><p>Some more oddness here - option types can be defined as None or Some of something. So when I want to say I have a value, I have to explicitly say I have "Some" value. </p><p>Although this works, I went wondering down a long road to figure out if there was a way to specify a type that is Gender with extra constraints. There is, and it isn't pretty:</p><pre><code class="language-fsharp">type Gender = Male | Female | Neuter
268
269module BinaryGender = 
270    type T = BinaryGender of Gender
271    let create (g:Gender) =
272        if g &lt;&gt; Neuter
273        then Some (BinaryGender g)
274        else None
275
276...
277
278
279type ItalianSide = {
280    text:string;
281    gender: BinaryGender.T option;    //Italian nouns may be male or female (no neuter)
282}
283</code></pre><p>I'm creating a grouping of concepts or a "module" called Binary Gender, and creating a type "T" underneath it, which is public. Then I'm defining a function called "create" that checks if the gender is a valid value. If it's not value it returns None otherwise it returns Some BinaryGender. This kinda works, though the syntax of use is a bit wordy:</p><pre><code class="language-fsharp">let myCard2 =
284    Card(
285        L({text="mensa"; gender=Some Female}),
286        I({text="tavolo"; gender=BinaryGender.create Male}))</code></pre><h2 id="lesson-learned-records-are-not-classes">Lesson learned - Records are not Classes</h2><p>So much of this weirdness around trying to get a Card to have sides of different related / inherited classes is because I'm using the record type. I went away and researched some more and discovered that Records are compiled to sealed types. If I really want to use inheritance structures in F#, I'm better of using the equivalent of a class. This is done as follows:</p><pre><code class="language-fsharp">type Side (text:string) =
287    member this.text:string = text
288    
289type EnglishSide(text:string) =
290    inherit Side(text)
291    
292type LatinSide(text:string, gender:Gender option) =
293    inherit Side(text)
294    member this.gender:Gender option = gender
295    
296type ItalianSide(text:string, gender:BinaryGender.T option) =
297    inherit Side(text)
298    member this.gender:BinaryGender.T option = gender
299
300type Card = Side * Side
301
302type Deck = array&lt;Card&gt;
303let sourceDeck:Deck =
304    [|
305        Card(EnglishSide("hello"),
306            LatinSide("salve", None));
307        Card(LatinSide("mensa", Some Female),
308            ItalianSide("tavola", BinaryGender.create Female));
309        Card(LatinSide("nomen", Some Neuter),
310            ItalianSide("nome", BinaryGender.create Male));
311    |]
312
313</code></pre><h2 id="now-to-actually-do-something">Now to actually do something</h2><p>So far, I've just been mucking around with representing the data. Let's start to actually do something by returning to the main function. What I want is basically to show the text of the card, and then allow the user to flip the card to see the other side, and whatever information is presented for the card.</p><p>I started off with a function that reads a side of a card, and at least this was fairly simple:</p><pre><code class="language-fsharp">let Read (side:Side) =
314    sprintf "The word is %s" side.text</code></pre><p>However, I realised that I need a way to tell the user what language the card is in, so I made a function for that and amended Read appropriately:</p><pre><code class="language-fsharp">let getLanguageFor (s:Side) =
315    match s with
316    | (:? LatinSide as l) -&gt; Some "Latin"
317    | (:? EnglishSide as l) -&gt; Some "English"
318    | (:? ItalianSide as l) -&gt; Some "Italian"
319    | _ -&gt; None
320
321let Read (side:Side) =
322    let language = getLanguageFor side
323    match language with
324        | Some lang -&gt; sprintf "The %s word is %s" lang side.text
325        | None -&gt; sprintf "The (unknown) word is %s" side.text</code></pre><p>I admit I had to look up how match expressions worked to figure this one out. You'll also note the <code>getLanguageFor</code> function returns "Some" or "None" of something, and we check if Some or None is returned to figure out how to read the card. I could expand this later to learn how to read more  information from the card maybe.</p><p>Then I created some functions that read different sides from a card. I think maybe there was a simpler way to do this with some syntax sugar, but this works well:</p><pre><code class="language-fsharp">let SideAOf (c:Card) =
326    let sideA, _ = c
327    sideA
328         
329let SideBOf (c:Card) =
330    let _, sideB = c
331    sideB</code></pre><p>Then, I added in abilities for handling the deck of cards - namely drawing and discarding so that we know 
331when we're out of cards and don't keep re-drawing the same card over and over. It's also more analogous to real life than a for array (which I also don't know how to do yet in F#).</p><pre><code class="language-fsharp">let anyIn (a:Deck) = a.Length &gt; 1
332
333let discardCardFrom (deck:Deck) =
334    if anyIn deck
335    then deck.[1..]:Deck   //return rest of array.
336    else [||]:Deck  //return empty array
337
338let drawCardFrom (deck:Deck) =
339    if anyIn deck
340    then Some deck.[0]
341    else None
342    </code></pre><p>Now, to revisit the main function. We draw from the deck and read the card and keep going until we're out of cards:</p><pre><code class="language-fsharp">[&lt;EntryPoint&gt;]
343let main argv =
344    printfn "Welcome to F#LASHCARDS"
345    let mutable deck = CardTypes.sourceDeck
346    let mutable card = drawCardFrom deck
347    let writeLn s = Console.WriteLine(s:string)
348    while card &lt;&gt; None do
349        (writeLn &lt;&lt; Read &lt;&lt; SideAOf) card.Value
350        let _ = Console.ReadKey()
351        (writeLn &lt;&lt; Read &lt;&lt; SideBOf) card.Value
352        writeLn ""
353        deck &lt;- discardCardFrom deck
354        card &lt;- drawCardFrom deck
355    writeLn "No more cards."
356    0</code></pre><p>What I like most about this i that it's fairly readable without any kind of F# knowledge, almost like pseudeo-code, though the pipe parts with "&lt;&lt;"  are a bit cryptic maybe. I figured out this was the way F# liked to have a value from a function move into another from a you-tube video, but it's basically the same as this:</p><pre><code class="language-fsharp">writeLn(Read(SideAOf card.Value))</code></pre><p>Note the mutable keyword here allows deck to be reassigned to - by default everything is not mutable, meaning the line of <code>deck -&gt; discardCardFrom deck</code> would never work, as that "mutates" the value. In most OO languages, it's the opposite behaviour - making a property or variable non-mutable requries a "readonly" keyword or similar.</p><p>That is it for me for the moment. I may experiment further, but for now - here's the completed Program:</p><pre><code class="language-fsharp">// Learn more about F# at http://docs.microsoft.com/dotnet/fsharp
357
358open System
359open CardTypes
360
361// Define a function to construct a message to print
362
363let anyIn (a:Deck) = a.Length &gt; 1
364
365let discardCardFrom (deck:Deck) =
366    if anyIn deck
367    then deck.[1..]:Deck   //return rest of array.
368    else [||]:Deck  //return empty array
369
370let drawCardFrom (deck:Deck) =
371    if anyIn deck
372    then Some deck.[0]
373    else None
374    
375let getLanguageFor (s:Side) =
376    match s with
377    | (:? LatinSide as l) -&gt; Some "Latin"
378    | (:? EnglishSide as l) -&gt; Some "English"
379    | (:? ItalianSide as l) -&gt; Some "Italian"
380    | _ -&gt; None
381    
382
383let Read (side:Side) =
384    let language = getLanguageFor side
385    match language with
386        | Some lang -&gt; sprintf "The %s word is %s" lang side.text
387        | None -&gt; sprintf "The (unknown) word is %s" side.text
388 
389let SideAOf (c:Card) =
390    let sideA, _ = c
391    sideA
392         
393let SideBOf (c:Card) =
394    let _, sideB = c
395    sideB
396
397[&lt;EntryPoint&gt;]
398let main argv =
399    printfn "Welcome to F#LASHCARDS"
400    let mutable deck = CardTypes.sourceDeck
401    let mutable card = drawCardFrom deck
402    let writeLn s = Console.WriteLine(s:string)
403    while card &lt;&gt; None do
404        (writeLn &lt;&lt; Read &lt;&lt; SideAOf) card.Value
405        let _ = Console.ReadKey()
406        (writeLn &lt;&lt; Read &lt;&lt; SideBOf) card.Value
407        writeLn ""
408        deck &lt;- discardCardFrom deck
409        card &lt;- drawCardFrom deck
410    writeLn "No more cards."
411    0
412
413</code></pre>
414    </div>
415
416
417    <footer class="post-footer">
418
419        <div class="authors">
420            <a class="author-box" href="/author/luke/">
421                <section class="author-box-content">
422                    <div class="author-box-label">Author</div>
423                    <h5 class="author-box-name">Luke Hannam</h5>
424                    <p>Read more posts by this author.</p>
425                </section>
426            </a>
427        </div>
428
429    </footer>
430
431
432</section>
433
434<script>
435    var images = document.querySelectorAll('.kg-gallery-image img');
436    images.forEach(function (image) {
437        var container = image.closest('.kg-gallery-image');
438        var width = image.attributes.width.value;
439        var height = image.attributes.height.value;
440        var ratio = width / height;
441        container.style.flex = ratio + ' 1 0%';
442    })
443</script>
443
444
445
446            </div>
447        </main>
448
449        <div id="sidebar">
450    <div class="inner">
451
452            <nav id="menu">
453    <header class="major">
454        <h2>Menu</h2>
455    </header>
456    <ul class="links">
457        <li class="nav-home "><a href="https://www.pepperoni.blog/">Home</a></li>
458        <li class="nav-opinion-pieces "><a href="https://www.pepperoni.blog/tag/opinion/">Opinion pieces</a></li>
459        <li class="nav-c "><a href="https://www.pepperoni.blog/tag/C/">C#</a></li>
460        <li class="nav-javascript "><a href="https://www.pepperoni.blog/tag/JavaScript/">JavaScript</a></li>
461        <li class="nav-sql "><a href="https://www.pepperoni.blog/tag/SQL/">SQL</a></li>
462        <li class="nav-about-me "><a href="https://www.pepperoni.blog/about/">About Me</a></li>
463    </ul>
464</nav>
465
466        <section>
467            <header class="major">
468                <h2>Recent Posts</h2>
469            </header>
470            <div class="mini-posts">
471                    <article>
472                        <h3><a href="/why-are-there-so-many-interfaces/">You&#x27;re Doing Interfaces Wrong</a></h3>
473                        <p>Interfaces are everywhere, but still we have tight coupling, and inflexibility. Dependency inversion is the answer.</p>
474                    </article>
475                    <article>
476                        <h3><a href="/the-software-crisis/">The Software Crisis</a></h3>
477                        <p>Software projects run over-budget, over-time, are inefficient, unmaintainable or ineffective. This is The Software &quot;crisis&quot;.</p>
478                    </article>
479                    <article>
480                        <h3><a href="/journey-into-f/">F#LASHCARDS</a></h3>
481                        <p>Learn F#, and a language by implementing a flash-card program, step-by-step.</p>
482                    </article>
483            </div>
484        </section>
485
486
487        <footer id="footer">
488            <p class="copyright">&copy; ◵ Pepperoni - All rights reserved<br>
489            Design: <a href="https://html5up.net" target="_blank" rel="noopener">HTML5 UP</a>, Published with <a href="https://ghost.org" target="_blank" rel="noopener">Ghost</a></p>
490        </footer>
491
492    </div>
493</div>
494
495    </div>
496
497    
497<script src="https://www.pepperoni.blog/assets/main/js/jquery.min.js?v=FgpCb_KJQlLNfOu9"></script>
497
498    
498<script src="https://www.pepperoni.blog/assets/main/js/browser.min.js?v=h5ENXtAFPZDK-DIw"></script>
498
499    
499<script src="https://www.pepperoni.blog/assets/main/js/breakpoints.min.js?v=MJ_rzW1uDPCSIBUy"></script>
499
500    
500<script src="https://www.pepperoni.blog/assets/main/js/util.js?v=wuHnKw3jVvbOGE46"></script>
500
501    
501<script src="https://www.pepperoni.blog/assets/main/js/main.js?v=o7i8BOgNHgFtAZ66"></script>
501
502
503    
503<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/prism.min.js"></script>
vendor: 1 bytes, line 503
503
504<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-javascript.min.js"></script>
vendor: 1 bytes, line 504
504
505<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-csharp.min.js"></script>
vendor: 1 bytes, line 505
505
506<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-sql.min.js"></script>
vendor: 1 bytes, line 506
506
507<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-fsharp.min.js"></script>
vendor: 1 bytes, line 507
507
508<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-vbnet0.min.js"></script>
508
509
510<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"fdde324fc3374f78a38d03a770fdfcfc","r":1,"spa":2}' crossorigin="anonymous"></script>
510
511</body>
512</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.