Does Elm's lambda syntax support destructuring? - elm

Can I write
\(a, b) -> ...
and expect a tuple parameter to be destructured?

Yes, you can...
For example, following code works.
import Html exposing (text)
main =
("Hello", "World!")
|> \(a,b) -> a ++ " " ++ b
|> text

Related

How to test an Elm module without exposing everything?

Working my way through "Elm in Action", I understand that to write tests, all functions and types needed in a test suite for some module must be exposed by that module. This seems to break encapsulation. I don't want to expose internal functions and type constructors that should remain hidden just to make them testable. Is there a way to expose internal functions an types for testing only, but not for regular use?
There are a few strategies to tackle this problem, each with its pluses and minuses. As a running example, let's make a module that models a simple store on the server, that we would like to test the internals of:
module FooService exposing (Foo, all, update)
import Http
import Json.Decode as Decode exposing (Decoder)
import Json.Encode as Encode exposing (Value)
type Id
= Id String
type alias Foo =
{ id : Id
, title : String
}
apiBase : String
apiBase =
"https://example.com/api/v2"
all : (Result Http.Error (List Foo) -> msg) -> Cmd msg
all tagger =
Http.get
{ url = apiBase ++ "/foos"
, expect = Http.expectJson tagger decodeMany
}
update : Foo -> (Result Http.Error Foo -> msg) -> Cmd msg
update foo tagger =
Http.post
{ url = apiBase ++ "/foos/" ++ idToString foo.id
, body = foo |> encode |> Http.jsonBody
, expect = Http.expectJson tagger decode
}
idToString : Id -> String
idToString (Id id_) =
id_
encode : Foo -> Value
encode foo =
Encode.object
[ ( "id", Encode.string (idToString foo.id) )
, ( "title", Encode.string foo.title )
]
decode : Decoder Foo
decode =
Decode.map2 Foo
(Decode.field "id" (Decode.map Id Decode.string))
(Decode.field "title" Decode.string)
decodeMany : Decoder (List Foo)
decodeMany =
Decode.field "values" (Decode.list decode)
Note that as is, the module is ideally encapsulated, but utterly untestable. Let's look at some strategies to alleviate this problem:
1. Tests within modules
elm-test is not actually that prescriptive about where you put your tests, as long as there is an exposed value with the Test type.
As such you can do something like this:
module FooService exposing (Foo, all, update, testSuite)
-- FooService remains exactly the same, but the following is added
import Test
import Fuzz
testSuite : Test
testSuite =
Test.describe "FooService internals"
[ Test.fuzz (Fuzz.map2 Fuzz (Fuzz.map Id Fuzz.string) Fuzz.string) "Encoding roundtrips"
\foo ->
encode foo
|> Decode.decodeValue decoder
|> Expect.equal (Ok foo)
-- more tests here
]
This can be quite nice in the sense that tests are also collocated with the functions they are testing. The downside is that modules can get quite large with all the test code in them. It also requires you to move elm-test from your test dependencies into the runtime dependencies. This should theoretically not have any runtime impact, since elm's dead code elimination is excellent, but it does leave a lot of developers a little nervous.
2. Internal Modules
Another option, which is heavily used within elm packages (since there is direct support for that kind of hiding in the built in elm.json) is to have modules that are considered internal to a certain module or library and no other modules should read from them. This can be enforced by convention or I believe there are elm-review rules on can use to enforce these boundaries.
In our example it would look something like this:
module FooService.Internal exposing (Foo, Id(..), encode, decode, decodeMany, idToString)
import Json.Decode as Decode exposing (Decoder)
import Json.Encode as Encode exposing (Value)
type Id
= Id String
type alias Foo =
{ id : Id
, title : String
}
idToString : Id -> String
idToString (Id id_) =
id_
encode : Foo -> Value
encode foo =
Encode.object
[ ( "id", Encode.string (idToString foo.id) )
, ( "title", Encode.string foo.title )
]
decode : Decoder Foo
decode =
Decode.map2 Foo
(Decode.field "id" (Decode.map Id Decode.string))
(Decode.field "title" Decode.string)
decodeMany : Decoder (List Foo)
decodeMany =
Decode.field "values" (Decode.list decode)
then FooService would simply become:
module FooService exposing (Foo, all, update)
import Http
import FooService.Internal as Internal
type alias Foo =
Internal.Foo
apiBase : String
apiBase =
"https://example.com/api/v2"
all : (Result Http.Error (List Foo) -> msg) -> Cmd msg
all tagger =
Http.get
{ url = apiBase ++ "/foos"
, expect = Http.expectJson tagger Internal.decodeMany
}
update : Foo -> (Result Http.Error Foo -> msg) -> Cmd msg
update foo tagger =
Http.post
{ url = apiBase ++ "/foos/" ++ Internal.idToString foo.id
, body = foo |> Internal.encode |> Http.jsonBody
, expect = Http.expectJson tagger Internal.decode
}
then all tests can be written against the internal module.
As I said, this is an extremely common pattern you will see in the majority of published elm packages, but in applications it suffers a bit from the fact that the tooling support isn't quite as good. For instance autocomplete will offer you these internal functions even in modules that shouldn't have access to them.
Nonetheless, we use this pattern quite successfully at work.
3. Change the design
Perhaps if a module isn't testable, then it is doing too much. One can look into things like the effect pattern to change the design to be more testable. For instance, one could argue that performing HTTP requests is outside the core competency of dealing with Foos, and the boundary should be at the decoder/encoder stage, which would make it quite testable; then a central module would deal with Http communication centrally.
We've been looking in this direction for a bit, but haven't found a good way to make it nice with really complex server interactions, but it might be something worth thinking about in each individual case: why is this module not testable? Would an alternative design be just as good and also be testable?

