I am trying to migrate a library from Elm 0.15 to 0.16. The record extension mechanism has been removed.
My library offers physics calculations on bodies (represented as record), and uses record extension to allow users to add labels and other metadata about the bodies.
My example code shows this use by adding a label to all the bodies after their creation:
labeledBodies = map (\b -> { b | label = bodyLabel b.restitution b.inverseMass }) someBodies
This list of labeled bodies is also passed to the library:
main = Signal.map scene (run labeledBodies tick)
What sort of works: hardcoding a meta parameter into the Body like this:
type alias Body a = {
pos: Vec2, -- reference position (center)
velocity: Vec2, -- direction and speed
inverseMass: Float, -- we usually use only inverse mass for calculations
restitution: Float, -- bounciness factor
shape: Shape,
meta: a
}
But this makes the API more clumsy because it forces the helper functions to take an extra parameter. Is there a more elegant way to deal with this change?
What if the meta field had type Dict String String? Then you wouldn't have to do any crazy type variables clumsiness. However, you lose guarantees that all the records you pass in do indeed have labels, so you have to work with a Maybe String when you do Dict.get "label" r.meta.
What about using a tagged union type?
type BodyWithMeta =
LabeledBody Body String
labeledBodies = map (\b -> LabeledBody b (bodyLabel b.restitution b.inverseMass)) someBodies
Related
I'm new to Elm (version 0.19).
On thing that's bugging me is the huge list of arguments I'm passing around. I think the problem is due to my OOP way of thinking. In my code I have a bunch of helper functions that require access to my model (TEA). I have been using let / in syntax in the view function to define these helpers as this gives them access to the model argument. However I have 10+ helper functions and I'm constantly passing them around, it makes my code look ugly and hard to comprehend. In OOP these helper functions would all be methods on some object that I would pass instead.
Code snippet below is a contrived example that uses elm-ui. Full example can be run on Ellie
Element.layout []
<| column
[ w |> px |> width
, h |> px |> height
, blueBg
, centerX
, centerY
]
[ el [centerX, centerY, whiteTxt, fontSize 40] <| text "Hello world"
, header w h scale whiteTxt space blueBg pad radius whiteBg fontSize blackTxt greyBg blueTxt
]
header w h scale whiteTxt space blueBg pad radius whiteBg fontSize blackTxt greyBg blueTxt =
-- code here
el [] Element.none
Whats the best way to do this?
There are several strategies for this:
Use types to abstract away data complexity. Model your problem using types, and then create simple functions to operate on those types.
Define constants and helper functions at higher levels so that they are in scope for functions, rather than passing them in as a parameter.
Narrow down function parameters to only what is strictly needed for that function.
In the header example, you might provide your own types for the style, title and subtitle:
type alias Title = String
type alias SubTitle = String
header : HeaderStyle -> Title -> SubTitle -> Element Msg
When you do have lots of information to pass around, records are usually the first go-to for holding data. For example, if you're reusing the header function multiple times with different styling, then a HeaderStyle record type might be useful:
type alias HeaderStyle = { borderColor : Color
, textColor: Color
, backgroundColor : Color
...
}
Now since the title and subtitle are part of the header you could also pull it all into one record:
type alias HeaderData = { borderColor : Color
, textColor: Color
, backgroundColor : Color
, bannerText : String
, bannerImage : Url
, headerTitle : Title
, headerSubTitle : SubTitle
...
}
If we pay attention to the types supplied by the library, we can see that for elm-ui it might make more sense to keep the styles in a list to match their types. Modifying our record, we get:
type alias HeaderData = { styleList : List (Attribute Msg)
, headerTitle : Title
, headerSubTitle : SubTitle
...
}
The advantage here is that we can extract the style list and use it directly in a function from the Element module by calling the automatic function styleList: HeaderData -> List (Attribute Msg).
The disadvantage is that we've lost our nice compiler error message if someone makes a HeaderData missing some key styles.
Either way, now our header function is down to only one input.
header : HeaderData -> Element Msg
Great, but this still means that every time we run header we need to populate a HeaderData. How can we simplify that?
One strategy is to use constants at the top level, and use helper functions / partial application to apply these constants.
We can for example define a top level constant and helper function that lets us create various predefined headers:
pageStyle : List (Attribute Msg)
pageStyle = [ Border.color <| rgb255 35 97 146
, Font.color <| rgb255 35 97 146 ]
redHeaderStyle : Title -> HeaderData
redHeaderStyle title =
{ styleList : pageStyle
++ [ Background.color red
, Font.color black
...
]
, headerTitle : title
}
Here pageStyle can be used elsewhere, and readHeaderStyle adds to it for this specific case. Note that we've left one parameter for later, since the title may change for each application.
Of course in Elm you are not restricted to lists and records - you could also use product and sum types for Header. When to use each type is going to depend on things like where you want the type safety and how you want to compose functions.
So to summarize:
Start by modelling your problem domain using types, rather than thinking in terms of objects or components.
Don't try to make everything generic and reusable. Instead create types that hold information, and use helper functions so you only have to worry about what's relevant.
Don't be afraid of defining constants and helper functions at the top-level. Limit your use of let..in constructs to improving readability, rather than for grouping definitions as though they're methods.
Write helper functions for your types on an as-needed basis, rather than trying to create a library of every possible functionality.
If you want to compartmentalise similar functions and types, you can use modules and then import them qualified to a convenient name. This way you can say e.g. Background.color and Element.row.
Finally, Scott Wlashchin gives a good talk on design strategies in functional programming: Functional programming design patterns by Scott Wlaschin
In Power Query (M) I've found 2 ways to declare types: myVar as type or type text
Each seems to apply to different contexts. For example:
Table.AddColumn(myTable, "NewName", each [aColumn], type text)
or
MyFunc = (aParam as any) as date => Date.From(aParam)
However, this doesn't work as I expect for more complex types, like {text} or {number}, which would be a list of only text values or only numbers. I can use these types with the type syntax, but not the as type syntax.
Why/not?
Also, does declaring types in M have any performance impact, or is it just to raise an error if an incorrect type is passed/returned?
Declaring types in "M" should normally have very little performance impact, and will make your functions more "self-documenting".
When a function is invoked, the function arguments type "kind" is checked, and not the custom, full type definition. So passing a list of numbers to a function that expects list-of-text doesn't cause any errors. You can see that with some "M":
let
FunctionType = type function (l as { text }) as any,
UntypedFunction = (l) => l{0},
TypedFunction = Value.ReplaceType(UntypedFunction, FunctionType),
Invoked = TypedFunction({0, 1, 2})
in
Invoked
Not checking the recursive type is good for performance because checking each element of a list would require looping through the whole list.
When you write a function value like (l) => l{0} you can only use primitive types like as list and not as { text }. I think this limitation is intended to guide the function author into not putting type restrictions that won't be honored by the function.
You can read more about what the syntax allows in the Language Specification. (If that link dies you should be able to follow the PDF link from MDSN.)
I'm coming from a Node.js background, and there a typical pattern is to have a function which takes an options object, i.e. an object where you set properties for optional parameters, such as:
foo({
bar: 23,
baz: 42
});
This is JavaScript's "equivalent" to optional and named parameters.
Now I have learnt that there are no optional parameters in Go, except variadic parameters, but they lack the readability of named parameters. So the usual pattern seems to be to hand over a struct.
OTOH a struct can not be defined with default values, so I need a function to set up the struct.
So I end up with:
Call a function that creates the struct and then fills it with default values.
Overwrite the values I would like to change.
Call the function I actually want to call and hand over the struct.
That's quite complicated and lengthy compared to JavaScript's solution.
Is this actually the idiomatic way of dealing with optional and named parameters in Go, or is there a simpler version?
Is there any way that you can take advantage of zero values? All data types get initialized to a zero value, so that is a form of default logic.
An options object is a pretty common idiom. The etcd client library has some examples (SetOptions,GetOptions,DeleteOptions) similar to the following.
type MyOptions struct {
Field1 int // zero value (default) of int is 0
Field2 string // zero value (default) of string is ""
}
func DoAction(arg1, arg2 string, options *MyOptions){
var defaultValue1 int = 30 // some reasonable default
var defaultValue2 string = "west" // some reasonable default
if options != nil {
defaultValue1 = options.Field1 // override with our values
defaultValue2 = options.Field2
}
doStuffWithValues
An relevant question (and very much in the mindset of Go) would be, do you need this kind of complexity? The flexibility is nice, but most things in the standard library try to only deal with 1 default piece of info/logic at a time to avoid this.
I've been looking for answers to this problem, but unfortunately without success.
I'm developing a mathematical app (Swift-based), which keeps data of every function the user enters.
(I then need to draw the functions on an NSView using a Parser)
The data structure is saved into a Dictionary but I'm not able to add values and keys.
The Dictionary is initialized like:
var functions = [String : [[String : NSBezierPath], [String : NSColor], [String : CGFloat], [String : Bool]]]();
//1A.The String key of the main Dictionary is the value of the function, such as "sin(x)"
//1B.The value of the `Dictionary` is an `Array` od `Dictionaries`
//2.The first value is a dictionary, whose key is a String and value NSBezierPath()
//3.The second value is a dictionary, whose key is a String and value NSColor()
//4.The third value is a dictionary, whose key is a String and value CGFloat()
//5.The first value is a dictionary, whose key is a String and value Bool()
To add the functions, I have implemented a method (I will report a part of) :
...
//Build the sub-dictionaries
let path : [String:NSBezierPath] = ["path" : thePath];
let color : [String:NSColor] = ["color" : theColor];
let line : [String:CGFloat] = ["lineWidth" : theLine];
let visible : [String:Bool] = ["visible" : theVisibility];
//Note that I'm 100% sure that the relative values are compatible with the relative types.
//Therefore I'm pretty sure there is a syntax error.
//Add the element (note: theFunction is a string, and I want it to be the key of the `Dictionary`)
functions[theFunction] = [path, color, line, visible]; //Error here
...
I'm given the following error:
'#|value $T10' is not identical to '(String,[([String:NSbezierPath],[String : NSColor],[String : CGFloat],[String : Bool])])'
I hope the question was enough clear and complete.
In case I will immediately add any kind of information you will need.
Best regards and happy holidays.
Dictionaries map from a specific key type to a specific value type. For example, you could make your key type String and your value type Int.
In your case, you’ve declared quite a strange dictionary: a mapping from Strings (fair enough), to an array of 4-tuples of 4 different dictionary types (each one from strings to a different type).
(It’s a new one on me, but it looks like this:
var thingy = [String,String]()
is shorthand for this:
var thingy = [(String,String)]()
Huh. Strange but it works. Your dictionary is using a variant of this trick)
This means to make your assignment work you need to create an array of a 4-tuple (note additional brackets):
functions[theFunction] = [(path, color, line, visible)]
I’m guessing you didn’t mean to do this though. Did you actually want an array of these 4 different dictionary types? If so, you’re out of luck – you can’t store different types (dictionaries that have different types for their values) in the same array.
(Well, you could if you made the values of the dictionary Any – but that’s a terrible idea and would be nightmare to use)
Probably the result you wanted was this (i.e. make the functions dictionary map from a string to a 4-tuple of dictionaries of different types):
var functions = [String : ([String : NSBezierPath], [String : NSColor], [String : CGFloat], [String : Bool])]()
You’d assign values to the dictionary like this (note, no square brackets on the rhs):
functions[theFunction] = (path, color, line, visible)
This will work but it will be pretty unpleasant to work with. But do you really want to store your structured data in dictionaries and arrays? This isn’t JavaScript ;-) You’ll tie yourself in knots navigating that multi-level dictionary. Declare a struct! It’ll be so much easier to work with in your code.
struct Functions {
var beziers: [String:NSBezierPath]
var color: [String:NSColor]
var line: [String:NSColor]
var floats: [String:CGFloat]
var bools: [String:Bool]
}
var functions: [String:Functions] = [:]
Even better, if all the beziers, colors etc are supposed to be references with the same key, declare a dictionary that contains all of them or similar.
Say i have a long data structure definition
data A = A {
x1 :: String
, x2 :: String
...
, x50 :: String
}
Now i have 3 tasks:
create a draft instance of A like A { x1 = "this is x1", ... }
create an instance of A from some other data structure
create another data instance from an instance of A
The three tasks involve the tediuous copying of the lables x1, ..., x50.
A better solution would be a generic list
[
Foo "x1" aValue1
, Foo "x2" aValue2
...
]
because it would make traversal and creating a draft much easier (the list definition is the draft already). The downside is that mapping other data structures to and from this would be more dangerous, since you lose static type checking.
Does this make sense?
Is there a generic but safe solution?
Edit: To give you a better idea, it's about mapping business data to textual representation like forms and letters. E.g.:
data TaxData = TaxData {
taxId :: String
, income :: Money
, taxPayed :: Money,
, isMarried :: Bool
...
}
data TaxFormA = TaxFormA {
taxId :: Text
, isMarried :: Text
...
}
data TaxFormB = TaxFormB {
taxId :: Text
, taxPayedRounded :: Text
...
}
Those get transformed into a stream of text, representing the actual forms. If i would create a form from tax data in one pass and next year any form field would have moved, there would e.g. be a stray "0.0" and i would not know where it belongs. That's what the intermediate datat strcuture is for: it makes it easy to create draft data.
So i need to map the actual TaxData to those intermediate form data; i need to map those form data to the actual form textual representation; i need to create draft intermediate form data. On one hand i hate repeating those data labels, on the other hand it gives me saftey, that i don't confuse any label while mapping. Is there a silver bullet?
Deeply structured data like this is most idiomatically expressed in Haskell as nested, algebraic data types, as you have done. Why? It gives the most type structure and safety to the data, preventing functions from putting the data into the wrong format. Further safety can be gained by newtyping some of the types, to increase the differences between data in each field.
However, very large ADTs like this can be unwieldy to name and manipulate. A common situation in compiler design is specifying such a large ADT, for example, and to help write the code for a compiler we tend to use a lot of generic programming tricks: SYB, meta-programming, even Template Haskell, to generate all the boilerplate we need.
So, in summary, I'd keep the ADT approach you are taking, but look at using generics (e.g. SYB or Template Haskell) to generate some of your definitions and helper functions.