-
-
Notifications
You must be signed in to change notification settings - Fork 113
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
b554615
99d7aea
23d94b1
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. |
| 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,35 @@ | ||
| 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 !@#$%^&\* | ||
|
|
||
| ## 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. | ||
| 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 the given password fails multiple rules, the `PasswordError` value should represent all of the failing rules. | ||
|
|
||
| ```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.
Contributor
Author
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. Done. |
||
|
|
||
| ```fsharp | ||
| getStatusPhrases (Error PasswordError.MissingDigit ||| PasswordError.LessThan12Characters) | ||
| // => Set ["12 characters"; "digit"] | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # 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 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 | ||
| ``` | ||
|
|
||
| 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/
Contributor
Author
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 think you're right -- probably some kind of map/filter combination. I will also try to come up with something. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| module ImprovedPasswordChecker | ||
|
|
||
| open System | ||
|
|
||
| [<Flags>] | ||
| type PasswordError = | ||
| | None = 0 | ||
| | 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 errors = ( | ||
| [ | ||
| ((fun (s: string) -> s.Length >= 12), PasswordError.LessThan12Characters); | ||
| (String.exists System.Char.IsDigit, PasswordError.MissingDigit); | ||
| (String.exists System.Char.IsLower, PasswordError.MissingLowercaseLetter); | ||
| (String.exists System.Char.IsUpper, PasswordError.MissingUppercaseLetter); | ||
| (String.exists (fun c -> "!@#$%^&*".Contains c), PasswordError.MissingSymbol) | ||
| ] | ||
| |> List.filter (fun (test, _) -> password |> test |> not) | ||
| |> List.map snd | ||
| |> List.fold (|||) PasswordError.None | ||
| ) | ||
| if errors = PasswordError.None then | ||
| Ok password | ||
| else | ||
| Error errors | ||
|
|
||
| /// Return a list of human-readable phrases indicating the meaning of the given result value. | ||
| let getStatusPhrases (result: Result<string, PasswordError>) : string list = | ||
| match result with | ||
| | Error e -> ( | ||
| [ | ||
| (PasswordError.LessThan12Characters, "12 characters"); | ||
| (PasswordError.MissingUppercaseLetter, "uppercase letter"); | ||
| (PasswordError.MissingLowercaseLetter, "lowercase letter"); | ||
| (PasswordError.MissingDigit, "digit"); | ||
| (PasswordError.MissingSymbol, "symbol") | ||
| ] | ||
| |> List.filter (fun (flag, _) -> e.HasFlag(flag)) | ||
| |> List.map snd | ||
| ) | ||
| | _ -> [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "authors": [ | ||
| "blackk-foxx" | ||
| ], | ||
| "contributors": [ | ||
| "ErikSchierboom" | ||
| ], | ||
| "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 list of human-readable phrases indicating the meaning of the given result value. | ||
| let getStatusPhrases (result: Result<string, PasswordError>) : string list = | ||
| 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.
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's already mentioned on line 20. Do you think additional verbiage is needed?