Decode JSON where value can be a string or an array of different values

Yet another question on how to decode things with Elm...
So the problem is that I need to decode a value which can be either a string, for example
"price_unknown"
or it can be an array of 2 elements, where first one is a string and the second is a float:
["price", 50.5]
And for the eventual value I have a type:
type Something
= PriceUnknown
= Price Float
into which I need to convert the value from the json response.
I tried to use a bunch of things using somewhat between the lines of:
decode MyString
|> required "other_value" Json.Decode.string
|> required "price" JD.oneOf [JD.string, JD.list (JD.oneOf [JD.string, JD.float])] |> JD.map mapper
( I use json_decode_pipeline package here )
But obviously it complains about different values in the lists and whatnot so I am stuck.
Thank you in advance.
You are pretty close, but all of the Decoders in oneOf have to have the same type. Also, destructuring mixed lists can be a pain. This uses elm-community/json-extra to make a manual decoding step easier.
myDecoder : Decoder SomethingElse
myDecoder =
decode MyString
|> required "other_value" Json.Decode.string
|> required "price" priceDecoder
priceDecoder : Decoder Something
priceDecoder =
JD.oneOf
[ priceUnknownDecoder
, priceKnownDecoder
]
priceUnknownDecoder : Decoder Something
priceUnknownDecoder =
JD.string
|> JD.andThen
(\string ->
if string == "price_unknown" then
JD.succeed PriceUnknown
else
JD.fail "Invalid unknown price string."
)
priceKnownDecoder : Decoder Something
priceKnownDecoder =
listTupleDecoder
JD.string
JD.float
|> JD.andThen
(\(tag, price) ->
if tag == "price" then
JD.succeed (Price price)
else
JD.fail "Invalid tag string."
)
listTupleDecoder : Decoder a -> Decoder b -> Decoder (a, b)
listTupleDecoder firstD secondD =
JD.list JD.value
|> JD.andThen
(\values ->
case values of
[first, second] ->
Result.map2
(,)
JD.decodeValue firstD first
JD.decodeValue secondD second
|> JD.Extra.fromResult
_ ->
JD.fail "There aren't two values in the list."
)

What is a good way to internationalize an Elm application?

