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
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.
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.
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.

```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`.

```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.

```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
55 changes: 55 additions & 0 deletions exercises/concept/improved-password-checker/.meta/Exemplar.fs
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> =
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