-
-
Notifications
You must be signed in to change notification settings - Fork 112
Add Flags Attribute concept exercise #1387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1103977
a80fd1a
08b80ff
cec664d
8a197fc
e19d68e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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"] | ||
| } |
| 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 |
| 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. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 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" | ||
| } | ||
| ] |
| 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. | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
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`. | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"] | ||||||||||
| ``` | ||||||||||
| 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. | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| ```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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| 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 | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
| 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 | ||
|
|
||
|
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> = | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is returning a |
||
| 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> |
There was a problem hiding this comment.
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.