Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions concepts/flag-discriminated-unions/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"blurb": "The Flags attribute allows the values defined in a discriminated union to be represented as bit positions (i.e. flags).",
"authors": ["blackk-foxx"],
"contributors": ["ErikSchierboom"]
}
41 changes: 41 additions & 0 deletions concepts/flag-discriminated-unions/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# About

The Flags attribute allows the values defined in a discriminated union to be represented as bit positions (i.e. flags).
As flags, such values can be combined, making it possible for multiple boolean conditions to be represented in a single value.

```fsharp
[<Flags>]
type PhoneFeatures =
| Call = 1
| Text = 2
```

```fsharp
[<Flags>]
type PhoneFeaturesBinary =
| Call = 0b00000001
| Text = 0b00000010
```

Setting a flag can be done with the bitwise OR operator (`|||`); unsetting a flag can be done with a combination of the bitwise AND operator (`&&&`) and the bitwise negation operator (`~~~`).
While checking flag's state can be done with the bitwise AND operator, one can also use the HasFlag() method.

```fsharp
let features = PhoneFeatures.Call

// Set the Text flag
let moreFeatures = features ||| PhoneFeatures.Text

moreFeatures.HasFlag(PhoneFeatures.Call) // => true
moreFeatures.HasFlag(PhoneFeatures.Text) // => true

// Unset the Call flag
let lessFeatures = features &&& ~~~PhoneFeatures.Call

lessFeatures.HasFlag(PhoneFeatures.Call) // => false
lessFeatures.HasFlag(PhoneFeatures.Text) // => true
```

See [Summary of Bitwise Operators][bitwise-operators] for a complete list of the bitwise operators available in the F# language.

[bitwise-operators]: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/symbol-and-operator-reference/bitwise-operators#summary-of-bitwise-operators
18 changes: 18 additions & 0 deletions concepts/flag-discriminated-unions/introduction.md

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth adding a mention that flags can be set or unset using bitwise operators.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Introduction

A common way to use discriminated union type in F# is to represent a fixed set of named constants -- a structure called an "enum" in other languages.

Normally, in such a discriminated union, each case can only refer to exactly one of those named constants.
However, sometimes it is useful to refer to more than one constant.
To do so, one can annotate the discriminated union with the Flags attribute.

A discriminated union with the Flags attribute can be defined as follows (using binary integer notation 0b):

```fsharp
[<Flags>]
type PhoneFeatures =
| Call = 0b00000001
| Text = 0b00000010
```

A `PhoneFeatures` instance with the value 0b00000011 has both its Call and Text flags set.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A `PhoneFeatures` instance with the value 0b00000011 has both its Call and Text flags set.
A `PhoneFeatures` instance with the value `0b00000011` has both its `Call` and `Text` flags set.