I need to internationalize the UI strings in my ELM HTML application to 3 different languages.
I am thinking of doing this:
1) I will get the currentLanguage from Javascript, and pass it down in the ProgramWithFlags. I'llkeep the language in the model
2) I'll setup some types in my code
type alias Languages = English | French | Spanish
-- One of these for each string I want to internationalize
type alias InternationalizedStrings = StringHello | StringFoo | StringBar
3) I'll make a function for returning each translated phrase to use in my Views.
getPhrase: InternationalizationString Languages -> string
getPhrase stringId lang =
case lang of
English ->
case stringId of
StringHello -> "Hello"
StringFoo -> "Foo"
StringBar -> "Bar"
French ->
case stringId of
StringHello -> "Bonjour"
StringFoo -> "Oui"
StringBar -> "Non"
...
Is there a better way to do this? I have lots of string.
In case you want compiler errors when you don't provide the translation for a string your solution is on the right track.
If you want to either allow yet untranslated strings or find it tedious to have a type for every translatable string, you might want to switch to a Dict-based solution. to tinker with it, just throw it into http://elm-lang.org/try:
import Dict exposing (Dict)
import Html exposing (text)
type Language
= English
| French
| Spanish
type alias Key =
String
main =
text <| translate French "Hello"
translate : Language -> Key -> String
translate lang key =
let
dict =
case lang of
English ->
Dict.fromList
[ ( "Hello", "in english" )
]
French ->
Dict.fromList
[ ( "Hello", "salut" )
]
Spanish ->
Dict.fromList
[ ( "Hello", "hola" )
, ( "someKeyThatOnlyExistsInSpanish", "42" )
]
in
Dict.get key dict |> Maybe.withDefault ("can not find translation for " ++ key)
A while ago, I had a crack at internationalisation, and came up with the following setup:
define the language in a global model
have a very simple function, to be used in view modules and functions
the function has a signature of localString : Language -> String -> String
localString basically does a lookup in a global dictionary to find a translation from the word you provide to the language you provide.
it will always give back a String, defaulting to the original word, if it cannot find the word you provide, or if it cannot find the translation to the language you provide.
keep the global dictionary (and helper) functions NOT in the model, but in a separate file (it is pretty static data, which won't change in runtime).
the Language type is a Union Type, to ensure we only have 'approved' languages.
the actual dictionary uses conversions to string. The Dict type does not allow strong types as the key.
That way, using internationalisation has minimal impact on the rest of the code:
You need to add a Language to your Model (which you could get through JS port)
You can still use short and readable code in your views to translate, like
p [] [ text <| localString model.language "car" ]
All hardcoded strings in your own code remain in one simple default language, to keep the rest of your code readable.
Here is the gist of what I was working on, you can copy/ paste to elm-lang.org/try (not fully tested functionally or performance-wise with large numbers of strings and translations)
import Html exposing (div, p, text)
import Dict exposing (Dict)
-- Manage your languages below
type Language = English | Spanish | French
defaultLanguage : Language
defaultLanguage = English
languageToKey : Language -> LanguageKey
languageToKey language =
case language of
English -> "English"
Spanish -> "Spanish"
French -> "French"
keyToLanguage : LanguageKey -> Language
keyToLanguage key =
case key of
"English" -> English
"Spanish"-> Spanish
"French" -> French
_ -> defaultLanguage
english : LocalWord -> (Language, LocalWord)
english word =
(English, word)
spanish : LocalWord -> (Language, LocalWord)
spanish word =
(Spanish, word)
french : LocalWord -> (Language, LocalWord)
french word =
(French, word)
-- Internal stuff
type alias Word = String
type alias LocalWord = String
type alias LanguageKey = String
type alias Dictionary = Dict Word WordDict
type alias WordDict = Dict LanguageKey LocalWord
init : Dictionary
init =
Dict.fromList []
newLocalWord : Word -> (Language, LocalWord) -> Maybe WordDict -> Maybe WordDict
newLocalWord word (localLanguage, localWord) wordDict =
wordDict
|> Maybe.withDefault (Dict.fromList [])
|> Dict.insert (languageToKey defaultLanguage) word
|> Dict.insert (languageToKey localLanguage) localWord
|> Just
addTranslation : Word -> (Language, LocalWord) -> Dictionary -> Dictionary
addTranslation word newTranslation dictionary =
dictionary
|> Dict.update word (newLocalWord word newTranslation)
localString : Language -> Word -> LocalWord
localString language word =
let
wordEntry =
Dict.get word globalDictionary
localLanguage =
languageToKey language
in
case wordEntry of
Just wordDict ->
Dict.get localLanguage wordDict
|> Maybe.withDefault word
Nothing ->
word
add : Word -> List (Language, LocalWord) -> Dictionary -> Dictionary
add word translationList dictionary =
List.foldl (addTranslation word) dictionary translationList
-- BUILD DICTIONARY BELOW
globalDictionary : Dictionary
globalDictionary =
init
|> add "Hello" [ spanish "Hola", french "Bonjour" ]
|> add "Man" [ spanish "Hombre", french "Homme" ]
|> add "Child" [ french "Enfant" ]
-- For Elm-lang Try only
localModel =
{ language = Spanish }
main =
div []
[ p []
[ text <| "Hello in Spanish: "
++ localString localModel.language "Hello"
]
, p []
[ text <| "In dictionary, but not in Spanish: "
++ localString localModel.language "Child"
]
, p []
[ text <| "Is not in dictionary: "
++ localString localModel.language "Car"
]
]
I wrote a blog post about this a couple months ago. If you have the ability, try to prefer using ADTs over Dicts since Dicts can't give you the same guarantees at a type level (which is why Dict.get returns Maybe a). ADTs can also have the data type you're acting on type checked as well MyPhrase Int String that you can pattern match on and use whatever toString method you'd like (e.g. MyPhrase foo bar -> "My phrase contains " ++ toString foo ++ " & " ++ bar ++ "."). That being said, existing systems/translation services might make it difficult to use this method without writing a parser from .elm to .json or .po.

How to force evaluation of value in Debug.log

Using Elm 0.17, I'd sometimes like to see the steps taken during a computation, such as:
let
names = Debug.log "accounts"
List.map (\x -> x.name) accounts
sortedNames = Debug.log "sorted accounts"
List.sortBy String.toLower names
options =
List.map (viewAccountOption selectedName) sortedNames
in
[ viewEmptyOption ] ++ options
This logs the following:
accounts: <function>
sorted accounts: <function:sortBy>
I understand Elm is lazy, and the thunks will be evaluated when the values are actually needed.
I couldn't find a force or strict function in elm-core Basics, or anywhere else in the package.
Is there a way to force evaluation of a value?
You can do that by either using parenthesis:
Debug.log "accounts"
(List.map (\x -> x.name) accounts)
Or using the <| operator
Debug.log "accounts" <|
List.map (\x -> x.name) accounts
EDIT:
The reason is that Debug.log was evaluated with List.map (a function) as the second argument and then returned that argument to be composed with the rest of the line. You just needed to hint elm a bit on what was your intended argument precedence

In Elm what is the correct way to implement my own toString

In Elm what is the correct way to take my Model and implement a toString function?
The type I am looking for would be toString : Model -> String, I am able to make a similar function with the type of toStr : Model -> String but I would think I would want the function to be called toString.
Example program (the Coin Changer kata):
module CoinChanger where
import Html exposing (..)
import StartApp.Simple as StartApp
import Signal exposing (Address)
import Html.Attributes exposing (..)
import Html.Events exposing (on, targetValue)
import String
---- MAIN ----
main =
StartApp.start
{
model = emptyModel
,update = update
,view = view
}
---- Model ----
type alias Model =
{
change : List Int
}
emptyModel : Model
emptyModel =
{
change = []
}
---- VIEW ----
toStr : Model -> String
toStr model =
model.change
|> List.map (\coin -> (toString coin) ++ "¢")
|> String.join ", "
view : Address String -> Model -> Html
view address model =
div []
[
input
[
placeholder "amount to make change for"
, on "input" targetValue (Signal.message address)
, autofocus True
-- style
]
[]
, div []
[
text (toStr model)
]
]
---- UPDATE ----
changeFor : Int -> List Int
changeFor amount =
[ 25, 10, 5, 1 ]
|> List.foldl
(\coin (change, amount)
-> ( change ++ List.repeat (amount // coin) coin
, amount % coin)
)
([], amount)
|> fst
update : String -> Model -> Model
update change model =
{ model | change =
case String.toInt change of
Ok amount
-> changeFor amount
Err msg
-> []
}
I would think the correct way to do this would be to call the function toString, but that gives me the following error from the compiler:
Detected errors in 1 module.
-- TYPE MISMATCH ----------------------------------------------- CoinChanger.elm
The type annotation for toString does not match its definition.
42│ toString : Model -> String
^^^^^^^^^^^^^^^ The type annotation is saying:
{ change : List Int } -> String
But I am inferring that the definition has this type:
{ change : List { change : List Int } } -> String
Renaming the function to toStr (or something not called toString) fixes the issue but seems wrong. What is the correct way to do this?
The problem is that, calling your function toString, you are overriding the toString function of the Basics module, which you are using at line 45.
To avoid this, you'll need to import the Basics module and use Basics.toString instead of simply toString to eliminare the ambiguity
The accepted answer is well out of date for anyone writing Elm 0.19+. The current solution is to write your own toString function for the type you want converted. There is a Debug.toString for use during development but its use in your code will prevent building for production.