elixir initialise struct with another struct - struct

What if I have the following struct:
%Registration{email: "john#gmail.com", first_name: "John", last_name: "Doe"}
And I want to create two different structs from it:
%Account{email: "john#gmail.com", password: nil, ...}
%Profile{first_name: "John", last_name: "Doe", age: nil, ...}
I have already taken a look at this question, but if I do
registration = %Registration{email: "john#gmail.com", first_name: "John", last_name: "Doe"}
struct(Account, registration)
I receive protocol Enumerable not implemented for %Registration{...
So I have to use Map.from_struct
registration = %Registration{email: "john#gmail.com", first_name: "John", last_name: "Doe"}
struct(Account, Map.from_struct(registration))
But I think it is not very clean. So I was just thinking is there a better way to do this? This should be quite a common problem, right?

As per the documentation:
Converts a struct to map.
It accepts the struct module or a struct itself and simply removes the
struct field from the struct.
The function exists for a reason. I don't see the way you did it as being a problem really. If it is a common problem I haven't seen it before. If you want to have a more expressive or cleaner code you may try:
registration = %Registration{email: "john#gmail.com", first_name: "John", last_name: "Doe"} |> Map.from_struct
struct(Account, registration)
or other equivalent code by checking the docs here.

You can do it easily, but in a bit hacky way - just delegate protocol functions to Enumerable.Map:
defmodule Contact do
defstruct [:name, group: "", version: "", status: :latent]
defimpl Enumerable, for: Contact do
defdelegate([count(e), member?(e, m), reduce(e, a, f)],
to: Enumerable.Map)
end
end
or in a bit more proper way like
defmodule Contact do
defstruct [:name, group: "", version: "", status: :latent]
defimpl Enumerable do
alias Enumerable, as: E
def count(c), do: Map.from_struct(c) |> E.count()
def member?(c, m), do: Map.from_struct(c) |> E.member?(m)
def reduce(c, a, f), do: Map.from_struct(c) |> E.reduce(a, f)
end
end
Enumerable.count implementation can be generated with macro to return struct members count which is already known at compile time.
Enumerable.member? can also be generated with macro or written by hand or delegated again to Enumerable.Map.member? to remove runtime penalty of Map.from_struct.
All the code above is generic so it can be placed to use-able module like
defmodule StructEnumerable do
defmacro __using__(opts \\ []) do
quote do
defimpl Enumerable do
alias Enumerable, as: E
def count(c), do: Map.from_struct(c) |> E.count()
def member?(c, m), do: Map.from_struct(c) |> E.member?(m)
def reduce(c, a, f), do: Map.from_struct(c) |> E.reduce(a, f)
end
# you can also add following code
# to provide Fetch behaviour implementation
# for your structs
defdelegate([
fetch(t, k),
get_and_update(t, k, l)
], to: Map)
end
end
end
and embed it like
defmodule Contact do
defstruct [:name, group: "", version: "", status: :latent]
use StructEnumerable
end

Related

How to annotate attrs field with validator?

I am having trouble annotating attrs class attribute.
I am using NewType for defining new UserId type and attrs frozen classes.
This is code where mypy doesn't complain and everything is alright:
from typing import NewType
from attr import frozen, field
UserId = NewType("UserId", str)
#frozen
class Order:
id: UserId = field()
mypy does not have any issues when checking this code.
The problem appears after using validators from attrs.
from typing import NewType
from attr import frozen, field, validators
UserId = NewType("UserId", str)
#frozen
class Order:
id: UserId = field(validator=validators.matches_re("^\d+$"))
mypy now complains about incorrect types:
project/test_annotation.py:10: error: Incompatible types in assignment
(expression has type "str", variable has type "UserId") [assignment]
Found 1 error in 1 file (checked 1 source file)
I don't understand how field() returns string type right now.
Can somebody explain that? Also, how can we work around this problem?
env:
Python 3.10.6
attrs==22.1.0
cattrs==22.2.0
To make it happy, cast. field is considerably complicated, your field returns a str thanks to this overload:
... # other overloads
# This form catches an explicit None or no default and infers the type from the
# other arguments.
#overload
def field(
*,
default: None = ...,
validator: Optional[_ValidatorArgType[_T]] = ...,
repr: _ReprArgType = ...,
hash: Optional[bool] = ...,
init: bool = ...,
metadata: Optional[Mapping[Any, Any]] = ...,
converter: Optional[_ConverterType] = ...,
factory: Optional[Callable[[], _T]] = ...,
kw_only: bool = ...,
eq: Optional[_EqOrderType] = ...,
order: Optional[_EqOrderType] = ...,
on_setattr: Optional[_OnSetAttrArgType] = ...,
alias: Optional[str] = ...,
) -> _T: ...
It basically says "when validators is a [sequence of or one of] validators working on some type T, then field returns T".
So, you pass a validator that works on str, and thus field type is str as well. NewType("UserID", str) is not a subtype of str, so this assignment fails. You have two major options:
cast to desired type:
from typing import cast
...
#frozen
class Order:
id: UserId = cast(str, field(validator=validators.matches_re("^\d+$")))
create your own validator. You don't need to replicate the logic, only change the signature and call original implementation with type: ignore or casts.
from typing import TYPE_CHECKING, Callable, Match, Optional, Pattern, Union, cast
if TYPE_CHECKING:
from attr import _ValidatorType
def userid_matches_re(
regex: Union[Pattern[str], str],
flags: int = ...,
func: Optional[
Callable[[str, str, int], Optional[Match[str]]]
] = ...,
) -> '_ValidatorType[UserID]':
return cast('_ValidatorType[UserID]', validators.matches_re(regex, flags, func))
... and use it in your class instead of validators.matches_re. The signature above is stolen from stubs with AnyStr replaced with str, because you don't allow bytes anyway.
I'd recommend the first variant, because another solution is just more boilerplate with the same cast as a result, it gives you no more safety. However, it may be viable if you have many fields with this validator.

Groovy - Replace ${} within a string

I receive a string like this: "The code is ${code}. Your currency is ${currency}".
The ${...} characters are already part of the string, not variables.
I have a map with key-value and I would like to replace all the occurrences of the ${key} with the value:
def myMap = [
'code' : '123456',
'currency' : 'CHF'
]
myMap.each{
fieldValue.replaceAll("${$.key}", it.value)
}
The expected result is the following one: "The code is 123456. Your currency is CHF".
I'm stuck because I don't know how to deal with the $ {} character escapes. I can escape the $ character but not {}. Any idea in order to achieve the expected result?
You need to quote the outer $ and use it.key: "\\${$it.key}"
Also you can use each here as it is for side-effects and replaceAll
does not modify the string (strings in java are immutable). So you need
something like this:
def result = myMap.inject(fieldValue){ acc, kv ->
acc.replaceAll("\\${$kv.key}", kv.value)
}
Or by using a regexp:
fieldValue.replaceAll(/\$\{(.+?)\}/, { _, k -> myMap[k] })
It works also with closure and with delegate strategy. You can evaluate your string in the context of the map. See this example:
def myMap = [
'code' : '123456',
'currency' : 'CHF'
]
closure = { "The code is ${code}. Your currency is ${currency}" }
closure.delegate = myMap
println closure()

(Swift 4.x+) What are the rules for naming parameters in structs’ initializers?

I have been doing an intense - yet basic apparently - study on structs in these last days and one of the things I cannot understand is why one would ever name parameters in an initializer differently from their original name.
I know that is possible, that it is allowed but, in practice, I have always seen shadowing instead.
For example:
struct Person {
var name: String
var age: Int
init(firstName: String, ancientness: Int) {
self.name = firstName
self.age = ancientness
}
}
Apart from the absurd fun one would have in inventing idiotic names, is there a truly practical reason why one would ever do such a thing?
Thank you
The short answer is no. The long answer is when creating a custom structure you don't even have to provide a custom initializer. The struct will provide it for you. Not related to your question but you should always declare your properties as constants. If you need a different value create a new structure with the values updated from the old instance. Just create a "plain" structure:
struct Person {
let name: String
let age: Int
}
This will provide a the default initializer with the following signature:
Person.init(name: String, age: Int)
If you were gonna provide yourself the same initializer for that structure it would be written as:
init(name: String, age: Int) {
self.name = name
self.age = age
}
the final thoughts
There is no reason to do such thing. You should keep your initializers names matching the name of the properties that they will be assigned to. The only "advantage" of choosing a different name is not having to explicitly call self inside the initializer.
In your example it would suffice
init(firstName: String, ancientness: Int) {
name = firstName
age = ancientness
}
but not on mine
init(name: String, age: Int) {
name = name // Cannot assign to value: 'name' is a 'let' constant
age = age // Cannot assign to value: 'name' is a 'let' constant
}
A Truly practical reason?
The only one I can see is dropping the self which can already be done 99% of the time already when coding in Swift. I actually like a lot to use the shadowing whenever it is possible in all of my answers. You can see it at this post Swift Array instance method drop(at: Int) where a local var indexshadowing the collection method index<T: Comparable>(_ T, offsetBy: T, limitedBy: T).
Or at this post Swift: second occurrence with indexOf a classic shadowing example
var startIndex = self.startIndex
Where you can refer to startIndex local method variable or the collection's instance property adding the self prefix self.startIndex.

Alloy signatures not shown in Alloy Analyzer 4.2

I have to use Alloy in a Requirements Analysis and Specification Document for a university project. I started with the easy stuff, only signatures and no facts. These are the signatures I use:
abstract sig Date{
year: one Int,
month: one Int,
day: one Int
}
abstract sig Time{
h: one Int,
m: one Int,
s: one Int
}
abstract sig Double{
leftPart: one Int,
rightPart: one Int
}
abstract sig Bool{
value: one String
}
sig DateBirth extends Date{}
sig DateRide extends Date{}
sig DateExpiry extends Date{}
abstract sig User {
email: one String,
name: one String,
surname: one String,
gender: one Bool,
address: one String,
password: one String,
dateOfBirth: one DateBirth,
IDRide: set Ride
}
sig TaxiDriver extends User{
taxiLicense: one String,
personalLicense: one String,
IBAN: one String,
positionInQueue: lone Int,
IDTaxi: set Taxi
}
sig Client extends User{
}
sig Zone {
numberOfZone: one Int,
vertexNorthWest: one Double,
vertexNorthEast: one Double,
vertexSouthWest: one Double,
vertexSouthEast: one Double,
currentQueue: set TaxiDriver
}
sig Taxi {
IDTaxi: one String,
plate: one String,
availablePlaces: one Int,
}
sig Ride {
IDRide: one String,
origin: one String,
destination: one String,
dateOfRide: one DateRide,
timeOfDeparture: one Time,
timeOfArrival: one Time,
price: one Double,
numberOfPeople: one Int,
accepted: one Bool,
userEmail: set User
}
sig Credit_Card {
number: Double,
owner: String,
expiryDate: DateExpiry,
ownerEmail: one Client
}
Then, I added the predicate "show" to veify whether the it is consistent or not:
pred Show{}
run Show for 10
After running "Execute" on Alloy Analyzer 4.2 this is the message I get:
Executing "Run Show for 10"
Solver=sat4j Bitwidth=4 MaxSeq=7 SkolemDepth=1 Symmetry=20
21067 vars. 3840 primary vars. 37164 clauses. 376ms.
Instance. found. Predicate is consistent. 375ms.
No problems, right? But then, when I click on "Show" there are no instances of the signature "User" (and its child signatures) shown on the display, while all the others are there. I tried to click on "Next" a gazillion times to try to see if maybe I could find a model in which they were shown, but there weren't any.
Any idea/suggestion? Thanks!
It's probably because of the use of String. As far as I know, String is a reserved word in Alloy, but it is not really implemented at this point. Try to remove the String fields or replace them with something else.
On a more general note, Alloy is not so much about modelling real data (ints, bools and strings), but more about modelling structure, i.e. relationships between entities. For the analysis of structure, you usually don't need concrete data types.
The purpose of building an Alloy model is to capture the essence of a design or system and explore subtle properties. You don't want to include all the details you'd find in a database schema. Your model has lots of implementation details too, such as the ids (which aren't needed since they're implicit in the object identities), and the use of strings instead of conceptual types -- destination, eg, should have a type such as "Location".
So I'd recommend that you start again, and think first about what kinds of questions you'd like this model to answer.
Thanks to everyone, removing strings solved the problem.
However, my "distorted" vision about Alloy's purpose was due to the fact that we were asked to use it but we weren't given a real explanation on how to use it, in most examples all details were written. I guess I'll have to try and study it a bit more!

Generating String from List in Erlang

I'm trying to generate a formatted string based on a list:
[{"Max", 18}, {"Peter", 25}]
To a string:
"(Name: Max, Age: 18), (Name: Peter, Age: 35)"
The first step is to make a function that can convert your {Name, Age} tuple to a list:
format_person({Name, Age}) ->
lists:flatten(io_lib:format("(Name: ~s, Age: ~b)", [Name, Age])).
The next part is simply to apply this function to each element in the list, and then join it together.
format_people(People) ->
string:join(lists:map(fun format_person/1, People), ", ").
The reason for the flatten is that io_lib returns an iolist and not a flat list.
If performance is important, you can use this solution:
format([]) -> [];
format(List) ->
[[_|F]|R] = [ [", ","(Name: ",Name,", Age: ",integer_to_list(Age)|")"]
|| {Name, Age} <- List ],
[F|R].
But remember that it returns io_list() so if you want see result, use lists:flatten/1. It is way how to write very efficient string manipulations in Erlang but use it only if performance is far more important than readability and maintainability.
A simple but slow way:
string:join([lists:flatten(io_lib:format("(~s: ~p)", [Key, Value])) || {Key,Value} <- [{"Max", 18}, {"Peter", 25}]], ", ").
is it JSON?
use some already written modules in e.g mochiweb.

Resources