6 changes: 6 additions & 0 deletions concepts/flag-discriminated-unions/links.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
{
"url": "https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-flagsattribute",
"description": "Flags attribute"
}
]
18 changes: 18 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,19 @@
"basics",
"pattern-matching"
]
},
{
"slug": "improved-password-checker",
"name": "Improved Password Checker",
"uuid": "094e458d-14bc-43db-af9c-3fd951a6c635",
"concepts": [
"flag-discriminated-unions"
],
"prerequisites": [
"basics",
"pattern-matching",
"results"
]
}
],
"practice": [
Expand Down Expand Up @@ -2360,6 +2373,11 @@
"slug": "discriminated-unions",
"name": "Discriminated Unions"
},
{
"uuid": "174f8c5e-e947-4e92-a0a5-1530f32b90ce",
"slug": "flag-discriminated-unions",
"name": "Flag Discriminated Unions"
},
{
"uuid": "1327d780-3263-4a22-b363-684d775241b8",
"slug": "floating-point-numbers",
Expand Down
1 change: 1 addition & 0 deletions exercises/Exercises.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<Project Path="concept/booking-up-for-beauty/BookingUpForBeauty.fsproj" />
<Project Path="concept/cars-assemble/CarsAssemble.fsproj" />
<Project Path="concept/guessing-game/GuessingGame.fsproj" />
<Project Path="concept/improved-password-checker/ImprovedPasswordChecker.fsproj" />
<Project Path="concept/interest-is-interesting/InterestIsInteresting.fsproj" />
<Project Path="concept/log-levels/LogLevels.fsproj" />
<Project Path="concept/lucians-luscious-lasagna/LuciansLusciousLasagna.fsproj" />
Expand Down
Empty file.
37 changes: 37 additions & 0 deletions exercises/concept/improved-password-checker/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
The goal of this exercise is to improve upon the Password Checker exercise.
Since a given password will likely violate more than one rule at a time, a useful password checker ought to communicate to the user all the rules that are violated, instead of just the first one that happens to be discovered.
The improved password checker should indicate all of the rules being violated by a given password in one go.

The rules for this password checker are the same as in the previous Password Checker exercise:

- Must have 12 or more characters
- Must have at least one uppercase letter
- Must have at least one lowercase letter
- Must have at least one digit
- Must have at least one symbol in the set !@#$%^&\*

Your solution must use a `Result` to encapsulate the success or failure status.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These instructions are specific to task 2 (the other tasks don't use results), so should probably be moved to that task (and I've already added some alternative text there).

For the success case, the `Result` must convey the validated password as a string.
For the failure case, the `Result` must indicate all of the violated rules.

## 1. Modify the `PasswordError` discriminated union to allow the individual values to be treated as flags

Note that the tests will not compile until this essential step is complete.

## 2. Implement the `checkPassword` function

The `checkPassword` function checks the given password against the aforementioned rules. On failure, it indicates the rule(s) that was/were violated by encapsulating one or more of the `PasswordError` values within the result value.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each sentence should be on a separate line. And maybe we can be slightly more specific on what should be returned?

Suggested change
The `checkPassword` function checks the given password against the aforementioned rules. On failure, it indicates the rule(s) that was/were violated by encapsulating one or more of the `PasswordError` values within the result value.
The `checkPassword` function checks the given password against the aforementioned rules.
The function should return a `Result` value, where `Ok` is returned when the password satisfies all rules, and an `Error` value when it fails one or more rules.
If multiple rules fail, the `PasswordError` value should represent all those failing rules.

Or something like that.


```fsharp
checkPassword "abcdefghijk5"
// => Error (PasswordError.MissingUppercaseLetter ||| PasswordError.MissingSymbol)
```

## 3. Implement the ``getStatusPhrases` function

The `getStatusPhrases` function returns a set of strings each containing a human-readable phrase corresponding to one of the erorrs in the result returned from `checkPassword`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned elsewhere, I'd suggest making this a simple list.


```fsharp
getStatusPhrases (Error PasswordError.MissingDigit ||| PasswordError.LessThan12Characters)
// => Set ["12 characters"; "digit"]
```
41 changes: 41 additions & 0 deletions exercises/concept/improved-password-checker/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Introduction

The Flags attribute allows the values defined in a discriminated union to be represented as bit positions (i.e. flags).
As flags, such values can be combined, making it possible for multiple boolean conditions to be represented in a single value.

```fsharp
[<Flags>]
type PhoneFeatures =
| Call = 1
| Text = 2
```

```fsharp
[<Flags>]
type PhoneFeaturesBinary =
| Call = 0b00000001
| Text = 0b00000010
```

Setting a flag can be done with the bitwise OR operator (`|||`); unsetting a flag can be done with a combination of the bitwise AND operator (`&&&`) and the bitwise negation operator (`~~~`).
While checking flag's state can be done with the bitwise AND operator, one can also use the HasFlag() method.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
While checking flag's state can be done with the bitwise AND operator, one can also use the HasFlag() method.
While checking a flag's state can be done with the bitwise AND operator, one can also use the `HasFlag()` method.


```fsharp
let features = PhoneFeatures.Call

// Set the Text flag
let moreFeatures = features ||| PhoneFeatures.Text

moreFeatures.HasFlag(PhoneFeatures.Call) // => true
moreFeatures.HasFlag(PhoneFeatures.Text) // => true

// Unset the Call flag
let lessFeatures = features &&& ~~~PhoneFeatures.Call

lessFeatures.HasFlag(PhoneFeatures.Call) // => false
lessFeatures.HasFlag(PhoneFeatures.Text) // => true
Comment on lines +31 to +36

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd remove this as I feel confident student should be able to figure this out based on the above example and what is in the other introduction text.

Suggested change
// Unset the Call flag
let lessFeatures = features &&& ~~~PhoneFeatures.Call
lessFeatures.HasFlag(PhoneFeatures.Call) // => false
lessFeatures.HasFlag(PhoneFeatures.Text) // => true

```

See [Summary of Bitwise Operators][bitwise-operators] for a complete list of the bitwise operators available in the F# language.

[bitwise-operators]: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/symbol-and-operator-reference/bitwise-operators#summary-of-bitwise-operators
55 changes: 55 additions & 0 deletions exercises/concept/improved-password-checker/.meta/Exemplar.fs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that there might be a more idiomatic F# solution here without using mutable. I'll try and come up with one/

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module ImprovedPasswordChecker

open System

[<Flags>]
type PasswordError =
| LessThan12Characters = 1
| MissingUppercaseLetter = 2
| MissingLowercaseLetter = 4
| MissingDigit = 8
| MissingSymbol = 16

/// Validate the given password against the rules defined in the instructions. If it meets all
/// of the rules, return a result indicating success; otherwise return a result indicating
/// failure with an error value indicating all of the rules that were violated.
let checkPassword (password: string) : Result<string, PasswordError> =
let mutable errors = (
PasswordError.LessThan12Characters |||
PasswordError.MissingUppercaseLetter |||
PasswordError.MissingLowercaseLetter |||
PasswordError.MissingDigit |||
PasswordError.MissingSymbol
)
if password.Length >= 12 then
errors <- errors &&& ~~~PasswordError.LessThan12Characters
for (charTest, flag) in [
(System.Char.IsUpper, PasswordError.MissingUppercaseLetter);
(System.Char.IsLower, PasswordError.MissingLowercaseLetter);
(System.Char.IsDigit, PasswordError.MissingDigit);
((fun c -> "!@#$%^&*".Contains c), PasswordError.MissingSymbol)
] do
if password |> String.exists charTest then
errors <- errors &&& ~~~flag

if int errors = 0 then
Ok password
else
Error errors

/// Return a set of human-readable phrases indicating the meaning of the given result value.
let getStatusPhrases (result: Result<string, PasswordError>) : Set<string> =
let mutable phrases: Set<string> = Set [ ]
match result with
| Error errors ->
for (flag, phrase) in [
(PasswordError.LessThan12Characters, "12 characters");
(PasswordError.MissingUppercaseLetter, "uppercase letter");
(PasswordError.MissingLowercaseLetter, "lowercase letter");
(PasswordError.MissingDigit, "digit");
(PasswordError.MissingSymbol, "symbol")
] do
if errors.HasFlag(flag) then
phrases <- phrases.Add(phrase)
| Ok _ -> ()
phrases
17 changes: 17 additions & 0 deletions exercises/concept/improved-password-checker/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"authors": [
"blackk-foxx"
],
"files": {
"solution": [
"ImprovedPasswordChecker.fs"
],
"test": [
"ImprovedPasswordCheckerTests.fs"
],
"exemplar": [
".meta/Exemplar.fs"
]
},
"blurb": "Learn how to use the Flags attribute to combine values within a discriminated union"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module ImprovedPasswordChecker

type PasswordError =
| LessThan12Characters
| MissingUppercaseLetter
| MissingLowercaseLetter
| MissingDigit
| MissingSymbol

Comment thread
blackk-foxx marked this conversation as resolved.
/// Validate the given password against the rules defined in the instructions. If it meets all
/// of the rules, return a result indicating success; otherwise return a result indicating
/// failure with an error value indicating all of the rules that were violated.
let checkPassword (password: string) : Result<string, PasswordError> =
failwith "Please implement this function"

/// Return a set of human-readable phrases indicating the meaning of the given result value.
let getStatusPhrases (result: Result<string, PasswordError>) : Set<string> =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is returning a Set, which means that the student has to know about sets. So either it needs to be a prerequisite, or we should use a list here (which is my preference).

failwith "Please implement this function"
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>Exercism</RootNamespace>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<Compile Include="ImprovedPasswordChecker.fs" />
<Compile Include="ImprovedPasswordCheckerTests.fs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="xunit.v3" Version="3.2.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageReference Include="FsUnit.xUnit" Version="7.1.1" />
<PackageReference Include="Exercism.Tests.xunit.v3" Version="0.1.0-beta1" />
</ItemGroup>

</Project>
Loading